-
Notifications
You must be signed in to change notification settings - Fork 0
Component Fugitive
This document provides a comprehensive technical analysis of the Field.get_component_fugitive() method in OPGEE, which calculates fugitive emission loss rates for oil and gas production equipment using Jeff's component fugitive model. The method implements sophisticated matrix operations on productivity-stratified loss data to estimate emissions from downhole pumps, separation equipment, and crude oil storage.
Location: opgee/core/field.py:Field.get_component_fugitive()
Return: (process_loss_rate: pd.Series, loss_mat_gas_ave_df: pd.DataFrame)
The most critical aspect of this implementation is understanding that the loss matrix CSV files contain productivity-stratified data where repeated column headers represent different data for different productivity tranches:
- Gas Wells: 140 columns = 10 productivity tranches × 14 equipment components
- Oil Wells: 120 columns = 10 productivity tranches × 12 equipment components
- Data Pattern: Loss rates systematically decrease as productivity increases (higher production wells have lower per-unit losses)
All columns are actively used - there are no duplicates or legacy artifacts.
The method relies on these key field characteristics:
| Attribute | Purpose | Typical Value |
|---|---|---|
GOR |
Gas-oil ratio for well classification | 1822.7 |
GOR_cutoff |
Threshold for gas vs oil well treatment | 1000 |
oil_prod |
Oil production rate affecting productivity | Variable |
gas_lifting, GLIR
|
Gas lifting flag and injection ratio | Boolean, ratio |
gas_flooding, flood_gas_type, GFIR, frac_CO2_breakthrough
|
CO2 flooding parameters | Various |
num_prod_wells |
Number of production wells | Count |
frac_wells_with_plunger, frac_wells_with_non_plunger
|
Plunger lift system distribution | Fractions |
Both productivity-gas.csv and productivity-oil.csv contain 10 rows × 9 columns:
| Column | Description | Example Values |
|---|---|---|
| Tranche | Productivity bin identifier | 1-10 |
| Bin low | Lower productivity bound | 0, 1, 5, 10, 20, 50, 100, 500, 1000, 10000 |
| Bin high | Upper productivity bound | 1, 5, 10, 20, 50, 100, 500, 1000, 10000, 1×10²⁰ |
| Normalized rate | Productivity normalization factor | 0.003314 to 161.068 |
| Frac total gas | Fraction of total gas from this tranche | 0.000295 to 0.414513 |
These tables define 10 productivity bins ranging from very low productivity (0-1 Mscf/well/day) to extremely high productivity (10,000+ Mscf/well/day).
- Column 0: "Tranche" (metadata, excluded from calculations)
-
Columns 1-140: Productivity-stratified loss data organized as:
[Tranche 1: Well, Header, Heater, Separator, Meter, Tanks-Leaks, Tank thief hatch, Recip Comp, Dehydrator, Chem Inj Pump, Pneum Controllers, Flash factor, LU-plunger, LU-non plunger] [Tranche 2: Well.1, Header.1, Heater.1, ..., LU-non plunger.1] ... [Tranche 10: Well.9, Header.9, Heater.9, ..., LU-non plunger.9]
Example Loss Rate Pattern (Well component):
- Tranche 1 (low productivity): 0.067326
- Tranche 5 (medium productivity): 0.007881
- Tranche 10 (high productivity): 0.000015
- Column 0: "Names" (metadata, excluded from calculations)
- Columns 1-120: Same pattern but 12 components per tranche (excludes LU-plunger, LU-non plunger)
Each row represents one Monte Carlo iteration (10,000+ iterations total) for uncertainty quantification.
productivity = oil_rate * (GOR + gas_lifting * GLIR)
if gas_flooding and flood_gas_type == "CO2":
productivity += oil_rate * GFIR * frac_CO2_breakthrough
# Convert to per-well basis
productivity_per_well = productivity / num_prod_wells # → kscf/dayThis calculates the total field gas productivity, accounting for:
- Natural gas-oil ratio
- Gas lifting operations
- CO2 breakthrough in gas flooding scenarios
DataFrame: field_productivity
- Shape: (10 rows × 4 columns)
- Index: [1, 2, 3, ..., 10] (productivity tranches from reference tables)
| Column | Description | Data Source |
|---|---|---|
| Assignment | Productivity tranche assignment (populated in Stage 3) | Calculated |
| col_shift | Unused placeholder | NaN |
| Mean gas rate (Mscf/well/day) | Normalized rate × per-well productivity | productivity_gas/oil["Normalized rate"] |
| Frac total gas | Fraction of total gas from this tranche | productivity_gas/oil["Frac total gas"] |
GOR-based Table Selection: If GOR > GOR_cutoff, uses gas productivity table; otherwise uses oil productivity table.
The comp_fugitive_productivity() helper function maps field productivity to appropriate bins:
def comp_fugitive_productivity(prod_mat_gas, mean):
result = prod_mat_gas[
(prod_mat_gas["Bin low"] < mean) & (prod_mat_gas["Bin high"] >= mean)
].index.values.astype(int)[0]
return resultProcess: For each of the 10 tranches, find which productivity bin the field's calculated productivity falls into.
Result: field_productivity["Assignment"] contains tranche numbers (1-10) indicating which loss data to use.
This is the most sophisticated part of the algorithm, where ALL 140/120 columns are utilized.
loss_mat_ave = loss_mat.mean(axis=0).values # Average across 10,000 iterations- Gas Result: (140,) array containing averaged loss rates for all tranches and components
- Oil Result: (120,) array containing averaged loss rates for all tranches and components
loss_mat_ave = loss_mat_ave.reshape(len(tranch), len(cols))
# Gas: (140,) → (10, 14) matrix
# Oil: (120,) → (10, 12) matrixThis reshape operation transforms the linear array into a productivity-organized matrix:
Row 0 (Tranche 1): [Well_loss_T1, Header_loss_T1, ..., LU-non_plunger_T1]
Row 1 (Tranche 2): [Well_loss_T2, Header_loss_T2, ..., LU-non_plunger_T2]
...
Row 9 (Tranche 10): [Well_loss_T10, Header_loss_T10, ..., LU-non_plunger_T10]
DataFrame: df (Productivity-Stratified Loss Matrix)
- Shape: (10 rows × 14/12 columns)
- Index: [0, 1, 2, ..., 9] (0-indexed tranches for array access)
- Columns: Component names ['Well', 'Header', 'Separator', 'Meter', 'Tanks-leaks', 'Tank-thief hatch', 'Recip Comp', 'Dehydrator', 'Chem Inj Pump', 'Pneum Controllers', 'Flash factor', 'LU-plunger', 'LU-no plunger']
- Values: Tranche-specific loss rates showing clear decreasing pattern with higher productivity
Additional DataFrame: loss_mat_gas_ave_df (debugging output)
- Same data as
dfbut with meaningful index labels (productivity bin boundaries) - Used for analysis and validation of gas well loss patterns
df = field_productivity.apply(
lambda row: self.comp_fugitive_loss(df, row["Assignment"]), axis=1
)The comp_fugitive_loss() helper function performs the lookup:
def comp_fugitive_loss(loss_mat_ave, assignment):
return loss_mat_ave.iloc[assignment - 1, :] # Convert 1-indexed to 0-indexedData Transformation:
-
Input:
dfwith tranche-organized loss rates (10 rows of different productivity levels) - Process: For each field tranche, select the appropriate productivity-specific loss rates
-
Index Handling: Critical
assignment - 1conversion (assignments are 1-indexed, DataFrame is 0-indexed) -
Output:
dfwhere each row now contains the loss rates specific to that tranche's productivity assignment
Example: If field productivity assigns Tranche 3 to productivity bin 6, then row 2 (0-indexed) of the result will contain the loss rates from row 5 (0-indexed) of the original matrix.
comp_fugitive = df.T.dot(field_productivity["Frac total gas"])Matrix Operation Details:
- df.T: Transpose to (14 rows × 10 columns) - components become rows, tranches become columns
- Dot Product: Matrix multiplication with (10,) "Frac total gas" vector
- Mathematical Purpose: Weight each component's loss rate by the field's gas production distribution across tranches
Result Series: comp_fugitive
- Index: Component names (14 for gas, 12 for oil)
- Values: Field-wide weighted average loss rate for each component
- Units: Dimensionless fractions (maintained through Pint units)
comp_fugitive["Flash factor"] /= 0.51 # Rutherford et al. 2021 correction factorThis corrects for the fraction of wells controlled in the study data (only 51% had controls).
if GOR > GOR_cutoff: # Gas wells
comp_fugitive["LU-plunger-norm"] = (
comp_fugitive["LU-plunger"] * frac_wells_with_plunger +
comp_fugitive["LU-no plunger"] * frac_wells_with_non_plunger
)
# Remove individual plunger components
comp_fugitive.drop(["LU-plunger", "LU-no plunger"], inplace=True)The final step aggregates component-level losses into process-level categories:
separation_loss_rate = comp_fugitive["Separator"]
tank_loss_rate = comp_fugitive["Flash factor"]
pump_loss_rate = comp_fugitive.drop(["Separator", "Flash factor"]).sum()Equipment Component Mapping:
- Separation: Separator equipment only
- CrudeOilStorage: Flash factor (tank breathing and working losses)
- DownholePump: All remaining components (Well, Header, Heater, Meter, Tanks-leaks, Tank-thief hatch, Recip Comp, Dehydrator, Chem Inj Pump, Pneum Controllers, plus normalized plunger lift for gas wells)
process_loss_rate = pd.Series({
"Separation": separation_loss_rate,
"CrudeOilStorage": tank_loss_rate,
"DownholePump": pump_loss_rate
}, dtype="pint[frac]")- Type: pd.Series with Pint fraction units
- Purpose: Provides aggregated loss rates for major process categories used elsewhere in OPGEE
- Purpose: Analysis and debugging of gas well loss patterns
- Contains: The reshaped gas loss matrix (10×14) with meaningful productivity bin labels as index
- Usage: Allows inspection of how loss rates vary across productivity spectrum
The CSV file structure represents a sophisticated 3D data organization:
- Dimension 1: Monte Carlo iterations (rows)
- Dimension 2: Productivity tranches (column groups)
- Dimension 3: Equipment components (columns within groups)
The reshape operation efficiently converts this flattened representation into a usable matrix format.
The data reveals important physical relationships:
- Higher productivity wells have systematically lower per-unit loss rates
- Example: Well component losses range from 0.067326 (low productivity) to 0.000015 (high productivity)
- Physical basis: More productive wells typically have better infrastructure and maintenance
The algorithm carefully manages multiple indexing schemes:
- CSV Structure: 1-indexed column groups representing tranches
- Pandas DataFrames: 0-indexed for array operations
-
Assignment Logic: 1-indexed assignments requiring
assignment - 1conversion - Final Output: Maintains physical meaning through appropriate index labels
- 10,000+ iterations provide robust uncertainty quantification
- Pre-averaged data improves computational efficiency during field-level calculations
- Statistical robustness supports risk assessment and sensitivity analysis
The reshape-and-lookup approach enables:
- Fast productivity-aware calculations without complex conditional logic
- Vectorized operations for computational efficiency
- Clean separation between data structure and algorithm logic
- Compact CSV storage with repeated headers minimizes file size
- Pre-computed averages eliminate need for real-time Monte Carlo calculations
- Matrix operations leverage NumPy/Pandas efficiency
- Productivity weighting recognizes heterogeneity in field characteristics
- Component-level detail supports detailed emissions accounting
- Process-level outputs integrate with broader OPGEE architecture
- Tranche-based organization easily accommodates additional productivity bins
- Component modularity supports equipment-specific analyses
- Clear data flow facilitates maintenance and updates
This method is typically called during field-level emissions calculations to estimate fugitive losses from production equipment. The results feed into:
- Process-specific emissions accounting
- Field-level carbon intensity calculations
- Uncertainty propagation through Monte Carlo analyses
- Equipment-level optimization studies
The productivity-stratified approach ensures that emissions estimates appropriately reflect the field's specific production characteristics and equipment distributions.