I’ve been trying to solve the water hammer PDE’s from the Maple example linked below in python (numpy/scipy). I’m getting very unstable results. Can anyone see my mistake? Guessing something is wrong with the boundary conditions.
https://www.maplesoft.com/support/help/view.aspx?path=applications/WaterHammer
import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
## Parameters
Dia = 0.1
V = 14.19058741 # Stead state
p = 1000 # Liquid density
u = 0.001 # Viscosity
L = 25
e = 0.0001 # Roughness
Psource = 0.510E6
thick = 0.001
E= 7010*10**9
K=20010E6
Vsteady= 14.19058741
Ks = 1/((1/K)+(Dia/E*thick))
# Darcy-Weisbach
def Friction(V):
Rey = ((Dia*V*p)/u)
fL = 64/Rey
fT = 1/((1.8*np.log10((6.9/Rey) + (e/(3.7*Dia))**1.11))**2)
if Rey >= 0 and Rey < 2000:
return fL
if Rey >= 2000 and Rey<4000:
return fL + ((fT-fL)*(Rey-2000))/(4000-2000)
if Rey >= 4000:
return fT
return 0
def model(D, t):
V = D[:N]
P = D[N:]
dVdt = np.zeros(N)
for i in range(1, len(dVdt)-1):
dVdt[i] = -(1/p)*((P[i+1]-P[i-1])/2*dx)-((Friction(np.abs(V[i]))*(np.abs(V[i])**2))/(2*Dia))
dPdt = np.zeros(N)
for i in range(1, len(dPdt)-1):
dPdt[i] = -((V[i+1]-V[i-1])/(2*dx))*Ks
if t < 2:
dVdt[29] = 0
else:
dVdt[29] = -1
dPdt[29] = 0
dVdt[0] = dVdt[1]
return np.append(dVdt,dPdt)
N = 30
x = np.linspace(0, L, N)
dx = x[1] - x[0]
## Initial conditions
Vi_0 = np.ones(N)*Vsteady
Pi_0 = np.arange(N)
for i in Pi_0:
Pi_0[i] = Psource - (i*dx/L)*Psource
# initial condition
y0 = np.append(Vi_0, Pi_0)
# time points
t = np.linspace(0,3,10000)
# solve ODE
y = odeint(model,y0,t)
Vr = y[:,0:N]
Pr = y[:,N:]
plt.plot(t,Pr[:,5])
I have just come across this posting and have modified your code to obtain exactly the same results in python
as from the Maple
code you cite. The following points are worth making:
boundary conditions
. The Maple
version uses ghost cells
and I have followed
the same approach.scripy.integrate.solve
integrator as it gives more
control over the numerical integration. I found that the most
efficient method (fastest) for this problem was DOP853
.The python code:
import numpy as np
import scipy as sc
from scipy import integrate
# from scipy.integrate import odeint
import matplotlib.pyplot as plt
import time
t_start = time.time()
## Parameters
p = 1000 # Liquid density
K = 200*1E6 # Bulk Modulus of Liquid
u = 0.001 # Viscosity
Dia = 0.1 # Pipe Diameter
thick = 0.001 # Pipe Thickness
e = 0.0001 # Roughness
L = 25 # Length of Pipe
E = 70*1E9 # Young's Modulus
A = (np.pi*Dia**2)/4 # Cross-sectional Area
Psource = 0.5*1E6 # Pressure at Start of Pipeline
Ks = 1/((1/K)+(Dia/(E*thick))) # Effective Modulus of system
Vsteady = 14.19058741 # Steady State (from Maple)
a0 = np.sqrt(K/p) # Speed of Sound
# Darcy-Weisbach
def Friction(V):
Rey = ((Dia*V*p)/u)
fL = 64/Rey
fT = 1/((1.8*np.log10((6.9/Rey) + (e/(3.7*Dia))**1.11))**2)
if Rey > 0 and Rey < 2000:
return fL
elif Rey >= 2000 and Rey<4000:
return fL + ((fT-fL)*(Rey-2000))/(4000-2000)
elif Rey >= 4000:
return fT
else:
return 0
def model(t, D):
V = np.zeros(N+2) # Allocate Nodes with ghost cells
P = np.zeros(N+2)
V[1:N+1] = D[0:N] # Populate Node Data
P[1:N+1] = D[N:2*N]
dVdt = np.zeros(N+2) # Allocate Derivative vars with ghost cells
dPdt = np.zeros(N+2)
if t >= t_trip:
# Set ghost cells equal to BCs, as Maple
V[0] = V[1]
P[0] = Psource
V[N+1] = Vsteady*np.exp(-70*(t-t_trip))
P[N+1] = 0
for i in range(1, N+1):
dVdt[i] = -(1/p)*(P[i+1]-P[i-1])/(2*dx)- \
((Friction(np.abs(V[i]))*(V[i]*np.abs(V[i])))/(2*Dia))
for i in range(1, N+1):
dPdt[i] = -((V[i+1]-V[i-1])/(2*dx))*Ks
return np.append(dVdt[1:N+1],dPdt[1:N+1])
N = 30
x = np.linspace(0, L, N)
dx = x[1] - x[0]
## Initial conditions
Vi_0 = np.ones(N)*Vsteady
Pi_0 = np.zeros(N)
for i in range(N):
Pi_0[i] = Psource - (i*dx/L)*Psource
y0 = np.append(Vi_0, Pi_0)
# time points
t0 = 0.0
tf = 3.0
t_trip = 2
# solve ODE
method = "DOP853" #"LSODA","DOP853","BDF","RK23","RK45,Radau""
tol = 1.e-12
out = sc.integrate.solve_ivp(fun=model,
t_span=[t0,tf], y0=y0,
method=method, first_step=1e-6, max_step=0.0001,
rtol=tol, atol=tol)
print(r"Integration Success =", out.success)
print(out.message)
dP_max = p*a0*Vsteady
print(f"Maximum Theoretical Pressure = {dP_max/10**6:.2f} [MPa]")
t_end = time.time()
total_time = t_end-t_start
print(f"Elapsed Time = {total_time:.2f}")
t = out.t
sol = out.y
Vr = sol[0:N,:]
Pr = sol[N:2*N,:]
plotNode = N-1
fig = plt.figure(figsize=(10,6))
ax1, ax2 = fig.subplots(2)
fig.suptitle(f'Water Hammer Plots at Node: {plotNode}',
fontsize=20)
ax1.plot(t,Pr[plotNode,:])
ax2.plot(t,Vr[plotNode,:])
ax1.set_xlabel('time [s]', fontsize=16)
ax1.set_ylabel('Pr [Pa]', fontsize=16)
ax2.set_xlabel('time [s]', fontsize=16)
ax2.set_ylabel('Vr [m/s]',fontsize=16)
ax1.grid()
ax2.grid()
fig.tight_layout()