Skip to the content.

Out-of-Zone Injection Modeling

Out-of-zone injection occurs when injected fluid (water, gas, or CO2) migrates outside the intended target reservoir interval. This can happen through:

NeqSim provides a suite of interconnected classes for modeling these mechanisms, assessing risk, and monitoring injection conformance.

Architecture Overview

Class Package Purpose
WellFlow (injection mode) process.equipment.reservoir Multi-zone injectivity with fracture containment
InjectionWellModel process.fielddevelopment.reservoir Multi-zone injection allocation with thermal stress
AnnularLeakagePath process.equipment.reservoir Behind-casing leakage (cubic law + Darcy) and MAASP per API RP 90
MultiCompartmentReservoir process.equipment.reservoir Inter-zone pressure communication
CementDegradationModel process.equipment.reservoir Time-dependent cement permeability under CO2
InjectionConformanceMonitor process.equipment.reservoir Hall plot analysis and conformance diagnosis

Multi-Zone Injection (WellFlow)

The WellFlow class supports injection mode via the FlowMode.INJECTION enum. Each reservoir layer can have a fracture pressure and barrier stress contrast for containment assessment.

Setting Up an Injection Well

// Create reservoir fluid
SystemInterface fluid = new SystemSrkEos(273.15 + 80.0, 250.0);
fluid.addComponent("water", 1.0);
fluid.setMixingRule("classic");

Stream injStream = new Stream("inj", fluid);
injStream.setFlowRate(5000.0, "kg/hr");
injStream.run();

// Create well in injection mode
WellFlow well = new WellFlow("INJ-1");
well.setInletStream(injStream);
well.setFlowMode(WellFlow.FlowMode.INJECTION);

// Add injection zones with injectivity and fracture data
well.addInjectionZone("Target Sand", injStream, 250.0, 1e-3, 350.0);
well.addInjectionZone("Thief Zone", injStream, 230.0, 2e-3, 300.0);
well.setTargetZone("Target Sand");
well.run();

// Assess results
double efficiency = well.getInjectionEfficiency();
double oozRate = well.getOutOfZoneRate("kg/hr");

Fracture Containment Check

Each ReservoirLayer tracks fracture pressure and barrier stress contrast:

WellFlow.ReservoirLayer layer = new WellFlow.ReservoirLayer("Zone A", stream, 250.0, 1e-3);
layer.setFracturePressure(350.0, "bara");
layer.setBarrierStressContrast(20.0, "bara");

// Check if BHP is safely below fracture propagation limit
boolean contained = layer.isFractureContained(360.0); // true if net pressure < barrier

// Get safety margin (positive = safe, negative = risk)
double margin = layer.getFractureContainmentMargin(340.0); // bar

The containment check uses: net pressure = BHP - fracture pressure. If net pressure < barrier stress contrast, fracture is contained within the zone.

Multi-Zone Injection Model (InjectionWellModel)

The InjectionWellModel provides rate allocation across multiple zones based on their individual injectivity indices, plus thermal stress effects on effective fracture pressure.

This is a steady water-injection screening calculation. For each zone it evaluates the radial Darcy coefficient in m3/s/Pa, converts it with 86400 s/day * 100000 Pa/bar, and divides by the configured formation-water volume factor in reservoir m3/Sm3. The default factor is 1.0, which preserves the established screening result; configure an independently justified value before interpreting the output as a standard-volume PVT conversion. Multi-zone results produced before the dimensional correction in #4228 were lower by a factor of 1e10 and must be recalculated rather than reused.

An opt-in IAPWS_IF97_PURE_WATER property basis reuses NeqSim’s canonical IF97 specific-volume implementation and IAPWS-fitted water-viscosity correlation. It evaluates every zone at its own reservoir pressure and the configured reservoir temperature. The volume factor is the ratio of the reservoir specific volume to the IF97 value at 288.15 K and 1.01325 bara; the result records each zone’s applied pressure, viscosity and volume factor. The implementation fails closed outside 275.15–573.15 K, 1–700 bara or the IF97 Region-1 liquid state. Constant configured inputs remain the default and serialized legacy behavior.

