diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index 9f3cc4188a..5ab246b134 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -200,7 +200,7 @@ When writing documentation that includes Java or Python code examples:
- **Style & Formatting**: Java code follows Google style with project overrides from `.config/checkstyle_neqsim.xml` and formatter profiles (`.config/neqsim_formatter.xml`); keep indentation at two spaces and respect existing comment minimalism.
- **Code Formatting (Spotless) - MANDATORY**: AI-generated Java is NOT auto-formatted. After creating or editing ANY `.java` file, run `./mvnw spotless:apply` (Windows: `mvnw.cmd spotless:apply`) to reformat to the project style, then `git add` the changes before committing. CI runs `./mvnw spotless:check` and FAILS the build on any unformatted file. Do not rely on local pre-commit hooks being installed, and NEVER bypass the gate with `git commit --no-verify`.
- **Serialization & Copying**: Many equipment classes rely on Java serialization (`ProcessEquipmentBaseClass.copy()`); avoid introducing non-serializable fields or mark them `transient` to preserve cloning. SpotBugs enforces this via the SE_BAD_FIELD rule. When adding fields to any `Serializable` class (equipment, measurement devices, mechanical design, thermo phases), use the correct modifier order: `private transient Type field;` or `private final transient Type field;`. Common non-serializable types that need `transient`: `Function`, `BiConsumer`, `Consumer`, `Thread`, JDBC `Connection`/`Statement`, Apache Commons Math interpolators, and any inner class that doesn't implement `Serializable`. The `ProcessLogic` interface extends `Serializable`.
-- **External Dependencies**: Core math depends on EJML, Commons Math, JAMA, and MTJ; check numerical stability when swapping linear algebra routines, and keep JSON/YAML handling aligned with gson/jackson versions pinned in pom.xml.
+- **External Dependencies**: Core math depends on ojAlgo, Commons Math, JAMA, and MTJ; check numerical stability when swapping linear algebra routines, and keep JSON/YAML handling aligned with gson/jackson versions pinned in pom.xml.
- **Java 8 Compatibility (MANDATORY)**: See the critical section at the top of this document. All code MUST compile with Java 8. The CI build will FAIL if you use Java 9+ features like `String.repeat()`, `var`, `List.of()`, etc.
- **Sample Flow**:
diff --git a/.github/workflows/publish_to_maven_central.yml b/.github/workflows/publish_to_maven_central.yml
index c5d08552d6..d69edfef41 100644
--- a/.github/workflows/publish_to_maven_central.yml
+++ b/.github/workflows/publish_to_maven_central.yml
@@ -40,11 +40,6 @@ jobs:
gpg-private-key: ${{ secrets.GPG_SIGNING_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
cache: 'maven'
- # Clean Maven cache for critical dependencies to avoid stale class issues
- - name: Clean dependency cache for EJML
- run: |
- rm -rf ~/.m2/repository/org/ejml
- echo "Cleaned EJML from Maven cache to ensure fresh download"
# --- Java 8 artifact ---
- name: Publish package (Java 8)
run: ./mvnw -f pomJava8.xml -P release --batch-mode clean deploy -Dmaven.deploy.skip=true -DskipTests -Djacoco.skip=true -Drevision=${{ steps.release_version.outputs.version }} -ntp
diff --git a/pom.xml b/pom.xml
index 57053774ba..e9cdbbdc33 100644
--- a/pom.xml
+++ b/pom.xml
@@ -102,9 +102,9 @@
@@ -32,34 +33,36 @@ public class ChemicalEquilibrium implements java.io.Serializable {
private static final int STAGNATION_LIMIT = 10;
/**
- * Iteration threshold to switch from simple to derivative-based iterations. First iterations use simple M_matrix
- * (cheap), then switch to derivatives for faster quadratic convergence near the solution.
+ * Iteration threshold to switch from simple to derivative-based iterations. First iterations use
+ * simple M_matrix (cheap), then switch to derivatives for faster quadratic convergence near the
+ * solution.
*/
private static final int DERIVATIVE_SWITCH_ITERATION = 5;
/**
- * Error threshold to switch to derivative-based iterations. When relative error falls below this, use derivatives for
- * faster convergence.
+ * Error threshold to switch to derivative-based iterations. When relative error falls below this,
+ * use derivatives for faster convergence.
*/
private static final double DERIVATIVE_SWITCH_ERROR = 1e-3;
/**
- * Flag to enable fugacity coefficient derivatives in M_matrix. When true, uses init(3) for derivative calculations
- * and includes dln(fugacity)/dN terms for more accurate Newton steps. Default is false for backward compatibility and
- * performance.
+ * Flag to enable fugacity coefficient derivatives in M_matrix. When true, uses init(3) for
+ * derivative calculations and includes dln(fugacity)/dN terms for more accurate Newton steps.
+ * Default is false for backward compatibility and performance.
*/
private boolean useFugacityDerivatives = false;
/**
- * Flag to enable automatic switching to derivatives after initial iterations. When true, starts with simple
- * iterations then switches to derivatives for faster convergence.
+ * Flag to enable automatic switching to derivatives after initial iterations. When true, starts
+ * with simple iterations then switches to derivatives for faster convergence.
*/
private boolean useAdaptiveDerivatives = false;
/**
- * Flag to enable the full Smith-Missen M-matrix with the -1/n_t coupling term. When true, uses M_ij = δ_ij/n_i -
- * 1/n_t instead of the simplified M_ij = δ_ij/n_i. The full form provides better quadratic convergence near the
- * solution but may be less stable for some systems. Default is false for backward compatibility and stability.
+ * Flag to enable the full Smith-Missen M-matrix with the -1/n_t coupling term. When true, uses
+ * M_ij = δ_ij/n_i - 1/n_t instead of the simplified M_ij = δ_ij/n_i. The full form provides
+ * better quadratic convergence near the solution but may be less stable for some systems. Default
+ * is false for backward compatibility and stability.
*/
private boolean useFullMMatrix = false;
@@ -160,8 +163,8 @@ public ChemicalEquilibrium(double[][] A_matrix, double[] b_element, SystemInterf
for (int i = 0; i < components.length; i++) {
if (components[i].getComponentName().equals("water")) {
- waterNumb = i;
- break;
+ waterNumb = i;
+ break;
}
}
system.init(1, phasenumb);
@@ -179,13 +182,14 @@ public ChemicalEquilibrium(double[][] A_matrix, double[] b_element, SystemInterf
public void calcRefPot() {
for (int i = 0; i < components.length; i++) {
// calculates the reduced chemical potential mu/RT
- this.chem_ref[i] = components[i].getReferencePotential() / (R * system.getPhase(phasenumb).getTemperature());
+ this.chem_ref[i] =
+ components[i].getReferencePotential() / (R * system.getPhase(phasenumb).getTemperature());
logactivityVec[i] = 0.0;
if (components[i].calcActivity()) {
- logactivityVec[i] = system.getPhase(phasenumb).getLogActivityCoefficient(components[i].getComponentNumber(),
- components[waterNumb].getComponentNumber());
- // System.out.println("activity " + Math.exp(logactivityVec[i]) + " " +
- // components[i].getComponentName());
+ logactivityVec[i] = system.getPhase(phasenumb).getLogActivityCoefficient(
+ components[i].getComponentNumber(), components[waterNumb].getComponentNumber());
+ // System.out.println("activity " + Math.exp(logactivityVec[i]) + " " +
+ // components[i].getComponentName());
}
}
}
@@ -202,62 +206,63 @@ public void chemSolve() {
// If using fugacity derivatives, need init(3) for derivative calculations
if (useFugacityDerivatives) {
try {
- system.init(3, phasenumb);
+ system.init(3, phasenumb);
} catch (Exception ex) {
- logger.debug("Failed to init(3) for derivatives, falling back to simple M_matrix");
+ logger.debug("Failed to init(3) for derivatives, falling back to simple M_matrix");
}
}
for (int i = 0; i < NSPEC; i++) {
n_mol[i] = system.getPhase(phasenumb).getComponents()[components[i].getComponentNumber()]
- .getNumberOfMolesInPhase();
+ .getNumberOfMolesInPhase();
for (int k = 0; k < NSPEC; k++) {
- if (k == i) {
- kronDelt = 1.0;
- } else {
- kronDelt = 0.0;
- }
- // M_matrix definition: M_ij = δ_ij/n_i for the simplified ideal case
- // The full Smith-Missen formulation uses M_ij = δ_ij/n_i - 1/n_t
- // The -1/n_t term couples all species through total moles and improves convergence
- // near the solution, but may be less stable for some systems.
- // Protect against division by zero using MIN_MOLES
- double molesForDiv = Math.max(MIN_MOLES,
- system.getPhase(phasenumb).getComponents()[components[i].getComponentNumber()].getNumberOfMolesInPhase());
- M_matrix[i][k] = kronDelt / molesForDiv;
-
- // Add the -1/n_t coupling term if full M-matrix is enabled
- if (useFullMMatrix) {
- M_matrix[i][k] -= 1.0 / Math.max(MIN_MOLES, n_t);
- }
-
- // Add fugacity coefficient derivative if enabled
- // dfugdn contains: δ_ij/n_i - 1/n_t + ∂ln(φ_i)/∂n_j
- // The original M_matrix is just δ_ij/n_i (ideal, no mixing term)
- // To add non-ideal effects, we need ∂ln(φ_i)/∂n_j = dfugdn - δ_ij/n_i + 1/n_t
- if (useFugacityDerivatives) {
- try {
- int compNumI = components[i].getComponentNumber();
- int compNumK = components[k].getComponentNumber();
- double dfugdN = system.getPhase(phasenumb).getComponent(compNumI).getdfugdn(compNumK);
- if (!Double.isNaN(dfugdN) && !Double.isInfinite(dfugdN)) {
- // Extract just the non-ideal part: ∂ln(φ)/∂n = dfugdn - δ/n + 1/n_t
- double idealPart = (i == k ? 1.0 / molesForDiv : 0.0) - 1.0 / n_t;
- double nonIdealPart = dfugdN - idealPart;
- M_matrix[i][k] += nonIdealPart;
- }
- } catch (Exception ex) {
- // Derivative not available, use ideal term only
- }
- }
-
- // System.out.println("dfugdn "
- // +system.getPhase(phasenumb).getComponent(i).logfugcoefdNi(this.system.getPhase(phasenumb),
- // i));
- // if (i == k) System.out.println("n "
- // +system.getPhase(phasenumb).getComponents()[components[i].getComponentNumber()].getNumberOfMolesInPhase()
- // );
+ if (k == i) {
+ kronDelt = 1.0;
+ } else {
+ kronDelt = 0.0;
+ }
+ // M_matrix definition: M_ij = δ_ij/n_i for the simplified ideal case
+ // The full Smith-Missen formulation uses M_ij = δ_ij/n_i - 1/n_t
+ // The -1/n_t term couples all species through total moles and improves convergence
+ // near the solution, but may be less stable for some systems.
+ // Protect against division by zero using MIN_MOLES
+ double molesForDiv = Math.max(MIN_MOLES,
+ system.getPhase(phasenumb).getComponents()[components[i].getComponentNumber()]
+ .getNumberOfMolesInPhase());
+ M_matrix[i][k] = kronDelt / molesForDiv;
+
+ // Add the -1/n_t coupling term if full M-matrix is enabled
+ if (useFullMMatrix) {
+ M_matrix[i][k] -= 1.0 / Math.max(MIN_MOLES, n_t);
+ }
+
+ // Add fugacity coefficient derivative if enabled
+ // dfugdn contains: δ_ij/n_i - 1/n_t + ∂ln(φ_i)/∂n_j
+ // The original M_matrix is just δ_ij/n_i (ideal, no mixing term)
+ // To add non-ideal effects, we need ∂ln(φ_i)/∂n_j = dfugdn - δ_ij/n_i + 1/n_t
+ if (useFugacityDerivatives) {
+ try {
+ int compNumI = components[i].getComponentNumber();
+ int compNumK = components[k].getComponentNumber();
+ double dfugdN = system.getPhase(phasenumb).getComponent(compNumI).getdfugdn(compNumK);
+ if (!Double.isNaN(dfugdN) && !Double.isInfinite(dfugdN)) {
+ // Extract just the non-ideal part: ∂ln(φ)/∂n = dfugdn - δ/n + 1/n_t
+ double idealPart = (i == k ? 1.0 / molesForDiv : 0.0) - 1.0 / n_t;
+ double nonIdealPart = dfugdN - idealPart;
+ M_matrix[i][k] += nonIdealPart;
+ }
+ } catch (Exception ex) {
+ // Derivative not available, use ideal term only
+ }
+ }
+
+ // System.out.println("dfugdn "
+ // +system.getPhase(phasenumb).getComponent(i).logfugcoefdNi(this.system.getPhase(phasenumb),
+ // i));
+ // if (i == k) System.out.println("n "
+ // +system.getPhase(phasenumb).getComponents()[components[i].getComponentNumber()].getNumberOfMolesInPhase()
+ // );
}
}
// printComp();
@@ -280,7 +285,8 @@ public void chemSolve() {
// calculates the reduced chemical potential mu/RT
// Protect against log(0) by ensuring minimum moles
double molesInPhase = Math.max(MIN_MOLES,
- system.getPhase(phasenumb).getComponents()[components[i].getComponentNumber()].getNumberOfMolesInPhase());
+ system.getPhase(phasenumb).getComponents()[components[i].getComponentNumber()]
+ .getNumberOfMolesInPhase());
chem_pot[i] = chem_ref[i] + Math.log(molesInPhase) - Math.log(n_t) + logactivity;
// System.out.println("chem ref pot " + chem_pot[i]);
}
@@ -293,7 +299,8 @@ public void chemSolve() {
try {
M_inv_AT = M_Jama_matrix.solve(A_Jama_matrix.transpose());
} catch (Exception e) {
- M_inv_AT = solveLeastSquares(M_Jama_matrix, A_Jama_matrix.transpose());
+ M_inv_AT = new Matrix(LinearAlgebraOps.solveLeastSquares(M_Jama_matrix.getArrayCopy(),
+ A_Jama_matrix.transpose().getArrayCopy()));
}
AMA_matrix = A_Jama_matrix.times(M_inv_AT);
// Similarly for AMU: M * Y = mu^T -> Y = M.solve(mu^T), then AMU = A * Y
@@ -301,7 +308,8 @@ public void chemSolve() {
try {
M_inv_mu = M_Jama_matrix.solve(chem_pot_Jama_Matrix.transpose());
} catch (Exception e) {
- M_inv_mu = solveLeastSquares(M_Jama_matrix, chem_pot_Jama_Matrix.transpose());
+ M_inv_mu = new Matrix(LinearAlgebraOps.solveLeastSquares(M_Jama_matrix.getArrayCopy(),
+ chem_pot_Jama_Matrix.transpose().getArrayCopy()));
}
AMU_matrix = A_Jama_matrix.times(M_inv_mu);
Matrix nmol = new Matrix(n_mol, 1);
@@ -351,23 +359,25 @@ public void chemSolve() {
// Check if solution is valid (no NaN or Inf)
boolean validSolution = true;
for (int i = 0; i <= NELE && validSolution; i++) {
- double val = x_solve.get(i, 0);
- if (Double.isNaN(val) || Double.isInfinite(val)) {
- validSolution = false;
- }
+ double val = x_solve.get(i, 0);
+ if (Double.isNaN(val) || Double.isInfinite(val)) {
+ validSolution = false;
+ }
}
if (!validSolution) {
- // Try pseudo-inverse for numerically unstable cases
- x_solve = solveLeastSquares(A_solve, b_solve);
+ // Try pseudo-inverse for numerically unstable cases
+ x_solve = new Matrix(
+ LinearAlgebraOps.solveLeastSquares(A_solve.getArrayCopy(), b_solve.getArrayCopy()));
}
} catch (Exception ex) {
// Matrix is singular or near-singular, use pseudo-inverse
try {
- x_solve = solveLeastSquares(A_solve, b_solve);
+ x_solve = new Matrix(
+ LinearAlgebraOps.solveLeastSquares(A_solve.getArrayCopy(), b_solve.getArrayCopy()));
} catch (Exception ex2) {
- logger.error("Both regular and least-squares solve failed: " + ex2.getMessage());
- x_solve = new Matrix(NELE + 1, 1); // Zero solution as fallback
+ logger.error("Both regular and least-squares solve failed: " + ex2.getMessage());
+ x_solve = new Matrix(NELE + 1, 1); // Zero solution as fallback
}
}
// d_n_t = x_solve.get(NELE,0)*n_t;
@@ -380,7 +390,8 @@ public void chemSolve() {
try {
M_inv_rhs = M_Jama_matrix.solve(rhs);
} catch (Exception e) {
- M_inv_rhs = solveLeastSquares(M_Jama_matrix, rhs);
+ M_inv_rhs = new Matrix(
+ LinearAlgebraOps.solveLeastSquares(M_Jama_matrix.getArrayCopy(), rhs.getArrayCopy()));
}
dn_matrix = M_inv_rhs.plus(new Matrix(n_mol, 1).transpose().times(x_solve.get(NELE, 0)));
d_n = dn_matrix.transpose().getArray()[0];
@@ -392,23 +403,24 @@ public void chemSolve() {
*
- * Updates the moles in the reactive phase based on the calculated n_mol values from the Newton solver. Uses
- * Phase.addMolesChemReac with totdn=0 to only affect phase moles without corrupting the total system moles (which
- * would violate element conservation).
+ * Updates the moles in the reactive phase based on the calculated n_mol values from the Newton
+ * solver. Uses Phase.addMolesChemReac with totdn=0 to only affect phase moles without corrupting
+ * the total system moles (which would violate element conservation).
*
- * When the chemical equilibrium solver fails to converge, ionic species like H3O+ and OH- can be left at
- * unrealistically low values. This method checks the water auto-ionization equilibrium (Kw = [H3O+][OH-]) and
- * corrects unrealistic values while respecting legitimate acidic or alkaline conditions.
+ * When the chemical equilibrium solver fails to converge, ionic species like H3O+ and OH- can be
+ * left at unrealistically low values. This method checks the water auto-ionization equilibrium
+ * (Kw = [H3O+][OH-]) and corrects unrealistic values while respecting legitimate acidic or
+ * alkaline conditions.
*
- * The method only intervenes when both H3O+ and OH- are unrealistically low (violating Kw), not when the solution is
- * legitimately acidic (high H3O+, low OH-) or alkaline (low H3O+, high OH-).
+ * The method only intervenes when both H3O+ and OH- are unrealistically low (violating Kw), not
+ * when the solution is legitimately acidic (high H3O+, low OH-) or alkaline (low H3O+, high OH-).
*
- * When enabled, the solver uses init(3, phase) to calculate fugacity derivatives and includes dln(fugacity)/dN terms
- * in the M_matrix for more accurate Newton steps. This can improve convergence for non-ideal mixtures but is
- * computationally more expensive.
+ * When enabled, the solver uses init(3, phase) to calculate fugacity derivatives and includes
+ * dln(fugacity)/dN terms in the M_matrix for more accurate Newton steps. This can improve
+ * convergence for non-ideal mixtures but is computationally more expensive.
*
- * When enabled, the solver starts with simple iterations (without fugacity derivatives) for the first few iterations,
- * then automatically switches to derivative-based iterations for faster quadratic convergence near the solution. This
- * combines the robustness of simple iterations with the speed of derivative-based methods.
+ * When enabled, the solver starts with simple iterations (without fugacity derivatives) for the
+ * first few iterations, then automatically switches to derivative-based iterations for faster
+ * quadratic convergence near the solution. This combines the robustness of simple iterations with
+ * the speed of derivative-based methods.
*
- * The switch occurs after {@code DERIVATIVE_SWITCH_ITERATION} iterations or when the error falls below
- * {@code DERIVATIVE_SWITCH_ERROR}.
+ * The switch occurs after {@code DERIVATIVE_SWITCH_ITERATION} iterations or when the error falls
+ * below {@code DERIVATIVE_SWITCH_ERROR}.
*
- * When enabled, the M-matrix uses the full form M_ij = δ_ij/n_i - 1/n_t instead of the simplified form M_ij =
- * δ_ij/n_i. The -1/n_t term couples all species through total moles and can improve quadratic convergence near the
- * solution, but may be less stable for some systems.
+ * When enabled, the M-matrix uses the full form M_ij = δ_ij/n_i - 1/n_t instead of the simplified
+ * form M_ij = δ_ij/n_i. The -1/n_t term couples all species through total moles and can improve
+ * quadratic convergence near the solution, but may be less stable for some systems.
*
- * For rank-deficient or ill-conditioned matrices, this provides a more robust solution than direct inversion. Uses
- * SVD decomposition: A = U * S * V^T, then x = V * S^(-1) * U^T * b.
- *
* GTSurfaceTensionODE class.
*
- * ODE-system for integrating the surface tension in cases where the a reference component number mole density can be
- * used as integration variable.
+ *
+ * ODE-system for integrating the surface tension in cases where the a reference component number
+ * mole density can be used as integration variable.
*
- * This method can only be used when the reference component density varies monotonically over the interface, and where
- * there are no binary interaction parameters for the attractive parameter in the EOS.
+ * This method can only be used when the reference component density varies monotonically over the
+ * interface, and where there are no binary interaction parameters for the attractive parameter in
+ * the EOS.
*
- * The controller supports both single-input operation and multivariable configurations with linear quality constraints.
- * A first-order discrete process model is used internally to predict the future trajectory of the controlled variable
- * across a configurable prediction horizon. The controller minimises a quadratic objective consisting of tracking
- * error, absolute control effort and control movement. The optimal actuation is calculated analytically which keeps the
+ * The controller supports both single-input operation and multivariable configurations with linear
+ * quality constraints. A first-order discrete process model is used internally to predict the
+ * future trajectory of the controlled variable across a configurable prediction horizon. The
+ * controller minimises a quadratic objective consisting of tracking error, absolute control effort
+ * and control movement. The optimal actuation is calculated analytically which keeps the
* implementation dependency free while still representing a full MPC formulation.
*
- * In addition to the control formulation the implementation exposes a receding horizon (moving horizon) estimation
- * routine. The estimator reuses the same first-order model to identify the process gain, time constant and bias from
- * historical measurement and actuation data. This allows automatic tuning of the internal model parameters when
- * operating on real process data without requiring external optimisation packages.
+ * In addition to the control formulation the implementation exposes a receding horizon (moving
+ * horizon) estimation routine. The estimator reuses the same first-order model to identify the
+ * process gain, time constant and bias from historical measurement and actuation data. This allows
+ * automatic tuning of the internal model parameters when operating on real process data without
+ * requiring external optimisation packages.
*
- * The column is solved using a sequential substitution approach. The {@link #init()} method sets initial tray
- * temperatures by running the feed tray and linearly distributing temperatures towards the top and bottom. During
- * {@link #run(UUID)} the trays are iteratively solved in upward and downward sweeps until the summed temperature change
- * between iterations is below the configured {@link #temperatureTolerance} or the iteration limit is reached.
+ * The column is solved using a sequential substitution approach. The {@link #init()} method sets
+ * initial tray temperatures by running the feed tray and linearly distributing temperatures towards
+ * the top and bottom. During {@link #run(UUID)} the trays are iteratively solved in upward and
+ * downward sweeps until the summed temperature change between iterations is below the configured
+ * {@link #temperatureTolerance} or the iteration limit is reached.
*
- * The column uses this specification as a tear variable by adjusting the corresponding tray side-draw fraction until
- * the withdrawn stream flow matches the target flow.
+ * The column uses this specification as a tear variable by adjusting the corresponding tray
+ * side-draw fraction until the withdrawn stream flow matches the target flow.
*
- * Add a feed stream to the specified tray. (Now allows multiple streams on the same trayNumber, using a list.)
+ * Add a feed stream to the specified tray. (Now allows multiple streams on the same trayNumber,
+ * using a list.)
*
- * The feed tray is estimated automatically when the column is run. The estimate uses an existing tray temperature
- * profile when available, otherwise it builds a simple temperature profile from configured condenser/reboiler
- * temperatures and the feed temperature. This is a robust initial placement heuristic, not a guarantee of global
- * optimum or convergence for every specification.
+ * The feed tray is estimated automatically when the column is run. The estimate uses an existing
+ * tray temperature profile when available, otherwise it builds a simple temperature profile from
+ * configured condenser/reboiler temperatures and the feed temperature. This is a robust initial
+ * placement heuristic, not a guarantee of global optimum or convergence for every specification.
*
- * This method does not connect the feed stream to the column. It is intended for diagnostics and for checking
- * automatic feed placement before calling {@link #run()}.
+ * This method does not connect the feed stream to the column. It is intended for diagnostics and
+ * for checking automatic feed placement before calling {@link #run()}.
*
- * The lookup first compares stream object identity and then falls back to the stream name. Feed streams added with
- * {@link #addFeedStream(StreamInterface)} are assigned when the column is run.
+ * The lookup first compares stream object identity and then falls back to the stream name. Feed
+ * streams added with {@link #addFeedStream(StreamInterface)} are assigned when the column is run.
*
- * The feed tray is solved first to obtain a temperature estimate. This temperature is then used to linearly guess
- * temperatures upwards to the condenser and downwards to the reboiler. Gas and liquid outlet streams are connected to
- * neighbouring trays so that a subsequent call to {@link #run(UUID)} can iterate to convergence.
+ * The feed tray is solved first to obtain a temperature estimate. This temperature is then used
+ * to linearly guess temperatures upwards to the condenser and downwards to the reboiler. Gas and
+ * liquid outlet streams are connected to neighbouring trays so that a subsequent call to
+ * {@link #run(UUID)} can iterate to convergence.
*
* Solve the column until tray temperatures converge.
*
- * The method applies sequential substitution with an adaptive relaxation controller. Pressures are set linearly
- * between bottom and top. Each iteration performs an upward sweep where liquid flows downward followed by a downward
- * sweep where vapour flows upward. Tray temperatures and inter-tray stream flow rates are relaxed if the combined
- * temperature, mass and energy residuals grow, providing basic line-search behaviour.
+ * The method applies sequential substitution with an adaptive relaxation controller. Pressures
+ * are set linearly between bottom and top. Each iteration performs an upward sweep where liquid
+ * flows downward followed by a downward sweep where vapour flows upward. Tray temperatures and
+ * inter-tray stream flow rates are relaxed if the combined temperature, mass and energy residuals
+ * grow, providing basic line-search behaviour.
*
- * The first stage starts from the current product value after a warm baseline solve and then ramps linearly to the
- * user-specified final target. This avoids an abrupt jump to a difficult purity, recovery, or product-flow target
- * while leaving the stored public specifications unchanged.
+ * The first stage starts from the current product value after a warm baseline solve and then
+ * ramps linearly to the user-specified final target. This avoids an abrupt jump to a difficult
+ * purity, recovery, or product-flow target while leaving the stored public specifications
+ * unchanged.
*
- * The rigorous residual solver is warm-started from the current inside-out path. If the Newton refinement does not
- * produce a residual-improving state, the accepted inside-out state is kept. In that rejected-state path, the column
- * products and solve metrics remain the inside-out warm-start values. This preserves the robust legacy behavior while
- * making the new solver an explicit residual-driven option.
+ * The rigorous residual solver is warm-started from the current inside-out path. If the Newton
+ * refinement does not produce a residual-improving state, the accepted inside-out state is kept.
+ * In that rejected-state path, the column products and solve metrics remain the inside-out
+ * warm-start values. This preserves the robust legacy behavior while making the new solver an
+ * explicit residual-driven option.
*
- * Running the aggressive Newton accelerator on a candidate protects the accepted inside-out solution from flash
- * failures, non-finite states, or residual growth. This provides a bounded line-search style guard for the
- * residual-monitored solver without changing the legacy Newton solver contract.
+ * Running the aggressive Newton accelerator on a candidate protects the accepted inside-out
+ * solution from flash failures, non-finite states, or residual growth. This provides a bounded
+ * line-search style guard for the residual-monitored solver without changing the legacy Newton
+ * solver contract.
*
- * User-facing feed stream maps are intentionally not replaced: they may contain stream object identities supplied by
- * callers and are reused on later runs. The candidate tray network already contains equivalent cloned feed streams
- * for the accepted solved state, and the preserved maps will refresh tray inputs from the caller-owned streams on the
- * next solve.
+ * User-facing feed stream maps are intentionally not replaced: they may contain stream object
+ * identities supplied by callers and are reused on later runs. The candidate tray network already
+ * contains equivalent cloned feed streams for the accepted solved state, and the preserved maps
+ * will refresh tray inputs from the caller-owned streams on the next solve.
*
- * Downstream equipment that captured {@link #getGasOutStream()} or {@link #getLiquidOutStream()} before the column
- * solved holds those stream objects by reference. Replacing the field with the candidate's freshly copied stream
- * orphaned the caller-held objects at their stale (pre-polish) flows, which dropped the difference between the stale
- * and solved product flows from the overall process mass balance. Copying the solved thermodynamic system into the
- * existing stream keeps the caller-held reference live and mass-consistent.
+ * Downstream equipment that captured {@link #getGasOutStream()} or {@link #getLiquidOutStream()}
+ * before the column solved holds those stream objects by reference. Replacing the field with the
+ * candidate's freshly copied stream orphaned the caller-held objects at their stale (pre-polish)
+ * flows, which dropped the difference between the stale and solved product flows from the overall
+ * process mass balance. Copying the solved thermodynamic system into the existing stream keeps
+ * the caller-held reference live and mass-consistent.
*
- * The accepted state comes from the warm-start solver, but the strategy reported to callers remains
- * {@link SolverType#NAPHTALI_SANDHOLM}. Naphtali-Sandholm telemetry from the rejected candidate is preserved so
- * diagnostics still show the attempted linearization work.
+ * The accepted state comes from the warm-start solver, but the strategy reported to callers
+ * remains {@link SolverType#NAPHTALI_SANDHOLM}. Naphtali-Sandholm telemetry from the rejected
+ * candidate is preserved so diagnostics still show the attempted linearization work.
*
- * The result is immutable and records the selected tray count, selected feed tray, product purity, duty estimates and
- * convergence diagnostics from the final candidate run.
+ * The result is immutable and records the selected tray count, selected feed tray, product
+ * purity, duty estimates and convergence diagnostics from the final candidate run.
*
- * The result records the Fenske-Underwood-Gilliland estimates and the translated rigorous-column settings applied to
- * {@link DistillationColumn}: total stage count, bottom-up feed tray and condenser reflux ratio.
+ * The result records the Fenske-Underwood-Gilliland estimates and the translated rigorous-column
+ * settings applied to {@link DistillationColumn}: total stage count, bottom-up feed tray and
+ * condenser reflux ratio.
*
- * The result extends the rigorous tray optimization result with mechanical design, installed capital cost, annual
- * utility cost, and annualized total-cost metrics. Costs are screening-level estimates using the column mechanical
- * design and column cost-estimation correlations.
+ * The result extends the rigorous tray optimization result with mechanical design, installed
+ * capital cost, annual utility cost, and annualized total-cost metrics. Costs are screening-level
+ * estimates using the column mechanical design and column cost-estimation correlations.
*
- * The search evaluates total tray count and feed tray together. It returns the first total tray count that has a
- * converged case meeting the requested purity, then selects the feed tray with the lowest absolute
- * condenser-plus-reboiler duty for that tray count. The selected candidate is applied back to this column and the
- * final solved state is left in the object.
+ * The search evaluates total tray count and feed tray together. It returns the first total tray
+ * count that has a converged case meeting the requested purity, then selects the feed tray with
+ * the lowest absolute condenser-plus-reboiler duty for that tray count. The selected candidate is
+ * applied back to this column and the final solved state is left in the object.
*
- * This method searches tray count and feed tray for all converged candidates that meet the product specification,
- * then selects the candidate with the lowest annualized cost. The cost is calculated as annualized installed capital
- * plus reboiler/condenser utility cost using the column mechanical design and cost-estimation correlations. Default
- * assumptions are 15%/year capital charge factor, 8000 operating hours/year, 25 USD/tonne steam, and 0.03 USD/m3
- * cooling water.
+ * This method searches tray count and feed tray for all converged candidates that meet the
+ * product specification, then selects the candidate with the lowest annualized cost. The cost is
+ * calculated as annualized installed capital plus reboiler/condenser utility cost using the
+ * column mechanical design and cost-estimation correlations. Default assumptions are 15%/year
+ * capital charge factor, 8000 operating hours/year, 25 USD/tonne steam, and 0.03 USD/m3 cooling
+ * water.
*
- * If reflux or reboiler ratio candidate arrays are supplied, each positive finite ratio is tried for every
- * tray-count/feed-tray case. If an array is {@code null} or empty, the current column specification is preserved for
- * that end of the column.
+ * If reflux or reboiler ratio candidate arrays are supplied, each positive finite ratio is tried
+ * for every tray-count/feed-tray case. If an array is {@code null} or empty, the current column
+ * specification is preserved for that end of the column.
*
- * The method runs {@link ShortcutDistillationColumn}, converts its stage and feed-tray estimates into this column's
- * bottom-up tray indexing, rebuilds the tray stack, adds the feed at the shortcut-estimated feed tray, applies
- * condenser reflux/duty and reboiler duty estimates, and stores light-key/heavy-key recovery specifications for later
- * rigorous solving.
+ * The method runs {@link ShortcutDistillationColumn}, converts its stage and feed-tray estimates
+ * into this column's bottom-up tray indexing, rebuilds the tray stack, adds the feed at the
+ * shortcut-estimated feed tray, applies condenser reflux/duty and reboiler duty estimates, and
+ * stores light-key/heavy-key recovery specifications for later rigorous solving.
*
- * This method reuses the normal setup validator and adds commercial-style active-bound warnings for specifications
- * that are mathematically valid but likely to make solver continuation or outer tear-variable convergence difficult.
+ * This method reuses the normal setup validator and adds commercial-style active-bound warnings
+ * for specifications that are mathematically valid but likely to make solver continuation or
+ * outer tear-variable convergence difficult.
*
- * Tray optimization is a configuration search. A candidate should not be discarded solely because the default
- * direct-substitution path stalls from a cold start when the more robust damped substitution solver can solve the
- * same thermodynamic and hydraulic setup. The retry is scoped to the current candidate and restores the caller's
- * configured solver type before returning.
+ * Tray optimization is a configuration search. A candidate should not be discarded solely because
+ * the default direct-substitution path stalls from a cold start when the more robust damped
+ * substitution solver can solve the same thermodynamic and hydraulic setup. The retry is scoped
+ * to the current candidate and restores the caller's configured solver type before returning.
*
- * The factor increases with the number of theoretical stages and independent feed streams and is bounded to avoid
- * overly loose convergence criteria.
+ * The factor increases with the number of theoretical stages and independent feed streams and is
+ * bounded to avoid overly loose convergence criteria.
*
* Key improvements over basic sequential substitution:
*
- * Large columns spend most of their wall time in repeated tray flashes once the K-value profile is already well
- * shaped. The hybrid handoff follows the common inside-out/simultaneous-correction pattern: use cheap fixed-point
- * sweeps for initialization, then solve the coupled temperature correction when additional sweeps mostly polish the
- * same profile.
+ * Large columns spend most of their wall time in repeated tray flashes once the K-value profile
+ * is already well shaped. The hybrid handoff follows the common
+ * inside-out/simultaneous-correction pattern: use cheap fixed-point sweeps for initialization,
+ * then solve the coupled temperature correction when additional sweeps mostly polish the same
+ * profile.
*
- * The matrix stage solves component material-balance tridiagonal systems using cached K-values and cached
- * K-temperature derivatives. It is accepted only as a warm start; the final products and convergence metrics still
- * come from the rigorous inside-out solver.
+ * The matrix stage solves component material-balance tridiagonal systems using cached K-values
+ * and cached K-temperature derivatives. It is accepted only as a warm start; the final products
+ * and convergence metrics still come from the rigorous inside-out solver.
*
- * The matrix component-balance stage has fixed setup cost and is only expected to pay off on larger columns. Small
- * benchmark columns are faster with the rigorous inside-out path directly.
+ * The matrix component-balance stage has fixed setup cost and is only expected to pay off on
+ * larger columns. Small benchmark columns are faster with the rigorous inside-out path directly.
*
- * K-values are computed as the ratio of vapor to liquid mole fractions for each component on each tray. This provides
- * a composition-based convergence metric that complements the temperature-based metric, similar to what commercial
- * inside-out implementations track.
+ * K-values are computed as the ratio of vapor to liquid mole fractions for each component on each
+ * tray. This provides a composition-based convergence metric that complements the
+ * temperature-based metric, similar to what commercial inside-out implementations track.
*
- * This method updates tray compositions using the simplified K-value correlation and adjusts temperatures via a
- * bubble-point calculation (sum of K*x = 1 condition). No PH-flash is called, making each inner iteration much
- * cheaper than a rigorous outer iteration.
+ * This method updates tray compositions using the simplified K-value correlation and adjusts
+ * temperatures via a bubble-point calculation (sum of K*x = 1 condition). No PH-flash is called,
+ * making each inner iteration much cheaper than a rigorous outer iteration.
*
- * Wegstein's method uses two consecutive fixed-point iterates to extrapolate a better estimate. For temperatures on
- * each tray the acceleration factor q is computed from the slope of the fixed-point map: q = s / (s - 1) where s =
- * (x_{k} - x_{k-1}) / (g(x_{k}) - g(x_{k-1})). The factor is bounded to [-5, 0] to prevent divergence.
+ * Wegstein's method uses two consecutive fixed-point iterates to extrapolate a better estimate.
+ * For temperatures on each tray the acceleration factor q is computed from the slope of the
+ * fixed-point map: q = s / (s - 1) where s = (x_{k} - x_{k-1}) / (g(x_{k}) - g(x_{k-1})). The
+ * factor is bounded to [-5, 0] to prevent divergence.
*
- * The sum-rates method adjusts tray liquid flow rates based on the ratio of computed to assumed total flow leaving
- * each tray. This is effective for absorber and stripper columns where the temperature profile is relatively flat.
- * The method alternates between: (1) bubble-point temperature calculations on each tray, and (2) flow rate
- * corrections using the sum-rates formula of Burningham and Otto (1967).
+ * The sum-rates method adjusts tray liquid flow rates based on the ratio of computed to assumed
+ * total flow leaving each tray. This is effective for absorber and stripper columns where the
+ * temperature profile is relatively flat. The method alternates between: (1) bubble-point
+ * temperature calculations on each tray, and (2) flow rate corrections using the sum-rates
+ * formula of Burningham and Otto (1967).
*
- * This is inspired by the Naphtali-Sandholm (1971) approach of solving MESH equations simultaneously, adapted to
- * NeqSim's tray-by-tray flash infrastructure. The method treats the N tray temperatures as the independent variables.
- * A residual vector is formed by running full tray sweeps and measuring the temperature discrepancy each tray
- * exhibits after equilibrium. The Jacobian is computed by finite-difference perturbation of each tray temperature.
+ * This is inspired by the Naphtali-Sandholm (1971) approach of solving MESH equations
+ * simultaneously, adapted to NeqSim's tray-by-tray flash infrastructure. The method treats the N
+ * tray temperatures as the independent variables. A residual vector is formed by running full
+ * tray sweeps and measuring the temperature discrepancy each tray exhibits after equilibrium. The
+ * Jacobian is computed by finite-difference perturbation of each tray temperature.
*
* Key features:
*
- * A value of one preserves the legacy direct outer-loop solve. Values above one ramp purity, recovery, and
- * product-flow targets from the current product value to the final target over the requested number of stages.
+ * A value of one preserves the legacy direct outer-loop solve. Values above one ramp purity,
+ * recovery, and product-flow targets from the current product value to the final target over the
+ * requested number of stages.
*
- * The report is intended for notebooks, agents, and troubleshooting scripts that need to know which convergence gate
- * failed and which common modelling choices should be checked first. It does not change the column state.
+ * The report is intended for notebooks, agents, and troubleshooting scripts that need to know
+ * which convergence gate failed and which common modelling choices should be checked first. It
+ * does not change the column state.
*
- * The Fs factor (gas load factor) is defined as {@code Fs = Vs * sqrt(rho_gas)} where {@code Vs} is the superficial
- * gas velocity (m/s) and {@code rho_gas} is the gas density (kg/m3). It is proportional to the aerodynamic lift
- * exerted by the gas on the liquid and is the primary hydraulic capacity indicator for the column.
+ * The Fs factor (gas load factor) is defined as {@code Fs = Vs * sqrt(rho_gas)} where {@code Vs}
+ * is the superficial gas velocity (m/s) and {@code rho_gas} is the gas density (kg/m3). It is
+ * proportional to the aerodynamic lift exerted by the gas on the liquid and is the primary
+ * hydraulic capacity indicator for the column.
*
* Re-initializes the capacity constraints so the new design value takes effect immediately.
@@ -8306,8 +8470,8 @@ public boolean isFsFactorWithinDesignLimit() {
}
/**
- * Calculates the minimum vessel internal diameter required to keep the Fs factor at or below the maximum allowable
- * value for the current gas flow rate.
+ * Calculates the minimum vessel internal diameter required to keep the Fs factor at or below the
+ * maximum allowable value for the current gas flow rate.
*
*
* From {@code Fs = Vs * sqrt(rho_gas)} and {@code Vs = Q / A}, the minimum diameter is
@@ -8333,19 +8497,21 @@ public double getMinimumDiameterForFsLimit() {
* Sets up the default capacity constraints for the distillation column.
*
*
- * Registers an Fs-factor (gas load factor) constraint that uses the live {@link #getFsFactor()} value against the
- * {@link #getMaxAllowableFsFactor()} design basis. This makes the column participate in process-wide bottleneck
- * analysis, capacity utilization summaries, and optimization constraint checking in the same way as other
- * capacity-constrained equipment.
+ * Registers an Fs-factor (gas load factor) constraint that uses the live {@link #getFsFactor()}
+ * value against the {@link #getMaxAllowableFsFactor()} design basis. This makes the column
+ * participate in process-wide bottleneck analysis, capacity utilization summaries, and
+ * optimization constraint checking in the same way as other capacity-constrained equipment.
*
- * Evaluates hydraulics on every tray (flooding, weeping, entrainment, downcomer backup, pressure drop, efficiency)
- * and sizes the column diameter from the controlling tray.
+ * Evaluates hydraulics on every tray (flooding, weeping, entrainment, downcomer backup, pressure
+ * drop, efficiency) and sizes the column diameter from the controlling tray.
*
- * Side draws are implemented on the tray outlet itself, so all column solver paths use the residual gas/liquid
- * traffic for inter-tray flow and expose the withdrawn stream separately.
+ * Side draws are implemented on the tray outlet itself, so all column solver paths use the
+ * residual gas/liquid traffic for inter-tray flow and expose the withdrawn stream separately.
*
- * The solver adjusts the side-draw fraction on the requested tray until the side-product stream flow matches the
- * target. This turns side draws into formal product specifications while preserving the existing tray split
- * implementation.
+ * The solver adjusts the side-draw fraction on the requested tray until the side-product stream
+ * flow matches the target. This turns side draws into formal product specifications while
+ * preserving the existing tray split implementation.
*
- * The draw is treated as an internal liquid withdrawal, not as a side-product stream. The return stream is updated
- * between column solves and added to the configured return tray as an internal recycle, so external mass-balance
- * reporting continues to use only true feeds and products.
+ * The draw is treated as an internal liquid withdrawal, not as a side-product stream. The return
+ * stream is updated between column solves and added to the configured return tray as an internal
+ * recycle, so external mass-balance reporting continues to use only true feeds and products.
*
- * Feeds registered through {@link #addFeedStream(StreamInterface, int)} are always included. The method also includes
- * named streams added directly to the tray to preserve legacy workflows that use
- * {@code getTray(index).addStream(stream)} for side feeds or stripping gas.
+ * Feeds registered through {@link #addFeedStream(StreamInterface, int)} are always included. The
+ * method also includes named streams added directly to the tray to preserve legacy workflows that
+ * use {@code getTray(index).addStream(stream)} for side feeds or stripping gas.
*
- * Iterative process solving (for example a recycle loop, or the solver's own candidate-copy accept path) can leave
- * cloned copies of a registered feed on its feed tray. Such clones share the registered feed name but have a
- * different object identity, so they must never be mistaken for genuine legacy direct side feeds.
+ * Iterative process solving (for example a recycle loop, or the solver's own candidate-copy
+ * accept path) can leave cloned copies of a registered feed on its feed tray. Such clones share
+ * the registered feed name but have a different object identity, so they must never be mistaken
+ * for genuine legacy direct side feeds.
*
- * A cloned registered feed shares the registered feed name but has a different identity. Keeping such clones in
- * {@link #directExternalFeedStreams} would inflate {@link #getExternalFeedStreams(int)} on every solve and make the
- * tray feed inventory grow without bound across repeated runs.
+ * A cloned registered feed shares the registered feed name but has a different identity. Keeping
+ * such clones in {@link #directExternalFeedStreams} would inflate
+ * {@link #getExternalFeedStreams(int)} on every solve and make the tray feed inventory grow
+ * without bound across repeated runs.
*
- * Product reconciliation updates {@link #gasOutStream} and {@link #liquidOutStream}. Legacy callers may also read the
- * condenser product or reboiler liquid stream directly, so those equipment-level product streams must be kept on the
- * same balanced basis.
+ * Product reconciliation updates {@link #gasOutStream} and {@link #liquidOutStream}. Legacy
+ * callers may also read the condenser product or reboiler liquid stream directly, so those
+ * equipment-level product streams must be kept on the same balanced basis.
*
- * Product reconciliation and guarded fallback updates can change the public column products after the tray solver has
- * produced terminal draws. MESH diagnostics must compare public products to these raw terminal draws rather than to
- * synchronized clones of the public products.
+ * Product reconciliation and guarded fallback updates can change the public column products after
+ * the tray solver has produced terminal draws. MESH diagnostics must compare public products to
+ * these raw terminal draws rather than to synchronized clones of the public products.
*
- * Used as a phase-preserving rescue when an accelerator solver (Inside-Out, Matrix-IO, Newton) converges to a tray-0
- * oil-phase composition that, when re-flashed in isolation at the reboiler T/P, collapses to single-phase gas. The
- * tray system itself was two-phase by construction, so the moles drawn from it are valid; forcing the phase preserves
- * the rigorous solver result and avoids the spurious overall-feed-flash fallback that otherwise triggers via
+ * Used as a phase-preserving rescue when an accelerator solver (Inside-Out, Matrix-IO, Newton)
+ * converges to a tray-0 oil-phase composition that, when re-flashed in isolation at the reboiler
+ * T/P, collapses to single-phase gas. The tray system itself was two-phase by construction, so
+ * the moles drawn from it are valid; forcing the phase preserves the rigorous solver result and
+ * avoids the spurious overall-feed-flash fallback that otherwise triggers via
* {@code bottomProductPhaseInvalid()}.
*
- * This fallback is used only after the tray solver has produced non-physical internal traffic. It gives bounded,
- * mass-conserving products for diagnostics without claiming that the rigorous tray MESH problem has converged.
+ * This fallback is used only after the tray solver has produced non-physical internal traffic. It
+ * gives bounded, mass-conserving products for diagnostics without claiming that the rigorous tray
+ * MESH problem has converged.
*
- * The split uses bottom terminal K-values and a Rachford-Rice vapor-fraction estimate to place volatile components
- * preferentially in the vapor and heavy components preferentially in the liquid. It is only used when an overall TP
- * flash does not expose both gas and liquid phases.
+ * The split uses bottom terminal K-values and a Rachford-Rice vapor-fraction estimate to place
+ * volatile components preferentially in the vapor and heavy components preferentially in the
+ * liquid. It is only used when an overall TP flash does not expose both gas and liquid phases.
*
- * Tray terminal thermo systems can hold both an ascending gas phase and a descending liquid phase. When reconciling a
- * single-phase public product against the external feed mass balance, the moles attributed to the product must come
- * only from the relevant phase. If no matching phase is present (e.g. a single-phase oil reboiler or pure-vapor
- * distillate), this method falls back to {@link #getComponentMoles(SystemInterface)} so the reconciliation step still
- * has a non-zero composition to scale.
+ * Tray terminal thermo systems can hold both an ascending gas phase and a descending liquid
+ * phase. When reconciling a single-phase public product against the external feed mass balance,
+ * the moles attributed to the product must come only from the relevant phase. If no matching
+ * phase is present (e.g. a single-phase oil reboiler or pure-vapor distillate), this method falls
+ * back to {@link #getComponentMoles(SystemInterface)} so the reconciliation step still has a
+ * non-zero composition to scale.
*
- * This focused validator is useful before a solve when users are scripting product-purity, component-recovery,
- * flow-rate, reflux, or duty specifications and want actionable diagnostics without validating the full equipment
- * setup.
+ * This focused validator is useful before a solve when users are scripting product-purity,
+ * component-recovery, flow-rate, reflux, or duty specifications and want actionable diagnostics
+ * without validating the full equipment setup.
*
- * This method is an alias for the legacy {@link #getNumerOfTrays()} method and is kept separate to preserve backwards
- * compatibility with existing scripts.
+ * This method is an alias for the legacy {@link #getNumerOfTrays()} method and is kept separate
+ * to preserve backwards compatibility with existing scripts.
*
- * A seed temperature is not a tray specification. Unlike {@link SimpleTray#setOutTemperature}, it does not pin the
- * stage temperature or replace the energy balance. The current {@link SolverType#NAPHTALI_SANDHOLM} implementation
- * uses finite seeds only when the same stage has no fixed output-temperature specification.
+ * A seed temperature is not a tray specification. Unlike {@link SimpleTray#setOutTemperature}, it
+ * does not pin the stage temperature or replace the energy balance. The current
+ * {@link SolverType#NAPHTALI_SANDHOLM} implementation uses finite seeds only when the same stage
+ * has no fixed output-temperature specification.
*
- * Stage numbering follows the column internal order: stage 0 is the reboiler when present and the last stage is the
- * condenser when present. Reboiler and condenser stages are still treated as equilibrium stages by the correction
- * algorithm, but the value is stored so external style scripts can round-trip stage efficiency data consistently.
+ * Stage numbering follows the column internal order: stage 0 is the reboiler when present and the
+ * last stage is the condenser when present. Reboiler and condenser stages are still treated as
+ * equilibrium stages by the correction algorithm, but the value is stored so external style
+ * scripts can round-trip stage efficiency data consistently.
*
- * The array length must equal the total number of stages including reboiler and condenser if they are present.
- * Entries equal to {@link Double#NaN} restore use of the column-wide default for that stage.
+ * The array length must equal the total number of stages including reboiler and condenser if they
+ * are present. Entries equal to {@link Double#NaN} restore use of the column-wide default for
+ * that stage.
*
- * For explicitly selected solvers this is normally the same as {@link #getSolverType()}. When {@link SolverType#AUTO}
- * is configured, this reports the concrete solver selected by the automatic solver factory.
+ * For explicitly selected solvers this is normally the same as {@link #getSolverType()}. When
+ * {@link SolverType#AUTO} is configured, this reports the concrete solver selected by the
+ * automatic solver factory.
*
- * When {@link SolverType#AUTO} is configured, the first solve runs the full feasibility pre-screen and
- * multi-candidate selection and caches the winning concrete solver. Subsequent warm re-solves (for example inside a
- * recycle loop) reuse this cached solver directly, skipping the expensive selection step. Returns {@code null} before
- * the first AUTO solve completes or after the column reverts to a cold start.
+ * When {@link SolverType#AUTO} is configured, the first solve runs the full feasibility
+ * pre-screen and multi-candidate selection and caches the winning concrete solver. Subsequent
+ * warm re-solves (for example inside a recycle loop) reuse this cached solver directly, skipping
+ * the expensive selection step. Returns {@code null} before the first AUTO solve completes or
+ * after the column reverts to a cold start.
*
- * Dynamic distillation column model. When {@code dynamicColumnEnabled} is true, performs a single forward-Euler
- * integration step on each tray's liquid holdup using the MESH equations. Liquid leaving each tray is calculated
- * using the Francis weir overflow formula.
+ * Dynamic distillation column model. When {@code dynamicColumnEnabled} is true, performs a single
+ * forward-Euler integration step on each tray's liquid holdup using the MESH equations. Liquid
+ * leaving each tray is calculated using the Francis weir overflow formula.
*
- *
*
* @param id calculation identifier
@@ -5385,10 +5520,11 @@ void solveInsideOut(UUID id) {
double totalFeedFlowIO = 0.0;
for (List
- *
*
* @param id calculation identifier
@@ -6699,7 +6861,8 @@ void solveNewton(UUID id) {
double baseEnergyTolerance = getEffectiveEnthalpyBalanceTolerance();
int baseIterationLimit = computeIterationLimit();
int iterationLimit = Math.max(baseIterationLimit, maxNumberOfIterations);
- int maxIterationLimit = iterationLimit + Math.max(numberOfTrays, 3) * ITERATION_OVERFLOW_MULTIPLIER;
+ int maxIterationLimit =
+ iterationLimit + Math.max(numberOfTrays, 3) * ITERATION_OVERFLOW_MULTIPLIER;
// Warm-up: run a few direct substitution iterations to establish a reasonable
// profile
@@ -6716,14 +6879,14 @@ void solveNewton(UUID id) {
err = tempRes;
if (convergenceHistory != null) {
- massErr = getMassBalanceError();
- energyErr = getEnergyBalanceError();
- recordConvergence(new double[] { err, massErr, energyErr });
+ massErr = getMassBalanceError();
+ energyErr = getEnergyBalanceError();
+ recordConvergence(new double[] {err, massErr, energyErr});
}
logger.debug("newton warm-up iteration {} tempErr={}", iter, err);
if (err < baseTempTolerance) {
- break;
+ break;
}
}
@@ -6747,20 +6910,20 @@ void solveNewton(UUID id) {
// Save current temperatures
for (int i = 0; i < numberOfTrays; i++) {
- temperatures[i] = trays.get(i).getThermoSystem().getTemperature();
+ temperatures[i] = trays.get(i).getThermoSystem().getTemperature();
}
// Compute base residuals: run a full sweep at current temperatures,
// residual = (post-sweep temperature) - (pre-sweep temperature)
performFullTraySweep(id, firstFeedTrayNumber, previousGasStreams, previousLiquidStreams, 1.0);
for (int i = 0; i < numberOfTrays; i++) {
- residuals[i] = trays.get(i).getThermoSystem().getTemperature() - temperatures[i];
+ residuals[i] = trays.get(i).getThermoSystem().getTemperature() - temperatures[i];
}
// Check if already converged
double normRes = 0.0;
for (int i = 0; i < numberOfTrays; i++) {
- normRes += Math.abs(residuals[i]);
+ normRes += Math.abs(residuals[i]);
}
normRes /= Math.max(1, numberOfTrays);
err = normRes;
@@ -6769,14 +6932,15 @@ void solveNewton(UUID id) {
energyErr = getEnergyBalanceError();
if (convergenceHistory != null) {
- recordConvergence(new double[] { err, massErr, energyErr });
+ recordConvergence(new double[] {err, massErr, energyErr});
}
- logger.debug("newton iteration {} tempErr={} massErr={} energyErr={}", iter, err, massErr, energyErr);
+ logger.debug("newton iteration {} tempErr={} massErr={} energyErr={}", iter, err, massErr,
+ energyErr);
boolean energyOk = !enforceEnergyBalanceTolerance || energyErr <= baseEnergyTolerance;
if (err <= baseTempTolerance && massErr <= baseMassTolerance && energyOk) {
- break;
+ break;
}
// Compute Jacobian by finite differences with banded structure
@@ -6788,9 +6952,9 @@ void solveNewton(UUID id) {
// Zero the Jacobian — entries outside the band stay zero
for (int i = 0; i < numberOfTrays; i++) {
- for (int jj = 0; jj < numberOfTrays; jj++) {
- jacobian[i][jj] = 0.0;
- }
+ for (int jj = 0; jj < numberOfTrays; jj++) {
+ jacobian[i][jj] = 0.0;
+ }
}
// Determine which columns actually need perturbation.
@@ -6802,54 +6966,56 @@ void solveNewton(UUID id) {
boolean[] needsPerturb = new boolean[numberOfTrays];
double residualSkipThreshold = 0.5 * baseTempTolerance;
for (int j = 0; j < numberOfTrays; j++) {
- if (numberOfTrays <= 6) {
- needsPerturb[j] = true;
- } else {
- int rowStart = Math.max(0, j - halfBand);
- int rowEnd = Math.min(numberOfTrays - 1, j + halfBand);
- double bandResidualMax = 0.0;
- for (int i = rowStart; i <= rowEnd; i++) {
- double r = Math.abs(residuals[i]);
- if (r > bandResidualMax) {
- bandResidualMax = r;
- }
- }
- // Always perturb the diagonal column itself, even if its own residual is
- // tight — the rest of the band may still couple through off-diagonal entries
- // on the next iteration. The skip only fires when the entire band is tight.
- needsPerturb[j] = bandResidualMax > residualSkipThreshold;
- }
+ if (numberOfTrays <= 6) {
+ needsPerturb[j] = true;
+ } else {
+ int rowStart = Math.max(0, j - halfBand);
+ int rowEnd = Math.min(numberOfTrays - 1, j + halfBand);
+ double bandResidualMax = 0.0;
+ for (int i = rowStart; i <= rowEnd; i++) {
+ double r = Math.abs(residuals[i]);
+ if (r > bandResidualMax) {
+ bandResidualMax = r;
+ }
+ }
+ // Always perturb the diagonal column itself, even if its own residual is
+ // tight — the rest of the band may still couple through off-diagonal entries
+ // on the next iteration. The skip only fires when the entire band is tight.
+ needsPerturb[j] = bandResidualMax > residualSkipThreshold;
+ }
}
for (int j = 0; j < numberOfTrays; j++) {
- if (!needsPerturb[j]) {
- continue;
- }
-
- // Reset temperatures to base state
- for (int i = 0; i < numberOfTrays; i++) {
- trays.get(i).setTemperature(temperatures[i]);
- trays.get(i).getThermoSystem().setTemperature(temperatures[i]);
- }
-
- // Perturb tray j
- double pertT = temperatures[j] + perturbation;
- trays.get(j).setTemperature(pertT);
- trays.get(j).getThermoSystem().setTemperature(pertT);
-
- // Run sweep with perturbed temperature
- performFullTraySweep(id, firstFeedTrayNumber, previousGasStreams, previousLiquidStreams, 1.0);
-
- // Compute perturbed residuals — only for rows within band of column j
- int rowStart = numberOfTrays <= 6 ? 0 : Math.max(0, j - halfBand);
- int rowEnd = numberOfTrays <= 6 ? numberOfTrays - 1 : Math.min(numberOfTrays - 1, j + halfBand);
- for (int i = rowStart; i <= rowEnd; i++) {
- double pertResidual = trays.get(i).getThermoSystem().getTemperature() - temperatures[i];
- if (j == i) {
- pertResidual = trays.get(i).getThermoSystem().getTemperature() - pertT;
- }
- jacobian[i][j] = (pertResidual - residuals[i]) / perturbation;
- }
+ if (!needsPerturb[j]) {
+ continue;
+ }
+
+ // Reset temperatures to base state
+ for (int i = 0; i < numberOfTrays; i++) {
+ trays.get(i).setTemperature(temperatures[i]);
+ trays.get(i).getThermoSystem().setTemperature(temperatures[i]);
+ }
+
+ // Perturb tray j
+ double pertT = temperatures[j] + perturbation;
+ trays.get(j).setTemperature(pertT);
+ trays.get(j).getThermoSystem().setTemperature(pertT);
+
+ // Run sweep with perturbed temperature
+ performFullTraySweep(id, firstFeedTrayNumber, previousGasStreams, previousLiquidStreams,
+ 1.0);
+
+ // Compute perturbed residuals — only for rows within band of column j
+ int rowStart = numberOfTrays <= 6 ? 0 : Math.max(0, j - halfBand);
+ int rowEnd =
+ numberOfTrays <= 6 ? numberOfTrays - 1 : Math.min(numberOfTrays - 1, j + halfBand);
+ for (int i = rowStart; i <= rowEnd; i++) {
+ double pertResidual = trays.get(i).getThermoSystem().getTemperature() - temperatures[i];
+ if (j == i) {
+ pertResidual = trays.get(i).getThermoSystem().getTemperature() - pertT;
+ }
+ jacobian[i][j] = (pertResidual - residuals[i]) / perturbation;
+ }
}
// For the Newton correction, we want to solve: J * deltaT = -residuals
@@ -6871,76 +7037,80 @@ void solveNewton(UUID id) {
// pivoting
double[] rhs = new double[numberOfTrays];
for (int i = 0; i < numberOfTrays; i++) {
- rhs[i] = -residuals[i];
+ rhs[i] = -residuals[i];
}
double[] deltaT = solveLinearSystem(jacobian, rhs);
if (deltaT == null || !isFiniteVector(deltaT)) {
- // Singular Jacobian — fall back to direct substitution step
- logger.warn("Newton: singular Jacobian at iter {}, using direct substitution step", iter);
- for (int i = 0; i < numberOfTrays; i++) {
- trays.get(i).setTemperature(temperatures[i] + 0.5 * residuals[i]);
- trays.get(i).getThermoSystem().setTemperature(temperatures[i] + 0.5 * residuals[i]);
- }
- // Reset damping memo: the Jacobian was bad, so prior step length is not a reliable hint.
- lastSuccessfulStepLength = 1.0;
- continue;
+ // Singular Jacobian — fall back to direct substitution step
+ logger.warn("Newton: singular Jacobian at iter {}, using direct substitution step", iter);
+ for (int i = 0; i < numberOfTrays; i++) {
+ trays.get(i).setTemperature(temperatures[i] + 0.5 * residuals[i]);
+ trays.get(i).getThermoSystem().setTemperature(temperatures[i] + 0.5 * residuals[i]);
+ }
+ // Reset damping memo: the Jacobian was bad, so prior step length is not a reliable hint.
+ lastSuccessfulStepLength = 1.0;
+ continue;
}
// Line search: try full Newton step, halve if residual increases.
// Damping memo: start at min(1.0, 2.0 * lastSuccessful) to skip probes that
// would predictably fail given prior nonlinearity. Periodically reset to 1.0
// so the algorithm can recover the full step when conditions improve.
- double trialStart = (iter % LINESEARCH_RESET_PERIOD == 0) ? 1.0 : Math.min(1.0, 2.0 * lastSuccessfulStepLength);
+ double trialStart = (iter % LINESEARCH_RESET_PERIOD == 0) ? 1.0
+ : Math.min(1.0, 2.0 * lastSuccessfulStepLength);
double bestStepLength = trialStart;
double bestNormRes = normRes;
for (double stepLength = trialStart; stepLength >= 0.125; stepLength *= 0.5) {
- // Apply trial step
- for (int i = 0; i < numberOfTrays; i++) {
- double newTemp = temperatures[i] + stepLength * deltaT[i];
- // Safeguard: keep temperatures reasonable
- newTemp = Math.max(50.0, Math.min(1000.0, newTemp));
- trays.get(i).setTemperature(newTemp);
- trays.get(i).getThermoSystem().setTemperature(newTemp);
- }
-
- // Check trial step quality with a sweep
- performFullTraySweep(id, firstFeedTrayNumber, previousGasStreams, previousLiquidStreams, 1.0);
-
- double trialNormRes = 0.0;
- for (int i = 0; i < numberOfTrays; i++) {
- double trialRes = trays.get(i).getThermoSystem().getTemperature() - temperatures[i] - stepLength * deltaT[i];
- trialNormRes += Math.abs(trialRes);
- }
- trialNormRes /= Math.max(1, numberOfTrays);
-
- if (trialNormRes < bestNormRes) {
- bestStepLength = stepLength;
- bestNormRes = trialNormRes;
- break; // Accept first improving step
- }
+ // Apply trial step
+ for (int i = 0; i < numberOfTrays; i++) {
+ double newTemp = temperatures[i] + stepLength * deltaT[i];
+ // Safeguard: keep temperatures reasonable
+ newTemp = Math.max(50.0, Math.min(1000.0, newTemp));
+ trays.get(i).setTemperature(newTemp);
+ trays.get(i).getThermoSystem().setTemperature(newTemp);
+ }
+
+ // Check trial step quality with a sweep
+ performFullTraySweep(id, firstFeedTrayNumber, previousGasStreams, previousLiquidStreams,
+ 1.0);
+
+ double trialNormRes = 0.0;
+ for (int i = 0; i < numberOfTrays; i++) {
+ double trialRes = trays.get(i).getThermoSystem().getTemperature() - temperatures[i]
+ - stepLength * deltaT[i];
+ trialNormRes += Math.abs(trialRes);
+ }
+ trialNormRes /= Math.max(1, numberOfTrays);
+
+ if (trialNormRes < bestNormRes) {
+ bestStepLength = stepLength;
+ bestNormRes = trialNormRes;
+ break; // Accept first improving step
+ }
}
// Apply the best step
if (bestStepLength < 1.0) {
- // Need to re-apply since the loop may have tried smaller steps
- for (int i = 0; i < numberOfTrays; i++) {
- double newTemp = temperatures[i] + bestStepLength * deltaT[i];
- newTemp = Math.max(50.0, Math.min(1000.0, newTemp));
- trays.get(i).setTemperature(newTemp);
- trays.get(i).getThermoSystem().setTemperature(newTemp);
- }
+ // Need to re-apply since the loop may have tried smaller steps
+ for (int i = 0; i < numberOfTrays; i++) {
+ double newTemp = temperatures[i] + bestStepLength * deltaT[i];
+ newTemp = Math.max(50.0, Math.min(1000.0, newTemp));
+ trays.get(i).setTemperature(newTemp);
+ trays.get(i).getThermoSystem().setTemperature(newTemp);
+ }
}
// Update damping memo for next iteration's line-search start point.
lastSuccessfulStepLength = bestStepLength;
- logger.debug("newton iteration {} step={} normRes={}->{}", iter, bestStepLength, normRes, bestNormRes);
+ logger.debug("newton iteration {} step={} normRes={}->{}", iter, bestStepLength, normRes,
+ bestNormRes);
// Overflow: extend limit if not converged
if (iter >= iterationLimit && err > baseTempTolerance && iterationLimit < maxIterationLimit) {
- iterationLimit = Math.min(maxIterationLimit, iterationLimit + 3);
+ iterationLimit = Math.min(maxIterationLimit, iterationLimit + 3);
}
}
@@ -6963,14 +7133,15 @@ void solveNewton(UUID id) {
* @param previousLiquidStreams cached liquid streams from previous iteration (updated in-place)
* @param relaxation relaxation factor for stream blending
*/
- private void performFullTraySweep(UUID id, int firstFeedTrayNumber, StreamInterface[] previousGasStreams,
- StreamInterface[] previousLiquidStreams, double relaxation) {
+ private void performFullTraySweep(UUID id, int firstFeedTrayNumber,
+ StreamInterface[] previousGasStreams, StreamInterface[] previousLiquidStreams,
+ double relaxation) {
// Downward liquid sweep: feed → reboiler
for (int stage = firstFeedTrayNumber; stage >= 1; stage--) {
int target = stage - 1;
int replaceStream = trays.get(target).getNumberOfInputStreams() - 1;
StreamInterface relaxedLiquid = applyRelaxation(previousLiquidStreams[stage],
- trays.get(stage).getLiquidOutStream(), relaxation);
+ trays.get(stage).getLiquidOutStream(), relaxation);
trays.get(target).replaceStream(replaceStream, relaxedLiquid);
previousLiquidStreams[stage] = relaxedLiquid.clone();
trays.get(target).run(id);
@@ -6981,10 +7152,10 @@ private void performFullTraySweep(UUID id, int firstFeedTrayNumber, StreamInterf
for (int stage = 1; stage <= numberOfTrays - 1; stage++) {
int replaceStream = trays.get(stage).getNumberOfInputStreams() - 2;
if (stage == (numberOfTrays - 1)) {
- replaceStream = trays.get(stage).getNumberOfInputStreams() - 1;
+ replaceStream = trays.get(stage).getNumberOfInputStreams() - 1;
}
StreamInterface relaxedGas = applyRelaxation(previousGasStreams[stage - 1],
- trays.get(stage - 1).getGasOutStream(), relaxation);
+ trays.get(stage - 1).getGasOutStream(), relaxation);
trays.get(stage).replaceStream(replaceStream, relaxedGas);
previousGasStreams[stage - 1] = relaxedGas.clone();
trays.get(stage).run(id);
@@ -6993,8 +7164,9 @@ private void performFullTraySweep(UUID id, int firstFeedTrayNumber, StreamInterf
}
/**
- * Compute the average absolute temperature residual across all trays. The residual is the difference between the
- * tray's stored temperature and its thermo system temperature after a flash.
+ * Compute the average absolute temperature residual across all trays. The residual is the
+ * difference between the tray's stored temperature and its thermo system temperature after a
+ * flash.
*
* @return average absolute temperature change per tray (K)
*/
@@ -7037,7 +7209,8 @@ private double[] captureStoredTrayTemperatures() {
private double computeTemperatureResidual(double[] referenceTemperatures) {
double residual = 0.0;
for (int i = 0; i < numberOfTrays; i++) {
- residual += Math.abs(trays.get(i).getThermoSystem().getTemperature() - referenceTemperatures[i]);
+ residual +=
+ Math.abs(trays.get(i).getThermoSystem().getTemperature() - referenceTemperatures[i]);
}
return residual / Math.max(1, numberOfTrays);
}
@@ -7051,72 +7224,27 @@ private double computeTemperatureResidual(double[] referenceTemperatures) {
*/
private double[] solveLinearSystem(double[][] matrixA, double[] vectorB) {
int n = vectorB.length;
- // Create copies to avoid modifying the originals from the caller's perspective
- double[][] a = new double[n][n];
- double[] b = new double[n];
for (int i = 0; i < n; i++) {
- b[i] = vectorB[i];
- if (!Double.isFinite(b[i])) {
- return null;
+ if (!Double.isFinite(vectorB[i])) {
+ return null;
}
for (int j = 0; j < n; j++) {
- a[i][j] = matrixA[i][j];
- if (!Double.isFinite(a[i][j])) {
- return null;
- }
+ if (!Double.isFinite(matrixA[i][j])) {
+ return null;
+ }
}
}
- // Forward elimination with partial pivoting
- for (int k = 0; k < n; k++) {
- // Find pivot
- int maxRow = k;
- double maxVal = Math.abs(a[k][k]);
- for (int i = k + 1; i < n; i++) {
- if (Math.abs(a[i][k]) > maxVal) {
- maxVal = Math.abs(a[i][k]);
- maxRow = i;
- }
- }
-
- if (!Double.isFinite(maxVal) || maxVal < 1e-30) {
- return null; // Singular matrix
- }
-
- // Swap rows
- if (maxRow != k) {
- double[] tempRow = a[k];
- a[k] = a[maxRow];
- a[maxRow] = tempRow;
- double tempB = b[k];
- b[k] = b[maxRow];
- b[maxRow] = tempB;
- }
-
- // Eliminate
- for (int i = k + 1; i < n; i++) {
- double factor = a[i][k] / a[k][k];
- for (int j = k + 1; j < n; j++) {
- a[i][j] -= factor * a[k][j];
- }
- b[i] -= factor * b[k];
- a[i][k] = 0.0;
- }
+ double[] solution = new double[n];
+ if (!LinearAlgebraOps.solveLinearSystem(matrixA, vectorB, solution)) {
+ return null;
}
-
- // Back substitution
- double[] x = new double[n];
- for (int i = n - 1; i >= 0; i--) {
- double sum = b[i];
- for (int j = i + 1; j < n; j++) {
- sum -= a[i][j] * x[j];
- }
- x[i] = sum / a[i][i];
- if (!Double.isFinite(x[i])) {
- return null;
+ for (int i = 0; i < n; i++) {
+ if (!Double.isFinite(solution[i])) {
+ return null;
}
}
- return x;
+ return solution;
}
/**
@@ -7128,7 +7256,7 @@ private double[] solveLinearSystem(double[][] matrixA, double[] vectorB) {
private boolean isFiniteVector(double[] values) {
for (int i = 0; i < values.length; i++) {
if (!Double.isFinite(values[i])) {
- return false;
+ return false;
}
}
return true;
@@ -7167,11 +7295,12 @@ public void setNumberOfTrays(int number) {
int change = tempNumberOfTrays - oldNumberOfTrays;
if (change > 0) {
for (int i = 0; i < change; i++) {
- trays.add(1, createMiddleTray("SimpleTray" + (oldNumberOfTrays + i + 1), oldNumberOfTrays + i));
+ trays.add(1,
+ createMiddleTray("SimpleTray" + (oldNumberOfTrays + i + 1), oldNumberOfTrays + i));
}
} else if (change < 0) {
for (int i = 0; i > change; i--) {
- trays.remove(1);
+ trays.remove(1);
}
}
numberOfTrays = tempNumberOfTrays;
@@ -7180,8 +7309,8 @@ public void setNumberOfTrays(int number) {
}
/**
- * Create a middle tray (between reboiler and condenser). Sets the reactive flash flag when the column is in reactive
- * mode and the tray index falls inside the reactive section.
+ * Create a middle tray (between reboiler and condenser). Sets the reactive flash flag when the
+ * column is in reactive mode and the tray index falls inside the reactive section.
*
* @param name the tray name
* @param middleTrayIndex 0-based index among the middle trays (excluding reboiler/condenser)
@@ -7209,9 +7338,9 @@ private boolean isInReactiveSection(int middleTrayIndex) {
}
/**
- * Enable or disable reactive distillation for all middle trays. When enabled, middle trays use {@link ReactiveTray}
- * (simultaneous chemical + phase equilibrium via the Modified RAND method). Can be called after construction;
- * existing trays will be replaced.
+ * Enable or disable reactive distillation for all middle trays. When enabled, middle trays use
+ * {@link ReactiveTray} (simultaneous chemical + phase equilibrium via the Modified RAND method).
+ * Can be called after construction; existing trays will be replaced.
*
* @param reactive {@code true} to enable reactive distillation
*/
@@ -7223,9 +7352,10 @@ public void setReactive(boolean reactive) {
}
/**
- * Enable reactive distillation on a specific section of middle trays. Tray indices are 0-based among the middle trays
- * (excluding reboiler/condenser). For example, in a column with reboiler + 10 middle trays + condenser,
- * {@code setReactive(true, 3, 7)} makes trays 4–8 (1-based) of the middle section reactive.
+ * Enable reactive distillation on a specific section of middle trays. Tray indices are 0-based
+ * among the middle trays (excluding reboiler/condenser). For example, in a column with reboiler +
+ * 10 middle trays + condenser, {@code setReactive(true, 3, 7)} makes trays 4–8 (1-based) of the
+ * middle section reactive.
*
* @param reactive {@code true} to enable reactive distillation
* @param startTray first reactive middle-tray index (0-based, inclusive)
@@ -7239,8 +7369,8 @@ public void setReactive(boolean reactive, int startTray, int endTray) {
}
/**
- * Update the reactive flash flag on middle trays to match the current reactive mode configuration. Called
- * automatically by {@link #setReactive}.
+ * Update the reactive flash flag on middle trays to match the current reactive mode
+ * configuration. Called automatically by {@link #setReactive}.
*/
private void replaceMiddleTrays() {
int start = hasReboiler ? 1 : 0;
@@ -7269,12 +7399,13 @@ public boolean isReactive() {
public void setSolverType(SolverType solverType) {
this.solverType = solverType == null ? SolverType.DIRECT_SUBSTITUTION : solverType;
this.solverTypeExplicitlySet = true;
- this.lastSolverTypeUsed = this.solverType == SolverType.AUTO ? SolverType.DIRECT_SUBSTITUTION : this.solverType;
+ this.lastSolverTypeUsed =
+ this.solverType == SolverType.AUTO ? SolverType.DIRECT_SUBSTITUTION : this.solverType;
}
/**
- * Enable or disable the opt-in fast path for large full fractionators that still use the legacy default
- * direct-substitution solver.
+ * Enable or disable the opt-in fast path for large full fractionators that still use the legacy
+ * default direct-substitution solver.
*
* @param enabled {@code true} to allow automatic feed re-centering and MESH residual selection
*/
@@ -7310,11 +7441,11 @@ public String getLastFullFractionatorFastPathReason() {
}
/**
- * Enable or disable the post-success damped-substitution verification run for accelerated solvers (Wegstein,
- * Sum-Rates, Naphtali-Sandholm, MESH residual). Off by default. When enabled every successful accelerated solve is
- * double-checked against a fresh damped-substitution solve on a column clone; if product flows differ by more than
- * 2 % the damped result is accepted. The verification roughly doubles wallclock time, so it is recommended only
- * for regression auditing.
+ * Enable or disable the post-success damped-substitution verification run for accelerated solvers
+ * (Wegstein, Sum-Rates, Naphtali-Sandholm, MESH residual). Off by default. When enabled every
+ * successful accelerated solve is double-checked against a fresh damped-substitution solve on a
+ * column clone; if product flows differ by more than 2 % the damped result is accepted. The
+ * verification roughly doubles wallclock time, so it is recommended only for regression auditing.
*
* @param enabled {@code true} to verify accelerated results against damped substitution
*/
@@ -7395,23 +7526,23 @@ public double getBottomPressure() {
@Override
public boolean solved() {
boolean acceptableStatus = lastSolveStatus == SolveStatus.RIGOROUS_CONVERGED
- || lastSolveStatus == SolveStatus.RECONCILED_PRODUCTS;
+ || lastSolveStatus == SolveStatus.RECONCILED_PRODUCTS;
return acceptableStatus && residualConvergenceSatisfied();
}
/**
* Check whether the current residual diagnostics satisfy all active rigorous convergence gates.
*
- * @return {@code true} when temperature, mass, energy, internal traffic, MESH, and specification gates are all
- * satisfied
+ * @return {@code true} when temperature, mass, energy, internal traffic, MESH, and specification
+ * gates are all satisfied
*/
private boolean residualConvergenceSatisfied() {
boolean temperatureSolved = err < getEffectiveTemperatureTolerance();
boolean massSolved = lastMassResidual <= getEffectiveMassBalanceTolerance();
boolean energySolved = !enforceEnergyBalanceTolerance
- || lastEnergyResidual <= getEffectiveEnthalpyBalanceTolerance();
- return temperatureSolved && massSolved && energySolved && internalTrafficSatisfied() && meshResidualsSatisfied()
- && specificationsSatisfied();
+ || lastEnergyResidual <= getEffectiveEnthalpyBalanceTolerance();
+ return temperatureSolved && massSolved && energySolved && internalTrafficSatisfied()
+ && meshResidualsSatisfied() && specificationsSatisfied();
}
/**
@@ -7421,7 +7552,7 @@ private boolean residualConvergenceSatisfied() {
*/
private boolean internalTrafficSatisfied() {
return Double.isFinite(lastInternalTrafficRatio) && !lastInternalTrafficGuardReached
- && lastInternalTrafficRatio <= MAX_RELAXED_INTERNAL_TRAFFIC_TO_FEED_RATIO;
+ && lastInternalTrafficRatio <= MAX_RELAXED_INTERNAL_TRAFFIC_TO_FEED_RATIO;
}
/**
@@ -7436,8 +7567,9 @@ private boolean meshResidualsSatisfied() {
if (lastMeshResidual == null) {
return false;
}
- return lastMeshResidual.isFinite() && lastMeshResidual.getInfinityNorm() <= meshResidualTolerance
- && productDrawResidualsSatisfied();
+ return lastMeshResidual.isFinite()
+ && lastMeshResidual.getInfinityNorm() <= meshResidualTolerance
+ && productDrawResidualsSatisfied();
}
/**
@@ -7447,7 +7579,8 @@ private boolean meshResidualsSatisfied() {
*/
private boolean productDrawResidualsSatisfied() {
double productDrawResidual = getLastMeshProductDrawResidualNorm();
- return Double.isFinite(productDrawResidual) && productDrawResidual <= meshProductDrawResidualTolerance;
+ return Double.isFinite(productDrawResidual)
+ && productDrawResidual <= meshProductDrawResidualTolerance;
}
/**
@@ -7463,7 +7596,8 @@ private boolean isEffectiveMeshResidualToleranceEnforced() {
}
/**
- * Check whether a solver uses a residual-based formulation that should satisfy the MESH residual gate by default.
+ * Check whether a solver uses a residual-based formulation that should satisfy the MESH residual
+ * gate by default.
*
* @param type solver type to inspect
* @return {@code true} when the solver should enforce full MESH residual diagnostics by default
@@ -7727,15 +7861,17 @@ public double getLastBottomSpecificationResidual() {
* @return maximum absolute top or bottom specification residual
*/
public double getLastSpecificationResidual() {
- return Math.max(Math.abs(lastTopSpecificationResidual), Math.abs(lastBottomSpecificationResidual));
+ return Math.max(Math.abs(lastTopSpecificationResidual),
+ Math.abs(lastBottomSpecificationResidual));
}
/**
* Set the number of continuation stages used for adjustable product specifications.
*
*
* y_i^out = y_i^in + E_MV * (y_i^eq - y_i^in)
*
*
- * where {@code E_MV} is the Murphree efficiency, {@code y_i^eq} is the equilibrium composition from the flash, and
- * {@code y_i^in} is the inlet vapor composition. When efficiency is 1.0, the tray is ideal and no correction is
- * applied. The correction is skipped for reboilers and condensers (first and last trays).
+ * where {@code E_MV} is the Murphree efficiency, {@code y_i^eq} is the equilibrium composition
+ * from the flash, and {@code y_i^in} is the inlet vapor composition. When efficiency is 1.0, the
+ * tray is ideal and no correction is applied. The correction is skipped for reboilers and
+ * condensers (first and last trays).
*
* @param trayIndex index of the tray in the {@code trays} list
*/
@@ -9635,7 +9818,7 @@ private void applyMurphreeCorrection(int trayIndex) {
}
if (sumY > 1e-15) {
for (int j = 0; j < nc; j++) {
- yActual[j] /= sumY;
+ yActual[j] /= sumY;
}
}
@@ -9668,7 +9851,8 @@ private void applyMurphreeCorrection(int trayIndex) {
* @param relaxation relaxation factor applied to the update
* @return relaxed stream instance to be used in the next tear
*/
- private StreamInterface applyRelaxation(StreamInterface previous, StreamInterface current, double relaxation) {
+ private StreamInterface applyRelaxation(StreamInterface previous, StreamInterface current,
+ double relaxation) {
return applyRelaxationInternal(previous, current, relaxation, false);
}
@@ -9680,7 +9864,8 @@ private StreamInterface applyRelaxation(StreamInterface previous, StreamInterfac
* @param relaxation relaxation factor applied to the update
* @return relaxed stream instance to be used in the next tear
*/
- private StreamInterface applyRelaxationFast(StreamInterface previous, StreamInterface current, double relaxation) {
+ private StreamInterface applyRelaxationFast(StreamInterface previous, StreamInterface current,
+ double relaxation) {
return applyRelaxationInternal(previous, current, relaxation, true);
}
@@ -9693,36 +9878,38 @@ private StreamInterface applyRelaxationFast(StreamInterface previous, StreamInte
* @param skipUnchangedReflash whether an unchanged clone can reuse the current stream flash state
* @return relaxed stream instance to be used in the next tear
*/
- private StreamInterface applyRelaxationInternal(StreamInterface previous, StreamInterface current, double relaxation,
- boolean skipUnchangedReflash) {
+ private StreamInterface applyRelaxationInternal(StreamInterface previous, StreamInterface current,
+ double relaxation, boolean skipUnchangedReflash) {
double maximumInternalFlow = getMaximumRelaxedInternalFlowKgPerHour();
// Fast path: no damping needed; clone the already-flashed tray outlet.
if (previous == null || relaxation >= 1.0) {
StreamInterface relaxed = current.clone();
- boolean requiresReflash = internalTrafficCapActive || !Double.isFinite(relaxed.getFlowRate("kg/hr"));
+ boolean requiresReflash =
+ internalTrafficCapActive || !Double.isFinite(relaxed.getFlowRate("kg/hr"));
if (requiresReflash) {
- capStreamFlow(relaxed, maximumInternalFlow);
+ capStreamFlow(relaxed, maximumInternalFlow);
}
if (requiresReflash || !skipUnchangedReflash) {
- relaxed.run();
+ relaxed.run();
}
return relaxed;
}
StreamInterface relaxed = current.clone();
double step = Math.max(0.0, Math.min(1.0, relaxation));
- double previousFlow = getRelaxedInternalFlow(previous.getFlowRate("kg/hr"), maximumInternalFlow);
+ double previousFlow =
+ getRelaxedInternalFlow(previous.getFlowRate("kg/hr"), maximumInternalFlow);
double currentFlow = getRelaxedInternalFlow(current.getFlowRate("kg/hr"), maximumInternalFlow);
double mixedFlow = previousFlow + step * (currentFlow - previousFlow);
mixedFlow = getRelaxedInternalFlow(mixedFlow, maximumInternalFlow);
relaxed.setFlowRate(mixedFlow, "kg/hr");
double mixedTemperature = previous.getTemperature("K")
- + step * (current.getTemperature("K") - previous.getTemperature("K"));
+ + step * (current.getTemperature("K") - previous.getTemperature("K"));
relaxed.setTemperature(mixedTemperature, "K");
double mixedPressure = previous.getPressure("bara")
- + step * (current.getPressure("bara") - previous.getPressure("bara"));
+ + step * (current.getPressure("bara") - previous.getPressure("bara"));
relaxed.setPressure(mixedPressure, "bara");
double[] zPrev = previous.getThermoSystem().getMolarComposition();
@@ -9743,16 +9930,16 @@ private StreamInterface applyRelaxationInternal(StreamInterface previous, Stream
double molesCurr_i = zCurr[i] * totalMolesCurr;
double mixedMoles_i = molesPrev_i + step * (molesCurr_i - molesPrev_i);
if (Double.isFinite(mixedMoles_i) && mixedMoles_i > 0.0) {
- zMixed[i] = mixedMoles_i;
- totalMolesMixed += mixedMoles_i;
+ zMixed[i] = mixedMoles_i;
+ totalMolesMixed += mixedMoles_i;
} else {
- zMixed[i] = 0.0;
+ zMixed[i] = 0.0;
}
}
if (totalMolesMixed > 1e-12 && relaxed.getThermoSystem().getTotalNumberOfMoles() > 1e-100) {
for (int i = 0; i < zMixed.length; i++) {
- zMixed[i] /= totalMolesMixed;
+ zMixed[i] /= totalMolesMixed;
}
relaxed.getThermoSystem().setMolarComposition(zMixed);
}
@@ -9772,17 +9959,18 @@ private StreamInterface applyRelaxationInternal(StreamInterface previous, Stream
* @param currentComposition current molar composition vector
* @return {@code true} when all streams expose the same component count and composition length
*/
- private boolean canRelaxMolarComposition(StreamInterface previous, StreamInterface current, StreamInterface relaxed,
- double[] previousComposition, double[] currentComposition) {
+ private boolean canRelaxMolarComposition(StreamInterface previous, StreamInterface current,
+ StreamInterface relaxed, double[] previousComposition, double[] currentComposition) {
if (previous == null || current == null || relaxed == null || previousComposition == null
- || currentComposition == null) {
+ || currentComposition == null) {
return false;
}
int previousComponents = previous.getThermoSystem().getNumberOfComponents();
int currentComponents = current.getThermoSystem().getNumberOfComponents();
int relaxedComponents = relaxed.getThermoSystem().getNumberOfComponents();
return previousComponents == currentComponents && currentComponents == relaxedComponents
- && previousComposition.length == previousComponents && currentComposition.length == currentComponents;
+ && previousComposition.length == previousComponents
+ && currentComposition.length == currentComponents;
}
/**
@@ -9805,7 +9993,8 @@ private double getRelaxedInternalFlow(double flow, double maximumInternalFlow) {
* @return maximum allowed internal tear-stream flow in kg/hr
*/
private double getMaximumRelaxedInternalFlowKgPerHour() {
- return Math.max(1.0e3, getTotalExternalFeedFlowKgPerHour() * MAX_RELAXED_INTERNAL_TRAFFIC_TO_FEED_RATIO);
+ return Math.max(1.0e3,
+ getTotalExternalFeedFlowKgPerHour() * MAX_RELAXED_INTERNAL_TRAFFIC_TO_FEED_RATIO);
}
/**
@@ -9847,8 +10036,8 @@ private void capStreamFlow(StreamInterface stream, double maximumFlow) {
* @param energyResidual final relative energy residual
* @param startTime nano time when the solve started
*/
- private void finalizeSolve(UUID id, int iterations, double temperatureResidual, double massResidual,
- double energyResidual, long startTime) {
+ private void finalizeSolve(UUID id, int iterations, double temperatureResidual,
+ double massResidual, double energyResidual, long startTime) {
err = temperatureResidual;
lastIterationCount = iterations;
lastTemperatureResidual = temperatureResidual;
@@ -9857,7 +10046,8 @@ private void finalizeSolve(UUID id, int iterations, double temperatureResidual,
lastSolveTimeSeconds = (System.nanoTime() - startTime) / 1.0e9;
lastUsedFeedFlashFallback = false;
- gasOutStream.setThermoSystem(trays.get(numberOfTrays - 1).getGasOutStream().getThermoSystem().clone());
+ gasOutStream
+ .setThermoSystem(trays.get(numberOfTrays - 1).getGasOutStream().getThermoSystem().clone());
gasOutStream.setCalculationIdentifier(id);
liquidOutStream.setThermoSystem(trays.get(0).getLiquidOutStream().getThermoSystem().clone());
liquidOutStream.setCalculationIdentifier(id);
@@ -9868,12 +10058,13 @@ private void finalizeSolve(UUID id, int iterations, double temperatureResidual,
if (!internalTrafficSatisfied()) {
capInternalTrayTraffic();
lastInternalTrafficGuardReached = true;
- lastInternalTrafficRatio = Math.min(getInternalTrafficRatio(), MAX_RELAXED_INTERNAL_TRAFFIC_TO_FEED_RATIO);
+ lastInternalTrafficRatio =
+ Math.min(getInternalTrafficRatio(), MAX_RELAXED_INTERNAL_TRAFFIC_TO_FEED_RATIO);
}
boolean fallbackProductsApplied = false;
if ((!internalTrafficSatisfied() || lastMassResidual > getEffectiveMassBalanceTolerance()
- || getExternalMassBalanceError() > getEffectiveMassBalanceTolerance() || bottomProductPhaseInvalid())
- && updateProductsFromOverallFeedFlash(id)) {
+ || getExternalMassBalanceError() > getEffectiveMassBalanceTolerance()
+ || bottomProductPhaseInvalid()) && updateProductsFromOverallFeedFlash(id)) {
fallbackProductsApplied = true;
}
synchronizeColumnEndProductStreams(id);
@@ -9882,19 +10073,19 @@ && updateProductsFromOverallFeedFlash(id)) {
lastMassResidual = Math.max(lastMassResidual, getExternalMassBalanceError());
if (lastInternalTrafficGuardReached) {
lastMassResidual = Math.max(lastMassResidual,
- lastInternalTrafficRatio / MAX_SOLVED_INTERNAL_TRAFFIC_TO_FEED_RATIO);
+ lastInternalTrafficRatio / MAX_SOLVED_INTERNAL_TRAFFIC_TO_FEED_RATIO);
}
boolean anyFeedMultiPhase = false;
for (List
*
*
* @param args command line arguments (not used)
@@ -10919,14 +11136,15 @@ public void energyBalanceCheck() {
@ExcludeFromJacocoGeneratedReport
public static void main(String[] args) {
// Create a test system
- neqsim.thermo.system.SystemInterface testSystem = new neqsim.thermo.system.SystemSrkEos(273.15 + 25.0, 15.0);
+ neqsim.thermo.system.SystemInterface testSystem =
+ new neqsim.thermo.system.SystemSrkEos(273.15 + 25.0, 15.0);
testSystem.addComponent("methane", 10.00);
testSystem.addComponent("ethane", 10.0);
testSystem.addComponent("propane", 10.0);
testSystem.createDatabase(true);
testSystem.setMixingRule(2);
- neqsim.thermodynamicoperations.ThermodynamicOperations ops = new neqsim.thermodynamicoperations.ThermodynamicOperations(
- testSystem);
+ neqsim.thermodynamicoperations.ThermodynamicOperations ops =
+ new neqsim.thermodynamicoperations.ThermodynamicOperations(testSystem);
ops.TPflash();
testSystem.display();
@@ -10942,7 +11160,8 @@ public static void main(String[] args) {
column.addFeedStream(feed2, 3);
// Build process
- neqsim.process.processmodel.ProcessSystem operations = new neqsim.process.processmodel.ProcessSystem();
+ neqsim.process.processmodel.ProcessSystem operations =
+ new neqsim.process.processmodel.ProcessSystem();
operations.add(feed1);
operations.add(feed2);
operations.add(column);
@@ -10979,9 +11198,9 @@ public ValidationResult validateSetup() {
* Validate only the configured top and bottom column specifications.
*
*
- * DistillationColumn col = DistillationColumn.builder("Deethanizer").numberOfTrays(7).withCondenserAndReboiler()
- * .topPressure(30.0, "bara").bottomPressure(31.0, "bara").insideOut().addFeedStream(feed, 4).build();
+ * DistillationColumn col = DistillationColumn.builder("Deethanizer").numberOfTrays(7)
+ * .withCondenserAndReboiler().topPressure(30.0, "bara").bottomPressure(31.0, "bara")
+ * .insideOut().addFeedStream(feed, 4).build();
*
*
* @author esol
@@ -12606,7 +12879,7 @@ public Builder internalDiameter(double diameter) {
* @return this builder
*/
public Builder addFeedStream(StreamInterface feed, int trayIndex) {
- this.feeds.add(new Object[] { feed, trayIndex });
+ this.feeds.add(new Object[] {feed, trayIndex});
return this;
}
@@ -12619,7 +12892,7 @@ public Builder addFeedStream(StreamInterface feed, int trayIndex) {
*/
public Builder topProductPurity(String componentName, double purity) {
this.topSpec = new ColumnSpecification(ColumnSpecification.SpecificationType.PRODUCT_PURITY,
- ColumnSpecification.ProductLocation.TOP, purity, componentName);
+ ColumnSpecification.ProductLocation.TOP, purity, componentName);
return this;
}
@@ -12642,37 +12915,37 @@ public Builder bottomSpecification(ColumnSpecification spec) {
public DistillationColumn build() {
DistillationColumn col = new DistillationColumn(name, numberOfTrays, condenser, reboiler);
if (topPressure >= 0) {
- col.setTopPressure(topPressure);
+ col.setTopPressure(topPressure);
}
if (bottomPressure >= 0) {
- col.setBottomPressure(bottomPressure);
+ col.setBottomPressure(bottomPressure);
}
if (tempTol >= 0) {
- col.setTemperatureTolerance(tempTol);
+ col.setTemperatureTolerance(tempTol);
}
if (massTol >= 0) {
- col.setMassBalanceTolerance(massTol);
+ col.setMassBalanceTolerance(massTol);
}
if (maxIter >= 0) {
- col.setMaxNumberOfIterations(maxIter);
+ col.setMaxNumberOfIterations(maxIter);
}
if (solver != null) {
- col.setSolverType(solver);
+ col.setSolverType(solver);
}
if (relaxation >= 0) {
- col.setRelaxationFactor(relaxation);
+ col.setRelaxationFactor(relaxation);
}
if (diameter >= 0) {
- col.setInternalDiameter(diameter);
+ col.setInternalDiameter(diameter);
}
if (topSpec != null) {
- col.topSpecification = topSpec;
+ col.topSpecification = topSpec;
}
if (bottomSpec != null) {
- col.bottomSpecification = bottomSpec;
+ col.bottomSpecification = bottomSpec;
}
for (Object[] feedEntry : feeds) {
- col.addFeedStream((StreamInterface) feedEntry[0], (Integer) feedEntry[1]);
+ col.addFeedStream((StreamInterface) feedEntry[0], (Integer) feedEntry[1]);
}
return col;
}
diff --git a/src/main/java/neqsim/process/equipment/distillation/NaphtaliSandholmSolver.java b/src/main/java/neqsim/process/equipment/distillation/NaphtaliSandholmSolver.java
index 5e5523e21e..b2e0db57ac 100644
--- a/src/main/java/neqsim/process/equipment/distillation/NaphtaliSandholmSolver.java
+++ b/src/main/java/neqsim/process/equipment/distillation/NaphtaliSandholmSolver.java
@@ -11,24 +11,26 @@
import neqsim.thermo.phase.PhaseType;
import neqsim.thermo.system.SystemInterface;
import neqsim.thermodynamicoperations.ThermodynamicOperations;
+import neqsim.util.math.LinearAlgebraOps;
/**
* Naphtali-Sandholm simultaneous correction solver for distillation columns.
*
* - * Solves the full MESH (Material balance, Equilibrium, Summation, Heat balance) system of equations simultaneously - * using Newton-Raphson iteration with block-tridiagonal Jacobian structure. + * Solves the full MESH (Material balance, Equilibrium, Summation, Heat balance) system of equations + * simultaneously using Newton-Raphson iteration with block-tridiagonal Jacobian structure. *
* *- * This solver handles wide-boiling-range systems where sequential substitution (tray-by-tray) methods diverge. The - * variable set per tray j is: liquid component flows l_{i,j} for each component i, temperature T_j, and vapor flow rate - * V_j. This gives (C+2) variables per tray for C components, and N*(C+2) total variables for N trays. + * This solver handles wide-boiling-range systems where sequential substitution (tray-by-tray) + * methods diverge. The variable set per tray j is: liquid component flows l_{i,j} for each + * component i, temperature T_j, and vapor flow rate V_j. This gives (C+2) variables per tray for C + * components, and N*(C+2) total variables for N trays. *
* *- * Reference: Naphtali, L.M. and Sandholm, D.P. (1971). "Multicomponent Separation Calculations by Linearization", AIChE - * Journal, 17(1), 148-153. + * Reference: Naphtali, L.M. and Sandholm, D.P. (1971). "Multicomponent Separation Calculations by + * Linearization", AIChE Journal, 17(1), 148-153. *
* * @author NeqSim / AI-assisted @@ -87,15 +89,16 @@ public class NaphtaliSandholmSolver { private double[][] K; /** - * Per-stage Murphree efficiency cached at solver init time. trayEta[j] is forced to 1.0 for the reboiler (j=0) and - * the condenser (j=N-1 when present) so those stages remain rigorous equilibrium. Other stages use the value - * registered on the parent column via setMurphreeEfficiency(stage, value), defaulting to the global value when no - * per-stage override is set. + * Per-stage Murphree efficiency cached at solver init time. trayEta[j] is forced to 1.0 for the + * reboiler (j=0) and the condenser (j=N-1 when present) so those stages remain rigorous + * equilibrium. Other stages use the value registered on the parent column via + * setMurphreeEfficiency(stage, value), defaulting to the global value when no per-stage override + * is set. * - * The efficiency is embedded into the simultaneous MESH solution through the Edmister proxy K_eff[j][i] = - * K[j][i]^trayEta[j]. At trayEta = 1.0 the stage is rigorous equilibrium; at trayEta -> 0 the K-values approach - * 1.0 and the tray becomes passive (vapor passes through ~ unchanged), matching the behaviour of a heavily de-rated - * tray + * The efficiency is embedded into the simultaneous MESH solution through the Edmister proxy + * K_eff[j][i] = K[j][i]^trayEta[j]. At trayEta = 1.0 the stage is rigorous equilibrium; at + * trayEta -> 0 the K-values approach 1.0 and the tray becomes passive (vapor passes through ~ + * unchanged), matching the behaviour of a heavily de-rated tray */ private double[] trayEta; @@ -135,15 +138,16 @@ public class NaphtaliSandholmSolver { private double[] Q; /** - * Fixed temperature specification for each tray in K. NaN means the temperature is a free variable (solved by the - * energy balance). + * Fixed temperature specification for each tray in K. NaN means the temperature is a free + * variable (solved by the energy balance). */ private double[] fixedTemperature; /** - * Per-tray seed temperature [K] used by the initializer when the tray is NOT fixed-T (i.e. T[j] is a Newton - * variable). NaN means "no seed, use Wilson estimate". This lets a user pass a reboiler T value as a hint when they - * specify boilup ratio instead of T as the actual MESH constraint. + * Per-tray seed temperature [K] used by the initializer when the tray is NOT fixed-T (i.e. T[j] + * is a Newton variable). NaN means "no seed, use Wilson estimate". This lets a user pass a + * reboiler T value as a hint when they specify boilup ratio instead of T as the actual MESH + * constraint. */ private double[] seedTemperature; @@ -151,10 +155,11 @@ public class NaphtaliSandholmSolver { private SystemInterface referenceSystem; /** - * Enable Boston-Sullivan inside-out refinement of the seed when at least one tray temperature is pinned (e.g. T-spec - * reboiler). The smooth Wilson BP seed misses sharp T-pinches; B-S inner loop (per-component tridiag MB + bubble-T) - * captures them and gives SR / Newton a sharper starting point. Auto-detected: only fires when - * {@code fixedTemperature[j]} is set on at least one tray. Default true. + * Enable Boston-Sullivan inside-out refinement of the seed when at least one tray temperature is + * pinned (e.g. T-spec reboiler). The smooth Wilson BP seed misses sharp T-pinches; B-S inner loop + * (per-component tridiag MB + bubble-T) captures them and gives SR / Newton a sharper starting + * point. Auto-detected: only fires when {@code fixedTemperature[j]} is set on at least one tray. + * Default true. */ private boolean enableBostonSullivan = true; @@ -171,10 +176,11 @@ public class NaphtaliSandholmSolver { private double refluxRatio; /** - * When true, the column uses overall mass balance closure L[0] = totalFeed - V[N-1] instead of V[0] = - * boilupRatio*L[0]. Enabled automatically when the user pins reboiler temperature but does not explicitly set a - * boilup ratio (i.e. boilupRatio is still the NeqSim default of 0.1). With pinned T, V[0] must be free to be - * determined by physics, not constrained to an arbitrary default ratio. + * When true, the column uses overall mass balance closure L[0] = totalFeed - V[N-1] instead of + * V[0] = boilupRatio*L[0]. Enabled automatically when the user pins reboiler temperature but does + * not explicitly set a boilup ratio (i.e. boilupRatio is still the NeqSim default of 0.1). With + * pinned T, V[0] must be free to be determined by physics, not constrained to an arbitrary + * default ratio. */ private boolean useOverallMBClosure = false; @@ -224,14 +230,14 @@ public class NaphtaliSandholmSolver { private double lastLinearSolveTimeSeconds; /** - * Original feed thermo systems cloned before column init() corrupts them. Map of tray index to list of - * SystemInterface clones. Null if not provided. + * Original feed thermo systems cloned before column init() corrupts them. Map of tray index to + * list of SystemInterface clones. Null if not provided. */ private Map- * The column's init() method may modify feed streams in-place (shared object references with trays). This constructor - * accepts deep clones of the original feed thermo systems and their molar flow rates, taken before init() runs, so - * the solver always uses the correct feed T, P, composition, and total moles. + * The column's init() method may modify feed streams in-place (shared object references with + * trays). This constructor accepts deep clones of the original feed thermo systems and their + * molar flow rates, taken before init() runs, so the solver always uses the correct feed T, P, + * composition, and total moles. *
* * @param column the distillation column to solve * @param originalFeedSystems map of tray index to list of original feed SystemInterface clones * @param originalFeedFlowRates map of tray index to list of feed molar flow rates in mol/hr */ - public NaphtaliSandholmSolver(DistillationColumn column, Map- * V[j] is NOT recomputed here — it is a free variable of the solver. The vapor component flows v_{i,j} = K_{i,j} * - * x_{i,j} * V_j are computed from the current V[j] and K-values. + * V[j] is NOT recomputed here — it is a free variable of the solver. The vapor component flows + * v_{i,j} = K_{i,j} * x_{i,j} * V_j are computed from the current V[j] and K-values. *
*/ private void evaluateThermo() { @@ -1251,21 +1280,23 @@ private void evaluateThermo() { * Seed a mass-balanced initial guess for direct Newton entry. * *- * Used when {@code useOverallMBClosure} is active (T-spec'd reboiler with no boilup specification). Bypasses the - * BP-Wilson V-cascade — which is unstable for wide-boiling stripper topologies — and replaces it with a sweep that: + * Used when {@code useOverallMBClosure} is active (T-spec'd reboiler with no boilup + * specification). Bypasses the BP-Wilson V-cascade — which is unstable for wide-boiling stripper + * topologies — and replaces it with a sweep that: *
*- * Mass conservation is satisfied at the initial guess to within numerical tolerance; Newton then refines energy - * balance and equilibrium. + * Mass conservation is satisfied at the initial guess to within numerical tolerance; Newton then + * refines energy balance and equilibrium. *
*/ private void seedSolutionForDirectNewton() { @@ -1282,11 +1313,11 @@ private void seedSolutionForDirectNewton() { double fjv = 0; double fjl = 0; for (int i = 0; i < C; i++) { - double fij = feedLiq[j][i] + feedVap[j][i]; - fj += fij; - fjv += feedVap[j][i]; - fjl += feedLiq[j][i]; - Fi[i] += fij; + double fij = feedLiq[j][i] + feedVap[j][i]; + fj += fij; + fjv += feedVap[j][i]; + fjl += feedLiq[j][i]; + Fi[i] += fij; } feedTotalPerTray[j] = fj; feedVapPerTray[j] = fjv; @@ -1334,10 +1365,10 @@ private void seedSolutionForDirectNewton() { double S = Kmean[i] * Vavg / Math.max(Lavg, 1e-20); double frac; if (Math.abs(S - 1.0) < 1e-3) { - frac = (double) N / (N + 1.0); + frac = (double) N / (N + 1.0); } else { - double Sn = Math.pow(S, N + 1); - frac = (Sn - S) / (Sn - 1.0); + double Sn = Math.pow(S, N + 1); + frac = (Sn - S) / (Sn - 1.0); } frac = Math.max(1e-6, Math.min(1.0 - 1e-6, frac)); overhead_i[i] = frac * Fi[i]; @@ -1381,11 +1412,11 @@ private void seedSolutionForDirectNewton() { for (int j = 0; j < N; j++) { double Pj = P[j] / 1e5; for (int i = 0; i < C; i++) { - ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); - double Tc = comp.getTC(); - double Pc = comp.getPC(); - double om = comp.getAcentricFactor(); - Kseed[j][i] = (Pc / Pj) * Math.exp(5.37 * (1.0 + om) * (1.0 - Tc / T[j])); + ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); + double Tc = comp.getTC(); + double Pc = comp.getPC(); + double om = comp.getAcentricFactor(); + Kseed[j][i] = (Pc / Pj) * Math.exp(5.37 * (1.0 + om) * (1.0 - Tc / T[j])); } } @@ -1393,23 +1424,23 @@ private void seedSolutionForDirectNewton() { // y_i ∝ K_ij × x_i[j]; renormalize to sum=1; then V_i[j] = y_i × V[j]. double sumKx = 0; for (int i = 0; i < C; i++) { - sumKx += Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20); + sumKx += Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20); } double norm = Math.max(sumKx, 1e-20); for (int i = 0; i < C; i++) { - double yi = (Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20)) / norm; - Viloc[j][i] = yi * V[j]; + double yi = (Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20)) / norm; + Viloc[j][i] = yi * V[j]; } if (j < N - 1) { - // Component MB on tray j (steady state): V_i[j-1] + L_i[j+1] + F_i[j] = V_i[j] - // + L_i[j] - for (int i = 0; i < C; i++) { - double Fij = feedLiq[j][i] + feedVap[j][i]; - double Lnext = Viloc[j][i] + Liloc[j][i] - VimPrev[i] - Fij; - Liloc[j + 1][i] = Math.max(Lnext, 1e-15); - } - System.arraycopy(Viloc[j], 0, VimPrev, 0, C); + // Component MB on tray j (steady state): V_i[j-1] + L_i[j+1] + F_i[j] = V_i[j] + // + L_i[j] + for (int i = 0; i < C; i++) { + double Fij = feedLiq[j][i] + feedVap[j][i]; + double Lnext = Viloc[j][i] + Liloc[j][i] - VimPrev[i] - Fij; + Liloc[j + 1][i] = Math.max(Lnext, 1e-15); + } + System.arraycopy(Viloc[j], 0, VimPrev, 0, C); } } @@ -1441,112 +1472,112 @@ private void seedSolutionForDirectNewton() { for (int bpIter = 0; bpIter < 12; bpIter++) { double maxDT = 0.0; for (int j = 0; j < N; j++) { - if (!Double.isNaN(fixedTemperature[j])) { - continue; // pinned (e.g. reboiler) - } - double sumL = 0; - for (int i = 0; i < C; i++) { - sumL += Liloc[j][i]; - } - if (sumL < 1e-12) { - continue; - } - double Tj = T[j]; - double Pj = P[j] / 1e5; - for (int newt = 0; newt < 40; newt++) { - double f = -1.0; - double df = 0.0; - for (int i = 0; i < C; i++) { - ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); - double Tc = comp.getTC(); - double Pc = comp.getPC(); - double om = comp.getAcentricFactor(); - double Ki = (Pc / Pj) * Math.exp(5.37 * (1.0 + om) * (1.0 - Tc / Tj)); - double xi = Liloc[j][i] / sumL; - f += Ki * xi; - double dKdT = Ki * 5.37 * (1.0 + om) * Tc / (Tj * Tj); - df += dKdT * xi; - } - if (Math.abs(df) < 1e-30) { - break; - } - double dT = -f / df; - if (dT > 15.0) { - dT = 15.0; - } else if (dT < -15.0) { - dT = -15.0; - } - Tj += dT; - if (Tj < 150.0) { - Tj = 150.0; - } else if (Tj > 800.0) { - Tj = 800.0; - } - if (Math.abs(dT) < 1e-3) { - break; - } - } - double change = Math.abs(Tj - T[j]); - if (change > maxDT) { - maxDT = change; - } - T[j] = Tj; + if (!Double.isNaN(fixedTemperature[j])) { + continue; // pinned (e.g. reboiler) + } + double sumL = 0; + for (int i = 0; i < C; i++) { + sumL += Liloc[j][i]; + } + if (sumL < 1e-12) { + continue; + } + double Tj = T[j]; + double Pj = P[j] / 1e5; + for (int newt = 0; newt < 40; newt++) { + double f = -1.0; + double df = 0.0; + for (int i = 0; i < C; i++) { + ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); + double Tc = comp.getTC(); + double Pc = comp.getPC(); + double om = comp.getAcentricFactor(); + double Ki = (Pc / Pj) * Math.exp(5.37 * (1.0 + om) * (1.0 - Tc / Tj)); + double xi = Liloc[j][i] / sumL; + f += Ki * xi; + double dKdT = Ki * 5.37 * (1.0 + om) * Tc / (Tj * Tj); + df += dKdT * xi; + } + if (Math.abs(df) < 1e-30) { + break; + } + double dT = -f / df; + if (dT > 15.0) { + dT = 15.0; + } else if (dT < -15.0) { + dT = -15.0; + } + Tj += dT; + if (Tj < 150.0) { + Tj = 150.0; + } else if (Tj > 800.0) { + Tj = 800.0; + } + if (Math.abs(dT) < 1e-3) { + break; + } + } + double change = Math.abs(Tj - T[j]); + if (change > maxDT) { + maxDT = change; + } + T[j] = Tj; } // Refresh Wilson K at new T. for (int j = 0; j < N; j++) { - double Pj = P[j] / 1e5; - for (int i = 0; i < C; i++) { - ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); - double Tc = comp.getTC(); - double Pc = comp.getPC(); - double om = comp.getAcentricFactor(); - Kseed[j][i] = (Pc / Pj) * Math.exp(5.37 * (1.0 + om) * (1.0 - Tc / T[j])); - } + double Pj = P[j] / 1e5; + for (int i = 0; i < C; i++) { + ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); + double Tc = comp.getTC(); + double Pc = comp.getPC(); + double om = comp.getAcentricFactor(); + Kseed[j][i] = (Pc / Pj) * Math.exp(5.37 * (1.0 + om) * (1.0 - Tc / T[j])); + } } // Re-shoot per-component flows with updated K-values. for (int i = 0; i < C; i++) { - VimPrev[i] = 0.0; + VimPrev[i] = 0.0; } for (int j = 0; j < N; j++) { - double sumKxJ = 0; - for (int i = 0; i < C; i++) { - sumKxJ += Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20); - } - double normJ = Math.max(sumKxJ, 1e-20); - for (int i = 0; i < C; i++) { - double yi = (Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20)) / normJ; - Viloc[j][i] = yi * V[j]; - } - if (j < N - 1) { - for (int i = 0; i < C; i++) { - double Fij = feedLiq[j][i] + feedVap[j][i]; - double Lnext = Viloc[j][i] + Liloc[j][i] - VimPrev[i] - Fij; - Liloc[j + 1][i] = Math.max(Lnext, 1e-15); - } - System.arraycopy(Viloc[j], 0, VimPrev, 0, C); - } + double sumKxJ = 0; + for (int i = 0; i < C; i++) { + sumKxJ += Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20); + } + double normJ = Math.max(sumKxJ, 1e-20); + for (int i = 0; i < C; i++) { + double yi = (Kseed[j][i] * Liloc[j][i] / Math.max(L[j], 1e-20)) / normJ; + Viloc[j][i] = yi * V[j]; + } + if (j < N - 1) { + for (int i = 0; i < C; i++) { + double Fij = feedLiq[j][i] + feedVap[j][i]; + double Lnext = Viloc[j][i] + Liloc[j][i] - VimPrev[i] - Fij; + Liloc[j + 1][i] = Math.max(Lnext, 1e-15); + } + System.arraycopy(Viloc[j], 0, VimPrev, 0, C); + } } // Re-enforce overhead boundary. for (int i = 0; i < C; i++) { - Viloc[N - 1][i] = overhead_i[i]; - double Fij = feedLiq[N - 1][i] + feedVap[N - 1][i]; - double VimN2 = (N >= 2) ? Viloc[N - 2][i] : 0.0; - double Lnew = VimN2 + Fij - Viloc[N - 1][i]; - Liloc[N - 1][i] = Math.max(Lnew, 1e-15); + Viloc[N - 1][i] = overhead_i[i]; + double Fij = feedLiq[N - 1][i] + feedVap[N - 1][i]; + double VimN2 = (N >= 2) ? Viloc[N - 2][i] : 0.0; + double Lnew = VimN2 + Fij - Viloc[N - 1][i]; + Liloc[N - 1][i] = Math.max(Lnew, 1e-15); } // Refresh L[j] totals from new component flows (V[j] is held; it carries // the boilup / overhead anchor). for (int j = 0; j < N; j++) { - double sL = 0; - for (int i = 0; i < C; i++) { - sL += Liloc[j][i]; - } - L[j] = Math.max(sL, 1e-20); + double sL = 0; + for (int i = 0; i < C; i++) { + sL += Liloc[j][i]; + } + L[j] = Math.max(sL, 1e-20); } if (maxDT < 0.05) { - break; + break; } } @@ -1561,14 +1592,14 @@ private void seedSolutionForDirectNewton() { // Copy into solver state and refresh total L[j], V[j] from the components. for (int j = 0; j < N; j++) { for (int i = 0; i < C; i++) { - liq[j][i] = Liloc[j][i]; - vap[j][i] = Viloc[j][i]; + liq[j][i] = Liloc[j][i]; + vap[j][i] = Viloc[j][i]; } double sL = 0; double sV = 0; for (int i = 0; i < C; i++) { - sL += liq[j][i]; - sV += vap[j][i]; + sL += liq[j][i]; + sV += vap[j][i]; } L[j] = Math.max(sL, 1e-20); V[j] = Math.max(sV, 1e-20); @@ -1591,25 +1622,26 @@ private void seedSolutionForDirectNewton() { double out = vap[N - 1][i] + liq[0][i]; double err = Math.abs(out - Fi[i]) / Math.max(Fi[i], 1e-20); if (err > maxCompMBerr) { - maxCompMBerr = err; + maxCompMBerr = err; } } logger.info( - "NS seed: totalFeed={} overheadTotal={} bottomsTotal={} boilup={} " - + "V[0]={} V[N-1]={} L[0]={} L[N-1]={} maxCompMBerr={}", - String.format("%.4f", totalFeed), String.format("%.4f", overheadTotal), String.format("%.4f", bottomsTotal), - String.format("%.4f", boilup), String.format("%.4f", V[0]), String.format("%.4f", V[N - 1]), - String.format("%.4f", L[0]), String.format("%.4f", L[N - 1]), String.format("%.4e", maxCompMBerr)); + "NS seed: totalFeed={} overheadTotal={} bottomsTotal={} boilup={} " + + "V[0]={} V[N-1]={} L[0]={} L[N-1]={} maxCompMBerr={}", + String.format("%.4f", totalFeed), String.format("%.4f", overheadTotal), + String.format("%.4f", bottomsTotal), String.format("%.4f", boilup), + String.format("%.4f", V[0]), String.format("%.4f", V[N - 1]), String.format("%.4f", L[0]), + String.format("%.4f", L[N - 1]), String.format("%.4e", maxCompMBerr)); } /** * Solve the column using the Bubble Point (BP) method (Wang-Henke, 1966). * *- * Decomposes the MESH equations into sequential subproblems: 1. Material balance: tridiagonal Thomas algorithm for - * each component 2. Summation: bubble point calculation for temperature on each tray 3. Energy: energy balance for - * vapor flow on each tray + * Decomposes the MESH equations into sequential subproblems: 1. Material balance: tridiagonal + * Thomas algorithm for each component 2. Summation: bubble point calculation for temperature on + * each tray 3. Energy: energy balance for vapor flow on each tray *
* * @return true if converged @@ -1640,31 +1672,31 @@ private boolean solveBubblePointMethod() { for (int iter = 0; iter < 80; iter++) { // Compute Wilson K-values at current T,P for (int j = 0; j < N; j++) { - double Pbar = P[j] / 1e5; - for (int i = 0; i < C; i++) { - K[j][i] = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); - K[j][i] = Math.max(K[j][i], 1e-20); - } - applyMurphreeEfficiencyToK(j); - // Update vapor flows from K-values - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - vap[j][i] = K[j][i] * xi * V[j]; - } + double Pbar = P[j] / 1e5; + for (int i = 0; i < C; i++) { + K[j][i] = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); + K[j][i] = Math.max(K[j][i], 1e-20); + } + applyMurphreeEfficiencyToK(j); + // Update vapor flows from K-values + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + vap[j][i] = K[j][i] * xi * V[j]; + } } // Step A: Solve tridiagonal material balance for each component for (int comp = 0; comp < C; comp++) { - solveTridiagonalForComponent(comp); + solveTridiagonalForComponent(comp); } // Step B: Update L totals for (int j = 0; j < N; j++) { - double sumL = 0; - for (int i = 0; i < C; i++) { - sumL += liq[j][i]; - } - L[j] = Math.max(sumL, 1e-20); + double sumL = 0; + for (int i = 0; i < C; i++) { + sumL += liq[j][i]; + } + L[j] = Math.max(sumL, 1e-20); } // Step C: T correction using analytical Wilson dK/dT @@ -1672,61 +1704,62 @@ private boolean solveBubblePointMethod() { // dK/dT = K * 5.37*(1+w)*Tc/T^2 double maxDeltaT = 0; for (int j = 0; j < N; j++) { - if (!Double.isNaN(fixedTemperature[j])) { - continue; - } - double Pbar = P[j] / 1e5; - double sumKx = 0; - double dsumKxdT = 0; - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - double Ki = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); - double dKidT = Ki * 5.37 * (1.0 + omega[i]) * Tc[i] / (T[j] * T[j]); - sumKx += Ki * xi; - dsumKxdT += dKidT * xi; - } - if (Math.abs(dsumKxdT) > 1e-15) { - double deltaT = -(sumKx - 1.0) / dsumKxdT; - deltaT = Math.max(-50.0, Math.min(50.0, deltaT)); - maxDeltaT = Math.max(maxDeltaT, Math.abs(dampT * deltaT)); - T[j] += dampT * deltaT; - T[j] = Math.max(T[j], 100.0); - T[j] = Math.min(T[j], 1000.0); - } + if (!Double.isNaN(fixedTemperature[j])) { + continue; + } + double Pbar = P[j] / 1e5; + double sumKx = 0; + double dsumKxdT = 0; + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + double Ki = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); + double dKidT = Ki * 5.37 * (1.0 + omega[i]) * Tc[i] / (T[j] * T[j]); + sumKx += Ki * xi; + dsumKxdT += dKidT * xi; + } + if (Math.abs(dsumKxdT) > 1e-15) { + double deltaT = -(sumKx - 1.0) / dsumKxdT; + deltaT = Math.max(-50.0, Math.min(50.0, deltaT)); + maxDeltaT = Math.max(maxDeltaT, Math.abs(dampT * deltaT)); + T[j] += dampT * deltaT; + T[j] = Math.max(T[j], 100.0); + T[j] = Math.min(T[j], 1000.0); + } } // Step E: V from sum-rates (only after initial T convergence) double maxDeltaV = 0; if (iter > 10 || maxDeltaT < 5.0) { - for (int j = 0; j < N; j++) { - double Pbar = P[j] / 1e5; - double sumKx = 0; - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - double Ki = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); - sumKx += Ki * xi; - } - double newV = V[j] * sumKx; - newV = Math.max(newV, 1e-10); - double deltaV = Math.abs(newV - V[j]); - maxDeltaV = Math.max(maxDeltaV, deltaV); - V[j] = newV; - } - // Closure at the reboiler: prefer overall MB when T is pinned. - if (hasReboiler && useOverallMBClosure) { - L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); - } else if (hasReboiler && boilupRatio > 0) { - V[0] = boilupRatio * L[0]; - } + for (int j = 0; j < N; j++) { + double Pbar = P[j] / 1e5; + double sumKx = 0; + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + double Ki = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); + sumKx += Ki * xi; + } + double newV = V[j] * sumKx; + newV = Math.max(newV, 1e-10); + double deltaV = Math.abs(newV - V[j]); + maxDeltaV = Math.max(maxDeltaV, deltaV); + V[j] = newV; + } + // Closure at the reboiler: prefer overall MB when T is pinned. + if (hasReboiler && useOverallMBClosure) { + L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); + } else if (hasReboiler && boilupRatio > 0) { + V[0] = boilupRatio * L[0]; + } } if (iter % 10 == 0) { - logger.debug("BP-Wilson iter {}: maxDeltaT={} maxDeltaV={}", iter, String.format("%.4f", maxDeltaT), - String.format("%.4f", maxDeltaV)); + logger.debug("BP-Wilson iter {}: maxDeltaT={} maxDeltaV={}", iter, + String.format("%.4f", maxDeltaT), String.format("%.4f", maxDeltaV)); } if (maxDeltaT < 0.1 && (maxDeltaV < 0.1 || iter <= 10)) { - logger.info("BP-Wilson converged at iter {}: maxDeltaT={}", iter, String.format("%.4f", maxDeltaT)); - break; + logger.info("BP-Wilson converged at iter {}: maxDeltaT={}", iter, + String.format("%.4f", maxDeltaT)); + break; } } @@ -1735,7 +1768,7 @@ private boolean solveBubblePointMethod() { // Here we use the same BP tridiagonal structure but with rigorous EOS K-values, // blending gradually from Wilson to EOS to avoid divergence. logger.info("BP phase 2: EOS K-value successive substitution. Reboiler T={} Top T={}", - String.format("%.2f", T[0] - 273.15), String.format("%.2f", T[N - 1] - 273.15)); + String.format("%.2f", T[0] - 273.15), String.format("%.2f", T[N - 1] - 273.15)); // Save Wilson solution as fallback double[][] wilsonLiq = new double[N][C]; @@ -1752,8 +1785,8 @@ private boolean solveBubblePointMethod() { for (int j = 0; j < N; j++) { double Pbar = P[j] / 1e5; for (int i = 0; i < C; i++) { - Kwilson[j][i] = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); - Kwilson[j][i] = Math.max(Kwilson[j][i], 1e-20); + Kwilson[j][i] = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); + Kwilson[j][i] = Math.max(Kwilson[j][i], 1e-20); } } @@ -1770,105 +1803,105 @@ private boolean solveBubblePointMethod() { for (int eosIter = 0; eosIter < maxEosIter; eosIter++) { if (System.nanoTime() - eosStart > maxEosTimeNs) { - logger.info("BP-EOS: time limit reached at iter {}", eosIter); - break; + logger.info("BP-EOS: time limit reached at iter {}", eosIter); + break; } // Compute EOS K-values with TPflash on each tray for (int j = 0; j < N; j++) { - double sumLiq = 0; - for (int i = 0; i < C; i++) { - sumLiq += liq[j][i]; - } - L[j] = Math.max(sumLiq, 1e-20); - - double[] x = new double[C]; - for (int i = 0; i < C; i++) { - x[i] = liq[j][i] / L[j]; - } - - // EOS K-values from TPflash - SystemInterface traySystem = referenceSystem.clone(); - traySystem.setTemperature(T[j]); - traySystem.setPressure(P[j] / 1e5); - traySystem.setTotalNumberOfMoles(1.0); - traySystem.setMolarComposition(x); - traySystem.setNumberOfPhases(2); - traySystem.init(0); - - ThermodynamicOperations ops = new ThermodynamicOperations(traySystem); - boolean eosOk = false; - try { - ops.TPflash(); - traySystem.init(2); - if (traySystem.getNumberOfPhases() >= 2) { - eosOk = true; - // Identify phases by density (lighter = vapor, heavier = liquid) - int vapIdx = 0; - int liqIdx = 1; - if (traySystem.getPhase(0).getDensity() > traySystem.getPhase(1).getDensity()) { - vapIdx = 1; - liqIdx = 0; - } - for (int i = 0; i < C; i++) { - double fugL = traySystem.getPhase(liqIdx).getComponent(i).getFugacityCoefficient(); - double fugV = traySystem.getPhase(vapIdx).getComponent(i).getFugacityCoefficient(); - K[j][i] = fugL / Math.max(fugV, 1e-30); - // Clamp extreme K-values to prevent divergence - K[j][i] = Math.max(K[j][i], 1e-15); - K[j][i] = Math.min(K[j][i], 1e15); - } - } - } catch (Exception e) { - // keep eosOk = false - } - - if (!eosOk) { - // Fall back to Wilson - double Pbar = P[j] / 1e5; - for (int i = 0; i < C; i++) { - K[j][i] = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); - K[j][i] = Math.max(K[j][i], 1e-20); - } - } - - // Apply Murphree-efficiency correction (Edmister K^eta proxy) so the - // simultaneous solve reflects any per-stage efficiency overrides. - applyMurphreeEfficiencyToK(j); - - // Compute actual vapor composition y[j] = K*x / sum(K*x) for correct enthalpy - double sumKxJ = 0; - for (int i = 0; i < C; i++) { - sumKxJ += K[j][i] * x[i]; - } - double[] yJ = new double[C]; - for (int i = 0; i < C; i++) { - yJ[i] = (sumKxJ > 1e-20) ? K[j][i] * x[i] / sumKxJ : x[i]; - } - - // Enthalpies at actual stream compositions (single-phase EOS evaluation) - hL[j] = computeSinglePhaseEnthalpy(x, T[j], P[j] / 1e5, false); - hV[j] = computeSinglePhaseEnthalpy(yJ, T[j], P[j] / 1e5, true); - - // Update vapor from K - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - vap[j][i] = K[j][i] * xi * V[j]; - } + double sumLiq = 0; + for (int i = 0; i < C; i++) { + sumLiq += liq[j][i]; + } + L[j] = Math.max(sumLiq, 1e-20); + + double[] x = new double[C]; + for (int i = 0; i < C; i++) { + x[i] = liq[j][i] / L[j]; + } + + // EOS K-values from TPflash + SystemInterface traySystem = referenceSystem.clone(); + traySystem.setTemperature(T[j]); + traySystem.setPressure(P[j] / 1e5); + traySystem.setTotalNumberOfMoles(1.0); + traySystem.setMolarComposition(x); + traySystem.setNumberOfPhases(2); + traySystem.init(0); + + ThermodynamicOperations ops = new ThermodynamicOperations(traySystem); + boolean eosOk = false; + try { + ops.TPflash(); + traySystem.init(2); + if (traySystem.getNumberOfPhases() >= 2) { + eosOk = true; + // Identify phases by density (lighter = vapor, heavier = liquid) + int vapIdx = 0; + int liqIdx = 1; + if (traySystem.getPhase(0).getDensity() > traySystem.getPhase(1).getDensity()) { + vapIdx = 1; + liqIdx = 0; + } + for (int i = 0; i < C; i++) { + double fugL = traySystem.getPhase(liqIdx).getComponent(i).getFugacityCoefficient(); + double fugV = traySystem.getPhase(vapIdx).getComponent(i).getFugacityCoefficient(); + K[j][i] = fugL / Math.max(fugV, 1e-30); + // Clamp extreme K-values to prevent divergence + K[j][i] = Math.max(K[j][i], 1e-15); + K[j][i] = Math.min(K[j][i], 1e15); + } + } + } catch (Exception e) { + // keep eosOk = false + } + + if (!eosOk) { + // Fall back to Wilson + double Pbar = P[j] / 1e5; + for (int i = 0; i < C; i++) { + K[j][i] = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / T[j])); + K[j][i] = Math.max(K[j][i], 1e-20); + } + } + + // Apply Murphree-efficiency correction (Edmister K^eta proxy) so the + // simultaneous solve reflects any per-stage efficiency overrides. + applyMurphreeEfficiencyToK(j); + + // Compute actual vapor composition y[j] = K*x / sum(K*x) for correct enthalpy + double sumKxJ = 0; + for (int i = 0; i < C; i++) { + sumKxJ += K[j][i] * x[i]; + } + double[] yJ = new double[C]; + for (int i = 0; i < C; i++) { + yJ[i] = (sumKxJ > 1e-20) ? K[j][i] * x[i] / sumKxJ : x[i]; + } + + // Enthalpies at actual stream compositions (single-phase EOS evaluation) + hL[j] = computeSinglePhaseEnthalpy(x, T[j], P[j] / 1e5, false); + hV[j] = computeSinglePhaseEnthalpy(yJ, T[j], P[j] / 1e5, true); + + // Update vapor from K + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + vap[j][i] = K[j][i] * xi * V[j]; + } } // Step A: Solve tridiagonal material balance for each component for (int comp = 0; comp < C; comp++) { - solveTridiagonalForComponent(comp); + solveTridiagonalForComponent(comp); } // Step B: Update L totals for (int j = 0; j < N; j++) { - double sumL = 0; - for (int i = 0; i < C; i++) { - sumL += liq[j][i]; - } - L[j] = Math.max(sumL, 1e-20); + double sumL = 0; + for (int i = 0; i < C; i++) { + sumL += liq[j][i]; + } + L[j] = Math.max(sumL, 1e-20); } // Step C: T correction using numerical EOS dK/dT @@ -1876,86 +1909,86 @@ private boolean solveBubblePointMethod() { double maxDeltaT = 0; double dTpert = 0.1; // K for (int j = 0; j < N; j++) { - if (!Double.isNaN(fixedTemperature[j])) { - continue; - } - - // sumKx at current K (already computed above) - double sumKx = 0; - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - sumKx += K[j][i] * xi; - } - - // Perturbed EOS K at T+dT - double[] x = new double[C]; - for (int i = 0; i < C; i++) { - x[i] = liq[j][i] / Math.max(L[j], 1e-20); - } - SystemInterface pertSystem = referenceSystem.clone(); - pertSystem.setTemperature(T[j] + dTpert); - pertSystem.setPressure(P[j] / 1e5); - pertSystem.setTotalNumberOfMoles(1.0); - pertSystem.setMolarComposition(x); - pertSystem.setNumberOfPhases(2); - pertSystem.init(0); - - ThermodynamicOperations pertOps = new ThermodynamicOperations(pertSystem); - double sumKxPert = 0; - boolean pertOk = false; - try { - pertOps.TPflash(); - pertSystem.init(2); - if (pertSystem.getNumberOfPhases() >= 2) { - pertOk = true; - // Identify phases by density - int vapIdxP = 0; - int liqIdxP = 1; - if (pertSystem.getPhase(0).getDensity() > pertSystem.getPhase(1).getDensity()) { - vapIdxP = 1; - liqIdxP = 0; - } - for (int i = 0; i < C; i++) { - double fugL = pertSystem.getPhase(liqIdxP).getComponent(i).getFugacityCoefficient(); - double fugV = pertSystem.getPhase(vapIdxP).getComponent(i).getFugacityCoefficient(); - double Kpert = fugL / Math.max(fugV, 1e-30); - Kpert = Math.max(Kpert, 1e-15); - Kpert = Math.min(Kpert, 1e15); - sumKxPert += Kpert * x[i]; - } - } - } catch (Exception e) { - // pertOk stays false - } - - if (pertOk) { - double dsumKxdT = (sumKxPert - sumKx) / dTpert; - if (Math.abs(dsumKxdT) > 1e-15) { - double deltaT = -(sumKx - 1.0) / dsumKxdT; - double eosDampT = 0.5; - deltaT = Math.max(-30.0, Math.min(30.0, deltaT)); - maxDeltaT = Math.max(maxDeltaT, Math.abs(eosDampT * deltaT)); - T[j] += eosDampT * deltaT; - T[j] = Math.max(T[j], 100.0); - T[j] = Math.min(T[j], 1000.0); - } - } else { - // Fall back to Wilson dK/dT - double Pbar = P[j] / 1e5; - double dsumKxdT = 0; - for (int i = 0; i < C; i++) { - double dKidT = K[j][i] * 5.37 * (1.0 + omega[i]) * Tc[i] / (T[j] * T[j]); - dsumKxdT += dKidT * x[i]; - } - if (Math.abs(dsumKxdT) > 1e-15) { - double deltaT = -(sumKx - 1.0) / dsumKxdT; - deltaT = Math.max(-20.0, Math.min(20.0, deltaT)); - maxDeltaT = Math.max(maxDeltaT, Math.abs(0.3 * deltaT)); - T[j] += 0.3 * deltaT; - T[j] = Math.max(T[j], 100.0); - T[j] = Math.min(T[j], 1000.0); - } - } + if (!Double.isNaN(fixedTemperature[j])) { + continue; + } + + // sumKx at current K (already computed above) + double sumKx = 0; + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + sumKx += K[j][i] * xi; + } + + // Perturbed EOS K at T+dT + double[] x = new double[C]; + for (int i = 0; i < C; i++) { + x[i] = liq[j][i] / Math.max(L[j], 1e-20); + } + SystemInterface pertSystem = referenceSystem.clone(); + pertSystem.setTemperature(T[j] + dTpert); + pertSystem.setPressure(P[j] / 1e5); + pertSystem.setTotalNumberOfMoles(1.0); + pertSystem.setMolarComposition(x); + pertSystem.setNumberOfPhases(2); + pertSystem.init(0); + + ThermodynamicOperations pertOps = new ThermodynamicOperations(pertSystem); + double sumKxPert = 0; + boolean pertOk = false; + try { + pertOps.TPflash(); + pertSystem.init(2); + if (pertSystem.getNumberOfPhases() >= 2) { + pertOk = true; + // Identify phases by density + int vapIdxP = 0; + int liqIdxP = 1; + if (pertSystem.getPhase(0).getDensity() > pertSystem.getPhase(1).getDensity()) { + vapIdxP = 1; + liqIdxP = 0; + } + for (int i = 0; i < C; i++) { + double fugL = pertSystem.getPhase(liqIdxP).getComponent(i).getFugacityCoefficient(); + double fugV = pertSystem.getPhase(vapIdxP).getComponent(i).getFugacityCoefficient(); + double Kpert = fugL / Math.max(fugV, 1e-30); + Kpert = Math.max(Kpert, 1e-15); + Kpert = Math.min(Kpert, 1e15); + sumKxPert += Kpert * x[i]; + } + } + } catch (Exception e) { + // pertOk stays false + } + + if (pertOk) { + double dsumKxdT = (sumKxPert - sumKx) / dTpert; + if (Math.abs(dsumKxdT) > 1e-15) { + double deltaT = -(sumKx - 1.0) / dsumKxdT; + double eosDampT = 0.5; + deltaT = Math.max(-30.0, Math.min(30.0, deltaT)); + maxDeltaT = Math.max(maxDeltaT, Math.abs(eosDampT * deltaT)); + T[j] += eosDampT * deltaT; + T[j] = Math.max(T[j], 100.0); + T[j] = Math.min(T[j], 1000.0); + } + } else { + // Fall back to Wilson dK/dT + double Pbar = P[j] / 1e5; + double dsumKxdT = 0; + for (int i = 0; i < C; i++) { + double dKidT = K[j][i] * 5.37 * (1.0 + omega[i]) * Tc[i] / (T[j] * T[j]); + dsumKxdT += dKidT * x[i]; + } + if (Math.abs(dsumKxdT) > 1e-15) { + double deltaT = -(sumKx - 1.0) / dsumKxdT; + deltaT = Math.max(-20.0, Math.min(20.0, deltaT)); + maxDeltaT = Math.max(maxDeltaT, Math.abs(0.3 * deltaT)); + T[j] += 0.3 * deltaT; + T[j] = Math.max(T[j], 100.0); + T[j] = Math.min(T[j], 1000.0); + } + } } // Step E: V from energy balance (Wang-Henke method) @@ -1967,113 +2000,116 @@ private boolean solveBubblePointMethod() { double maxDeltaV = 0; double dampV = 0.5; for (int j = 0; j < N; j++) { - if (j == 0 && hasReboiler && useOverallMBClosure) { - // Reboiler: with T-spec and no explicit boilup, close via overall MB. - L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); - continue; - } - if (j == 0 && hasReboiler && boilupRatio > 0) { - // Reboiler: V fixed by boilup specification - V[0] = boilupRatio * L[0]; - continue; - } - - double denominator = hV[j] - hL[j]; - if (Math.abs(denominator) < 1e-3) { - // hV ≈ hL means no phase split enthalpy difference — fall back to sum-rates - double sumKx = 0; - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - sumKx += K[j][i] * xi; - } - double newV = V[j] * sumKx; - newV = Math.max(newV, 1e-10); - double deltaV = Math.abs(newV - V[j]); - maxDeltaV = Math.max(maxDeltaV, deltaV); - V[j] = V[j] + dampV * (newV - V[j]); - continue; - } - - // H_in from tray below (vapor) and tray above (liquid) - double numerator = 0; - if (j < N - 1) { - // Liquid from tray above: L_{j+1} * (hL_{j+1} - hL_j) - numerator += L[j + 1] * (hL[j + 1] - hL[j]); - } - if (j > 0) { - // Vapor from tray below enters with hV_{j-1}, already mixed - // The energy balance formulation for BP method: - // V_j * hV_j + L_j * hL_j = V_{j-1} * hV_{j-1} + L_{j+1} * hL_{j+1} + F - // Rearranging: V_j = [V_{j-1}*hV_{j-1} + L_{j+1}*hL_{j+1} + F - L_j*hL_j] / - // hV_j - // But L_j = V_{j-1} + L_{j+1} + F_j - V_j from total mass balance - // This creates a coupling. Standard BP uses a simpler form. - } - - // Feed enthalpy: F_liq * (hF_liq - hL_j) + F_vap * (hF_vap - hL_j) - numerator += feedLTotal[j] * (feedHL[j] - hL[j]); - numerator += feedVTotal[j] * (feedHV[j] - hL[j]); - - // Heat duty on tray (Q_j) - numerator += Q[j]; - - // Also add V_{j-1} * (hV_{j-1} - hL_j) to numerator - // This is the standard formulation: Energy balance gives - // V_j = [L_{j+1}*(hL_{j+1}-hL_j) + V_{j-1}*(hV_{j-1}-hL_j) + F*hF - F*hL_j + Q] - // / (hV_j - hL_j) - if (j > 0) { - numerator += V[j - 1] * (hV[j - 1] - hL[j]); - } - - double newV = numerator / denominator; - newV = Math.max(newV, 1e-10); - - double deltaV = Math.abs(newV - V[j]); - maxDeltaV = Math.max(maxDeltaV, deltaV); - V[j] = V[j] + dampV * (newV - V[j]); + if (j == 0 && hasReboiler && useOverallMBClosure) { + // Reboiler: with T-spec and no explicit boilup, close via overall MB. + L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); + continue; + } + if (j == 0 && hasReboiler && boilupRatio > 0) { + // Reboiler: V fixed by boilup specification + V[0] = boilupRatio * L[0]; + continue; + } + + double denominator = hV[j] - hL[j]; + if (Math.abs(denominator) < 1e-3) { + // hV ≈ hL means no phase split enthalpy difference — fall back to sum-rates + double sumKx = 0; + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + sumKx += K[j][i] * xi; + } + double newV = V[j] * sumKx; + newV = Math.max(newV, 1e-10); + double deltaV = Math.abs(newV - V[j]); + maxDeltaV = Math.max(maxDeltaV, deltaV); + V[j] = V[j] + dampV * (newV - V[j]); + continue; + } + + // H_in from tray below (vapor) and tray above (liquid) + double numerator = 0; + if (j < N - 1) { + // Liquid from tray above: L_{j+1} * (hL_{j+1} - hL_j) + numerator += L[j + 1] * (hL[j + 1] - hL[j]); + } + if (j > 0) { + // Vapor from tray below enters with hV_{j-1}, already mixed + // The energy balance formulation for BP method: + // V_j * hV_j + L_j * hL_j = V_{j-1} * hV_{j-1} + L_{j+1} * hL_{j+1} + F + // Rearranging: V_j = [V_{j-1}*hV_{j-1} + L_{j+1}*hL_{j+1} + F - L_j*hL_j] / + // hV_j + // But L_j = V_{j-1} + L_{j+1} + F_j - V_j from total mass balance + // This creates a coupling. Standard BP uses a simpler form. + } + + // Feed enthalpy: F_liq * (hF_liq - hL_j) + F_vap * (hF_vap - hL_j) + numerator += feedLTotal[j] * (feedHL[j] - hL[j]); + numerator += feedVTotal[j] * (feedHV[j] - hL[j]); + + // Heat duty on tray (Q_j) + numerator += Q[j]; + + // Also add V_{j-1} * (hV_{j-1} - hL_j) to numerator + // This is the standard formulation: Energy balance gives + // V_j = [L_{j+1}*(hL_{j+1}-hL_j) + V_{j-1}*(hV_{j-1}-hL_j) + F*hF - F*hL_j + Q] + // / (hV_j - hL_j) + if (j > 0) { + numerator += V[j - 1] * (hV[j - 1] - hL[j]); + } + + double newV = numerator / denominator; + newV = Math.max(newV, 1e-10); + + double deltaV = Math.abs(newV - V[j]); + maxDeltaV = Math.max(maxDeltaV, deltaV); + V[j] = V[j] + dampV * (newV - V[j]); } // Check mass balance and equilibrium error (sumKx deviation from 1.0) double mbErr = computeMassBalanceError(); double maxSumKxErr = 0; for (int j = 0; j < N; j++) { - if (!Double.isNaN(fixedTemperature[j])) { - continue; - } - double sumKx = 0; - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - sumKx += K[j][i] * xi; - } - maxSumKxErr = Math.max(maxSumKxErr, Math.abs(sumKx - 1.0)); + if (!Double.isNaN(fixedTemperature[j])) { + continue; + } + double sumKx = 0; + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + sumKx += K[j][i] * xi; + } + maxSumKxErr = Math.max(maxSumKxErr, Math.abs(sumKx - 1.0)); } if (eosIter % 5 == 0 || eosIter < 5) { - double eErr = computeMaxRelativeEnergyError(); - logger.info("BP-EOS iter {}: dT={} dV={} mbErr={}% energy={}% sumKxErr={} T[0]={} T[N-1]={}", eosIter, - String.format("%.2f", maxDeltaT), String.format("%.2f", maxDeltaV), String.format("%.4f", mbErr * 100), - String.format("%.2f", eErr * 100), String.format("%.6f", maxSumKxErr), String.format("%.1f", T[0] - 273.15), - String.format("%.1f", T[N - 1] - 273.15)); + double eErr = computeMaxRelativeEnergyError(); + logger.info( + "BP-EOS iter {}: dT={} dV={} mbErr={}% energy={}% sumKxErr={} T[0]={} T[N-1]={}", + eosIter, String.format("%.2f", maxDeltaT), String.format("%.2f", maxDeltaV), + String.format("%.4f", mbErr * 100), String.format("%.2f", eErr * 100), + String.format("%.6f", maxSumKxErr), String.format("%.1f", T[0] - 273.15), + String.format("%.1f", T[N - 1] - 273.15)); } // Track best solution if (mbErr < bestEosMassBalErr) { - bestEosMassBalErr = mbErr; - saveTrayState(bestEosLiq, bestEosT, bestEosV); + bestEosMassBalErr = mbErr; + saveTrayState(bestEosLiq, bestEosT, bestEosV); } // Check convergence — require both mass balance AND equilibrium satisfied if (maxDeltaT < 0.1 && maxDeltaV < 0.5 && mbErr < 0.005 && maxSumKxErr < 0.01) { - logger.info("BP-EOS converged at iter {}: massBalErr={}% sumKxErr={}", eosIter, - String.format("%.4f", mbErr * 100), String.format("%.6f", maxSumKxErr)); - break; + logger.info("BP-EOS converged at iter {}: massBalErr={}% sumKxErr={}", eosIter, + String.format("%.4f", mbErr * 100), String.format("%.6f", maxSumKxErr)); + break; } // Divergence detection: if mass balance gets much worse than Wilson, revert if (mbErr > 0.5) { - logger.warn("BP-EOS diverging (massBalErr={}%), reverting to best", String.format("%.2f", mbErr * 100)); - restoreTrayState(bestEosLiq, bestEosT, bestEosV); - break; + logger.warn("BP-EOS diverging (massBalErr={}%), reverting to best", + String.format("%.2f", mbErr * 100)); + restoreTrayState(bestEosLiq, bestEosT, bestEosV); + break; } } @@ -2083,7 +2119,8 @@ private boolean solveBubblePointMethod() { // Final EOS evaluation evaluateThermo(); - logger.info("BP-EOS complete: bestMassBalErr={}%", String.format("%.4f", bestEosMassBalErr * 100)); + logger.info("BP-EOS complete: bestMassBalErr={}%", + String.format("%.4f", bestEosMassBalErr * 100)); // Phase 3: Energy balance temperature correction (Option B) // DISABLED: Sequential BP→energy correction is unconditionally unstable for @@ -2101,16 +2138,17 @@ private boolean solveBubblePointMethod() { * Phase 3: PH-flash temperature correction for energy balance. * *- * After the BP method converges on material balance/equilibrium, tray temperatures may be off because the BP method - * only solves MESH equations without the E (energy balance). This method computes the enthalpy of all streams - * entering each tray, mixes them, then PH-flashes to find the energy-consistent temperature. The resulting K-values - * and flows are fed back to the tridiagonal solver, and the process iterates until the temperature profile - * stabilizes. + * After the BP method converges on material balance/equilibrium, tray temperatures may be off + * because the BP method only solves MESH equations without the E (energy balance). This method + * computes the enthalpy of all streams entering each tray, mixes them, then PH-flashes to find + * the energy-consistent temperature. The resulting K-values and flows are fed back to the + * tridiagonal solver, and the process iterates until the temperature profile stabilizes. *
*/ private void phaseThreePHflashCorrection() { - System.out.println("Phase 3: Newton-E T correction. T[0]=" + String.format("%.2f", T[0] - 273.15) + " T[N-1]=" - + String.format("%.2f", T[N - 1] - 273.15)); + System.out + .println("Phase 3: Newton-E T correction. T[0]=" + String.format("%.2f", T[0] - 273.15) + + " T[N-1]=" + String.format("%.2f", T[N - 1] - 273.15)); int maxIter = 40; double dampE = 0.3; @@ -2138,90 +2176,92 @@ private void phaseThreePHflashCorrection() { double maxEErr = 0; for (int j = 0; j < N; j++) { - maxEErr = Math.max(maxEErr, Math.abs(energyErr[j])); - if (!Double.isNaN(fixedTemperature[j])) { - continue; - } - - // Numerical dE/dT: perturb T[j], recompute, get derivative - double t0 = T[j]; - T[j] = t0 + pertDT; - evaluateThermoForTray(j); - double[] energyErrP = computeEnergyErrors(); - double dEdT = (energyErrP[j] - energyErr[j]) / pertDT; - T[j] = t0; // restore - evaluateThermoForTray(j); - - if (Math.abs(dEdT) < 1e-10) { - continue; - } - - double deltaT = -energyErr[j] / dEdT; - deltaT = Math.max(-maxClamp, Math.min(maxClamp, deltaT)); - deltaT *= dampE; - maxDeltaT = Math.max(maxDeltaT, Math.abs(deltaT)); - - if (iter == 0) { - System.out.println(" Tray " + j + ": T=" + String.format("%.1f", T[j] - 273.15) + " E=" - + String.format("%.0f", energyErr[j]) + " dE/dT=" + String.format("%.0f", dEdT) + " dT=" - + String.format("%.2f", deltaT)); - } - - T[j] += deltaT; - T[j] = Math.max(T[j], 200.0); - T[j] = Math.min(T[j], 800.0); + maxEErr = Math.max(maxEErr, Math.abs(energyErr[j])); + if (!Double.isNaN(fixedTemperature[j])) { + continue; + } + + // Numerical dE/dT: perturb T[j], recompute, get derivative + double t0 = T[j]; + T[j] = t0 + pertDT; + evaluateThermoForTray(j); + double[] energyErrP = computeEnergyErrors(); + double dEdT = (energyErrP[j] - energyErr[j]) / pertDT; + T[j] = t0; // restore + evaluateThermoForTray(j); + + if (Math.abs(dEdT) < 1e-10) { + continue; + } + + double deltaT = -energyErr[j] / dEdT; + deltaT = Math.max(-maxClamp, Math.min(maxClamp, deltaT)); + deltaT *= dampE; + maxDeltaT = Math.max(maxDeltaT, Math.abs(deltaT)); + + if (iter == 0) { + System.out.println(" Tray " + j + ": T=" + String.format("%.1f", T[j] - 273.15) + " E=" + + String.format("%.0f", energyErr[j]) + " dE/dT=" + String.format("%.0f", dEdT) + + " dT=" + String.format("%.2f", deltaT)); + } + + T[j] += deltaT; + T[j] = Math.max(T[j], 200.0); + T[j] = Math.min(T[j], 800.0); } // Inner BP loop: reconverge material balance at new temperatures evaluateThermo(); for (int inner = 0; inner < innerBP; inner++) { - for (int comp = 0; comp < C; comp++) { - solveTridiagonalForComponent(comp); - } - for (int j = 0; j < N; j++) { - double sumL = 0; - for (int i = 0; i < C; i++) { - sumL += liq[j][i]; - } - L[j] = Math.max(sumL, 1e-20); - } - for (int j = 0; j < N; j++) { - double sumKx = 0; - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - sumKx += K[j][i] * xi; - } - V[j] = Math.max(V[j] * sumKx, 1e-10); - } - if (hasReboiler && useOverallMBClosure) { - L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); - } else if (hasReboiler && boilupRatio > 0 && Double.isNaN(fixedTemperature[0])) { - V[0] = boilupRatio * L[0]; - } - evaluateThermo(); + for (int comp = 0; comp < C; comp++) { + solveTridiagonalForComponent(comp); + } + for (int j = 0; j < N; j++) { + double sumL = 0; + for (int i = 0; i < C; i++) { + sumL += liq[j][i]; + } + L[j] = Math.max(sumL, 1e-20); + } + for (int j = 0; j < N; j++) { + double sumKx = 0; + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + sumKx += K[j][i] * xi; + } + V[j] = Math.max(V[j] * sumKx, 1e-10); + } + if (hasReboiler && useOverallMBClosure) { + L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); + } else if (hasReboiler && boilupRatio > 0 && Double.isNaN(fixedTemperature[0])) { + V[0] = boilupRatio * L[0]; + } + evaluateThermo(); } double mbErr = computeMassBalanceError(); System.out.println("E-iter " + iter + ": maxDT=" + String.format("%.3f", maxDeltaT) + " maxE=" - + String.format("%.0f", maxEErr) + " mb=" + String.format("%.4f%%", mbErr * 100) + " T[0]=" - + String.format("%.1f", T[0] - 273.15) + " T[N-1]=" + String.format("%.1f", T[N - 1] - 273.15)); + + String.format("%.0f", maxEErr) + " mb=" + String.format("%.4f%%", mbErr * 100) + + " T[0]=" + String.format("%.1f", T[0] - 273.15) + " T[N-1]=" + + String.format("%.1f", T[N - 1] - 273.15)); if (mbErr < bestMbErr || (mbErr < 0.005 && maxDeltaT < 1.0)) { - bestMbErr = mbErr; - saveTrayState(bestLiq, bestT, bestV); + bestMbErr = mbErr; + saveTrayState(bestLiq, bestT, bestV); } if (maxDeltaT < 0.1 && mbErr < 0.005) { - System.out.println("Newton-E CONVERGED at iter " + iter); - break; + System.out.println("Newton-E CONVERGED at iter " + iter); + break; } if (mbErr > 1.0) { - System.out.println("Newton-E diverging (mb=" + String.format("%.1f%%", mbErr * 100) + "), reducing damping"); - restoreTrayState(bestLiq, bestT, bestV); - evaluateThermo(); - dampE *= 0.5; - maxClamp = Math.max(maxClamp * 0.5, 2.0); + System.out.println("Newton-E diverging (mb=" + String.format("%.1f%%", mbErr * 100) + + "), reducing damping"); + restoreTrayState(bestLiq, bestT, bestV); + evaluateThermo(); + dampE *= 0.5; + maxClamp = Math.max(maxClamp * 0.5, 2.0); } } @@ -2229,16 +2269,17 @@ private void phaseThreePHflashCorrection() { evaluateThermo(); System.out.println("Phase 3 done: T[0]=" + String.format("%.2f", T[0] - 273.15) + " T[N-1]=" - + String.format("%.2f", T[N - 1] - 273.15) + " mb=" + String.format("%.4f%%", bestMbErr * 100)); + + String.format("%.2f", T[N - 1] - 273.15) + " mb=" + + String.format("%.4f%%", bestMbErr * 100)); } /** * Sum-Rates (SR) energy balance correction phase. * *- * After the BP method converges on material balance, tray temperatures may be off because BP determines T from - * bubble-point (sum Kx = 1), which is inappropriate for wide-boiling / absorber-type columns. The SR method instead - * determines T from the energy balance: + * After the BP method converges on material balance, tray temperatures may be off because BP + * determines T from bubble-point (sum Kx = 1), which is inappropriate for wide-boiling / + * absorber-type columns. The SR method instead determines T from the energy balance: *
*- * Reference: Seader, Henley & Roper, "Separation Process Principles", Chapter 10.4 — Sum-Rates method for - * absorber/stripper columns. + * Reference: Seader, Henley & Roper, "Separation Process Principles", Chapter 10.4 — + * Sum-Rates method for absorber/stripper columns. *
*/ private void solveSumRatesPhase() { - System.out.println("[SR] Starting Sum-Rates energy correction. T[0]=" + String.format("%.1f", T[0] - 273.15) - + "C T[N-1]=" + String.format("%.1f", T[N - 1] - 273.15) + "C"); + System.out.println( + "[SR] Starting Sum-Rates energy correction. T[0]=" + String.format("%.1f", T[0] - 273.15) + + "C T[N-1]=" + String.format("%.1f", T[N - 1] - 273.15) + "C"); // Print feed enthalpy diagnostics for (int j = 0; j < N; j++) { double feedMolesJ = 0; for (int i = 0; i < C; i++) { - feedMolesJ += feedLiq[j][i] + feedVap[j][i]; + feedMolesJ += feedLiq[j][i] + feedVap[j][i]; } if (feedMolesJ > 1e-10) { - System.out.println("[SR-FEED] tray " + j + ": feedLTotal=" + String.format("%.1f", feedLTotal[j]) + " feedHL=" - + String.format("%.1f", feedHL[j]) + " feedVTotal=" + String.format("%.1f", feedVTotal[j]) + " feedHV=" - + String.format("%.1f", feedHV[j]) + " feedEnthalpy=" - + String.format("%.0f", feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j])); + System.out.println("[SR-FEED] tray " + j + ": feedLTotal=" + + String.format("%.1f", feedLTotal[j]) + " feedHL=" + String.format("%.1f", feedHL[j]) + + " feedVTotal=" + String.format("%.1f", feedVTotal[j]) + " feedHV=" + + String.format("%.1f", feedHV[j]) + " feedEnthalpy=" + + String.format("%.0f", feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j])); } } - logger.info("SR: starting Sum-Rates energy correction. T[0]={} T[N-1]={}", String.format("%.1f", T[0] - 273.15), - String.format("%.1f", T[N - 1] - 273.15)); + logger.info("SR: starting Sum-Rates energy correction. T[0]={} T[N-1]={}", + String.format("%.1f", T[0] - 273.15), String.format("%.1f", T[N - 1] - 273.15)); // Save BP solution as fallback double[][] bpLiq = new double[N][C]; @@ -2300,17 +2343,17 @@ private void solveSumRatesPhase() { double maxTrayTemp = 0.0; for (int j = 0; j < N; j++) { if (!Double.isNaN(fixedTemperature[j])) { - maxTrayTemp = Math.max(maxTrayTemp, fixedTemperature[j]); + maxTrayTemp = Math.max(maxTrayTemp, fixedTemperature[j]); } double feedMoles = 0; for (int i = 0; i < C; i++) { - feedMoles += feedLiq[j][i] + feedVap[j][i]; + feedMoles += feedLiq[j][i] + feedVap[j][i]; } if (feedMoles > 1e-10) { - // Use feed enthalpy to estimate feed temperature - // For now, use the BP starting temperatures as bounds - minTrayTemp = Math.min(minTrayTemp, bpT[j]); - maxTrayTemp = Math.max(maxTrayTemp, bpT[j]); + // Use feed enthalpy to estimate feed temperature + // For now, use the BP starting temperatures as bounds + minTrayTemp = Math.min(minTrayTemp, bpT[j]); + maxTrayTemp = Math.max(maxTrayTemp, bpT[j]); } } // Add margins @@ -2321,7 +2364,7 @@ private void solveSumRatesPhase() { double bpBotT = bpT[0]; minTrayTemp = Math.max(bpTopT - 30.0, 200.0); logger.info("SR: T bounds: min={}C max={}C", String.format("%.1f", minTrayTemp - 273.15), - String.format("%.1f", maxTrayTemp - 273.15)); + String.format("%.1f", maxTrayTemp - 273.15)); for (int iter = 0; iter < maxIter; iter++) { // Step 1: Update K-values and enthalpies at current T, x @@ -2329,7 +2372,7 @@ private void solveSumRatesPhase() { // Step 2: Tridiagonal M-solve for each component (BP-style) for (int comp = 0; comp < C; comp++) { - solveTridiagonalForComponent(comp); + solveTridiagonalForComponent(comp); } // Step 3: Proper Sum-Rates L/V update. @@ -2349,11 +2392,11 @@ private void solveSumRatesPhase() { double[] newV = new double[N]; double srLVDamp = 0.6; // blending factor for L/V updates for (int j = 0; j < N; j++) { - double sumL = 0; - for (int i = 0; i < C; i++) { - sumL += liq[j][i]; - } - newL[j] = Math.max(sumL, 1e-20); + double sumL = 0; + for (int i = 0; i < C; i++) { + sumL += liq[j][i]; + } + newL[j] = Math.max(sumL, 1e-20); } // Note: do NOT anchor newL[0] from overall-MB. // M-equations are component-conservative; sum_i gives newL[j] that @@ -2361,36 +2404,37 @@ private void solveSumRatesPhase() { // (totalFeed - V[N-1]) creates a conflict that prevents the cascade // from converging (forces newV[N-1] == V_curr[N-1]). if (hasReboiler && boilupRatio > 0) { - // V[0] = boilupRatio * L[0] gets enforced below by cascade + // V[0] = boilupRatio * L[0] gets enforced below by cascade } // Reboiler vapor anchor: V[0] = L[1] + feed[0] - L[0] newV[0] = Math.max(newL[1] + feedLTotal[0] + feedVTotal[0] - newL[0], 1e-10); // Cascade up: V[j] = V[j-1] + L[j+1] - L[j] + feed[j], j=1..N-2 for (int j = 1; j < N - 1; j++) { - double netFeed = feedLTotal[j] + feedVTotal[j]; - newV[j] = Math.max(newV[j - 1] + newL[j + 1] - newL[j] + netFeed, 1e-10); + double netFeed = feedLTotal[j] + feedVTotal[j]; + newV[j] = Math.max(newV[j - 1] + newL[j + 1] - newL[j] + netFeed, 1e-10); } // Top: V[N-1] = V[N-2] + feed[N-1] - L[N-1] (no L[N]) - newV[N - 1] = Math.max(newV[N - 2] + feedLTotal[N - 1] + feedVTotal[N - 1] - newL[N - 1], 1e-10); + newV[N - 1] = + Math.max(newV[N - 2] + feedLTotal[N - 1] + feedVTotal[N - 1] - newL[N - 1], 1e-10); // Blend with current to damp the change (stability) for (int j = 0; j < N; j++) { - double blendedL = (1.0 - srLVDamp) * L[j] + srLVDamp * newL[j]; - double scale = blendedL / Math.max(L[j], 1e-20); - L[j] = Math.max(blendedL, 1e-20); - // Scale component liquid flows to match new L[j] - for (int i = 0; i < C; i++) { - liq[j][i] *= scale; - } - V[j] = Math.max((1.0 - srLVDamp) * V[j] + srLVDamp * newV[j], 1e-10); + double blendedL = (1.0 - srLVDamp) * L[j] + srLVDamp * newL[j]; + double scale = blendedL / Math.max(L[j], 1e-20); + L[j] = Math.max(blendedL, 1e-20); + // Scale component liquid flows to match new L[j] + for (int i = 0; i < C; i++) { + liq[j][i] *= scale; + } + V[j] = Math.max((1.0 - srLVDamp) * V[j] + srLVDamp * newV[j], 1e-10); } // Update vapor compositions for (int j = 0; j < N; j++) { - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(L[j], 1e-20); - vap[j][i] = K[j][i] * xi * V[j]; - } + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Math.max(L[j], 1e-20); + vap[j][i] = K[j][i] * xi * V[j]; + } } // Step 4: Refresh enthalpies at updated compositions @@ -2402,65 +2446,66 @@ private void solveSumRatesPhase() { double maxAbsEErr = 0; for (int j = 0; j < N; j++) { - maxAbsEErr = Math.max(maxAbsEErr, Math.abs(energyErr[j])); - - if (!Double.isNaN(fixedTemperature[j])) { - continue; // skip fixed-T trays (reboiler) - } - - // Numerical dE/dT by perturbation - double t0 = T[j]; - T[j] = t0 + pertDT; - evaluateThermoForTray(j); - double[] energyErrP = computeEnergyErrors(); - double dEdT = (energyErrP[j] - energyErr[j]) / pertDT; - T[j] = t0; - evaluateThermoForTray(j); // restore - - if (Math.abs(dEdT) < 1e-6) { - if (iter == 0) { - System.out - .println("[SR-DIAG] tray " + j + ": dEdT too small (" + String.format("%.6f", dEdT) + "), skipping"); - } - continue; - } - - double rawDeltaT = -energyErr[j] / dEdT; - - // Diagnostic: print full details on first iteration - if (iter == 0) { - double hOutJ = V[j] * hV[j] + L[j] * hL[j]; - double hInJ = 0; - if (j > 0) { - hInJ += V[j - 1] * hV[j - 1]; - } - if (j < N - 1) { - hInJ += L[j + 1] * hL[j + 1]; - } - double feedH = feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j]; - double hInTotal = hInJ + feedH; - System.out.println("[SR-DIAG] tray " + j + ": T=" + String.format("%.1fC", T[j] - 273.15) + " hV=" - + String.format("%.1f", hV[j]) + " hL=" + String.format("%.1f", hL[j]) + " V=" - + String.format("%.1f", V[j]) + " L=" + String.format("%.1f", L[j]) + " Hout=" - + String.format("%.0f", hOutJ) + " Hin(noFeed)=" + String.format("%.0f", hInJ) + " feedH=" - + String.format("%.0f", feedH) + " Hin(total)=" + String.format("%.0f", hInTotal) + " Ej=" - + String.format("%.0f", energyErr[j]) + " dEdT=" + String.format("%.1f", dEdT) + " rawDT=" - + String.format("%.2f", rawDeltaT)); - } - - double deltaT = Math.max(-maxClampT, Math.min(maxClampT, rawDeltaT)); - deltaT *= dampT; - maxDeltaT = Math.max(maxDeltaT, Math.abs(deltaT)); - - T[j] += deltaT; - T[j] = Math.max(T[j], minTrayTemp); - T[j] = Math.min(T[j], maxTrayTemp); + maxAbsEErr = Math.max(maxAbsEErr, Math.abs(energyErr[j])); + + if (!Double.isNaN(fixedTemperature[j])) { + continue; // skip fixed-T trays (reboiler) + } + + // Numerical dE/dT by perturbation + double t0 = T[j]; + T[j] = t0 + pertDT; + evaluateThermoForTray(j); + double[] energyErrP = computeEnergyErrors(); + double dEdT = (energyErrP[j] - energyErr[j]) / pertDT; + T[j] = t0; + evaluateThermoForTray(j); // restore + + if (Math.abs(dEdT) < 1e-6) { + if (iter == 0) { + System.out.println("[SR-DIAG] tray " + j + ": dEdT too small (" + + String.format("%.6f", dEdT) + "), skipping"); + } + continue; + } + + double rawDeltaT = -energyErr[j] / dEdT; + + // Diagnostic: print full details on first iteration + if (iter == 0) { + double hOutJ = V[j] * hV[j] + L[j] * hL[j]; + double hInJ = 0; + if (j > 0) { + hInJ += V[j - 1] * hV[j - 1]; + } + if (j < N - 1) { + hInJ += L[j + 1] * hL[j + 1]; + } + double feedH = feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j]; + double hInTotal = hInJ + feedH; + System.out.println("[SR-DIAG] tray " + j + ": T=" + String.format("%.1fC", T[j] - 273.15) + + " hV=" + String.format("%.1f", hV[j]) + " hL=" + String.format("%.1f", hL[j]) + + " V=" + String.format("%.1f", V[j]) + " L=" + String.format("%.1f", L[j]) + " Hout=" + + String.format("%.0f", hOutJ) + " Hin(noFeed)=" + String.format("%.0f", hInJ) + + " feedH=" + String.format("%.0f", feedH) + " Hin(total)=" + + String.format("%.0f", hInTotal) + " Ej=" + String.format("%.0f", energyErr[j]) + + " dEdT=" + String.format("%.1f", dEdT) + " rawDT=" + + String.format("%.2f", rawDeltaT)); + } + + double deltaT = Math.max(-maxClampT, Math.min(maxClampT, rawDeltaT)); + deltaT *= dampT; + maxDeltaT = Math.max(maxDeltaT, Math.abs(deltaT)); + + T[j] += deltaT; + T[j] = Math.max(T[j], minTrayTemp); + T[j] = Math.min(T[j], maxTrayTemp); } // Adaptive damping: increase damping if making progress steadily if (iter > 20 && maxDeltaT < maxClampT * 0.5) { - dampT = Math.min(dampT * 1.05, 0.5); - maxClampT = Math.min(maxClampT * 1.05, 8.0); + dampT = Math.min(dampT * 1.05, 0.5); + maxClampT = Math.min(maxClampT * 1.05, 8.0); } // Check convergence @@ -2468,43 +2513,45 @@ private void solveSumRatesPhase() { double eErr = computeMaxRelativeEnergyError(); if (iter % 10 == 0 || iter < 5) { - System.out.println("[SR] iter " + iter + ": dT=" + String.format("%.3f", maxDeltaT) + " mb=" - + String.format("%.4f%%", mbErr * 100) + " energy=" + String.format("%.1f%%", eErr * 100) + " T[0]=" - + String.format("%.1f", T[0] - 273.15) + " T[top]=" + String.format("%.1f", T[N - 1] - 273.15) + " damp=" - + String.format("%.3f", dampT)); - logger.info("SR iter {}: maxDT={} mbErr={}% energy={}% T[0]={} T[N-1]={} damp={}", iter, - String.format("%.3f", maxDeltaT), String.format("%.4f", mbErr * 100), String.format("%.2f", eErr * 100), - String.format("%.1f", T[0] - 273.15), String.format("%.1f", T[N - 1] - 273.15), - String.format("%.3f", dampT)); + System.out.println("[SR] iter " + iter + ": dT=" + String.format("%.3f", maxDeltaT) + " mb=" + + String.format("%.4f%%", mbErr * 100) + " energy=" + + String.format("%.1f%%", eErr * 100) + " T[0]=" + String.format("%.1f", T[0] - 273.15) + + " T[top]=" + String.format("%.1f", T[N - 1] - 273.15) + " damp=" + + String.format("%.3f", dampT)); + logger.info("SR iter {}: maxDT={} mbErr={}% energy={}% T[0]={} T[N-1]={} damp={}", iter, + String.format("%.3f", maxDeltaT), String.format("%.4f", mbErr * 100), + String.format("%.2f", eErr * 100), String.format("%.1f", T[0] - 273.15), + String.format("%.1f", T[N - 1] - 273.15), String.format("%.3f", dampT)); } // Track best solution (minimize energy error while keeping mass OK) if (eErr < bestEnergyErr || (mbErr < 0.01 && eErr < bestEnergyErr * 1.05)) { - bestEnergyErr = eErr; - bestMbErr = mbErr; - saveTrayState(bestLiq, bestT, bestV); + bestEnergyErr = eErr; + bestMbErr = mbErr; + saveTrayState(bestLiq, bestT, bestV); } // Convergence: T stable and mass balance acceptable if (maxDeltaT < 0.05 && mbErr < 0.01) { - logger.info("SR converged at iter {}: mbErr={}% energy={}%", iter, String.format("%.4f", mbErr * 100), - String.format("%.2f", eErr * 100)); - break; + logger.info("SR converged at iter {}: mbErr={}% energy={}%", iter, + String.format("%.4f", mbErr * 100), String.format("%.2f", eErr * 100)); + break; } // Divergence check: if mass balance deteriorates badly, reduce damping if (mbErr > 5.0) { - System.out - .println("[SR] mass balance degraded to " + String.format("%.1f%%", mbErr * 100) + " — reducing damping"); - logger.warn("SR: mass balance degraded to {}%, reducing damping", String.format("%.1f", mbErr * 100)); - restoreTrayState(bestLiq, bestT, bestV); - evaluateThermo(); - dampT *= 0.5; - maxClampT = Math.max(maxClampT * 0.5, 1.0); - if (dampT < 0.01) { - logger.info("SR: damping too low ({} ), stopping", String.format("%.3f", dampT)); - break; - } + System.out.println("[SR] mass balance degraded to " + String.format("%.1f%%", mbErr * 100) + + " — reducing damping"); + logger.warn("SR: mass balance degraded to {}%, reducing damping", + String.format("%.1f", mbErr * 100)); + restoreTrayState(bestLiq, bestT, bestV); + evaluateThermo(); + dampT *= 0.5; + maxClampT = Math.max(maxClampT * 0.5, 1.0); + if (dampT < 0.01) { + logger.info("SR: damping too low ({} ), stopping", String.format("%.3f", dampT)); + break; + } } } @@ -2514,12 +2561,12 @@ private void solveSumRatesPhase() { double finalMb = computeMassBalanceError(); double finalE = computeMaxRelativeEnergyError(); - System.out.println( - "[SR] Done: mb=" + String.format("%.4f%%", finalMb * 100) + " energy=" + String.format("%.1f%%", finalE * 100) - + " T[0]=" + String.format("%.1f", T[0] - 273.15) + " T[top]=" + String.format("%.1f", T[N - 1] - 273.15)); - logger.info("SR Phase done: mbErr={}% energy={}% T[0]={} T[N-1]={}", String.format("%.4f", finalMb * 100), - String.format("%.2f", finalE * 100), String.format("%.1f", T[0] - 273.15), - String.format("%.1f", T[N - 1] - 273.15)); + System.out.println("[SR] Done: mb=" + String.format("%.4f%%", finalMb * 100) + " energy=" + + String.format("%.1f%%", finalE * 100) + " T[0]=" + String.format("%.1f", T[0] - 273.15) + + " T[top]=" + String.format("%.1f", T[N - 1] - 273.15)); + logger.info("SR Phase done: mbErr={}% energy={}% T[0]={} T[N-1]={}", + String.format("%.4f", finalMb * 100), String.format("%.2f", finalE * 100), + String.format("%.1f", T[0] - 273.15), String.format("%.1f", T[N - 1] - 273.15)); // If SR solution is worse than BP in both metrics, revert to BP if (finalMb > bpMbErr * 5 && finalE > bpEnergyErr) { @@ -2534,8 +2581,8 @@ private void solveSumRatesPhase() { * Compute the maximum relative energy balance error across all non-fixed trays. * *- * The relative error for each tray is |E_j| / max(|H_out_j|, |H_in_j|). This gives a dimensionless metric comparable - * to the mass balance error fraction. + * The relative error for each tray is |E_j| / max(|H_out_j|, |H_in_j|). This gives a + * dimensionless metric comparable to the mass balance error fraction. *
* * @return maximum relative energy error (0.0 = perfect, 1.0 = 100% imbalance) @@ -2545,20 +2592,20 @@ private double computeMaxRelativeEnergyError() { double maxRelErr = 0; for (int j = 0; j < N; j++) { if (!Double.isNaN(fixedTemperature[j])) { - continue; // skip fixed-T trays (reboiler) — they have a duty + continue; // skip fixed-T trays (reboiler) — they have a duty } double hOut = Math.abs(V[j] * hV[j] + L[j] * hL[j]); double hIn = 0; if (j > 0) { - hIn += Math.abs(V[j - 1] * hV[j - 1]); + hIn += Math.abs(V[j - 1] * hV[j - 1]); } if (j < N - 1) { - hIn += Math.abs(L[j + 1] * hL[j + 1]); + hIn += Math.abs(L[j + 1] * hL[j + 1]); } double scale = Math.max(hOut, hIn); if (scale > 1e-10) { - double relErr = Math.abs(eErr[j]) / scale; - maxRelErr = Math.max(maxRelErr, relErr); + double relErr = Math.abs(eErr[j]) / scale; + maxRelErr = Math.max(maxRelErr, relErr); } } return maxRelErr; @@ -2568,8 +2615,8 @@ private double computeMaxRelativeEnergyError() { * Compute energy balance error for each tray. * *- * E_j = H_out_j - H_in_j where H_out_j = V[j]*hV[j] + L[j]*hL[j] and H_in_j = V[j-1]*hV[j-1] + L[j+1]*hL[j+1] + - * feedEnthalpy[j]. + * E_j = H_out_j - H_in_j where H_out_j = V[j]*hV[j] + L[j]*hL[j] and H_in_j = V[j-1]*hV[j-1] + + * L[j+1]*hL[j+1] + feedEnthalpy[j]. *
* * @return array of energy errors per tray in J @@ -2580,18 +2627,18 @@ private double[] computeEnergyErrors() { double hOut = V[j] * hV[j] + L[j] * hL[j]; double hIn = 0; if (j > 0) { - hIn += V[j - 1] * hV[j - 1]; + hIn += V[j - 1] * hV[j - 1]; } if (j < N - 1) { - hIn += L[j + 1] * hL[j + 1]; + hIn += L[j + 1] * hL[j + 1]; } // Feed enthalpy double feedMolesJ = 0; for (int i = 0; i < C; i++) { - feedMolesJ += feedLiq[j][i] + feedVap[j][i]; + feedMolesJ += feedLiq[j][i] + feedVap[j][i]; } if (feedMolesJ > 1e-10) { - hIn += feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j]; + hIn += feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j]; } eErr[j] = hOut - hIn; } @@ -2602,8 +2649,8 @@ private double[] computeEnergyErrors() { * Solve the tridiagonal material balance for a single component. * *- * For component i, the material balance on tray j gives: a_j * l_{i,j-1} + b_j * l_{i,j} + c_j * l_{i,j+1} = d_j - * where a_j = -K_{i,j-1}*V_{j-1}/L_{j-1}, b_j = 1 + K_{i,j}*V_j/L_j, c_j = -1 + * For component i, the material balance on tray j gives: a_j * l_{i,j-1} + b_j * l_{i,j} + c_j * + * l_{i,j+1} = d_j where a_j = -K_{i,j-1}*V_{j-1}/L_{j-1}, b_j = 1 + K_{i,j}*V_j/L_j, c_j = -1 *
* * @param comp component index @@ -2629,7 +2676,7 @@ private void solveTridiagonalForComponent(int comp) { for (int j = 1; j < N; j++) { double m = b[j] - a[j] * cp[j - 1]; if (Math.abs(m) < 1e-30) { - m = 1e-30; + m = 1e-30; } cp[j] = c[j] / m; dp[j] = (d[j] - a[j] * dp[j - 1]) / m; @@ -2676,15 +2723,15 @@ private double solveBubblePointTemperature(int j) { double fMid = sumKxWilson(x, Tmid, P[j]) - 1.0; if (Math.abs(fMid) < 1e-8 || (Thigh - Tlow) < 0.001) { - return Tmid; + return Tmid; } if (fMid * fLow < 0) { - Thigh = Tmid; - fHigh = fMid; + Thigh = Tmid; + fHigh = fMid; } else { - Tlow = Tmid; - fLow = fMid; + Tlow = Tmid; + fLow = fMid; } } @@ -2728,12 +2775,13 @@ private double sumKxWilson(double[] x, double temp, double press) { * Variable ordering per tray: [l_{0,j}, l_{1,j}, ..., l_{C-1,j}, T_j, V_j] * *- * Material balance for component i on tray j: M_{i,j} = l_{i,j}(1 + s_{i,j}) - l_{i,j-1} - v_{i,j+1} - f_{i,j} = 0 - * where s_{i,j} = K_{i,j} * V_j / L_j is the stripping factor, and v_{i,j} = K_{i,j} * l_{i,j} * V_j / L_j + * Material balance for component i on tray j: M_{i,j} = l_{i,j}(1 + s_{i,j}) - l_{i,j-1} - + * v_{i,j+1} - f_{i,j} = 0 where s_{i,j} = K_{i,j} * V_j / L_j is the stripping factor, and + * v_{i,j} = K_{i,j} * l_{i,j} * V_j / L_j * *
- * Energy balance for tray j: H_j = L_j*h_L_j + V_j*h_V_j - L_{j-1}*h_L_{j-1} - V_{j+1}*h_V_{j+1} - F_L_j*h_FL_j - - * F_V_j*h_FV_j - Q_j = 0 + * Energy balance for tray j: H_j = L_j*h_L_j + V_j*h_V_j - L_{j-1}*h_L_{j-1} - V_{j+1}*h_V_{j+1} + * - F_L_j*h_FL_j - F_V_j*h_FV_j - Q_j = 0 * *
* Summation: S_j = sum_i(y_{i,j}) - 1 = sum_i(K_{i,j}*x_{i,j}) - 1 = 0 @@ -2751,58 +2799,58 @@ private double[] computeResidual() { // Material balances: C equations for (int i = 0; i < C; i++) { - double Mij = liq[j][i] + vap[j][i]; // leaving tray j + double Mij = liq[j][i] + vap[j][i]; // leaving tray j - // Liquid from tray above (j+1) - if (j < N - 1) { - Mij -= liq[j + 1][i]; - } + // Liquid from tray above (j+1) + if (j < N - 1) { + Mij -= liq[j + 1][i]; + } - // Vapor from tray below (j-1) - if (j > 0) { - Mij -= vap[j - 1][i]; - } + // Vapor from tray below (j-1) + if (j > 0) { + Mij -= vap[j - 1][i]; + } - // Feed - Mij -= feedLiq[j][i] + feedVap[j][i]; + // Feed + Mij -= feedLiq[j][i] + feedVap[j][i]; - F[base + i] = Mij / flowScale; + F[base + i] = Mij / flowScale; } // Energy balance (or fixed temperature specification) if (!Double.isNaN(fixedTemperature[j])) { - // Fixed temperature: residual is T_j - T_spec - F[base + C] = (T[j] - fixedTemperature[j]) / tempScale; + // Fixed temperature: residual is T_j - T_spec + F[base + C] = (T[j] - fixedTemperature[j]) / tempScale; } else { - double Hj = Lj * hL[j] + V[j] * hV[j]; + double Hj = Lj * hL[j] + V[j] * hV[j]; - if (j < N - 1) { - Hj -= L[j + 1] * hL[j + 1]; - } - if (j > 0) { - Hj -= V[j - 1] * hV[j - 1]; - } + if (j < N - 1) { + Hj -= L[j + 1] * hL[j + 1]; + } + if (j > 0) { + Hj -= V[j - 1] * hV[j - 1]; + } - // Feed enthalpies - Hj -= feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j]; + // Feed enthalpies + Hj -= feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j]; - // Heat duty - Hj -= Q[j]; + // Heat duty + Hj -= Q[j]; - // Scale energy equation: total tray throughput * latent heat gives the - // characteristic energy magnitude. This brings H residuals to ~O(1) - // consistent with scaled material balances. - double trayFlow = Math.max(Lj + V[j], flowScale); - double latentHeat = Math.max(Math.abs(hV[j] - hL[j]), 1e3); - double energyScale = trayFlow * latentHeat; - F[base + C] = Hj / energyScale; + // Scale energy equation: total tray throughput * latent heat gives the + // characteristic energy magnitude. This brings H residuals to ~O(1) + // consistent with scaled material balances. + double trayFlow = Math.max(Lj + V[j], flowScale); + double latentHeat = Math.max(Math.abs(hV[j] - hL[j]), 1e3); + double energyScale = trayFlow * latentHeat; + F[base + C] = Hj / energyScale; } // Summation equation: sum(K_{i,j} * x_{i,j}) - 1 = 0 double sumKx = 0; for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Math.max(Lj, 1e-20); - sumKx += K[j][i] * xi; + double xi = liq[j][i] / Math.max(Lj, 1e-20); + sumKx += K[j][i] * xi; } F[base + C + 1] = sumKx - 1.0; } @@ -2814,9 +2862,10 @@ private double[] computeResidual() { * Compute the Jacobian matrix numerically using finite differences. * *
- * The Jacobian has block-tridiagonal structure. Only entries in the tri-diagonal bands are computed. For variable k - * on tray jj, we compute d(residual_j)/d(var_{jj,k}) for j in {jj-1, jj, jj+1}. After perturbing a variable on tray - * jj, only tray jj's thermo is re-evaluated (K-values, enthalpies depend only on local T and x). + * The Jacobian has block-tridiagonal structure. Only entries in the tri-diagonal bands are + * computed. For variable k on tray jj, we compute d(residual_j)/d(var_{jj,k}) for j in {jj-1, jj, + * jj+1}. After perturbing a variable on tray jj, only tray jj's thermo is re-evaluated (K-values, + * enthalpies depend only on local T and x). *
* * @param F0 current residual vector @@ -2830,32 +2879,32 @@ private double[][] computeJacobian(double[] F0) { for (int jj = 0; jj < N; jj++) { for (int k = 0; k < varsPerTray; k++) { - int varIdx = jj * varsPerTray + k; + int varIdx = jj * varsPerTray + k; - // Save original value - double origVal = getVariable(jj, k); - double h = Math.max(Math.abs(origVal) * pertSize, minPert); + // Save original value + double origVal = getVariable(jj, k); + double h = Math.max(Math.abs(origVal) * pertSize, minPert); - // Perturb - setVariable(jj, k, origVal + h); + // Perturb + setVariable(jj, k, origVal + h); - // Re-evaluate thermo ONLY for the tray whose variable changed - evaluateThermoForTray(jj); + // Re-evaluate thermo ONLY for the tray whose variable changed + evaluateThermoForTray(jj); - // Compute perturbed residuals for affected trays (j-1, j, j+1) - int jStart = Math.max(0, jj - 1); - int jEnd = Math.min(N - 1, jj + 1); - for (int j = jStart; j <= jEnd; j++) { - double[] Fpert = computeResidualForTray(j); - int rowBase = j * varsPerTray; - for (int eq = 0; eq < varsPerTray; eq++) { - J[rowBase + eq][varIdx] = (Fpert[eq] - F0[rowBase + eq]) / h; - } - } + // Compute perturbed residuals for affected trays (j-1, j, j+1) + int jStart = Math.max(0, jj - 1); + int jEnd = Math.min(N - 1, jj + 1); + for (int j = jStart; j <= jEnd; j++) { + double[] Fpert = computeResidualForTray(j); + int rowBase = j * varsPerTray; + for (int eq = 0; eq < varsPerTray; eq++) { + J[rowBase + eq][varIdx] = (Fpert[eq] - F0[rowBase + eq]) / h; + } + } - // Restore - setVariable(jj, k, origVal); - evaluateThermoForTray(jj); + // Restore + setVariable(jj, k, origVal); + evaluateThermoForTray(jj); } } @@ -2881,10 +2930,10 @@ private double[] computeResidualForTray(int j) { double Mij = liq[j][i] + vap[j][i]; if (j < N - 1) { - Mij -= liq[j + 1][i]; + Mij -= liq[j + 1][i]; } if (j > 0) { - Mij -= vap[j - 1][i]; + Mij -= vap[j - 1][i]; } Mij -= feedLiq[j][i] + feedVap[j][i]; @@ -2897,10 +2946,10 @@ private double[] computeResidualForTray(int j) { } else { double Hj = Lj * hL[j] + V[j] * hV[j]; if (j < N - 1) { - Hj -= L[j + 1] * hL[j + 1]; + Hj -= L[j + 1] * hL[j + 1]; } if (j > 0) { - Hj -= V[j - 1] * hV[j - 1]; + Hj -= V[j - 1] * hV[j - 1]; } Hj -= feedLTotal[j] * feedHL[j] + feedVTotal[j] * feedHV[j]; Hj -= Q[j]; @@ -2923,8 +2972,8 @@ private double[] computeResidualForTray(int j) { } /** - * Evaluate thermodynamic properties for a single tray. Used during Jacobian perturbation to avoid full column - * re-evaluation. V[j] is NOT overwritten — it is a free variable. + * Evaluate thermodynamic properties for a single tray. Used during Jacobian perturbation to avoid + * full column re-evaluation. V[j] is NOT overwritten — it is a free variable. * * @param j tray index */ @@ -2932,20 +2981,20 @@ private double[] computeResidualForTray(int j) { * Apply the Edmister Murphree-efficiency proxy to the K-values of tray j. * *- * The exact Murphree definition mixes the actual outlet vapor with the vapor entering from below: y_actual = y_in + - * eta * (y_eq - y_in). Embedding that into the simultaneous MESH solve requires the residual on tray j to couple to - * vap[j-1], which would expand the Jacobian bandwidth. + * The exact Murphree definition mixes the actual outlet vapor with the vapor entering from below: + * y_actual = y_in + eta * (y_eq - y_in). Embedding that into the simultaneous MESH solve requires + * the residual on tray j to couple to vap[j-1], which would expand the Jacobian bandwidth. *
* * The Edmister approximation K_eff = K^eta has the correct limits: *- * This forces the EOS to evaluate at the specified phase root (liquid or vapor) rather than doing a flash. This is - * essential for energy balance in the BP method: hL must be evaluated at the actual liquid composition x[j] using the - * liquid root, and hV at the actual vapor composition y[j] using the vapor root. Using the enthalpies from a TPflash - * of x[j] gives wrong hV because the flash vapor has a different composition than the actual column vapor. + * This forces the EOS to evaluate at the specified phase root (liquid or vapor) rather than doing + * a flash. This is essential for energy balance in the BP method: hL must be evaluated at the + * actual liquid composition x[j] using the liquid root, and hV at the actual vapor composition + * y[j] using the vapor root. Using the enthalpies from a TPflash of x[j] gives wrong hV because + * the flash vapor has a different composition than the actual column vapor. *
* * @param composition molar composition array (must sum to ~1.0) @@ -3096,23 +3146,26 @@ private void evaluateThermoForTray(int j) { * Boston-Sullivan inside-out refinement of the seed state. * *- * Activates when at least one tray has a fixed temperature spec — the case where the smooth Wilson BP cascade - * under-resolves a sharp T-pinch. The algorithm follows Boston & Sullivan (1974): + * Activates when at least one tray has a fixed temperature spec — the case where the smooth + * Wilson BP cascade under-resolves a sharp T-pinch. The algorithm follows Boston & Sullivan + * (1974): *
*- * Mass balance is preserved by the tridiagonal solve. V[j] and L[j] are held at their BP-Wilson values; the - * downstream SR / Newton phase polishes the energy balance. + * Mass balance is preserved by the tridiagonal solve. V[j] and L[j] are held at their BP-Wilson + * values; the downstream SR / Newton phase polishes the energy balance. *
*/ private void runBostonSullivanRefinement() { @@ -3122,8 +3175,8 @@ private void runBostonSullivanRefinement() { boolean hasFixedT = false; for (int j = 0; j < N; j++) { if (!Double.isNaN(fixedTemperature[j])) { - hasFixedT = true; - break; + hasFixedT = true; + break; } } if (!hasFixedT) { @@ -3171,179 +3224,181 @@ private void runBostonSullivanRefinement() { // (A) Compute Kb (x-weighted geomean of K) and alpha[j][i] from rigorous K. double maxAlphaChange = 0.0; for (int j = 0; j < N; j++) { - double Lsum = 0; - for (int i = 0; i < C; i++) { - Lsum += liq[j][i]; - } - Lsum = Math.max(Lsum, 1e-20); - double lnKb = 0; - for (int i = 0; i < C; i++) { - double xi = liq[j][i] / Lsum; - double Ki = Math.max(K[j][i], 1e-30); - lnKb += xi * Math.log(Ki); - } - Kb[j] = Math.max(Math.exp(lnKb), 1e-30); - for (int i = 0; i < C; i++) { - double Ki = Math.max(K[j][i], 1e-30); - alpha[j][i] = Ki / Kb[j]; - if (outer > 0) { - double aOld = alphaOld[j][i]; - if (aOld > 1e-30) { - double rel = Math.abs(alpha[j][i] - aOld) / aOld; - if (rel > maxAlphaChange) { - maxAlphaChange = rel; - } - } - } - } - - // (B) Antoine fit: ln(Kb) = A - B/T via Wilson K-perturbation at T+5K. - // ln(Kb_pert) = ln(Kb) + sum_i x_i * ln(K_i_pert / K_i_base) - double Tj = T[j]; - double Tpert = Tj + 5.0; - double Pbar = Math.max(P[j] / 1e5, 1e-12); - double shift = 0; - for (int i = 0; i < C; i++) { - double KiPert = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / Tpert)); - double KiBase = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / Tj)); - double xi = liq[j][i] / Lsum; - shift += xi * Math.log(Math.max(KiPert, 1e-30) / Math.max(KiBase, 1e-30)); - } - double lnKbAtPert = Math.log(Kb[j]) + shift; - double inv1 = 1.0 / Tj; - double inv2 = 1.0 / Tpert; - double Bv; - if (Math.abs(inv2 - inv1) > 1e-14) { - Bv = (Math.log(Kb[j]) - lnKbAtPert) / (inv2 - inv1); - } else { - Bv = 2500.0; - } - // Sanity bound on B (typical 1000..5000 K for hydrocarbons). - if (!Double.isFinite(Bv) || Bv < 200.0) { - Bv = 2500.0; - } else if (Bv > 15000.0) { - Bv = 15000.0; - } - Bant[j] = Bv; - Aant[j] = Math.log(Kb[j]) + Bv / Tj; + double Lsum = 0; + for (int i = 0; i < C; i++) { + Lsum += liq[j][i]; + } + Lsum = Math.max(Lsum, 1e-20); + double lnKb = 0; + for (int i = 0; i < C; i++) { + double xi = liq[j][i] / Lsum; + double Ki = Math.max(K[j][i], 1e-30); + lnKb += xi * Math.log(Ki); + } + Kb[j] = Math.max(Math.exp(lnKb), 1e-30); + for (int i = 0; i < C; i++) { + double Ki = Math.max(K[j][i], 1e-30); + alpha[j][i] = Ki / Kb[j]; + if (outer > 0) { + double aOld = alphaOld[j][i]; + if (aOld > 1e-30) { + double rel = Math.abs(alpha[j][i] - aOld) / aOld; + if (rel > maxAlphaChange) { + maxAlphaChange = rel; + } + } + } + } + + // (B) Antoine fit: ln(Kb) = A - B/T via Wilson K-perturbation at T+5K. + // ln(Kb_pert) = ln(Kb) + sum_i x_i * ln(K_i_pert / K_i_base) + double Tj = T[j]; + double Tpert = Tj + 5.0; + double Pbar = Math.max(P[j] / 1e5, 1e-12); + double shift = 0; + for (int i = 0; i < C; i++) { + double KiPert = + (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / Tpert)); + double KiBase = (Pc[i] / Pbar) * Math.exp(5.37 * (1.0 + omega[i]) * (1.0 - Tc[i] / Tj)); + double xi = liq[j][i] / Lsum; + shift += xi * Math.log(Math.max(KiPert, 1e-30) / Math.max(KiBase, 1e-30)); + } + double lnKbAtPert = Math.log(Kb[j]) + shift; + double inv1 = 1.0 / Tj; + double inv2 = 1.0 / Tpert; + double Bv; + if (Math.abs(inv2 - inv1) > 1e-14) { + Bv = (Math.log(Kb[j]) - lnKbAtPert) / (inv2 - inv1); + } else { + Bv = 2500.0; + } + // Sanity bound on B (typical 1000..5000 K for hydrocarbons). + if (!Double.isFinite(Bv) || Bv < 200.0) { + Bv = 2500.0; + } else if (Bv > 15000.0) { + Bv = 15000.0; + } + Bant[j] = Bv; + Aant[j] = Math.log(Kb[j]) + Bv / Tj; } if (outer > 0 && maxAlphaChange < alphaTol) { - logger.info("BS outer {} converged on alpha (maxRelChange={})", outer, String.format("%.3e", maxAlphaChange)); - // Snapshot alphaOld for next call (no-op here since we're breaking) - break; + logger.info("BS outer {} converged on alpha (maxRelChange={})", outer, + String.format("%.3e", maxAlphaChange)); + // Snapshot alphaOld for next call (no-op here since we're breaking) + break; } // (C) Inner loop: tridiag MB per component + bubble-T per tray. double maxDTinner = 0; int innerUsed = 0; for (int inner = 0; inner < maxInner; inner++) { - innerUsed = inner + 1; - // Build & solve C tridiagonal systems for x[*][i]. - for (int i = 0; i < C; i++) { - for (int j = 0; j < N; j++) { - double Kij = alpha[j][i] * Kb[j]; - bDiag[j] = L[j] + V[j] * Kij; - if (j > 0) { - double Kjm1 = alpha[j - 1][i] * Kb[j - 1]; - aDiag[j] = -V[j - 1] * Kjm1; - } else { - aDiag[j] = 0; - } - if (j < N - 1) { - cDiag[j] = -L[j + 1]; - } else { - cDiag[j] = 0; - } - rhs[j] = feedLiq[j][i] + feedVap[j][i]; - } - thomas(aDiag, bDiag, cDiag, rhs, xCol); - for (int j = 0; j < N; j++) { - xUpd[j][i] = Math.max(xCol[j], 1e-25); - } - } - - // Normalize x per tray; bubble criterion -> Kb_new -> T_new -> Kb at T_new. - maxDTinner = 0; - for (int j = 0; j < N; j++) { - double sumX = 0; - for (int i = 0; i < C; i++) { - sumX += xUpd[j][i]; - } - if (sumX <= 0) { - sumX = 1.0; - } - for (int i = 0; i < C; i++) { - xUpd[j][i] /= sumX; - } - double sumAX = 0; - for (int i = 0; i < C; i++) { - sumAX += alpha[j][i] * xUpd[j][i]; - } - double KbBubble = 1.0 / Math.max(sumAX, 1e-30); - - double tNew; - if (!Double.isNaN(fixedTemperature[j])) { - tNew = fixedTemperature[j]; - } else { - double denom = Aant[j] - Math.log(Math.max(KbBubble, 1e-30)); - if (Math.abs(denom) < 1e-10) { - tNew = T[j]; - } else { - tNew = Bant[j] / denom; - } - // Guard against runaway estimates. - if (!Double.isFinite(tNew) || tNew < 150.0 || tNew > 1500.0) { - tNew = T[j]; - } - } - double dT = tNew - T[j]; - if (dT > tStepLimit) { - dT = tStepLimit; - } - if (dT < -tStepLimit) { - dT = -tStepLimit; - } - double tFinal = T[j] + tDamp * dT; - if (!Double.isNaN(fixedTemperature[j])) { - tFinal = fixedTemperature[j]; - } - if (Math.abs(tFinal - T[j]) > maxDTinner) { - maxDTinner = Math.abs(tFinal - T[j]); - } - T[j] = tFinal; - Kb[j] = Math.exp(Aant[j] - Bant[j] / tFinal); - } - - // Update liq[j][i] and vap[j][i] from xUpd and current Kb. - // Preserve total L[j], V[j] (energy balance polished by downstream SR). - for (int j = 0; j < N; j++) { - double sumY = 0; - double[] y = new double[C]; - for (int i = 0; i < C; i++) { - y[i] = alpha[j][i] * Kb[j] * xUpd[j][i]; - sumY += y[i]; - } - if (sumY <= 0) { - sumY = 1.0; - } - for (int i = 0; i < C; i++) { - y[i] /= sumY; - liq[j][i] = L[j] * xUpd[j][i]; - vap[j][i] = V[j] * y[i]; - } - } - - if (maxDTinner < tTol) { - break; - } + innerUsed = inner + 1; + // Build & solve C tridiagonal systems for x[*][i]. + for (int i = 0; i < C; i++) { + for (int j = 0; j < N; j++) { + double Kij = alpha[j][i] * Kb[j]; + bDiag[j] = L[j] + V[j] * Kij; + if (j > 0) { + double Kjm1 = alpha[j - 1][i] * Kb[j - 1]; + aDiag[j] = -V[j - 1] * Kjm1; + } else { + aDiag[j] = 0; + } + if (j < N - 1) { + cDiag[j] = -L[j + 1]; + } else { + cDiag[j] = 0; + } + rhs[j] = feedLiq[j][i] + feedVap[j][i]; + } + thomas(aDiag, bDiag, cDiag, rhs, xCol); + for (int j = 0; j < N; j++) { + xUpd[j][i] = Math.max(xCol[j], 1e-25); + } + } + + // Normalize x per tray; bubble criterion -> Kb_new -> T_new -> Kb at T_new. + maxDTinner = 0; + for (int j = 0; j < N; j++) { + double sumX = 0; + for (int i = 0; i < C; i++) { + sumX += xUpd[j][i]; + } + if (sumX <= 0) { + sumX = 1.0; + } + for (int i = 0; i < C; i++) { + xUpd[j][i] /= sumX; + } + double sumAX = 0; + for (int i = 0; i < C; i++) { + sumAX += alpha[j][i] * xUpd[j][i]; + } + double KbBubble = 1.0 / Math.max(sumAX, 1e-30); + + double tNew; + if (!Double.isNaN(fixedTemperature[j])) { + tNew = fixedTemperature[j]; + } else { + double denom = Aant[j] - Math.log(Math.max(KbBubble, 1e-30)); + if (Math.abs(denom) < 1e-10) { + tNew = T[j]; + } else { + tNew = Bant[j] / denom; + } + // Guard against runaway estimates. + if (!Double.isFinite(tNew) || tNew < 150.0 || tNew > 1500.0) { + tNew = T[j]; + } + } + double dT = tNew - T[j]; + if (dT > tStepLimit) { + dT = tStepLimit; + } + if (dT < -tStepLimit) { + dT = -tStepLimit; + } + double tFinal = T[j] + tDamp * dT; + if (!Double.isNaN(fixedTemperature[j])) { + tFinal = fixedTemperature[j]; + } + if (Math.abs(tFinal - T[j]) > maxDTinner) { + maxDTinner = Math.abs(tFinal - T[j]); + } + T[j] = tFinal; + Kb[j] = Math.exp(Aant[j] - Bant[j] / tFinal); + } + + // Update liq[j][i] and vap[j][i] from xUpd and current Kb. + // Preserve total L[j], V[j] (energy balance polished by downstream SR). + for (int j = 0; j < N; j++) { + double sumY = 0; + double[] y = new double[C]; + for (int i = 0; i < C; i++) { + y[i] = alpha[j][i] * Kb[j] * xUpd[j][i]; + sumY += y[i]; + } + if (sumY <= 0) { + sumY = 1.0; + } + for (int i = 0; i < C; i++) { + y[i] /= sumY; + liq[j][i] = L[j] * xUpd[j][i]; + vap[j][i] = V[j] * y[i]; + } + } + + if (maxDTinner < tTol) { + break; + } } // Snapshot alpha for outer-loop convergence check next pass. for (int j = 0; j < N; j++) { - for (int i = 0; i < C; i++) { - alphaOld[j][i] = alpha[j][i]; - } + for (int i = 0; i < C; i++) { + alphaOld[j][i] = alpha[j][i]; + } } // (D) Refresh rigorous K for next outer iteration. @@ -3356,34 +3411,34 @@ private void runBostonSullivanRefinement() { // — without it, V/L stays at the BP-Wilson seed (too-low boilup) and // SR later reverses the T improvement to match the low V cascade. for (int j = 0; j < N; j++) { - double Lsum = 0; - double Vsum = 0; - for (int i = 0; i < C; i++) { - Lsum += liq[j][i]; - Vsum += vap[j][i]; - } - double[] xj = new double[C]; - double[] yj = new double[C]; - for (int i = 0; i < C; i++) { - xj[i] = (Lsum > 1e-20) ? liq[j][i] / Lsum : 1.0 / C; - yj[i] = (Vsum > 1e-20) ? vap[j][i] / Vsum : 1.0 / C; - } - double Pbar = P[j] / 1e5; - hL[j] = computeSinglePhaseEnthalpy(xj, T[j], Pbar, false); - hV[j] = computeSinglePhaseEnthalpy(yj, T[j], Pbar, true); + double Lsum = 0; + double Vsum = 0; + for (int i = 0; i < C; i++) { + Lsum += liq[j][i]; + Vsum += vap[j][i]; + } + double[] xj = new double[C]; + double[] yj = new double[C]; + for (int i = 0; i < C; i++) { + xj[i] = (Lsum > 1e-20) ? liq[j][i] / Lsum : 1.0 / C; + yj[i] = (Vsum > 1e-20) ? vap[j][i] / Vsum : 1.0 / C; + } + double Pbar = P[j] / 1e5; + hL[j] = computeSinglePhaseEnthalpy(xj, T[j], Pbar, false); + hV[j] = computeSinglePhaseEnthalpy(yj, T[j], Pbar, true); } double vDamp = 0.25; double[] Vold = new double[N]; double[] Lold = new double[N]; for (int j = 0; j < N; j++) { - Vold[j] = V[j]; - Lold[j] = L[j]; + Vold[j] = V[j]; + Lold[j] = L[j]; } // Pre-compute Fmol (used both for V[0] closure and L rebuild below). double[] FmolPre = new double[N]; for (int j = 0; j < N; j++) { - FmolPre[j] = feedLTotal[j] + feedVTotal[j]; + FmolPre[j] = feedLTotal[j] + feedVTotal[j]; } // For T-pin reboiler stripper: Q[0] (reboiler duty) is the IMPLICIT // free variable that holds T[0] at its pinned value. Compute it from @@ -3396,114 +3451,114 @@ private void runBostonSullivanRefinement() { double qRebOverall = 0.0; boolean useOverallQreb = hasReboiler && useOverallMBClosure; if (useOverallQreb) { - qRebOverall = L[0] * hL[0] + V[N - 1] * hV[N - 1]; - for (int jj = 0; jj < N; jj++) { - qRebOverall -= feedLTotal[jj] * feedHL[jj]; - qRebOverall -= feedVTotal[jj] * feedHV[jj]; - if (jj != 0) { - qRebOverall -= Q[jj]; - } - } + qRebOverall = L[0] * hL[0] + V[N - 1] * hV[N - 1]; + for (int jj = 0; jj < N; jj++) { + qRebOverall -= feedLTotal[jj] * feedHL[jj]; + qRebOverall -= feedVTotal[jj] * feedHV[jj]; + if (jj != 0) { + qRebOverall -= Q[jj]; + } + } } for (int j = 0; j < N; j++) { - // Boundary trays: keep current closure. - if (j == 0 && hasReboiler && useOverallMBClosure) { - // T-pin reboiler: V[0] free, fixed by tray-0 EB with Q[0] - // taken from overall column EB (not the input Q[0]=0). - // Tray-0 EB: - // L[1]*hL[1] + F_L[0]*hF_L[0] + F_V[0]*hF_V[0] + Q_reb - // = V[0]*hV[0] + L[0]*hL[0] - if (Math.abs(hV[0]) < 1e-3) { - continue; - } - double num = -L[0] * hL[0]; - if (N > 1) { - num += L[1] * hL[1]; - } - num += feedLTotal[0] * feedHL[0]; - num += feedVTotal[0] * feedHV[0]; - num += qRebOverall; - double newV0 = num / hV[0]; - if (newV0 > 0 && Double.isFinite(newV0)) { - // V[0] (boilup) gets aggressive damping/cap: it's the master - // driver of the bottom-section T-cliff in T-pin strippers. - // Without this, vDamp=0.25 keeps V[0] stuck near the BP-Wilson - // seed (way too low) and the bottom section can't warm up. - double cap = 2.0 * Math.max(Vold[0], 1.0); - double dv = newV0 - V[0]; - if (dv > cap) { - dv = cap; - } else if (dv < -cap) { - dv = -cap; - } - V[0] = V[0] + 0.6 * dv; - if (V[0] < 1e-10) { - V[0] = 1e-10; - } - } - continue; - } - if (j == 0 && hasReboiler && boilupRatio > 0) { - V[0] = boilupRatio * L[0]; - continue; - } - // Skip V[N-1] update: with no condenser and T-pin reboiler, V[N-1] - // is the overhead product stream, fixed by overall MB closure - // (V[N-1] = totalFeed - L[0]). The tray-EB at j=N-1 lacks a - // closure equation (Q_reboiler is the free parameter, not V[N-1]) - // so its computed value runs to the cap. Leave V[N-1] at its - // current value (BP-Wilson seed already satisfies overall MB). - if (j == N - 1) { - continue; - } - // Internal tray energy balance: - // V_j*(hV_j-hL_j) = L_{j+1}*(hL_{j+1}-hL_j) + V_{j-1}*(hV_{j-1}-hL_j) - // + F_L*(hF_L-hL_j) + F_V*(hF_V-hL_j) + Q_j - double den = hV[j] - hL[j]; - if (Math.abs(den) < 1e-3) { - continue; - } - double num = 0; - if (j < N - 1) { - num += L[j + 1] * (hL[j + 1] - hL[j]); - } - if (j > 0) { - num += V[j - 1] * (hV[j - 1] - hL[j]); - } - num += feedLTotal[j] * (feedHL[j] - hL[j]); - num += feedVTotal[j] * (feedHV[j] - hL[j]); - num += Q[j]; - double newV = num / den; - if (newV > 0 && Double.isFinite(newV)) { - // Hard-clamp the step: at most ±25% of the prior V per outer pass - // (smaller than V[0]'s ±200% — internal trays must propagate - // gradually to avoid oscillation while V[0] catches up). - double cap = 0.25 * Vold[j]; - double dv = newV - V[j]; - if (dv > cap) { - dv = cap; - } else if (dv < -cap) { - dv = -cap; - } - V[j] = V[j] + 0.15 * dv; - if (V[j] < 1e-10) { - V[j] = 1e-10; - } - // Also bound to [0.25, 4.0] of prior V to prevent runaway. - if (V[j] < 0.25 * Vold[j]) { - V[j] = 0.25 * Vold[j]; - } else if (V[j] > 4.0 * Vold[j]) { - V[j] = 4.0 * Vold[j]; - } - // Hard physical cap: no internal vapor can exceed total feed - // (overall vapor mass balance: V[N-1] ≤ totalFeed). Apply to - // every tray since internal V can't exceed cumulative feed - // either. Leave a small margin for numerical headroom. - double Vcap = 0.98 * totalFeedMolesField; - if (V[j] > Vcap) { - V[j] = Vcap; - } - } + // Boundary trays: keep current closure. + if (j == 0 && hasReboiler && useOverallMBClosure) { + // T-pin reboiler: V[0] free, fixed by tray-0 EB with Q[0] + // taken from overall column EB (not the input Q[0]=0). + // Tray-0 EB: + // L[1]*hL[1] + F_L[0]*hF_L[0] + F_V[0]*hF_V[0] + Q_reb + // = V[0]*hV[0] + L[0]*hL[0] + if (Math.abs(hV[0]) < 1e-3) { + continue; + } + double num = -L[0] * hL[0]; + if (N > 1) { + num += L[1] * hL[1]; + } + num += feedLTotal[0] * feedHL[0]; + num += feedVTotal[0] * feedHV[0]; + num += qRebOverall; + double newV0 = num / hV[0]; + if (newV0 > 0 && Double.isFinite(newV0)) { + // V[0] (boilup) gets aggressive damping/cap: it's the master + // driver of the bottom-section T-cliff in T-pin strippers. + // Without this, vDamp=0.25 keeps V[0] stuck near the BP-Wilson + // seed (way too low) and the bottom section can't warm up. + double cap = 2.0 * Math.max(Vold[0], 1.0); + double dv = newV0 - V[0]; + if (dv > cap) { + dv = cap; + } else if (dv < -cap) { + dv = -cap; + } + V[0] = V[0] + 0.6 * dv; + if (V[0] < 1e-10) { + V[0] = 1e-10; + } + } + continue; + } + if (j == 0 && hasReboiler && boilupRatio > 0) { + V[0] = boilupRatio * L[0]; + continue; + } + // Skip V[N-1] update: with no condenser and T-pin reboiler, V[N-1] + // is the overhead product stream, fixed by overall MB closure + // (V[N-1] = totalFeed - L[0]). The tray-EB at j=N-1 lacks a + // closure equation (Q_reboiler is the free parameter, not V[N-1]) + // so its computed value runs to the cap. Leave V[N-1] at its + // current value (BP-Wilson seed already satisfies overall MB). + if (j == N - 1) { + continue; + } + // Internal tray energy balance: + // V_j*(hV_j-hL_j) = L_{j+1}*(hL_{j+1}-hL_j) + V_{j-1}*(hV_{j-1}-hL_j) + // + F_L*(hF_L-hL_j) + F_V*(hF_V-hL_j) + Q_j + double den = hV[j] - hL[j]; + if (Math.abs(den) < 1e-3) { + continue; + } + double num = 0; + if (j < N - 1) { + num += L[j + 1] * (hL[j + 1] - hL[j]); + } + if (j > 0) { + num += V[j - 1] * (hV[j - 1] - hL[j]); + } + num += feedLTotal[j] * (feedHL[j] - hL[j]); + num += feedVTotal[j] * (feedHV[j] - hL[j]); + num += Q[j]; + double newV = num / den; + if (newV > 0 && Double.isFinite(newV)) { + // Hard-clamp the step: at most ±25% of the prior V per outer pass + // (smaller than V[0]'s ±200% — internal trays must propagate + // gradually to avoid oscillation while V[0] catches up). + double cap = 0.25 * Vold[j]; + double dv = newV - V[j]; + if (dv > cap) { + dv = cap; + } else if (dv < -cap) { + dv = -cap; + } + V[j] = V[j] + 0.15 * dv; + if (V[j] < 1e-10) { + V[j] = 1e-10; + } + // Also bound to [0.25, 4.0] of prior V to prevent runaway. + if (V[j] < 0.25 * Vold[j]) { + V[j] = 0.25 * Vold[j]; + } else if (V[j] > 4.0 * Vold[j]) { + V[j] = 4.0 * Vold[j]; + } + // Hard physical cap: no internal vapor can exceed total feed + // (overall vapor mass balance: V[N-1] ≤ totalFeed). Apply to + // every tray since internal V can't exceed cumulative feed + // either. Leave a small margin for numerical headroom. + double Vcap = 0.98 * totalFeedMolesField; + if (V[j] > Vcap) { + V[j] = Vcap; + } + } } // Rebuild L from total mass balance (bottom-up cumulative): @@ -3525,53 +3580,55 @@ private void runBostonSullivanRefinement() { // L[j+1] = L[j] + V[j] - V[j-1] - Fmol[j] (rearranged tray-j MB) // starting from L[0] from overall MB closure. if (hasReboiler && useOverallMBClosure) { - L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); + L[0] = Math.max(totalFeedMolesField - V[N - 1], totalFeedMolesField * 0.001); } for (int j = 0; j < N - 1; j++) { - // tray-j MB: V[j-1] + L[j+1] + F[j] = V[j] + L[j] - // -> L[j+1] = L[j] + V[j] - (j>0 ? V[j-1] : 0) - F[j] - double Vprev = (j > 0) ? V[j - 1] : 0.0; - double newLjp1 = L[j] + V[j] - Vprev - Fmol[j]; - if (newLjp1 < 1e-10) { - newLjp1 = 1e-10; - } - L[j + 1] = L[j + 1] + vDamp * (newLjp1 - L[j + 1]); - if (L[j + 1] < 1e-10) { - L[j + 1] = 1e-10; - } + // tray-j MB: V[j-1] + L[j+1] + F[j] = V[j] + L[j] + // -> L[j+1] = L[j] + V[j] - (j>0 ? V[j-1] : 0) - F[j] + double Vprev = (j > 0) ? V[j - 1] : 0.0; + double newLjp1 = L[j] + V[j] - Vprev - Fmol[j]; + if (newLjp1 < 1e-10) { + newLjp1 = 1e-10; + } + L[j + 1] = L[j + 1] + vDamp * (newLjp1 - L[j + 1]); + if (L[j + 1] < 1e-10) { + L[j + 1] = 1e-10; + } } // Recompute per-component liq/vap from current x,y and new V,L. for (int j = 0; j < N; j++) { - double Lsum = 0; - double Vsum = 0; - for (int i = 0; i < C; i++) { - Lsum += liq[j][i]; - Vsum += vap[j][i]; - } - for (int i = 0; i < C; i++) { - double xij = (Lsum > 1e-20) ? liq[j][i] / Lsum : 1.0 / C; - double yij = (Vsum > 1e-20) ? vap[j][i] / Vsum : 1.0 / C; - liq[j][i] = L[j] * xij; - vap[j][i] = V[j] * yij; - } + double Lsum = 0; + double Vsum = 0; + for (int i = 0; i < C; i++) { + Lsum += liq[j][i]; + Vsum += vap[j][i]; + } + for (int i = 0; i < C; i++) { + double xij = (Lsum > 1e-20) ? liq[j][i] / Lsum : 1.0 / C; + double yij = (Vsum > 1e-20) ? vap[j][i] / Vsum : 1.0 / C; + liq[j][i] = L[j] * xij; + vap[j][i] = V[j] * yij; + } } logger.info( - "BS outer {}: innerUsed={} maxDTinner={}K maxAlphaChange={} " - + "T[0]={}C T[mid]={}C T[N-1]={}C V[0]={} V[N-1]={}", - outer + 1, innerUsed, String.format("%.3f", maxDTinner), String.format("%.3e", maxAlphaChange), - String.format("%.2f", T[0] - 273.15), String.format("%.2f", T[N / 2] - 273.15), - String.format("%.2f", T[N - 1] - 273.15), String.format("%.1f", V[0]), String.format("%.1f", V[N - 1])); + "BS outer {}: innerUsed={} maxDTinner={}K maxAlphaChange={} " + + "T[0]={}C T[mid]={}C T[N-1]={}C V[0]={} V[N-1]={}", + outer + 1, innerUsed, String.format("%.3f", maxDTinner), + String.format("%.3e", maxAlphaChange), String.format("%.2f", T[0] - 273.15), + String.format("%.2f", T[N / 2] - 273.15), String.format("%.2f", T[N - 1] - 273.15), + String.format("%.1f", V[0]), String.format("%.1f", V[N - 1])); } - logger.info("BS done: T[0]={}C->{}C T[N-1]={}C->{}C", String.format("%.2f", initialT0 - 273.15), - String.format("%.2f", T[0] - 273.15), String.format("%.2f", initialTtop - 273.15), - String.format("%.2f", T[N - 1] - 273.15)); + logger.info("BS done: T[0]={}C->{}C T[N-1]={}C->{}C", + String.format("%.2f", initialT0 - 273.15), String.format("%.2f", T[0] - 273.15), + String.format("%.2f", initialTtop - 273.15), String.format("%.2f", T[N - 1] - 273.15)); } /** - * Thomas algorithm for tridiagonal systems {@code a[j] * x[j-1] + b[j] * x[j] + c[j] * x[j+1] = d[j]}. + * Thomas algorithm for tridiagonal systems + * {@code a[j] * x[j-1] + b[j] * x[j] + c[j] * x[j+1] = d[j]}. * * @param a sub-diagonal (a[0] ignored), length n * @param b main diagonal, length n @@ -3592,7 +3649,7 @@ private void thomas(double[] a, double[] b, double[] c, double[] d, double[] x) for (int j = 1; j < n; j++) { double m = b[j] - a[j] * cp[j - 1]; if (Math.abs(m) < 1e-30) { - m = (m >= 0) ? 1e-30 : -1e-30; + m = (m >= 0) ? 1e-30 : -1e-30; } cp[j] = (j < n - 1) ? c[j] / m : 0; dp[j] = (d[j] - a[j] * dp[j - 1]) / m; @@ -3604,8 +3661,9 @@ private void thomas(double[] a, double[] b, double[] c, double[] d, double[] x) } /** - * Wilson-K bubble-point temperature for a composition at a given pressure. Solves sum(z_i * K_i(T,P)) = 1 by Newton - * iteration. Used only as an initial-guess generator for the BP method T-profile. + * Wilson-K bubble-point temperature for a composition at a given pressure. Solves sum(z_i * + * K_i(T,P)) = 1 by Newton iteration. Used only as an initial-guess generator for the BP method + * T-profile. * * @param z molar composition * @param pBar pressure in bara @@ -3618,24 +3676,24 @@ private double wilsonBubbleTemperature(double[] z, double pBar, double tGuess) { double f = -1.0; double dfdt = 0.0; for (int i = 0; i < C; i++) { - ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); - double tc = comp.getTC(); - double pc = comp.getPC(); - double omega = comp.getAcentricFactor(); - double exponent = 5.37 * (1.0 + omega) * (1.0 - tc / t); - double k = (pc / pBar) * Math.exp(exponent); - double dkdt = k * 5.37 * (1.0 + omega) * tc / (t * t); - f += z[i] * k; - dfdt += z[i] * dkdt; + ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); + double tc = comp.getTC(); + double pc = comp.getPC(); + double omega = comp.getAcentricFactor(); + double exponent = 5.37 * (1.0 + omega) * (1.0 - tc / t); + double k = (pc / pBar) * Math.exp(exponent); + double dkdt = k * 5.37 * (1.0 + omega) * tc / (t * t); + f += z[i] * k; + dfdt += z[i] * dkdt; } if (Math.abs(f) < 1e-8 || Math.abs(dfdt) < 1e-30) { - break; + break; } double dt = -f / dfdt; if (dt > 30.0) - dt = 30.0; + dt = 30.0; if (dt < -30.0) - dt = -30.0; + dt = -30.0; t += dt; t = Math.max(100.0, Math.min(1000.0, t)); } @@ -3643,8 +3701,9 @@ private double wilsonBubbleTemperature(double[] z, double pBar, double tGuess) { } /** - * Wilson-K dew-point temperature for a composition at a given pressure. Solves sum(z_i / K_i(T,P)) = 1 by Newton - * iteration. Used only as an initial-guess generator for the BP method T-profile. + * Wilson-K dew-point temperature for a composition at a given pressure. Solves sum(z_i / + * K_i(T,P)) = 1 by Newton iteration. Used only as an initial-guess generator for the BP method + * T-profile. * * @param z molar composition * @param pBar pressure in bara @@ -3657,24 +3716,24 @@ private double wilsonDewTemperature(double[] z, double pBar, double tGuess) { double f = -1.0; double dfdt = 0.0; for (int i = 0; i < C; i++) { - ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); - double tc = comp.getTC(); - double pc = comp.getPC(); - double omega = comp.getAcentricFactor(); - double exponent = 5.37 * (1.0 + omega) * (1.0 - tc / t); - double k = (pc / pBar) * Math.exp(exponent); - double dkdt = k * 5.37 * (1.0 + omega) * tc / (t * t); - f += z[i] / k; - dfdt += -z[i] * dkdt / (k * k); + ComponentInterface comp = referenceSystem.getPhase(0).getComponent(i); + double tc = comp.getTC(); + double pc = comp.getPC(); + double omega = comp.getAcentricFactor(); + double exponent = 5.37 * (1.0 + omega) * (1.0 - tc / t); + double k = (pc / pBar) * Math.exp(exponent); + double dkdt = k * 5.37 * (1.0 + omega) * tc / (t * t); + f += z[i] / k; + dfdt += -z[i] * dkdt / (k * k); } if (Math.abs(f) < 1e-8 || Math.abs(dfdt) < 1e-30) { - break; + break; } double dt = -f / dfdt; if (dt > 30.0) - dt = 30.0; + dt = 30.0; if (dt < -30.0) - dt = -30.0; + dt = -30.0; t += dt; t = Math.max(100.0, Math.min(1000.0, t)); } @@ -3682,14 +3741,14 @@ private double wilsonDewTemperature(double[] z, double pBar, double tGuess) { } /** - * Compute fugacity coefficients for a single phase (liquid or vapor) at given composition, temperature and pressure - * using a forced single-phase EOS root. + * Compute fugacity coefficients for a single phase (liquid or vapor) at given composition, + * temperature and pressure using a forced single-phase EOS root. * *- * Mirrors {@link #computeSinglePhaseEnthalpy} — sets one phase, forces the requested phase type (GAS or LIQUID), and - * reads the fugacity coefficients directly. This is the correct way to evaluate K-values inside a column MESH - * residual, because K_i = phi_L(x) / phi_V(y) must be evaluated at the tray's actual compositions x and y, NOT at a - * flash composition. + * Mirrors {@link #computeSinglePhaseEnthalpy} — sets one phase, forces the requested phase type + * (GAS or LIQUID), and reads the fugacity coefficients directly. This is the correct way to + * evaluate K-values inside a column MESH residual, because K_i = phi_L(x) / phi_V(y) must be + * evaluated at the tray's actual compositions x and y, NOT at a flash composition. *
* * @param composition mole fractions (length C, normalised by caller) @@ -3699,8 +3758,8 @@ private double wilsonDewTemperature(double[] z, double pBar, double tGuess) { * @param phiOut output array of length C to be populated with phi_i * @return true on success, false if the forced-phase calculation failed */ - private boolean computeSinglePhaseFugacityCoefficients(double[] composition, double tempK, double pressBar, - boolean isVapor, double[] phiOut) { + private boolean computeSinglePhaseFugacityCoefficients(double[] composition, double tempK, + double pressBar, boolean isVapor, double[] phiOut) { try { SystemInterface sys = referenceSystem.clone(); sys.setTemperature(tempK); @@ -3713,22 +3772,23 @@ private boolean computeSinglePhaseFugacityCoefficients(double[] composition, dou sys.setForcePhaseTypes(true); sys.init(2); for (int i = 0; i < C; i++) { - double phi = sys.getPhase(0).getComponent(i).getFugacityCoefficient(); - if (!(phi > 0.0) || Double.isNaN(phi) || Double.isInfinite(phi)) { - return false; - } - phiOut[i] = phi; + double phi = sys.getPhase(0).getComponent(i).getFugacityCoefficient(); + if (!(phi > 0.0) || Double.isNaN(phi) || Double.isInfinite(phi)) { + return false; + } + phiOut[i] = phi; } return true; } catch (Exception e) { - logger.debug("Single-phase fugacity failed for {} phase at T={}K P={}bar", isVapor ? "vapor" : "liquid", tempK, - pressBar); + logger.debug("Single-phase fugacity failed for {} phase at T={}K P={}bar", + isVapor ? "vapor" : "liquid", tempK, pressBar); return false; } } /** - * Wilson correlation for K-values, used as a fallback / initialisation when an EOS evaluation fails. + * Wilson correlation for K-values, used as a fallback / initialisation when an EOS evaluation + * fails. * * @param i component index in the reference system * @param tempK temperature in Kelvin @@ -3743,7 +3803,8 @@ private double wilsonK(int i, double tempK, double pressBar) { return (Pc / pressBar) * Math.exp(5.37 * (1.0 + omega) * (1.0 - Tc / tempK)); } - private double computeSinglePhaseEnthalpy(double[] composition, double tempK, double pressBar, boolean isVapor) { + private double computeSinglePhaseEnthalpy(double[] composition, double tempK, double pressBar, + boolean isVapor) { try { SystemInterface sys = referenceSystem.clone(); sys.setTemperature(tempK); @@ -3760,10 +3821,11 @@ private double computeSinglePhaseEnthalpy(double[] composition, double tempK, do sys.setPhaseType(0, isVapor ? PhaseType.GAS : PhaseType.LIQUID); sys.setForcePhaseTypes(true); sys.init(2); - return sys.getPhase(0).getEnthalpy() / Math.max(sys.getPhase(0).getNumberOfMolesInPhase(), 1e-20); + return sys.getPhase(0).getEnthalpy() + / Math.max(sys.getPhase(0).getNumberOfMolesInPhase(), 1e-20); } catch (Exception e) { - logger.debug("Single-phase enthalpy failed for {} phase at T={}K P={}bar", isVapor ? "vapor" : "liquid", tempK, - pressBar); + logger.debug("Single-phase enthalpy failed for {} phase at T={}K P={}bar", + isVapor ? "vapor" : "liquid", tempK, pressBar); return 0.0; } } @@ -3772,7 +3834,8 @@ private double computeSinglePhaseEnthalpy(double[] composition, double tempK, do * Get the value of a variable by tray and intra-tray index. * * @param tray tray index - * @param k intra-tray variable index: 0..C-1 are liquid flows, C is temperature, C+1 is vapor flow + * @param k intra-tray variable index: 0..C-1 are liquid flows, C is temperature, C+1 is vapor + * flow * @return variable value */ private double getVariable(int tray, int k) { @@ -3806,8 +3869,8 @@ private void setVariable(int tray, int k, double value) { * Compute the overall mass balance error as a fraction of total feed. * *- * The overall mass balance is: V[N-1] + L[0] = total_feed. This method returns the absolute relative error |V[N-1] + - * L[0] - feed| / feed. + * The overall mass balance is: V[N-1] + L[0] = total_feed. This method returns the absolute + * relative error |V[N-1] + L[0] - feed| / feed. *
* * @return mass balance error as a fraction (0.005 = 0.5%) @@ -3816,7 +3879,7 @@ private double computeMassBalanceError() { double totalFeedFlow = 0; for (int j = 0; j < N; j++) { for (int i = 0; i < C; i++) { - totalFeedFlow += feedLiq[j][i] + feedVap[j][i]; + totalFeedFlow += feedLiq[j][i] + feedVap[j][i]; } } double topFlow = V[N - 1]; // vapor leaving top tray @@ -3825,14 +3888,17 @@ private double computeMassBalanceError() { } /** - * Maximum per-tray, per-component molar imbalance, expressed relative to the total feed molar flow. + * Maximum per-tray, per-component molar imbalance, expressed relative to the total feed molar + * flow. * *- * For each tray j and component i this evaluates the MESH M-residual {@code feed_{j,i} + liq_{j+1,i} + vap_{j-1,i} - - * liq_{j,i} - vap_{j,i}} (the same equation that {@link #computeResidual()} packs into the F vector), takes the - * absolute value, and divides by the total feed flow. Returning the worst value across all (j, i) pairs exposes - * leakage on individual species, which the scalar {@link #computeMassBalanceError()} (overall column closure) and the - * L2 norm {@code ||F||} can both mask. + * For each tray j and component i this evaluates the MESH M-residual + * {@code feed_{j,i} + liq_{j+1,i} + vap_{j-1,i} - + * liq_{j,i} - vap_{j,i}} (the same equation that {@link #computeResidual()} packs into the F + * vector), takes the absolute value, and divides by the total feed flow. Returning the worst + * value across all (j, i) pairs exposes leakage on individual species, which the scalar + * {@link #computeMassBalanceError()} (overall column closure) and the L2 norm {@code ||F||} can + * both mask. *
* * @return maximum relative component imbalance (1.0e-3 = 0.1%) @@ -3841,25 +3907,25 @@ private double computeMaxComponentImbalance() { double totalFeedFlow = 0.0; for (int j = 0; j < N; j++) { for (int i = 0; i < C; i++) { - totalFeedFlow += feedLiq[j][i] + feedVap[j][i]; + totalFeedFlow += feedLiq[j][i] + feedVap[j][i]; } } double denom = Math.max(totalFeedFlow, 1.0e-20); double worst = 0.0; for (int j = 0; j < N; j++) { for (int i = 0; i < C; i++) { - double mij = liq[j][i] + vap[j][i]; - if (j < N - 1) { - mij -= liq[j + 1][i]; - } - if (j > 0) { - mij -= vap[j - 1][i]; - } - mij -= feedLiq[j][i] + feedVap[j][i]; - double rel = Math.abs(mij) / denom; - if (rel > worst) { - worst = rel; - } + double mij = liq[j][i] + vap[j][i]; + if (j < N - 1) { + mij -= liq[j + 1][i]; + } + if (j > 0) { + mij -= vap[j - 1][i]; + } + mij -= feedLiq[j][i] + feedVap[j][i]; + double rel = Math.abs(mij) / denom; + if (rel > worst) { + worst = rel; + } } } return worst; @@ -3869,13 +3935,14 @@ private double computeMaxComponentImbalance() { * Apply a trust-region clamp to the Newton direction dx (in-place). * *- * Limits each variable's per-iteration change to a physically reasonable magnitude to prevent Newton from leaving the - * basin of attraction. The whole step vector is scaled by the smallest acceptable ratio so the descent direction is - * preserved. + * Limits each variable's per-iteration change to a physically reasonable magnitude to prevent + * Newton from leaving the basin of attraction. The whole step vector is scaled by the smallest + * acceptable ratio so the descent direction is preserved. *
* *- * Bounds: |dT| ≤ 10 K, |dV| ≤ 0.5*V + 0.05*flowScale, |d(liq_ij)| ≤ 0.5*liq_ij + 1e-3*flowScale. + * Bounds: |dT| ≤ 10 K, |dV| ≤ 0.5*V + 0.05*flowScale, |d(liq_ij)| ≤ 0.5*liq_ij + + * 1e-3*flowScale. *
* * @param dx the Newton step (will be scaled in place) @@ -3887,27 +3954,27 @@ private double applyTrustRegion(double[] dx) { for (int j = 0; j < N; j++) { int base = j * varsPerTray; for (int i = 0; i < C; i++) { - double cap = 0.5 * liq[j][i] + 1.0e-3 * flowScale; - double step = Math.abs(dx[base + i]); - if (step > cap && cap > 0.0) { - scale = Math.min(scale, cap / step); - } + double cap = 0.5 * liq[j][i] + 1.0e-3 * flowScale; + double step = Math.abs(dx[base + i]); + if (step > cap && cap > 0.0) { + scale = Math.min(scale, cap / step); + } } if (Double.isNaN(fixedTemperature[j])) { - double step = Math.abs(dx[base + C]); - if (step > maxDT) { - scale = Math.min(scale, maxDT / step); - } + double step = Math.abs(dx[base + C]); + if (step > maxDT) { + scale = Math.min(scale, maxDT / step); + } } double capV = 0.5 * V[j] + 0.05 * flowScale; double stepV = Math.abs(dx[base + C + 1]); if (stepV > capV && capV > 0.0) { - scale = Math.min(scale, capV / stepV); + scale = Math.min(scale, capV / stepV); } } if (scale < 1.0) { for (int k = 0; k < dx.length; k++) { - dx[k] *= scale; + dx[k] *= scale; } } return scale; @@ -3917,7 +3984,8 @@ private double applyTrustRegion(double[] dx) { * Apply the Newton update dx with step size alpha to the current state. * *- * Includes bounds enforcement: temperatures must remain positive, component flows must remain non-negative. + * Includes bounds enforcement: temperatures must remain positive, component flows must remain + * non-negative. *
* * @param dx correction vector @@ -3929,15 +3997,15 @@ private void applyUpdate(double[] dx, double alpha) { // Update liquid component flows with non-negativity bounds for (int i = 0; i < C; i++) { - double newVal = liq[j][i] - alpha * dx[base + i]; - liq[j][i] = Math.max(newVal, 1e-20); + double newVal = liq[j][i] - alpha * dx[base + i]; + liq[j][i] = Math.max(newVal, 1e-20); } // Update temperature with physical bounds (skip if fixed) if (Double.isNaN(fixedTemperature[j])) { - double newT = T[j] - alpha * dx[base + C]; - T[j] = Math.max(newT, 100.0); // minimum 100K - T[j] = Math.min(T[j], 1000.0); // maximum 1000K + double newT = T[j] - alpha * dx[base + C]; + T[j] = Math.max(newT, 100.0); // minimum 100K + T[j] = Math.min(T[j], 1000.0); // maximum 1000K } // Update vapor flow @@ -4004,23 +4072,23 @@ private double lineSearch(double[] dx, double currentNorm) { for (int bt = 0; bt < maxBacktrack; bt++) { // Trial update for (int j = 0; j < N; j++) { - for (int i = 0; i < C; i++) { - liq[j][i] = Math.max(saveLiq[j][i] - alpha * dx[j * varsPerTray + i], 1e-20); - } - if (Double.isNaN(fixedTemperature[j])) { - T[j] = Math.max(saveT[j] - alpha * dx[j * varsPerTray + C], 100.0); - T[j] = Math.min(T[j], 1000.0); - } - V[j] = Math.max(saveV[j] - alpha * dx[j * varsPerTray + C + 1], 0.0); + for (int i = 0; i < C; i++) { + liq[j][i] = Math.max(saveLiq[j][i] - alpha * dx[j * varsPerTray + i], 1e-20); + } + if (Double.isNaN(fixedTemperature[j])) { + T[j] = Math.max(saveT[j] - alpha * dx[j * varsPerTray + C], 100.0); + T[j] = Math.min(T[j], 1000.0); + } + V[j] = Math.max(saveV[j] - alpha * dx[j * varsPerTray + C + 1], 0.0); } evaluateThermo(); double[] Ftrial = computeResidual(); - double trialNorm = vectorNorm(Ftrial); + double trialNorm = LinearAlgebraOps.vectorNorm(Ftrial); if (trialNorm < (1.0 - c * alpha) * currentNorm || alpha < 0.01) { - bestAlpha = alpha; - break; + bestAlpha = alpha; + break; } alpha *= rho; @@ -4042,8 +4110,8 @@ private double lineSearch(double[] dx, double currentNorm) { * Solve the block-tridiagonal linear system J * dx = -F. * *- * The Jacobian J is block-tridiagonal with block size (C+2). Blocks: A_j (sub-diagonal, coupling to tray j-1), B_j - * (diagonal, tray j), C_j (super-diagonal, coupling to tray j+1). + * The Jacobian J is block-tridiagonal with block size (C+2). Blocks: A_j (sub-diagonal, coupling + * to tray j-1), B_j (diagonal, tray j), C_j (super-diagonal, coupling to tray j+1). *
* *@@ -4069,28 +4137,28 @@ private double[] solveBlockTridiagonal(double[][] J, double[] F) { // Diagonal block: columns from tray j for (int r = 0; r < m; r++) { - for (int c = 0; c < m; c++) { - Bdiag[j][r][c] = J[rowBase + r][j * m + c]; - } - rhs[j][r] = F[rowBase + r]; + for (int c = 0; c < m; c++) { + Bdiag[j][r][c] = J[rowBase + r][j * m + c]; + } + rhs[j][r] = F[rowBase + r]; } // Sub-diagonal block: columns from tray j-1 if (j > 0) { - for (int r = 0; r < m; r++) { - for (int c = 0; c < m; c++) { - Asub[j][r][c] = J[rowBase + r][(j - 1) * m + c]; - } - } + for (int r = 0; r < m; r++) { + for (int c = 0; c < m; c++) { + Asub[j][r][c] = J[rowBase + r][(j - 1) * m + c]; + } + } } // Super-diagonal block: columns from tray j+1 if (j < N - 1) { - for (int r = 0; r < m; r++) { - for (int c = 0; c < m; c++) { - Csup[j][r][c] = J[rowBase + r][(j + 1) * m + c]; - } - } + for (int r = 0; r < m; r++) { + for (int c = 0; c < m; c++) { + Csup[j][r][c] = J[rowBase + r][(j + 1) * m + c]; + } + } } } @@ -4109,7 +4177,7 @@ private double[] solveBlockTridiagonal(double[][] J, double[] F) { // Compute: fac = A_j * inv(B'_{j-1}) double[][] invBprev = invertBlock(Bprime[j - 1]); if (invBprev == null) { - return null; // singular + return null; // singular } double[][] fac = multiplyBlocks(Asub[j], invBprev); @@ -4117,15 +4185,15 @@ private double[] solveBlockTridiagonal(double[][] J, double[] F) { // B'_j = B_j - fac * C_{j-1} double[][] facC = multiplyBlocks(fac, Csup[j - 1]); for (int r = 0; r < m; r++) { - for (int c = 0; c < m; c++) { - Bprime[j][r][c] = Bdiag[j][r][c] - facC[r][c]; - } + for (int c = 0; c < m; c++) { + Bprime[j][r][c] = Bdiag[j][r][c] - facC[r][c]; + } } // rhs'_j = rhs_j - fac * rhs'_{j-1} double[] facRhs = multiplyBlockVec(fac, rhsPrime[j - 1]); for (int r = 0; r < m; r++) { - rhsPrime[j][r] = rhs[j][r] - facRhs[r]; + rhsPrime[j][r] = rhs[j][r] - facRhs[r]; } } @@ -4144,11 +4212,11 @@ private double[] solveBlockTridiagonal(double[][] J, double[] F) { double[] CxNext = multiplyBlockVec(Csup[j], xBlocks[j + 1]); double[] rhsAdj = new double[m]; for (int r = 0; r < m; r++) { - rhsAdj[r] = rhsPrime[j][r] - CxNext[r]; + rhsAdj[r] = rhsPrime[j][r] - CxNext[r]; } double[][] invBj = invertBlock(Bprime[j]); if (invBj == null) { - return null; + return null; } xBlocks[j] = multiplyBlockVec(invBj, rhsAdj); } @@ -4186,37 +4254,37 @@ private double[][] invertBlock(double[][] A) { int maxRow = col; double maxVal = Math.abs(aug[col][col]); for (int row = col + 1; row < m; row++) { - if (Math.abs(aug[row][col]) > maxVal) { - maxVal = Math.abs(aug[row][col]); - maxRow = row; - } + if (Math.abs(aug[row][col]) > maxVal) { + maxVal = Math.abs(aug[row][col]); + maxRow = row; + } } if (maxVal < 1e-30) { - return null; // singular + return null; // singular } // Swap rows if (maxRow != col) { - double[] tmp = aug[col]; - aug[col] = aug[maxRow]; - aug[maxRow] = tmp; + double[] tmp = aug[col]; + aug[col] = aug[maxRow]; + aug[maxRow] = tmp; } // Scale pivot row double pivot = aug[col][col]; for (int k = 0; k < 2 * m; k++) { - aug[col][k] /= pivot; + aug[col][k] /= pivot; } // Eliminate column for (int row = 0; row < m; row++) { - if (row != col) { - double factor = aug[row][col]; - for (int k = 0; k < 2 * m; k++) { - aug[row][k] -= factor * aug[col][k]; - } - } + if (row != col) { + double factor = aug[row][col]; + for (int k = 0; k < 2 * m; k++) { + aug[row][k] -= factor * aug[col][k]; + } + } } } @@ -4241,11 +4309,11 @@ private double[][] multiplyBlocks(double[][] A, double[][] B) { double[][] result = new double[m][m]; for (int i = 0; i < m; i++) { for (int j = 0; j < m; j++) { - double sum = 0; - for (int k = 0; k < m; k++) { - sum += A[i][k] * B[k][j]; - } - result[i][j] = sum; + double sum = 0; + for (int k = 0; k < m; k++) { + sum += A[i][k] * B[k][j]; + } + result[i][j] = sum; } } return result; @@ -4264,7 +4332,7 @@ private double[] multiplyBlockVec(double[][] A, double[] v) { for (int i = 0; i < m; i++) { double sum = 0; for (int k = 0; k < m; k++) { - sum += A[i][k] * v[k]; + sum += A[i][k] * v[k]; } result[i] = sum; } @@ -4294,27 +4362,27 @@ private double[] solveDenseLU(double[][] J, double[] F) { int maxRow = col; double maxVal = Math.abs(aug[col][col]); for (int row = col + 1; row < n; row++) { - if (Math.abs(aug[row][col]) > maxVal) { - maxVal = Math.abs(aug[row][col]); - maxRow = row; - } + if (Math.abs(aug[row][col]) > maxVal) { + maxVal = Math.abs(aug[row][col]); + maxRow = row; + } } if (maxVal < 1e-30) { - return null; + return null; } if (maxRow != col) { - double[] tmp = aug[col]; - aug[col] = aug[maxRow]; - aug[maxRow] = tmp; + double[] tmp = aug[col]; + aug[col] = aug[maxRow]; + aug[maxRow] = tmp; } for (int row = col + 1; row < n; row++) { - double factor = aug[row][col] / aug[col][col]; - for (int k = col; k <= n; k++) { - aug[row][k] -= factor * aug[col][k]; - } + double factor = aug[row][col] / aug[col][col]; + for (int k = col; k <= n; k++) { + aug[row][k] -= factor * aug[col][k]; + } } } @@ -4323,7 +4391,7 @@ private double[] solveDenseLU(double[][] J, double[] F) { for (int i = n - 1; i >= 0; i--) { double sum = aug[i][n]; for (int j = i + 1; j < n; j++) { - sum -= aug[i][j] * dx[j]; + sum -= aug[i][j] * dx[j]; } dx[i] = sum / aug[i][i]; } @@ -4333,27 +4401,14 @@ private double[] solveDenseLU(double[][] J, double[] F) { return dx; } - /** - * Compute the L2 norm of a vector. - * - * @param v vector - * @return ||v||_2 - */ - private double vectorNorm(double[] v) { - double sum = 0; - for (double vi : v) { - sum += vi * vi; - } - return Math.sqrt(sum); - } - /** * Apply the converged solution back to the DistillationColumn trays. * *
- * For each tray, creates a two-phase thermo system at the converged T, P, and overall composition (from liquid + - * vapor flows), performs a TPflash, then extracts the gas phase via {@code phaseToSystem(0)} and the liquid phase via - * {@code phaseToSystem(1)}. The phase flow rates are set to V[j] and L[j] from the solver variables. + * For each tray, creates a two-phase thermo system at the converged T, P, and overall composition + * (from liquid + vapor flows), performs a TPflash, then extracts the gas phase via + * {@code phaseToSystem(0)} and the liquid phase via {@code phaseToSystem(1)}. The phase flow + * rates are set to V[j] and L[j] from the solver variables. *
* * @param id calculation identifier @@ -4368,36 +4423,36 @@ private void applyResultsToColumn(UUID id, int iterations, double finalNorm, lon // Recompute L[j] from solved liquid flows double sumLiq = 0; for (int i = 0; i < C; i++) { - sumLiq += liq[j][i]; + sumLiq += liq[j][i]; } L[j] = Math.max(sumLiq, 1e-20); // Liquid composition x double[] x = new double[C]; for (int i = 0; i < C; i++) { - x[i] = liq[j][i] / L[j]; + x[i] = liq[j][i] / L[j]; } // Vapor composition y from K-values double[] y = new double[C]; double sumY = 0; for (int i = 0; i < C; i++) { - y[i] = K[j][i] * x[i]; - sumY += y[i]; + y[i] = K[j][i] * x[i]; + sumY += y[i]; } if (sumY > 1e-20) { - for (int i = 0; i < C; i++) { - y[i] /= sumY; - } + for (int i = 0; i < C; i++) { + y[i] /= sumY; + } } else { - System.arraycopy(x, 0, y, 0, C); + System.arraycopy(x, 0, y, 0, C); } // Overall composition z from liquid + vapor component flows double totalMoles = L[j] + V[j]; double[] z = new double[C]; for (int i = 0; i < C; i++) { - z[i] = (liq[j][i] + vap[j][i]) / Math.max(totalMoles, 1e-20); + z[i] = (liq[j][i] + vap[j][i]) / Math.max(totalMoles, 1e-20); } // Create overall tray system and flash to get proper two-phase equilibrium @@ -4412,16 +4467,16 @@ private void applyResultsToColumn(UUID id, int iterations, double finalNorm, lon ThermodynamicOperations ops = new ThermodynamicOperations(traySystem); try { - ops.TPflash(); - traySystem.init(2); - traySystem.initPhysicalProperties(); + ops.TPflash(); + traySystem.init(2); + traySystem.initPhysicalProperties(); } catch (Exception e) { - logger.warn("Final TPflash failed on tray {}", j); + logger.warn("Final TPflash failed on tray {}", j); } // Set the tray's mixed stream if (tray.getOutletStream() != null) { - tray.getOutletStream().setThermoSystem(traySystem); + tray.getOutletStream().setThermoSystem(traySystem); } tray.setTemperature(T[j]); @@ -4452,19 +4507,21 @@ private void applyResultsToColumn(UUID id, int iterations, double finalNorm, lon liqSystem.setNumberOfPhases(1); liqSystem.init(0); liqSystem.init(2); - tray.setCachedLiquidOutStream(new neqsim.process.equipment.stream.Stream("liq_" + j, liqSystem)); + tray.setCachedLiquidOutStream( + new neqsim.process.equipment.stream.Stream("liq_" + j, liqSystem)); } // Compute mass balance error for diagnostics double totalFeedFlow = 0; for (int j = 0; j < N; j++) { for (int i = 0; i < C; i++) { - totalFeedFlow += feedLiq[j][i] + feedVap[j][i]; + totalFeedFlow += feedLiq[j][i] + feedVap[j][i]; } } double topFlow = V[N - 1]; // vapor leaving top tray double botFlow = L[0]; // liquid leaving bottom tray - double massBalErr = Math.abs(topFlow + botFlow - totalFeedFlow) / Math.max(totalFeedFlow, 1e-20); + double massBalErr = + Math.abs(topFlow + botFlow - totalFeedFlow) / Math.max(totalFeedFlow, 1e-20); double solveTime = (System.nanoTime() - startTime) / 1.0e9; @@ -4474,9 +4531,10 @@ private void applyResultsToColumn(UUID id, int iterations, double finalNorm, lon lastSolveTimeSeconds = solveTime; logger.info( - "Naphtali-Sandholm results: iter={}, ||F||={}, " - + "massBalErr={}, topFlow={}, botFlow={}, feedFlow={}, time={}s", - iterations, String.format("%.6e", finalNorm), String.format("%.6e", massBalErr), String.format("%.4f", topFlow), - String.format("%.4f", botFlow), String.format("%.4f", totalFeedFlow), String.format("%.2f", solveTime)); + "Naphtali-Sandholm results: iter={}, ||F||={}, " + + "massBalErr={}, topFlow={}, botFlow={}, feedFlow={}, time={}s", + iterations, String.format("%.6e", finalNorm), String.format("%.6e", massBalErr), + String.format("%.4f", topFlow), String.format("%.4f", botFlow), + String.format("%.4f", totalFeedFlow), String.format("%.2f", solveTime)); } } diff --git a/src/main/java/neqsim/process/equipment/distillation/RateBasedPackedColumn.java b/src/main/java/neqsim/process/equipment/distillation/RateBasedPackedColumn.java index 8fe23f8cbc..7dd6e2d83c 100644 --- a/src/main/java/neqsim/process/equipment/distillation/RateBasedPackedColumn.java +++ b/src/main/java/neqsim/process/equipment/distillation/RateBasedPackedColumn.java @@ -22,17 +22,18 @@ import neqsim.thermo.system.SystemInterface; import neqsim.thermodynamicoperations.ThermodynamicOperations; import neqsim.util.ExcludeFromJacocoGeneratedReport; +import neqsim.util.math.LinearAlgebraOps; import neqsim.util.validation.ValidationResult; /** * Counter-current rate-based packed column for non-reactive absorption and stripping. * *- * The column is divided into axial segments. In each segment, NeqSim phase-equilibrium calculations provide the - * interfacial equilibrium driving force, {@link PhysicalProperties} provides effective diffusivities, and - * {@link PackingHydraulicsCalculator} provides packing hydraulics, wetted area, and film mass-transfer coefficients. - * Component transfer is bidirectional, so the same equipment can model gas-to-liquid absorption and liquid-to-gas - * stripping. + * The column is divided into axial segments. In each segment, NeqSim phase-equilibrium calculations + * provide the interfacial equilibrium driving force, {@link PhysicalProperties} provides effective + * diffusivities, and {@link PackingHydraulicsCalculator} provides packing hydraulics, wetted area, + * and film mass-transfer coefficients. Component transfer is bidirectional, so the same equipment + * can model gas-to-liquid absorption and liquid-to-gas stripping. *
* * @author NeqSim @@ -103,7 +104,8 @@ public class RateBasedPackedColumn extends ProcessEquipmentBaseClass { private double massTransferCorrectionFactor = 1.0; /** Packing specification used in all segments. */ - private PackingSpecification packingSpecification = PackingSpecificationLibrary.getOrDefault("Pall-Ring-50"); + private PackingSpecification packingSpecification = + PackingSpecificationLibrary.getOrDefault("Pall-Ring-50"); /** Optional transfer component whitelist. Empty means all components are considered. */ private final List- * Replaces the O(n³) Gaussian elimination in the Newton-Raphson GGA solver with efficient alternatives from the EJML - * library (already a NeqSim dependency): + * Replaces the O(n³) Gaussian elimination in the Newton-Raphson GGA solver with efficient + * alternatives from ojAlgo: *
*- * The Schur complement matrix in the Todini-Pilati GGA is structurally sparse: entry (i,j) is nonzero only if nodes i - * and j are connected by at least one pipe. For a gas gathering network with n=100 nodes and average degree 3, matrix - * density is ~6%, making sparse solvers 10–50x faster than dense Gaussian. + * The Schur complement matrix in the Todini-Pilati GGA is structurally sparse: entry (i,j) is + * nonzero only if nodes i and j are connected by at least one pipe. For a gas gathering network + * with n=100 nodes and average degree 3, matrix density is ~6%, making sparse solvers 10–50x faster + * than dense Gaussian. *
* * @author Even Solbraa @@ -38,14 +34,15 @@ public class NetworkLinearSolver { private static final Logger logger = LogManager.getLogger(NetworkLinearSolver.class); /** - * Threshold for switching from Gaussian to EJML solvers. Networks with more free nodes than this threshold use EJML - * (dense LU for n ≤ 100, sparse CSC LU for n > 100). Below this threshold, Gaussian elimination is used for - * backward compatibility with existing solver convergence behavior. + * Threshold for switching from Gaussian to EJML solvers. Networks with more free nodes than this + * threshold use ojAlgo (dense LU for n ≤ 100, sparse path for n > 100). Below this + * threshold, Gaussian elimination is used for backward compatibility with existing solver + * convergence behavior. */ private static final int EJML_THRESHOLD = 30; /** - * Threshold for switching from dense to sparse EJML solver within the EJML path. + * Threshold for switching from dense to sparse-path solver within the ojAlgo path. */ private static final int SPARSE_THRESHOLD = 100; @@ -53,9 +50,10 @@ public class NetworkLinearSolver { * Solve the linear system Ax = b using the most appropriate method. * *- * For small systems (n ≤ {@value #EJML_THRESHOLD}), uses Gaussian elimination with partial pivoting for backward - * compatibility. For medium systems, uses EJML dense LU. For large systems (n > {@value #SPARSE_THRESHOLD}), uses - * EJML sparse CSC LU. Falls back to Gaussian elimination if EJML solvers fail. + * For small systems (n ≤ {@value #EJML_THRESHOLD}), uses Gaussian elimination with partial + * pivoting for backward compatibility. For medium systems, uses ojAlgo dense LU. For large + * systems (n > {@value #SPARSE_THRESHOLD}), uses sparse-path solve. Falls back to Gaussian + * elimination if EJML solvers fail. *
* * @param matA coefficient matrix (n x n) @@ -80,22 +78,23 @@ public static double[] solve(double[][] matA, double[] vecB, int n) { try { if (n > SPARSE_THRESHOLD) { - return solveSparse(matA, vecB, n); + return solveSparse(matA, vecB, n); } else { - return solveDense(matA, vecB, n); + return solveDense(matA, vecB, n); } } catch (Exception e) { - logger.warn("EJML solver failed (n=" + n + "), falling back to Gaussian: " + e.getMessage()); + logger + .warn("ojAlgo solver failed (n=" + n + "), falling back to Gaussian: " + e.getMessage()); return solveGaussian(matA, vecB, n); } } /** - * Solve using EJML dense LU decomposition with partial pivoting. + * Solve using ojAlgo dense LU decomposition with partial pivoting. * *- * Uses EJML LinearSolverFactory_DDRM.lu() which provides O(n³/3) factorization with BLAS-optimized operations. For - * n=50, approximately 5x faster than hand-coded Gaussian elimination. + * Uses ojAlgo LU decomposition which provides O(n³/3) factorization with BLAS-optimized + * operations. For n=50, approximately 5x faster than hand-coded Gaussian elimination. *
* * @param matA coefficient matrix (n x n) @@ -104,40 +103,20 @@ public static double[] solve(double[][] matA, double[] vecB, int n) { * @return solution vector x */ public static double[] solveDense(double[][] matA, double[] vecB, int n) { - DMatrixRMaj denseA = new DMatrixRMaj(n, n); - DMatrixRMaj denseB = new DMatrixRMaj(n, 1); - DMatrixRMaj denseX = new DMatrixRMaj(n, 1); - - // Copy data to EJML matrices - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - denseA.set(i, j, matA[i][j]); - } - denseB.set(i, 0, vecB[i]); - } - - LinearSolverDense- * Converts the dense Schur complement to compressed sparse column (CSC) format, discarding structural zeros. Uses - * EJML sparse LU with natural ordering; EJML 0.41/0.45 do not expose a deterministic AMD/COLAMD fill-reducing option - * through this factory. For a 100x100 matrix with 6% density, sparse storage can be significantly faster than dense - * Gaussian. + * Converts through the sparsity decision path. The current backend uses dense ojAlgo LU for + * robustness and deterministic behavior; dense fallback remains in place. *
* * @param matA coefficient matrix (n x n) — may have many zeros @@ -150,59 +129,32 @@ public static double[] solveSparse(double[][] matA, double[] vecB, int n) { int nnz = 0; for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { - if (Math.abs(matA[i][j]) > 1e-30) { - nnz++; - } + if (Math.abs(matA[i][j]) > 1e-30) { + nnz++; + } } } double density = (double) nnz / (n * n); if (density > 0.5) { // Matrix is too dense for sparse solver benefit; use dense - logger.debug("Sparse matrix density " + String.format("%.1f%%", density * 100) + " too high, using dense solver"); - return solveDense(matA, vecB, n); - } - - // Build sparse CSC matrix - DMatrixSparseCSC sparseA = new DMatrixSparseCSC(n, n, nnz); - for (int j = 0; j < n; j++) { - for (int i = 0; i < n; i++) { - if (Math.abs(matA[i][j]) > 1e-30) { - sparseA.set(i, j, matA[i][j]); - } - } - } - - DMatrixRMaj denseB = new DMatrixRMaj(n, 1); - DMatrixRMaj denseX = new DMatrixRMaj(n, 1); - for (int i = 0; i < n; i++) { - denseB.set(i, 0, vecB[i]); - } - - LinearSolverSparse- * This is the original O(n³) solver, kept as a robust fallback when EJML solvers encounter issues (singular matrices, - * numerical edge cases). Makes a defensive copy of the input arrays. + * This is the original O(n³) solver, kept as a robust fallback when EJML solvers encounter issues + * (singular matrices, numerical edge cases). Makes a defensive copy of the input arrays. *
* * @param matAOrig coefficient matrix (n x n) — not modified @@ -225,9 +177,9 @@ public static double[] solveGaussian(double[][] matAOrig, double[] vecBOrig, int for (int k = 0; k < n; k++) { int maxRow = k; for (int i = k + 1; i < n; i++) { - if (Math.abs(matA[i][k]) > Math.abs(matA[maxRow][k])) { - maxRow = i; - } + if (Math.abs(matA[i][k]) > Math.abs(matA[maxRow][k])) { + maxRow = i; + } } double[] tempRow = matA[k]; matA[k] = matA[maxRow]; @@ -237,15 +189,15 @@ public static double[] solveGaussian(double[][] matAOrig, double[] vecBOrig, int vecB[maxRow] = tempB; if (Math.abs(matA[k][k]) < 1e-20) { - continue; + continue; } for (int i = k + 1; i < n; i++) { - double factor = matA[i][k] / matA[k][k]; - for (int j = k + 1; j < n; j++) { - matA[i][j] -= factor * matA[k][j]; - } - vecB[i] -= factor * vecB[k]; + double factor = matA[i][k] / matA[k][k]; + for (int j = k + 1; j < n; j++) { + matA[i][j] -= factor * matA[k][j]; + } + vecB[i] -= factor * vecB[k]; } } @@ -253,10 +205,10 @@ public static double[] solveGaussian(double[][] matAOrig, double[] vecBOrig, int for (int i = n - 1; i >= 0; i--) { x[i] = vecB[i]; for (int j = i + 1; j < n; j++) { - x[i] -= matA[i][j] * x[j]; + x[i] -= matA[i][j] * x[j]; } if (Math.abs(matA[i][i]) > 1e-20) { - x[i] /= matA[i][i]; + x[i] /= matA[i][i]; } } return x; @@ -271,8 +223,8 @@ public static double[] solveGaussian(double[][] matAOrig, double[] vecBOrig, int * * @param nodeCount number of free nodes * @param pipeCount number of pipe elements - * @return array [density, nonzeros, recommended_threshold] where recommended_threshold is 0 for dense and 1 for - * sparse + * @return array [density, nonzeros, recommended_threshold] where recommended_threshold is 0 for + * dense and 1 for sparse */ public static double[] estimateSparsity(int nodeCount, int pipeCount) { // In a pipe network, each pipe connects exactly 2 nodes. @@ -283,6 +235,6 @@ public static double[] estimateSparsity(int nodeCount, int pipeCount) { int totalEntries = nodeCount * nodeCount; double density = (totalEntries > 0) ? (double) estimatedNnz / totalEntries : 1.0; double usesSparse = (nodeCount > SPARSE_THRESHOLD && density < 0.5) ? 1.0 : 0.0; - return new double[] { density, estimatedNnz, usesSparse }; + return new double[] {density, estimatedNnz, usesSparse}; } } diff --git a/src/main/java/neqsim/process/equipment/reactor/GibbsReactor.java b/src/main/java/neqsim/process/equipment/reactor/GibbsReactor.java index 3db756aec2..6e0c203800 100644 --- a/src/main/java/neqsim/process/equipment/reactor/GibbsReactor.java +++ b/src/main/java/neqsim/process/equipment/reactor/GibbsReactor.java @@ -1,5 +1,10 @@ package neqsim.process.equipment.reactor; +import neqsim.process.equipment.TwoPortEquipment; +import neqsim.process.equipment.stream.StreamInterface; +import neqsim.thermo.system.SystemInterface; +import neqsim.util.math.LinearAlgebraOps; + import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; @@ -9,20 +14,17 @@ import java.util.Scanner; import java.util.UUID; import java.util.regex.Pattern; + import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.ejml.simple.SimpleMatrix; -import neqsim.process.equipment.TwoPortEquipment; -import neqsim.process.equipment.stream.StreamInterface; -import neqsim.thermo.system.SystemInterface; /** * Gibbs reactor for chemical equilibrium calculations using Gibbs free energy minimization. * *- * This reactor computes chemical equilibrium compositions by minimizing the total Gibbs free energy of the system - * subject to elemental mass balance constraints. The implementation uses the Newton-Raphson method with Lagrange - * multipliers to solve the constrained optimization problem. + * This reactor computes chemical equilibrium compositions by minimizing the total Gibbs free energy + * of the system subject to elemental mass balance constraints. The implementation uses the + * Newton-Raphson method with Lagrange multipliers to solve the constrained optimization problem. *
* *- * where nᵢ are molar amounts, μᵢ⁰ is standard chemical potential, φᵢ is fugacity coefficient, yᵢ is mole fraction, λⱼ - * are Lagrange multipliers, aᵢⱼ are stoichiometric coefficients, and bⱼ are element totals. + * where nᵢ are molar amounts, μᵢ⁰ is standard chemical potential, φᵢ is fugacity coefficient, yᵢ is + * mole fraction, λⱼ are Lagrange multipliers, aᵢⱼ are stoichiometric coefficients, and bⱼ are + * element totals. *
* *- * A species can only form if every element it contains is present in the feed. When an element has zero total feed - * availability, the corresponding element mass-balance constraint row is degenerate (its "determinator" is zero) and - * every species requiring that element is frozen at its feed amount and removed as an optimization variable. This - * prevents the solver from creating spurious trace amounts of species (for example sulfuric acid when no sulfur is - * fed). + * A species can only form if every element it contains is present in the feed. When an element + * has zero total feed availability, the corresponding element mass-balance constraint row is + * degenerate (its "determinator" is zero) and every species requiring that element is frozen at + * its feed amount and removed as an optimization variable. This prevents the solver from creating + * spurious trace amounts of species (for example sulfuric acid when no sulfur is fed). *
* * @param componentName the name of the component to check @@ -476,13 +481,14 @@ public boolean isComponentExcludedByFeed(String componentName) { } /** - * Determine which components must be excluded from the optimization matrix for the current run because at least one - * of their constituent elements is not available in the feed. + * Determine which components must be excluded from the optimization matrix for the current run + * because at least one of their constituent elements is not available in the feed. * *- * Uses the already-computed inlet element mole balance ({@link #elementMoleBalanceIn}). For each component, if it has - * a non-zero coefficient for an element whose total feed availability is at or below {@link #ELEMENT_ZERO_THRESHOLD}, - * the component is added to {@link #feedExcludedComponents}. The result is recomputed on every run. + * Uses the already-computed inlet element mole balance ({@link #elementMoleBalanceIn}). For each + * component, if it has a non-zero coefficient for an element whose total feed availability is at + * or below {@link #ELEMENT_ZERO_THRESHOLD}, the component is added to + * {@link #feedExcludedComponents}. The result is recomputed on every run. *
* * @param system the thermodynamic system whose components are checked @@ -493,17 +499,17 @@ private void determineFeedExcludedComponents(SystemInterface system) { String compName = system.getComponent(i).getComponentName(); GibbsComponent comp = componentMap.get(compName.toLowerCase()); if (comp == null) { - continue; + continue; } double[] elements = comp.getElements(); for (int j = 0; j < elementNames.length; j++) { - if (Math.abs(elements[j]) > ELEMENT_ZERO_THRESHOLD - && Math.abs(elementMoleBalanceIn[j]) <= ELEMENT_ZERO_THRESHOLD) { - feedExcludedComponents.add(compName.toLowerCase()); - logger.debug("Excluding component '" + compName + "' from Gibbs matrix: element '" + elementNames[j] - + "' not available in feed."); - break; - } + if (Math.abs(elements[j]) > ELEMENT_ZERO_THRESHOLD + && Math.abs(elementMoleBalanceIn[j]) <= ELEMENT_ZERO_THRESHOLD) { + feedExcludedComponents.add(compName.toLowerCase()); + logger.debug("Excluding component '" + compName + "' from Gibbs matrix: element '" + + elementNames[j] + "' not available in feed."); + break; + } } } } @@ -524,9 +530,9 @@ private void determineFeedExcludedComponents(SystemInterface system) { * Enable Armijo backtracking line search for guaranteed Gibbs energy decrease. * *- * When enabled, the Newton step is scaled by alpha in (0, 1] such that the Armijo sufficient decrease condition is - * satisfied: G(n + alpha * dn) <= G(n) + c1 * alpha * grad^T * dn. This replaces fixed damping with an adaptive, - * globally convergent strategy. + * When enabled, the Newton step is scaled by alpha in (0, 1] such that the Armijo sufficient + * decrease condition is satisfied: G(n + alpha * dn) <= G(n) + c1 * alpha * grad^T * dn. This + * replaces fixed damping with an adaptive, globally convergent strategy. *
* *@@ -554,9 +560,9 @@ private void determineFeedExcludedComponents(SystemInterface system) { * Enable Tikhonov regularization for ill-conditioned Jacobians. * *
- * When the condition number of the Jacobian exceeds {@code regularizationThreshold}, a regularization term tau * I is - * added to the Hessian block: H_reg = H + tau * I. This prevents divergence near phase boundaries where the Hessian - * becomes singular. + * When the condition number of the Jacobian exceeds {@code regularizationThreshold}, a + * regularization term tau * I is added to the Hessian block: H_reg = H + tau * I. This prevents + * divergence near phase boundaries where the Hessian becomes singular. *
* *
@@ -596,15 +602,15 @@ private void determineFeedExcludedComponents(SystemInterface system) {
private transient List
- * The Jacobian was column-scaled: J_scaled[:,j] = J[:,j] * n_j. Solving gives deltaX_scaled, and the true step is
- * deltaX[j] = deltaX_scaled[j] / n_j. Falls back to pseudo-inverse if LU solve fails (e.g., singular matrix).
+ * The Jacobian was column-scaled: J_scaled[:,j] = J[:,j] * n_j. Solving gives deltaX_scaled, and
+ * the true step is deltaX[j] = deltaX_scaled[j] / n_j. Falls back to pseudo-inverse if LU solve
+ * fails (e.g., singular matrix).
*
- * When enabled, the Newton step is adaptively scaled to guarantee sufficient decrease in the total Gibbs free energy
- * at each iteration. This provides a globally convergent algorithm replacing fixed damping.
+ * When enabled, the Newton step is adaptively scaled to guarantee sufficient decrease in the
+ * total Gibbs free energy at each iteration. This provides a globally convergent algorithm
+ * replacing fixed damping.
*
- * Starting from alpha = alpha_max, the step is contracted by factor rho until the Armijo condition is satisfied: G(n
- * + alpha*dn) <= G(n) + c1*alpha*grad^T*dn.
+ * Starting from alpha = alpha_max, the step is contracted by factor rho until the Armijo
+ * condition is satisfied: G(n + alpha*dn) <= G(n) + c1*alpha*grad^T*dn.
*
@@ -2693,15 +2653,15 @@ private double armijoLineSearch(double[] deltaX, double alphaMax, double current
String compName = variableComponents.get(i);
Double fi = fValues.get(compName);
if (fi != null) {
- directionalDerivative += fi * deltaX[i];
+ directionalDerivative += fi * deltaX[i];
}
}
// If directional derivative is non-negative, Newton direction is not a descent direction;
// fall back to the max alpha (steepest descent would be needed, but we just use the step)
if (directionalDerivative >= 0.0) {
- logger.debug("Armijo: directional derivative non-negative ({}), using alphaMax={}", directionalDerivative,
- alphaMax);
+ logger.debug("Armijo: directional derivative non-negative ({}), using alphaMax={}",
+ directionalDerivative, alphaMax);
return alphaMax;
}
@@ -2713,12 +2673,12 @@ private double armijoLineSearch(double[] deltaX, double alphaMax, double current
for (int k = 0; k < armijoMaxBacktracks; k++) {
// Trial update: n_trial = n + alpha * dn (composition only)
for (int i = 0; i < numComponents; i++) {
- String compName = variableComponents.get(i);
- int globalIdx = processedComponentIndexMap.getOrDefault(compName, -1);
- if (globalIdx >= 0 && globalIdx < outlet_mole.size()) {
- double trialValue = savedMoles.get(globalIdx) + alpha * deltaX[i];
- outlet_mole.set(globalIdx, Math.max(trialValue, 1e-15));
- }
+ String compName = variableComponents.get(i);
+ int globalIdx = processedComponentIndexMap.getOrDefault(compName, -1);
+ if (globalIdx >= 0 && globalIdx < outlet_mole.size()) {
+ double trialValue = savedMoles.get(globalIdx) + alpha * deltaX[i];
+ outlet_mole.set(globalIdx, Math.max(trialValue, 1e-15));
+ }
}
// Update system and evaluate Gibbs energy at trial point
@@ -2729,14 +2689,15 @@ private double armijoLineSearch(double[] deltaX, double alphaMax, double current
// Armijo condition: G(trial) <= G(current) + c1 * alpha * grad^T * dn
double sufficientDecrease = currentG + armijoC1 * alpha * directionalDerivative;
if (trialG <= sufficientDecrease) {
- logger.debug("Armijo accepted: alpha={}, G={} -> {}, backtracks={}", alpha, currentG, trialG, k);
- // Restore moles (the actual update will happen in performIterationUpdate)
- for (int i = 0; i < outlet_mole.size(); i++) {
- outlet_mole.set(i, savedMoles.get(i));
- }
- System.arraycopy(savedLambda, 0, lambda, 0, lambda.length);
- updateSystemWithNewCompositions();
- return alpha;
+ logger.debug("Armijo accepted: alpha={}, G={} -> {}, backtracks={}", alpha, currentG,
+ trialG, k);
+ // Restore moles (the actual update will happen in performIterationUpdate)
+ for (int i = 0; i < outlet_mole.size(); i++) {
+ outlet_mole.set(i, savedMoles.get(i));
+ }
+ System.arraycopy(savedLambda, 0, lambda, 0, lambda.length);
+ updateSystemWithNewCompositions();
+ return alpha;
}
// Contract step
@@ -2754,12 +2715,12 @@ private double armijoLineSearch(double[] deltaX, double alphaMax, double current
}
/**
- * Apply Tikhonov regularization to the Jacobian matrix if the condition number exceeds the threshold. Adds tau*I to
- * the Hessian (composition-composition) block of the Jacobian.
+ * Apply Tikhonov regularization to the Jacobian matrix if the condition number exceeds the
+ * threshold. Adds tau*I to the Hessian (composition-composition) block of the Jacobian.
*
*
- * This converts the saddle-point system into a positive-definite system when the Hessian is nearly singular, ensuring
- * the Newton direction remains well-defined.
+ * This converts the saddle-point system into a positive-definite system when the Hessian is
+ * nearly singular, ensuring the Newton direction remains well-defined.
*
- * At each iteration, a QP sub-problem is formed with a BFGS approximation of the Hessian of the Lagrangian, and the
- * solution provides a search direction. An Armijo-backtracking line search on a merit function ensures global
- * convergence.
+ * At each iteration, a QP sub-problem is formed with a BFGS approximation of the Hessian of the
+ * Lagrangian, and the solution provides a search direction. An Armijo-backtracking line search on a
+ * merit function ensures global convergence.
*
- * Minimizes: 0.5 * d^T * H * d + grad_f^T * d subject to linearized constraints. Uses a simplified projected gradient
- * approach with active-set handling.
+ * Minimizes: 0.5 * d^T * H * d + grad_f^T * d subject to linearized constraints. Uses a
+ * simplified projected gradient approach with active-set handling.
*
@@ -22,7 +19,8 @@
* @author Even Solbraa
* @version $Id: $Id
*/
-public class PhaseElectrolyteCPA extends PhaseModifiedFurstElectrolyteEos implements PhaseCPAInterface {
+public class PhaseElectrolyteCPA extends PhaseModifiedFurstElectrolyteEos
+ implements PhaseCPAInterface {
/** Serialization version UID. */
private static final long serialVersionUID = 1000;
/** Logger object for class. */
@@ -55,31 +53,31 @@ public class PhaseElectrolyteCPA extends PhaseModifiedFurstElectrolyteEos implem
private double[][] deltadT = null;
private double[][] deltadTdT = null;
private double[][][] Klkni = null;
- private SimpleMatrix KlkTVMatrix = null;
- private SimpleMatrix KlkTTMatrix = null;
- private SimpleMatrix KlkTMatrix = null;
- private SimpleMatrix udotTimesmMatrix = null;
- private SimpleMatrix mVector = null;
- private SimpleMatrix udotMatrix = null;
- private SimpleMatrix uMatrix = null;
- private SimpleMatrix QMatksiksiksi = null;
- private SimpleMatrix KlkVVVMatrix = null;
- private SimpleMatrix KlkVVMatrix = null;
- private SimpleMatrix udotTimesmiMatrix = null;
- private SimpleMatrix ksiMatrix = null;
- private SimpleMatrix KlkMatrix = null;
- private SimpleMatrix hessianMatrix = null;
- private SimpleMatrix hessianInvers = null;
+ private DenseMatrix KlkTVMatrix = null;
+ private DenseMatrix KlkTTMatrix = null;
+ private DenseMatrix KlkTMatrix = null;
+ private DenseMatrix udotTimesmMatrix = null;
+ private DenseMatrix mVector = null;
+ private DenseMatrix udotMatrix = null;
+ private DenseMatrix uMatrix = null;
+ private DenseMatrix QMatksiksiksi = null;
+ private DenseMatrix KlkVVVMatrix = null;
+ private DenseMatrix KlkVVMatrix = null;
+ private DenseMatrix udotTimesmiMatrix = null;
+ private DenseMatrix ksiMatrix = null;
+ private DenseMatrix KlkMatrix = null;
+ private DenseMatrix hessianMatrix = null;
+ private DenseMatrix hessianInvers = null;
/** Cached LU factorization of {@code hessianMatrix} for repeated Hessian backsolves. */
- private transient LinearSolverDense
@@ -42,7 +39,7 @@ public class PhaseSrkCPA extends PhaseSrkEos implements PhaseCPAInterface {
double dFCPAdVdV = 0.0;
double dFCPAdVdVdV = 0.0;
double gcpav = 0.0;
- protected double[] dFdNtemp = { 0, 0 };
+ protected double[] dFdNtemp = {0, 0};
int cpaon = 1;
int oldTotalNumberOfAccociationSites = 0;
int totalNumberOfAccociationSites = 0;
@@ -58,36 +55,38 @@ public class PhaseSrkCPA extends PhaseSrkEos implements PhaseCPAInterface {
private double[][] deltadT = null;
private double[][] deltadTdT = null;
double[][][] Klkni = null;
- private SimpleMatrix KlkTVMatrix = null;
- private SimpleMatrix KlkTTMatrix = null;
- private SimpleMatrix KlkTMatrix = null;
- private SimpleMatrix udotTimesmMatrix = null;
- private SimpleMatrix mVector = null;
- private SimpleMatrix udotMatrix = null;
- private SimpleMatrix uMatrix = null;
- private SimpleMatrix QMatksiksiksi = null;
- private SimpleMatrix KlkVVVMatrix = null;
- private SimpleMatrix KlkVVMatrix = null;
- private SimpleMatrix udotTimesmiMatrix = null;
- private SimpleMatrix ksiMatrix = null;
- private SimpleMatrix KlkMatrix = null;
- private SimpleMatrix hessianMatrix = null;
- private SimpleMatrix hessianInvers = null;
+ private DenseMatrix KlkTVMatrix = null;
+ private DenseMatrix KlkTTMatrix = null;
+ private DenseMatrix KlkTMatrix = null;
+ private DenseMatrix udotTimesmMatrix = null;
+ private DenseMatrix mVector = null;
+ private DenseMatrix udotMatrix = null;
+ private DenseMatrix uMatrix = null;
+ private DenseMatrix QMatksiksiksi = null;
+ private DenseMatrix KlkVVVMatrix = null;
+ private DenseMatrix KlkVVMatrix = null;
+ private DenseMatrix udotTimesmiMatrix = null;
+ private DenseMatrix ksiMatrix = null;
+ private DenseMatrix KlkMatrix = null;
+ private DenseMatrix hessianMatrix = null;
+ private DenseMatrix hessianInvers = null;
/**
- * Cached LU factorization of {@code hessianMatrix}. Reused by {@link #applyHessianInv} instead of multiplying by an
- * explicit inverse when available. The solver is rebuilt when the number of association sites changes.
+ * Cached LU factorization of {@code hessianMatrix}. Reused by {@link #applyHessianInv} instead of
+ * multiplying by an explicit inverse when available. The solver is rebuilt when the number of
+ * association sites changes.
*/
- private transient LinearSolverDense
@@ -109,7 +108,8 @@ public PhaseSrkCPA clone() {
}
if (activeAccosComp != null) {
clonedPhase.activeAccosComp = activeAccosComp.clone();
- System.arraycopy(this.activeAccosComp, 0, clonedPhase.activeAccosComp, 0, activeAccosComp.length);
+ System.arraycopy(this.activeAccosComp, 0, clonedPhase.activeAccosComp, 0,
+ activeAccosComp.length);
}
// clonedPhase.cpaSelect = (CPAMixing) cpaSelect.clone();
// clonedPhase.cpamix = (CPAMixingInterface) cpamix.clone();
@@ -120,92 +120,106 @@ public PhaseSrkCPA clone() {
/** {@inheritDoc} */
@Override
- public void init(double totalNumberOfMoles, int numberOfComponents, int initType, PhaseType pt, double beta) {
+ public void init(double totalNumberOfMoles, int numberOfComponents, int initType, PhaseType pt,
+ double beta) {
boolean changedAssosiationStatus = false;
if (initType == 0) {
activeAccosComp = new int[numberOfComponents];
for (int i = 0; i < numberOfComponents; i++) {
- if (componentArray[i].getNumberOfmoles() < 1e-50) {
- componentArray[i].setNumberOfAssociationSites(0);
- if (activeAccosComp[i] == 1) {
- activeAccosComp[i] = 0;
- changedAssosiationStatus = true;
- }
- } else {
- if (activeAccosComp[i] == 0) {
- changedAssosiationStatus = true;
- activeAccosComp[i] = 1;
- }
- }
+ if (componentArray[i].getNumberOfmoles() < 1e-50) {
+ componentArray[i].setNumberOfAssociationSites(0);
+ if (activeAccosComp[i] == 1) {
+ activeAccosComp[i] = 0;
+ changedAssosiationStatus = true;
+ }
+ } else {
+ if (activeAccosComp[i] == 0) {
+ changedAssosiationStatus = true;
+ activeAccosComp[i] = 1;
+ }
+ }
}
if (changedAssosiationStatus || lngi == null) {
- setTotalNumberOfAccociationSites(0);
- selfAccociationScheme = new int[numberOfComponents][0][0];
- crossAccociationScheme = new int[numberOfComponents][numberOfComponents][0][0];
- for (int i = 0; i < numberOfComponents; i++) {
- if (componentArray[i].getNumberOfmoles() < 1e-50) {
- componentArray[i].setNumberOfAssociationSites(0);
- } else {
- componentArray[i].setNumberOfAssociationSites(componentArray[i].getOrginalNumberOfAssociationSites());
- setTotalNumberOfAccociationSites(
- getTotalNumberOfAccociationSites() + componentArray[i].getNumberOfAssociationSites());
- selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
- for (int j = 0; j < numberOfComponents; j++) {
- crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
- }
- }
- }
+ setTotalNumberOfAccociationSites(0);
+ selfAccociationScheme = new int[numberOfComponents][0][0];
+ crossAccociationScheme = new int[numberOfComponents][numberOfComponents][0][0];
+ for (int i = 0; i < numberOfComponents; i++) {
+ if (componentArray[i].getNumberOfmoles() < 1e-50) {
+ componentArray[i].setNumberOfAssociationSites(0);
+ } else {
+ componentArray[i].setNumberOfAssociationSites(
+ componentArray[i].getOrginalNumberOfAssociationSites());
+ setTotalNumberOfAccociationSites(getTotalNumberOfAccociationSites()
+ + componentArray[i].getNumberOfAssociationSites());
+ selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
+ for (int j = 0; j < numberOfComponents; j++) {
+ crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
+ }
+ }
+ }
}
for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- ((ComponentSrkCPA) componentArray[i]).setXsite(j, 1.0);
- ((ComponentSrkCPA) componentArray[i]).setXsitedV(j, 0.0);
- ((ComponentSrkCPA) componentArray[i]).setXsitedT(j, 0.0);
- }
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ ((ComponentSrkCPA) componentArray[i]).setXsite(j, 1.0);
+ ((ComponentSrkCPA) componentArray[i]).setXsitedV(j, 0.0);
+ ((ComponentSrkCPA) componentArray[i]).setXsitedT(j, 0.0);
+ }
}
if (changedAssosiationStatus || lngi == null || mVector == null) {
- lngi = new double[numberOfComponents];
- mVector = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- KlkMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- hessianMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- corr2Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr3Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr4Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- Klkni = new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- ksiMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- uMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- udotMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- moleculeNumber = new int[getTotalNumberOfAccociationSites()];
- assSiteNumber = new int[getTotalNumberOfAccociationSites()];
- gvector = new double[getTotalNumberOfAccociationSites()][1];
- udotTimesmMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- delta = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltaNog = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltadT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltadTdT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- QMatksiksiksi = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- udotTimesmiMatrix = new SimpleMatrix(numberOfComponents, getTotalNumberOfAccociationSites());
-
- oldTotalNumberOfAccociationSites = getTotalNumberOfAccociationSites();
-
- int temp = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- moleculeNumber[temp + j] = i;
- assSiteNumber[temp + j] = j;
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
+ lngi = new double[numberOfComponents];
+ mVector = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ KlkMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ hessianMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ corr2Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr3Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr4Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ Klkni =
+ new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ ksiMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ uMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ moleculeNumber = new int[getTotalNumberOfAccociationSites()];
+ assSiteNumber = new int[getTotalNumberOfAccociationSites()];
+ gvector = new double[getTotalNumberOfAccociationSites()][1];
+ udotTimesmMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ delta = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ deltaNog =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ deltadT =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ deltadTdT =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ QMatksiksiksi = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotTimesmiMatrix = new DenseMatrix(numberOfComponents, getTotalNumberOfAccociationSites());
+
+ oldTotalNumberOfAccociationSites = getTotalNumberOfAccociationSites();
+
+ int temp = 0;
+ for (int i = 0; i < numberOfComponents; i++) {
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ moleculeNumber[temp + j] = i;
+ assSiteNumber[temp + j] = j;
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
+ }
}
}
@@ -262,46 +276,47 @@ public void initCPAMatrix(int type) {
double tempVar2;
for (int i = 0; i < numberOfComponents; i++) {
for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- tempVar1 = ksiMatrix.get(temp + j, 0);
- tempVar2 = udotMatrix.get(temp + j, 0);
- uMatrix.set(temp + j, 0, Math.log(tempVar1) - tempVar1 + 1.0);
- gvector[temp + j][0] = mVector.get(temp + j, 0) * tempVar2;
-
- if (moleculeNumber[temp + j] == i) {
- udotTimesmiMatrix.set(i, temp + j, tempVar2);
- } else {
- udotTimesmiMatrix.set(i, temp + j, 0.0);
- }
+ tempVar1 = ksiMatrix.unsafe_get(temp + j, 0);
+ tempVar2 = udotMatrix.unsafe_get(temp + j, 0);
+ uMatrix.unsafe_set(temp + j, 0, Math.log(tempVar1) - tempVar1 + 1.0);
+ gvector[temp + j][0] = mVector.unsafe_get(temp + j, 0) * tempVar2;
+
+ if (moleculeNumber[temp + j] == i) {
+ udotTimesmiMatrix.unsafe_set(i, temp + j, tempVar2);
+ } else {
+ udotTimesmiMatrix.unsafe_set(i, temp + j, 0.0);
+ }
}
temp += componentArray[i].getNumberOfAssociationSites();
}
if (type > 2) {
for (int p = 0; p < numberOfComponents; p++) {
- lngi[p] = ((ComponentSrkCPA) componentArray[p]).calc_lngi(this);
+ lngi[p] = ((ComponentSrkCPA) componentArray[p]).calc_lngi(this);
}
}
for (int i = 0; i < totalNumberOfAccociationSites; i++) {
for (int j = i; j < totalNumberOfAccociationSites; j++) {
- delta[i][j] = deltaNog[i][j] * gcpa;
- delta[j][i] = delta[i][j];
- if (type > 1) {
- deltadT[i][j] = cpamix.calcDeltadT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i], moleculeNumber[j],
- this, getTemperature(), getPressure(), numberOfComponents);
- deltadT[j][i] = deltadT[i][j];
-
- deltadTdT[i][j] = cpamix.calcDeltadTdT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
- moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
- deltadTdT[j][i] = deltadTdT[i][j];
- }
+ delta[i][j] = deltaNog[i][j] * gcpa;
+ delta[j][i] = delta[i][j];
+ if (type > 1) {
+ deltadT[i][j] = cpamix.calcDeltadT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
+ moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
+ deltadT[j][i] = deltadT[i][j];
+
+ deltadTdT[i][j] =
+ cpamix.calcDeltadTdT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
+ moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
+ deltadTdT[j][i] = deltadTdT[i][j];
+ }
}
}
double totalVolume = getTotalVolume();
double totalVolume2 = totalVolume * totalVolume;
double totalVolume3 = totalVolume2 * totalVolume;
- double gdv1 = getGcpav() - 1.0 / totalVolume;
+ double gdv1 = gcpav - 1.0 / totalVolume;
double gdv2 = gdv1 * gdv1;
double gdv3 = gdv2 * gdv1;
double Klk = 0.0;
@@ -309,84 +324,90 @@ public void initCPAMatrix(int type) {
double tempKsiRead = 0.0;
for (int i = 0; i < totalNumberOfAccociationSites; i++) {
for (int j = i; j < totalNumberOfAccociationSites; j++) {
- Klk = KlkMatrix.get(i, j);
- tempVar = Klk * gdv1;
- KlkVMatrix.set(i, j, tempVar);
- KlkVMatrix.set(j, i, tempVar);
-
- tempVar = Klk * gdv2 + Klk * (gcpavv + 1.0 / totalVolume2);
- KlkVVMatrix.set(i, j, tempVar);
- KlkVVMatrix.set(j, i, tempVar);
-
- tempVar = Klk * gdv3 + 3.0 * Klk * (gcpav - 1.0 / totalVolume) * (gcpavv + 1.0 / (totalVolume2))
- + Klk * (gcpavvv - 2.0 / (totalVolume3));
- KlkVVVMatrix.set(i, j, tempVar);
- KlkVVVMatrix.set(j, i, tempVar);
-
- if (type > 1) {
- tempVar = deltadT[i][j] / delta[i][j];
-
- if (Math.abs(tempVar) > 1e-50) {
- double tempVardT = deltadTdT[i][j] / delta[i][j]
- - (deltadT[i][j] * deltadT[i][j]) / (delta[i][j] * delta[i][j]);
-
- tempVar2 = Klk * tempVar;
- KlkTMatrix.set(i, j, tempVar2);
- KlkTMatrix.set(j, i, tempVar2);
-
- tempVar2 = Klk * tempVar * (gcpav - 1.0 / totalVolume);
- KlkTVMatrix.set(i, j, tempVar2);
- KlkTVMatrix.set(j, i, tempVar2);
-
- tempVar2 = Klk * (tempVar * tempVar + tempVardT);
- KlkTTMatrix.set(i, j, tempVar2);
- KlkTTMatrix.set(j, i, tempVar2);
- }
-
- if (type > 2) {
- for (int p = 0; p < numberOfComponents; p++) {
- double t1 = 0.0;
- double t2 = 0.0;
- if (moleculeNumber[i] == p) {
- t1 = 1.0 / mVector.get(i, 0);
- }
- if (moleculeNumber[j] == p) {
- t2 = 1.0 / mVector.get(j, 0);
- }
- Klkni[p][i][j] = Klk * (t1 + t2 + lngi[p]); // ((ComponentSrkCPA)
- // getComponent(p)).calc_lngi(this));
- Klkni[p][j][i] = Klkni[p][i][j];
- }
- }
- }
+ Klk = KlkMatrix.unsafe_get(i, j);
+ tempVar = Klk * gdv1;
+ KlkVMatrix.unsafe_set(i, j, tempVar);
+ KlkVMatrix.unsafe_set(j, i, tempVar);
+
+ tempVar = Klk * gdv2 + Klk * (gcpavv + 1.0 / totalVolume2);
+ KlkVVMatrix.unsafe_set(i, j, tempVar);
+ KlkVVMatrix.unsafe_set(j, i, tempVar);
+
+ tempVar =
+ Klk * gdv3 + 3.0 * Klk * (gcpav - 1.0 / totalVolume) * (gcpavv + 1.0 / totalVolume2)
+ + Klk * (gcpavvv - 2.0 / totalVolume3);
+ KlkVVVMatrix.unsafe_set(i, j, tempVar);
+ KlkVVVMatrix.unsafe_set(j, i, tempVar);
+
+ if (type > 1) {
+ tempVar = deltadT[i][j] / delta[i][j];
+
+ if (Math.abs(tempVar) > 1e-50) {
+ double tempVardT = deltadTdT[i][j] / delta[i][j]
+ - (deltadT[i][j] * deltadT[i][j]) / (delta[i][j] * delta[i][j]);
+
+ tempVar2 = Klk * tempVar;
+ KlkTMatrix.unsafe_set(i, j, tempVar2);
+ KlkTMatrix.unsafe_set(j, i, tempVar2);
+
+ tempVar2 = Klk * tempVar * (gcpav - 1.0 / totalVolume);
+ KlkTVMatrix.unsafe_set(i, j, tempVar2);
+ KlkTVMatrix.unsafe_set(j, i, tempVar2);
+
+ tempVar2 = Klk * (tempVar * tempVar + tempVardT);
+ KlkTTMatrix.unsafe_set(i, j, tempVar2);
+ KlkTTMatrix.unsafe_set(j, i, tempVar2);
+ }
+
+ if (type > 2) {
+ for (int p = 0; p < numberOfComponents; p++) {
+ double t1 = 0.0;
+ double t2 = 0.0;
+ if (moleculeNumber[i] == p) {
+ t1 = 1.0 / mVector.unsafe_get(i, 0);
+ }
+ if (moleculeNumber[j] == p) {
+ t2 = 1.0 / mVector.unsafe_get(j, 0);
+ }
+ Klkni[p][i][j] = Klk * (t1 + t2 + lngi[p]);
+ Klkni[p][j][i] = Klkni[p][i][j];
+ }
+ }
+ }
}
- tempKsiRead = ksiMatrix.get(i, 0);
- QMatksiksiksi.set(i, 0, 2.0 * mVector.get(i, 0) / (tempKsiRead * tempKsiRead * tempKsiRead));
+ tempKsiRead = ksiMatrix.unsafe_get(i, 0);
+ QMatksiksiksi.unsafe_set(i, 0,
+ 2.0 * mVector.unsafe_get(i, 0) / (tempKsiRead * tempKsiRead * tempKsiRead));
}
- SimpleMatrix ksiMatrixTranspose = ksiMatrix.transpose();
+ DenseMatrix ksiMatrixTranspose = transpose(ksiMatrix);
- // dXdV
- SimpleMatrix KlkVMatrixksi = KlkVMatrix.mult(ksiMatrix);
- SimpleMatrix XV = applyHessianInv(KlkVMatrixksi);
- SimpleMatrix XVtranspose = XV.transpose();
+ DenseMatrix klkVMatrixksi = multiply(KlkVMatrix, ksiMatrix);
+ DenseMatrix XV = applyHessianInv(klkVMatrixksi);
+ DenseMatrix XVtranspose = transpose(XV);
- FCPA = mVector.transpose().mult(uMatrix.minus(ksiMatrix.elementMult(udotMatrix).scale(0.5))).get(0, 0); // QCPA.get(0,
- // 0);
- // //*0.5;
+ DenseMatrix qCpa = multiply(transpose(mVector),
+ subtract(uMatrix, scale(elementMult(ksiMatrix, udotMatrix), 0.5)));
+ FCPA = qCpa.unsafe_get(0, 0);
- dFCPAdV = ksiMatrixTranspose.mult(KlkVMatrixksi).get(0, 0) * (-0.5);
- SimpleMatrix KlkVVMatrixTImesKsi = KlkVVMatrix.mult(ksiMatrix);
- dFCPAdVdV = ksiMatrixTranspose.mult(KlkVVMatrixTImesKsi).scale(-0.5).minus(KlkVMatrixksi.transpose().mult(XV))
- .get(0, 0);
+ DenseMatrix tempMatrix = scale(multiply(ksiMatrixTranspose, klkVMatrixksi), -0.5);
+ dFCPAdV = tempMatrix.unsafe_get(0, 0);
+ DenseMatrix klkVvMatrixTimesKsi = multiply(KlkVVMatrix, ksiMatrix);
+ DenseMatrix tempMatrixVV =
+ subtract(scale(multiply(ksiMatrixTranspose, klkVvMatrixTimesKsi), -0.5),
+ multiply(transpose(klkVMatrixksi), XV));
+ dFCPAdVdV = tempMatrixVV.unsafe_get(0, 0);
- SimpleMatrix QVVV = ksiMatrixTranspose.mult(KlkVVVMatrix.mult(ksiMatrix)); // .scale(-0.5);
- SimpleMatrix QVVksi = KlkVVMatrixTImesKsi.scale(-1.0);
- SimpleMatrix QksiVksi = KlkVMatrix.scale(-1.0);
+ DenseMatrix qVvv = multiply(ksiMatrixTranspose, multiply(KlkVVVMatrix, ksiMatrix));
+ DenseMatrix qVvksi = scale(klkVvMatrixTimesKsi, -1.0);
+ DenseMatrix qKsiVksi = scale(KlkVMatrix, -1.0);
- dFCPAdVdVdV = -0.5 * QVVV.get(0, 0) + QVVksi.transpose().mult(XV).get(0, 0) * 3.0
- + XVtranspose.mult(QksiVksi.mult(XV)).get(0, 0) * 3.0
- + XVtranspose.mult(QMatksiksiksi.mult(XVtranspose)).mult(XV).get(0, 0);
+ DenseMatrix mat1 = scale(multiply(transpose(qVvksi), XV), 3.0);
+ DenseMatrix mat2 = scale(multiply(XVtranspose, multiply(qKsiVksi, XV)), 3.0);
+ DenseMatrix mat4 = multiply(multiply(XVtranspose, multiply(QMatksiksiksi, XVtranspose)), XV);
+
+ dFCPAdVdVdV = -0.5 * qVvv.unsafe_get(0, 0) + mat1.unsafe_get(0, 0) + mat2.unsafe_get(0, 0)
+ + mat4.unsafe_get(0, 0);
if (type == 1) {
return;
@@ -395,36 +416,30 @@ public void initCPAMatrix(int type) {
temp = 0;
for (int p = 0; p < numberOfComponents; p++) {
for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedV(kk, XV.get(temp + kk, 0));
+ ((ComponentCPAInterface) getComponent(p)).setXsitedV(kk, XV.unsafe_get(temp + kk, 0));
}
temp += getComponent(p).getNumberOfAssociationSites();
}
- // KlkTMatrix = new SimpleMatrix(KlkdT);
- SimpleMatrix KlkTMatrixTImesKsi = KlkTMatrix.mult(ksiMatrix);
- // dQdT
- SimpleMatrix tempMatrix2 = ksiMatrixTranspose.mult(KlkTMatrixTImesKsi); // .scale(-0.5);
- dFCPAdT = tempMatrix2.get(0, 0) * (-0.5);
-
- // SimpleMatrix KlkTVMatrix = new SimpleMatrix(KlkdTdV);
- // SimpleMatrix tempMatrixTV =
- // ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5).minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- // dFCPAdTdV = tempMatrixTV.get(0, 0);
- // dXdT
- SimpleMatrix XT = applyHessianInv(KlkTMatrixTImesKsi);
- // dQdTdT
- SimpleMatrix tempMatrixTT = ksiMatrixTranspose.mult(KlkTTMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XT));
- dFCPAdTdT = tempMatrixTT.get(0, 0);
-
- SimpleMatrix tempMatrixTV = ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- dFCPAdTdV = tempMatrixTV.get(0, 0);
+ DenseMatrix klkTMatrixTimesKsi = multiply(KlkTMatrix, ksiMatrix);
+ DenseMatrix tempMatrix2 = scale(multiply(ksiMatrixTranspose, klkTMatrixTimesKsi), -0.5);
+ dFCPAdT = tempMatrix2.unsafe_get(0, 0);
+
+ DenseMatrix XT = applyHessianInv(klkTMatrixTimesKsi);
+ DenseMatrix tempMatrixTT =
+ subtract(scale(multiply(ksiMatrixTranspose, multiply(KlkTTMatrix, ksiMatrix)), -0.5),
+ multiply(transpose(klkTMatrixTimesKsi), XT));
+ dFCPAdTdT = tempMatrixTT.unsafe_get(0, 0);
+
+ DenseMatrix tempMatrixTV =
+ subtract(scale(multiply(ksiMatrixTranspose, multiply(KlkTVMatrix, ksiMatrix)), -0.5),
+ multiply(transpose(klkTMatrixTimesKsi), XV));
+ dFCPAdTdV = tempMatrixTV.unsafe_get(0, 0);
temp = 0;
for (int p = 0; p < numberOfComponents; p++) {
for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedT(kk, XT.get(temp + kk, 0));
+ ((ComponentCPAInterface) getComponent(p)).setXsitedT(kk, XT.unsafe_get(temp + kk, 0));
}
temp += getComponent(p).getNumberOfAssociationSites();
}
@@ -433,51 +448,20 @@ public void initCPAMatrix(int type) {
return;
}
- // int assSites = 0;
- // if(true) return;
for (int p = 0; p < numberOfComponents; p++) {
- SimpleMatrix KiMatrix = new SimpleMatrix(Klkni[p]);
- // KiMatrix.print(10,10);
- // Matrix dQdniMatrix =
- // (ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5)); // this
- // methods misses one part of ....
- // dQdniMatrix.print(10,10);
- // KiMatrix.print(10, 10);
- // miMatrix.getMatrix(assSites, assSites, 0, totalNumberOfAccociationSites -
- // 1).print(10, 10);
- // Matrix tempMatrix20 = miMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites -
- // 1).times(uMatrix).minus(ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5));
- //
- // ksiMatrix.transpose().times(KlkTMatrix.times(ksiMatrix)).times(-0.5);
- // System.out.println("dQdn ");
- // tempMatrix20.print(10, 10);
- SimpleMatrix tempMatrix4 = KiMatrix.mult(ksiMatrix);
- // udotTimesmiMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites - 1).print(10, 10);
- SimpleMatrix tempMatrix5 = udotTimesmiMatrix.extractVector(true, p).transpose().minus(tempMatrix4);
- // tempMki[0] = mki[p];
- // Matrix amatrix = new Matrix(croeneckerProduct(tempMki,
- // udotMatrix.getArray()));
- // System.out.println("aMatrix ");
- // amatrix.transpose().print(10, 10);
- // System.out.println("temp4 matrix");
- // tempMatrix4.print(10, 10);
- // Matrix tempMatrix5 = amatrix.minus(tempMatrix4);
- SimpleMatrix tempMatrix6 = applyHessianInv(tempMatrix5); // .scale(-1.0);
- // System.out.println("dXdni");
- // tempMatrix4.print(10, 10);
- // tempMatrix5.print(10, 10);
- // System.out.println("dXdn ");
- // tempMatrix6.print(10, 10);
+ DenseMatrix kiMatrix = new DenseMatrix(Klkni[p]);
+ DenseMatrix tempMatrix4 = multiply(kiMatrix, ksiMatrix);
+ DenseMatrix tempMatrix5 =
+ subtract(transpose(extractVector(udotTimesmiMatrix, true, p)), tempMatrix4);
+ DenseMatrix tempMatrix6 = applyHessianInv(tempMatrix5);
int temp2 = 0;
for (int compp = 0; compp < numberOfComponents; compp++) {
- for (int kk = 0; kk < getComponent(compp).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(compp)).setXsitedni(kk, p, -1.0 * tempMatrix6.get(temp2 + kk, 0));
- }
- temp2 += getComponent(compp).getNumberOfAssociationSites();
+ for (int kk = 0; kk < getComponent(compp).getNumberOfAssociationSites(); kk++) {
+ ((ComponentCPAInterface) getComponent(compp)).setXsitedni(kk, p,
+ -1.0 * tempMatrix6.unsafe_get(temp2 + kk, 0));
+ }
+ temp2 += getComponent(compp).getNumberOfAssociationSites();
}
- // assSites += getComponent(p).getNumberOfAssociationSites();
}
}
@@ -498,9 +482,9 @@ public void setMixingRule(MixingRuleTypeInterface mr) {
public void calcDelta() {
for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- deltaNog[i][j] = cpamix.calcDeltaNog(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i], moleculeNumber[j],
- this, getTemperature(), getPressure(), numberOfComponents);
- deltaNog[j][i] = deltaNog[i][j];
+ deltaNog[i][j] = cpamix.calcDeltaNog(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
+ moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
+ deltaNog[j][i] = deltaNog[i][j];
}
}
}
@@ -512,7 +496,7 @@ public void addComponent(String name, double moles, double molesInPhase, int com
componentArray[compNumber] = new ComponentSrkCPA(name, moles, molesInPhase, compNumber, this);
for (int i = 0; i < numberOfComponents; i++) {
if (componentArray[i] instanceof ComponentSrkCPA) {
- ((ComponentSrkCPA) componentArray[i]).resizeXsitedni(numberOfComponents);
+ ((ComponentSrkCPA) componentArray[i]).resizeXsitedni(numberOfComponents);
}
}
}
@@ -570,10 +554,10 @@ public double dFdTdT() {
*/
public double FCPA() {
/*
- * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0; for (int j = 0; j <
- * componentArray[i].getNumberOfAssociationSites(); j++) { double xai = ((ComponentSrkCPA)
- * componentArray[i]).getXsite()[j]; tot += (Math.log(xai) - 1.0 / 2.0 * xai + 1.0 / 2.0); } ans +=
- * componentArray[i].getNumberOfMolesInPhase() * tot; } return ans;
+ * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0;
+ * for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) { double xai =
+ * ((ComponentSrkCPA) componentArray[i]).getXsite()[j]; tot += (Math.log(xai) - 1.0 / 2.0 * xai
+ * + 1.0 / 2.0); } ans += componentArray[i].getNumberOfMolesInPhase() * tot; } return ans;
*/
return FCPA;
}
@@ -629,12 +613,12 @@ public double dFCPAdVdVdV() {
*/
public double dFCPAdT() {
/*
- * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0; for (int j = 0; j <
- * componentArray[i].getNumberOfAssociationSites(); j++) { double xai = ((ComponentSrkCPA)
- * componentArray[i]).getXsite()[j]; double xaidT = ((ComponentSrkCPA) componentArray[i]).getXsitedT()[j]; tot +=
- * 1.0 / xai * xaidT - 0.5 * xaidT; // - 1.0 / 2.0 * xai + 1.0 / 2.0); } ans +=
- * componentArray[i].getNumberOfMolesInPhase() * tot; } System.out.println("dFCPAdT1 " + ans + " dfcpa2 "
- * +dFCPAdT); return ans;
+ * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0;
+ * for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) { double xai =
+ * ((ComponentSrkCPA) componentArray[i]).getXsite()[j]; double xaidT = ((ComponentSrkCPA)
+ * componentArray[i]).getXsitedT()[j]; tot += 1.0 / xai * xaidT - 0.5 * xaidT; // - 1.0 / 2.0 *
+ * xai + 1.0 / 2.0); } ans += componentArray[i].getNumberOfMolesInPhase() * tot; }
+ * System.out.println("dFCPAdT1 " + ans + " dfcpa2 " +dFCPAdT); return ans;
*/
return dFCPAdT;
}
@@ -662,8 +646,8 @@ public double dFCPAdTdV() {
// getTotalVolume()) * (1.0 - getTotalVolume() * getGcpav()) * hcpatotdT));
return dFCPAdTdV;
/*
- * if (totalNumberOfAccociationSites > 0) { return 1.0 / (2.0 * getTotalVolume()) * (1.0 - getTotalVolume() *
- * getGcpav()) * hcpatotdT; } else { return 0; }
+ * if (totalNumberOfAccociationSites > 0) { return 1.0 / (2.0 * getTotalVolume()) * (1.0 -
+ * getTotalVolume() * getGcpav()) * hcpatotdT; } else { return 0; }
*/
}
@@ -684,13 +668,13 @@ protected double[] calcdFdNtemp() {
// temp = ((ComponentSrkCPA) getComponent(k)).calc_lngi(this);
// temp2 = ((ComponentSrkCPA) getComponent(k)).calc_lngidV(this);
for (int i = 0; i < getComponent(k).getNumberOfAssociationSites(); i++) {
- tot2 -= 1.0 * ((ComponentSrkCPA) getComponent(k)).getXsitedV()[i];
- tot3 += (1.0 - ((ComponentSrkCPA) getComponent(k)).getXsite()[i]) * 1.0;
+ tot2 -= 1.0 * ((ComponentSrkCPA) getComponent(k)).getXsitedV()[i];
+ tot3 += (1.0 - ((ComponentSrkCPA) getComponent(k)).getXsite()[i]) * 1.0;
}
tot1 += 1.0 / 2.0 * tot2 * getComponent(k).getNumberOfMolesInPhase();
tot4 += 0.5 * getComponent(k).getNumberOfMolesInPhase() * tot3;
}
- return new double[] { -tot1, -tot4 };
+ return new double[] {-tot1, -tot4};
}
/**
@@ -705,21 +689,24 @@ public void calcXsitedV() {
}
/**
- * Apply the inverse of the current Hessian to a right-hand side. If an LU factorization is available (cached by
- * {@link #solveX}), it is used to back-solve; otherwise, the explicit {@code hessianInvers} matrix is used (e.g. when
- * called from legacy code paths).
+ * Apply the inverse of the current Hessian to a right-hand side. If an LU factorization is
+ * available (cached by {@link #solveX}), it is used to back-solve; otherwise, the explicit
+ * {@code hessianInvers} matrix is used (e.g. when called from legacy code paths).
*
* @param rhs right-hand side
- * @return {@code H^{-1} * rhs} as a SimpleMatrix
+ * @return {@code H^{-1} * rhs} as a dense matrix
*/
- private SimpleMatrix applyHessianInv(SimpleMatrix rhs) {
+ private DenseMatrix applyHessianInv(DenseMatrix rhs) {
+ if (hessianInvers != null) {
+ return multiply(hessianInvers, rhs);
+ }
if (hessianLU != null && hessianLUSize == totalNumberOfAccociationSites) {
- DMatrixRMaj rhsMat = rhs.getDDRM();
- DMatrixRMaj out = new DMatrixRMaj(rhsMat.numRows, rhsMat.numCols);
- hessianLU.solve(rhsMat, out);
- return SimpleMatrix.wrap(out);
+ DenseMatrix out = new DenseMatrix(rhs.numRows, rhs.numCols);
+ LinearAlgebraOps.solveLu(hessianLU, rhs.numRows, rhs.numCols, (i, j) -> rhs.unsafe_get(i, j),
+ (i, j, value) -> out.unsafe_set(i, j, value));
+ return out;
}
- return hessianInvers.mult(rhs);
+ throw new IllegalStateException("Hessian factorization has not been initialized");
}
/**
@@ -736,8 +723,8 @@ public boolean solveX() {
boolean solvedX = solveX2(15);
- DMatrixRMaj mVectorMat = mVector.getMatrix();
- DMatrixRMaj ksiMatrixMat = ksiMatrix.getMatrix();
+ DenseMatrix mVectorMat = mVector;
+ DenseMatrix ksiMatrixMat = ksiMatrix;
// ksiMatrix.print();
// second order method not working correctly and not used t the moment b ecause of numerical
@@ -746,12 +733,12 @@ public boolean solveX() {
int iter = 0;
for (int i = 0; i < numberOfComponents; i++) {
for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- mVectorMat.unsafe_set(temp + j, 0, componentArray[i].getNumberOfMolesInPhase());
+ mVectorMat.unsafe_set(temp + j, 0, componentArray[i].getNumberOfMolesInPhase());
}
temp += componentArray[i].getNumberOfAssociationSites();
}
- DMatrixRMaj mat1 = KlkMatrix.getMatrix();
+ DenseMatrix mat1 = KlkMatrix;
double Klk = 0.0;
double totvolume = getTotalVolume();
double tempVari;
@@ -759,14 +746,13 @@ public boolean solveX() {
for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
tempVari = mVectorMat.unsafe_get(i, 0);
for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- tempVarj = mVectorMat.unsafe_get(j, 0);
- Klk = tempVari * tempVarj / totvolume * delta[i][j];
- mat1.unsafe_set(i, j, Klk);
- mat1.unsafe_set(j, i, Klk);
+ tempVarj = mVectorMat.unsafe_get(j, 0);
+ Klk = tempVari * tempVarj / totvolume * delta[i][j];
+ mat1.unsafe_set(i, j, Klk);
+ mat1.unsafe_set(j, i, Klk);
}
}
boolean solved = true;
- // SimpleMatrix corrMatrix = null;
do {
solved = true;
iter++;
@@ -776,87 +762,83 @@ public boolean solveX() {
double temp1;
double temp2;
for (int i = 0; i < numberOfComponents; i++) {
- temp1 = componentArray[i].getNumberOfMolesInPhase();
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- ksi = ((ComponentSrkCPA) componentArray[i]).getXsite()[j];
- ksiMatrixMat.unsafe_set(temp + j, 0, ksi);
- // ksiMatrix.getMatrix().unsafe_set(temp + j, 0,
- // ksiMatrix.getMatrix().unsafe_get(temp + j, 0));
- tempVari = 1.0 / ksi - 1.0;
- udotMatrix.set(temp + j, 0, tempVari);
- udotTimesmMatrix.set(temp + j, 0, temp1 * tempVari);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
+ temp1 = componentArray[i].getNumberOfMolesInPhase();
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ ksi = ((ComponentSrkCPA) componentArray[i]).getXsite()[j];
+ ksiMatrixMat.unsafe_set(temp + j, 0, ksi);
+ // ksiMatrix.getMatrix().unsafe_set(temp + j, 0,
+ // ksiMatrix.getMatrix().unsafe_get(temp + j, 0));
+ tempVari = 1.0 / ksi - 1.0;
+ udotMatrix.unsafe_set(temp + j, 0, tempVari);
+ udotTimesmMatrix.unsafe_set(temp + j, 0, temp1 * tempVari);
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
}
int krondelt;
for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- temp1 = mVectorMat.unsafe_get(i, 0);
- temp2 = ksiMatrix.get(i, 0);
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- krondelt = 0;
- if (i == j) {
- krondelt = 1;
- }
- tempVari = -temp1 / (temp2 * temp2) * krondelt - mat1.unsafe_get(i, j);
- hessianMatrix.set(i, j, tempVari);
- hessianMatrix.set(j, i, tempVari);
- }
+ temp1 = mVectorMat.unsafe_get(i, 0);
+ temp2 = ksiMatrix.unsafe_get(i, 0);
+ for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
+ krondelt = 0;
+ if (i == j) {
+ krondelt = 1;
+ }
+ tempVari = -temp1 / (temp2 * temp2) * krondelt - mat1.unsafe_get(i, j);
+ hessianMatrix.unsafe_set(i, j, tempVari);
+ hessianMatrix.unsafe_set(j, i, tempVari);
+ }
}
- // ksiMatrix = new SimpleMatrix(ksi);
- // SimpleMatrix hessianMatrix = new SimpleMatrix(hessian);
int n = totalNumberOfAccociationSites;
if (hessianLU == null || hessianLUSize != n) {
- hessianLU = LinearSolverFactory_DDRM.lu(n);
- hessianLUinput = new DMatrixRMaj(n, n);
- hessianLUSize = n;
+ hessianLU = LU.PRIMITIVE.make(n, n);
+ hessianLUinput = new DenseMatrix(n, n);
+ hessianLUSize = n;
}
// Defensive copy: EJML's LU solver may decompose in place, which would corrupt
// hessianMatrix for any later reader. We keep hessianMatrix intact by feeding the
// solver a reusable scratch copy.
- System.arraycopy(hessianMatrix.getDDRM().getData(), 0, hessianLUinput.getData(), 0, n * n);
- if (!hessianLU.setA(hessianLUinput)) {
- return false;
+ System.arraycopy(hessianMatrix.getData(), 0, hessianLUinput.getData(), 0, n * n);
+ if (!LinearAlgebraOps.decomposeLu(hessianLU, n, (i, j) -> hessianLUinput.unsafe_get(i, j))) {
+ return false;
}
hessianInvers = null;
if (solvedX) {
- // System.out.println("solvedX ");
- return true;
+ // System.out.println("solvedX ");
+ return true;
}
- DMatrixRMaj mat2 = ksiMatrix.getMatrix();
- CommonOps_DDRM.mult(mat1, mat2, corr2Matrix);
- CommonOps_DDRM.subtract(udotTimesmMatrix.getDDRM(), corr2Matrix, corr3Matrix);
- hessianLU.solve(corr3Matrix, corr4Matrix);
- // SimpleMatrix gMatrix = udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix));
+ DenseMatrix mat2 = ksiMatrix;
+ LinearAlgebraOps.mult(mat1, mat2, corr2Matrix);
+ LinearAlgebraOps.subtract(udotTimesmMatrix, corr2Matrix, corr3Matrix);
+ LinearAlgebraOps.solveLu(hessianLU, corr3Matrix.numRows, corr3Matrix.numCols,
+ (i, j) -> corr3Matrix.unsafe_get(i, j),
+ (i, j, value) -> corr4Matrix.unsafe_set(i, j, value));
// corrMatrix =
// hessianInvers.mult(udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix)));
// //.scale(-1.0);
temp = 0;
- // System.out.println("print SimpleMatrix ...");
// corrMatrix.print(10, 10);
- // SimpleMatrix simp = new SimpleMatrix(corr4Matrix);
// System.out.println("print CommonOps ...");
- // simp.print(10,10);
double newX;
for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- newX = ksiMatrix.get(temp + j, 0) - corr4Matrix.unsafe_get((temp + j), 0);
- if (newX < 0) {
- newX = 1e-10;
- solved = false;
- }
- ((ComponentCPAInterface) componentArray[i]).setXsite(j, newX);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ newX = ksiMatrix.unsafe_get(temp + j, 0) - corr4Matrix.unsafe_get((temp + j), 0);
+ if (newX < 0) {
+ newX = 1e-10;
+ solved = false;
+ }
+ ((ComponentCPAInterface) componentArray[i]).setXsite(j, newX);
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
}
// System.out.println("corrmatrix error " );
- // System.out.println("error " + NormOps_DDRM.normF(corr4Matrix));
- } while ((NormOps_DDRM.normF(corr4Matrix) > 1e-12 || !solved) && iter < 100);
+ // System.out.println("error " + LinearAlgebraOps.normF(corr4Matrix));
+ } while ((LinearAlgebraOps.normF(corr4Matrix) > 1e-12 || !solved) && iter < 100);
// System.out.println("iter " + iter + " error " +
- // NormOps_DDRM.normF(corr4Matrix)); // corrMatrix.print(10, 10);
+ // LinearAlgebraOps.normF(corr4Matrix)); // corrMatrix.print(10, 10);
// ksiMatrix.print(10, 10);
return true;
}
@@ -898,15 +880,15 @@ public boolean solveX2(int maxIter) {
iter++;
err = 0.0;
for (int i = 0; i < n; i++) {
- final double old = xArr[i];
- final double[] deltaRow = delta[i];
- double sum = 0.0;
- for (int j = 0; j < n; j++) {
- sum += nMoles[j] * deltaRow[j] * xArr[j];
- }
- double neeval = 1.0 / (1.0 + invV * sum);
- xArr[i] = neeval;
- err += Math.abs((old - neeval) / neeval);
+ final double old = xArr[i];
+ final double[] deltaRow = delta[i];
+ double sum = 0.0;
+ for (int j = 0; j < n; j++) {
+ sum += nMoles[j] * deltaRow[j] * xArr[j];
+ }
+ double neeval = 1.0 / (1.0 + invV * sum);
+ xArr[i] = neeval;
+ err += Math.abs((old - neeval) / neeval);
}
} while (Math.abs(err) > 1e-12 && iter < maxIter);
@@ -995,28 +977,29 @@ public double calcRootVolFinder(PhaseType pt) {
int solveXAttempts = 0;
while (!solveX() && solveXAttempts < 50) {
- solveXAttempts++;
+ solveXAttempts++;
}
if (solveXAttempts >= 50) {
- // solveX failed to converge, skip this BonV value
- oldh = h;
- continue;
+ // solveX failed to converge, skip this BonV value
+ oldh = h;
+ continue;
}
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
if (Math.signum(h) * Math.signum(oldh) < 0 && i > 2) {
- if (solvedBonVlow < 1e-3) {
- solvedBonVlow = (BonV + BonVold) / 2.0;
- if (pt == PhaseType.GAS) {
- break;
- }
- } else {
- solvedBonVHigh = (BonV + BonVold) / 2.0;
- if (pt == PhaseType.LIQUID) {
- break;
- }
- }
+ if (solvedBonVlow < 1e-3) {
+ solvedBonVlow = (BonV + BonVold) / 2.0;
+ if (pt == PhaseType.GAS) {
+ break;
+ }
+ } else {
+ solvedBonVHigh = (BonV + BonVold) / 2.0;
+ if (pt == PhaseType.LIQUID) {
+ break;
+ }
+ }
}
solvedBonVHigh = (BonV + BonVold) / 2.0;
oldh = h;
@@ -1041,9 +1024,10 @@ public double calcRootVolFinder(PhaseType pt) {
/** {@inheritDoc} */
@Override
public double molarVolume(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
+ throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
double BonV = pt == PhaseType.GAS ? pressure * getB() / (numberOfMolesInPhase * temperature * R)
- : 2.0 / (2.0 + temperature / getPseudoCriticalTemperature());
+ : 2.0 / (2.0 + temperature / getPseudoCriticalTemperature());
BonV = Math.max(1.0e-8, Math.min(1.0 - 1.0e-8, BonV));
double BonVold;
double BonV2;
@@ -1064,8 +1048,8 @@ public double molarVolume(double pressure, double temperature, double A, double
iterations++;
gcpa = calc_g();
if (gcpa < 0) {
- setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
- gcpa = calc_g();
+ setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
+ gcpa = calc_g();
}
// lngcpa =
@@ -1075,74 +1059,78 @@ public double molarVolume(double pressure, double temperature, double A, double
gcpavvv = calc_lngVVV();
if (totalNumberOfAccociationSites > 0) {
- solveX();
+ solveX();
}
initCPAMatrix(1);
BonV2 = BonV * BonV;
BonVold = BonV;
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
dh = 1.0 + Btemp / (BonV2) * (Btemp / numberOfMolesInPhase * dFdVdV());
dhh = -2.0 * Btemp / (BonV2 * BonV) * (Btemp / numberOfMolesInPhase * dFdVdV())
- - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
+ - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
d1 = -h / dh;
d2 = -dh / dhh;
// System.out.println("h " + h + " iter " + iterations + " " + d1 + " d2 " + d2
// + " d1 / d2 " + (d1 / d2));
if (Double.isNaN(d1) || Double.isNaN(d2)) {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
} else if (Math.abs(d1 / d2) <= 1.0) {
- BonV += d1 * (1.0 + 0.5 * d1 / d2);
+ BonV += d1 * (1.0 + 0.5 * d1 / d2);
} else if (d1 / d2 < -1) {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
} else if (d1 > d2) {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- // BonV += d2;
- // double hnew = h + d2 * dh;
- // if (Math.abs(hnew) > Math.abs(h)) {
- // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
- // temperature * R);
- // }
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ // BonV += d2;
+ // double hnew = h + d2 * dh;
+ // if (Math.abs(hnew) > Math.abs(h)) {
+ // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
+ // temperature * R);
+ // }
} else {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
}
if (Math.abs((BonV - BonVold) / BonV) > 0.1) {
- BonV = BonVold + 0.1 * (BonV - BonVold);
+ BonV = BonVold + 0.1 * (BonV - BonVold);
}
if (Double.isNaN(BonV)) {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
}
if (BonV < 0) {
- if (iterations < 10) {
- // System.out.println(iterations + " BonV " + BonV);
- BonV = (BonVold + BonV) / 2.0;
- } else {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- }
+ if (iterations < 10) {
+ // System.out.println(iterations + " BonV " + BonV);
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ }
}
if (BonV >= 1.0) {
- if (iterations < 10) {
- BonV = (BonVold + BonV) / 2.0;
- } else {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- }
+ if (iterations < 10) {
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ }
}
/*
- * if (BonV > 0.9999) { if (iterations < 10) { BonV = (BonVold + BonV) / 2.0; } else { // BonV =
- * calcRootVolFinder(pt); // BonV = molarVolumeChangePhase(pressure, temperature, A, B, pt); // BonV = 0.9999; //
- * BonV = pt == 1 ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature()) : pressure * getB() /
- * (numberOfMolesInPhase * temperature * R); } } else if (BonV < 0) { if (iterations < 10) { BonV =
- * Math.abs(BonVold + BonV) / 2.0; } else { // BonV = calcRootVolFinder(pt); // return
- * molarVolumeChangePhase(pressure, temperature, A, B, pt); // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- * getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase * temperature * R); } }
+ * if (BonV > 0.9999) { if (iterations < 10) { BonV = (BonVold + BonV) / 2.0; } else { // BonV
+ * = calcRootVolFinder(pt); // BonV = molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ * // BonV = 0.9999; // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ * getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase * temperature *
+ * R); } } else if (BonV < 0) { if (iterations < 10) { BonV = Math.abs(BonVold + BonV) / 2.0;
+ * } else { // BonV = calcRootVolFinder(pt); // return molarVolumeChangePhase(pressure,
+ * temperature, A, B, pt); // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ * getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase * temperature *
+ * R); } }
*/
setMolarVolume(1.0 / BonV * Btemp / numberOfMolesInPhase);
Z = pressure * getMolarVolume() / (R * temperature);
- } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12) && iterations < maxIterations);
+ } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12)
+ && iterations < maxIterations);
// System.out.println("h failed " + h + " Z" + Z + " iterations " + iterations +
// " BonV " + BonV);
@@ -1187,10 +1175,11 @@ public double molarVolume(double pressure, double temperature, double A, double
* @throws neqsim.util.exception.IsNaNException if any.
* @throws neqsim.util.exception.TooManyIterationsException if any.
*/
- public double molarVolumeChangePhase(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
+ public double molarVolumeChangePhase(double pressure, double temperature, double A, double B,
+ PhaseType pt) throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
double BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
// double BonV = calcRootVolFinder(pt);
// double BonVInit = BonV;
if (BonV < 0) {
@@ -1221,8 +1210,8 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
iterations++;
gcpa = calc_g();
if (gcpa < 0) {
- setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
- gcpa = calc_g();
+ setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
+ gcpa = calc_g();
}
// lngcpa =
@@ -1236,57 +1225,58 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
initCPAMatrix(1);
double BonV2 = BonV * BonV;
BonVold = BonV;
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
dh = 1.0 + Btemp / (BonV2) * (Btemp / numberOfMolesInPhase * dFdVdV());
dhh = -2.0 * Btemp / (BonV2 * BonV) * (Btemp / numberOfMolesInPhase * dFdVdV())
- - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
+ - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
d1 = -h / dh;
d2 = -dh / dhh;
// System.out.println("d1" + d1 + " d2 " + d2 + " d1 / d2 " + (d1 / d2));
if (Double.isNaN(d1) || Double.isNaN(d2)) {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
} else if (Math.abs(d1 / d2) <= 1.0) {
- BonV += d1 * (1.0 + 0.5 * d1 / d2);
+ BonV += d1 * (1.0 + 0.5 * d1 / d2);
} else if (d1 / d2 < -1) {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
} else if (d1 > d2) {
- BonV += d2;
- double hnew = h + d2 * dh;
- if (Math.abs(hnew) > Math.abs(h)) {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ BonV += d2;
+ double hnew = h + d2 * dh;
+ if (Math.abs(hnew) > Math.abs(h)) {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
} else {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
}
if (Math.abs((BonV - BonVold) / BonVold) > 0.1) {
- BonV = BonVold + 0.1 * (BonV - BonVold);
+ BonV = BonVold + 0.1 * (BonV - BonVold);
}
if (Double.isNaN(BonV)) {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
}
if (BonV > 1.1) {
- if (iterations < 3) {
- BonV = (BonVold + BonV) / 2.0;
- } else {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ if (iterations < 3) {
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
}
if (BonV < 0) {
- if (iterations < 3) {
- BonV = Math.abs(BonVold + BonV) / 2.0;
- } else {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ if (iterations < 3) {
+ BonV = Math.abs(BonVold + BonV) / 2.0;
+ } else {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
}
setMolarVolume(1.0 / BonV * Btemp / numberOfMolesInPhase);
@@ -1297,8 +1287,8 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
} while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10) && iterations < 100);
/*
- * if (Math.abs(h) > 1e-8) { if (pt == 0) { molarVolume(pressure, temperature, A, B, 1); } else {
- * molarVolume(pressure, temperature, A, B, 0); } return getMolarVolume(); }
+ * if (Math.abs(h) > 1e-8) { if (pt == 0) { molarVolume(pressure, temperature, A, B, 1); } else
+ * { molarVolume(pressure, temperature, A, B, 0); } return getMolarVolume(); }
*/
// System.out.println("Z" + Z + " iterations " + iterations + " BonV " + BonV);
// System.out.println("pressure " + Z*R*temperature/getMolarVolume());
@@ -1312,7 +1302,8 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
// System.out.println("BonV: " + BonV + " "+" itert: " + iterations +" " +h + " " +dh + " B
// " + Btemp + " gv" + gV() + " fv " + fv() + " fvv" + fVV());
if (Double.isNaN(getMolarVolume())) {
- throw new neqsim.util.exception.IsNaNException(this, "molarVolumeChangePhase", "Molar volume");
+ throw new neqsim.util.exception.IsNaNException(this, "molarVolumeChangePhase",
+ "Molar volume");
// System.out.println("BonV: " + BonV + " "+" itert: " + iterations +" " +h + "
// " +dh + " B " + Btemp + " D " + Dtemp + " gv" + gV() + " fv " + fv() + " fvv"
// + fVV());
@@ -1324,7 +1315,8 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
/** {@inheritDoc} */
@Override
public double molarVolume2(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
+ throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
Z = pt == PhaseType.LIQUID ? 1.0 : 1.0e-5;
setMolarVolume(Z * R * temperature / pressure);
// super.molarVolume(pressure,temperature, A, B, phase);
@@ -1347,7 +1339,8 @@ public double molarVolume2(double pressure, double temperature, double A, double
// System.out.println("pressure " + -R * temperature * dFdV + " " + R *
// temperature / getMolarVolume());
// -pressure;
- dErrdV = -R * temperature * dFdVdV - R * temperature * numberOfMolesInPhase / Math.pow(getVolume(), 2.0);
+ dErrdV = -R * temperature * dFdVdV
+ - R * temperature * numberOfMolesInPhase / Math.pow(getVolume(), 2.0);
// System.out.println("errdV " + dErrdV);
// System.out.println("err " + err);
@@ -1357,8 +1350,8 @@ public double molarVolume2(double pressure, double temperature, double A, double
Z = pressure * getMolarVolume() / (R * temperature);
if (Z < 0) {
- Z = 1e-6;
- setMolarVolume(Z * R * temperature / pressure);
+ Z = 1e-6;
+ setMolarVolume(Z * R * temperature / pressure);
}
// System.out.println("Z " + Z);
} while (Math.abs(err) > 1.0e-8 || iterations < 100);
@@ -1431,11 +1424,11 @@ public double[][] croeneckerProduct(double[][] a, double[][] b) {
double[][] result = new double[aLength * bLength][(aCols) * (bCols)];
for (int z = 0; z < aLength; z++) {
for (int i = 0; i < aCols; i++) {
- for (int j = 0; j < bLength; j++) {
- for (int k = 0; k < bCols; k++) {
- result[j + (z * bLength)][k + (i * bCols)] = a[z][i] * b[j][k];
- }
- }
+ for (int j = 0; j < bLength; j++) {
+ for (int k = 0; k < bCols; k++) {
+ result[j + (z * bLength)][k + (i * bCols)] = a[z][i] * b[j][k];
+ }
+ }
}
}
return result;
@@ -1464,65 +1457,78 @@ public void setTotalNumberOfAccociationSites(int totalNumberOfAccociationSites)
* @param pt the PhaseType of the phase
* @param beta a double
*/
- public void initOld2(double totalNumberOfMoles, int numberOfComponents, int type, PhaseType pt, double beta) {
+ public void initOld2(double totalNumberOfMoles, int numberOfComponents, int type, PhaseType pt,
+ double beta) {
// type = 0 start init, type = 1 gi nye betingelser
if (type == 0) {
setTotalNumberOfAccociationSites(0);
selfAccociationScheme = new int[numberOfComponents][0][0];
crossAccociationScheme = new int[numberOfComponents][numberOfComponents][0][0];
for (int i = 0; i < numberOfComponents; i++) {
- if (componentArray[i].getNumberOfmoles() < 1e-50) {
- componentArray[i].setNumberOfAssociationSites(0);
- } else {
- componentArray[i].setNumberOfAssociationSites(componentArray[i].getOrginalNumberOfAssociationSites());
- setTotalNumberOfAccociationSites(
- getTotalNumberOfAccociationSites() + componentArray[i].getNumberOfAssociationSites());
- selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
- for (int j = 0; j < numberOfComponents; j++) {
- crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
- }
- }
+ if (componentArray[i].getNumberOfmoles() < 1e-50) {
+ componentArray[i].setNumberOfAssociationSites(0);
+ } else {
+ componentArray[i]
+ .setNumberOfAssociationSites(componentArray[i].getOrginalNumberOfAssociationSites());
+ setTotalNumberOfAccociationSites(
+ getTotalNumberOfAccociationSites() + componentArray[i].getNumberOfAssociationSites());
+ selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
+ for (int j = 0; j < numberOfComponents; j++) {
+ crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
+ }
+ }
}
// had to remove if below - dont understand why.. Even
// if (getTotalNumberOfAccociationSites() != oldTotalNumberOfAccociationSites) {
- mVector = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- KlkMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- hessianMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- corr2Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr3Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr4Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- Klkni = new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- ksiMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- uMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- udotMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
+ mVector = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ KlkMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ hessianMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ corr2Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr3Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr4Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ Klkni =
+ new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ ksiMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ uMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
moleculeNumber = new int[getTotalNumberOfAccociationSites()];
assSiteNumber = new int[getTotalNumberOfAccociationSites()];
gvector = new double[getTotalNumberOfAccociationSites()][1];
- udotTimesmMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotTimesmMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
delta = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
deltaNog = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
deltadT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltadTdT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- QMatksiksiksi = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
+ deltadTdT =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ QMatksiksiksi = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
// }
- udotTimesmiMatrix = new SimpleMatrix(getNumberOfComponents(), getTotalNumberOfAccociationSites());
+ udotTimesmiMatrix =
+ new DenseMatrix(getNumberOfComponents(), getTotalNumberOfAccociationSites());
oldTotalNumberOfAccociationSites = getTotalNumberOfAccociationSites();
int temp = 0;
for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- moleculeNumber[temp + j] = i;
- assSiteNumber[temp + j] = j;
- }
- temp += componentArray[i].getNumberOfAssociationSites();
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ moleculeNumber[temp + j] = i;
+ assSiteNumber[temp + j] = j;
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
}
}
if (cpamix == null) {
@@ -1561,231 +1567,7 @@ public void initOld2(double totalNumberOfMoles, int numberOfComponents, int type
* @param type a int
*/
public void initCPAMatrixOld(int type) {
- if (getTotalNumberOfAccociationSites() == 0) {
- FCPA = 0.0;
- dFCPAdTdV = 0.0;
- dFCPAdTdT = 0.0;
- dFCPAdT = 0;
- dFCPAdV = 0;
- dFCPAdVdV = 0.0;
- dFCPAdVdVdV = 0.0;
-
- return;
- }
-
- int temp = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- uMatrix.set(temp + j, 0, Math.log(ksiMatrix.get(temp + j, 0)) - ksiMatrix.get(temp + j, 0) + 1.0);
- gvector[temp + j][0] = mVector.get(temp + j, 0) * udotMatrix.get(temp + j, 0);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
- for (int i = 0; i < getNumberOfComponents(); i++) {
- for (int j = 0; j < getTotalNumberOfAccociationSites(); j++) {
- if (moleculeNumber[j] == i) {
- udotTimesmiMatrix.set(i, j, udotMatrix.get(j, 0));
- } else {
- udotTimesmiMatrix.set(i, j, 0.0);
- }
- }
- }
-
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- delta[i][j] = deltaNog[i][j] * getGcpa();
- delta[j][i] = delta[i][j];
- if (type > 1) {
- deltadT[i][j] = cpamix.calcDeltadT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i], moleculeNumber[j],
- this, getTemperature(), getPressure(), numberOfComponents);
- deltadT[j][i] = deltadT[i][j];
-
- deltadTdT[i][j] = cpamix.calcDeltadTdT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
- moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
- deltadTdT[j][i] = deltadTdT[i][j];
- }
- }
- }
-
- double totalVolume = getTotalVolume();
- double totalVolume2 = totalVolume * totalVolume;
- double totalVolume3 = totalVolume2 * totalVolume;
- double gdv1 = getGcpav() - 1.0 / totalVolume;
- double gdv2 = gdv1 * gdv1;
- double gdv3 = gdv2 * gdv1;
- // double Klk = 0.0;
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- KlkVMatrix.set(i, j, KlkMatrix.get(i, j) * gdv1);
- KlkVMatrix.set(j, i, KlkVMatrix.get(i, j));
-
- KlkVVMatrix.set(i, j,
- KlkMatrix.get(i, j) * gdv2 + KlkMatrix.get(i, j) * (gcpavv + 1.0 / totalVolume / totalVolume));
- KlkVVMatrix.set(j, i, KlkVVMatrix.get(i, j));
-
- KlkVVVMatrix.set(i, j,
- KlkMatrix.get(i, j) * gdv3
- + 3.0 * KlkMatrix.get(i, j) * (getGcpav() - 1.0 / totalVolume) * (gcpavv + 1.0 / (totalVolume2))
- + KlkMatrix.get(i, j) * (gcpavvv - 2.0 / (totalVolume3)));
- KlkVVVMatrix.set(j, i, KlkVVVMatrix.get(i, j));
-
- if (type > 1) {
- double tempVar = deltadT[i][j] / delta[i][j];
- double tempVardT = deltadTdT[i][j] / delta[i][j]
- - (deltadT[i][j] * deltadT[i][j]) / (delta[i][j] * delta[i][j]);
-
- if (!Double.isNaN(tempVar)) {
- // KlkdT[i][j] = KlkMatrix.getMatrix().unsafe_get(i, j) * tempVar;
- // KlkdT[j][i] = KlkdT[i][j];
-
- KlkTMatrix.set(i, j, KlkMatrix.get(i, j) * tempVar);
- KlkTMatrix.set(j, i, KlkTMatrix.get(i, j));
-
- KlkTVMatrix.set(i, j, KlkMatrix.get(i, j) * tempVar * (gcpav - 1.0 / totalVolume));
- KlkTVMatrix.set(j, i, KlkTVMatrix.get(i, j));
-
- KlkTTMatrix.set(i, j, KlkMatrix.get(i, j) * (tempVar * tempVar + tempVardT));
- KlkTTMatrix.set(j, i, KlkTTMatrix.get(i, j));
- }
-
- if (type > 2) {
- for (int p = 0; p < numberOfComponents; p++) {
- double t1 = 0.0;
- double t2 = 0.0;
- if (moleculeNumber[i] == p) {
- t1 = 1.0 / mVector.get(i, 0);
- }
- if (moleculeNumber[j] == p) {
- t2 = 1.0 / mVector.get(j, 0);
- }
- Klkni[p][i][j] = KlkMatrix.get(i, j) * (t1 + t2 + ((ComponentSrkCPA) getComponent(p)).calc_lngi(this));
- Klkni[p][j][i] = Klkni[p][i][j];
- }
- }
- }
- }
- QMatksiksiksi.set(i, 0,
- 2.0 * mVector.get(i, 0) / (ksiMatrix.get(i, 0) * ksiMatrix.get(i, 0) * ksiMatrix.get(i, 0)));
- }
-
- SimpleMatrix ksiMatrixTranspose = ksiMatrix.transpose();
-
- // dXdV
- SimpleMatrix KlkVMatrixksi = KlkVMatrix.mult(ksiMatrix);
- SimpleMatrix XV = hessianInvers.mult(KlkVMatrixksi);
- SimpleMatrix XVtranspose = XV.transpose();
-
- SimpleMatrix QCPA = mVector.transpose().mult(uMatrix.minus(ksiMatrix.elementMult(udotMatrix).scale(0.5)));
- FCPA = QCPA.get(0, 0);
-
- SimpleMatrix tempMatrix = ksiMatrixTranspose.mult(KlkVMatrixksi).scale(-0.5);
- dFCPAdV = tempMatrix.get(0, 0);
- SimpleMatrix KlkVVMatrixTImesKsi = KlkVVMatrix.mult(ksiMatrix);
- SimpleMatrix tempMatrixVV = ksiMatrixTranspose.mult(KlkVVMatrixTImesKsi).scale(-0.5)
- .minus(KlkVMatrixksi.transpose().mult(XV));
- dFCPAdVdV = tempMatrixVV.get(0, 0);
-
- SimpleMatrix QVVV = ksiMatrixTranspose.mult(KlkVVVMatrix.mult(ksiMatrix)).scale(-0.5);
- SimpleMatrix QVVksi = KlkVVMatrixTImesKsi.scale(-1.0);
- SimpleMatrix QksiVksi = KlkVMatrix.scale(-1.0);
-
- SimpleMatrix mat1 = QVVksi.transpose().mult(XV).scale(3.0);
- SimpleMatrix mat2 = XVtranspose.mult(QksiVksi.mult(XV)).scale(3.0);
- SimpleMatrix mat4 = XVtranspose.mult(QMatksiksiksi.mult(XVtranspose)).mult(XV);
-
- SimpleMatrix dFCPAdVdVdVMatrix = QVVV.plus(mat1).plus(mat2).plus(mat2).plus(mat4);
- dFCPAdVdVdV = dFCPAdVdVdVMatrix.get(0, 0);
- temp = 0;
-
- if (type == 1) {
- return;
- }
- for (int p = 0; p < numberOfComponents; p++) {
- for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedV(kk, XV.get(temp + kk, 0));
- }
- temp += getComponent(p).getNumberOfAssociationSites();
- }
-
- // KlkTMatrix = new SimpleMatrix(KlkdT);
- SimpleMatrix KlkTMatrixTImesKsi = KlkTMatrix.mult(ksiMatrix);
- // dQdT
- SimpleMatrix tempMatrix2 = ksiMatrixTranspose.mult(KlkTMatrixTImesKsi).scale(-0.5);
- dFCPAdT = tempMatrix2.get(0, 0);
-
- // SimpleMatrix KlkTVMatrix = new SimpleMatrix(KlkdTdV);
- // SimpleMatrix tempMatrixTV =
- // ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5).minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- // dFCPAdTdV = tempMatrixTV.get(0, 0);
- // dXdT
- SimpleMatrix XT = hessianInvers.mult(KlkTMatrixTImesKsi);
- // dQdTdT
- SimpleMatrix tempMatrixTT = ksiMatrixTranspose.mult(KlkTTMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XT));
- dFCPAdTdT = tempMatrixTT.get(0, 0);
-
- SimpleMatrix tempMatrixTV = ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- dFCPAdTdV = tempMatrixTV.get(0, 0);
-
- temp = 0;
- for (int p = 0; p < numberOfComponents; p++) {
- for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedT(kk, XT.get(temp + kk, 0));
- }
- temp += getComponent(p).getNumberOfAssociationSites();
- }
-
- if (type == 2) {
- return;
- }
-
- // int assSites = 0;
- // if(true) return;
- for (int p = 0; p < numberOfComponents; p++) {
- SimpleMatrix KiMatrix = new SimpleMatrix(Klkni[p]);
- // KiMatrix.print(10,10);
- // Matrix dQdniMatrix =
- // (ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5)); // this
- // methods misses one part of ....
- // dQdniMatrix.print(10,10);
- // KiMatrix.print(10, 10);
- // miMatrix.getMatrix(assSites, assSites, 0, totalNumberOfAccociationSites -
- // 1).print(10, 10);
- // Matrix tempMatrix20 = miMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites -
- // 1).times(uMatrix).minus(ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5));
- //
- // ksiMatrix.transpose().times(KlkTMatrix.times(ksiMatrix)).times(-0.5);
- // System.out.println("dQdn ");
- // tempMatrix20.print(10, 10);
- SimpleMatrix tempMatrix4 = KiMatrix.mult(ksiMatrix);
- // udotTimesmiMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites - 1).print(10, 10);
- SimpleMatrix tempMatrix5 = udotTimesmiMatrix.extractVector(true, p).transpose().minus(tempMatrix4);
- // tempMki[0] = mki[p];
- // Matrix amatrix = new Matrix(croeneckerProduct(tempMki,
- // udotMatrix.getArray()));
- // System.out.println("aMatrix ");
- // amatrix.transpose().print(10, 10);
- // System.out.println("temp4 matrix");
- // tempMatrix4.print(10, 10);
- // Matrix tempMatrix5 = amatrix.minus(tempMatrix4);
- SimpleMatrix tempMatrix6 = hessianInvers.mult(tempMatrix5); // .scale(-1.0);
- // System.out.println("dXdni");
- // tempMatrix4.print(10, 10);
- // tempMatrix5.print(10, 10);
- // System.out.println("dXdn ");
- // tempMatrix6.print(10, 10);
- int temp2 = 0;
- for (int compp = 0; compp < numberOfComponents; compp++) {
- for (int kk = 0; kk < getComponent(compp).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(compp)).setXsitedni(kk, p, -1.0 * tempMatrix6.get(temp2 + kk, 0));
- }
- temp2 += getComponent(compp).getNumberOfAssociationSites();
- }
- // assSites += getComponent(p).getNumberOfAssociationSites();
- }
+ initCPAMatrix(type);
}
/**
@@ -1796,106 +1578,7 @@ public void initCPAMatrixOld(int type) {
* @return a boolean
*/
public boolean solveXOld() {
- if (getTotalNumberOfAccociationSites() == 0) {
- return true;
- }
-
- boolean solvedX = solveX2(5);
- if (solvedX) {
- // return true;
- }
-
- DMatrixRMaj mat1 = KlkMatrix.getMatrix();
- DMatrixRMaj mat2 = ksiMatrix.getMatrix();
- // second order method not working correctly and not used t the moment b ecause of numerical
- // stability
- int temp = 0;
- int iter = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- mVector.set(temp + j, 0, componentArray[i].getNumberOfMolesInPhase());
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
- double Klk = 0.0;
- double totalVolume = getTotalVolume();
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- Klk = mVector.get(i, 0) * mVector.get(j, 0) / totalVolume * delta[i][j];
- KlkMatrix.set(i, j, Klk);
- KlkMatrix.set(j, i, Klk);
- }
- }
- boolean solved = true;
- // SimpleMatrix corrMatrix = null;
- do {
- solved = true;
- iter++;
- temp = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- ksiMatrix.set(temp + j, 0, ((ComponentSrkCPA) componentArray[i]).getXsite()[j]);
- // ksiMatrix.getMatrix().unsafe_set(temp + j, 0,
- // ksiMatrix.getMatrix().unsafe_get(temp + j, 0));
- udotMatrix.set(temp + j, 0, 1.0 / ksiMatrix.get(temp + j, 0) - 1.0);
- udotTimesmMatrix.set(temp + j, 0, mVector.get(temp + j, 0) * udotMatrix.get(temp + j, 0));
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
-
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- int krondelt = 0;
- if (i == j) {
- krondelt = 1;
- }
- hessianMatrix.set(i, j,
- -mVector.get(i, 0) / (ksiMatrix.get(i, 0) * ksiMatrix.get(i, 0)) * krondelt - KlkMatrix.get(i, j));
- hessianMatrix.set(j, i, hessianMatrix.get(i, j));
- }
- }
-
- // ksiMatrix = new SimpleMatrix(ksi);
- // SimpleMatrix hessianMatrix = new SimpleMatrix(hessian);
- try {
- hessianInvers = hessianMatrix.invert();
- } catch (Exception ex) {
- logger.error(ex.getMessage(), ex);
- return false;
- }
-
- CommonOps_DDRM.mult(mat1, mat2, corr2Matrix);
- CommonOps_DDRM.subtract(udotTimesmMatrix.getDDRM(), corr2Matrix, corr3Matrix);
- CommonOps_DDRM.mult(hessianInvers.getDDRM(), corr3Matrix, corr4Matrix);
- // SimpleMatrix gMatrix = udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix));
- // corrMatrix =
- // hessianInvers.mult(udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix)));
- // //.scale(-1.0);
- temp = 0;
- // System.out.println("print SimpleMatrix ...");
- // corrMatrix.print(10, 10);
- // SimpleMatrix simp = new SimpleMatrix(corr4Matrix);
- // System.out.println("print CommonOps ...");
- // simp.print(10,10);
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- double newX = ksiMatrix.get(temp + j, 0) - corr4Matrix.unsafe_get((temp + j), 0);
- if (newX < 0) {
- newX = 1e-10;
- solved = false;
- }
- ((ComponentCPAInterface) componentArray[i]).setXsite(j, newX);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
- // System.out.println("corrmatrix error " );
- // System.out.println("error " + corrMatrix.norm1());
- } while ((NormOps_DDRM.normF(corr4Matrix) > 1e-12 || !solved) && iter < 100);
-
- // System.out.println("iter " + iter + " error " + NormOps.normF(corr4Matrix));
- // // corrMatrix.print(10, 10);
- // ksiMatrix.print(10, 10);
- return true;
+ return solveX();
}
/**
@@ -1907,37 +1590,7 @@ public boolean solveXOld() {
* @return a boolean
*/
public boolean solveX2Old(int maxIter) {
- double err = .0;
- int iter = 0;
- // if (delta == null) {
- // initCPAMatrix(1);
- double old = 0.0;
- double neeval = 0.0;
- double totalVolume = getTotalVolume();
- // }
- do {
- iter++;
- err = 0.0;
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- old = ((ComponentSrkCPA) getComponent(moleculeNumber[i])).getXsite()[assSiteNumber[i]];
- neeval = 0;
- for (int j = 0; j < getTotalNumberOfAccociationSites(); j++) {
- neeval += getComponent(moleculeNumber[j]).getNumberOfMolesInPhase() * delta[i][j]
- * ((ComponentSrkCPA) getComponent(moleculeNumber[j])).getXsite()[assSiteNumber[j]];
- }
- neeval = 1.0 / (1.0 + 1.0 / totalVolume * neeval);
- ((ComponentCPAInterface) getComponent(moleculeNumber[i])).setXsite(assSiteNumber[i], neeval);
- err += Math.abs((old - neeval) / neeval);
- }
- } while (Math.abs(err) > 1e-10 && iter < maxIter);
- // System.out.println("iter " + iter);
- // if (Math.abs(err)
- // < 1e-12) {
- // return true;
- // } else {
- // System.out.println("did not solve for Xi in iterations: " + iter);
- // System.out.println("error: " + err);
- return false;
+ return solveX2(maxIter);
}
/**
@@ -1954,10 +1607,12 @@ public boolean solveX2Old(int maxIter) {
* @throws neqsim.util.exception.IsNaNException if any.
* @throws neqsim.util.exception.TooManyIterationsException if any.
*/
- public double molarVolumeOld(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
- double BonV = pt == PhaseType.LIQUID ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ public double molarVolumeOld(double pressure, double temperature, double A, double B,
+ PhaseType pt) throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
+ double BonV =
+ pt == PhaseType.LIQUID ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
// if (pressure > 1000) {
// BonV = 0.9999;
// }
@@ -1991,8 +1646,8 @@ public double molarVolumeOld(double pressure, double temperature, double A, doub
iterations++;
gcpa = calc_g();
if (gcpa < 0) {
- setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
- gcpa = calc_g();
+ setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
+ gcpa = calc_g();
}
// lngcpa =
@@ -2002,62 +1657,64 @@ public double molarVolumeOld(double pressure, double temperature, double A, doub
gcpavvv = calc_lngVVV();
if (getTotalNumberOfAccociationSites() > 0) {
- solveX();
+ solveX();
}
initCPAMatrix(1);
double BonV2 = BonV * BonV;
BonVold = BonV;
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
dh = 1.0 + Btemp / (BonV2) * (Btemp / numberOfMolesInPhase * dFdVdV());
dhh = -2.0 * Btemp / (BonV2 * BonV) * (Btemp / numberOfMolesInPhase * dFdVdV())
- - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
+ - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
d1 = -h / dh;
d2 = -dh / dhh;
// System.out.println("h " + h + " iter " + iterations + " " + d1 + " d2 " + d2
// + " d1 / d2 " + (d1 / d2));
if (Math.abs(d1 / d2) <= 1.0) {
- BonV += d1 * (1.0 + 0.5 * d1 / d2);
+ BonV += d1 * (1.0 + 0.5 * d1 / d2);
} else if (d1 / d2 < -1) {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
} else if (d1 > d2) {
- BonV += d2;
- double hnew = h + d2 * dh;
- if (Math.abs(hnew) > Math.abs(h)) {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ BonV += d2;
+ double hnew = h + d2 * dh;
+ if (Math.abs(hnew) > Math.abs(h)) {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
} else {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
}
if (Math.abs((BonV - BonVold) / BonVold) > 0.1) {
- BonV = BonVold + 0.1 * (BonV - BonVold);
+ BonV = BonVold + 0.1 * (BonV - BonVold);
}
if (BonV > 0.9999) {
- if (iterations < 3) {
- BonV = (BonVold + BonV) / 2.0;
- } else {
- // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- // BonV = 0.9999;
- // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
- // temperature * R);
- }
+ if (iterations < 3) {
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ // BonV = 0.9999;
+ // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
+ // temperature * R);
+ }
} else if (BonV < 0) {
- if (iterations < 3) {
- BonV = Math.abs(BonVold + BonV) / 2.0;
- } else {
- // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
- // temperature * R);
- }
+ if (iterations < 3) {
+ BonV = Math.abs(BonVold + BonV) / 2.0;
+ } else {
+ // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
+ // temperature * R);
+ }
}
setMolarVolume(1.0 / BonV * Btemp / numberOfMolesInPhase);
Z = pressure * getMolarVolume() / (R * temperature);
- } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12) && iterations < 100);
+ } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12)
+ && iterations < 100);
if (Math.abs(h) > 1e-12) {
// System.out.println("h failed " + "Z" + Z + " iterations " + iterations + "
@@ -2084,4 +1741,121 @@ public double molarVolumeOld(double pressure, double temperature, double A, doub
}
return getMolarVolume();
}
+
+ /**
+ * Returns the transpose of a dense matrix.
+ *
+ * @param matrix input matrix
+ * @return transposed matrix
+ */
+ private static DenseMatrix transpose(DenseMatrix matrix) {
+ DenseMatrix out = new DenseMatrix(matrix.numCols, matrix.numRows);
+ for (int i = 0; i < matrix.numRows; i++) {
+ for (int j = 0; j < matrix.numCols; j++) {
+ out.unsafe_set(j, i, matrix.unsafe_get(i, j));
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Multiplies two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return matrix product
+ */
+ private static DenseMatrix multiply(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, right.numCols);
+ LinearAlgebraOps.mult(left, right, out);
+ return out;
+ }
+
+ /**
+ * Adds two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return sum matrix
+ */
+ private static DenseMatrix add(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, left.numCols);
+ for (int i = 0; i < left.numRows; i++) {
+ for (int j = 0; j < left.numCols; j++) {
+ out.unsafe_set(i, j, left.unsafe_get(i, j) + right.unsafe_get(i, j));
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Subtracts two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return difference matrix
+ */
+ private static DenseMatrix subtract(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, left.numCols);
+ LinearAlgebraOps.subtract(left, right, out);
+ return out;
+ }
+
+ /**
+ * Scales all elements in a dense matrix.
+ *
+ * @param matrix input matrix
+ * @param factor scale factor
+ * @return scaled matrix
+ */
+ private static DenseMatrix scale(DenseMatrix matrix, double factor) {
+ DenseMatrix out = new DenseMatrix(matrix.numRows, matrix.numCols);
+ for (int i = 0; i < matrix.numRows; i++) {
+ for (int j = 0; j < matrix.numCols; j++) {
+ out.unsafe_set(i, j, matrix.unsafe_get(i, j) * factor);
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Element-wise multiplication of two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return Hadamard product
+ */
+ private static DenseMatrix elementMult(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, left.numCols);
+ for (int i = 0; i < left.numRows; i++) {
+ for (int j = 0; j < left.numCols; j++) {
+ out.unsafe_set(i, j, left.unsafe_get(i, j) * right.unsafe_get(i, j));
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Extracts one row or column as a matrix view copy.
+ *
+ * @param matrix source matrix
+ * @param extractRow true to extract a row, false to extract a column
+ * @param index row/column index
+ * @return extracted row/column matrix
+ */
+ private static DenseMatrix extractVector(DenseMatrix matrix, boolean extractRow, int index) {
+ if (extractRow) {
+ DenseMatrix row = new DenseMatrix(1, matrix.numCols);
+ for (int j = 0; j < matrix.numCols; j++) {
+ row.unsafe_set(0, j, matrix.unsafe_get(index, j));
+ }
+ return row;
+ }
+ DenseMatrix col = new DenseMatrix(matrix.numRows, 1);
+ for (int i = 0; i < matrix.numRows; i++) {
+ col.unsafe_set(i, 0, matrix.unsafe_get(i, index));
+ }
+ return col;
+ }
+
}
diff --git a/src/main/java/neqsim/thermo/phase/PhaseSrkCPAandersonReduced.java b/src/main/java/neqsim/thermo/phase/PhaseSrkCPAandersonReduced.java
index 15826b03e8..72c87df056 100644
--- a/src/main/java/neqsim/thermo/phase/PhaseSrkCPAandersonReduced.java
+++ b/src/main/java/neqsim/thermo/phase/PhaseSrkCPAandersonReduced.java
@@ -4,6 +4,7 @@
import org.apache.logging.log4j.Logger;
import neqsim.thermo.component.ComponentCPAInterface;
import neqsim.thermo.component.ComponentSrkCPA;
+import neqsim.util.math.LinearAlgebraOps;
/**
* Anderson-accelerated nested CPA phase solver with site symmetry reduction.
@@ -12,21 +13,22 @@
* Combines two orthogonal acceleration strategies:
*
- * The outer Halley iteration for molar volume and the volume derivative computation ({@code initCPAMatrix(1)}) also use
- * the reduced dimension p, reducing the Hessian linear system from O(n_s^3) to O(p^3).
+ * The outer Halley iteration for molar volume and the volume derivative computation
+ * ({@code initCPAMatrix(1)}) also use the reduced dimension p, reducing the Hessian linear system
+ * from O(n_s^3) to O(p^3).
*
- * This is a nested-family solver: site fractions are fully converged at each volume step before computing exact
- * volume derivatives via the implicit function theorem. It therefore avoids the coupled-family equilibrium sensitivity
- * documented for the Broyden and fully implicit solvers.
+ * This is a nested-family solver: site fractions are fully converged at each volume step
+ * before computing exact volume derivatives via the implicit function theorem. It therefore avoids
+ * the coupled-family equilibrium sensitivity documented for the Broyden and fully implicit solvers.
*
@@ -133,8 +135,8 @@ public class PhaseSrkCPAandersonReduced extends PhaseSrkCPAs {
private transient int workOuterNs = 0;
/**
- * When true, {@code initCPAMatrix(1)} skips the {@code updateDeltaWithG(ns)} call. Set by the Halley outer loop where
- * delta has just been refreshed; cleared on exit.
+ * When true, {@code initCPAMatrix(1)} skips the {@code updateDeltaWithG(ns)} call. Set by the
+ * Halley outer loop where delta has just been refreshed; cleared on exit.
*/
private transient boolean skipDeltaUpdateInInitCPA = false;
@@ -170,9 +172,10 @@ public static String getProfileSummary() {
double avgOuter = callCount > 0 ? (double) totalOuterIters / callCount : 0;
double avgInner = totalOuterIters > 0 ? (double) totalInnerIters / totalOuterIters : 0;
return String.format(
- "Calls=%d AvgOuterIters=%.1f AvgInnerIters=%.1f AndersonConverged=%d "
- + "NewtonFallback=%d NumTypes(last)=%d",
- callCount, avgOuter, avgInner, andersonConvergedCount, newtonFallbackCount, callCount > 0 ? 0 : -1);
+ "Calls=%d AvgOuterIters=%.1f AvgInnerIters=%.1f AndersonConverged=%d "
+ + "NewtonFallback=%d NumTypes(last)=%d",
+ callCount, avgOuter, avgInner, andersonConvergedCount, newtonFallbackCount,
+ callCount > 0 ? 0 : -1);
}
/**
@@ -264,14 +267,15 @@ public void addComponent(String name, double moles, double molesInPhase, int com
* {@inheritDoc}
*
*
- * Molar volume calculation using the Halley outer loop for volume and Anderson-accelerated successive substitution on
- * reduced site type fractions. The Halley step uses the implicit function theorem to compute dF_CPA/dV
- * derivatives in the reduced p-dimensional space.
+ * Molar volume calculation using the Halley outer loop for volume and Anderson-accelerated
+ * successive substitution on reduced site type fractions. The Halley step uses the
+ * implicit function theorem to compute dF_CPA/dV derivatives in the reduced p-dimensional space.
*
- * Anderson acceleration (mixing depth m=3) is applied to this reduced map. After convergence, the p type fractions
- * are expanded back to all n_s individual site fractions.
+ * Anderson acceleration (mixing depth m=3) is applied to this reduced map. After convergence, the
+ * p type fractions are expanded back to all n_s individual site fractions.
*
- * Uses the normal equations: (G^T G) gamma = G^T g. The system is at most m x m (typically 3x3) so a direct solve via
- * Gaussian elimination is efficient and stable.
+ * Uses the normal equations: (G^T G) gamma = G^T g. The system is at most m x m (typically 3x3)
+ * so a direct solve via Gaussian elimination is efficient and stable.
*
- * Two individual sites are equivalent if they belong to the same component and have identical deltaNog rows (same
- * bonding pattern to all other sites). This corresponds to sites with the same charge in the CPA association scheme.
+ * Two individual sites are equivalent if they belong to the same component and have identical
+ * deltaNog rows (same bonding pattern to all other sites). This corresponds to sites with the
+ * same charge in the CPA association scheme.
*
- * Override type 1 initialization to use reduced-dimension site type computation. Volume derivatives (FCPA, dFCPAdV,
- * dFCPAdVdV, dFCPAdVdVdV) are computed using the type grouping, reducing the Hessian linear system from n_s to p
- * dimensions.
+ * Override type 1 initialization to use reduced-dimension site type computation. Volume
+ * derivatives (FCPA, dFCPAdV, dFCPAdVdV, dFCPAdVdVdV) are computed using the type grouping,
+ * reducing the Hessian linear system from n_s to p dimensions.
*
- * Reduces the coupled (n_s + 1)-dimensional Newton system to (p + 1) dimensions, where p is the number of unique
- * association site types. Sites on the same component with identical bonding patterns (same delta row in the
- * association matrix) are grouped into a single "type" with a multiplicity factor. This exploits the mathematical
- * theorem that equivalent sites converge to equal site fractions at equilibrium.
+ * Reduces the coupled (n_s + 1)-dimensional Newton system to (p + 1) dimensions, where p is the
+ * number of unique association site types. Sites on the same component with identical bonding
+ * patterns (same delta row in the association matrix) are grouped into a single "type" with a
+ * multiplicity factor. This exploits the mathematical theorem that equivalent sites converge to
+ * equal site fractions at equilibrium.
*
- * Combined with Broyden rank-1 updates of the inverse Jacobian after initial convergence, this yields compounded
- * speedup from both dimension reduction and reduced per-iteration cost.
+ * Combined with Broyden rank-1 updates of the inverse Jacobian after initial convergence, this
+ * yields compounded speedup from both dimension reduction and reduced per-iteration cost.
*
@@ -117,8 +119,7 @@ public class PhaseSrkCPAreduced extends PhaseSrkCPAs {
/**
* Construct a PhaseSrkCPAreduced phase.
*/
- public PhaseSrkCPAreduced() {
- }
+ public PhaseSrkCPAreduced() {}
/** {@inheritDoc} */
@Override
@@ -155,14 +156,15 @@ public void addComponent(String name, double moles, double molesInPhase, int com
* {@inheritDoc}
*
*
- * Reduced-dimension coupled Newton/Broyden molar volume solver. Exploits association site symmetry to work in (p+1)
- * dimensions where p is the number of unique site types, combined with Broyden rank-1 inverse-Jacobian updates after
- * the initial Newton phase.
+ * Reduced-dimension coupled Newton/Broyden molar volume solver. Exploits association site
+ * symmetry to work in (p+1) dimensions where p is the number of unique site types, combined with
+ * Broyden rank-1 inverse-Jacobian updates after the initial Newton phase.
*
- * Two individual sites are equivalent if they belong to the same component and have identical deltaNog rows (same
- * bonding pattern to all other sites). This corresponds to sites with the same charge in the CPA association scheme —
- * e.g., the two electron-donor sites of water (4C) have identical interactions with all other sites.
+ * Two individual sites are equivalent if they belong to the same component and have identical
+ * deltaNog rows (same bonding pattern to all other sites). This corresponds to sites with the
+ * same charge in the CPA association scheme — e.g., the two electron-donor sites of water (4C)
+ * have identical interactions with all other sites.
*
- * Override type 1 initialization to use reduced-dimension site type computation. Higher-order volume derivatives
- * (FCPA, dFCPAdV, dFCPAdVdV, dFCPAdVdVdV) are computed using the type grouping, reducing linear system size from n_s
- * to p.
+ * Override type 1 initialization to use reduced-dimension site type computation. Higher-order
+ * volume derivatives (FCPA, dFCPAdV, dFCPAdVdV, dFCPAdVdVdV) are computed using the type
+ * grouping, reducing linear system size from n_s to p.
*
@@ -43,7 +39,7 @@ public class PhaseUMRCPA extends PhasePrEos implements PhaseCPAInterface {
double dFCPAdVdV = 0.0;
double dFCPAdVdVdV = 0.0;
double gcpav = 0.0;
- private double[] dFdNtemp = { 0, 0 };
+ private double[] dFdNtemp = {0, 0};
int cpaon = 1;
int oldTotalNumberOfAccociationSites = 0;
int totalNumberOfAccociationSites = 0;
@@ -59,31 +55,31 @@ public class PhaseUMRCPA extends PhasePrEos implements PhaseCPAInterface {
private double[][] deltadT = null;
private double[][] deltadTdT = null;
double[][][] Klkni = null;
- private SimpleMatrix KlkTVMatrix = null;
- private SimpleMatrix KlkTTMatrix = null;
- private SimpleMatrix KlkTMatrix = null;
- private SimpleMatrix udotTimesmMatrix = null;
- private SimpleMatrix mVector = null;
- private SimpleMatrix udotMatrix = null;
- private SimpleMatrix uMatrix = null;
- private SimpleMatrix QMatksiksiksi = null;
- private SimpleMatrix KlkVVVMatrix = null;
- private SimpleMatrix KlkVVMatrix = null;
- private SimpleMatrix udotTimesmiMatrix = null;
- private SimpleMatrix ksiMatrix = null;
- private SimpleMatrix KlkMatrix = null;
- private SimpleMatrix hessianMatrix = null;
- private SimpleMatrix hessianInvers = null;
+ private DenseMatrix KlkTVMatrix = null;
+ private DenseMatrix KlkTTMatrix = null;
+ private DenseMatrix KlkTMatrix = null;
+ private DenseMatrix udotTimesmMatrix = null;
+ private DenseMatrix mVector = null;
+ private DenseMatrix udotMatrix = null;
+ private DenseMatrix uMatrix = null;
+ private DenseMatrix QMatksiksiksi = null;
+ private DenseMatrix KlkVVVMatrix = null;
+ private DenseMatrix KlkVVMatrix = null;
+ private DenseMatrix udotTimesmiMatrix = null;
+ private DenseMatrix ksiMatrix = null;
+ private DenseMatrix KlkMatrix = null;
+ private DenseMatrix hessianMatrix = null;
+ private DenseMatrix hessianInvers = null;
/** Cached LU factorization of {@code hessianMatrix} for repeated Hessian backsolves. */
- private transient LinearSolverDense
@@ -105,7 +101,8 @@ public PhaseUMRCPA clone() {
}
if (activeAccosComp != null) {
clonedPhase.activeAccosComp = activeAccosComp.clone();
- System.arraycopy(this.activeAccosComp, 0, clonedPhase.activeAccosComp, 0, activeAccosComp.length);
+ System.arraycopy(this.activeAccosComp, 0, clonedPhase.activeAccosComp, 0,
+ activeAccosComp.length);
}
clonedPhase.hessianLU = null;
clonedPhase.hessianLUinput = null;
@@ -119,92 +116,106 @@ public PhaseUMRCPA clone() {
/** {@inheritDoc} */
@Override
- public void init(double totalNumberOfMoles, int numberOfComponents, int initType, PhaseType pt, double beta) {
+ public void init(double totalNumberOfMoles, int numberOfComponents, int initType, PhaseType pt,
+ double beta) {
boolean changedAssosiationStatus = false;
if (initType == 0) {
activeAccosComp = new int[numberOfComponents];
for (int i = 0; i < numberOfComponents; i++) {
- if (componentArray[i].getNumberOfmoles() < 1e-50) {
- componentArray[i].setNumberOfAssociationSites(0);
- if (activeAccosComp[i] == 1) {
- activeAccosComp[i] = 0;
- changedAssosiationStatus = true;
- }
- } else {
- if (activeAccosComp[i] == 0) {
- changedAssosiationStatus = true;
- activeAccosComp[i] = 1;
- }
- }
+ if (componentArray[i].getNumberOfmoles() < 1e-50) {
+ componentArray[i].setNumberOfAssociationSites(0);
+ if (activeAccosComp[i] == 1) {
+ activeAccosComp[i] = 0;
+ changedAssosiationStatus = true;
+ }
+ } else {
+ if (activeAccosComp[i] == 0) {
+ changedAssosiationStatus = true;
+ activeAccosComp[i] = 1;
+ }
+ }
}
if (changedAssosiationStatus || lngi == null) {
- setTotalNumberOfAccociationSites(0);
- selfAccociationScheme = new int[numberOfComponents][0][0];
- crossAccociationScheme = new int[numberOfComponents][numberOfComponents][0][0];
- for (int i = 0; i < numberOfComponents; i++) {
- if (componentArray[i].getNumberOfmoles() < 1e-50) {
- componentArray[i].setNumberOfAssociationSites(0);
- } else {
- componentArray[i].setNumberOfAssociationSites(componentArray[i].getOrginalNumberOfAssociationSites());
- setTotalNumberOfAccociationSites(
- getTotalNumberOfAccociationSites() + componentArray[i].getNumberOfAssociationSites());
- selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
- for (int j = 0; j < numberOfComponents; j++) {
- crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
- }
- }
- }
+ setTotalNumberOfAccociationSites(0);
+ selfAccociationScheme = new int[numberOfComponents][0][0];
+ crossAccociationScheme = new int[numberOfComponents][numberOfComponents][0][0];
+ for (int i = 0; i < numberOfComponents; i++) {
+ if (componentArray[i].getNumberOfmoles() < 1e-50) {
+ componentArray[i].setNumberOfAssociationSites(0);
+ } else {
+ componentArray[i].setNumberOfAssociationSites(
+ componentArray[i].getOrginalNumberOfAssociationSites());
+ setTotalNumberOfAccociationSites(getTotalNumberOfAccociationSites()
+ + componentArray[i].getNumberOfAssociationSites());
+ selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
+ for (int j = 0; j < numberOfComponents; j++) {
+ crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
+ }
+ }
+ }
}
for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- ((ComponentUMRCPA) componentArray[i]).setXsite(j, 1.0);
- ((ComponentUMRCPA) componentArray[i]).setXsitedV(j, 0.0);
- ((ComponentUMRCPA) componentArray[i]).setXsitedT(j, 0.0);
- }
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ ((ComponentUMRCPA) componentArray[i]).setXsite(j, 1.0);
+ ((ComponentUMRCPA) componentArray[i]).setXsitedV(j, 0.0);
+ ((ComponentUMRCPA) componentArray[i]).setXsitedT(j, 0.0);
+ }
}
if (changedAssosiationStatus || lngi == null || mVector == null) {
- lngi = new double[numberOfComponents];
- mVector = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- KlkMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- hessianMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- corr2Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr3Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr4Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- Klkni = new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- ksiMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- uMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- udotMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- moleculeNumber = new int[getTotalNumberOfAccociationSites()];
- assSiteNumber = new int[getTotalNumberOfAccociationSites()];
- gvector = new double[getTotalNumberOfAccociationSites()][1];
- udotTimesmMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- delta = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltaNog = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltadT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltadTdT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- QMatksiksiksi = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- udotTimesmiMatrix = new SimpleMatrix(numberOfComponents, getTotalNumberOfAccociationSites());
-
- oldTotalNumberOfAccociationSites = getTotalNumberOfAccociationSites();
-
- int temp = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- moleculeNumber[temp + j] = i;
- assSiteNumber[temp + j] = j;
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
+ lngi = new double[numberOfComponents];
+ mVector = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ KlkMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ hessianMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ corr2Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr3Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr4Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ Klkni =
+ new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ ksiMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ uMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ moleculeNumber = new int[getTotalNumberOfAccociationSites()];
+ assSiteNumber = new int[getTotalNumberOfAccociationSites()];
+ gvector = new double[getTotalNumberOfAccociationSites()][1];
+ udotTimesmMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ delta = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ deltaNog =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ deltadT =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ deltadTdT =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ QMatksiksiksi = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotTimesmiMatrix = new DenseMatrix(numberOfComponents, getTotalNumberOfAccociationSites());
+
+ oldTotalNumberOfAccociationSites = getTotalNumberOfAccociationSites();
+
+ int temp = 0;
+ for (int i = 0; i < numberOfComponents; i++) {
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ moleculeNumber[temp + j] = i;
+ assSiteNumber[temp + j] = j;
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
+ }
}
}
@@ -261,46 +272,47 @@ public void initCPAMatrix(int type) {
double tempVar2;
for (int i = 0; i < numberOfComponents; i++) {
for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- tempVar1 = ksiMatrix.get(temp + j, 0);
- tempVar2 = udotMatrix.get(temp + j, 0);
- uMatrix.set(temp + j, 0, Math.log(tempVar1) - tempVar1 + 1.0);
- gvector[temp + j][0] = mVector.get(temp + j, 0) * tempVar2;
-
- if (moleculeNumber[temp + j] == i) {
- udotTimesmiMatrix.set(i, temp + j, tempVar2);
- } else {
- udotTimesmiMatrix.set(i, temp + j, 0.0);
- }
+ tempVar1 = ksiMatrix.unsafe_get(temp + j, 0);
+ tempVar2 = udotMatrix.unsafe_get(temp + j, 0);
+ uMatrix.unsafe_set(temp + j, 0, Math.log(tempVar1) - tempVar1 + 1.0);
+ gvector[temp + j][0] = mVector.unsafe_get(temp + j, 0) * tempVar2;
+
+ if (moleculeNumber[temp + j] == i) {
+ udotTimesmiMatrix.unsafe_set(i, temp + j, tempVar2);
+ } else {
+ udotTimesmiMatrix.unsafe_set(i, temp + j, 0.0);
+ }
}
temp += componentArray[i].getNumberOfAssociationSites();
}
if (type > 2) {
for (int p = 0; p < numberOfComponents; p++) {
- lngi[p] = ((ComponentUMRCPA) componentArray[p]).calc_lngi(this);
+ lngi[p] = ((ComponentUMRCPA) componentArray[p]).calc_lngi(this);
}
}
for (int i = 0; i < totalNumberOfAccociationSites; i++) {
for (int j = i; j < totalNumberOfAccociationSites; j++) {
- delta[i][j] = deltaNog[i][j] * gcpa;
- delta[j][i] = delta[i][j];
- if (type > 1) {
- deltadT[i][j] = cpamix.calcDeltadT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i], moleculeNumber[j],
- this, getTemperature(), getPressure(), numberOfComponents);
- deltadT[j][i] = deltadT[i][j];
-
- deltadTdT[i][j] = cpamix.calcDeltadTdT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
- moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
- deltadTdT[j][i] = deltadTdT[i][j];
- }
+ delta[i][j] = deltaNog[i][j] * gcpa;
+ delta[j][i] = delta[i][j];
+ if (type > 1) {
+ deltadT[i][j] = cpamix.calcDeltadT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
+ moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
+ deltadT[j][i] = deltadT[i][j];
+
+ deltadTdT[i][j] =
+ cpamix.calcDeltadTdT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
+ moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
+ deltadTdT[j][i] = deltadTdT[i][j];
+ }
}
}
double totalVolume = getTotalVolume();
double totalVolume2 = totalVolume * totalVolume;
double totalVolume3 = totalVolume2 * totalVolume;
- double gdv1 = getGcpav() - 1.0 / totalVolume;
+ double gdv1 = gcpav - 1.0 / totalVolume;
double gdv2 = gdv1 * gdv1;
double gdv3 = gdv2 * gdv1;
double Klk = 0.0;
@@ -308,84 +320,90 @@ public void initCPAMatrix(int type) {
double tempKsiRead = 0.0;
for (int i = 0; i < totalNumberOfAccociationSites; i++) {
for (int j = i; j < totalNumberOfAccociationSites; j++) {
- Klk = KlkMatrix.get(i, j);
- tempVar = Klk * gdv1;
- KlkVMatrix.set(i, j, tempVar);
- KlkVMatrix.set(j, i, tempVar);
-
- tempVar = Klk * gdv2 + Klk * (gcpavv + 1.0 / totalVolume2);
- KlkVVMatrix.set(i, j, tempVar);
- KlkVVMatrix.set(j, i, tempVar);
-
- tempVar = Klk * gdv3 + 3.0 * Klk * (gcpav - 1.0 / totalVolume) * (gcpavv + 1.0 / (totalVolume2))
- + Klk * (gcpavvv - 2.0 / (totalVolume3));
- KlkVVVMatrix.set(i, j, tempVar);
- KlkVVVMatrix.set(j, i, tempVar);
-
- if (type > 1) {
- tempVar = deltadT[i][j] / delta[i][j];
-
- if (Math.abs(tempVar) > 1e-50) {
- double tempVardT = deltadTdT[i][j] / delta[i][j]
- - (deltadT[i][j] * deltadT[i][j]) / (delta[i][j] * delta[i][j]);
-
- tempVar2 = Klk * tempVar;
- KlkTMatrix.set(i, j, tempVar2);
- KlkTMatrix.set(j, i, tempVar2);
-
- tempVar2 = Klk * tempVar * (gcpav - 1.0 / totalVolume);
- KlkTVMatrix.set(i, j, tempVar2);
- KlkTVMatrix.set(j, i, tempVar2);
-
- tempVar2 = Klk * (tempVar * tempVar + tempVardT);
- KlkTTMatrix.set(i, j, tempVar2);
- KlkTTMatrix.set(j, i, tempVar2);
- }
-
- if (type > 2) {
- for (int p = 0; p < numberOfComponents; p++) {
- double t1 = 0.0;
- double t2 = 0.0;
- if (moleculeNumber[i] == p) {
- t1 = 1.0 / mVector.get(i, 0);
- }
- if (moleculeNumber[j] == p) {
- t2 = 1.0 / mVector.get(j, 0);
- }
- Klkni[p][i][j] = Klk * (t1 + t2 + lngi[p]); // ((ComponentSrkCPA)
- // getComponent(p)).calc_lngi(this));
- Klkni[p][j][i] = Klkni[p][i][j];
- }
- }
- }
+ Klk = KlkMatrix.unsafe_get(i, j);
+ tempVar = Klk * gdv1;
+ KlkVMatrix.unsafe_set(i, j, tempVar);
+ KlkVMatrix.unsafe_set(j, i, tempVar);
+
+ tempVar = Klk * gdv2 + Klk * (gcpavv + 1.0 / totalVolume2);
+ KlkVVMatrix.unsafe_set(i, j, tempVar);
+ KlkVVMatrix.unsafe_set(j, i, tempVar);
+
+ tempVar =
+ Klk * gdv3 + 3.0 * Klk * (gcpav - 1.0 / totalVolume) * (gcpavv + 1.0 / totalVolume2)
+ + Klk * (gcpavvv - 2.0 / totalVolume3);
+ KlkVVVMatrix.unsafe_set(i, j, tempVar);
+ KlkVVVMatrix.unsafe_set(j, i, tempVar);
+
+ if (type > 1) {
+ tempVar = deltadT[i][j] / delta[i][j];
+
+ if (Math.abs(tempVar) > 1e-50) {
+ double tempVardT = deltadTdT[i][j] / delta[i][j]
+ - (deltadT[i][j] * deltadT[i][j]) / (delta[i][j] * delta[i][j]);
+
+ tempVar2 = Klk * tempVar;
+ KlkTMatrix.unsafe_set(i, j, tempVar2);
+ KlkTMatrix.unsafe_set(j, i, tempVar2);
+
+ tempVar2 = Klk * tempVar * (gcpav - 1.0 / totalVolume);
+ KlkTVMatrix.unsafe_set(i, j, tempVar2);
+ KlkTVMatrix.unsafe_set(j, i, tempVar2);
+
+ tempVar2 = Klk * (tempVar * tempVar + tempVardT);
+ KlkTTMatrix.unsafe_set(i, j, tempVar2);
+ KlkTTMatrix.unsafe_set(j, i, tempVar2);
+ }
+
+ if (type > 2) {
+ for (int p = 0; p < numberOfComponents; p++) {
+ double t1 = 0.0;
+ double t2 = 0.0;
+ if (moleculeNumber[i] == p) {
+ t1 = 1.0 / mVector.unsafe_get(i, 0);
+ }
+ if (moleculeNumber[j] == p) {
+ t2 = 1.0 / mVector.unsafe_get(j, 0);
+ }
+ Klkni[p][i][j] = Klk * (t1 + t2 + lngi[p]);
+ Klkni[p][j][i] = Klkni[p][i][j];
+ }
+ }
+ }
}
- tempKsiRead = ksiMatrix.get(i, 0);
- QMatksiksiksi.set(i, 0, 2.0 * mVector.get(i, 0) / (tempKsiRead * tempKsiRead * tempKsiRead));
+ tempKsiRead = ksiMatrix.unsafe_get(i, 0);
+ QMatksiksiksi.unsafe_set(i, 0,
+ 2.0 * mVector.unsafe_get(i, 0) / (tempKsiRead * tempKsiRead * tempKsiRead));
}
- SimpleMatrix ksiMatrixTranspose = ksiMatrix.transpose();
+ DenseMatrix ksiMatrixTranspose = transpose(ksiMatrix);
- // dXdV
- SimpleMatrix KlkVMatrixksi = KlkVMatrix.mult(ksiMatrix);
- SimpleMatrix XV = applyHessianInv(KlkVMatrixksi);
- SimpleMatrix XVtranspose = XV.transpose();
+ DenseMatrix klkVMatrixksi = multiply(KlkVMatrix, ksiMatrix);
+ DenseMatrix XV = applyHessianInv(klkVMatrixksi);
+ DenseMatrix XVtranspose = transpose(XV);
- FCPA = mVector.transpose().mult(uMatrix.minus(ksiMatrix.elementMult(udotMatrix).scale(0.5))).get(0, 0); // QCPA.get(0,
- // 0);
- // //*0.5;
+ DenseMatrix qCpa = multiply(transpose(mVector),
+ subtract(uMatrix, scale(elementMult(ksiMatrix, udotMatrix), 0.5)));
+ FCPA = qCpa.unsafe_get(0, 0);
- dFCPAdV = ksiMatrixTranspose.mult(KlkVMatrixksi).get(0, 0) * (-0.5);
- SimpleMatrix KlkVVMatrixTImesKsi = KlkVVMatrix.mult(ksiMatrix);
- dFCPAdVdV = ksiMatrixTranspose.mult(KlkVVMatrixTImesKsi).scale(-0.5).minus(KlkVMatrixksi.transpose().mult(XV))
- .get(0, 0);
+ DenseMatrix tempMatrix = scale(multiply(ksiMatrixTranspose, klkVMatrixksi), -0.5);
+ dFCPAdV = tempMatrix.unsafe_get(0, 0);
+ DenseMatrix klkVvMatrixTimesKsi = multiply(KlkVVMatrix, ksiMatrix);
+ DenseMatrix tempMatrixVV =
+ subtract(scale(multiply(ksiMatrixTranspose, klkVvMatrixTimesKsi), -0.5),
+ multiply(transpose(klkVMatrixksi), XV));
+ dFCPAdVdV = tempMatrixVV.unsafe_get(0, 0);
- SimpleMatrix QVVV = ksiMatrixTranspose.mult(KlkVVVMatrix.mult(ksiMatrix)); // .scale(-0.5);
- SimpleMatrix QVVksi = KlkVVMatrixTImesKsi.scale(-1.0);
- SimpleMatrix QksiVksi = KlkVMatrix.scale(-1.0);
+ DenseMatrix qVvv = multiply(ksiMatrixTranspose, multiply(KlkVVVMatrix, ksiMatrix));
+ DenseMatrix qVvksi = scale(klkVvMatrixTimesKsi, -1.0);
+ DenseMatrix qKsiVksi = scale(KlkVMatrix, -1.0);
- dFCPAdVdVdV = -0.5 * QVVV.get(0, 0) + QVVksi.transpose().mult(XV).get(0, 0) * 3.0
- + XVtranspose.mult(QksiVksi.mult(XV)).get(0, 0) * 3.0
- + XVtranspose.mult(QMatksiksiksi.mult(XVtranspose)).mult(XV).get(0, 0);
+ DenseMatrix mat1 = scale(multiply(transpose(qVvksi), XV), 3.0);
+ DenseMatrix mat2 = scale(multiply(XVtranspose, multiply(qKsiVksi, XV)), 3.0);
+ DenseMatrix mat4 = multiply(multiply(XVtranspose, multiply(QMatksiksiksi, XVtranspose)), XV);
+
+ dFCPAdVdVdV = -0.5 * qVvv.unsafe_get(0, 0) + mat1.unsafe_get(0, 0) + mat2.unsafe_get(0, 0)
+ + mat4.unsafe_get(0, 0);
if (type == 1) {
return;
@@ -394,36 +412,30 @@ public void initCPAMatrix(int type) {
temp = 0;
for (int p = 0; p < numberOfComponents; p++) {
for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedV(kk, XV.get(temp + kk, 0));
+ ((ComponentCPAInterface) getComponent(p)).setXsitedV(kk, XV.unsafe_get(temp + kk, 0));
}
temp += getComponent(p).getNumberOfAssociationSites();
}
- // KlkTMatrix = new SimpleMatrix(KlkdT);
- SimpleMatrix KlkTMatrixTImesKsi = KlkTMatrix.mult(ksiMatrix);
- // dQdT
- SimpleMatrix tempMatrix2 = ksiMatrixTranspose.mult(KlkTMatrixTImesKsi); // .scale(-0.5);
- dFCPAdT = tempMatrix2.get(0, 0) * (-0.5);
-
- // SimpleMatrix KlkTVMatrix = new SimpleMatrix(KlkdTdV);
- // SimpleMatrix tempMatrixTV =
- // ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5).minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- // dFCPAdTdV = tempMatrixTV.get(0, 0);
- // dXdT
- SimpleMatrix XT = applyHessianInv(KlkTMatrixTImesKsi);
- // dQdTdT
- SimpleMatrix tempMatrixTT = ksiMatrixTranspose.mult(KlkTTMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XT));
- dFCPAdTdT = tempMatrixTT.get(0, 0);
-
- SimpleMatrix tempMatrixTV = ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- dFCPAdTdV = tempMatrixTV.get(0, 0);
+ DenseMatrix klkTMatrixTimesKsi = multiply(KlkTMatrix, ksiMatrix);
+ DenseMatrix tempMatrix2 = scale(multiply(ksiMatrixTranspose, klkTMatrixTimesKsi), -0.5);
+ dFCPAdT = tempMatrix2.unsafe_get(0, 0);
+
+ DenseMatrix XT = applyHessianInv(klkTMatrixTimesKsi);
+ DenseMatrix tempMatrixTT =
+ subtract(scale(multiply(ksiMatrixTranspose, multiply(KlkTTMatrix, ksiMatrix)), -0.5),
+ multiply(transpose(klkTMatrixTimesKsi), XT));
+ dFCPAdTdT = tempMatrixTT.unsafe_get(0, 0);
+
+ DenseMatrix tempMatrixTV =
+ subtract(scale(multiply(ksiMatrixTranspose, multiply(KlkTVMatrix, ksiMatrix)), -0.5),
+ multiply(transpose(klkTMatrixTimesKsi), XV));
+ dFCPAdTdV = tempMatrixTV.unsafe_get(0, 0);
temp = 0;
for (int p = 0; p < numberOfComponents; p++) {
for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedT(kk, XT.get(temp + kk, 0));
+ ((ComponentCPAInterface) getComponent(p)).setXsitedT(kk, XT.unsafe_get(temp + kk, 0));
}
temp += getComponent(p).getNumberOfAssociationSites();
}
@@ -432,51 +444,20 @@ public void initCPAMatrix(int type) {
return;
}
- // int assSites = 0;
- // if(true) return;
for (int p = 0; p < numberOfComponents; p++) {
- SimpleMatrix KiMatrix = new SimpleMatrix(Klkni[p]);
- // KiMatrix.print(10,10);
- // Matrix dQdniMatrix =
- // (ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5)); // this
- // methods misses one part of ....
- // dQdniMatrix.print(10,10);
- // KiMatrix.print(10, 10);
- // miMatrix.getMatrix(assSites, assSites, 0, totalNumberOfAccociationSites -
- // 1).print(10, 10);
- // Matrix tempMatrix20 = miMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites -
- // 1).times(uMatrix).minus(ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5));
- //
- // ksiMatrix.transpose().times(KlkTMatrix.times(ksiMatrix)).times(-0.5);
- // System.out.println("dQdn ");
- // tempMatrix20.print(10, 10);
- SimpleMatrix tempMatrix4 = KiMatrix.mult(ksiMatrix);
- // udotTimesmiMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites - 1).print(10, 10);
- SimpleMatrix tempMatrix5 = udotTimesmiMatrix.extractVector(true, p).transpose().minus(tempMatrix4);
- // tempMki[0] = mki[p];
- // Matrix amatrix = new Matrix(croeneckerProduct(tempMki,
- // udotMatrix.getArray()));
- // System.out.println("aMatrix ");
- // amatrix.transpose().print(10, 10);
- // System.out.println("temp4 matrix");
- // tempMatrix4.print(10, 10);
- // Matrix tempMatrix5 = amatrix.minus(tempMatrix4);
- SimpleMatrix tempMatrix6 = applyHessianInv(tempMatrix5); // .scale(-1.0);
- // System.out.println("dXdni");
- // tempMatrix4.print(10, 10);
- // tempMatrix5.print(10, 10);
- // System.out.println("dXdn ");
- // tempMatrix6.print(10, 10);
+ DenseMatrix kiMatrix = new DenseMatrix(Klkni[p]);
+ DenseMatrix tempMatrix4 = multiply(kiMatrix, ksiMatrix);
+ DenseMatrix tempMatrix5 =
+ subtract(transpose(extractVector(udotTimesmiMatrix, true, p)), tempMatrix4);
+ DenseMatrix tempMatrix6 = applyHessianInv(tempMatrix5);
int temp2 = 0;
for (int compp = 0; compp < numberOfComponents; compp++) {
- for (int kk = 0; kk < getComponent(compp).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(compp)).setXsitedni(kk, p, -1.0 * tempMatrix6.get(temp2 + kk, 0));
- }
- temp2 += getComponent(compp).getNumberOfAssociationSites();
+ for (int kk = 0; kk < getComponent(compp).getNumberOfAssociationSites(); kk++) {
+ ((ComponentCPAInterface) getComponent(compp)).setXsitedni(kk, p,
+ -1.0 * tempMatrix6.unsafe_get(temp2 + kk, 0));
+ }
+ temp2 += getComponent(compp).getNumberOfAssociationSites();
}
- // assSites += getComponent(p).getNumberOfAssociationSites();
}
}
@@ -497,9 +478,9 @@ public void setMixingRule(MixingRuleTypeInterface mr) {
public void calcDelta() {
for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- deltaNog[i][j] = cpamix.calcDeltaNog(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i], moleculeNumber[j],
- this, getTemperature(), getPressure(), numberOfComponents);
- deltaNog[j][i] = deltaNog[i][j];
+ deltaNog[i][j] = cpamix.calcDeltaNog(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
+ moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
+ deltaNog[j][i] = deltaNog[i][j];
}
}
}
@@ -564,10 +545,10 @@ public double dFdTdT() {
*/
public double FCPA() {
/*
- * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0; for (int j = 0; j <
- * componentArray[i].getNumberOfAssociationSites(); j++) { double xai = ((ComponentSrkCPA)
- * componentArray[i]).getXsite()[j]; tot += (Math.log(xai) - 1.0 / 2.0 * xai + 1.0 / 2.0); } ans +=
- * componentArray[i].getNumberOfMolesInPhase() * tot; } return ans;
+ * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0;
+ * for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) { double xai =
+ * ((ComponentSrkCPA) componentArray[i]).getXsite()[j]; tot += (Math.log(xai) - 1.0 / 2.0 * xai
+ * + 1.0 / 2.0); } ans += componentArray[i].getNumberOfMolesInPhase() * tot; } return ans;
*/
return FCPA;
}
@@ -623,12 +604,12 @@ public double dFCPAdVdVdV() {
*/
public double dFCPAdT() {
/*
- * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0; for (int j = 0; j <
- * componentArray[i].getNumberOfAssociationSites(); j++) { double xai = ((ComponentSrkCPA)
- * componentArray[i]).getXsite()[j]; double xaidT = ((ComponentSrkCPA) componentArray[i]).getXsitedT()[j]; tot +=
- * 1.0 / xai * xaidT - 0.5 * xaidT; // - 1.0 / 2.0 * xai + 1.0 / 2.0); } ans +=
- * componentArray[i].getNumberOfMolesInPhase() * tot; } System.out.println("dFCPAdT1 " + ans + " dfcpa2 "
- * +dFCPAdT); return ans;
+ * double tot = 0.0; double ans = 0.0; for (int i = 0; i < numberOfComponents; i++) { tot = 0.0;
+ * for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) { double xai =
+ * ((ComponentSrkCPA) componentArray[i]).getXsite()[j]; double xaidT = ((ComponentSrkCPA)
+ * componentArray[i]).getXsitedT()[j]; tot += 1.0 / xai * xaidT - 0.5 * xaidT; // - 1.0 / 2.0 *
+ * xai + 1.0 / 2.0); } ans += componentArray[i].getNumberOfMolesInPhase() * tot; }
+ * System.out.println("dFCPAdT1 " + ans + " dfcpa2 " +dFCPAdT); return ans;
*/
return dFCPAdT;
}
@@ -656,17 +637,18 @@ public double dFCPAdTdV() {
// getTotalVolume()) * (1.0 - getTotalVolume() * getGcpav()) * hcpatotdT));
return dFCPAdTdV;
/*
- * if (totalNumberOfAccociationSites > 0) { return 1.0 / (2.0 * getTotalVolume()) * (1.0 - getTotalVolume() *
- * getGcpav()) * hcpatotdT; } else { return 0; }
+ * if (totalNumberOfAccociationSites > 0) { return 1.0 / (2.0 * getTotalVolume()) * (1.0 -
+ * getTotalVolume() * getGcpav()) * hcpatotdT; } else { return 0; }
*/
}
/** {@inheritDoc} */
@Override
public double molarVolume(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
+ throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
double BonV = pt == PhaseType.GAS ? pressure * getB() / (numberOfMolesInPhase * temperature * R)
- : 2.0 / (2.0 + temperature / getPseudoCriticalTemperature());
+ : 2.0 / (2.0 + temperature / getPseudoCriticalTemperature());
BonV = Math.max(1.0e-8, Math.min(1.0 - 1.0e-8, BonV));
double BonVold;
double BonV2;
@@ -687,8 +669,8 @@ public double molarVolume(double pressure, double temperature, double A, double
iterations++;
gcpa = calc_g();
if (gcpa < 0) {
- setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
- gcpa = calc_g();
+ setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
+ gcpa = calc_g();
}
// lngcpa =
@@ -698,69 +680,73 @@ public double molarVolume(double pressure, double temperature, double A, double
gcpavvv = calc_lngVVV();
if (totalNumberOfAccociationSites > 0) {
- solveX();
+ solveX();
}
initCPAMatrix(1);
BonV2 = BonV * BonV;
BonVold = BonV;
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
dh = 1.0 + Btemp / (BonV2) * (Btemp / numberOfMolesInPhase * dFdVdV());
dhh = -2.0 * Btemp / (BonV2 * BonV) * (Btemp / numberOfMolesInPhase * dFdVdV())
- - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
+ - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
d1 = -h / dh;
d2 = -dh / dhh;
// System.out.println("h " + h + " iter " + iterations + " " + d1 + " d2 " + d2
// + " d1 / d2 " + (d1 / d2));
if (Math.abs(d1 / d2) <= 1.0) {
- BonV += d1 * (1.0 + 0.5 * d1 / d2);
+ BonV += d1 * (1.0 + 0.5 * d1 / d2);
} else if (d1 / d2 < -1) {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
} else if (d1 > d2) {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- // BonV += d2;
- // double hnew = h + d2 * dh;
- // if (Math.abs(hnew) > Math.abs(h)) {
- // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
- // temperature * R);
- // }
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ // BonV += d2;
+ // double hnew = h + d2 * dh;
+ // if (Math.abs(hnew) > Math.abs(h)) {
+ // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
+ // temperature * R);
+ // }
} else {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
}
if (Math.abs((BonV - BonVold) / BonV) > 0.1) {
- BonV = BonVold + 0.1 * (BonV - BonVold);
+ BonV = BonVold + 0.1 * (BonV - BonVold);
}
if (BonV < 0) {
- if (iterations < 10) {
- // System.out.println(iterations + " BonV " + BonV);
- BonV = (BonVold + BonV) / 2.0;
- } else {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- }
+ if (iterations < 10) {
+ // System.out.println(iterations + " BonV " + BonV);
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ }
}
if (BonV >= 1.0) {
- if (iterations < 10) {
- BonV = (BonVold + BonV) / 2.0;
- } else {
- return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- }
+ if (iterations < 10) {
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ }
}
/*
- * if (BonV > 0.9999) { if (iterations < 10) { BonV = (BonVold + BonV) / 2.0; } else { // BonV =
- * calcRootVolFinder(pt); // BonV = molarVolumeChangePhase(pressure, temperature, A, B, pt); // BonV = 0.9999; //
- * BonV = pt == 1 ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature()) : pressure * getB() /
- * (numberOfMolesInPhase * temperature * R); } } else if (BonV < 0) { if (iterations < 10) { BonV =
- * Math.abs(BonVold + BonV) / 2.0; } else { // BonV = calcRootVolFinder(pt); // return
- * molarVolumeChangePhase(pressure, temperature, A, B, pt); // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- * getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase * temperature * R); } }
+ * if (BonV > 0.9999) { if (iterations < 10) { BonV = (BonVold + BonV) / 2.0; } else { // BonV
+ * = calcRootVolFinder(pt); // BonV = molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ * // BonV = 0.9999; // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ * getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase * temperature *
+ * R); } } else if (BonV < 0) { if (iterations < 10) { BonV = Math.abs(BonVold + BonV) / 2.0;
+ * } else { // BonV = calcRootVolFinder(pt); // return molarVolumeChangePhase(pressure,
+ * temperature, A, B, pt); // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ * getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase * temperature *
+ * R); } }
*/
setMolarVolume(1.0 / BonV * Btemp / numberOfMolesInPhase);
Z = pressure * getMolarVolume() / (R * temperature);
- } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12) && iterations < maxIterations);
+ } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12)
+ && iterations < maxIterations);
// System.out.println("h failed " + h + " Z" + Z + " iterations " + iterations +
// " BonV " + BonV);
@@ -819,13 +805,13 @@ private double[] calcdFdNtemp() {
// temp = ((ComponentSrkCPA) getComponent(k)).calc_lngi(this);
// temp2 = ((ComponentSrkCPA) getComponent(k)).calc_lngidV(this);
for (int i = 0; i < getComponent(k).getNumberOfAssociationSites(); i++) {
- tot2 -= 1.0 * ((ComponentUMRCPA) getComponent(k)).getXsitedV()[i];
- tot3 += (1.0 - ((ComponentUMRCPA) getComponent(k)).getXsite()[i]) * 1.0;
+ tot2 -= 1.0 * ((ComponentUMRCPA) getComponent(k)).getXsitedV()[i];
+ tot3 += (1.0 - ((ComponentUMRCPA) getComponent(k)).getXsite()[i]) * 1.0;
}
tot1 += 1.0 / 2.0 * tot2 * getComponent(k).getNumberOfMolesInPhase();
tot4 += 0.5 * getComponent(k).getNumberOfMolesInPhase() * tot3;
}
- return new double[] { -tot1, -tot4 };
+ return new double[] {-tot1, -tot4};
}
/**
@@ -840,21 +826,21 @@ public void calcXsitedV() {
}
/**
- * Applies the current Hessian inverse to a right-hand side without forming the inverse when a cached LU factorization
- * is available.
+ * Applies the current Hessian inverse to a right-hand side without forming the inverse when a
+ * cached LU factorization is available.
*
* @param rhs right-hand-side matrix with one row per association site
* @return solution of {@code hessianMatrix * x = rhs}
*/
- private SimpleMatrix applyHessianInv(SimpleMatrix rhs) {
+ private DenseMatrix applyHessianInv(DenseMatrix rhs) {
if (hessianInvers != null) {
- return hessianInvers.mult(rhs);
+ return multiply(hessianInvers, rhs);
}
if (hessianLU != null && hessianLUSize == totalNumberOfAccociationSites) {
- DMatrixRMaj rhsMat = rhs.getDDRM();
- DMatrixRMaj out = new DMatrixRMaj(rhsMat.numRows, rhsMat.numCols);
- hessianLU.solve(rhsMat, out);
- return SimpleMatrix.wrap(out);
+ DenseMatrix out = new DenseMatrix(rhs.numRows, rhs.numCols);
+ LinearAlgebraOps.solveLu(hessianLU, rhs.numRows, rhs.numCols, (i, j) -> rhs.unsafe_get(i, j),
+ (i, j, value) -> out.unsafe_set(i, j, value));
+ return out;
}
throw new IllegalStateException("Hessian factorization has not been initialized");
}
@@ -873,8 +859,8 @@ public boolean solveX() {
boolean solvedX = solveX2(15);
- DMatrixRMaj mVectorMat = mVector.getMatrix();
- DMatrixRMaj ksiMatrixMat = ksiMatrix.getMatrix();
+ DenseMatrix mVectorMat = mVector;
+ DenseMatrix ksiMatrixMat = ksiMatrix;
// ksiMatrix.print();
// second order method not working correctly and not used t the moment b ecause of numerical
@@ -883,12 +869,12 @@ public boolean solveX() {
int iter = 0;
for (int i = 0; i < numberOfComponents; i++) {
for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- mVectorMat.unsafe_set(temp + j, 0, componentArray[i].getNumberOfMolesInPhase());
+ mVectorMat.unsafe_set(temp + j, 0, componentArray[i].getNumberOfMolesInPhase());
}
temp += componentArray[i].getNumberOfAssociationSites();
}
- DMatrixRMaj mat1 = KlkMatrix.getMatrix();
+ DenseMatrix mat1 = KlkMatrix;
double Klk = 0.0;
double totvolume = getTotalVolume();
double tempVari;
@@ -896,14 +882,13 @@ public boolean solveX() {
for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
tempVari = mVectorMat.unsafe_get(i, 0);
for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- tempVarj = mVectorMat.unsafe_get(j, 0);
- Klk = tempVari * tempVarj / totvolume * delta[i][j];
- mat1.unsafe_set(i, j, Klk);
- mat1.unsafe_set(j, i, Klk);
+ tempVarj = mVectorMat.unsafe_get(j, 0);
+ Klk = tempVari * tempVarj / totvolume * delta[i][j];
+ mat1.unsafe_set(i, j, Klk);
+ mat1.unsafe_set(j, i, Klk);
}
}
boolean solved = true;
- // SimpleMatrix corrMatrix = null;
do {
solved = true;
iter++;
@@ -913,84 +898,80 @@ public boolean solveX() {
double temp1;
double temp2;
for (int i = 0; i < numberOfComponents; i++) {
- temp1 = componentArray[i].getNumberOfMolesInPhase();
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- ksi = ((ComponentUMRCPA) componentArray[i]).getXsite()[j];
- ksiMatrixMat.unsafe_set(temp + j, 0, ksi);
- // ksiMatrix.getMatrix().unsafe_set(temp + j, 0,
- // ksiMatrix.getMatrix().unsafe_get(temp + j, 0));
- tempVari = 1.0 / ksi - 1.0;
- udotMatrix.set(temp + j, 0, tempVari);
- udotTimesmMatrix.set(temp + j, 0, temp1 * tempVari);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
+ temp1 = componentArray[i].getNumberOfMolesInPhase();
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ ksi = ((ComponentUMRCPA) componentArray[i]).getXsite()[j];
+ ksiMatrixMat.unsafe_set(temp + j, 0, ksi);
+ // ksiMatrix.getMatrix().unsafe_set(temp + j, 0,
+ // ksiMatrix.getMatrix().unsafe_get(temp + j, 0));
+ tempVari = 1.0 / ksi - 1.0;
+ udotMatrix.unsafe_set(temp + j, 0, tempVari);
+ udotTimesmMatrix.unsafe_set(temp + j, 0, temp1 * tempVari);
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
}
int krondelt;
for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- temp1 = mVectorMat.unsafe_get(i, 0);
- temp2 = ksiMatrix.get(i, 0);
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- krondelt = 0;
- if (i == j) {
- krondelt = 1;
- }
- tempVari = -temp1 / (temp2 * temp2) * krondelt - mat1.unsafe_get(i, j);
- hessianMatrix.set(i, j, tempVari);
- hessianMatrix.set(j, i, tempVari);
- }
+ temp1 = mVectorMat.unsafe_get(i, 0);
+ temp2 = ksiMatrix.unsafe_get(i, 0);
+ for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
+ krondelt = 0;
+ if (i == j) {
+ krondelt = 1;
+ }
+ tempVari = -temp1 / (temp2 * temp2) * krondelt - mat1.unsafe_get(i, j);
+ hessianMatrix.unsafe_set(i, j, tempVari);
+ hessianMatrix.unsafe_set(j, i, tempVari);
+ }
}
- // ksiMatrix = new SimpleMatrix(ksi);
- // SimpleMatrix hessianMatrix = new SimpleMatrix(hessian);
int n = totalNumberOfAccociationSites;
if (hessianLU == null || hessianLUSize != n) {
- hessianLU = LinearSolverFactory_DDRM.lu(n);
- hessianLUinput = new DMatrixRMaj(n, n);
- hessianLUSize = n;
+ hessianLU = LU.PRIMITIVE.make(n, n);
+ hessianLUinput = new DenseMatrix(n, n);
+ hessianLUSize = n;
}
- System.arraycopy(hessianMatrix.getDDRM().getData(), 0, hessianLUinput.getData(), 0, n * n);
- if (!hessianLU.setA(hessianLUinput)) {
- return false;
+ System.arraycopy(hessianMatrix.getData(), 0, hessianLUinput.getData(), 0, n * n);
+ if (!LinearAlgebraOps.decomposeLu(hessianLU, n, (i, j) -> hessianLUinput.unsafe_get(i, j))) {
+ return false;
}
hessianInvers = null;
if (solvedX) {
- // System.out.println("solvedX ");
- return true;
+ // System.out.println("solvedX ");
+ return true;
}
- DMatrixRMaj mat2 = ksiMatrix.getMatrix();
- CommonOps_DDRM.mult(mat1, mat2, corr2Matrix);
- CommonOps_DDRM.subtract(udotTimesmMatrix.getDDRM(), corr2Matrix, corr3Matrix);
- hessianLU.solve(corr3Matrix, corr4Matrix);
- // SimpleMatrix gMatrix = udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix));
+ DenseMatrix mat2 = ksiMatrix;
+ LinearAlgebraOps.mult(mat1, mat2, corr2Matrix);
+ LinearAlgebraOps.subtract(udotTimesmMatrix, corr2Matrix, corr3Matrix);
+ LinearAlgebraOps.solveLu(hessianLU, corr3Matrix.numRows, corr3Matrix.numCols,
+ (i, j) -> corr3Matrix.unsafe_get(i, j),
+ (i, j, value) -> corr4Matrix.unsafe_set(i, j, value));
// corrMatrix =
// hessianInvers.mult(udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix)));
// //.scale(-1.0);
temp = 0;
- // System.out.println("print SimpleMatrix ...");
// corrMatrix.print(10, 10);
- // SimpleMatrix simp = new SimpleMatrix(corr4Matrix);
// System.out.println("print CommonOps ...");
- // simp.print(10,10);
double newX;
for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- newX = ksiMatrix.get(temp + j, 0) - corr4Matrix.unsafe_get((temp + j), 0);
- if (newX < 0) {
- newX = 1e-10;
- solved = false;
- }
- ((ComponentCPAInterface) componentArray[i]).setXsite(j, newX);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ newX = ksiMatrix.unsafe_get(temp + j, 0) - corr4Matrix.unsafe_get((temp + j), 0);
+ if (newX < 0) {
+ newX = 1e-10;
+ solved = false;
+ }
+ ((ComponentCPAInterface) componentArray[i]).setXsite(j, newX);
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
}
// System.out.println("corrmatrix error " );
- // System.out.println("error " + NormOps_DDRM.normF(corr4Matrix));
- } while ((NormOps_DDRM.normF(corr4Matrix) > 1e-12 || !solved) && iter < 100);
+ // System.out.println("error " + LinearAlgebraOps.normF(corr4Matrix));
+ } while ((LinearAlgebraOps.normF(corr4Matrix) > 1e-12 || !solved) && iter < 100);
// System.out.println("iter " + iter + " error " +
- // NormOps_DDRM.normF(corr4Matrix)); // corrMatrix.print(10, 10);
+ // LinearAlgebraOps.normF(corr4Matrix)); // corrMatrix.print(10, 10);
// ksiMatrix.print(10, 10);
return true;
}
@@ -1016,15 +997,15 @@ public boolean solveX2(int maxIter) {
iter++;
err = 0.0;
for (int i = 0; i < totalNumberOfAccociationSites; i++) {
- old = ((ComponentUMRCPA) componentArray[moleculeNumber[i]]).getXsite()[assSiteNumber[i]];
- neeval = 0.0;
- for (int j = 0; j < totalNumberOfAccociationSites; j++) {
- neeval += componentArray[moleculeNumber[j]].getNumberOfMolesInPhase() * delta[i][j]
- * ((ComponentUMRCPA) componentArray[moleculeNumber[j]]).getXsite()[assSiteNumber[j]];
- }
- neeval = 1.0 / (1.0 + 1.0 / totalVolume * neeval);
- ((ComponentUMRCPA) componentArray[moleculeNumber[i]]).setXsite(assSiteNumber[i], neeval);
- err += Math.abs((old - neeval) / neeval);
+ old = ((ComponentUMRCPA) componentArray[moleculeNumber[i]]).getXsite()[assSiteNumber[i]];
+ neeval = 0.0;
+ for (int j = 0; j < totalNumberOfAccociationSites; j++) {
+ neeval += componentArray[moleculeNumber[j]].getNumberOfMolesInPhase() * delta[i][j]
+ * ((ComponentUMRCPA) componentArray[moleculeNumber[j]]).getXsite()[assSiteNumber[j]];
+ }
+ neeval = 1.0 / (1.0 + 1.0 / totalVolume * neeval);
+ ((ComponentUMRCPA) componentArray[moleculeNumber[i]]).setXsite(assSiteNumber[i], neeval);
+ err += Math.abs((old - neeval) / neeval);
}
} while (Math.abs(err) > 1e-12 && iter < maxIter);
// System.out.println("iter " + iter);
@@ -1099,28 +1080,29 @@ public double calcRootVolFinder(PhaseType pt) {
int solveXAttempts = 0;
while (!solveX() && solveXAttempts < 50) {
- solveXAttempts++;
+ solveXAttempts++;
}
if (solveXAttempts >= 50) {
- // solveX failed to converge, skip this BonV value
- oldh = h;
- continue;
+ // solveX failed to converge, skip this BonV value
+ oldh = h;
+ continue;
}
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
if (Math.signum(h) * Math.signum(oldh) < 0 && i > 2) {
- if (solvedBonVlow < 1e-3) {
- solvedBonVlow = (BonV + BonVold) / 2.0;
- if (pt == PhaseType.GAS) {
- break;
- }
- } else {
- solvedBonVHigh = (BonV + BonVold) / 2.0;
- if (pt == PhaseType.LIQUID) {
- break;
- }
- }
+ if (solvedBonVlow < 1e-3) {
+ solvedBonVlow = (BonV + BonVold) / 2.0;
+ if (pt == PhaseType.GAS) {
+ break;
+ }
+ } else {
+ solvedBonVHigh = (BonV + BonVold) / 2.0;
+ if (pt == PhaseType.LIQUID) {
+ break;
+ }
+ }
}
solvedBonVHigh = (BonV + BonVold) / 2.0;
oldh = h;
@@ -1156,10 +1138,11 @@ public double calcRootVolFinder(PhaseType pt) {
* @throws neqsim.util.exception.IsNaNException if any.
* @throws neqsim.util.exception.TooManyIterationsException if any.
*/
- public double molarVolumeChangePhase(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
+ public double molarVolumeChangePhase(double pressure, double temperature, double A, double B,
+ PhaseType pt) throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
double BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
// double BonV = calcRootVolFinder(pt);
// double BonVInit = BonV;
if (BonV < 0) {
@@ -1190,8 +1173,8 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
iterations++;
gcpa = calc_g();
if (gcpa < 0) {
- setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
- gcpa = calc_g();
+ setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
+ gcpa = calc_g();
}
// lngcpa =
@@ -1205,48 +1188,49 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
initCPAMatrix(1);
double BonV2 = BonV * BonV;
BonVold = BonV;
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
dh = 1.0 + Btemp / (BonV2) * (Btemp / numberOfMolesInPhase * dFdVdV());
dhh = -2.0 * Btemp / (BonV2 * BonV) * (Btemp / numberOfMolesInPhase * dFdVdV())
- - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
+ - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
d1 = -h / dh;
d2 = -dh / dhh;
// System.out.println("d1" + d1 + " d2 " + d2 + " d1 / d2 " + (d1 / d2));
if (Math.abs(d1 / d2) <= 1.0) {
- BonV += d1 * (1.0 + 0.5 * d1 / d2);
+ BonV += d1 * (1.0 + 0.5 * d1 / d2);
} else if (d1 / d2 < -1) {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
} else if (d1 > d2) {
- BonV += d2;
- double hnew = h + d2 * dh;
- if (Math.abs(hnew) > Math.abs(h)) {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ BonV += d2;
+ double hnew = h + d2 * dh;
+ if (Math.abs(hnew) > Math.abs(h)) {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
} else {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
}
if (Math.abs((BonV - BonVold) / BonVold) > 0.1) {
- BonV = BonVold + 0.1 * (BonV - BonVold);
+ BonV = BonVold + 0.1 * (BonV - BonVold);
}
if (BonV > 1.1) {
- if (iterations < 3) {
- BonV = (BonVold + BonV) / 2.0;
- } else {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ if (iterations < 3) {
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
}
if (BonV < 0) {
- if (iterations < 3) {
- BonV = Math.abs(BonVold + BonV) / 2.0;
- } else {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ if (iterations < 3) {
+ BonV = Math.abs(BonVold + BonV) / 2.0;
+ } else {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
}
setMolarVolume(1.0 / BonV * Btemp / numberOfMolesInPhase);
@@ -1257,8 +1241,8 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
} while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10) && iterations < 100);
/*
- * if (Math.abs(h) > 1e-8) { if (pt == 0) { molarVolume(pressure, temperature, A, B, 1); } else {
- * molarVolume(pressure, temperature, A, B, 0); } return getMolarVolume(); }
+ * if (Math.abs(h) > 1e-8) { if (pt == 0) { molarVolume(pressure, temperature, A, B, 1); } else
+ * { molarVolume(pressure, temperature, A, B, 0); } return getMolarVolume(); }
*/
// System.out.println("Z" + Z + " iterations " + iterations + " BonV " + BonV);
// System.out.println("pressure " + Z*R*temperature/getMolarVolume());
@@ -1272,7 +1256,8 @@ public double molarVolumeChangePhase(double pressure, double temperature, double
// System.out.println("BonV: " + BonV + " "+" itert: " + iterations +" " +h + " " +dh + " B
// " + Btemp + " gv" + gV() + " fv " + fv() + " fvv" + fVV());
if (Double.isNaN(getMolarVolume())) {
- throw new neqsim.util.exception.IsNaNException(this, "molarVolumeChangePhase", "Molar volume");
+ throw new neqsim.util.exception.IsNaNException(this, "molarVolumeChangePhase",
+ "Molar volume");
// System.out.println("BonV: " + BonV + " "+" itert: " + iterations +" " +h + "
// " +dh + " B " + Btemp + " D " + Dtemp + " gv" + gV() + " fv " + fv() + " fvv"
// + fVV());
@@ -1347,11 +1332,11 @@ public double[][] croeneckerProduct(double[][] a, double[][] b) {
double[][] result = new double[aLength * bLength][(aCols) * (bCols)];
for (int z = 0; z < aLength; z++) {
for (int i = 0; i < aCols; i++) {
- for (int j = 0; j < bLength; j++) {
- for (int k = 0; k < bCols; k++) {
- result[j + (z * bLength)][k + (i * bCols)] = a[z][i] * b[j][k];
- }
- }
+ for (int j = 0; j < bLength; j++) {
+ for (int k = 0; k < bCols; k++) {
+ result[j + (z * bLength)][k + (i * bCols)] = a[z][i] * b[j][k];
+ }
+ }
}
}
return result;
@@ -1380,65 +1365,78 @@ public void setTotalNumberOfAccociationSites(int totalNumberOfAccociationSites)
* @param pt a int
* @param beta a double
*/
- public void initOld2(double totalNumberOfMoles, int numberOfComponents, int type, PhaseType pt, double beta) {
+ public void initOld2(double totalNumberOfMoles, int numberOfComponents, int type, PhaseType pt,
+ double beta) {
// type = 0 start init, type = 1 gi ny betingelser
if (type == 0) {
setTotalNumberOfAccociationSites(0);
selfAccociationScheme = new int[numberOfComponents][0][0];
crossAccociationScheme = new int[numberOfComponents][numberOfComponents][0][0];
for (int i = 0; i < numberOfComponents; i++) {
- if (componentArray[i].getNumberOfmoles() < 1e-50) {
- componentArray[i].setNumberOfAssociationSites(0);
- } else {
- componentArray[i].setNumberOfAssociationSites(componentArray[i].getOrginalNumberOfAssociationSites());
- setTotalNumberOfAccociationSites(
- getTotalNumberOfAccociationSites() + componentArray[i].getNumberOfAssociationSites());
- selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
- for (int j = 0; j < numberOfComponents; j++) {
- crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
- }
- }
+ if (componentArray[i].getNumberOfmoles() < 1e-50) {
+ componentArray[i].setNumberOfAssociationSites(0);
+ } else {
+ componentArray[i]
+ .setNumberOfAssociationSites(componentArray[i].getOrginalNumberOfAssociationSites());
+ setTotalNumberOfAccociationSites(
+ getTotalNumberOfAccociationSites() + componentArray[i].getNumberOfAssociationSites());
+ selfAccociationScheme[i] = cpaSelect.setAssociationScheme(i, this);
+ for (int j = 0; j < numberOfComponents; j++) {
+ crossAccociationScheme[i][j] = cpaSelect.setCrossAssociationScheme(i, j, this);
+ }
+ }
}
// had to remove if below - dont understand why.. Even
// if (getTotalNumberOfAccociationSites() != oldTotalNumberOfAccociationSites) {
- mVector = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- KlkMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkVVVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- hessianMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTTMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- KlkTVMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
- corr2Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr3Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- corr4Matrix = new DMatrixRMaj(getTotalNumberOfAccociationSites(), 1);
- Klkni = new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- ksiMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- uMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
- udotMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
+ mVector = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ KlkMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkVVVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ hessianMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTTMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ KlkTVMatrix =
+ new DenseMatrix(getTotalNumberOfAccociationSites(), getTotalNumberOfAccociationSites());
+ corr2Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr3Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ corr4Matrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ Klkni =
+ new double[numberOfComponents][getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ ksiMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ uMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
moleculeNumber = new int[getTotalNumberOfAccociationSites()];
assSiteNumber = new int[getTotalNumberOfAccociationSites()];
gvector = new double[getTotalNumberOfAccociationSites()][1];
- udotTimesmMatrix = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
+ udotTimesmMatrix = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
delta = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
deltaNog = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
deltadT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- deltadTdT = new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
- QMatksiksiksi = new SimpleMatrix(getTotalNumberOfAccociationSites(), 1);
+ deltadTdT =
+ new double[getTotalNumberOfAccociationSites()][getTotalNumberOfAccociationSites()];
+ QMatksiksiksi = new DenseMatrix(getTotalNumberOfAccociationSites(), 1);
// }
- udotTimesmiMatrix = new SimpleMatrix(getNumberOfComponents(), getTotalNumberOfAccociationSites());
+ udotTimesmiMatrix =
+ new DenseMatrix(getNumberOfComponents(), getTotalNumberOfAccociationSites());
oldTotalNumberOfAccociationSites = getTotalNumberOfAccociationSites();
int temp = 0;
for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- moleculeNumber[temp + j] = i;
- assSiteNumber[temp + j] = j;
- }
- temp += componentArray[i].getNumberOfAssociationSites();
+ for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
+ moleculeNumber[temp + j] = i;
+ assSiteNumber[temp + j] = j;
+ }
+ temp += componentArray[i].getNumberOfAssociationSites();
}
}
if (cpamix == null) {
@@ -1472,7 +1470,8 @@ public void initOld2(double totalNumberOfMoles, int numberOfComponents, int type
/** {@inheritDoc} */
@Override
public double molarVolume2(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
+ throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
Z = pt == PhaseType.LIQUID ? 1.0 : 1.0e-5;
setMolarVolume(Z * R * temperature / pressure);
// super.molarVolume(pressure,temperature, A, B, phase);
@@ -1495,7 +1494,8 @@ public double molarVolume2(double pressure, double temperature, double A, double
// System.out.println("pressure " + -R * temperature * dFdV + " " + R *
// temperature / getMolarVolume());
// -pressure;
- dErrdV = -R * temperature * dFdVdV - R * temperature * numberOfMolesInPhase / Math.pow(getVolume(), 2.0);
+ dErrdV = -R * temperature * dFdVdV
+ - R * temperature * numberOfMolesInPhase / Math.pow(getVolume(), 2.0);
// System.out.println("errdV " + dErrdV);
// System.out.println("err " + err);
@@ -1505,8 +1505,8 @@ public double molarVolume2(double pressure, double temperature, double A, double
Z = pressure * getMolarVolume() / (R * temperature);
if (Z < 0) {
- Z = 1e-6;
- setMolarVolume(Z * R * temperature / pressure);
+ Z = 1e-6;
+ setMolarVolume(Z * R * temperature / pressure);
}
// System.out.println("Z " + Z);
} while (Math.abs(err) > 1.0e-8 || iterations < 100);
@@ -1521,231 +1521,7 @@ public double molarVolume2(double pressure, double temperature, double A, double
* @param type a int
*/
public void initCPAMatrixOld(int type) {
- if (getTotalNumberOfAccociationSites() == 0) {
- FCPA = 0.0;
- dFCPAdTdV = 0.0;
- dFCPAdTdT = 0.0;
- dFCPAdT = 0;
- dFCPAdV = 0;
- dFCPAdVdV = 0.0;
- dFCPAdVdVdV = 0.0;
-
- return;
- }
-
- int temp = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- uMatrix.set(temp + j, 0, Math.log(ksiMatrix.get(temp + j, 0)) - ksiMatrix.get(temp + j, 0) + 1.0);
- gvector[temp + j][0] = mVector.get(temp + j, 0) * udotMatrix.get(temp + j, 0);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
- for (int i = 0; i < getNumberOfComponents(); i++) {
- for (int j = 0; j < getTotalNumberOfAccociationSites(); j++) {
- if (moleculeNumber[j] == i) {
- udotTimesmiMatrix.set(i, j, udotMatrix.get(j, 0));
- } else {
- udotTimesmiMatrix.set(i, j, 0.0);
- }
- }
- }
-
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- delta[i][j] = deltaNog[i][j] * getGcpa();
- delta[j][i] = delta[i][j];
- if (type > 1) {
- deltadT[i][j] = cpamix.calcDeltadT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i], moleculeNumber[j],
- this, getTemperature(), getPressure(), numberOfComponents);
- deltadT[j][i] = deltadT[i][j];
-
- deltadTdT[i][j] = cpamix.calcDeltadTdT(assSiteNumber[i], assSiteNumber[j], moleculeNumber[i],
- moleculeNumber[j], this, getTemperature(), getPressure(), numberOfComponents);
- deltadTdT[j][i] = deltadTdT[i][j];
- }
- }
- }
-
- double totalVolume = getTotalVolume();
- double totalVolume2 = totalVolume * totalVolume;
- double totalVolume3 = totalVolume2 * totalVolume;
- double gdv1 = getGcpav() - 1.0 / totalVolume;
- double gdv2 = gdv1 * gdv1;
- double gdv3 = gdv2 * gdv1;
- // double Klk = 0.0;
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- KlkVMatrix.set(i, j, KlkMatrix.get(i, j) * gdv1);
- KlkVMatrix.set(j, i, KlkVMatrix.get(i, j));
-
- KlkVVMatrix.set(i, j,
- KlkMatrix.get(i, j) * gdv2 + KlkMatrix.get(i, j) * (gcpavv + 1.0 / totalVolume / totalVolume));
- KlkVVMatrix.set(j, i, KlkVVMatrix.get(i, j));
-
- KlkVVVMatrix.set(i, j,
- KlkMatrix.get(i, j) * gdv3
- + 3.0 * KlkMatrix.get(i, j) * (getGcpav() - 1.0 / totalVolume) * (gcpavv + 1.0 / (totalVolume2))
- + KlkMatrix.get(i, j) * (gcpavvv - 2.0 / (totalVolume3)));
- KlkVVVMatrix.set(j, i, KlkVVVMatrix.get(i, j));
-
- if (type > 1) {
- double tempVar = deltadT[i][j] / delta[i][j];
- double tempVardT = deltadTdT[i][j] / delta[i][j]
- - (deltadT[i][j] * deltadT[i][j]) / (delta[i][j] * delta[i][j]);
-
- if (!Double.isNaN(tempVar)) {
- // KlkdT[i][j] = KlkMatrix.getMatrix().unsafe_get(i, j) * tempVar;
- // KlkdT[j][i] = KlkdT[i][j];
-
- KlkTMatrix.set(i, j, KlkMatrix.get(i, j) * tempVar);
- KlkTMatrix.set(j, i, KlkTMatrix.get(i, j));
-
- KlkTVMatrix.set(i, j, KlkMatrix.get(i, j) * tempVar * (gcpav - 1.0 / totalVolume));
- KlkTVMatrix.set(j, i, KlkTVMatrix.get(i, j));
-
- KlkTTMatrix.set(i, j, KlkMatrix.get(i, j) * (tempVar * tempVar + tempVardT));
- KlkTTMatrix.set(j, i, KlkTTMatrix.get(i, j));
- }
-
- if (type > 2) {
- for (int p = 0; p < numberOfComponents; p++) {
- double t1 = 0.0;
- double t2 = 0.0;
- if (moleculeNumber[i] == p) {
- t1 = 1.0 / mVector.get(i, 0);
- }
- if (moleculeNumber[j] == p) {
- t2 = 1.0 / mVector.get(j, 0);
- }
- Klkni[p][i][j] = KlkMatrix.get(i, j) * (t1 + t2 + ((ComponentUMRCPA) getComponent(p)).calc_lngi(this));
- Klkni[p][j][i] = Klkni[p][i][j];
- }
- }
- }
- }
- QMatksiksiksi.set(i, 0,
- 2.0 * mVector.get(i, 0) / (ksiMatrix.get(i, 0) * ksiMatrix.get(i, 0) * ksiMatrix.get(i, 0)));
- }
-
- SimpleMatrix ksiMatrixTranspose = ksiMatrix.transpose();
-
- // dXdV
- SimpleMatrix KlkVMatrixksi = KlkVMatrix.mult(ksiMatrix);
- SimpleMatrix XV = hessianInvers.mult(KlkVMatrixksi);
- SimpleMatrix XVtranspose = XV.transpose();
-
- SimpleMatrix QCPA = mVector.transpose().mult(uMatrix.minus(ksiMatrix.elementMult(udotMatrix).scale(0.5)));
- FCPA = QCPA.get(0, 0);
-
- SimpleMatrix tempMatrix = ksiMatrixTranspose.mult(KlkVMatrixksi).scale(-0.5);
- dFCPAdV = tempMatrix.get(0, 0);
- SimpleMatrix KlkVVMatrixTImesKsi = KlkVVMatrix.mult(ksiMatrix);
- SimpleMatrix tempMatrixVV = ksiMatrixTranspose.mult(KlkVVMatrixTImesKsi).scale(-0.5)
- .minus(KlkVMatrixksi.transpose().mult(XV));
- dFCPAdVdV = tempMatrixVV.get(0, 0);
-
- SimpleMatrix QVVV = ksiMatrixTranspose.mult(KlkVVVMatrix.mult(ksiMatrix)).scale(-0.5);
- SimpleMatrix QVVksi = KlkVVMatrixTImesKsi.scale(-1.0);
- SimpleMatrix QksiVksi = KlkVMatrix.scale(-1.0);
-
- SimpleMatrix mat1 = QVVksi.transpose().mult(XV).scale(3.0);
- SimpleMatrix mat2 = XVtranspose.mult(QksiVksi.mult(XV)).scale(3.0);
- SimpleMatrix mat4 = XVtranspose.mult(QMatksiksiksi.mult(XVtranspose)).mult(XV);
-
- SimpleMatrix dFCPAdVdVdVMatrix = QVVV.plus(mat1).plus(mat2).plus(mat2).plus(mat4);
- dFCPAdVdVdV = dFCPAdVdVdVMatrix.get(0, 0);
- temp = 0;
-
- if (type == 1) {
- return;
- }
- for (int p = 0; p < numberOfComponents; p++) {
- for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedV(kk, XV.get(temp + kk, 0));
- }
- temp += getComponent(p).getNumberOfAssociationSites();
- }
-
- // KlkTMatrix = new SimpleMatrix(KlkdT);
- SimpleMatrix KlkTMatrixTImesKsi = KlkTMatrix.mult(ksiMatrix);
- // dQdT
- SimpleMatrix tempMatrix2 = ksiMatrixTranspose.mult(KlkTMatrixTImesKsi).scale(-0.5);
- dFCPAdT = tempMatrix2.get(0, 0);
-
- // SimpleMatrix KlkTVMatrix = new SimpleMatrix(KlkdTdV);
- // SimpleMatrix tempMatrixTV =
- // ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5).minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- // dFCPAdTdV = tempMatrixTV.get(0, 0);
- // dXdT
- SimpleMatrix XT = hessianInvers.mult(KlkTMatrixTImesKsi);
- // dQdTdT
- SimpleMatrix tempMatrixTT = ksiMatrixTranspose.mult(KlkTTMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XT));
- dFCPAdTdT = tempMatrixTT.get(0, 0);
-
- SimpleMatrix tempMatrixTV = ksiMatrixTranspose.mult(KlkTVMatrix.mult(ksiMatrix)).scale(-0.5)
- .minus(KlkTMatrixTImesKsi.transpose().mult(XV));
- dFCPAdTdV = tempMatrixTV.get(0, 0);
-
- temp = 0;
- for (int p = 0; p < numberOfComponents; p++) {
- for (int kk = 0; kk < getComponent(p).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(p)).setXsitedT(kk, XT.get(temp + kk, 0));
- }
- temp += getComponent(p).getNumberOfAssociationSites();
- }
-
- if (type == 2) {
- return;
- }
-
- // int assSites = 0;
- // if(true) return;
- for (int p = 0; p < numberOfComponents; p++) {
- SimpleMatrix KiMatrix = new SimpleMatrix(Klkni[p]);
- // KiMatrix.print(10,10);
- // Matrix dQdniMatrix =
- // (ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5)); // this
- // methods misses one part of ....
- // dQdniMatrix.print(10,10);
- // KiMatrix.print(10, 10);
- // miMatrix.getMatrix(assSites, assSites, 0, totalNumberOfAccociationSites -
- // 1).print(10, 10);
- // Matrix tempMatrix20 = miMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites -
- // 1).times(uMatrix).minus(ksiMatrix.transpose().times(KiMatrix.times(ksiMatrix)).times(-0.5));
- //
- // ksiMatrix.transpose().times(KlkTMatrix.times(ksiMatrix)).times(-0.5);
- // System.out.println("dQdn ");
- // tempMatrix20.print(10, 10);
- SimpleMatrix tempMatrix4 = KiMatrix.mult(ksiMatrix);
- // udotTimesmiMatrix.getMatrix(assSites, assSites, 0,
- // totalNumberOfAccociationSites - 1).print(10, 10);
- SimpleMatrix tempMatrix5 = udotTimesmiMatrix.extractVector(true, p).transpose().minus(tempMatrix4);
- // tempMki[0] = mki[p];
- // Matrix amatrix = new Matrix(croeneckerProduct(tempMki,
- // udotMatrix.getArray()));
- // System.out.println("aMatrix ");
- // amatrix.transpose().print(10, 10);
- // System.out.println("temp4 matrix");
- // tempMatrix4.print(10, 10);
- // Matrix tempMatrix5 = amatrix.minus(tempMatrix4);
- SimpleMatrix tempMatrix6 = hessianInvers.mult(tempMatrix5); // .scale(-1.0);
- // System.out.println("dXdni");
- // tempMatrix4.print(10, 10);
- // tempMatrix5.print(10, 10);
- // System.out.println("dXdn ");
- // tempMatrix6.print(10, 10);
- int temp2 = 0;
- for (int compp = 0; compp < numberOfComponents; compp++) {
- for (int kk = 0; kk < getComponent(compp).getNumberOfAssociationSites(); kk++) {
- ((ComponentCPAInterface) getComponent(compp)).setXsitedni(kk, p, -1.0 * tempMatrix6.get(temp2 + kk, 0));
- }
- temp2 += getComponent(compp).getNumberOfAssociationSites();
- }
- // assSites += getComponent(p).getNumberOfAssociationSites();
- }
+ initCPAMatrix(type);
}
/**
@@ -1756,109 +1532,7 @@ public void initCPAMatrixOld(int type) {
* @return a boolean
*/
public boolean solveXOld() {
- if (getTotalNumberOfAccociationSites() == 0) {
- return true;
- }
-
- boolean solvedX = solveX2(5);
- if (solvedX) {
- // return true;
- }
-
- DMatrixRMaj mat1 = KlkMatrix.getMatrix();
- DMatrixRMaj mat2 = ksiMatrix.getMatrix();
- // second order method not working correctly and not used t the moment b ecause of numerical
- // stability
- int temp = 0;
- int iter = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- mVector.set(temp + j, 0, componentArray[i].getNumberOfMolesInPhase());
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
- double Klk = 0.0;
- double totalVolume = getTotalVolume();
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- Klk = mVector.get(i, 0) * mVector.get(j, 0) / totalVolume * delta[i][j];
- KlkMatrix.set(i, j, Klk);
- KlkMatrix.set(j, i, Klk);
- }
- }
- boolean solved = true;
- // SimpleMatrix corrMatrix = null;
- do {
- solved = true;
- iter++;
- temp = 0;
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- ksiMatrix.set(temp + j, 0, ((ComponentUMRCPA) componentArray[i]).getXsite()[j]);
- // ksiMatrix.getMatrix().unsafe_set(temp + j, 0,
- // ksiMatrix.getMatrix().unsafe_get(temp + j, 0));
- udotMatrix.set(temp + j, 0, 1.0 / ksiMatrix.get(temp + j, 0) - 1.0);
- udotTimesmMatrix.set(temp + j, 0, mVector.get(temp + j, 0) * udotMatrix.get(temp + j, 0));
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
-
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- for (int j = i; j < getTotalNumberOfAccociationSites(); j++) {
- int krondelt = 0;
- if (i == j) {
- krondelt = 1;
- }
- hessianMatrix.set(i, j,
- -mVector.get(i, 0) / (ksiMatrix.get(i, 0) * ksiMatrix.get(i, 0)) * krondelt - KlkMatrix.get(i, j));
- hessianMatrix.set(j, i, hessianMatrix.get(i, j));
- }
- }
-
- int n = totalNumberOfAccociationSites;
- if (hessianLU == null || hessianLUSize != n) {
- hessianLU = LinearSolverFactory_DDRM.lu(n);
- hessianLUinput = new DMatrixRMaj(n, n);
- hessianLUSize = n;
- }
- System.arraycopy(hessianMatrix.getDDRM().getData(), 0, hessianLUinput.getData(), 0, n * n);
- if (!hessianLU.setA(hessianLUinput)) {
- return false;
- }
- hessianInvers = null;
-
- CommonOps_DDRM.mult(mat1, mat2, corr2Matrix);
- CommonOps_DDRM.subtract(udotTimesmMatrix.getDDRM(), corr2Matrix, corr3Matrix);
- hessianLU.solve(corr3Matrix, corr4Matrix);
- // SimpleMatrix gMatrix = udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix));
- // corrMatrix =
- // hessianInvers.mult(udotTimesmMatrix.minus(KlkMatrix.mult(ksiMatrix)));
- // //.scale(-1.0);
- temp = 0;
- // System.out.println("print SimpleMatrix ...");
- // corrMatrix.print(10, 10);
- // SimpleMatrix simp = new SimpleMatrix(corr4Matrix);
- // System.out.println("print CommonOps ...");
- // simp.print(10,10);
- for (int i = 0; i < numberOfComponents; i++) {
- for (int j = 0; j < componentArray[i].getNumberOfAssociationSites(); j++) {
- double newX = ksiMatrix.get(temp + j, 0) - corr4Matrix.unsafe_get((temp + j), 0);
- if (newX < 0) {
- newX = 1e-10;
- solved = false;
- }
- ((ComponentCPAInterface) componentArray[i]).setXsite(j, newX);
- }
- temp += componentArray[i].getNumberOfAssociationSites();
- }
- // System.out.println("corrmatrix error " );
- // System.out.println("error " + corrMatrix.norm1());
- } while ((NormOps_DDRM.normF(corr4Matrix) > 1e-12 || !solved) && iter < 100);
-
- // System.out.println("iter " + iter + " error " + NormOps.normF(corr4Matrix));
- // // corrMatrix.print(10, 10);
- // ksiMatrix.print(10, 10);
- return true;
+ return solveX();
}
/**
@@ -1870,37 +1544,7 @@ public boolean solveXOld() {
* @return a boolean
*/
public boolean solveX2Old(int maxIter) {
- double err = .0;
- int iter = 0;
- // if (delta == null) {
- // initCPAMatrix(1);
- double old = 0.0;
- double neeval = 0.0;
- double totalVolume = getTotalVolume();
- // }
- do {
- iter++;
- err = 0.0;
- for (int i = 0; i < getTotalNumberOfAccociationSites(); i++) {
- old = ((ComponentUMRCPA) getComponent(moleculeNumber[i])).getXsite()[assSiteNumber[i]];
- neeval = 0;
- for (int j = 0; j < getTotalNumberOfAccociationSites(); j++) {
- neeval += getComponent(moleculeNumber[j]).getNumberOfMolesInPhase() * delta[i][j]
- * ((ComponentUMRCPA) getComponent(moleculeNumber[j])).getXsite()[assSiteNumber[j]];
- }
- neeval = 1.0 / (1.0 + 1.0 / totalVolume * neeval);
- ((ComponentCPAInterface) getComponent(moleculeNumber[i])).setXsite(assSiteNumber[i], neeval);
- err += Math.abs((old - neeval) / neeval);
- }
- } while (Math.abs(err) > 1e-10 && iter < maxIter);
- // System.out.println("iter " + iter);
- // if (Math.abs(err)
- // < 1e-12) {
- // return true;
- // } else {
- // System.out.println("did not solve for Xi in iterations: " + iter);
- // System.out.println("error: " + err);
- return false;
+ return solveX2(maxIter);
}
/**
@@ -1917,10 +1561,12 @@ public boolean solveX2Old(int maxIter) {
* @throws neqsim.util.exception.IsNaNException if any.
* @throws neqsim.util.exception.TooManyIterationsException if any.
*/
- public double molarVolumeOld(double pressure, double temperature, double A, double B, PhaseType pt)
- throws neqsim.util.exception.IsNaNException, neqsim.util.exception.TooManyIterationsException {
- double BonV = pt == PhaseType.LIQUID ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ public double molarVolumeOld(double pressure, double temperature, double A, double B,
+ PhaseType pt) throws neqsim.util.exception.IsNaNException,
+ neqsim.util.exception.TooManyIterationsException {
+ double BonV =
+ pt == PhaseType.LIQUID ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
// if (pressure > 1000) {
// BonV = 0.9999;
// }
@@ -1954,8 +1600,8 @@ public double molarVolumeOld(double pressure, double temperature, double A, doub
iterations++;
gcpa = calc_g();
if (gcpa < 0) {
- setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
- gcpa = calc_g();
+ setMolarVolume(1.0 / Btemp / numberOfMolesInPhase);
+ gcpa = calc_g();
}
// lngcpa =
@@ -1965,62 +1611,64 @@ public double molarVolumeOld(double pressure, double temperature, double A, doub
gcpavvv = calc_lngVVV();
if (getTotalNumberOfAccociationSites() > 0) {
- solveX();
+ solveX();
}
initCPAMatrix(1);
double BonV2 = BonV * BonV;
BonVold = BonV;
- h = BonV - Btemp / numberOfMolesInPhase * dFdV() - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
+ h = BonV - Btemp / numberOfMolesInPhase * dFdV()
+ - pressure * Btemp / (numberOfMolesInPhase * R * temperature);
dh = 1.0 + Btemp / (BonV2) * (Btemp / numberOfMolesInPhase * dFdVdV());
dhh = -2.0 * Btemp / (BonV2 * BonV) * (Btemp / numberOfMolesInPhase * dFdVdV())
- - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
+ - (Btemp * Btemp) / (BonV2 * BonV2) * (Btemp / numberOfMolesInPhase * dFdVdVdV());
d1 = -h / dh;
d2 = -dh / dhh;
// System.out.println("h " + h + " iter " + iterations + " " + d1 + " d2 " + d2
// + " d1 / d2 " + (d1 / d2));
if (Math.abs(d1 / d2) <= 1.0) {
- BonV += d1 * (1.0 + 0.5 * d1 / d2);
+ BonV += d1 * (1.0 + 0.5 * d1 / d2);
} else if (d1 / d2 < -1) {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
} else if (d1 > d2) {
- BonV += d2;
- double hnew = h + d2 * dh;
- if (Math.abs(hnew) > Math.abs(h)) {
- BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
- : pressure * getB() / (numberOfMolesInPhase * temperature * R);
- }
+ BonV += d2;
+ double hnew = h + d2 * dh;
+ if (Math.abs(hnew) > Math.abs(h)) {
+ BonV = pt == PhaseType.GAS ? 2.0 / (2.0 + temperature / getPseudoCriticalTemperature())
+ : pressure * getB() / (numberOfMolesInPhase * temperature * R);
+ }
} else {
- BonV += 0.5 * d1;
+ BonV += 0.5 * d1;
}
if (Math.abs((BonV - BonVold) / BonVold) > 0.1) {
- BonV = BonVold + 0.1 * (BonV - BonVold);
+ BonV = BonVold + 0.1 * (BonV - BonVold);
}
if (BonV > 0.9999) {
- if (iterations < 3) {
- BonV = (BonVold + BonV) / 2.0;
- } else {
- // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- // BonV = 0.9999;
- // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
- // temperature * R);
- }
+ if (iterations < 3) {
+ BonV = (BonVold + BonV) / 2.0;
+ } else {
+ // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ // BonV = 0.9999;
+ // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
+ // temperature * R);
+ }
} else if (BonV < 0) {
- if (iterations < 3) {
- BonV = Math.abs(BonVold + BonV) / 2.0;
- } else {
- // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
- // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
- // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
- // temperature * R);
- }
+ if (iterations < 3) {
+ BonV = Math.abs(BonVold + BonV) / 2.0;
+ } else {
+ // return molarVolumeChangePhase(pressure, temperature, A, B, pt);
+ // BonV = pt == 1 ? 2.0 / (2.0 + temperature /
+ // getPseudoCriticalTemperature()) : pressure * getB() / (numberOfMolesInPhase *
+ // temperature * R);
+ }
}
setMolarVolume(1.0 / BonV * Btemp / numberOfMolesInPhase);
Z = pressure * getMolarVolume() / (R * temperature);
- } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12) && iterations < 100);
+ } while ((Math.abs((BonV - BonVold) / BonV) > 1.0e-10 || Math.abs(h) > 1e-12)
+ && iterations < 100);
if (Math.abs(h) > 1e-12) {
// System.out.println("h failed " + "Z" + Z + " iterations " + iterations + "
@@ -2047,4 +1695,120 @@ public double molarVolumeOld(double pressure, double temperature, double A, doub
}
return getMolarVolume();
}
+
+ /**
+ * Returns the transpose of a dense matrix.
+ *
+ * @param matrix input matrix
+ * @return transposed matrix
+ */
+ private static DenseMatrix transpose(DenseMatrix matrix) {
+ DenseMatrix out = new DenseMatrix(matrix.numCols, matrix.numRows);
+ for (int i = 0; i < matrix.numRows; i++) {
+ for (int j = 0; j < matrix.numCols; j++) {
+ out.unsafe_set(j, i, matrix.unsafe_get(i, j));
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Multiplies two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return matrix product
+ */
+ private static DenseMatrix multiply(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, right.numCols);
+ LinearAlgebraOps.mult(left, right, out);
+ return out;
+ }
+
+ /**
+ * Adds two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return sum matrix
+ */
+ private static DenseMatrix add(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, left.numCols);
+ for (int i = 0; i < left.numRows; i++) {
+ for (int j = 0; j < left.numCols; j++) {
+ out.unsafe_set(i, j, left.unsafe_get(i, j) + right.unsafe_get(i, j));
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Subtracts two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return difference matrix
+ */
+ private static DenseMatrix subtract(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, left.numCols);
+ LinearAlgebraOps.subtract(left, right, out);
+ return out;
+ }
+
+ /**
+ * Scales all elements in a dense matrix.
+ *
+ * @param matrix input matrix
+ * @param factor scale factor
+ * @return scaled matrix
+ */
+ private static DenseMatrix scale(DenseMatrix matrix, double factor) {
+ DenseMatrix out = new DenseMatrix(matrix.numRows, matrix.numCols);
+ for (int i = 0; i < matrix.numRows; i++) {
+ for (int j = 0; j < matrix.numCols; j++) {
+ out.unsafe_set(i, j, matrix.unsafe_get(i, j) * factor);
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Element-wise multiplication of two dense matrices.
+ *
+ * @param left left matrix
+ * @param right right matrix
+ * @return Hadamard product
+ */
+ private static DenseMatrix elementMult(DenseMatrix left, DenseMatrix right) {
+ DenseMatrix out = new DenseMatrix(left.numRows, left.numCols);
+ for (int i = 0; i < left.numRows; i++) {
+ for (int j = 0; j < left.numCols; j++) {
+ out.unsafe_set(i, j, left.unsafe_get(i, j) * right.unsafe_get(i, j));
+ }
+ }
+ return out;
+ }
+
+ /**
+ * Extracts one row or column as a matrix view copy.
+ *
+ * @param matrix source matrix
+ * @param extractRow true to extract a row, false to extract a column
+ * @param index row/column index
+ * @return extracted row/column matrix
+ */
+ private static DenseMatrix extractVector(DenseMatrix matrix, boolean extractRow, int index) {
+ if (extractRow) {
+ DenseMatrix row = new DenseMatrix(1, matrix.numCols);
+ for (int j = 0; j < matrix.numCols; j++) {
+ row.unsafe_set(0, j, matrix.unsafe_get(index, j));
+ }
+ return row;
+ }
+ DenseMatrix col = new DenseMatrix(matrix.numRows, 1);
+ for (int i = 0; i < matrix.numRows; i++) {
+ col.unsafe_set(i, 0, matrix.unsafe_get(i, index));
+ }
+ return col;
+ }
}
diff --git a/src/main/java/neqsim/thermodynamicoperations/flashops/CriticalPointFlash.java b/src/main/java/neqsim/thermodynamicoperations/flashops/CriticalPointFlash.java
index 2699167844..c114efe3a2 100644
--- a/src/main/java/neqsim/thermodynamicoperations/flashops/CriticalPointFlash.java
+++ b/src/main/java/neqsim/thermodynamicoperations/flashops/CriticalPointFlash.java
@@ -2,16 +2,17 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.ejml.data.Complex_F64;
-import org.ejml.data.DMatrixRMaj;
-import org.ejml.dense.row.factory.DecompositionFactory_DDRM;
-import org.ejml.interfaces.decomposition.EigenDecomposition_F64;
-import org.ejml.simple.SimpleMatrix;
+import org.ojalgo.array.Array1D;
+import org.ojalgo.matrix.decomposition.Eigenvalue;
+import org.ojalgo.matrix.store.MatrixStore;
+import org.ojalgo.matrix.store.Primitive64Store;
+import org.ojalgo.scalar.ComplexNumber;
import neqsim.thermo.system.SystemInterface;
+import neqsim.util.math.LinearAlgebraOps;
/**
*
- * CriticalPointFlash class.
+ * Critical-point flash calculation using the Heidemann and Khalil criterion.
*
- * Returns the eigenvector associated with the eigenvalue of {@link #Mmatrix} that is closest to zero.
+ * Returns the eigenvector associated with the eigenvalue of {@link #Mmatrix} that is closest to
+ * zero.
*
- * At a mixture critical point the smallest eigenvalue of the Q matrix vanishes, and the corresponding eigenvector
- * defines the direction of the critical composition perturbation (Heidemann & Khalil, 1980). Selecting the
- * eigenvector by smallest eigenvalue magnitude — rather than by a fixed index — ensures the correct
- * critical direction is used and avoids returning a null (complex) eigenvector.
+ * At a mixture critical point the smallest eigenvalue of the Q matrix vanishes, and the
+ * corresponding eigenvector defines the direction of the critical composition perturbation
+ * (Heidemann & Khalil, 1980). Selecting the eigenvector by smallest eigenvalue magnitude
+ * — rather than by a fixed index — ensures the correct critical direction is used and
+ * avoids returning a null (complex) eigenvector.
*
- * Variables: u[i] = beta * y[i] (Michelsen 1982b). Residual: g[i] = ln(f_gas_i) - ln(f_liq_i). Jacobian: Hessian of the
- * reduced Gibbs energy Q(u). Uses Armijo backtracking line search on Q for global convergence (Michelsen & Mollerup
- * 2007, Ch. 12).
+ * Variables: u[i] = beta * y[i] (Michelsen 1982b). Residual: g[i] = ln(f_gas_i) - ln(f_liq_i).
+ * Jacobian: Hessian of the reduced Gibbs energy Q(u). Uses Armijo backtracking line search on Q for
+ * global convergence (Michelsen & Mollerup 2007, Ch. 12).
*
* Performance optimizations vs standard implementation:
*
@@ -75,19 +78,21 @@ public class SysNewtonRhapsonTPflash implements java.io.Serializable {
* @param numberOfPhases a int
* @param numberOfComponents a int
*/
- public SysNewtonRhapsonTPflash(SystemInterface system, int numberOfPhases, int numberOfComponents) {
+ public SysNewtonRhapsonTPflash(SystemInterface system, int numberOfPhases,
+ int numberOfComponents) {
this.system = system;
this.numberOfComponents = numberOfComponents;
neq = numberOfComponents;
- // Pre-allocate EJML matrices
- jacMatrix = new DMatrixRMaj(neq, neq);
- fvecVector = new DMatrixRMaj(neq, 1);
- dxVector = new DMatrixRMaj(neq, 1);
- jacWork = new DMatrixRMaj(neq, neq);
+ // Pre-allocate matrix and vector workspaces
+ jacMatrix = Primitive64Store.FACTORY.make(neq, neq);
+ fvecVector = new double[neq];
+ rhsVector = Primitive64Store.FACTORY.make(neq, 1);
+ dxVector = new double[neq];
+ jacWork = Primitive64Store.FACTORY.make(neq, neq);
uVector = new double[neq];
uTrialVector = new double[neq];
- linearSolver = LinearSolverFactory_DDRM.lu(neq);
+ linearSolver = LU.PRIMITIVE.make(neq, neq);
setu();
z = new double[numberOfComponents];
@@ -97,8 +102,8 @@ public SysNewtonRhapsonTPflash(SystemInterface system, int numberOfPhases, int n
}
/**
- * Number of components this solver was sized for. Used by the caller to detect when the cached instance is stale
- * (e.g. after solid precipitation removed a component).
+ * Number of components this solver was sized for. Used by the caller to detect when the cached
+ * instance is stale (e.g. after solid precipitation removed a component).
*
* @return number of components
*/
@@ -107,8 +112,9 @@ public int getNumberOfComponents() {
}
/**
- * Re-bind this solver to a (possibly updated) system reference and refresh the cached feed composition, without
- * re-allocating the EJML matrices. The component count must match — call {@link #getNumberOfComponents()} first.
+ * Re-bind this solver to a (possibly updated) system reference and refresh the cached feed
+ * composition, without re-allocating the matrix workspaces. The component count must match — call
+ * {@link #getNumberOfComponents()} first.
*
* @param system system to solve
*/
@@ -129,10 +135,11 @@ public void setSystem(SystemInterface system) {
*/
public void setfvec() {
for (int i = 0; i < numberOfComponents; i++) {
- fvecVector.set(i, 0, Math
- .log(system.getPhase(0).getComponent(i).getFugacityCoefficient() * system.getPhase(0).getComponent(i).getx())
- - Math.log(
- system.getPhase(1).getComponent(i).getFugacityCoefficient() * system.getPhase(1).getComponent(i).getx()));
+ fvecVector[i] = Math
+ .log(system.getPhase(0).getComponent(i).getFugacityCoefficient()
+ * system.getPhase(0).getComponent(i).getx())
+ - Math.log(system.getPhase(1).getComponent(i).getFugacityCoefficient()
+ * system.getPhase(1).getComponent(i).getx());
}
}
@@ -150,10 +157,11 @@ public void setJac() {
double invYi = 1.0 / system.getPhase(0).getComponent(i).getx();
double invXi = 1.0 / system.getPhase(1).getComponent(i).getx();
for (int j = 0; j < numberOfComponents; j++) {
- dij = i == j ? 1.0 : 0.0;
- tempJ = invBeta * (dij * invYi - 1.0 + system.getPhase(0).getComponent(i).getdfugdx(j))
- + invOneMinusBeta * (dij * invXi - 1.0 + system.getPhase(1).getComponent(i).getdfugdx(j));
- jacMatrix.set(i, j, tempJ);
+ dij = i == j ? 1.0 : 0.0;
+ tempJ = invBeta * (dij * invYi - 1.0 + system.getPhase(0).getComponent(i).getdfugdx(j))
+ + invOneMinusBeta
+ * (dij * invXi - 1.0 + system.getPhase(1).getComponent(i).getdfugdx(j));
+ jacMatrix.set(i, j, tempJ);
}
}
}
@@ -184,8 +192,8 @@ public void init() {
for (int i = 0; i < numberOfComponents; i++) {
system.getPhase(0).getComponent(i).setx(uVector[i] / betaSum);
system.getPhase(1).getComponent(i).setx((z[i] - uVector[i]) / (1.0 - betaSum));
- system.getPhase(0).getComponent(i)
- .setK(system.getPhase(0).getComponent(i).getx() / system.getPhase(1).getComponent(i).getx());
+ system.getPhase(0).getComponent(i).setK(
+ system.getPhase(0).getComponent(i).getx() / system.getPhase(1).getComponent(i).getx());
system.getPhase(1).getComponent(i).setK(system.getPhase(0).getComponent(i).getK());
}
@@ -194,8 +202,8 @@ public void init() {
}
/**
- * Compute Michelsen's reduced Gibbs energy Q(u). Q = sum[u_i * ln(y_i * phi_gas_i) + (z_i - u_i) * ln(x_i *
- * phi_liq_i)]. Gradient of Q equals fvec; Hessian of Q equals Jac.
+ * Compute Michelsen's reduced Gibbs energy Q(u). Q = sum[u_i * ln(y_i * phi_gas_i) + (z_i - u_i)
+ * * ln(x_i * phi_liq_i)]. Gradient of Q equals fvec; Hessian of Q equals Jac.
*
* @return Q value
*/
@@ -221,7 +229,7 @@ private boolean isFeasible(double[] uTrial) {
double betaTrial = 0.0;
for (int i = 0; i < numberOfComponents; i++) {
if (uTrial[i] < 1e-15 || uTrial[i] > z[i] - 1e-15) {
- return false;
+ return false;
}
betaTrial += uTrial[i];
}
@@ -229,8 +237,8 @@ private boolean isFeasible(double[] uTrial) {
}
/**
- * Set compositions from trial u vector and compute fugacities only (init level 1). Used for line search Q evaluation
- * at trial points.
+ * Set compositions from trial u vector and compute fugacities only (init level 1). Used for line
+ * search Q evaluation at trial points.
*
* @param uTrial trial u vector
*/
@@ -249,11 +257,11 @@ private void setTrialAndComputeFugacities(double[] uTrial) {
}
/**
- * Lazily initializes the EJML solver after deserialization.
+ * Lazily initializes the ojAlgo LU solver after deserialization.
*/
private void ensureSolverInitialized() {
if (linearSolver == null) {
- linearSolver = LinearSolverFactory_DDRM.lu(neq);
+ linearSolver = LU.PRIMITIVE.make(neq, neq);
}
if (uTrialVector == null || uTrialVector.length != numberOfComponents) {
uTrialVector = new double[numberOfComponents];
@@ -286,15 +294,24 @@ public double solve() {
jacMatrix.set(i, i, jacMatrix.get(i, i) + lambda);
}
- // Solve J * dx = fvec using EJML (pre-allocated work buffer)
- jacWork.setTo(jacMatrix);
- linearSolver.setA(jacWork);
- linearSolver.solve(fvecVector, dxVector);
+ // Solve J * dx = fvec using ojAlgo LU (pre-allocated work buffer)
+ for (int i = 0; i < neq; i++) {
+ for (int j = 0; j < neq; j++) {
+ jacWork.set(i, j, jacMatrix.get(i, j));
+ }
+ }
+ for (int i = 0; i < neq; i++) {
+ rhsVector.set(i, 0, fvecVector[i]);
+ }
+
+ if (!LinearAlgebraOps.solveLinearSystem(jacWork, rhsVector, dxVector, linearSolver)) {
+ throw new IllegalStateException("Failed to decompose Jacobian matrix in Newton solver");
+ }
// Directional derivative: slope = fvec^T * dx = grad(Q)^T * (Jac^-1 * grad(Q)) > 0
double slope = 0.0;
for (int i = 0; i < neq; i++) {
- slope += fvecVector.get(i, 0) * dxVector.get(i, 0);
+ slope += fvecVector[i] * dxVector[i];
}
// Armijo backtracking line search on Michelsen Q function
@@ -303,20 +320,20 @@ public double solve() {
int maxBacktrack = 8;
for (int bt = 0; bt < maxBacktrack; bt++) {
for (int i = 0; i < numberOfComponents; i++) {
- uTrialVector[i] = uVector[i] - alpha * dxVector.get(i, 0);
+ uTrialVector[i] = uVector[i] - alpha * dxVector[i];
}
if (isFeasible(uTrialVector)) {
- try {
- setTrialAndComputeFugacities(uTrialVector);
- double qTrial = computeQ();
- // Armijo condition: Q_trial <= Q_current - c1 * alpha * slope
- if (qTrial <= qCurrent - c1 * alpha * slope) {
- break;
- }
- } catch (Exception ex) {
- // Cubic solver failed at trial point — try shorter step
- }
+ try {
+ setTrialAndComputeFugacities(uTrialVector);
+ double qTrial = computeQ();
+ // Armijo condition: Q_trial <= Q_current - c1 * alpha * slope
+ if (qTrial <= qCurrent - c1 * alpha * slope) {
+ break;
+ }
+ } catch (Exception ex) {
+ // Cubic solver failed at trial point — try shorter step
+ }
}
alpha *= 0.5;
@@ -326,7 +343,7 @@ public double solve() {
double stepNormSq = 0.0;
double uNormSq = 0.0;
for (int i = 0; i < numberOfComponents; i++) {
- double step = alpha * dxVector.get(i, 0);
+ double step = alpha * dxVector[i];
uVector[i] -= step;
stepNormSq += step * step;
uNormSq += uVector[i] * uVector[i];
diff --git a/src/main/java/neqsim/thermodynamicoperations/flashops/TPmultiflash.java b/src/main/java/neqsim/thermodynamicoperations/flashops/TPmultiflash.java
index 29ca2a74cb..fe532321cb 100644
--- a/src/main/java/neqsim/thermodynamicoperations/flashops/TPmultiflash.java
+++ b/src/main/java/neqsim/thermodynamicoperations/flashops/TPmultiflash.java
@@ -10,12 +10,10 @@
import java.util.ArrayList;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.ejml.data.DMatrixRMaj;
-import org.ejml.dense.row.CommonOps_DDRM;
-import org.ejml.simple.SimpleMatrix;
import neqsim.thermo.component.ComponentInterface;
import neqsim.thermo.phase.PhaseType;
import neqsim.thermo.system.SystemInterface;
+import neqsim.util.math.LinearAlgebraOps;
/**
*
@@ -81,8 +79,7 @@ public TPmultiflash(SystemInterface system, boolean checkForSolids) {
* calcMultiPhaseBeta.
*
@@ -106,35 +103,35 @@ public void setXY() {
boolean isAqueous = system.getPhase(k).getType() == PhaseType.AQUEOUS;
for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
- if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
- // Check for ions - ions can only exist in aqueous phases
- // This check must happen regardless of isChemicalSystem() status
- if (system.getPhase(0).getComponent(i).getIonicCharge() != 0
- || system.getPhase(0).getComponent(i).isIsIon()) {
- // Ions only exist in aqueous phases, near-zero in gas/oil
- if (isAqueous) {
- // In aqueous phase, calculate ion x from moles
- double totalMoles = system.getPhase(k).getNumberOfMolesInPhase();
- if (totalMoles > 1e-100) {
- system.getPhase(k).getComponent(i)
- .setx(system.getPhase(k).getComponent(i).getNumberOfmoles() / totalMoles);
- } else {
- system.getPhase(k).getComponent(i).setx(system.getPhase(0).getComponent(i).getz());
- }
- } else {
- // No ions in gas or oil phases
- system.getPhase(k).getComponent(i).setx(1e-50);
- }
- } else {
- // Non-ionic components: normal flash calculation
- double newX = system.getPhase(0).getComponent(i).getz() / Erow[i]
- / system.getPhase(k).getComponent(i).getFugacityCoefficient();
- if (!Double.isFinite(newX) || newX <= 0.0) {
- newX = Math.max(system.getPhase(0).getComponent(i).getz(), 1.0e-30);
- }
- system.getPhase(k).getComponent(i).setx(newX);
- }
- }
+ if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
+ // Check for ions - ions can only exist in aqueous phases
+ // This check must happen regardless of isChemicalSystem() status
+ if (system.getPhase(0).getComponent(i).getIonicCharge() != 0
+ || system.getPhase(0).getComponent(i).isIsIon()) {
+ // Ions only exist in aqueous phases, near-zero in gas/oil
+ if (isAqueous) {
+ // In aqueous phase, calculate ion x from moles
+ double totalMoles = system.getPhase(k).getNumberOfMolesInPhase();
+ if (totalMoles > 1e-100) {
+ system.getPhase(k).getComponent(i)
+ .setx(system.getPhase(k).getComponent(i).getNumberOfmoles() / totalMoles);
+ } else {
+ system.getPhase(k).getComponent(i).setx(system.getPhase(0).getComponent(i).getz());
+ }
+ } else {
+ // No ions in gas or oil phases
+ system.getPhase(k).getComponent(i).setx(1e-50);
+ }
+ } else {
+ // Non-ionic components: normal flash calculation
+ double newX = system.getPhase(0).getComponent(i).getz() / Erow[i]
+ / system.getPhase(k).getComponent(i).getFugacityCoefficient();
+ if (!Double.isFinite(newX) || newX <= 0.0) {
+ newX = Math.max(system.getPhase(0).getComponent(i).getz(), 1.0e-30);
+ }
+ system.getPhase(k).getComponent(i).setx(newX);
+ }
+ }
}
system.getPhase(k).normalize();
@@ -151,14 +148,15 @@ public void calcE() {
for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
Erow[i] = 0.0;
for (int k = 0; k < system.getNumberOfPhases(); k++) {
- Erow[i] += system.getPhase(k).getBeta() / system.getPhase(k).getComponent(i).getFugacityCoefficient();
+ Erow[i] += system.getPhase(k).getBeta()
+ / system.getPhase(k).getComponent(i).getFugacityCoefficient();
}
if (Erow[i] < 1e-100) {
- Erow[i] = 1e-100;
+ Erow[i] = 1e-100;
}
if (Double.isNaN(Erow[i])) {
- logger.error("Erow is NaN for component " + system.getPhase(0).getComponent(i).getName());
- Erow[i] = 1e-100;
+ logger.error("Erow is NaN for component " + system.getPhase(0).getComponent(i).getName());
+ Erow[i] = 1e-100;
}
}
}
@@ -189,30 +187,31 @@ public double calcQ() {
for (int k = 0; k < system.getNumberOfPhases(); k++) {
dQdbeta[k][0] = 1.0;
for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
- dQdbeta[k][0] -= multTerm[i] / system.getPhase(k).getComponent(i).getFugacityCoefficient();
+ dQdbeta[k][0] -= multTerm[i] / system.getPhase(k).getComponent(i).getFugacityCoefficient();
}
}
for (int i = 0; i < system.getNumberOfPhases(); i++) {
for (int j = 0; j < system.getNumberOfPhases(); j++) {
- Qmatrix[i][j] = 0.0;
- for (int k = 0; k < system.getPhase(0).getNumberOfComponents(); k++) {
- Qmatrix[i][j] += multTerm2[k] / (system.getPhase(j).getComponent(k).getFugacityCoefficient()
- * system.getPhase(i).getComponent(k).getFugacityCoefficient());
- }
- if (i == j) {
- double reg = 1.0e-3;
- if (shouldApplyEnhancedMultiPhaseCheck()) {
- double absDiag = Math.abs(Qmatrix[i][j]);
- double beta = Math.abs(system.getPhase(i).getBeta());
- // Keep strong regularization for near-singular small-beta phases,
- // but reduce bias in well-conditioned enhanced-mode cases.
- if (beta > 1.0e-8 && absDiag > 1.0e-8) {
- reg = Math.max(1.0e-12, absDiag * 1.0e-8);
- }
- }
- Qmatrix[i][j] += reg;
- }
+ Qmatrix[i][j] = 0.0;
+ for (int k = 0; k < system.getPhase(0).getNumberOfComponents(); k++) {
+ Qmatrix[i][j] +=
+ multTerm2[k] / (system.getPhase(j).getComponent(k).getFugacityCoefficient()
+ * system.getPhase(i).getComponent(k).getFugacityCoefficient());
+ }
+ if (i == j) {
+ double reg = 1.0e-3;
+ if (shouldApplyEnhancedMultiPhaseCheck()) {
+ double absDiag = Math.abs(Qmatrix[i][j]);
+ double beta = Math.abs(system.getPhase(i).getBeta());
+ // Keep strong regularization for near-singular small-beta phases,
+ // but reduce bias in well-conditioned enhanced-mode cases.
+ if (beta > 1.0e-8 && absDiag > 1.0e-8) {
+ reg = Math.max(1.0e-12, absDiag * 1.0e-8);
+ }
+ }
+ Qmatrix[i][j] += reg;
+ }
}
}
return Q;
@@ -226,67 +225,75 @@ public double calcQ() {
* @return a double
*/
public double solveBeta() {
- SimpleMatrix betaMatrix = new SimpleMatrix(1, system.getNumberOfPhases());
- SimpleMatrix ans = null;
+ double[] betaVector = new double[system.getNumberOfPhases()];
+ double[] ans = new double[system.getNumberOfPhases()];
double err = 1.0;
double gradResidual = 1.0;
int iter = 1;
do {
iter++;
for (int k = 0; k < system.getNumberOfPhases(); k++) {
- betaMatrix.set(0, k, system.getPhase(k).getBeta());
+ betaVector[k] = system.getPhase(k).getBeta();
}
calcQ();
- SimpleMatrix dQM = new SimpleMatrix(dQdbeta);
- gradResidual = dQM.normF();
- SimpleMatrix dQdBM = new SimpleMatrix(Qmatrix);
+ gradResidual = LinearAlgebraOps.columnNorm(dQdbeta);
+ double[] rhs = new double[system.getNumberOfPhases()];
+ for (int k = 0; k < system.getNumberOfPhases(); k++) {
+ rhs[k] = dQdbeta[k][0];
+ }
try {
- ans = dQdBM.solve(dQM).transpose();
+ if (!LinearAlgebraOps.solveLinearSystem(Qmatrix, rhs, ans)) {
+ throw new RuntimeException("LU decomposition failed in solveBeta");
+ }
} catch (Exception ex) {
- if (shouldApplyEnhancedMultiPhaseCheck()) {
- for (int kk = 0; kk < system.getNumberOfPhases(); kk++) {
- Qmatrix[kk][kk] += 1.0e-2;
- }
- dQdBM = new SimpleMatrix(Qmatrix);
- try {
- ans = dQdBM.solve(dQM).transpose();
- } catch (Exception ex2) {
- logger.error(ex2.getMessage());
- break;
- }
- } else {
- logger.error(ex.getMessage());
- break;
- }
- }
-
- betaMatrix = betaMatrix.minus(ans.scale(iter / (iter + 3.0)));
+ if (shouldApplyEnhancedMultiPhaseCheck()) {
+ for (int kk = 0; kk < system.getNumberOfPhases(); kk++) {
+ Qmatrix[kk][kk] += 1.0e-2;
+ }
+ try {
+ if (!LinearAlgebraOps.solveLinearSystem(Qmatrix, rhs, ans)) {
+ throw new RuntimeException("Regularized LU decomposition failed in solveBeta");
+ }
+ } catch (Exception ex2) {
+ logger.error(ex2.getMessage());
+ break;
+ }
+ } else {
+ logger.error(ex.getMessage());
+ break;
+ }
+ }
+
+ double damping = iter / (iter + 3.0);
+ for (int k = 0; k < betaVector.length; k++) {
+ betaVector[k] -= ans[k] * damping;
+ }
removePhase = false;
for (int k = 0; k < system.getNumberOfPhases(); k++) {
- double currBeta = betaMatrix.get(0, k);
- if (currBeta < phaseFractionMinimumLimit) {
- system.setBeta(k, phaseFractionMinimumLimit);
- if (checkOneRemove) {
- if (system.getPhase(k).getType() == PhaseType.GAS) {
- system.setPhaseType(k, PhaseType.LIQUID);
- }
- checkOneRemove = false;
- removePhase = true;
- }
- checkOneRemove = true;
- } else if (currBeta > (1.0 - phaseFractionMinimumLimit)) {
- system.setBeta(k, 1.0 - phaseFractionMinimumLimit);
- } else {
- system.setBeta(k, currBeta);
- }
+ double currBeta = betaVector[k];
+ if (currBeta < phaseFractionMinimumLimit) {
+ system.setBeta(k, phaseFractionMinimumLimit);
+ if (checkOneRemove) {
+ if (system.getPhase(k).getType() == PhaseType.GAS) {
+ system.setPhaseType(k, PhaseType.LIQUID);
+ }
+ checkOneRemove = false;
+ removePhase = true;
+ }
+ checkOneRemove = true;
+ } else if (currBeta > (1.0 - phaseFractionMinimumLimit)) {
+ system.setBeta(k, 1.0 - phaseFractionMinimumLimit);
+ } else {
+ system.setBeta(k, currBeta);
+ }
}
system.normalizeBeta();
system.init(1);
calcE();
setXY();
system.init(1);
- err = ans.normF();
+ err = LinearAlgebraOps.vectorNorm(ans);
} while (((err > 1e-12 || gradResidual > 1e-10) && iter < 50) || iter < 3);
// logger.info("iterations " + iter);
return err;
@@ -309,11 +316,12 @@ private void requestBoundedRerun() {
}
/**
- * Remove a duplicate phase while conserving its mass by merging its phase fraction into the surviving
- * (near-identical) phase before removal. Two phases flagged as numerical duplicates have essentially identical
- * mole-fraction vectors, so the merged phase fraction is simply the sum of the two betas. Without this merge the
- * removed phase's mass leaks into the remaining phases through {@code normalizeBeta()}, which halves trace liquid
- * dropout (see UMR-PRU trace oil regression).
+ * Remove a duplicate phase while conserving its mass by merging its phase fraction into the
+ * surviving (near-identical) phase before removal. Two phases flagged as numerical duplicates
+ * have essentially identical mole-fraction vectors, so the merged phase fraction is simply the
+ * sum of the two betas. Without this merge the removed phase's mass leaks into the remaining
+ * phases through {@code normalizeBeta()}, which halves trace liquid dropout (see UMR-PRU trace
+ * oil regression).
*
* @param keepPhase index of the phase to keep
* @param removePhase2 index of the duplicate phase to remove
@@ -331,7 +339,8 @@ private void mergeAndRemoveDuplicatePhase(int keepPhase, int removePhase2) {
@Override
public void stabilityAnalysis() {
double[] logWi = new double[system.getPhase(0).getNumberOfComponents()];
- double[][] Wi = new double[system.getPhase(0).getNumberOfComponents()][system.getPhase(0).getNumberOfComponents()];
+ double[][] Wi = new double[system.getPhase(0).getNumberOfComponents()][system.getPhase(0)
+ .getNumberOfComponents()];
double[] deltalogWi = new double[system.getPhases()[0].getNumberOfComponents()];
double[] oldDeltalogWi = new double[system.getPhases()[0].getNumberOfComponents()];
@@ -341,7 +350,8 @@ public void stabilityAnalysis() {
double[] oldoldlogw = new double[system.getPhases()[0].getNumberOfComponents()];
double[] oldoldoldlogw = new double[system.getPhases()[0].getNumberOfComponents()];
double[] d = new double[system.getPhase(0).getNumberOfComponents()];
- double[][] x = new double[system.getPhase(0).getNumberOfComponents()][system.getPhase(0).getNumberOfComponents()];
+ double[][] x = new double[system.getPhase(0).getNumberOfComponents()][system.getPhase(0)
+ .getNumberOfComponents()];
tm = new double[system.getPhase(0).getNumberOfComponents()];
double[] alpha = null;
@@ -354,50 +364,55 @@ public void stabilityAnalysis() {
clonedSystem.add(system.clone());
/*
* for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) { if
- * (system.getPhase(0).getComponent(i).getx() < 1e-100) { clonedSystem.add(null); continue; } double numb = 0;
- * clonedSystem.add(system.clone());
+ * (system.getPhase(0).getComponent(i).getx() < 1e-100) { clonedSystem.add(null); continue; }
+ * double numb = 0; clonedSystem.add(system.clone());
*
* // (clonedSystem.get(i)).init(0); commented out sept 2005, Even S. for (int j = 0; j <
- * system.getPhase(0).getNumberOfComponents(); j++) { numb = i == j ? 1.0 : 1.0e-12; // set to 0 by Even Solbraa
- * 23.01.2013 - chaged back to 1.0e-12 27.04.13 if (system.getPhase(0).getComponent(j).getz() < 1e-100) { numb = 0;
- * } ( clonedSystem.get(i)).getPhase(1).getComponent(j).setx(numb); } if
- * (system.getPhase(0).getComponent(i).getIonicCharge() == 0) { ( clonedSystem.get(i)).init(1); } }
+ * system.getPhase(0).getNumberOfComponents(); j++) { numb = i == j ? 1.0 : 1.0e-12; // set to 0
+ * by Even Solbraa 23.01.2013 - chaged back to 1.0e-12 27.04.13 if
+ * (system.getPhase(0).getComponent(j).getz() < 1e-100) { numb = 0; } (
+ * clonedSystem.get(i)).getPhase(1).getComponent(j).setx(numb); } if
+ * (system.getPhase(0).getComponent(i).getIonicCharge() == 0) { ( clonedSystem.get(i)).init(1);
+ * } }
*/
lowestGibbsEnergyPhase = 0;
/*
* // logger.info("low gibbs phase " + lowestGibbsEnergyPhase); for (int k = 0; k <
* minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); k++) { for (int i = 0; i <
- * minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); i++) { if (!(( clonedSystem.get(k)) == null)) {
- * sumw[k] += ( clonedSystem.get(k)).getPhase(1).getComponent(i).getx(); } } }
+ * minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); i++) { if (!((
+ * clonedSystem.get(k)) == null)) { sumw[k] += (
+ * clonedSystem.get(k)).getPhase(1).getComponent(i).getx(); } } }
*
- * for (int k = 0; k < minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); k++) { for (int i = 0; i <
- * minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); i++) { if (!(( clonedSystem.get(k)) == null) &&
- * system.getPhase(0).getComponent(k).getx() > 1e-100) { ( clonedSystem.get(k)).getPhase(1).getComponent(i).setx((
+ * for (int k = 0; k < minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); k++) { for
+ * (int i = 0; i < minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); i++) { if (!((
+ * clonedSystem.get(k)) == null) && system.getPhase(0).getComponent(k).getx() > 1e-100) { (
+ * clonedSystem.get(k)).getPhase(1).getComponent(i).setx((
* clonedSystem.get(k)).getPhase(1).getComponent(i).getx() / sumw[k]); } logger.info("x: " + (
- * clonedSystem.get(k)).getPhase(0).getComponent(i).getx()); } if (system.getPhase(0).getComponent(k).getx() >
- * 1e-100) { d[k] = Math.log(system.getPhase(0).getComponent(k).getx()) +
+ * clonedSystem.get(k)).getPhase(0).getComponent(i).getx()); } if
+ * (system.getPhase(0).getComponent(k).getx() > 1e-100) { d[k] =
+ * Math.log(system.getPhase(0).getComponent(k).getx()) +
* system.getPhase(0).getComponent(k).getLogFugacityCoefficient();
- * if(minimumGibbsEnergySystem.getPhases()[lowestGibbsEnergyPhase].getComponents ()[k].getIonicCharge()!=0) d[k]=0;
- * } //logger.info("dk: " + d[k]); }
+ * if(minimumGibbsEnergySystem.getPhases()[lowestGibbsEnergyPhase].getComponents
+ * ()[k].getIonicCharge()!=0) d[k]=0; } //logger.info("dk: " + d[k]); }
*/
// Calculate reference fugacities d[k] = ln(x_k) + ln(phi_k) for feed phase
for (int k = 0; k < minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); k++) {
if (system.getPhase(0).getComponent(k).getx() > 1e-100) {
- d[k] = Math.log(system.getPhase(0).getComponent(k).getx())
- + system.getPhase(0).getComponent(k).getLogFugacityCoefficient();
- // if(minimumGibbsEnergySystem.getPhases()[lowestGibbsEnergyPhase].getComponent(k).getIonicCharge()!=0)
- // d[k]=0;
+ d[k] = Math.log(system.getPhase(0).getComponent(k).getx())
+ + system.getPhase(0).getComponent(k).getLogFugacityCoefficient();
+ // if(minimumGibbsEnergySystem.getPhases()[lowestGibbsEnergyPhase].getComponent(k).getIonicCharge()!=0)
+ // d[k]=0;
}
}
// Initialize logWi array (will be overwritten for each trial)
for (int j = 0; j < minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); j++) {
if (system.getPhase(0).getComponent(j).getz() > 1e-100) {
- logWi[j] = 0.0;
+ logWi[j] = 0.0;
} else {
- logWi[j] = -10000.0;
+ logWi[j] = -10000.0;
}
}
@@ -412,20 +427,20 @@ public void stabilityAnalysis() {
for (int i = 0; i < numComp; i++) {
double z = system.getPhase(0).getComponent(i).getz();
boolean isIon = system.getPhase(0).getComponent(i).getIonicCharge() != 0
- || system.getPhase(0).getComponent(i).isIsIon();
+ || system.getPhase(0).getComponent(i).isIsIon();
validComp[i] = z > 1e-100 && !isIon;
if (validComp[i]) {
- double tc = system.getPhase(0).getComponent(i).getTC();
- double pc = system.getPhase(0).getComponent(i).getPC();
- double omega = system.getPhase(0).getComponent(i).getAcentricFactor();
- double kVal = (pc / presBar) * Math.exp(5.373 * (1.0 + omega) * (1.0 - tc / tempK));
- wilsonK[i] = Math.max(kVal, 1e-20);
- double absLogK = Math.abs(Math.log(wilsonK[i]));
- if (absLogK > maxAbsLogK) {
- maxAbsLogK = absLogK;
- }
+ double tc = system.getPhase(0).getComponent(i).getTC();
+ double pc = system.getPhase(0).getComponent(i).getPC();
+ double omega = system.getPhase(0).getComponent(i).getAcentricFactor();
+ double kVal = (pc / presBar) * Math.exp(5.373 * (1.0 + omega) * (1.0 - tc / tempK));
+ wilsonK[i] = Math.max(kVal, 1e-20);
+ double absLogK = Math.abs(Math.log(wilsonK[i]));
+ if (absLogK > maxAbsLogK) {
+ maxAbsLogK = absLogK;
+ }
} else {
- wilsonK[i] = 1.0;
+ wilsonK[i] = 1.0;
}
}
@@ -443,20 +458,21 @@ public void stabilityAnalysis() {
for (int trial = 0; !skipWilsonKTrials && trial < 2; trial++) {
// Initialize trial composition from Wilson K
for (int i = 0; i < numComp; i++) {
- if (validComp[i]) {
- double z = system.getPhase(0).getComponent(i).getz();
- // trial 0 = liquid-like (z/K), trial 1 = vapor-like (K*z)
- double wVal = (trial == 0) ? z / wilsonK[i] : wilsonK[i] * z;
- logWi[i] = Math.log(Math.max(wVal, 1e-100));
- } else {
- logWi[i] = -10000.0;
- }
+ if (validComp[i]) {
+ double z = system.getPhase(0).getComponent(i).getz();
+ // trial 0 = liquid-like (z/K), trial 1 = vapor-like (K*z)
+ double wVal = (trial == 0) ? z / wilsonK[i] : wilsonK[i] * z;
+ logWi[i] = Math.log(Math.max(wVal, 1e-100));
+ } else {
+ logWi[i] = -10000.0;
+ }
}
// Set trial phase composition (unnormalized, same as pure-component trials)
for (int i = 0; i < numComp; i++) {
- if (clonedSystem.get(0).isPhase(1)) {
- clonedSystem.get(0).getPhase(1).getComponent(i).setx(validComp[i] ? safeExp(logWi[i]) : 1e-50);
- }
+ if (clonedSystem.get(0).isPhase(1)) {
+ clonedSystem.get(0).getPhase(1).getComponent(i)
+ .setx(validComp[i] ? safeExp(logWi[i]) : 1e-50);
+ }
}
// Successive substitution with Wegstein acceleration (same as pure-component trials)
@@ -467,66 +483,68 @@ public void stabilityAnalysis() {
boolean trialInitFailed = false;
int maxiter = 50;
do {
- errOld = err;
- iter++;
- err = 0;
-
- for (int i = 0; i < numComp; i++) {
- oldoldoldlogw[i] = oldoldlogw[i];
- oldoldlogw[i] = oldlogw[i];
- oldlogw[i] = logWi[i];
- oldoldDeltalogWi[i] = oldoldlogw[i] - oldoldoldlogw[i];
- oldDeltalogWi[i] = oldlogw[i] - oldoldlogw[i];
- }
- try {
- clonedSystem.get(0).init(1, 1);
- } catch (Exception ex) {
- trialInitFailed = true;
- break;
- }
- for (int i = 0; i < numComp; i++) {
- if (validComp[i]
- && !Double.isInfinite(clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient())) {
- logWi[i] = d[i] - clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient();
- }
- deltalogWi[i] = logWi[i] - oldlogw[i];
- err += Math.abs(deltalogWi[i]);
- }
-
- // Wegstein acceleration every 7th iteration
- if (iter % 7 == 0 && iter > 7 && useAccel && err < errOld) {
- double prod1 = 0.0;
- double prod2 = 0.0;
- for (int i = 0; i < numComp; i++) {
- if (validComp[i]) {
- prod1 += deltalogWi[i] * oldDeltalogWi[i];
- prod2 += oldDeltalogWi[i] * oldDeltalogWi[i];
- }
- }
- if (prod2 > 1e-20) {
- double lambda = prod1 / prod2;
- if (lambda > 0.0 && lambda < 1.0) {
- double accelFactor = lambda / (1.0 - lambda);
- for (int i = 0; i < numComp; i++) {
- if (validComp[i]) {
- logWi[i] += accelFactor * deltalogWi[i];
- }
- }
- }
- }
- }
- if (iter > 2 && err > errOld) {
- useAccel = false;
- }
-
- // Update trial phase composition
- for (int i = 0; i < numComp; i++) {
- clonedSystem.get(0).getPhase(1).getComponent(i).setx(validComp[i] ? safeExp(logWi[i]) : 1e-50);
- }
+ errOld = err;
+ iter++;
+ err = 0;
+
+ for (int i = 0; i < numComp; i++) {
+ oldoldoldlogw[i] = oldoldlogw[i];
+ oldoldlogw[i] = oldlogw[i];
+ oldlogw[i] = logWi[i];
+ oldoldDeltalogWi[i] = oldoldlogw[i] - oldoldoldlogw[i];
+ oldDeltalogWi[i] = oldlogw[i] - oldoldlogw[i];
+ }
+ try {
+ clonedSystem.get(0).init(1, 1);
+ } catch (Exception ex) {
+ trialInitFailed = true;
+ break;
+ }
+ for (int i = 0; i < numComp; i++) {
+ if (validComp[i] && !Double.isInfinite(
+ clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient())) {
+ logWi[i] =
+ d[i] - clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient();
+ }
+ deltalogWi[i] = logWi[i] - oldlogw[i];
+ err += Math.abs(deltalogWi[i]);
+ }
+
+ // Wegstein acceleration every 7th iteration
+ if (iter % 7 == 0 && iter > 7 && useAccel && err < errOld) {
+ double prod1 = 0.0;
+ double prod2 = 0.0;
+ for (int i = 0; i < numComp; i++) {
+ if (validComp[i]) {
+ prod1 += deltalogWi[i] * oldDeltalogWi[i];
+ prod2 += oldDeltalogWi[i] * oldDeltalogWi[i];
+ }
+ }
+ if (prod2 > 1e-20) {
+ double lambda = prod1 / prod2;
+ if (lambda > 0.0 && lambda < 1.0) {
+ double accelFactor = lambda / (1.0 - lambda);
+ for (int i = 0; i < numComp; i++) {
+ if (validComp[i]) {
+ logWi[i] += accelFactor * deltalogWi[i];
+ }
+ }
+ }
+ }
+ }
+ if (iter > 2 && err > errOld) {
+ useAccel = false;
+ }
+
+ // Update trial phase composition
+ for (int i = 0; i < numComp; i++) {
+ clonedSystem.get(0).getPhase(1).getComponent(i)
+ .setx(validComp[i] ? safeExp(logWi[i]) : 1e-50);
+ }
} while (!trialInitFailed && (Math.abs(err) > 1e-9 || err > errOld) && iter < maxiter);
if (trialInitFailed) {
- continue;
+ continue;
}
// Calculate tangent plane distance and check for instability
@@ -535,44 +553,44 @@ public void stabilityAnalysis() {
double xTrivialCheck1 = 0.0;
double[] xTrial = new double[numComp];
for (int i = 0; i < numComp; i++) {
- if (validComp[i]) {
- tmVal -= safeExp(logWi[i]);
- }
- xTrial[i] = clonedSystem.get(0).getPhase(1).getComponent(i).getx();
- xTrivialCheck0 += Math.abs(xTrial[i] - system.getPhase(0).getComponent(i).getx());
- xTrivialCheck1 += Math.abs(xTrial[i] - system.getPhase(1).getComponent(i).getx());
+ if (validComp[i]) {
+ tmVal -= safeExp(logWi[i]);
+ }
+ xTrial[i] = clonedSystem.get(0).getPhase(1).getComponent(i).getx();
+ xTrivialCheck0 += Math.abs(xTrial[i] - system.getPhase(0).getComponent(i).getx());
+ xTrivialCheck1 += Math.abs(xTrial[i] - system.getPhase(1).getComponent(i).getx());
}
boolean isTrivial = Math.abs(xTrivialCheck0) < 1e-4 || Math.abs(xTrivialCheck1) < 1e-4;
if (!isTrivial && tmVal < -1e-8 && iter < maxiter) {
- // Unstable — add new phase and return
- system.addPhase();
- int newPhaseIdx = system.getNumberOfPhases() - 1;
- for (int i = 0; i < numComp; i++) {
- system.getPhase(newPhaseIdx).getComponent(i).setx(xTrial[i]);
- }
- system.getPhases()[newPhaseIdx].normalize();
- multiPhaseTest = true;
- int dominantComp = 0;
- double maxX = 0;
- for (int i = 0; i < numComp; i++) {
- if (xTrial[i] > maxX) {
- maxX = xTrial[i];
- dominantComp = i;
- }
- }
- system.setBeta(newPhaseIdx, system.getPhase(0).getComponent(dominantComp).getz());
- try {
- system.init(1);
- } catch (Exception ex) {
- logger.warn("K-value trial addPhase init failed: " + ex.getMessage());
- system.removePhaseKeepTotalComposition(newPhaseIdx);
- multiPhaseTest = false;
- return;
- }
- system.normalizeBeta();
- return;
+ // Unstable — add new phase and return
+ system.addPhase();
+ int newPhaseIdx = system.getNumberOfPhases() - 1;
+ for (int i = 0; i < numComp; i++) {
+ system.getPhase(newPhaseIdx).getComponent(i).setx(xTrial[i]);
+ }
+ system.getPhases()[newPhaseIdx].normalize();
+ multiPhaseTest = true;
+ int dominantComp = 0;
+ double maxX = 0;
+ for (int i = 0; i < numComp; i++) {
+ if (xTrial[i] > maxX) {
+ maxX = xTrial[i];
+ dominantComp = i;
+ }
+ }
+ system.setBeta(newPhaseIdx, system.getPhase(0).getComponent(dominantComp).getz());
+ try {
+ system.init(1);
+ } catch (Exception ex) {
+ logger.warn("K-value trial addPhase init failed: " + ex.getMessage());
+ system.removePhaseKeepTotalComposition(newPhaseIdx);
+ multiPhaseTest = false;
+ return;
+ }
+ system.normalizeBeta();
+ return;
}
}
@@ -589,70 +607,72 @@ public void stabilityAnalysis() {
double Mmin = 1e10;
for (int i = 0; i < minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); i++) {
if (minimumGibbsEnergySystem.getPhase(0).getComponent(i).isHydrocarbon()) {
- if ((minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) > Mmax) {
- Mmax = minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass();
- }
- if ((minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) < Mmin) {
- Mmin = minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass();
- }
+ if ((minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) > Mmax) {
+ Mmax = minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass();
+ }
+ if ((minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) < Mmin) {
+ Mmin = minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass();
+ }
}
}
for (int i = 0; i < minimumGibbsEnergySystem.getPhase(0).getNumberOfComponents(); i++) {
if (minimumGibbsEnergySystem.getPhase(0).getComponent(i).isHydrocarbon()
- && minimumGibbsEnergySystem.getPhase(0).getComponent(i).getz() > 1e-50) {
- if (Math.abs((minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) - Mmax) < 1e-5) {
- hydrocarbonTestCompNumb = i;
- // logger.info("CHECKING heavy component " + hydrocarbonTestCompNumb);
- }
+ && minimumGibbsEnergySystem.getPhase(0).getComponent(i).getz() > 1e-50) {
+ if (Math.abs(
+ (minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) - Mmax) < 1e-5) {
+ hydrocarbonTestCompNumb = i;
+ // logger.info("CHECKING heavy component " + hydrocarbonTestCompNumb);
+ }
}
if (minimumGibbsEnergySystem.getPhase(0).getComponent(i).isHydrocarbon()
- && minimumGibbsEnergySystem.getPhase(0).getComponent(i).getz() > 1e-50) {
- if (Math.abs((minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) - Mmin) < 1e-5) {
- lightTestCompNumb = i;
- // logger.info("CHECKING light component " + lightTestCompNumb);
- }
+ && minimumGibbsEnergySystem.getPhase(0).getComponent(i).getz() > 1e-50) {
+ if (Math.abs(
+ (minimumGibbsEnergySystem.getPhase(0).getComponent(i).getMolarMass()) - Mmin) < 1e-5) {
+ lightTestCompNumb = i;
+ // logger.info("CHECKING light component " + lightTestCompNumb);
+ }
}
}
// boolean checkdForHCmix = false;
for (int j = system.getPhase(0).getNumberOfComponents() - 1; j >= 0; j--) {
if (minimumGibbsEnergySystem.getPhase(0).getComponent(j).getx() < 1e-100
- || (minimumGibbsEnergySystem.getPhase(0).getComponent(j).getIonicCharge() != 0)
- || (minimumGibbsEnergySystem.getPhase(0).getComponent(j).isHydrocarbon() && j != hydrocarbonTestCompNumb
- && j != lightTestCompNumb)) {
- continue;
+ || (minimumGibbsEnergySystem.getPhase(0).getComponent(j).getIonicCharge() != 0)
+ || (minimumGibbsEnergySystem.getPhase(0).getComponent(j).isHydrocarbon()
+ && j != hydrocarbonTestCompNumb && j != lightTestCompNumb)) {
+ continue;
}
double nomb = 0.0;
for (int cc = 0; cc < system.getPhase(0).getNumberOfComponents(); cc++) {
- // Pure component trial phase: component j = 1.0, others = trace
- nomb = cc == j ? 1.0 : 1.0e-12;
- if (system.getPhase(0).getComponent(cc).getz() < 1e-100) {
- nomb = 0.0;
- }
-
- // Initialize logWi to match pure component trial phase (Michelsen's algorithm)
- // For pure component j trial: Wi[j] = 1.0, Wi[others] = trace
- // So logWi[j] = 0, logWi[others] = log(1e-12) ≈ -27.6
- if (system.getPhase(0).getComponent(cc).getz() > 1e-100) {
- logWi[cc] = Math.log(Math.max(nomb, 1e-100));
- } else {
- logWi[cc] = -10000.0;
- }
-
- if (clonedSystem.get(0).isPhase(1)) {
- try {
- clonedSystem.get(0).getPhase(1).getComponent(cc).setx(nomb);
- /*
- * if (system.getPhase(1).getType() == PhaseType.AQUEOUS && !checkdForHCmix) {
- * clonedSystem.get(0).getPhase(1).getComponent(cc)
- * .setx(clonedSystem.get(0).getPhase(0).getComponent(cc).getK() /
- * clonedSystem.get(0).getPhase(0).getComponent(cc).getx()); } else {
- * clonedSystem.get(0).getPhase(1).getComponent(cc).setx(nomb); }
- */
- } catch (Exception ex) {
- logger.warn(ex.getMessage());
- }
- }
+ // Pure component trial phase: component j = 1.0, others = trace
+ nomb = cc == j ? 1.0 : 1.0e-12;
+ if (system.getPhase(0).getComponent(cc).getz() < 1e-100) {
+ nomb = 0.0;
+ }
+
+ // Initialize logWi to match pure component trial phase (Michelsen's algorithm)
+ // For pure component j trial: Wi[j] = 1.0, Wi[others] = trace
+ // So logWi[j] = 0, logWi[others] = log(1e-12) ≈ -27.6
+ if (system.getPhase(0).getComponent(cc).getz() > 1e-100) {
+ logWi[cc] = Math.log(Math.max(nomb, 1e-100));
+ } else {
+ logWi[cc] = -10000.0;
+ }
+
+ if (clonedSystem.get(0).isPhase(1)) {
+ try {
+ clonedSystem.get(0).getPhase(1).getComponent(cc).setx(nomb);
+ /*
+ * if (system.getPhase(1).getType() == PhaseType.AQUEOUS && !checkdForHCmix) {
+ * clonedSystem.get(0).getPhase(1).getComponent(cc)
+ * .setx(clonedSystem.get(0).getPhase(0).getComponent(cc).getK() /
+ * clonedSystem.get(0).getPhase(0).getComponent(cc).getx()); } else {
+ * clonedSystem.get(0).getPhase(1).getComponent(cc).setx(nomb); }
+ */
+ } catch (Exception ex) {
+ logger.warn(ex.getMessage());
+ }
+ }
}
// if (system.getPhase(1).getType() == PhaseType.AQUEOUS && !checkdForHCmix) {
@@ -671,155 +691,158 @@ public void stabilityAnalysis() {
int maxsucssubiter = 150;
int maxiter = 200;
- // Pre-allocate Newton matrices outside the iteration loop to avoid GC pressure
+ // Pre-allocate Newton arrays outside the iteration loop to avoid GC pressure
int nc = system.getPhase(0).getNumberOfComponents();
- DMatrixRMaj newtonF = new DMatrixRMaj(nc, 1);
- DMatrixRMaj newtonJ = new DMatrixRMaj(nc, nc);
- DMatrixRMaj newtonDx = new DMatrixRMaj(nc, 1);
+ double[] newtonF = new double[nc];
+ double[][] newtonJ = new double[nc][nc];
+ double[] newtonDx = new double[nc];
do {
- errOld = err;
- iter++;
- err = 0;
-
- if (iter <= maxsucssubiter || !system.isImplementedCompositionDeriativesofFugacity()) {
- // DEM acceleration every 5th iteration (Michelsen 1982b, Risnes et al. 1981)
- // Uses dominant eigenvalue estimate: λ = (Δg_n · Δg_{n-1}) / (Δg_{n-1} · Δg_{n-1})
- if (iter % 5 == 0 && iter > 5 && useaccsubst) {
- double prod1 = 0.0;
- double prod2 = 0.0;
- for (int i = 0; i < nc; i++) {
- // Correct DEM formula: λ = Σ(Δg_n · Δg_{n-1}) / Σ(Δg_{n-1}²)
- prod1 += deltalogWi[i] * oldDeltalogWi[i];
- prod2 += oldDeltalogWi[i] * oldDeltalogWi[i];
- }
-
- if (prod2 > 1e-20) {
- double lambda = prod1 / prod2;
- // Only accelerate if 0 < λ < 1 (convergent regime)
- if (lambda > 0.0 && lambda < 1.0) {
- double accelFactor = lambda / (1.0 - lambda);
- for (int i = 0; i < nc; i++) {
- logWi[i] += accelFactor * deltalogWi[i];
- Wi[j][i] = safeExp(logWi[i]);
- }
- }
- }
- // Must still update compositions after acceleration
- for (int i = 0; i < nc; i++) {
- err += Math.abs(logWi[i] - oldlogw[i]);
- }
- } else {
- for (int i = 0; i < nc; i++) {
- oldoldoldlogw[i] = oldoldlogw[i];
- oldoldlogw[i] = oldlogw[i];
- oldlogw[i] = logWi[i];
- oldoldDeltalogWi[i] = oldoldlogw[i] - oldoldoldlogw[i];
- oldDeltalogWi[i] = oldlogw[i] - oldoldlogw[i];
- }
- try {
- clonedSystem.get(0).init(1, 1);
- } catch (Exception ex) {
- pureTrialInitFailed = true;
- break;
- }
- for (int i = 0; i < nc; i++) {
- if (!Double.isInfinite(clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient())
- && system.getPhase(0).getComponent(i).getz() > 1e-100) {
- logWi[i] = d[i] - clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient();
- if (clonedSystem.get(0).getPhase(1).getComponent(i).getIonicCharge() != 0) {
- logWi[i] = -1000.0;
- }
- }
- deltalogWi[i] = logWi[i] - oldlogw[i];
- err += Math.abs(logWi[i] - oldlogw[i]);
- Wi[j][i] = safeExp(logWi[i]);
- }
- if (iter > 2 && err > errOld) {
- useaccsubst = false;
- }
- }
- } else {
- // Second-order (Newton) method using Michelsen's α-substitution
- // α_i = 2√(W_i), which ensures W_i ≥ 0 (Michelsen 1982a)
- for (int i = 0; i < nc; i++) {
- oldoldoldlogw[i] = oldoldlogw[i];
- oldoldlogw[i] = oldlogw[i];
- oldlogw[i] = logWi[i];
- }
- // Newton needs fugcoef + composition derivatives
- try {
- clonedSystem.get(0).init(3, 1);
- } catch (Exception ex) {
- pureTrialInitFailed = true;
- break;
- }
- alpha = new double[nc];
-
- for (int i = 0; i < nc; i++) {
- alpha[i] = 2.0 * Math.sqrt(Wi[j][i]);
- }
-
- // Build gradient and Jacobian using raw EJML (no SimpleMatrix allocation)
- for (int i = 0; i < nc; i++) {
- if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
- newtonF.set(i, 0, Math.sqrt(Wi[j][i]) * (Math.log(Wi[j][i])
- + clonedSystem.get(0).getPhases()[1].getComponent(i).getLogFugacityCoefficient() - d[i]));
- } else {
- newtonF.set(i, 0, 0.0);
- }
- for (int k = 0; k < nc; k++) {
- double kronDelt = (i == k) ? 1.0 : 0.0;
- if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
- newtonJ.set(i, k, kronDelt
- + Math.sqrt(Wi[j][k] * Wi[j][i]) * clonedSystem.get(0).getPhases()[1].getComponent(i).getdfugdn(k));
- } else {
- newtonJ.set(i, k, 0.0);
- }
- }
- }
-
- // Solve J·dx = -f using raw EJML
- boolean solved = CommonOps_DDRM.solve(newtonJ, newtonF, newtonDx);
- if (!solved) {
- // Regularize: add small diagonal and retry
- for (int i = 0; i < nc; i++) {
- newtonJ.add(i, i, 0.1);
- }
- solved = CommonOps_DDRM.solve(newtonJ, newtonF, newtonDx);
- }
-
- if (solved) {
- for (int i = 0; i < nc; i++) {
- double alphaNew = alpha[i] - newtonDx.get(i, 0);
- Wi[j][i] = Math.pow(alphaNew / 2.0, 2.0);
- if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
- logWi[i] = Math.log(Wi[j][i]);
- }
- if (system.getPhase(0).getComponent(i).getIonicCharge() != 0
- || system.getPhase(0).getComponent(i).isIsIon()) {
- logWi[i] = -1000.0;
- }
- err += Math.abs((logWi[i] - oldlogw[i]) / oldlogw[i]);
- }
- }
- }
- // logger.info("err: " + err);
-
- for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
- if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
- clonedSystem.get(0).getPhase(1).getComponent(i).setx(safeExp(logWi[i]));
- }
- if (system.getPhase(0).getComponent(i).getIonicCharge() != 0
- || system.getPhase(0).getComponent(i).isIsIon()) {
- clonedSystem.get(0).getPhase(1).getComponent(i).setx(1e-50);
- }
- }
+ errOld = err;
+ iter++;
+ err = 0;
+
+ if (iter <= maxsucssubiter || !system.isImplementedCompositionDeriativesofFugacity()) {
+ // DEM acceleration every 5th iteration (Michelsen 1982b, Risnes et al. 1981)
+ // Uses dominant eigenvalue estimate: λ = (Δg_n · Δg_{n-1}) / (Δg_{n-1} · Δg_{n-1})
+ if (iter % 5 == 0 && iter > 5 && useaccsubst) {
+ double prod1 = 0.0;
+ double prod2 = 0.0;
+ for (int i = 0; i < nc; i++) {
+ // Correct DEM formula: λ = Σ(Δg_n · Δg_{n-1}) / Σ(Δg_{n-1}²)
+ prod1 += deltalogWi[i] * oldDeltalogWi[i];
+ prod2 += oldDeltalogWi[i] * oldDeltalogWi[i];
+ }
+
+ if (prod2 > 1e-20) {
+ double lambda = prod1 / prod2;
+ // Only accelerate if 0 < λ < 1 (convergent regime)
+ if (lambda > 0.0 && lambda < 1.0) {
+ double accelFactor = lambda / (1.0 - lambda);
+ for (int i = 0; i < nc; i++) {
+ logWi[i] += accelFactor * deltalogWi[i];
+ Wi[j][i] = safeExp(logWi[i]);
+ }
+ }
+ }
+ // Must still update compositions after acceleration
+ for (int i = 0; i < nc; i++) {
+ err += Math.abs(logWi[i] - oldlogw[i]);
+ }
+ } else {
+ for (int i = 0; i < nc; i++) {
+ oldoldoldlogw[i] = oldoldlogw[i];
+ oldoldlogw[i] = oldlogw[i];
+ oldlogw[i] = logWi[i];
+ oldoldDeltalogWi[i] = oldoldlogw[i] - oldoldoldlogw[i];
+ oldDeltalogWi[i] = oldlogw[i] - oldoldlogw[i];
+ }
+ try {
+ clonedSystem.get(0).init(1, 1);
+ } catch (Exception ex) {
+ pureTrialInitFailed = true;
+ break;
+ }
+ for (int i = 0; i < nc; i++) {
+ if (!Double.isInfinite(
+ clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient())
+ && system.getPhase(0).getComponent(i).getz() > 1e-100) {
+ logWi[i] = d[i]
+ - clonedSystem.get(0).getPhase(1).getComponent(i).getLogFugacityCoefficient();
+ if (clonedSystem.get(0).getPhase(1).getComponent(i).getIonicCharge() != 0) {
+ logWi[i] = -1000.0;
+ }
+ }
+ deltalogWi[i] = logWi[i] - oldlogw[i];
+ err += Math.abs(logWi[i] - oldlogw[i]);
+ Wi[j][i] = safeExp(logWi[i]);
+ }
+ if (iter > 2 && err > errOld) {
+ useaccsubst = false;
+ }
+ }
+ } else {
+ // Second-order (Newton) method using Michelsen's α-substitution
+ // α_i = 2√(W_i), which ensures W_i ≥ 0 (Michelsen 1982a)
+ for (int i = 0; i < nc; i++) {
+ oldoldoldlogw[i] = oldoldlogw[i];
+ oldoldlogw[i] = oldlogw[i];
+ oldlogw[i] = logWi[i];
+ }
+ // Newton needs fugcoef + composition derivatives
+ try {
+ clonedSystem.get(0).init(3, 1);
+ } catch (Exception ex) {
+ pureTrialInitFailed = true;
+ break;
+ }
+ alpha = new double[nc];
+
+ for (int i = 0; i < nc; i++) {
+ alpha[i] = 2.0 * Math.sqrt(Wi[j][i]);
+ }
+
+ // Build gradient and Jacobian using pre-allocated arrays
+ for (int i = 0; i < nc; i++) {
+ if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
+ newtonF[i] = Math.sqrt(Wi[j][i]) * (Math.log(Wi[j][i])
+ + clonedSystem.get(0).getPhases()[1].getComponent(i).getLogFugacityCoefficient()
+ - d[i]);
+ } else {
+ newtonF[i] = 0.0;
+ }
+ for (int k = 0; k < nc; k++) {
+ double kronDelt = (i == k) ? 1.0 : 0.0;
+ if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
+ newtonJ[i][k] = kronDelt + Math.sqrt(Wi[j][k] * Wi[j][i])
+ * clonedSystem.get(0).getPhases()[1].getComponent(i).getdfugdn(k);
+ } else {
+ newtonJ[i][k] = 0.0;
+ }
+ }
+ }
+
+ // Solve J·dx = f using ojAlgo LU
+ boolean solved = LinearAlgebraOps.solveLinearSystem(newtonJ, newtonF, newtonDx);
+ if (!solved) {
+ // Regularize: add small diagonal and retry
+ for (int i = 0; i < nc; i++) {
+ newtonJ[i][i] += 0.1;
+ }
+ solved = LinearAlgebraOps.solveLinearSystem(newtonJ, newtonF, newtonDx);
+ }
+
+ if (solved) {
+ for (int i = 0; i < nc; i++) {
+ double alphaNew = alpha[i] - newtonDx[i];
+ Wi[j][i] = Math.pow(alphaNew / 2.0, 2.0);
+ if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
+ logWi[i] = Math.log(Wi[j][i]);
+ }
+ if (system.getPhase(0).getComponent(i).getIonicCharge() != 0
+ || system.getPhase(0).getComponent(i).isIsIon()) {
+ logWi[i] = -1000.0;
+ }
+ err += Math.abs((logWi[i] - oldlogw[i]) / oldlogw[i]);
+ }
+ }
+ }
+ // logger.info("err: " + err);
+
+ for (int i = 0; i < system.getPhase(0).getNumberOfComponents(); i++) {
+ if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
+ clonedSystem.get(0).getPhase(1).getComponent(i).setx(safeExp(logWi[i]));
+ }
+ if (system.getPhase(0).getComponent(i).getIonicCharge() != 0
+ || system.getPhase(0).getComponent(i).isIsIon()) {
+ clonedSystem.get(0).getPhase(1).getComponent(i).setx(1e-50);
+ }
+ }
} while (!pureTrialInitFailed && (Math.abs(err) > 1e-9 || err > errOld) && iter < maxiter);
if (pureTrialInitFailed) {
- tm[j] = 10.0;
- continue;
+ tm[j] = 10.0;
+ continue;
}
// logger.info("err: " + err + " ITER " + iter);
@@ -829,56 +852,57 @@ public void stabilityAnalysis() {
tm[j] = 1.0;
for (int i = 0; i < system.getPhase(1).getNumberOfComponents(); i++) {
- // Use getz() so heavy HCs (with near-zero x in gas phase 0) still contribute to tm
- if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
- tm[j] -= safeExp(logWi[i]);
- }
- x[j][i] = clonedSystem.get(0).getPhase(1).getComponent(i).getx();
- // logger.info("txji: " + x[j][i]);
+ // Use getz() so heavy HCs (with near-zero x in gas phase 0) still contribute to tm
+ if (system.getPhase(0).getComponent(i).getz() > 1e-100) {
+ tm[j] -= safeExp(logWi[i]);
+ }
+ x[j][i] = clonedSystem.get(0).getPhase(1).getComponent(i).getx();
+ // logger.info("txji: " + x[j][i]);
- xTrivialCheck0 += Math.abs(x[j][i] - system.getPhase(0).getComponent(i).getx());
- xTrivialCheck1 += Math.abs(x[j][i] - system.getPhase(1).getComponent(i).getx());
+ xTrivialCheck0 += Math.abs(x[j][i] - system.getPhase(0).getComponent(i).getx());
+ xTrivialCheck1 += Math.abs(x[j][i] - system.getPhase(1).getComponent(i).getx());
}
if (iter >= maxiter) {
- // logger.info("iter > maxiter multiphase stability ");
- // logger.info("error " + Math.abs(err));
- // logger.info("tm: " + tm[j]);
+ // logger.info("iter > maxiter multiphase stability ");
+ // logger.info("error " + Math.abs(err));
+ // logger.info("tm: " + tm[j]);
}
if (Math.abs(xTrivialCheck0) < 1e-4 || Math.abs(xTrivialCheck1) < 1e-4) {
- tm[j] = 10.0;
+ tm[j] = 10.0;
}
if (tm[j] < -1e-8) {
- break;
+ break;
}
}
int unstabcomp = 0;
for (int k = system.getPhase(0).getNumberOfComponents() - 1; k >= 0; k--) {
if (tm[k] < -1e-8 && !(Double.isNaN(tm[k]))) {
- system.addPhase();
- unstabcomp = k;
- for (int i = 0; i < system.getPhase(1).getNumberOfComponents(); i++) {
- system.getPhase(system.getNumberOfPhases() - 1).getComponent(i).setx(x[k][i]);
- }
- system.getPhases()[system.getNumberOfPhases() - 1].normalize();
- multiPhaseTest = true;
- system.setBeta(system.getNumberOfPhases() - 1, system.getPhase(0).getComponent(unstabcomp).getz());
- try {
- system.init(1);
- } catch (Exception ex) {
- logger.warn("stabilityAnalysis addPhase init failed: " + ex.getMessage());
- system.removePhaseKeepTotalComposition(system.getNumberOfPhases() - 1);
- multiPhaseTest = false;
- return;
- }
- system.normalizeBeta();
-
- // logger.info("STABILITY ANALYSIS: ");
- // logger.info("tm1: " + k + " "+ tm[k]);
- // system.display();
- return;
+ system.addPhase();
+ unstabcomp = k;
+ for (int i = 0; i < system.getPhase(1).getNumberOfComponents(); i++) {
+ system.getPhase(system.getNumberOfPhases() - 1).getComponent(i).setx(x[k][i]);
+ }
+ system.getPhases()[system.getNumberOfPhases() - 1].normalize();
+ multiPhaseTest = true;
+ system.setBeta(system.getNumberOfPhases() - 1,
+ system.getPhase(0).getComponent(unstabcomp).getz());
+ try {
+ system.init(1);
+ } catch (Exception ex) {
+ logger.warn("stabilityAnalysis addPhase init failed: " + ex.getMessage());
+ system.removePhaseKeepTotalComposition(system.getNumberOfPhases() - 1);
+ multiPhaseTest = false;
+ return;
+ }
+ system.normalizeBeta();
+
+ // logger.info("STABILITY ANALYSIS: ");
+ // logger.info("tm1: " + k + " "+ tm[k]);
+ // system.display();
+ return;
}
}
@@ -889,9 +913,9 @@ public void stabilityAnalysis() {
}
/**
- * Enhanced stability analysis that uses Wilson K-values for initial guesses and tests multiple trial phase
- * compositions. This method is more robust for detecting liquid-liquid equilibria and three-phase systems (e.g.,
- * CO2/H2S/hydrocarbon mixtures).
+ * Enhanced stability analysis that uses Wilson K-values for initial guesses and tests multiple
+ * trial phase compositions. This method is more robust for detecting liquid-liquid equilibria and
+ * three-phase systems (e.g., CO2/H2S/hydrocarbon mixtures).
*
*
* Key improvements over basic stabilityAnalysis():
@@ -899,8 +923,9 @@ public void stabilityAnalysis() {
*
- * Finds the maximum pressure point on the phase envelope by solving an (n+2)-dimensional Newton system simultaneously,
- * following the formulation of Michelsen (1980) and Michelsen & Mollerup (2007), Chapter 12.
+ * Finds the maximum pressure point on the phase envelope by solving an (n+2)-dimensional Newton
+ * system simultaneously, following the formulation of Michelsen (1980) and Michelsen & Mollerup
+ * (2007), Chapter 12.
*
@@ -19,14 +21,14 @@
*
- * The full (n+2)x(n+2) Jacobian is built analytically using fugacity coefficient derivatives (d ln phi/dT, d ln phi/dP,
- * d ln phi/dx_j) available from system.init(3). This gives quadratic convergence, typically converging in 3-8
- * iterations from a good initial estimate.
+ * The full (n+2)x(n+2) Jacobian is built analytically using fugacity coefficient derivatives (d ln
+ * phi/dT, d ln phi/dP, d ln phi/dx_j) available from system.init(3). This gives quadratic
+ * convergence, typically converging in 3-8 iterations from a good initial estimate.
*
- * g_i = ln K_i + ln phi_i^V - ln phi_i^L, i=0..n-1 (equilibrium) g_n = sum_i z_i*(K_i-1)/(1+beta*(K_i-1))
- * (Rachford-Rice) g_{n+1} = S_P = sum_i (dg_i/d(lnT)) * s_i (cricondenbar condition) where s_i = -dg_i/d(lnP) is the
- * sensitivity coefficient
+ * g_i = ln K_i + ln phi_i^V - ln phi_i^L, i=0..n-1 (equilibrium) g_n = sum_i
+ * z_i*(K_i-1)/(1+beta*(K_i-1)) (Rachford-Rice) g_{n+1} = S_P = sum_i (dg_i/d(lnT)) * s_i
+ * (cricondenbar condition) where s_i = -dg_i/d(lnP) is the sensitivity coefficient
*
- * Finds the maximum temperature point on the phase envelope by solving an (n+2)-dimensional Newton system
- * simultaneously, following the formulation of Michelsen (1980) and Michelsen & Mollerup (2007), Chapter 12.
+ * Finds the maximum temperature point on the phase envelope by solving an (n+2)-dimensional Newton
+ * system simultaneously, following the formulation of Michelsen (1980) and Michelsen & Mollerup
+ * (2007), Chapter 12.
*
@@ -18,14 +20,14 @@
*
- * The full (n+2)x(n+2) Jacobian is built analytically using fugacity coefficient derivatives (d ln phi/dT, d ln phi/dP,
- * d ln phi/dx_j) available from system.init(3). This gives quadratic convergence, typically converging in 3-8
- * iterations from a good initial estimate.
+ * The full (n+2)x(n+2) Jacobian is built analytically using fugacity coefficient derivatives (d ln
+ * phi/dT, d ln phi/dP, d ln phi/dx_j) available from system.init(3). This gives quadratic
+ * convergence, typically converging in 3-8 iterations from a good initial estimate.
*
- * g_i = ln K_i + ln phi_i^V - ln phi_i^L, i=0..n-1 (equilibrium) g_n = sum_i z_i*(K_i-1)/(1+beta*(K_i-1))
- * (Rachford-Rice) g_{n+1} = S_T = sum_i s_i * dg_i/d(lnP) (cricondentherm condition: dP/dT = 0) where s_i = (y_i -
- * x_i) normalized
+ * g_i = ln K_i + ln phi_i^V - ln phi_i^L, i=0..n-1 (equilibrium) g_n = sum_i
+ * z_i*(K_i-1)/(1+beta*(K_i-1)) (Rachford-Rice) g_{n+1} = S_T = sum_i s_i * dg_i/d(lnP)
+ * (cricondentherm condition: dP/dT = 0) where s_i = (y_i - x_i) normalized
*
+ * This class centralizes dense linear algebra operations that are reused by multiple thermodynamic
+ * and process modules, so domain classes can focus on model-specific logic.
+ *
+ * The coefficient matrix {@code matrix} and right-hand side {@code rhs} are modified in place,
+ * and {@code rhs} is overwritten with the solution vector on return.
+ *
+ * This method supports one or more right-hand sides in {@code B} and is robust for rank-deficient
+ * or ill-conditioned systems.
+ *
- * Benchmarks JAMA vs EJML linear solve, init level costs, and measures the overhead of T/P derivatives vs
- * composition-only derivatives.
+ * Benchmarks JAMA vs ojAlgo linear solve, init level costs, and measures the overhead of T/P
+ * derivatives vs composition-only derivatives.
*
- * JAMA uses full LU decomposition via double[][] arrays. EJML uses optimized row-major storage with native-tuned
- * operations.
+ * JAMA uses full LU decomposition via double[][] arrays. ojAlgo uses optimized dense storage with
+ * native-tuned operations.
* Features
@@ -51,14 +52,15 @@
* sqp.setObjectiveFunction(x -> computeNPV(x, process));
* sqp.addEqualityConstraint(x -> massBalance(x));
* sqp.addInequalityConstraint(x -> maxPressure - x[0]);
- * sqp.setVariableBounds(new double[] { 50.0, 0.5 }, new double[] { 200.0, 1.0 });
- * sqp.setInitialPoint(new double[] { 100.0, 0.8 });
+ * sqp.setVariableBounds(new double[] {50.0, 0.5}, new double[] {200.0, 1.0});
+ * sqp.setInitialPoint(new double[] {100.0, 0.8});
* SQPoptimizer.OptimizationResult result = sqp.solve();
*
*
* References
*
- *
@@ -150,8 +152,7 @@ public interface ConstraintFunc {
/**
* Default constructor.
*/
- public SQPoptimizer() {
- }
+ public SQPoptimizer() {}
/**
* Constructor with number of variables.
@@ -312,8 +313,8 @@ public OptimizationResult solve() {
// Check KKT optimality (with Lagrange multiplier contributions)
double kktError = computeKKTError(gradF, gEq, hIneq, jacEq, jacIneq, x);
if (kktError < tolerance) {
- converged = true;
- break;
+ converged = true;
+ break;
}
// Solve QP sub-problem for search direction
@@ -328,7 +329,7 @@ public OptimizationResult solve() {
// Update x
for (int i = 0; i < n; i++) {
- x[i] = x[i] + alpha * dx[i];
+ x[i] = x[i] + alpha * dx[i];
}
projectToBounds(x);
@@ -344,21 +345,21 @@ public OptimizationResult solve() {
double[] gEqNew = evaluateConstraints(equalityConstraints, x);
double[] hIneqNew = evaluateConstraints(inequalityConstraints, x);
for (int i = 0; i < gEqNew.length; i++) {
- if (Math.abs(gEqNew[i]) > tolerance * 100.0) {
- feasible = false;
- break;
- }
+ if (Math.abs(gEqNew[i]) > tolerance * 100.0) {
+ feasible = false;
+ break;
+ }
}
if (feasible) {
- for (int j = 0; j < hIneqNew.length; j++) {
- if (hIneqNew[j] < -tolerance * 100.0) {
- feasible = false;
- break;
- }
- }
+ for (int j = 0; j < hIneqNew.length; j++) {
+ if (hIneqNew[j] < -tolerance * 100.0) {
+ feasible = false;
+ break;
+ }
+ }
}
if (feasible && fBest < objectiveFunction.evaluate(xBest)) {
- xBest = Arrays.copyOf(x, n);
+ xBest = Arrays.copyOf(x, n);
}
}
@@ -373,9 +374,11 @@ public OptimizationResult solve() {
double fFinal = objectiveFunction.evaluate(xFinal);
return new OptimizationResult(xFinal, fFinal, iterCount, converged,
- computeKKTError(computeGradient(objectiveFunction, xFinal), evaluateConstraints(equalityConstraints, xFinal),
- evaluateConstraints(inequalityConstraints, xFinal), computeJacobian(equalityConstraints, xFinal),
- computeJacobian(inequalityConstraints, xFinal), xFinal));
+ computeKKTError(computeGradient(objectiveFunction, xFinal),
+ evaluateConstraints(equalityConstraints, xFinal),
+ evaluateConstraints(inequalityConstraints, xFinal),
+ computeJacobian(equalityConstraints, xFinal),
+ computeJacobian(inequalityConstraints, xFinal), xFinal));
}
/**
@@ -386,8 +389,8 @@ public OptimizationResult solve() {
private void projectToBounds(double[] x) {
if (lowerBounds != null && upperBounds != null) {
for (int i = 0; i < n; i++) {
- x[i] = Math.max(x[i], lowerBounds[i]);
- x[i] = Math.min(x[i], upperBounds[i]);
+ x[i] = Math.max(x[i], lowerBounds[i]);
+ x[i] = Math.min(x[i], upperBounds[i]);
}
}
}
@@ -446,7 +449,7 @@ private double[][] computeJacobian(List
- *
*
*
- *
*
@@ -44,18 +44,21 @@ public class SysNewtonRhapsonTPflash implements java.io.Serializable {
/** Cached feed compositions (constant during flash). */
private double[] z;
- // Pre-allocated EJML matrices for zero-allocation Newton steps
+ // Pre-allocated matrices for zero-allocation Newton steps
/** Jacobian matrix (Hessian of Q). */
- private DMatrixRMaj jacMatrix;
+ private Primitive64Store jacMatrix;
/** Residual vector (gradient of Q). */
- private DMatrixRMaj fvecVector;
+ private double[] fvecVector;
+
+ /** Pre-allocated right-hand-side vector for linear solve. */
+ private Primitive64Store rhsVector;
/** Newton step vector. */
- private DMatrixRMaj dxVector;
+ private double[] dxVector;
/** Work copy of Jacobian for LU decomposition. */
- private DMatrixRMaj jacWork;
+ private Primitive64Store jacWork;
/** u-variable array: u[i] = beta * y[i]. */
private double[] uVector;
@@ -63,8 +66,8 @@ public class SysNewtonRhapsonTPflash implements java.io.Serializable {
/** Trial u-vector used by the Armijo line search. */
private double[] uTrialVector;
- /** Pre-allocated EJML LU solver. */
- private transient LinearSolverDense
*
*
*
*
*
*
*