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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ Register configured rotating-equipment maps with
existing `Pump`/`Compressor` remains the chart owner under the canonical edge identity.
`ProcessModel.getPower(...)` includes solved pump/compressor demand inside standalone
`LoopedPipeNetwork` and wrapped `FieldWellNetworkProcessUnit` equipment; use that total
for shared host-power evidence and fail closed on non-finite map results.
for shared host-power evidence and fail closed on non-finite map results. Registration
requires an enabled map before graph mutation, and a chart-backed compressor calculation
failure must remain infeasible rather than falling through to simplified polytropic physics.
Recheck chart availability and the equipment's `solved()` contract for every mapped
calculation. Finite forward results from a failed pressure-target speed search are not
accepted operating points; rejected results must invalidate power and clear stored thermal state.
The dedicated `TwoFluidPipeNetwork` already owns storage-node transients; do not
replace it. Live well/injection coupling must reuse WellSystem/WellFlow physics.

Expand Down
6 changes: 6 additions & 0 deletions docs/development/TASK_LOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ requirement`, or `confidential compressor route`.
**Solution:** Extract the existing `EosMixingRuleHandler` correction and six deterministic `ClassicMixingRuleAccumulatorTest` cases from PR #4306 as a shared foundation. Use calculation-local accumulation in general covolume, classic attraction temperature second derivative, and composition-dependent attraction calculations; retain legacy serial result fields.
**Notes:** All six interleaving regressions fail on the unchanged implementation and pass with the correction. The three-train compressor guide counterfactual removes 39 observed search/replay discrepancies by changing this mixing-rule class alone; single, multi-variable, staged and helper examples remain feasible with unchanged physical limits. Validation used Java 17 with Java 8 source/target; Windows Java 8 remains a CI gate. No compressor solver, tolerance, equation or public API changes. Coverage does not claim every mixing family or concurrent configuration mutation. The mixing-rules guide documents the scope.

### 2026-10-14 — Coordinate mapped-network and compressor speed-solve acceptance
**Type:** E (Feature)
**Keywords:** PR 4328, PR 4326, mapped compressor, pressure target, convergence, stale thermal state, shared power
**Solution:** `LoopedPipeNetwork.calculateHeadLossCompressor`, `MappedCompressorAcceptanceTest`, and the SURF integration guide.
**Notes:** A bounded chart speed search requested 100 bara and returned an internally consistent 11.647013866 bara forward state with `solved() == false`, while the network labelled it a valid chart result and retained 53.131953657 kW. Require the existing equipment convergence contract, an enabled chart, finite non-negative power, and positive finite outlet pressure/temperature before accepting mapped evidence. Reuse the existing infeasible-edge path to invalidate power and clear previous thermal state. Four regressions first establish a valid state, then exercise an unsolved result, chart disablement, non-finite power, and non-finite temperature. Keep the common network boundary in PR #4328 and the compressor's speed-search implementation in PR #4326; no system-specific solver or new constraint framework is introduced.

### 2026-10-14 — Repair shared CI failures and preserve workflow diagnostics
**Type:** G (Workflow)
**Keywords:** PR 4315, hermetic tests, evidence-source-cues, slow shards, required checks, Surefire, CI
Expand Down
9 changes: 9 additions & 0 deletions docs/fielddevelopment/SURF_NETWORK_INTEGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,15 @@ same canonical topology. Both builders require an enabled chart before mutating
preserve the configured model object, stable equipment tag and edge identity, and leave
route-local hydraulic selection unchanged. Fixed-pressure and fixed-differential-pressure
pump builders remain available for screening cases.
The direct `LoopedPipeNetwork` mapped-equipment builders apply the same enabled-chart
precondition before graph mutation. If a configured compressor chart fails during the
network solve, the edge reports non-finite power and an infeasible chart status; it does
not silently continue with the simplified polytropic compressor equations.
Each subsequent evaluation also requires the chart to remain enabled, the compressor's
existing `solved()` contract to pass, and finite non-negative power with positive finite
outlet pressure and temperature. A pressure-target speed search that returns a finite
forward state but misses its target therefore remains infeasible. Rejected results clear
the edge's previous thermal state so later network and shared-power checks cannot reuse it.

`LoopedPipeNetwork.getPower(...)` aggregates the solved shaft power of its compressor and
pump edges. `FieldWellNetworkProcessUnit` exposes that same demand, and normal
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2955,8 +2955,13 @@ public NetworkPipe addCompressor(String fromNode, String toNode, String elementN
* @param elementName element name
* @param compressor pre-configured NeqSim {@link Compressor} with chart
* @return the created element
* @throws IllegalArgumentException if the compressor or enabled chart is missing
*/
public NetworkPipe addCompressorWithChart(String fromNode, String toNode, String elementName, Compressor compressor) {
if (compressor == null || compressor.getCompressorChart() == null
|| !compressor.getCompressorChart().isUseCompressorChart()) {
throw new IllegalArgumentException("Chart-backed compressor requires an enabled compressor chart");
}
NetworkPipe element = addPipe(fromNode, toNode, elementName, 1.0, 0.5);
element.setElementType(NetworkElementType.COMPRESSOR);
element.setCompressorModel(compressor);
Expand Down Expand Up @@ -3013,10 +3018,11 @@ public NetworkPipe addPumpDifferentialPressure(String fromNode, String toNode, S
* @param elementName element name
* @param pump configured pump with head/efficiency/NPSH curves
* @return created pump edge
* @throws IllegalArgumentException if the pump or enabled chart is missing
*/
public NetworkPipe addPumpWithCurve(String fromNode, String toNode, String elementName, Pump pump) {
if (pump == null) {
throw new IllegalArgumentException("Configured pump model cannot be null");
if (pump == null || pump.getPumpChart() == null || !pump.getPumpChart().isUsePumpChart()) {
throw new IllegalArgumentException("Curve-backed pump requires an enabled pump chart");
}
NetworkPipe element = addPipe(fromNode, toNode, elementName, 1.0, 0.3);
element.setElementType(NetworkElementType.PUMP);
Expand Down Expand Up @@ -5573,6 +5579,9 @@ private double calculateHeadLossCompressor(NetworkPipe pipe, SystemInterface flu
if (pipe.getCompressorModel() != null) {
try {
Compressor comp = pipe.getCompressorModel();
if (comp.getCompressorChart() == null || !comp.getCompressorChart().isUseCompressorChart()) {
throw new IllegalStateException("Registered compressor chart is no longer enabled");
}
SystemInterface compFluid = fluid.clone();
compFluid.setPressure(upstreamNode.getPressure() / 1e5, "bara");

Expand All @@ -5585,18 +5594,29 @@ private double calculateHeadLossCompressor(NetworkPipe pipe, SystemInterface flu
comp.run();

double power = comp.getPower("kW");
SystemInterface outletFluid = comp.getOutletStream().getFluid().clone();
double outletPressure = comp.getOutletStream().getPressure("Pa");
double outletTemperature = outletFluid.getTemperature();
if (!comp.solved() || !Double.isFinite(power) || power < 0.0 || !Double.isFinite(outletPressure)
|| outletPressure <= 0.0 || !Double.isFinite(outletTemperature) || outletTemperature <= 0.0) {
throw new IllegalStateException("Compressor chart returned an unsolved or non-finite operating point");
}
pipe.setCompressorPower(power);
pipe.setCompressorEfficiency(comp.getPolytropicEfficiency());
SystemInterface outletFluid = comp.getOutletStream().getFluid().clone();
pipe.setThermodynamicState(compFluid, outletFluid, forward);
pipe.setOutletTemperature(outletFluid.getTemperature());
pipe.setOutletTemperature(outletTemperature);

// Pressure rise from suction to discharge
double pRise = comp.getOutletStream().getPressure("Pa") - upstreamNode.getPressure();
double pRise = outletPressure - upstreamNode.getPressure();
pipe.setFlowRegime("Compressor-Chart");
return -Math.signum(pipe.getFlowRate()) * Math.abs(pRise);
} catch (Exception ex) {
logger.warn("Compressor chart calculation failed for {}: {}", pipe.getName(), ex.getMessage());
pipe.setCompressorPower(Double.NaN);
pipe.setFlowRegime("Compressor-Chart-Infeasible");
pipe.setThermodynamicState(null, null, true);
pipe.setOutletTemperature(Double.NaN);
return 1.0e12;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/neqsim/mcp/engineering-coverage.json
Original file line number Diff line number Diff line change
Expand Up @@ -2623,7 +2623,7 @@
{"id":"neqsim.process.equipment.network.GasLinepackState","kind":"class","source":"src/main/java/neqsim/process/equipment/network/GasLinepackState.java","sourceDigest":"7eb4b8f59fe2601603d5dbd0f882f829122dcf209fc989b0c359d5daa64030d3","domain":"process","skills":["neqsim-capability-map","neqsim-pipeline-and-terminal-networks"],"agents":["capability-scout","engineering-deliverables","field-development","flow-assurance","mechanical-design","network-energy-systems","solve-task"],"capabilities":[],"disposition":"review_required"},
{"id":"neqsim.process.equipment.network.GasQualityMetric","kind":"enum","source":"src/main/java/neqsim/process/equipment/network/GasQualityMetric.java","sourceDigest":"23b8f66a72f2ee31a8a5b3f71c5c2c797d69f4dcee2c3d1d789055bbb484ffa0","domain":"process","skills":[],"agents":[],"capabilities":[],"disposition":"review_required"},
{"id":"neqsim.process.equipment.network.LoopDetector","kind":"class","source":"src/main/java/neqsim/process/equipment/network/LoopDetector.java","sourceDigest":"2862d83ae012efcb8cba56340a85d3b1638379bacc4408b8ee66a32dddd43820","domain":"process","skills":["neqsim-pipeline-and-terminal-networks"],"agents":["field-development","flow-assurance","network-energy-systems"],"capabilities":[],"disposition":"review_required"},
{"id":"neqsim.process.equipment.network.LoopedPipeNetwork","kind":"class","source":"src/main/java/neqsim/process/equipment/network/LoopedPipeNetwork.java","sourceDigest":"6de374779ac1f055a7b82a44b4b1ab33bbb5564825fca12022b3adae8a3412e1","domain":"process","skills":["neqsim-capability-map","neqsim-flow-assurance","neqsim-pipeline-and-terminal-networks","neqsim-production-optimization","neqsim-subsea-and-wells"],"agents":["capability-scout","capacity-increase-screening","ccs-hydrogen","engineering-deliverables","field-development","flow-assurance","lng-value-chain","mechanical-design","network-energy-systems","production-chemistry","solve-task"],"capabilities":["surf-network-hydraulic-fidelity"],"disposition":"registered_anchor"},
{"id":"neqsim.process.equipment.network.LoopedPipeNetwork","kind":"class","source":"src/main/java/neqsim/process/equipment/network/LoopedPipeNetwork.java","sourceDigest":"6d564d66a0d10f33f427c6bbb3e54d4f40e8904d33db08b9aeb1dc0e367c8d95","domain":"process","skills":["neqsim-capability-map","neqsim-flow-assurance","neqsim-pipeline-and-terminal-networks","neqsim-production-optimization","neqsim-subsea-and-wells"],"agents":["capability-scout","capacity-increase-screening","ccs-hydrogen","engineering-deliverables","field-development","flow-assurance","lng-value-chain","mechanical-design","network-energy-systems","production-chemistry","solve-task"],"capabilities":["surf-network-hydraulic-fidelity"],"disposition":"registered_anchor"},
{"id":"neqsim.process.equipment.network.NetworkAvailabilitySchedule","kind":"class","source":"src/main/java/neqsim/process/equipment/network/NetworkAvailabilitySchedule.java","sourceDigest":"8918703d9bd1df161d80fe3431dd1af75b61588082d31848c7a243a90466e73b","domain":"process","skills":["neqsim-pipeline-and-terminal-networks"],"agents":["field-development","flow-assurance","network-energy-systems"],"capabilities":[],"disposition":"review_required"},
{"id":"neqsim.process.equipment.network.NetworkCandidateEvaluation","kind":"class","source":"src/main/java/neqsim/process/equipment/network/NetworkCandidateEvaluation.java","sourceDigest":"fff07066158b002e6c301ba91742e406a1f8a01a020d57051ff57c70a2f04b3e","domain":"process","skills":[],"agents":[],"capabilities":[],"disposition":"review_required"},
{"id":"neqsim.process.equipment.network.NetworkCompositionConvergenceReport","kind":"class","source":"src/main/java/neqsim/process/equipment/network/NetworkCompositionConvergenceReport.java","sourceDigest":"7e64c63388a67b48d6ace9ebcc3077183a0d24facd904d29826aaa7cfbbd5839","domain":"process","skills":[],"agents":[],"capabilities":[],"disposition":"review_required"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.Test;
import neqsim.process.equipment.compressor.Compressor;
import neqsim.process.equipment.pump.Pump;
import neqsim.process.equipment.pipeline.PipeBeggsAndBrills;
import neqsim.process.equipment.reservoir.SimpleReservoir;
import neqsim.process.equipment.stream.Stream;
Expand Down Expand Up @@ -1531,6 +1534,57 @@ void testCompressorElement() {
assertEquals(LoopedPipeNetwork.NetworkElementType.COMPRESSOR, comp.getElementType());
}

/** Chart-backed rotating equipment rejects incomplete maps before mutating the graph. */
@Test
void testMappedRotatingEquipmentRegistrationIsAtomic() {
LoopedPipeNetwork network = new LoopedPipeNetwork("mapped registration");
network.addJunctionNode("suction");
network.addJunctionNode("discharge");

Pump chartlessPump = new Pump("chartless pump", new Stream("pump feed", testGas.clone()));
Compressor chartlessCompressor = new Compressor("chartless compressor",
new Stream("compressor feed", testGas.clone()));

assertThrows(IllegalArgumentException.class,
() -> network.addPumpWithCurve("suction", "discharge", "pump", chartlessPump));
assertThrows(IllegalArgumentException.class,
() -> network.addCompressorWithChart("suction", "discharge", "compressor", chartlessCompressor));
assertTrue(network.getPipeNames().isEmpty());
}

/** A configured compressor-chart failure must not fall through to simplified polytropic physics. */
@Test
void testMappedCompressorFailureFailsClosed() {
LoopedPipeNetwork network = new LoopedPipeNetwork("failed mapped compressor");
network.setFluidTemplate(testGas);
network.setSolverType(LoopedPipeNetwork.SolverType.NEWTON_RAPHSON);
network.setMaxIterations(10);
network.addSourceNode("supply", 30.0, 0.0);
network.addFixedPressureSinkNode("delivery", 60.0);

Compressor failingCompressor = new Compressor("failing compressor",
new Stream("compressor feed", testGas.clone())) {
private static final long serialVersionUID = 1L;

@Override
public void run(UUID id) {
throw new IllegalStateException("synthetic chart failure");
}
};
failingCompressor.getCompressorChart().setCurves(new double[] {298.15, 1.0, 1.0, 1.0}, new double[] {9000.0},
new double[][] {{1000.0, 1500.0, 2000.0}}, new double[][] {{80.0, 70.0, 55.0}},
new double[][] {{72.0, 78.0, 74.0}});
failingCompressor.getCompressorChart().setUseCompressorChart(true);
network.addCompressorWithChart("supply", "delivery", "compressor", failingCompressor);

network.run();

LoopedPipeNetwork.NetworkPipe edge = network.getPipe("compressor");
assertEquals("Compressor-Chart-Infeasible", edge.getFlowRegime());
assertTrue(Double.isNaN(edge.getCompressorPower()));
assertTrue(Double.isNaN(network.getPower("kW")));
}

/**
* Test regulator (pressure reducing valve) element.
*/
Expand Down
Loading
Loading