diff --git a/.github/skills/neqsim-pipeline-and-terminal-networks/SKILL.md b/.github/skills/neqsim-pipeline-and-terminal-networks/SKILL.md index 13540ec4a90..bc9a7c5f7aa 100644 --- a/.github/skills/neqsim-pipeline-and-terminal-networks/SKILL.md +++ b/.github/skills/neqsim-pipeline-and-terminal-networks/SKILL.md @@ -82,6 +82,10 @@ input: one write atomically assigns exact zero/one availability to the complete controlled edge set and preserves all per-edge hydraulic models. Do not emulate a multi-edge line-up with independent availability actions; that permits partial configurations during candidate application and restoration. +The same selector works for live `WellFlow` injection networks. Update a zone's +lifecycle boundary through `WellFlow.ReservoirLayer.setReservoirPressure(...)` so +the layer pressure and its reservoir stream remain synchronized; do not change the +public pressure field alone in a repeated coupled calculation. The dedicated `TwoFluidPipeNetwork` already owns storage-node transients; do not replace it. Live well/injection coupling must reuse WellSystem/WellFlow physics. diff --git a/docs/fielddevelopment/SURF_NETWORK_INTEGRATION.md b/docs/fielddevelopment/SURF_NETWORK_INTEGRATION.md index 95ed07c770c..fc8a972bd1f 100644 --- a/docs/fielddevelopment/SURF_NETWORK_INTEGRATION.md +++ b/docs/fielddevelopment/SURF_NETWORK_INTEGRATION.md @@ -476,6 +476,17 @@ dual-path configurations, exact restoration, positive flow in both redundant pat network mass conservation. This is synthetic integration evidence, not an outage or brownfield field qualification. +The same line-up contract is exercised for a live two-zone water injector. Primary, +brownfield and dual-path injection configurations feed one canonical live injection-well +node and preserve the existing `WellFlow` zone allocation contract. +`WellFlow.ReservoirLayer.setReservoirPressure(...)` updates the layer boundary and its +owning reservoir stream together for controlled repeated lifecycle solves. It does not +change injectivity, fracture pressure, composition, topology or hydraulic fidelity. The +synthetic acceptance checks selected-path flow, dual-path flow, zone allocation, fracture +limits, exact route restoration, mass conservation and the expected lower injectivity at +higher reservoir pressure; it is not injectivity, fracture, outage or brownfield +qualification. + The synthetic `FieldToFacilityOptimizationAcceptanceTest` connects two live wells and well chokes through exclusive per-well routes to distinct 45 bara HP and 30 bara LP fixed-pressure sinks. Those stable sink streams feed separators, compressor-suction @@ -498,8 +509,10 @@ physical limits to obtain a feasible optimizer result. boundaries, qualified pump/compressor maps, shared host power/capacity constraints and controlled reservoir-pressure updates without duplicating reservoir ownership. The canonical multi-edge line-up contract now supports branch/loop outage and brownfield - actions through #4188/#3154 optimization; next exercise it in the larger production and - injection field cases with qualified facility maps and lifecycle evidence. + actions through #4188/#3154 optimization and is exercised on live production and + multi-zone injection wells with controlled reservoir-pressure updates. Next scale the + same contract to qualified pump/compressor maps, shared host constraints and multi-period + lifecycle evidence. 2. Qualify steady arrival temperature, DNV-ST-F101 and DNV-RP-F109 route cases against independent field, laboratory or commercial-simulator evidence. The software contract for staged OD/ID/wall/material/installation/insulation/stability alternatives now exists with synthetic diff --git a/src/main/java/neqsim/process/equipment/reservoir/WellFlow.java b/src/main/java/neqsim/process/equipment/reservoir/WellFlow.java index 96dd3ed50f8..c57cac612f5 100644 --- a/src/main/java/neqsim/process/equipment/reservoir/WellFlow.java +++ b/src/main/java/neqsim/process/equipment/reservoir/WellFlow.java @@ -16,6 +16,7 @@ import neqsim.process.equipment.valve.ThrottlingValve; import neqsim.process.mechanicaldesign.well.WellFlowMechanicalDesign; import neqsim.thermo.system.SystemInterface; +import neqsim.util.unit.PressureUnit; /** * WellFlow class - Inflow Performance Relationship (IPR) model for reservoir-to-wellbore flow. @@ -236,6 +237,39 @@ public ReservoirLayer(String name, StreamInterface stream, double reservoirPress this.productivityIndex = pi; } + /** + * Update the reservoir pressure owned by this layer. + * + *

