Pipeline Network Optimization
NeqSim’s LoopedPipeNetwork provides a comprehensive pipeline network solver with formal
optimization, sparse linear algebra, and analytical validation benchmarks.
Overview
| Feature | Class | Description |
|---|---|---|
| Generalized NLP | NetworkOptimizer |
Bounded source, sink, route, valve, compressor, and pump decisions |
| Typed Constraints | NetworkConstraint |
Hard rejection and scaled soft-residual penalties |
| Composable Objectives | NetworkObjective |
Throughput, power, economics, emissions, and Java callbacks |
| Automation | ProcessAutomation |
Discoverable safe network node/edge addresses |
| Sparse Solver | NetworkLinearSolver |
Auto-selects Gaussian, Dense EJML, or Sparse CSC |
| Multiphase Caching | LoopedPipeNetwork |
Reuses Beggs-Brill models across iterations |
| Benchmarks | NetworkValidationBenchmarks |
6 analytical/published verification cases |
Generalized whole-network optimization
Register finite-bounded variables, objective terms, and typed constraints. This fragment
requires an existing LoopedPipeNetwork network containing source field-a, compressor
edge export compressor, route edge north route, and delivery node delivery.
The complete gas and oil notebooks linked below define networks with these equipment types. The
optimizer evaluates hydraulics and point quality on the same candidate and
restores all decision state after every evaluation.
NetworkOptimizer optimizer = new NetworkOptimizer(network);
optimizer.setAlgorithm(NetworkOptimizer.Algorithm.BOBYQA);
optimizer.setDeterministicSeed(42L);
optimizer.addDecisionVariable(new NetworkDecisionVariable(
"source.field-a.rate",
NetworkDecisionVariable.Type.SOURCE_RATE,
"field-a", "kg/hr",
NetworkDecisionVariable.RateBasis.MASS,
10000.0, 200000.0));
optimizer.addDecisionVariable(new NetworkDecisionVariable(
"compressor.export.speed",
NetworkDecisionVariable.Type.COMPRESSOR_SPEED,
"export compressor", "rpm",
NetworkDecisionVariable.RateBasis.NONE,
3000.0, 9000.0));
optimizer.addDecisionVariable(new NetworkDecisionVariable(
"route.north.availability",
NetworkDecisionVariable.Type.ROUTE_ALLOCATION,
"north route", "-",
NetworkDecisionVariable.RateBasis.NONE,
0.01, 1.0));
optimizer.addObjective(NetworkObjectives.maximizeThroughput(1.0));
optimizer.addObjective(
NetworkObjectives.minimizeCompressorPower(0.02));
optimizer.addConstraint(NetworkConstraints.convergence());
optimizer.addConstraint(
NetworkConstraints.nodePressure(
"delivery", 45.0, 80.0, true));
optimizer.addConstraint(
NetworkConstraints.qualityCompliance(true));
NetworkOptimizer.OptimizationResult result = optimizer.optimize();
discoverDecisionVariables(...) registers common source/sink rates, source
pressures, chokes, regulators, compressor/pump speeds, and edge availability.
Use explicit registration when bounds or rate basis differ by asset.
Hard constraints make a candidate infeasible. Soft constraints add the square
of the scaled residual, so a larger physical violation receives a larger
penalty. NetworkCandidateEvaluation reports decisions, rate bases, objective
terms, feasibility, residuals, active constraints, and solver diagnostics as
Java objects and JSON.
BOBYQA is a local derivative-free optimizer. CMA-ES is useful for discontinuous availability/routing responses but requires more evaluations. Both require finite bounds; neither is a proof of global optimality.
ProcessAutomation addresses
Internal network values are discoverable through ProcessAutomation:
allocation network.node.delivery.pressure
allocation network.source.field-a.rate
allocation network.sink.delivery.nomination
allocation network.edge.export.flowRate
allocation network.edge.export.availability
allocation network.choke.well-a.opening
allocation network.regulator.handover.setPoint
allocation network.compressor.export.speed
allocation network.pump.oil booster.speed
Address get/set performs unit conversion and rejects non-writable calculated values. This lets generic automation and agentic optimization inspect network internals without bypassing the bounded network API.
Legacy choke optimization
The NetworkOptimizer replaces gradient-finite-difference with formal bound-constrained
optimization from Apache Commons Math:
- BOBYQA: Derivative-free trust-region (best for 2-20 variables, smooth objectives)
- CMA-ES: Population-based global optimizer (robust for noisy/multi-modal, 5-50 variables)
Java Example
Use these statements inside a method with neqsim.process.equipment.network.* imports,
a Log4j2 Logger logger, and the gas template below. The Python example is independently
runnable and constructs the same network.
neqsim.thermo.system.SystemSrkEos gas =
new neqsim.thermo.system.SystemSrkEos(298.15, 50.0);
gas.addComponent("methane", 0.85);
gas.addComponent("ethane", 0.10);
gas.addComponent("propane", 0.05);
gas.setMixingRule("classic");
LoopedPipeNetwork network = new LoopedPipeNetwork("MyNetwork");
network.setFluidTemplate(gas);
network.setSolverType(LoopedPipeNetwork.SolverType.NEWTON_RAPHSON);
network.setMaxIterations(200);
network.setTolerance(100.0);
// Build network with two independently controlled chokes (BOBYQA needs >= 2 variables).
network.addSourceNode("res1", 200.0, 0.0);
network.addJunctionNode("wh1");
network.addSourceNode("res2", 180.0, 0.0);
network.addJunctionNode("wh2");
network.addJunctionNode("manifold");
network.addFixedPressureSinkNode("export", 50.0);
network.addWellIPR("res1", "wh1", "ipr1", 5e-6, false);
network.addChoke("wh1", "manifold", "choke1", 50.0, 80.0);
network.addWellIPR("res2", "wh2", "ipr2", 4e-6, false);
network.addChoke("wh2", "manifold", "choke2", 45.0, 75.0);
network.addPipe("manifold", "export", "export_pipe", 20000.0, 0.3, 0.00005);
// Create and configure optimizer
NetworkOptimizer optimizer = network.createOptimizer();
optimizer.setAlgorithm(NetworkOptimizer.Algorithm.BOBYQA);
optimizer.setObjectiveType(NetworkOptimizer.ObjectiveType.MAX_PRODUCTION);
optimizer.setMaxEvaluations(300);
// Run optimization
NetworkOptimizer.OptimizationResult result = optimizer.optimize();
logger.info("Production: " + result.totalProductionKgHr + " kg/hr");
logger.info("Converged: " + result.converged);
Legacy objective types
| Type | Description |
|---|---|
MAX_PRODUCTION |
Maximize total mass flow at all sinks |
MAX_REVENUE |
Maximize price-weighted production |
MIN_COMPRESSOR_POWER |
Minimize total compressor power |
MAX_SPECIFIC_PRODUCTION |
Maximize production per unit power |
Convenience Methods
// Quick single-objective optimization
NetworkOptimizer.OptimizationResult result = network.optimizeProductionNLP();
// Multi-objective Pareto front (11 points)
List<NetworkOptimizer.OptimizationResult> pareto = network.optimizeMultiObjective(11);
Multi-Objective Pareto Optimization
The multi-objective method sweeps a weight parameter $w$ from 0 to 1:
\[f(x) = w \cdot \text{production}(x) - (1 - w) \cdot \text{power}(x)\]Each Pareto point represents a different production-vs-power tradeoff. Results include
paretoWeight, totalProductionKgHr, and totalCompressorPowerKW.
Sparse Matrix Solver
NetworkLinearSolver automatically selects the optimal solver for the Newton-Raphson
Schur complement system:
| System Size | Solver | Reason |
|---|---|---|
| n ≤ 30 | Gaussian elimination | Backward compatible, fastest for small systems |
| 30 < n ≤ 100 | Dense EJML LU | Better numerical stability |
| n > 100 | Sparse CSC LU | Exploits sparsity for large networks |
All three solvers are also available directly:
double[] x = NetworkLinearSolver.solve(matA, vecB, n); // Auto-select
double[] x = NetworkLinearSolver.solveGaussian(matA, vecB, n); // Force Gaussian
double[] x = NetworkLinearSolver.solveDense(matA, vecB, n); // Force Dense EJML
double[] x = NetworkLinearSolver.solveSparse(matA, vecB, n); // Force Sparse EJML
Multiphase Stream Caching
The multiphase head loss calculation now caches PipeBeggsAndBrills models on each
NetworkPipe, avoiding re-creation of Stream and solver objects on every iteration.
This is transparent — no API changes required.
Validation Benchmarks
Six analytical/published benchmark cases verify solver accuracy:
| # | Benchmark | Verification |
|---|---|---|
| 1 | Single Pipe (Darcy-Weisbach) | Swamee-Jain analytical pressure drop |
| 2 | Two Parallel Pipes | Known flow split ratio $(D_1/D_2)^{5/2}$ |
| 3 | Triangle Loop | Mass balance conservation at all nodes |
| 4 | HC vs NR Cross-Verification | Both solvers converge to same solution |
| 5 | Pressure Monotonicity | Pressure decreases along flow direction |
| 6 | Sparse vs Dense Agreement | All three solvers produce identical results |
Running Benchmarks
List<NetworkValidationBenchmarks.BenchmarkResult> results =
NetworkValidationBenchmarks.runAllBenchmarks();
for (NetworkValidationBenchmarks.BenchmarkResult r : results) {
logger.info(r.getSummary());
}
Or via the static convenience method on LoopedPipeNetwork:
List<NetworkValidationBenchmarks.BenchmarkResult> results =
LoopedPipeNetwork.runValidationBenchmarks();
for (NetworkValidationBenchmarks.BenchmarkResult r : results) {
logger.info(r.getSummary());
}
Python Example
from neqsim import jneqsim
LoopedPipeNetwork = jneqsim.process.equipment.network.LoopedPipeNetwork
NetworkOptimizer = jneqsim.process.equipment.network.NetworkOptimizer
SystemSrkEos = jneqsim.thermo.system.SystemSrkEos
gas = SystemSrkEos(298.15, 50.0)
gas.addComponent("methane", 0.85)
gas.addComponent("ethane", 0.10)
gas.addComponent("propane", 0.05)
gas.createDatabase(True)
gas.setMixingRule("classic")
gas.init(0)
gas.init(1)
network = LoopedPipeNetwork("GatheringNetwork")
network.setFluidTemplate(gas)
network.setSolverType(LoopedPipeNetwork.SolverType.NEWTON_RAPHSON)
network.setMaxIterations(200)
network.setTolerance(100.0)
# Two independently controlled chokes give BOBYQA two decision variables.
network.addSourceNode("res1", 200.0, 0.0)
network.addSourceNode("res2", 180.0, 0.0)
network.addJunctionNode("wh1")
network.addJunctionNode("wh2")
network.addJunctionNode("manifold")
network.addFixedPressureSinkNode("export", 50.0)
network.addWellIPR("res1", "wh1", "ipr1", 5e-6, False)
network.addWellIPR("res2", "wh2", "ipr2", 4e-6, False)
network.addChoke("wh1", "manifold", "choke1", 50.0, 80.0)
network.addChoke("wh2", "manifold", "choke2", 45.0, 75.0)
network.addPipe("manifold", "export", "export_pipe", 20000.0, 0.3, 0.00005)
network.run()
# Optimize
optimizer = network.createOptimizer()
optimizer.setAlgorithm(NetworkOptimizer.Algorithm.BOBYQA)
optimizer.setMaxEvaluations(300)
result = optimizer.optimize()
if not result.converged:
raise RuntimeError("Network optimization did not converge")
print(f"Production: {result.totalProductionKgHr:.0f} kg/hr")
For generalized problems, construct NetworkDecisionVariable,
NetworkConstraints, and NetworkObjectives through jneqsim exactly as in
Java. The executed gas and oil notebooks below demonstrate JPype collection,
enum, and JSON handling.