The reference tests reproduce the official IF97 Region-1 specific-volume verification point at 300 K and 3 MPa and compare the fitted viscosity with the IAPWS R12-08 298.15 K, 998 kg/m3 sample. See the IAPWS-IF97 release and the IAPWS viscosity release. This evidence is limited to ordinary pure water. Salinity, dissolved gas, relative permeability, formation damage, crossflow, reservoir transients and fracture growth still require separate qualified inputs and models.

The same immutable radial-Darcy law can be attached to a live WellFlow zone with addRadialDarcyWaterInjectionZone. The live path accepts only the ordinary pure-water basis, reevaluates each zone at its current reservoir pressure, and converts the reported Sm3/day to the canonical network kg/s using the law’s explicit IF97 standard density. Its most recent pressure, viscosity, volume factor, standard density, injectivity and rate are available from ReservoirLayer.getRadialDarcyWaterInjectivityResult(). A controlled reservoir-pressure update therefore changes live well demand without rebuilding the field graph. Constant configured PVT is intentionally not accepted by this bridge because it does not define the standard density needed for the mass conversion. This shares one software law; it does not turn the screening model into field-qualified injectivity or add the excluded physics above.

The common BHP is solved from the piecewise-linear accepting-zone equation $q = \sum_i II_i\max(p_{bh}-p_{r,i},0)$. A zone whose reservoir pressure is at or above the solved BHP remains shut in and is excluded before the BHP is recomputed. This prevents a closed, higher-pressure zone from biasing the pressure and over-allocating the requested rate to lower-pressure zones. The result records the requested rate, unconstrained required BHP, applied pressure limit and whether that limit caused a rate shortfall. The calculation does not model inter-zone crossflow.

Zone Allocation

InjectionWellModel model = new InjectionWellModel();
model.setMaxBHP(350.0, "bara");
model.setDrainageRadius(500.0);
model.setWellboreRadius(0.1);
model.setFormationWaterViscosity(0.45); // cP at reservoir conditions
model.setFormationWaterVolumeFactor(1.02); // reservoir m3/Sm3

// Define injection zones
InjectionWellModel.InjectionZone target = new InjectionWellModel.InjectionZone(
    "Target Sand", 2500.0, 250.0, 100.0, 30.0, 350.0);
target.skinFactor = 2.0;
target.isTargetZone = true;

InjectionWellModel.InjectionZone thief = new InjectionWellModel.InjectionZone(
    "Thief Zone", 2600.0, 230.0, 500.0, 10.0, 300.0);

model.addZone(target);
model.addZone(thief);

// Calculate allocation for target rate
InjectionWellModel.MultiZoneInjectionResult result =
    model.calculateMultiZone(10000.0);

System.out.println("Total rate: " + result.totalRate + " Sm3/day");
System.out.println("Injection efficiency: " + result.injectionEfficiency);
System.out.println("Out-of-zone rate: " + result.outOfZoneRate + " Sm3/day");
System.out.println("Required BHP: " + result.requiredBHP + " bara");
System.out.println("Pressure limited: " + result.limitedByPressure);

Thermal Stress Effects

Cold-water injection reduces the effective fracture pressure due to thermal contraction of the near-wellbore rock:

\[\Delta\sigma_{thermal} = \frac{\alpha \cdot E \cdot \Delta T}{1 - \nu}\]
// Injection at 30°C into 80°C reservoir
model.setThermalStressReduction(303.15, 353.15, 1.2e-5, 20.0);
model.setPoissonsRatio(0.25);

double thermalReduction = model.getThermalStressReduction(); // bar
double effectiveFracP = model.getEffectiveFracturePressure(); // bar

Annular Leakage (AnnularLeakagePath)

Models fluid leakage behind casing through two mechanisms:

AnnularLeakagePath leakage = new AnnularLeakagePath("cement leak");
leakage.setLeakageMechanism(AnnularLeakagePath.LeakageMechanism.COMBINED);
leakage.setPathGeometry(1500.0, 1600.0, 0.10, 0.001); // 100m path, 1mm gap
leakage.setCementPermeability(0.001, "mD");
leakage.setCementCrossSectionArea(0.01); // m2
leakage.setFluidViscosity(0.001); // Pa.s