+ * The layer pressure and its reservoir stream are updated together so repeated coupled field-network calculations + * do not carry contradictory lifecycle state. This method changes only the reservoir boundary; it does not alter + * injectivity/productivity, fracture pressure, fluid composition, or network topology. + *

+ * + * @param pressure new absolute reservoir pressure + * @param unit pressure unit accepted by {@link PressureUnit} + */ + public void setReservoirPressure(double pressure, String unit) { + double pressureBara = new PressureUnit(pressure, unit).getValue("bara"); + if (!Double.isFinite(pressureBara) || pressureBara <= 0.0) { + throw new IllegalArgumentException("Reservoir pressure must be finite and positive"); + } + this.reservoirPressure = pressureBara; + if (stream != null) { + stream.setPressure(pressureBara, "bara"); + } + } + + /** + * Get the reservoir pressure owned by this layer. + * + * @param unit requested pressure unit accepted by {@link PressureUnit} + * @return reservoir pressure in the requested unit + */ + public double getReservoirPressure(String unit) { + return new PressureUnit(reservoirPressure, "bara").getValue(unit); + } + /** * Set the fracture pressure for this layer. * diff --git a/src/main/resources/neqsim/mcp/engineering-coverage.json b/src/main/resources/neqsim/mcp/engineering-coverage.json index e6becfaf200..44d6b1f972a 100644 --- a/src/main/resources/neqsim/mcp/engineering-coverage.json +++ b/src/main/resources/neqsim/mcp/engineering-coverage.json @@ -2869,7 +2869,7 @@ {"id":"neqsim.process.equipment.reservoir.SimpleReservoir","kind":"class","source":"src/main/java/neqsim/process/equipment/reservoir/SimpleReservoir.java","sourceDigest":"cf508908233aeec20d6ab1b76afe5674218cab06bdbaa885cc7cbd7b18498b16","domain":"process","skills":["neqsim-capability-map","neqsim-dynamic-equipment-implementation","neqsim-field-development","neqsim-integrated-production-and-lifecycle","neqsim-process-extraction","neqsim-production-optimization","neqsim-task-workflow","neqsim-troubleshooting"],"agents":["capability-scout","capacity-increase-screening","dynamic-equipment","engineering-deliverables","extract-process","field-development","gas-processing-refining","mechanical-design","neqsim-test","network-energy-systems","notebook-example","process-model","reaction-engineering","root-cause","rotating-equipment","solve-process","solve-task","thermo-fluid","unisim-reader"],"capabilities":[],"disposition":"review_required"}, {"id":"neqsim.process.equipment.reservoir.TubingPerformance","kind":"class","source":"src/main/java/neqsim/process/equipment/reservoir/TubingPerformance.java","sourceDigest":"f11cc0d0c041ec61600af65a7b8d91df74bb116af658f378c8812f195e5bc0d0","domain":"process","skills":[],"agents":[],"capabilities":[],"disposition":"review_required"}, {"id":"neqsim.process.equipment.reservoir.Well","kind":"class","source":"src/main/java/neqsim/process/equipment/reservoir/Well.java","sourceDigest":"cad09aced79bd293324292757c684a63590b112c61ed5c984de736b5e1c46719","domain":"process","skills":["neqsim-api-patterns","neqsim-capability-map","neqsim-ccs-hydrogen","neqsim-eos-regression","neqsim-field-development","neqsim-field-economics","neqsim-flow-assurance","neqsim-integrated-production-and-lifecycle","neqsim-pipeline-and-terminal-networks","neqsim-platform-modeling","neqsim-process-extraction","neqsim-production-optimization","neqsim-regression-baselines","neqsim-standards-lookup","neqsim-subsea-and-wells","neqsim-task-workflow","neqsim-technical-document-reading","neqsim-tight-reservoir-test-design","neqsim-troubleshooting"],"agents":["capability-scout","capacity-increase-screening","ccs-hydrogen","consequence-analysis","control-system","documentation","dynamic-equipment","emissions-environmental","engineering-deliverables","extract-process","field-development","flow-assurance","gas-processing-refining","gas-quality","literature-scout","lng-value-chain","mechanical-design","neqsim-test","network-energy-systems","notebook-example","optimize","optimize-processmodel","process-model","production-chemistry","pvt-simulation","reaction-engineering","root-cause","rotating-equipment","safety-depressuring","solve-process","solve-task","standards-review","technical-reader","thermo-fluid","unisim-reader","utility-design"],"capabilities":[],"disposition":"review_required"}, - {"id":"neqsim.process.equipment.reservoir.WellFlow","kind":"class","source":"src/main/java/neqsim/process/equipment/reservoir/WellFlow.java","sourceDigest":"60c6f8c7ec6f1b86c74643094effa1447c8db3e861e9911acf6dada9cdae9994","domain":"process","skills":["neqsim-dynamic-equipment-implementation","neqsim-field-development","neqsim-pipeline-and-terminal-networks","neqsim-platform-modeling","neqsim-task-workflow","neqsim-troubleshooting"],"agents":["dynamic-equipment","field-development","flow-assurance","gas-processing-refining","neqsim-test","network-energy-systems","notebook-example","optimize-processmodel","process-model","reaction-engineering","root-cause","rotating-equipment","solve-process","solve-task","thermo-fluid","unisim-reader"],"capabilities":[],"disposition":"review_required"}, + {"id":"neqsim.process.equipment.reservoir.WellFlow","kind":"class","source":"src/main/java/neqsim/process/equipment/reservoir/WellFlow.java","sourceDigest":"1f069cbbe2bef5c1aea7b1b632b77fa0244c3e6621ec66e552831c58dfedb040","domain":"process","skills":["neqsim-dynamic-equipment-implementation","neqsim-field-development","neqsim-pipeline-and-terminal-networks","neqsim-platform-modeling","neqsim-task-workflow","neqsim-troubleshooting"],"agents":["dynamic-equipment","field-development","flow-assurance","gas-processing-refining","neqsim-test","network-energy-systems","notebook-example","optimize-processmodel","process-model","reaction-engineering","root-cause","rotating-equipment","solve-process","solve-task","thermo-fluid","unisim-reader"],"capabilities":[],"disposition":"review_required"}, {"id":"neqsim.process.equipment.reservoir.WellSystem","kind":"class","source":"src/main/java/neqsim/process/equipment/reservoir/WellSystem.java","sourceDigest":"5a9c015750f7f0c8f8691db3523507db6b58f4dec5c6977e1e98202a17edcca4","domain":"process","skills":["neqsim-field-development","neqsim-integrated-production-and-lifecycle","neqsim-pipeline-and-terminal-networks","neqsim-production-optimization"],"agents":["capacity-increase-screening","field-development","flow-assurance","network-energy-systems"],"capabilities":[],"disposition":"review_required"}, {"id":"neqsim.process.equipment.reservoir.WellUnloadingScreening","kind":"class","source":"src/main/java/neqsim/process/equipment/reservoir/WellUnloadingScreening.java","sourceDigest":"55a5c3c86928d0c6436f7c2017196005640dc354cd799df96c76aa598018e896","domain":"process","skills":[],"agents":[],"capabilities":[],"disposition":"review_required"}, {"id":"neqsim.process.equipment.separator.CryogenicSeparator","kind":"class","source":"src/main/java/neqsim/process/equipment/separator/CryogenicSeparator.java","sourceDigest":"f5ebcf4f51f5d5889167ac94a2994b685920fd2c5af96d9a178d1727a3c51098","domain":"process","skills":["neqsim-produced-water-and-solids-separation"],"agents":["gas-processing-refining","production-chemistry"],"capabilities":[],"disposition":"review_required"}, diff --git a/src/test/java/neqsim/process/equipment/network/FieldWellNetworkProcessUnitTest.java b/src/test/java/neqsim/process/equipment/network/FieldWellNetworkProcessUnitTest.java index e8378f02359..ce6fbec634f 100644 --- a/src/test/java/neqsim/process/equipment/network/FieldWellNetworkProcessUnitTest.java +++ b/src/test/java/neqsim/process/equipment/network/FieldWellNetworkProcessUnitTest.java @@ -202,6 +202,73 @@ void switchesCanonicalMultiEdgeRouteConfigurationsThroughAutomation() { unit.getHydraulicNetwork().getPipe("primary flowline").setAvailability(1.0); } + /** Live injection must switch complete line-ups and respond to controlled reservoir-pressure updates. */ + @Test + void switchesLiveInjectionRouteConfigurationsAndReservoirState() { + WellFlow injectionWell = createConfiguredInjectionWell(); + FieldWellNetworkProcessUnit unit = createConfiguredInjectionUnit(injectionWell); + ProcessSystem injection = new ProcessSystem("injection field"); + injection.add(unit); + ProcessModel model = new ProcessModel(); + model.add("Injection", injection); + model.run(); + + assertTrue(model.isModelConverged()); + assertTrue(unit.solved(), unit.getLastCouplingResult().getMessage()); + String address = "Injection::configured injection network.configuration.injector route.selection"; + ProcessAutomation automation = model.getAutomation(); + assertTrue(automation.isWritableAddress(address)); + double primaryRate = unit.getLastCouplingResult().getWellResults().get(0).getAppliedRateKgS(); + assertTrue(primaryRate > 0.0); + assertFalse(injectionWell.getZoneFractureRisk()[0]); + assertFalse(injectionWell.getZoneFractureRisk()[1]); + + automation.setVariableValue(address, 1.0, "-"); + model.run(); + assertTrue(unit.solved(), couplingDiagnostic(unit)); + assertTrue(model.isModelConverged(), couplingDiagnostic(unit)); + double brownfieldRate = unit.getLastCouplingResult().getWellResults().get(0).getAppliedRateKgS(); + assertTrue(brownfieldRate > 0.0); + assertEquals(0.0, unit.getHydraulicNetwork().getPipe("primary injection flowline").getFlowRate(), 1.0e-12); + assertTrue(unit.getHydraulicNetwork().getPipe("brownfield injection flowline").getFlowRate() > 0.0); + + automation.setVariableValue(address, 2.0, "-"); + model.run(); + assertTrue(unit.solved(), couplingDiagnostic(unit)); + assertTrue(model.isModelConverged(), couplingDiagnostic(unit)); + double dualPathRate = unit.getLastCouplingResult().getWellResults().get(0).getAppliedRateKgS(); + assertTrue(dualPathRate > 0.0); + assertTrue(unit.getHydraulicNetwork().getPipe("primary injection flowline").getFlowRate() > 0.0); + assertTrue(unit.getHydraulicNetwork().getPipe("brownfield injection flowline").getFlowRate() > 0.0); + assertEquals(0.0, unit.getLastCouplingResult().getMassBalanceResidualKgS(), 1.0e-8); + assertEquals(1.0, injectionWell.getZoneAllocationFractions()[0] + injectionWell.getZoneAllocationFractions()[1], + 1.0e-12); + + automation.setVariableValue(address, 0.0, "-"); + model.run(); + assertEquals(primaryRate, unit.getLastCouplingResult().getWellResults().get(0).getAppliedRateKgS(), + primaryRate * 1.0e-4); + injectionWell.getLayer(0).setReservoirPressure(115.0, "bara"); + injectionWell.getLayer(1).setReservoirPressure(125.0, "bara"); + assertEquals(115.0, injectionWell.getLayer(0).getReservoirPressure("bara"), 0.0); + assertEquals(115.0, injectionWell.getLayer(0).stream.getPressure("bara"), 0.0); + model.run(); + double higherReservoirPressureRate = unit.getLastCouplingResult().getWellResults().get(0).getAppliedRateKgS(); + assertTrue(higherReservoirPressureRate < primaryRate, + "Higher reservoir pressure must reduce injectivity at unchanged source pressure and route"); + assertTrue(unit.solved(), unit.getLastCouplingResult().getMessage()); + assertFalse(injectionWell.getZoneFractureRisk()[0]); + assertFalse(injectionWell.getZoneFractureRisk()[1]); + } + + /** Build a compact failure diagnostic for coupled acceptance assertions. */ + private String couplingDiagnostic(FieldWellNetworkProcessUnit unit) { + return unit.getLastCouplingResult().getMessage() + "; iterations=" + unit.getLastCouplingResult().getIterations() + + "; rate residual=" + unit.getLastCouplingResult().getMaximumRateResidualKgS() + "; pressure residual=" + + unit.getLastCouplingResult().getMaximumPressureResidualBar() + "; network residual=" + + unit.getLastCouplingResult().getNetworkResidualPa(); + } + /** The process unit must remain unsolved when the complete coupled convergence contract is not met. */ @Test void incompleteCouplingFailsClosed() { @@ -335,6 +402,61 @@ private FieldWellNetworkProcessUnit createConfiguredUnit() { "synthetic direct, brownfield and redundant host line-ups"); } + /** Create a live two-zone injector for route and lifecycle acceptance. */ + private WellFlow createConfiguredInjectionWell() { + WellFlow well = new WellFlow("configured injection well"); + well.setInletStream(new Stream("injection water", water(180.0))); + well.addInjectionZone("target", new Stream("target reservoir zone", water(100.0)), 100.0, 7.0e-8, 205.0); + well.addInjectionZone("secondary", new Stream("secondary reservoir zone", water(110.0)), 110.0, 3.0e-8, 200.0); + well.setTargetZone("target"); + return well; + } + + /** Create primary, brownfield and dual-path injection line-ups on one canonical graph. */ + private FieldWellNetworkProcessUnit createConfiguredInjectionUnit(WellFlow well) { + FieldNetworkTopology topology = new FieldNetworkTopology("configured injection network hydraulics"); + LoopedPipeNetwork network = topology.getHydraulicNetwork(); + network.setFluidTemplate(water(180.0)); + network.setSolverType(LoopedPipeNetwork.SolverType.NEWTON_RAPHSON); + network.setTolerance(3000.0); + topology.addPressureSource("injection source", "P-401-D", NodeRole.INJECTION_SOURCE, Service.INJECTION, 180.0, 0.0, + 0.0); + topology.addJunction("primary injection PLEM", "PLEM-I401", NodeRole.PLEM, Service.INJECTION, 0.0); + topology.addJunction("brownfield injection tie-in", "TI-I401", NodeRole.BROWNFIELD_TIE_IN, Service.INJECTION, 0.0); + topology.addLiveWellNode("injector", "XT-I401", NodeRole.INJECTION_WELL, Service.INJECTION, 145.0, 0.0); + topology.addPipe("primary injection flowline", "WI-401", EdgeRole.FLOWLINE, Service.INJECTION, + FlowDirection.FROM_TO, "injection source", "primary discharge", "primary injection PLEM", "inlet", 900.0, 0.22, + LoopedPipeNetwork.PipeModelType.DARCY_WEISBACH); + topology.addPipe("primary injection jumper", "JU-I401", EdgeRole.JUMPER, Service.INJECTION, FlowDirection.FROM_TO, + "primary injection PLEM", "outlet", "injector", "primary injection", 700.0, 0.22, + LoopedPipeNetwork.PipeModelType.DARCY_WEISBACH); + topology.addPipe("brownfield injection flowline", "WI-402", EdgeRole.TIE_IN, Service.INJECTION, + FlowDirection.FROM_TO, "injection source", "brownfield discharge", "brownfield injection tie-in", "inlet", + 2800.0, 0.16, LoopedPipeNetwork.PipeModelType.DARCY_WEISBACH); + topology.addPipe("brownfield injection jumper", "JU-I402", EdgeRole.JUMPER, Service.INJECTION, + FlowDirection.FROM_TO, "brownfield injection tie-in", "outlet", "injector", "brownfield injection", 900.0, 0.16, + LoopedPipeNetwork.PipeModelType.DARCY_WEISBACH); + network.getPipe("brownfield injection flowline").setAvailability(0.0); + network.getPipe("brownfield injection jumper").setAvailability(0.0); + + FieldWellNetworkCoupler coupler = new FieldWellNetworkCoupler(topology); + coupler.setMaximumIterations(200); + coupler.setTolerances(2.0e-7, 3.0e-5); + coupler.setRelaxationFactor(0.6); + coupler.bindInjectionWell("injector", well); + return new FieldWellNetworkProcessUnit("configured injection network", coupler) + .registerRouteConfigurationSelector("injector route", + Arrays.asList( + new FieldWellNetworkProcessUnit.RouteConfiguration("primary", + Arrays.asList("primary injection flowline", "primary injection jumper")), + new FieldWellNetworkProcessUnit.RouteConfiguration("brownfield", + Arrays.asList("brownfield injection flowline", "brownfield injection jumper")), + new FieldWellNetworkProcessUnit.RouteConfiguration("dual path", + Arrays.asList("primary injection flowline", "primary injection jumper", + "brownfield injection flowline", "brownfield injection jumper"))), + "synthetic primary, brownfield and redundant injection line-ups"); + } + /** Assert exact availability on the four controlled configuration edges. */ private void assertConfiguration(FieldWellNetworkProcessUnit unit, double primaryFlowline, double primaryRiser, double brownfieldFlowline, double brownfieldRiser) { @@ -359,4 +481,18 @@ private SystemInterface gas(double pressureBara) { fluid.init(0); return fluid; } + + /** + * Create a single-component water fluid. + * + * @param pressureBara pressure in bara + * @return initialized water fluid + */ + private SystemInterface water(double pressureBara) { + SystemInterface fluid = new SystemSrkEos(298.15, pressureBara); + fluid.addComponent("water", 1.0); + fluid.setMixingRule("classic"); + fluid.init(0); + return fluid; + } } diff --git a/src/test/java/neqsim/process/equipment/reservoir/WellFlowTest.java b/src/test/java/neqsim/process/equipment/reservoir/WellFlowTest.java index d47cfad3931..135b1252e7d 100644 --- a/src/test/java/neqsim/process/equipment/reservoir/WellFlowTest.java +++ b/src/test/java/neqsim/process/equipment/reservoir/WellFlowTest.java @@ -1,5 +1,7 @@ package neqsim.process.equipment.reservoir; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.junit.jupiter.api.Test; @@ -257,4 +259,23 @@ void testVogelCorrelation() { assert (Math.abs(wellflow.getOutletStream().getPressure("bara") - 100.0) < 1.0); } + + /** Reservoir-layer pressure updates keep the typed boundary and its stream synchronized. */ + @Test + void updatesReservoirLayerPressureWithUnitsAndValidation() { + neqsim.thermo.system.SystemInterface fluid = new neqsim.thermo.system.SystemSrkEos(298.15, 100.0); + fluid.addComponent("water", 1.0); + fluid.setMixingRule("classic"); + StreamInterface zone = new neqsim.process.equipment.stream.Stream("injection zone", fluid); + WellFlow well = new WellFlow("injection well"); + well.setInletStream(new neqsim.process.equipment.stream.Stream("injection fluid", fluid.clone())); + well.addInjectionZone("target", zone, 100.0, 1.0e-8, 200.0); + + well.getLayer(0).setReservoirPressure(1450.38, "psia"); + + assertEquals(100.0, well.getLayer(0).getReservoirPressure("bara"), 1.0e-3); + assertEquals(well.getLayer(0).getReservoirPressure("bara"), zone.getPressure("bara"), 1.0e-12); + assertThrows(IllegalArgumentException.class, () -> well.getLayer(0).setReservoirPressure(Double.NaN, "bara")); + assertThrows(IllegalArgumentException.class, () -> well.getLayer(0).setReservoirPressure(0.0, "bara")); + } }