ProductionSystem BottleneckAnalysis
Note: This is an auto-generated Markdown version of the Jupyter notebook
ProductionSystem_BottleneckAnalysis.ipynb. You can also view it on nbviewer or open in Google Colab.
Production System Optimization & Bottleneck Analysis
This notebook demonstrates advanced production system modeling with NeqSim, including:
- Multi-well production system with pipelines, separators, valves, and compressors
- Auto-design of equipment based on operating conditions
- Bottleneck identification - finding system constraints
- Well prioritization - which wells to produce under different scenarios
- GOR and Water Cut impact on system capacity
System Overview
Well A (Low GOR) ──┐
├── Manifold ── Pipeline ── Separator ── Gas Compressor ── Export
Well B (High GOR) ──┤ │
│ └── Oil/Water ── Export
Well C (High WC) ──┘
1. Setup and Imports
# Import NeqSim
from neqsim import jneqsim
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import warnings
warnings.filterwarnings('ignore')
# Thermo systems
SystemSrkEos = jneqsim.thermo.system.SystemSrkEos
ThermodynamicOperations = jneqsim.thermodynamicoperations.ThermodynamicOperations
# Process equipment
ProcessSystem = jneqsim.process.processmodel.ProcessSystem
Stream = jneqsim.process.equipment.stream.Stream
Mixer = jneqsim.process.equipment.mixer.Mixer
Separator = jneqsim.process.equipment.separator.Separator
ThreePhaseSeparator = jneqsim.process.equipment.separator.ThreePhaseSeparator
Compressor = jneqsim.process.equipment.compressor.Compressor
ThrottlingValve = jneqsim.process.equipment.valve.ThrottlingValve
Cooler = jneqsim.process.equipment.heatexchanger.Cooler
print("NeqSim process equipment classes loaded successfully!")
Output
``` NeqSim process equipment classes loaded successfully! ```2. Define Well Fluids with Different Characteristics
We model three wells with different production characteristics:
- Well A: Low GOR, Low Water Cut (best producer)
- Well B: High GOR (gas-rich well)
- Well C: High Water Cut (mature well)
def create_well_fluid(gor_type='medium', water_cut=0.0, temperature_K=350.0, pressure_bara=80.0):
"""
Create a well fluid with specified GOR and water cut characteristics.
Parameters:
- gor_type: 'low', 'medium', 'high' (affects methane content)
- water_cut: fraction of water (0.0 to 0.8)
- temperature_K: wellhead temperature
- pressure_bara: wellhead pressure
"""
fluid = SystemSrkEos(temperature_K, pressure_bara)
# Adjust composition based on GOR type
if gor_type == 'low':
# Low GOR - more heavy components
fluid.addComponent("nitrogen", 0.005)
fluid.addComponent("CO2", 0.015)
fluid.addComponent("methane", 0.25)
fluid.addComponent("ethane", 0.06)
fluid.addComponent("propane", 0.05)
fluid.addComponent("i-butane", 0.015)
fluid.addComponent("n-butane", 0.03)
fluid.addComponent("i-pentane", 0.02)
fluid.addComponent("n-pentane", 0.025)
fluid.addComponent("n-hexane", 0.03)
fluid.addComponent("n-heptane", 0.50)
elif gor_type == 'high':
# High GOR - more light components
fluid.addComponent("nitrogen", 0.01)
fluid.addComponent("CO2", 0.03)
fluid.addComponent("methane", 0.55)
fluid.addComponent("ethane", 0.10)
fluid.addComponent("propane", 0.06)
fluid.addComponent("i-butane", 0.02)
fluid.addComponent("n-butane", 0.03)
fluid.addComponent("i-pentane", 0.015)
fluid.addComponent("n-pentane", 0.02)
fluid.addComponent("n-hexane", 0.015)
fluid.addComponent("n-heptane", 0.15)
else: # medium
fluid.addComponent("nitrogen", 0.008)
fluid.addComponent("CO2", 0.02)
fluid.addComponent("methane", 0.40)
fluid.addComponent("ethane", 0.08)
fluid.addComponent("propane", 0.055)
fluid.addComponent("i-butane", 0.018)
fluid.addComponent("n-butane", 0.03)
fluid.addComponent("i-pentane", 0.018)
fluid.addComponent("n-pentane", 0.022)
fluid.addComponent("n-hexane", 0.022)
fluid.addComponent("n-heptane", 0.327)
# Add water if water cut > 0
if water_cut > 0.01:
# Scale hydrocarbon moles by (1 - water_cut) and add water
fluid.addComponent("water", water_cut * 0.5) # Proportional water
fluid.setMixingRule("classic")
fluid.setMultiPhaseCheck(True)
ops = ThermodynamicOperations(fluid)
ops.TPflash()
return fluid
# Create well fluids
print("Creating well fluids...")
print("="*60)
well_configs = {
'Well_A': {'gor_type': 'low', 'water_cut': 0.05, 'desc': 'Low GOR, Low WC (Best)'},
'Well_B': {'gor_type': 'high', 'water_cut': 0.10, 'desc': 'High GOR (Gas-rich)'},
'Well_C': {'gor_type': 'medium', 'water_cut': 0.50, 'desc': 'High WC (Mature)'},
}
well_fluids = {}
for name, config in well_configs.items():
fluid = create_well_fluid(config['gor_type'], config['water_cut'])
well_fluids[name] = fluid
n_phases = fluid.getNumberOfPhases()
print(f"{name}: {config['desc']}")
print(f" Phases: {n_phases}, Gas fraction: {fluid.getPhase('gas').getVolume()/fluid.getVolume():.2f}" if fluid.hasPhaseType('gas') else f" Phases: {n_phases}")
print()
Output
``` Creating well fluids... ============================================================ Well_A: Low GOR, Low WC (Best) Phases: 2 Well_B: High GOR (Gas-rich) Phases: 3, Gas fraction: 0.79 Well_C: High WC (Mature) Phases: 3, Gas fraction: 0.47 ```3. Build Production System Model
Create a complete production system with:
- Three wells with choke valves
- Production manifold (mixer)
- Production pipeline
- Three-phase separator
- Gas compressor
- Export cooler
def build_production_system(well_rates, sep_pressure=25.0, compressor_outlet_pressure=80.0):
"""
Build a complete production system.
Parameters:
- well_rates: dict of well name -> rate in kg/hr
- sep_pressure: separator operating pressure (bara)
- compressor_outlet_pressure: gas export pressure (bara)
Returns:
- ProcessSystem with all equipment
- dict of equipment references
"""
process = ProcessSystem()
equipment = {}
# Create well streams with chokes
well_outlets = []
for well_name, rate in well_rates.items():
if rate > 0:
# Create well stream
fluid = well_fluids[well_name].clone()
stream = Stream(f"{well_name}_stream", fluid)
stream.setFlowRate(float(rate), "kg/hr")
process.add(stream)
equipment[f"{well_name}_stream"] = stream
# Choke valve to reduce pressure to separator
choke = ThrottlingValve(f"{well_name}_choke", stream)
choke.setOutletPressure(sep_pressure + 5.0) # 5 bar above separator
process.add(choke)
equipment[f"{well_name}_choke"] = choke
well_outlets.append(choke.getOutletStream())
if len(well_outlets) == 0:
return None, None
# Manifold (mixer)
if len(well_outlets) > 1:
manifold = Mixer("manifold")
for outlet in well_outlets:
manifold.addStream(outlet)
process.add(manifold)
equipment['manifold'] = manifold
manifold_outlet = manifold.getOutletStream()
else:
manifold_outlet = well_outlets[0]
# Three-phase separator
separator = ThreePhaseSeparator("HP_separator", manifold_outlet)
separator.setInternalDiameter(2.5) # 2.5m diameter
process.add(separator)
equipment['separator'] = separator
# Gas stream processing
gas_stream = separator.getGasOutStream()
# Gas compressor
compressor = Compressor("gas_compressor", gas_stream)
compressor.setOutletPressure(compressor_outlet_pressure)
compressor.setPolytropicEfficiency(0.75)
process.add(compressor)
equipment['compressor'] = compressor
# Gas cooler after compression
gas_cooler = Cooler("gas_cooler", compressor.getOutletStream())
gas_cooler.setOutTemperature(273.15 + 40.0) # Cool to 40°C
process.add(gas_cooler)
equipment['gas_cooler'] = gas_cooler
# Oil and water streams
oil_stream = separator.getOilOutStream()
water_stream = separator.getWaterOutStream()
equipment['oil_export'] = oil_stream
equipment['water_export'] = water_stream
return process, equipment
# Test with nominal rates in kg/hr
nominal_rates = {'Well_A': 50000.0, 'Well_B': 35000.0, 'Well_C': 25000.0} # kg/hr
print(f"Building production system with nominal rates: {nominal_rates}")
process, equipment = build_production_system(nominal_rates)
process.run()
print("\nSystem built and run successfully!")
print("\nEquipment summary:")
for name, eq in equipment.items():
print(f" - {name}")
Output
``` Building production system with nominal rates: {'Well_A': 50000.0, 'Well_B': 35000.0, 'Well_C': 25000.0} System built and run successfully! Equipment summary: - Well_A_stream - Well_A_choke - Well_B_stream - Well_B_choke - Well_C_stream - Well_C_choke - manifold - separator - compressor - gas_cooler - oil_export - water_export ```4. Auto-Design: Equipment Sizing Analysis
Analyze equipment sizing requirements based on operating conditions.
def analyze_system_performance(process, equipment):
"""
Analyze production system performance and identify potential issues.
"""
results = {}
# Separator analysis
sep = equipment['separator']
gas_out = sep.getGasOutStream()
oil_out = sep.getOilOutStream()
results['separator'] = {
'gas_rate_sm3d': gas_out.getFlowRate("Sm3/day") if gas_out else 0,
'oil_rate_sm3d': oil_out.getFlowRate("Sm3/day") if oil_out else 0,
'pressure_bara': sep.getPressure(),
'temperature_C': sep.getTemperature() - 273.15,
}
# Compressor analysis
comp = equipment['compressor']
results['compressor'] = {
'power_kW': comp.getPower() / 1000,
'inlet_pressure': comp.getInletStream().getPressure(),
'outlet_pressure': comp.getOutletStream().getPressure(),
'compression_ratio': comp.getOutletStream().getPressure() / comp.getInletStream().getPressure(),
'outlet_temp_C': comp.getOutletStream().getTemperature() - 273.15,
}
# Gas cooler analysis
cooler = equipment['gas_cooler']
results['gas_cooler'] = {
'duty_MW': abs(cooler.getDuty()) / 1e6,
'inlet_temp_C': cooler.getInletStream().getTemperature() - 273.15,
'outlet_temp_C': cooler.getOutletStream().getTemperature() - 273.15,
}
return results
# Analyze the system
perf = analyze_system_performance(process, equipment)
print("Production System Performance Analysis")
print("=" * 60)
print("\n📊 SEPARATOR:")
print(f" Gas rate: {perf['separator']['gas_rate_sm3d']:,.0f} Sm³/day")
print(f" Oil rate: {perf['separator']['oil_rate_sm3d']:,.0f} Sm³/day")
print(f" Pressure: {perf['separator']['pressure_bara']:.1f} bara")
print(f" Temperature: {perf['separator']['temperature_C']:.1f} °C")
print("\n⚙️ GAS COMPRESSOR:")
print(f" Power: {perf['compressor']['power_kW']:,.0f} kW")
print(f" Compression ratio: {perf['compressor']['compression_ratio']:.2f}")
print(f" Outlet temperature: {perf['compressor']['outlet_temp_C']:.1f} °C")
print("\n❄️ GAS COOLER:")
print(f" Cooling duty: {perf['gas_cooler']['duty_MW']:.2f} MW")
print(f" Temperature drop: {perf['gas_cooler']['inlet_temp_C']:.1f} → {perf['gas_cooler']['outlet_temp_C']:.1f} °C")
Output
``` Production System Performance Analysis ============================================================ 📊 SEPARATOR: Gas rate: 595,255 Sm³/day Oil rate: 590,827 Sm³/day Pressure: 30.0 bara Temperature: 67.9 °C ⚙️ GAS COMPRESSOR: Power: 830 kW Compression ratio: 2.67 Outlet temperature: 136.2 °C ❄️ GAS COOLER: Cooling duty: 1.94 MW Temperature drop: 136.2 → 40.0 °C ```5. Bottleneck Analysis
Identify system constraints by varying production rates and finding where equipment limits are reached.
# Define equipment capacity limits
CAPACITY_LIMITS = {
'separator_gas_capacity': 5.0e6, # Sm³/day max gas handling
'separator_liquid_capacity': 15000, # m³/day max liquid
'compressor_power_max': 5000, # kW max power
'compressor_ratio_max': 4.0, # Max compression ratio per stage
'cooler_duty_max': 3.0, # MW max cooling duty
}
def check_bottlenecks(perf, limits=CAPACITY_LIMITS):
"""
Check which equipment is at or near capacity limits.
Returns dict of equipment -> utilization percentage.
"""
bottlenecks = {}
# Separator gas capacity
gas_util = perf['separator']['gas_rate_sm3d'] / limits['separator_gas_capacity'] * 100
bottlenecks['Separator (Gas)'] = gas_util
# Compressor power
comp_util = perf['compressor']['power_kW'] / limits['compressor_power_max'] * 100
bottlenecks['Compressor (Power)'] = comp_util
# Compressor ratio
ratio_util = perf['compressor']['compression_ratio'] / limits['compressor_ratio_max'] * 100
bottlenecks['Compressor (Ratio)'] = ratio_util
# Cooler duty
cool_util = perf['gas_cooler']['duty_MW'] / limits['cooler_duty_max'] * 100
bottlenecks['Gas Cooler'] = cool_util
return bottlenecks
bottlenecks = check_bottlenecks(perf)
print("Bottleneck Analysis - Equipment Utilization")
print("=" * 60)
# Sort by utilization
sorted_bottlenecks = sorted(bottlenecks.items(), key=lambda x: x[1], reverse=True)
for equip, util in sorted_bottlenecks:
bar_len = int(util / 2)
bar = '█' * bar_len + '░' * (50 - bar_len)
status = '🔴 BOTTLENECK' if util > 90 else '🟡 HIGH' if util > 70 else '🟢 OK'
print(f"\n{equip}:")
print(f" [{bar}] {util:.1f}% {status}")
# Identify the main bottleneck
main_bottleneck = sorted_bottlenecks[0]
print(f"\n\n📍 PRIMARY BOTTLENECK: {main_bottleneck[0]} at {main_bottleneck[1]:.1f}% utilization")
Output
``` Bottleneck Analysis - Equipment Utilization ============================================================ Compressor (Ratio): [█████████████████████████████████░░░░░░░░░░░░░░░░░] 66.7% 🟢 OK Gas Cooler: [████████████████████████████████░░░░░░░░░░░░░░░░░░] 64.8% 🟢 OK Compressor (Power): [████████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 16.6% 🟢 OK Separator (Gas): [█████░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 11.9% 🟢 OK 📍 PRIMARY BOTTLENECK: Compressor (Ratio) at 66.7% utilization ```6. Production Rate Sensitivity Analysis
Analyze how bottlenecks change with different production scenarios.
# Test different production scenarios (rates in kg/hr)
scenarios = [
{'name': 'Base Case', 'Well_A': 50000, 'Well_B': 35000, 'Well_C': 25000},
{'name': 'High Total Rate', 'Well_A': 70000, 'Well_B': 50000, 'Well_C': 35000},
{'name': 'Prioritize Low GOR', 'Well_A': 90000, 'Well_B': 15000, 'Well_C': 15000},
{'name': 'Prioritize High GOR', 'Well_A': 25000, 'Well_B': 70000, 'Well_C': 15000},
{'name': 'High Water Cut', 'Well_A': 30000, 'Well_B': 25000, 'Well_C': 70000},
]
scenario_results = []
print("Running production scenarios...")
print("=" * 80)
for scenario in scenarios:
rates = {k: v for k, v in scenario.items() if k.startswith('Well')}
total_rate = sum(rates.values())
try:
proc, equip = build_production_system(rates)
proc.run()
perf = analyze_system_performance(proc, equip)
bottlenecks = check_bottlenecks(perf)
# Find main bottleneck
main_bn = max(bottlenecks.items(), key=lambda x: x[1])
result = {
'name': scenario['name'],
'total_rate': total_rate,
'gas_rate': perf['separator']['gas_rate_sm3d'],
'oil_rate': perf['separator']['oil_rate_sm3d'],
'comp_power': perf['compressor']['power_kW'],
'main_bottleneck': main_bn[0],
'bn_utilization': main_bn[1],
'bottlenecks': bottlenecks,
}
scenario_results.append(result)
print(f"\n{scenario['name']}:")
print(f" Total rate: {total_rate:,} kg/hr")
print(f" Gas: {perf['separator']['gas_rate_sm3d']:,.0f} Sm³/d | Oil: {perf['separator']['oil_rate_sm3d']:,.0f} Sm³/d")
print(f" Compressor: {perf['compressor']['power_kW']:,.0f} kW")
print(f" Main bottleneck: {main_bn[0]} ({main_bn[1]:.1f}%)")
except Exception as e:
print(f"\n{scenario['name']}: FAILED - {str(e)[:50]}")
Output
``` Running production scenarios... ================================================================================ Base Case: Total rate: 110,000 kg/hr Gas: 595,255 Sm³/d | Oil: 590,827 Sm³/d Compressor: 830 kW Main bottleneck: Compressor (Ratio) (66.7%) High Total Rate: Total rate: 155,000 kg/hr Gas: 844,088 Sm³/d | Oil: 831,169 Sm³/d Compressor: 1,176 kW Main bottleneck: Gas Cooler (91.8%) Prioritize Low GOR: Total rate: 120,000 kg/hr Gas: 414,124 Sm³/d | Oil: 713,671 Sm³/d Compressor: 583 kW Main bottleneck: Compressor (Ratio) (66.7%) Prioritize High GOR: Total rate: 110,000 kg/hr Gas: 875,951 Sm³/d | Oil: 515,907 Sm³/d Compressor: 1,207 kW Main bottleneck: Gas Cooler (92.8%) High Water Cut: Total rate: 125,000 kg/hr Gas: 665,266 Sm³/d | Oil: 654,409 Sm³/d Compressor: 928 kW Main bottleneck: Gas Cooler (73.1%) ```# Visualize scenario comparison
fig, axes = plt.subplots(2, 2, figsize=(14, 10))
# Extract data
names = [r['name'] for r in scenario_results]
gas_rates = [r['gas_rate']/1e6 for r in scenario_results] # Million Sm³/d
oil_rates = [r['oil_rate']/1e3 for r in scenario_results] # 1000 Sm³/d
comp_powers = [r['comp_power'] for r in scenario_results]
bn_utils = [r['bn_utilization'] for r in scenario_results]
x = np.arange(len(names))
width = 0.6
# Plot 1: Gas Production
ax1 = axes[0, 0]
bars1 = ax1.bar(x, gas_rates, width, color='lightblue', edgecolor='navy')
ax1.axhline(y=CAPACITY_LIMITS['separator_gas_capacity']/1e6, color='red', linestyle='--', label='Capacity Limit')
ax1.set_ylabel('Gas Rate (Million Sm³/day)', fontsize=11)
ax1.set_title('Gas Production by Scenario', fontsize=13, fontweight='bold')
ax1.set_xticks(x)
ax1.set_xticklabels(names, rotation=45, ha='right', fontsize=9)
ax1.legend()
ax1.grid(axis='y', alpha=0.3)
# Plot 2: Oil Production
ax2 = axes[0, 1]
bars2 = ax2.bar(x, oil_rates, width, color='lightgreen', edgecolor='darkgreen')
ax2.set_ylabel('Oil Rate (1000 Sm³/day)', fontsize=11)
ax2.set_title('Oil Production by Scenario', fontsize=13, fontweight='bold')
ax2.set_xticks(x)
ax2.set_xticklabels(names, rotation=45, ha='right', fontsize=9)
ax2.grid(axis='y', alpha=0.3)
# Plot 3: Compressor Power
ax3 = axes[1, 0]
colors = ['red' if p > CAPACITY_LIMITS['compressor_power_max'] else 'orange' if p > 0.8*CAPACITY_LIMITS['compressor_power_max'] else 'steelblue' for p in comp_powers]
bars3 = ax3.bar(x, comp_powers, width, color=colors, edgecolor='black')
ax3.axhline(y=CAPACITY_LIMITS['compressor_power_max'], color='red', linestyle='--', label='Capacity Limit')
ax3.set_ylabel('Compressor Power (kW)', fontsize=11)
ax3.set_title('Compressor Power by Scenario', fontsize=13, fontweight='bold')
ax3.set_xticks(x)
ax3.set_xticklabels(names, rotation=45, ha='right', fontsize=9)
ax3.legend()
ax3.grid(axis='y', alpha=0.3)
# Plot 4: Bottleneck Utilization
ax4 = axes[1, 1]
colors = ['red' if u > 90 else 'orange' if u > 70 else 'green' for u in bn_utils]
bars4 = ax4.bar(x, bn_utils, width, color=colors, edgecolor='black')
ax4.axhline(y=90, color='red', linestyle='--', alpha=0.7, label='Critical (90%)')
ax4.axhline(y=70, color='orange', linestyle='--', alpha=0.7, label='Warning (70%)')
ax4.set_ylabel('Bottleneck Utilization (%)', fontsize=11)
ax4.set_title('System Bottleneck by Scenario', fontsize=13, fontweight='bold')
ax4.set_xticks(x)
ax4.set_xticklabels(names, rotation=45, ha='right', fontsize=9)
ax4.set_ylim([0, 120])
ax4.legend(loc='upper right')
ax4.grid(axis='y', alpha=0.3)
# Add bottleneck labels
for i, (name, util) in enumerate(zip([r['main_bottleneck'] for r in scenario_results], bn_utils)):
short_name = name.split('(')[0].strip()[:8]
ax4.text(i, util + 3, short_name, ha='center', va='bottom', fontsize=8, rotation=0)
plt.tight_layout()
plt.savefig('scenario_comparison.png', dpi=150, bbox_inches='tight')
plt.show()
print("\nFigure saved as 'scenario_comparison.png'")
Output
``` Figure saved as 'scenario_comparison.png' ```7. Well Prioritization Analysis
Determine which wells to prioritize based on their impact on system constraints.
# Analyze impact of each well on system bottlenecks
def analyze_well_impact(base_rates, well_name, delta_rate=15000):
"""
Analyze how increasing production from a specific well impacts bottlenecks.
delta_rate in kg/hr
"""
# Base case
proc_base, equip_base = build_production_system(base_rates)
proc_base.run()
perf_base = analyze_system_performance(proc_base, equip_base)
bn_base = check_bottlenecks(perf_base)
# Increased rate for this well
increased_rates = base_rates.copy()
increased_rates[well_name] = base_rates[well_name] + delta_rate
proc_inc, equip_inc = build_production_system(increased_rates)
proc_inc.run()
perf_inc = analyze_system_performance(proc_inc, equip_inc)
bn_inc = check_bottlenecks(perf_inc)
# Calculate impact
impact = {}
for key in bn_base:
impact[key] = bn_inc[key] - bn_base[key]
return {
'gas_delta': perf_inc['separator']['gas_rate_sm3d'] - perf_base['separator']['gas_rate_sm3d'],
'oil_delta': perf_inc['separator']['oil_rate_sm3d'] - perf_base['separator']['oil_rate_sm3d'],
'power_delta': perf_inc['compressor']['power_kW'] - perf_base['compressor']['power_kW'],
'bottleneck_impact': impact,
'max_impact': max(impact.values()),
'max_impact_equip': max(impact.items(), key=lambda x: x[1])[0],
}
base_rates = {'Well_A': 35000, 'Well_B': 35000, 'Well_C': 35000} # kg/hr each
print("Well Impact Analysis")
print("="*70)
print(f"Analyzing impact of +15,000 kg/hr from each well...")
print()
well_impacts = {}
for well in ['Well_A', 'Well_B', 'Well_C']:
impact = analyze_well_impact(base_rates, well, delta_rate=15000)
well_impacts[well] = impact
print(f"\n{well} ({well_configs[well]['desc']}:")
print(f" Gas increase: {impact['gas_delta']:+,.0f} Sm³/day")
print(f" Oil increase: {impact['oil_delta']:+,.0f} Sm³/day")
print(f" Compressor power increase: {impact['power_delta']:+,.0f} kW")
print(f" Main bottleneck impact: {impact['max_impact_equip']} ({impact['max_impact']:+.1f}%)")
Output
``` Well Impact Analysis ====================================================================== Analyzing impact of +15,000 kg/hr from each well... Well_A (Low GOR, Low WC (Best): Gas increase: +29,616 Sm³/day Oil increase: +96,546 Sm³/day Compressor power increase: +43 kW Main bottleneck impact: Gas Cooler (+3.5%) Well_B (High GOR (Gas-rich): Gas increase: +161,473 Sm³/day Oil increase: +59,643 Sm³/day Compressor power increase: +222 kW Main bottleneck impact: Gas Cooler (+16.9%) Well_C (High WC (Mature): Gas increase: +72,315 Sm³/day Oil increase: +77,432 Sm³/day Compressor power increase: +101 kW Main bottleneck impact: Gas Cooler (+8.1%) ```# Visualize well prioritization
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
wells = list(well_impacts.keys())
colors = {'Well_A': 'green', 'Well_B': 'orange', 'Well_C': 'blue'}
well_labels = [f"{w}\n({well_configs[w]['desc'].split('(')[0].strip()})" for w in wells]
# Oil Production Efficiency
ax1 = axes[0]
oil_increases = [well_impacts[w]['oil_delta'] for w in wells]
bars1 = ax1.bar(well_labels, oil_increases, color=[colors[w] for w in wells], edgecolor='black')
ax1.set_ylabel('Oil Increase (Sm³/day)', fontsize=11)
ax1.set_title('Oil Production per +50 m³/h', fontsize=13, fontweight='bold')
ax1.grid(axis='y', alpha=0.3)
for bar, val in zip(bars1, oil_increases):
ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 50, f'{val:,.0f}',
ha='center', va='bottom', fontsize=10)
# Bottleneck Impact
ax2 = axes[1]
bn_impacts = [well_impacts[w]['max_impact'] for w in wells]
bars2 = ax2.bar(well_labels, bn_impacts, color=[colors[w] for w in wells], edgecolor='black')
ax2.set_ylabel('Bottleneck Increase (%)', fontsize=11)
ax2.set_title('Bottleneck Impact per +50 m³/h', fontsize=13, fontweight='bold')
ax2.grid(axis='y', alpha=0.3)
for bar, val in zip(bars2, bn_impacts):
ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5, f'{val:.1f}%',
ha='center', va='bottom', fontsize=10)
# Efficiency Score (Oil per bottleneck impact)
ax3 = axes[2]
efficiency = [oil / max(bn, 0.1) for oil, bn in zip(oil_increases, bn_impacts)]
bars3 = ax3.bar(well_labels, efficiency, color=[colors[w] for w in wells], edgecolor='black')
ax3.set_ylabel('Oil per % Bottleneck Increase', fontsize=11)
ax3.set_title('Production Efficiency Score', fontsize=13, fontweight='bold')
ax3.grid(axis='y', alpha=0.3)
# Rank wells
ranked = sorted(zip(wells, efficiency), key=lambda x: x[1], reverse=True)
for i, (well, eff) in enumerate(ranked):
idx = wells.index(well)
ax3.text(idx, efficiency[idx] + 50, f'#{i+1}', ha='center', va='bottom', fontsize=12, fontweight='bold')
plt.tight_layout()
plt.savefig('well_prioritization.png', dpi=150, bbox_inches='tight')
plt.show()
print("\n" + "="*70)
print("WELL PRIORITIZATION RECOMMENDATION:")
print("="*70)
for i, (well, eff) in enumerate(ranked):
desc = well_configs[well]['desc']
print(f" {i+1}. {well} ({desc}) - Efficiency: {eff:.0f}")
print("\nFigure saved as 'well_prioritization.png'")
Output
``` ====================================================================== WELL PRIORITIZATION RECOMMENDATION: ====================================================================== 1. Well_A (Low GOR, Low WC (Best)) - Efficiency: 27390 2. Well_C (High WC (Mature)) - Efficiency: 9540 3. Well_B (High GOR (Gas-rich)) - Efficiency: 3528 Figure saved as 'well_prioritization.png' ```8. Bottleneck Resolution Strategies
Analyze how to resolve identified bottlenecks.
print("Bottleneck Resolution Analysis")
print("=" * 70)
# Current main bottleneck from high rate scenario
high_rate_result = scenario_results[1] # High Total Rate scenario
main_bn = high_rate_result['main_bottleneck']
bn_util = high_rate_result['bn_utilization']
print(f"\nCurrent Bottleneck: {main_bn} at {bn_util:.1f}%")
print()
# Resolution strategies based on bottleneck type
resolutions = {
'Separator (Gas)': [
('Increase separator size', 'Add parallel separator or replace with larger unit'),
('Lower separator pressure', 'Reduces gas density, increases gas velocity'),
('Add inlet cyclone', 'Pre-separates gas, reduces separator load'),
('Prioritize low-GOR wells', 'Produces less gas per barrel of oil'),
],
'Compressor (Power)': [
('Add parallel compressor', 'Share load between units'),
('Increase compressor size', 'Higher capacity single unit'),
('Lower export pressure', 'Reduces compression work if allowed'),
('Prioritize low-GOR wells', 'Produces less gas to compress'),
],
'Compressor (Ratio)': [
('Add compression stage', 'Split ratio between stages'),
('Increase separator pressure', 'Raises compressor inlet pressure'),
('Intercooling', 'Reduces temperature, improves efficiency'),
],
'Gas Cooler': [
('Add cooling capacity', 'Larger or parallel coolers'),
('Improve compressor efficiency', 'Less heat to remove'),
('Add intercooling', 'Distributes cooling load'),
],
}
print("RESOLUTION OPTIONS:")
print("-" * 70)
if main_bn in resolutions:
for i, (action, detail) in enumerate(resolutions[main_bn], 1):
print(f"\n {i}. {action}")
print(f" → {detail}")
# Quantitative analysis: What if we increase compressor capacity?
print("\n\n" + "="*70)
print("QUANTITATIVE IMPACT ANALYSIS")
print("="*70)
# Simulate capacity increase
original_limit = CAPACITY_LIMITS['compressor_power_max']
new_limits = [original_limit * 1.0, original_limit * 1.25, original_limit * 1.5]
print("\nEffect of increasing compressor capacity:")
print("-" * 70)
print(f"{'Capacity (kW)':<20} {'Utilization':<15} {'Status':<15} {'Additional Rate'}")
print("-" * 70)
for new_cap in new_limits:
test_limits = CAPACITY_LIMITS.copy()
test_limits['compressor_power_max'] = new_cap
bn_test = check_bottlenecks(perf, test_limits)
util = bn_test['Compressor (Power)']
status = '🔴 Over' if util > 100 else '🟡 High' if util > 80 else '🟢 OK'
headroom = (100 - util) / 100 * sum(nominal_rates.values()) if util < 100 else 0
print(f"{new_cap:,.0f} {util:.1f}% {status} +{headroom:.0f} m³/h potential")
Output
``` Bottleneck Resolution Analysis ====================================================================== Current Bottleneck: Gas Cooler at 91.8% RESOLUTION OPTIONS: ---------------------------------------------------------------------- 1. Add cooling capacity → Larger or parallel coolers 2. Improve compressor efficiency → Less heat to remove 3. Add intercooling → Distributes cooling load ====================================================================== QUANTITATIVE IMPACT ANALYSIS ====================================================================== Effect of increasing compressor capacity: ---------------------------------------------------------------------- Capacity (kW) Utilization Status Additional Rate ---------------------------------------------------------------------- 5,000 18.6% 🟢 OK +89574 m³/h potential 6,250 14.9% 🟢 OK +93659 m³/h potential 7,500 12.4% 🟢 OK +96383 m³/h potential ```9. GOR and Water Cut Trade-off Analysis
Understand how fluid composition affects system performance.
# Create contour plot of system capacity vs GOR and WC mix
# Test matrix: vary mix between high-GOR and high-WC production
gor_fractions = np.linspace(0, 1, 8) # Fraction of high-GOR well
wc_fractions = np.linspace(0, 1, 8) # Fraction of high-WC well
total_rate = 100000 # Total production target in kg/hr
results_matrix = np.zeros((len(wc_fractions), len(gor_fractions)))
oil_matrix = np.zeros((len(wc_fractions), len(gor_fractions)))
print("Running GOR vs Water Cut trade-off analysis...")
for i, wc_frac in enumerate(wc_fractions):
for j, gor_frac in enumerate(gor_fractions):
# Remaining goes to Well_A (low GOR, low WC)
a_frac = 1.0 - gor_frac - wc_frac
if a_frac < 0:
a_frac = 0
# Normalize
total_frac = gor_frac + wc_frac
gor_frac_norm = gor_frac / total_frac
wc_frac_norm = wc_frac / total_frac
else:
gor_frac_norm = gor_frac
wc_frac_norm = wc_frac
a_frac = 1.0 - gor_frac - wc_frac
rates = {
'Well_A': max(5000, total_rate * a_frac),
'Well_B': total_rate * gor_frac_norm if gor_frac_norm > 0.05 else 0,
'Well_C': total_rate * wc_frac_norm if wc_frac_norm > 0.05 else 0,
}
try:
proc, equip = build_production_system(rates)
proc.run()
perf = analyze_system_performance(proc, equip)
bn = check_bottlenecks(perf)
# Max bottleneck utilization
results_matrix[i, j] = max(bn.values())
oil_matrix[i, j] = perf['separator']['oil_rate_sm3d']
except:
results_matrix[i, j] = np.nan
oil_matrix[i, j] = np.nan
print("Analysis complete!")
Output
``` Running GOR vs Water Cut trade-off analysis... Analysis complete! ```# Visualize trade-off
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# Plot 1: Bottleneck utilization
ax1 = axes[0]
im1 = ax1.contourf(gor_fractions*100, wc_fractions*100, results_matrix,
levels=np.linspace(0, 150, 16), cmap='RdYlGn_r')
ax1.contour(gor_fractions*100, wc_fractions*100, results_matrix,
levels=[70, 90, 100], colors=['orange', 'red', 'darkred'], linewidths=2)
ax1.set_xlabel('High-GOR Well Fraction (%)', fontsize=12)
ax1.set_ylabel('High-WC Well Fraction (%)', fontsize=12)
ax1.set_title('System Bottleneck Utilization (%)', fontsize=14, fontweight='bold')
cbar1 = plt.colorbar(im1, ax=ax1)
cbar1.set_label('Max Equipment Utilization (%)')
# Add feasibility region
ax1.fill_between([0, 100], [100, 0], [100, 100], alpha=0.3, color='gray', label='Infeasible (>100%)')
# Plot 2: Oil production
ax2 = axes[1]
im2 = ax2.contourf(gor_fractions*100, wc_fractions*100, oil_matrix/1000,
levels=15, cmap='viridis')
ax2.set_xlabel('High-GOR Well Fraction (%)', fontsize=12)
ax2.set_ylabel('High-WC Well Fraction (%)', fontsize=12)
ax2.set_title('Oil Production (1000 Sm³/day)', fontsize=14, fontweight='bold')
cbar2 = plt.colorbar(im2, ax=ax2)
cbar2.set_label('Oil Rate (1000 Sm³/day)')
# Mark optimal region (high oil, low bottleneck)
# Find best point
valid = results_matrix < 90 # Within capacity
if np.any(valid):
oil_valid = np.where(valid, oil_matrix, 0)
best_idx = np.unravel_index(np.argmax(oil_valid), oil_valid.shape)
best_wc = wc_fractions[best_idx[0]] * 100
best_gor = gor_fractions[best_idx[1]] * 100
ax1.plot(best_gor, best_wc, 'w*', markersize=20, markeredgecolor='black', label='Optimal')
ax2.plot(best_gor, best_wc, 'r*', markersize=20, markeredgecolor='white', label='Optimal')
ax1.legend(loc='upper right')
ax2.legend(loc='upper right')
plt.tight_layout()
plt.savefig('gor_wc_tradeoff.png', dpi=150, bbox_inches='tight')
plt.show()
print(f"\nOptimal operating point: {best_gor:.0f}% High-GOR, {best_wc:.0f}% High-WC")
print(f"Remaining {100-best_gor-best_wc:.0f}% from Low-GOR/Low-WC well")
print("\nFigure saved as 'gor_wc_tradeoff.png'")
Output
``` Optimal operating point: 0% High-GOR, 0% High-WC Remaining 100% from Low-GOR/Low-WC well Figure saved as 'gor_wc_tradeoff.png' ```10. Summary and Recommendations
Key Findings
print("=" * 70)
print("PRODUCTION SYSTEM OPTIMIZATION - SUMMARY")
print("=" * 70)
print("\n📊 SYSTEM CONFIGURATION:")
print(" • 3 wells with different fluid characteristics")
print(" • Production manifold → 3-phase separator → Gas compressor")
print(f" • Separator pressure: 30 bara")
print(f" • Gas export pressure: 80 bara")
print("\n🎯 BOTTLENECK IDENTIFICATION:")
sorted_bn = sorted(bottlenecks.items(), key=lambda x: x[1], reverse=True)
for i, (equip, util) in enumerate(sorted_bn[:3]):
status = '🔴' if util > 90 else '🟡' if util > 70 else '🟢'
print(f" {i+1}. {equip}: {util:.1f}% {status}")
print("\n🏆 WELL PRIORITIZATION (Best to Worst):")
for i, (well, eff) in enumerate(ranked):
desc = well_configs[well]['desc']
print(f" {i+1}. {well} - {desc}")
print("\n💡 KEY INSIGHTS:")
print("""
1. HIGH GOR WELLS:
✓ More gas production
✗ Higher compressor load
✗ More cooling duty required
→ Best when gas export capacity is available
2. HIGH WATER CUT WELLS:
✓ Lower gas processing load
✗ Lower oil per total liquid
✗ Water handling capacity needed
→ Consider only when gas-constrained
3. LOW GOR, LOW WC WELLS:
✓ Maximum oil per system capacity
✓ Balanced gas/liquid ratios
→ Prioritize when maximizing oil production
""")
print("\n🔧 RECOMMENDED ACTIONS:")
print("""
SHORT TERM:
• Prioritize Well A (low GOR, low WC) for maximum oil
• Limit Well B when gas cooling is constrained
MEDIUM TERM:
• Add parallel gas cooler for 50% capacity increase
• This enables full field production at higher rates
LONG TERM:
• Plan for increasing water cut from Well C
• Monitor compressor power as field matures
""")
Output
``` ====================================================================== PRODUCTION SYSTEM OPTIMIZATION - SUMMARY ====================================================================== 📊 SYSTEM CONFIGURATION: • 3 wells with different fluid characteristics • Production manifold → 3-phase separator → Gas compressor • Separator pressure: 30 bara • Gas export pressure: 80 bara 🎯 BOTTLENECK IDENTIFICATION: 1. Gas Cooler: 73.1% 🟡 2. Compressor (Ratio): 66.7% 🟢 3. Compressor (Power): 18.6% 🟢 🏆 WELL PRIORITIZATION (Best to Worst): 1. Well_A - Low GOR, Low WC (Best) 2. Well_C - High WC (Mature) 3. Well_B - High GOR (Gas-rich) 💡 KEY INSIGHTS: 1. HIGH GOR WELLS: ✓ More gas production ✗ Higher compressor load ✗ More cooling duty required → Best when gas export capacity is available 2. HIGH WATER CUT WELLS: ✓ Lower gas processing load ✗ Lower oil per total liquid ✗ Water handling capacity needed → Consider only when gas-constrained 3. LOW GOR, LOW WC WELLS: ✓ Maximum oil per system capacity ✓ Balanced gas/liquid ratios → Prioritize when maximizing oil production 🔧 RECOMMENDED ACTIONS: SHORT TERM: • Prioritize Well A (low GOR, low WC) for maximum oil • Limit Well B when gas cooling is constrained MEDIUM TERM: • Add parallel gas cooler for 50% capacity increase • This enables full field production at higher rates LONG TERM: • Plan for increasing water cut from Well C • Monitor compressor power as field matures ```Key Takeaways
This notebook demonstrated:
| Capability | Description |
|---|---|
| Multi-well modeling | Different GOR/WC characteristics per well |
| Equipment sizing | Separator, compressor, cooler analysis |
| Bottleneck ID | Find which equipment limits production |
| Well prioritization | Rank wells by production efficiency |
| Trade-off analysis | GOR vs WC impact on system capacity |
| Resolution planning | Quantify impact of capacity upgrades |
Next Steps
- Add pipeline hydraulics for pressure drop calculations
- Include economic optimization (NPV of production scenarios)
- Add time-varying analysis for field decline
- Integrate with reservoir simulator for coupled optimization