leakage.calculate(350.0, 250.0); // source 350 bara, sink 250 bara

double channelRate = leakage.getChannelLeakageRate("m3/day");
double cementRate = leakage.getCementLeakageRate("m3/day");
double totalRate = leakage.getTotalLeakageRate("m3/day");

MAASP Calculation (API RP 90)

The AnnularLeakagePath also calculates Maximum Allowable Annular Surface Pressure (MAASP) per API RP 90. MAASP is the minimum of three independent limits:

\[\text{MAASP} = \min\left(\frac{P_{burst}}{SF_{burst}},\; \frac{P_{collapse}}{SF_{collapse}},\; P_{frac} - \rho g h\right)\]

where:

AnnularLeakagePath leakage = new AnnularLeakagePath("A-annulus");

// Set MAASP parameters
leakage.setMAASPParameters(
    620.0,    // casing burst rating (bara)
    480.0,    // tubing collapse rating (bara)
    2500.0,   // shoe depth (m)
    500.0,    // fracture pressure at shoe (bara)
    0.098     // annular fluid gradient (bar/m) — completion brine
);

// Optional: override API RP 90 default safety factors
leakage.setMAASPSafetyFactors(1.10, 1.00);

// Calculate
double maasp = leakage.calculateMAASP();
String limiting = leakage.getMAASPLimitingCriterion();
// Returns: "Fracture at shoe", "Casing burst", or "Tubing collapse"

// Check if monitored annulus pressure is safe
boolean exceeded = leakage.isAnnularPressureExceeded(300.0);

The limiting criterion tells the operator which constraint controls, guiding the annular pressure management strategy.

Multi-Compartment Reservoir

Tracks pressure evolution across connected reservoir compartments using material balance with inter-zone transmissibility:

\[V_i \cdot c_{t,i} \cdot \frac{dP_i}{dt} = q_{inj,i} - q_{prod,i} + \sum_j T_{ij}(P_j - P_i)\]
MultiCompartmentReservoir reservoir = new MultiCompartmentReservoir("Field");

// Add compartments (name, fluid, poreVolume_m3, pressure_bara)
reservoir.addCompartment("Target Sand", targetFluid, 1.0e7, 250.0);
reservoir.addCompartment("Thief Zone", thiefFluid, 5.0e6, 230.0);
reservoir.addCompartment("Aquifer", aquiferFluid, 1.0e9, 260.0);

// Set compressibility and inter-zone transmissibility
reservoir.setCompressibility("Target Sand", 1e-4);
reservoir.setCompressibility("Thief Zone", 2e-4);
reservoir.setTransmissibility("Target Sand", "Thief Zone", 0.5);
reservoir.setTransmissibility("Target Sand", "Aquifer", 0.1);

// Add wells
reservoir.addInjectionRate("INJ-1", "Target Sand", 5000.0);
reservoir.addProductionRate("PROD-1", "Target Sand", 3000.0);

// Time-step for 365 days
for (int day = 0; day < 365; day++) {
    reservoir.runTimeStep(86400.0); // 1 day in seconds
    double pTarget = reservoir.getCompartmentPressure("Target Sand", "bara");
    double crossflow = reservoir.getInterZoneFlowRate(
        "Target Sand", "Thief Zone", "Sm3/day");
}

Cement Degradation (CO2 Wells)

Critical for CCS projects: models time-dependent cement degradation under CO2 exposure.

The carbonation front advances as:

\[d(t) = A \sqrt{D_{eff} \cdot t}\]

where $D_{eff}$ depends on temperature (Arrhenius) and CO2 partial pressure.

CementDegradationModel cement = new CementDegradationModel("Prod Casing Cement");
cement.setCementType(CementDegradationModel.CementType.PORTLAND);
cement.setInitialPermeability(0.001, "mD");
cement.setCementThickness(0.05, "m"); // 50 mm

// 10 bar CO2, 60°C
cement.setCO2Conditions(10.0, 273.15 + 60.0);

