This notebook shows how to set up core functions in MRST to run a very basic reservoir simulation. It is based on examples in the book "Geometric Modelling, Numerical Simulation, and Optimization" by Geir Hasle, Knut-Andreas Lie, Ewald Quak.
The Matlab scripts below are stand-alone which means you don't need to load anything else to run a basic reservoir simulation. At this stage, there is no need to install MRST. The scripts described below are enough.
library(RcppOctave) .CallOctave("version")
This work has been performed on a Linux Virtual Machine based on Mint 18.2 and Octave 4.0.
library(RcppOctave) o.TPFA <- OctaveFunction(" function [P,V] = TPFA(Grid, K, q) % Compute transmissibilities by harmonic averaging. Nx=Grid.Nx; Ny=Grid.Ny; Nz=Grid.Nz; N=Nx*Ny*Nz; hx=Grid.hx; hy=Grid.hy; hz=Grid.hz; L = K.^(-1); tx = 2*hy*hz/hx; TX = zeros(Nx+1,Ny,Nz); ty = 2*hx*hz/hy; TY = zeros(Nx,Ny+1,Nz); tz = 2*hx*hy/hz; TZ = zeros(Nx,Ny,Nz+1); TX(2:Nx,:,:) = tx./(L(1,1:Nx-1,:,:) + L(1,2:Nx ,:,:)); TY(:,2:Ny,:) = ty./(L (2,:,1: Ny-1,:) + L(2,:,2:Ny,:)); TZ (:,:,2: Nz) = tz./(L (3,:,:,1: Nz-1)+L(3,:,:,2:Nz)); % Assemble TPFA discretization matrix. x1 = reshape(TX(1:Nx,:,:),N,1); x2 = reshape(TX(2:Nx+1,:,:),N,1); y1 = reshape(TY(:,1:Ny,:),N,1); y2 = reshape(TY(:,2:Ny+1,:),N,1); z1 = reshape(TZ(:,:,1:Nz),N,1); z2 = reshape(TZ(:,:,2:Nz+1),N,1); DiagVecs = [-z2,-y2,-x2,x1+x2+y1+y2+z1+z2,-x1,-y1,-z1]; DiagIndx = [-Nx*Ny,-Nx,-1,0,1,Nx,Nx*Ny]; A = spdiags(DiagVecs,DiagIndx,N,N); A(1,1) = A(1,1)+sum(Grid.K(:,1,1,1)); % Solve linear system and extract interface fluxes. u = A\\q; P = reshape(u,Nx,Ny,Nz); V.x = zeros(Nx+1,Ny,Nz); V.y = zeros(Nx,Ny+1,Nz); V.z = zeros(Nx,Ny,Nz+1); V.x(2:Nx ,:,:) = (P(1:Nx-1,:,:)-P(2:Nx,:,:)).*TX(2:Nx,:,:); V.y (:,2:Ny,:) = (P(:,1:Ny-1,:)-P(:,2:Ny,:)).*TY(:,2:Ny,:); V.z (:,:,2: Nz) = (P (:,:,1: Nz-1)-P(:,:,2:Nz)).*TZ(:,:,2:Nz); ") o.GenA <- OctaveFunction(" function A = GenA(Grid,V,q) Nx=Grid.Nx; Ny=Grid.Ny; Nz=Grid.Nz; N=Nx*Ny*Nz; N=Nx*Ny*Nz; % number of unknowns fp=min(q,0); % production XN=min(V.x,0); x1=reshape(XN(1:Nx,:,:),N,1); % separate flux into YN=min(V.y,0); y1=reshape(YN(:,1:Ny,:),N,1); % − flow in positive coordinate ZN=min(V.z,0); z1=reshape(ZN(:,:,1:Nz),N,1); % direction (XP,YP,ZP) XP=max(V.x,0); x2=reshape(XP(2:Nx+1,:,:),N,1); % − flow in negative coordinate YP=max(V.y,0); y2=reshape(YP(:,2:Ny+1,:),N,1); % direction (XN,YN,ZN) ZP=max(V.z,0); z2=reshape(ZP(:,:,2:Nz+1),N,1); % DiagVecs=[z2,y2,x2,fp+x1-x2+y1-y2+z1-z2,-x1,-y1,-z1]; % diagonal vectors DiagIndx=[-Nx*Ny,-Nx,-1,0,1,Nx,Nx*Ny]; % diagonal index A=spdiags(DiagVecs,DiagIndx,N,N); % matrix with upwind FV stencil ") o.RelPerm <- OctaveFunction(" function [Mw,Mo,dMw,dMo] = RelPerm(s,Fluid) S = (s-Fluid.swc)/(1-Fluid.swc-Fluid.sor); % Rescale saturations Mw = S.^2/Fluid.vw; % Water mobility Mo =(1-S).^2/Fluid.vo; % Oil mobility if (nargout==4) dMw = 2*S/Fluid.vw/(1-Fluid.swc-Fluid.sor); dMo = -2*(1-S)/Fluid.vo/(1-Fluid.swc-Fluid.sor); end ") o.Pres <- OctaveFunction(" function [P,V] = Pres(Grid,S,Fluid,q) % Compute K∗lambda(S) [Mw,Mo] = RelPerm(S,Fluid); Mt = Mw+Mo; KM = reshape([Mt,Mt,Mt]',3,Grid.Nx,Grid.Ny,Grid.Nz).*Grid.K; % Compute pressure and extract fluxes [P,V] = TPFA(Grid,KM,q); ") o.Upstream <- OctaveFunction(" function S = Upstream(Grid,S,Fluid,V,q,T) Nx=Grid.Nx; Ny=Grid.Ny; Nz=Grid.Nz; % number of grid points N=Nx*Ny*Nz; % number of unknowns pv = Grid.V(:).*Grid.por(:); % pore volume=cell volume∗porosity fi =max(q,0); % inflow from wells XP=max(V.x,0); XN=min(V.x,0); % influx and outflux, x−faces YP=max(V.y,0); YN=min(V.y,0); % influx and outflux, y−faces ZP=max(V.z,0); ZN=min(V.z,0); % influx and outflux, z−faces Vi = XP(1:Nx,:,:)+YP(:,1:Ny,:)+ZP(:,:,1:Nz)-... % total flux into XN(2:Nx+1,:,:)-YN(:,2:Ny+1,:)-ZN(:,:,2:Nz+1); % each gridblock pm = min(pv./(Vi(:)+fi)); % estimate of influx cfl = ((1-Fluid.swc-Fluid.sor)/3)*pm; % CFL restriction Nts = ceil(T/cfl); % number of local time steps dtx = (T/Nts)./pv; % local time steps A = GenA(Grid,V,q); % system matrix A=spdiags(dtx,0,N,N)*A; % A ∗ dt/|Omega i| fi =max(q,0).*dtx; % injection for t=1:Nts [mw,mo]=RelPerm(S,Fluid); % compute mobilities fw = mw./(mw+mo); % compute fractional flow S = S+(A*fw+fi); % update saturation end ")
All the scripts are practically executed in Octave. The graphics window is also opened by sending the plot commands contourf
and drawnow
.
o.runExample <- OctaveFunction(" function [Grid, N, S, Fluid, Q] = runExample() Grid.Nx=64; Dx=1; Grid.hx = Dx/Grid.Nx; % Dimension in x−direction Grid.Ny=64; Dy=1; Grid.hy = Dy/Grid.Ny; % Dimension in y−direction Grid.Nz=1; Dz=1; Grid.hz = Dz/Grid.Nz; % Dimension in z−direction N = Grid.Nx*Grid.Ny; % Total number of grid blocks Grid.V = Grid.hx*Grid.hy*Grid.hz; % Cell volumes Grid.K = ones(3,Grid.Nx,Grid.Ny,Grid.Nz); % Unit permeability Grid.por = ones(Grid.Nx,Grid.Ny,Grid.Nz); % Unit porosity Q = zeros(N,1); Q([1 N])=[1 -1]; % Production/injection Fluid.vw = 1.0; Fluid.vo=1.0; % Viscosities Fluid.swc = 0.0; Fluid.sor=0.0; % Irreducible saturations S = zeros(N,1); % Initial saturation nt = 25; dt = 0.7/nt; % Time steps for t=1:nt [P,V]=Pres(Grid,S,Fluid,Q); % pressure solver S=Upstream(Grid,S,Fluid,V,Q,dt); % saturation solver % plot filled contours at the midpoints of the grid cells contourf(linspace(Grid.hx/2,Dx-Grid.hx/2,Grid.Nx),... linspace(Grid.hy/2,Dy-Grid.hy/2,Grid.Ny),... reshape(S,Grid.Nx,Grid.Ny),11,'k'); axis square; caxis([0 1]); % equal axes and color drawnow; % force update of plot end ") runExample <- o.runExample()
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.