rm(list=ls()) # remove all variables from workspace
# Define variables
# Groundwater flow
ne = 0.3 # m^3/m^3
gradhx=-0.1 # m/m
gradhy=0 # m/m
Kd=1e-4*60*60*24 # m/d
d=10 # m
jwx=-Kd*gradhx / ne # m/d -> Darcy * porosity
A = 1 * d # m2 -> for 1D flow, assuming the aquifer has a width of 1m and d= 10 m
# Transport parameters
Mo=10 # kg
alpha_L = 1 # m
tout=100 # d
Dxx = alpha_L*jwx # m2/d -> calculate from dispersivity and flow velocity
Dyy = 0.1*Dxx # m2/d -> as indicated
# Estimate location of the peak
xcent=jwx*tout # m -> centre of the distribution in x direction, calculate from advection
ycent = 0 # m -> centre of the distribution in y direction
Estimate the width of the distribution: Remember that the spread of the distribution (\(\sigma\)) depends on the dispersivity and travelling time (see lecture notes). Also, we know that 99.7% of the spread is covered by \(\mu\) +- 3* \(\sigma\), where \(\mu\)= x_cent= \(j_{wx} \cdot t_{out}\). Thus, select the plotting area to cover x_cent +- 3 * \(\sigma\)
sig3 = 3*sqrt(Dxx*2*tout) # see lecture note, only calculate in longitudinal direction
# The solution should be given in the following resolution
xstep=1e-1 # resolution in x direction
ystep=xstep # same resolution in y direction
# select the area comprising 99.7 % of the concentration
x=seq(xcent-sig3,xcent+sig3, by=xstep) # these are the x-points the solution will be calculated on
y=seq(ycent-sig3,ycent+sig3, by=ystep) # these are the y-points the solution will be calculated on
I am calculating again the 2-dimensional analytical solution, just in order to be able to compare it to the particle tracking below.
# initialize a matrix to receive the solution
Ct_2d<-matrix(NA,length(x),length(y)) # length(x) = number of rows, length(y) = number of columns
# calculate the solution
for(i in 1:length(y)) {
Ct_2d[,i]=(Mo/(4*pi*ne*d*tout*sqrt(Dxx*Dyy)))*exp((-(x-jwx*tout)^2/(4*Dxx*tout)) - (y[i]^2/(4*Dyy*tout)) )
}
opar <- par() # I am saving the current parameters as a backup
par(pin=c(3,3)) # now I am changing the plot parameters on the plot size
# the following is the plot command, it will use the paramers I set above and therefore the plot will be quadratic
contour(x,y,Ct_2d,xlim = c(xcent-sig3,xcent+sig3), ylim=c(ycent-sig3,ycent+sig3), xlab="x [m]", ylab="y [m]")
Use the implementation shown in the lecture.
I am stepping each particle one day at a time. Each step consists of a deterministic (advection) and random (dispersion) component. Steps in x and y direction are calculated separatly.
np = 2.5e+4 # number of particles to be implemented in the field
tstep = 1 # days
ntime = tout/tstep # number of time steps to be modelled
# initial conditions for particles (all start at x=0, y=0)
pos_x_p = rep(0,np) # this is an array that contains np columns
pos_y_p = rep(0,np) # this is an array that contains np columns
for (ti in 1:ntime) {
delta_x_p = jwx*tstep+sqrt(Dxx*2*tstep)*rnorm(np,0,1) # step all np particles in x direction
delta_y_p = 0 + sqrt(Dyy*2*tstep)*rnorm(np,0,1) # step all np particles in y direction
pos_x_p = pos_x_p + delta_x_p # new x position for all np particles
pos_y_p = pos_y_p + delta_y_p # new y position for all np particles
}
# plot particle tracking solution at the end
par(pin=c(3,3))
plot(pos_x_p, pos_y_p, cex=0.05, xlim = c(xcent-sig3,xcent+sig3), ylim=c(ycent-sig3,ycent+sig3), xlab="x [m]", ylab="y [m]")
Test whether the particle tracking yields the same result as the analytical solution. One option to do this is to calculate the mass distribution obtained from the analytical solution and particle tracking and compare, whether they give the same result. For matters of simplicity, I will not compare the 2D solution directly, but the mass distribution along the x and y axis.
# Initialize a vector which will contain the number of particles contained in each window (or section) of size xstep along the x-axis.
counts_1D_x = matrix(data=0,nrow=length(x), ncol=1)
# count the particles falling in a certain window along x direction
for (i in 1:length(x)) {
counts_1D_x[i] = length(which(abs(x[i]-pos_x_p)<xstep/2))
}
# To convert counts to concentration we have to calculate, which mass corresponds to each particle. We added np patricles to simulate Mo kg of added tracer, thus each particle corresponds to
m_p = Mo/np # kg
# The 1D mass distribution of the average concentration along the x-axis estimated from particle tracking is
m_pt_1d_x = counts_1D_x*m_p
# The corresponding mass distribution based on the analytical solution is
m_anal_1d_x = rowSums(Ct_2d*xstep*ystep*d*ne)
# note 1: concentration * volume = mass
# note 2: rowsum means a sum along the y axis and yields the mass distribution along the x axis
# Overlay the two graphs to see that they do indeed overlap pretty well!
par(pin=c(3,3))
plot(x,m_anal_1d_x, col="red", ylab="Mass [kg]", xlab="x [m]", main="along x-axis")
points(x,m_pt_1d_x, cex=0.05)
# the same can be done in the y-direction:
counts_1D_y = matrix(data=0,nrow=length(y), ncol=1)
# count particles falling in a certain window along y direction
for (i in 1:length(y)) {
counts_1D_y[i] = length(which(abs(y[i]-pos_y_p)<ystep/2))
}
m_pt_1d_y = counts_1D_y*m_p # mass distribution rom particle traking, y-direction
# corresponding mass distribution from the analytical solution
m_anal_1d_y = colSums(Ct_2d*xstep*ystep*d*ne)
plot(m_anal_1d_y,y, col="green", xlab="Mass in [kg]", ylab="y [m]",main="along y-axis")
points(m_pt_1d_y,y, cex=0.5)
Using a function called “layout” (use help or google to learn more), the plots can be arranged in an intuitive fashion.
layout(matrix(c(1,2,3,4), 2, 2, byrow = TRUE)) # this arranges the figures
contour(x,y,Ct_2d,xlim = c(xcent-sig3,xcent+sig3), ylim=c(ycent-sig3,ycent+sig3), xlab="x [m]", ylab="y [m]")
points(pos_x_p, pos_y_p, cex=0.02)
plot(m_anal_1d_y,y, col="green", xlab="Mass in [kg]", ylab="y [m]")
points(m_pt_1d_y,y, cex=0.5)
plot(x,m_anal_1d_x, col="red", ylab="Mass in [kg]", xlab = "x [m]")
points(x,m_pt_1d_x, cex=0.5)