Material Balance: example 1, part 2

R Reservoir engineering

Oil material balance calculation for a model without a gas cap and aquifer. Fit and Drive indeces.

Rigoberto Chandomi Vazquez https://www.linkedin.com/in/rigoberto-chandomi-v%C3%A1zquez-79038495/
02-05-2022

After estimate STOIP using both, Havlena–Odeh and Campbell diagnostic plots in the first part, link, the cumulative production, \(N_p\) can be simulate to validate the results. With \(w_p =0\) and \(m=0\), the equation is

\[ N_p = \frac{NB_{oi}}{(B_o+(R_p-R_s)B_g)}\left[\frac{(B_o-B_{oi})+(R_{si}-R_s)B_g}{B_{oi}} +\frac{c_wS_{wc}+c_f}{1-S_{wc}} \Delta p \right]\]

Getting \(N=250\) from Campbell plot, add a calculated Np column

N = 250

BM_data <- BM_data %>% 
  mutate(Np_cal  = ((N*Boi)/(Bo+Bg*(Rp-Rs)))*(((Bo-Boi)+(Rsi-Rs)*Bg)/Boi+(Pi-Pavg)*(cw*Sw+cf)/(1-Sw)))

plot_MB_3 <- ggplot(BM_data) + 
  geom_point(aes(x = Date, y = Np)) + 
  geom_line(aes(x = Date, y = Np_cal))

plot_MB_3

We can observe some different at the end of plot, it can be improve using non-linear regression a estimate a value of \(N\) that fit better \(Np\). With minpack.lm we can use Levenberg-Marquardt algorithm o minimiza the differente. First, we need to define a residual function, in this case, the different between real Np and calcualted. Once getting a fit \(N\) value, we recalculate \(Np\)

library(minpack.lm)

residFun <- function(parS, observed, data, Swf = Sw, cff = cf, cwf = cw, Pif = Pi){
  
  Boif <- data$Bo[1]
  Rsif <- data$Rs[1]
  Pif <- data$Pavg[1]
  
  Np_cal <- ((parS$Nf*Boi)/(data$Bo+data$Bg*(data$Rp-data$Rs)))*
    (((data$Bo-Boif)+(Rsif-data$Rs)*data$Bg)/Boif+(Pif-data$Pavg)*(cwf*Swf+cff)/(1-Swf)) 
  
  #sum(abs(observed - Np_cal))
  observed - Np_cal
}


parStart <- list(Nf = N)

fit_model <- nls.lm(par = parStart, fn = residFun, observed =  BM_data$Np,
                    data = BM_data, Swf = Sw, cff = cf, cwf = cw, 
                    Pif = Pi, control = nls.lm.control(nprint=1))
It.    0, RSS =    1.88377, Par. =        250
It.    1, RSS =   0.403168, Par. =    253.265
It.    2, RSS =   0.403168, Par. =    253.265
fit_coeff <- as.list(coef(fit_model))

print(paste("Fit N = ", fit_coeff, sep = ""))
[1] "Fit N = 253.264567990061"
BM_data <- BM_data %>% 
  mutate(Np_cal  = ((fit_coeff$Nf*Boi)/(Bo+Bg*(Rp-Rs)))*
           (((Bo-Boi)+(Rsi-Rs)*Bg)/Boi+(Pi-Pavg)*(cw*Sw+cf)/(1-Sw)))


plot_MB_3 <- ggplot(BM_data) + 
  geom_point(aes(x = Date, y = Np)) + 
  geom_line(aes(x = Date, y = Np_cal))

plot_MB_3

Finally, The drive indices can be calculated using the following equations

Depletion Drive Index \[ DDI = \frac{N[(B_o-B_{oi})+(R_{si}-R_s)B_g]}{N_p(B_o+(R_{si}-R_s)B_g)}\]

Compaction Drive Index

\[CDI = \frac{NB_{oi}(1+m) \left( \frac{c_wS_{wc}+c_f}{1-S_{wc}} \right) \Delta p}{N_p(B_o+(R_{si}-R_s)B_g)}\]

BM_data <- BM_data %>% 
mutate(DDI = ((fit_coeff$Nf*((Bo-Boi)+(Rsi-Rs)*Bg))/(Np*(Bo+Bg*(Rp-Rs)))),
       CDI = 1-DDI)

DDI <- BM_data[,c("Date", "DDI")] 
DDI$Index <- "DDI"

CDI <- BM_data[,c("Date", "CDI")] 
CDI$Index <- "CDI"

colnames(DDI) <- c("Date", "Value", "Index")
colnames(CDI) <- c("Date", "Value", "Index")
Index <- rbind(DDI, CDI)

ggplot(Index, aes(x=Date, y=Value, fill=Index)) +
  geom_area() 

References Sanni, M. (2019) Petroleum Engineering. Principles, Calculatios, and Workflows

Citation

For attribution, please cite this work as

Vazquez (2022, Feb. 5). Chato Solutions: Material Balance: example 1, part 2. Retrieved from https://www.chatosolutions.com/posts/2022-02-05-mb2/

BibTeX citation

@misc{vazquez2022material,
  author = {Vazquez, Rigoberto Chandomi},
  title = {Chato Solutions: Material Balance: example 1, part 2},
  url = {https://www.chatosolutions.com/posts/2022-02-05-mb2/},
  year = {2022}
}