// Assess at 30 years
double depth30 = cement.getDegradationDepth(30.0, "mm");
double perm30 = cement.getPermeabilityAtTime(30.0, "mD");
boolean compromised = cement.isCementCompromised(30.0);
double timeToFull = cement.getTimeToFullCarbonation("years");

Cement Types

Type Description Carbonation Rate Factor
PORTLAND Standard Class G/H 1.0 (baseline)
SILICA_PORTLAND Portland + silica flour 0.6 (more resistant)
CO2_RESISTANT Calcium aluminate / geopolymer 0.2 (most resistant)

Injection Conformance Monitoring

The InjectionConformanceMonitor uses Hall plot analysis and injection profile data to diagnose conformance issues.

Hall Plot Analysis

The Hall plot cumulates (WHP $\times$ $\Delta t$) vs. cumulative injection. Slope changes indicate:

InjectionConformanceMonitor monitor = new InjectionConformanceMonitor("INJ-1 Monitor");

// Record daily injection data (time_days, whp_bar, rate_m3/d)
for (int day = 0; day < 100; day++) {
    double whp = 250.0 + day * 0.1;  // slowly rising
    double rate = 5000.0;
    monitor.recordInjectionData(day, whp, rate);
}

// Analyze
monitor.calculateHallPlot();
double currentSlope = monitor.getCurrentHallSlope();
double initialSlope = monitor.getInitialHallSlope();

// Detect changes
boolean changed = monitor.detectSlopeChange(0.2); // 20% threshold
String diagnosis = monitor.getDiagnosis();
// Returns: NORMAL, FRACTURE_GROWTH, PLUGGING, or OUT_OF_ZONE_SUSPECTED

Injection Profile Analysis

// Add zone profile data from production logging
monitor.addZoneProfile("Target Sand", 2500.0, 0.6, true);   // 60% of injection
monitor.addZoneProfile("Thief Zone", 2600.0, 0.3, false);   // 30% thief zone
monitor.addZoneProfile("Unconfirmed", 2700.0, 0.1, false);  // 10% unknown

double efficiency = monitor.getInjectionEfficiency();    // 0.6
double oozFraction = monitor.getOutOfZoneFraction();     // 0.4

Integrated Workflow

A typical out-of-zone injection risk assessment combines multiple classes:

                           ┌──────────────────────┐
                           │ InjectionWellModel    │
                           │ (zone allocation +    │
                           │  thermal stress)      │
                           └──────────┬───────────┘
                                      │
              ┌───────────────────────┼───────────────────────┐
              ▼                       ▼                       ▼
   ┌──────────────────┐   ┌──────────────────┐   ┌──────────────────┐
   │ WellFlow          │   │ AnnularLeakage   │   │ CementDegradation│
   │ (IPR + fracture   │   │ (cubic law +     │   │ (CO2 carbonation │
   │  containment)     │   │  Darcy flow)     │   │  over time)      │
   └────────┬─────────┘   └────────┬─────────┘   └────────┬─────────┘
            │                      │                       │
            ▼                      ▼                       ▼
   ┌──────────────────────────────────────────────────────────────┐
   │ MultiCompartmentReservoir                                    │
   │ (pressure response across connected zones)                   │
   └──────────────────────────────────────────────────────────────┘
            │
            ▼
   ┌──────────────────┐
   │ Conformance       │
   │ Monitor           │
   │ (Hall plot +      │
   │  diagnostics)     │
   └──────────────────┘

Step-by-Step Assessment

  1. Define zones: Create injection zones with injectivity, fracture pressure, and barrier stress
  2. Thermal stress: Calculate effective fracture pressure under cold injection
  3. Zone allocation: Run InjectionWellModel.calculateMultiZone() to get rates per zone
  4. Leakage paths: Model annular leakage with AnnularLeakagePath
  5. Cement integrity: Assess long-term cement condition with CementDegradationModel
  6. Pressure response: Time-step MultiCompartmentReservoir to track inter-zone flow
  7. Surveillance: Feed injection data into InjectionConformanceMonitor for diagnostics