[1]:
import matplotlib.pyplot as plt
import numpy as np
import numpy.linalg as npl
from scipy.sparse.linalg import cg
import pherosensor
from pheromone_dispersion.convection_diffusion_2D import DiffusionConvectionReaction2DEquation, Source
from pheromone_dispersion.diffusion_tensor import DiffusionTensor
from pheromone_dispersion.geom import MeshRect2D
from pheromone_dispersion.velocity import Velocity
from source_localization.cost import Cost
from source_localization.control import Control
from source_localization.adjoint_convection_diffusion_2D import AdjointDiffusionConvectionReaction2DEquation
from source_localization.obs import Obs
[2]:
Lx = 20#50 # the length along the x-axis
Ly = 25#60 # the length along the y-axis
Delta_x = 1. # 0.4 the space step along the x-axis
Delta_y = 1. # 0.4 the space step along the y-axis
T_final = 2. # the final time of the simulation
Test of the numerical scheme of the adjoint model
This notebook aims to test the numerical scheme of the direct model and its implementation by comparing the results of the numerical solver with a reference solution used to derive an associated source term.
Reference solution
In the present case, we consider a velocity field of shape \(U(x,y) = (u(x,y),0)^T\) (with \(u\geq0\)) with the horizontal velocity \(u(x,y)=\frac{4}{L_x^2}(x-L_x)^2\frac{3}{2L_y}y\). Let us note that this velocity satisfies \(u\geq0\) and \(up = 0\) at \(x=L_x\).
[3]:
def velocity_horizontal(x,y):
xx, yy = np.meshgrid(x, y)
return 4 * (xx - Lx)**2 * 3 * yy / (Lx**2 * 2 * Ly)
def velocity_field(msh):
U_hi = np.zeros((msh.y_horizontal_interface.size, msh.x.size,2))
U_hi[:,:,0] = velocity_horizontal(msh.x, msh.y_horizontal_interface)
U_vi = np.zeros((msh.y.size, msh.x_vertical_interface.size,2))
U_vi[:,:,0] = velocity_horizontal(msh.x_vertical_interface, msh.y)
return Velocity(msh, U_vi, U_hi)
The diffusion tensor is considered of shape \(K=diag(K_x,K_y)\) with constant \(K_x\) and \(K_y\) and the reaction coefficient constant.
[4]:
K_x = 5./6 # diffusion coefficient in the crosswind direction (less weak)
K_y = 0.01 # diffusion coefficient in the downwind direction (very weak)
tau_loss = 10
[5]:
nx = 1
ny = 3
lambda_x = 2 * np.pi * nx / Lx
lambda_y = 2 * np.pi * ny / Ly
def p_reference(msh):
xx, yy = np.meshgrid(msh.x, msh.y)
p_ref = np.zeros((msh.t_array.size, msh.y.size, msh.x.size))
for it,t in enumerate(msh.t_array):
p_ref[it,:,:] = (msh.T_final - t) * ( np.cos(lambda_x * xx) + np.cos(lambda_y * yy) )
return p_ref
[6]:
def Obs_reference(msh):
u = velocity_horizontal(msh.x,msh.y)
N_obs = msh.y.size * msh.x.size * msh.t_array.size
N_xy = msh.y.size * msh.x.size
X_obs = np.zeros((N_obs,2))
t_obs = np.zeros((N_obs,))
C_obs = np.zeros((N_obs,))
i_obs = 0
for it, tc in enumerate(msh.t_array):
dt = msh.T_final - tc
for ix, x in enumerate(msh.x):
for iy, y in enumerate(msh.y):
C_obs[i_obs] = 0.5 * (1 + tau_loss * dt + K_x * lambda_x**2 * dt) * np.cos(lambda_x * x)
C_obs[i_obs]+= 0.5 * (1 + tau_loss * dt + K_y * lambda_y**2 * dt) * np.cos(lambda_y * y)
C_obs[i_obs]+= 0.5 * u[iy,ix] * lambda_x * dt * np.sin(lambda_x * x)
X_obs[i_obs, 0] = x
X_obs[i_obs, 1] = y
t_obs[i_obs] = tc
i_obs +=1
return Obs(t_obs, X_obs, C_obs/msh.dt, msh)
Numerical solver
The space discretization of the diffusion and advection terms are presented in the notebooks dedicated to the test of the associated schemes.
In the following, \(p^{solver}\) denotes the estimation of \(p\) obtained by solving numerically the equation.
[8]:
def EDP(msh, dt):
U = velocity_field(msh)
msh.calc_dt_implicit_solver(dt)
K = DiffusionTensor(U, K_x, K_y)
coeff_loss = tau_loss * np.ones((msh.y.size, msh.x.size))
obs = Obs_reference(msh)
S = Source(msh, np.zeros((msh.y.size, msh.x.size)))
ctrl = Control(S, msh)
cost = Cost(msh, obs, ctrl)
cost.obs.d_est = np.zeros(cost.obs.d_obs.shape)
return AdjointDiffusionConvectionReaction2DEquation(U, K, coeff_loss, msh, time_discretization='implicit'), cost
Analysis of the error estimate
In this section, the estimate of the error \(e(x,y,t)=|p^{solver}(x,y,t)-p^{ref}(x,y,t)|\) is analyzed.
Analysis of the error estimate for several space step
[9]:
coeff_dx_a = [0.5, 1., 1.5, 2.]
abs_a = np.zeros((len(coeff_dx_a)))
MAE_vs_space_step = np.zeros((len(coeff_dx_a)))
RMSE_vs_space_step = np.zeros((len(coeff_dx_a)))
for i, coeff_dx in enumerate(coeff_dx_a):
msh = MeshRect2D(Lx, Ly, Delta_x * coeff_dx, Delta_y * coeff_dx, T_final)
model_adjoint, cost = EDP(msh, 0.025)
p_ref = p_reference(msh)
abs_a[i] = msh.dx + msh.dt
print("")
print("dx = ", msh.dx)
print("dt = ", msh.dt)
p_solver = model_adjoint.solver(cost.obs.adjoint_derivative_obs_operator, cost)
p_solver = p_solver.reshape((msh.t_array.size, msh.y.size, msh.x.size))
RMSE_vs_space_step[i] = npl.norm(p_solver - p_ref) / np.sqrt(p_solver.size)
MAE_vs_space_step[i] = np.mean(np.abs(p_solver - p_ref))
/home/tmalou/anaconda3/envs/pherosensor-new/lib/python3.7/site-packages/pherosensor-0.1.1-py3.7.egg/pheromone_dispersion/diffusion_tensor.py:55: RuntimeWarning: invalid value encountered in true_divide
U_at_vertical_interface = self.U.at_vertical_interface / norm_U_at_vertical_interface[:, :, None]
/home/tmalou/anaconda3/envs/pherosensor-new/lib/python3.7/site-packages/pherosensor-0.1.1-py3.7.egg/pheromone_dispersion/diffusion_tensor.py:74: RuntimeWarning: invalid value encountered in true_divide
U_at_horizontal_interface = self.U.at_horizontal_interface / norm_U_at_horizontal_interface[:, :, None]
dx = 0.5
dt = 0.025
t = 0.000 / 2.000 s
dx = 1.0
dt = 0.025
t = 0.000 / 2.000 s
dx = 1.5
dt = 0.025
t = 0.000 / 2.000 s
dx = 2.0
dt = 0.025
t = 0.000 / 2.000 s
[10]:
slope_MAE_vs_space_step = ( np.log(MAE_vs_space_step[0]) - np.log(MAE_vs_space_step[-1]) ) / ( np.log(abs_a[0]) - np.log(abs_a[-1]) )
slope_RMSE_vs_space_step = ( np.log(RMSE_vs_space_step[0]) - np.log(RMSE_vs_space_step[-1]) ) / ( np.log(abs_a[0]) - np.log(abs_a[-1]) )
fontsize = 25
fig, ax1 = plt.subplots(figsize=(20, 10))
ax1.plot(abs_a,RMSE_vs_space_step,'-ob',label=r'$\frac{1}{\sqrt{T|\Omega|}}||e||_{L_2([0;T]\times\Omega)}$'+f', slope = {"{:.5f}".format(slope_RMSE_vs_space_step)}')
ax1.tick_params(axis='both',labelsize=fontsize-5)
ax1.set_ylabel(r'$\frac{1}{\sqrt{T|\Omega|}}||e||_{L_2([0;T]\times\Omega)}$', fontsize=fontsize)
ax1.set_xlabel('$\Delta t + \Delta x$', fontsize=fontsize)
ax1.set_xscale('log')
ax1.set_yscale('log')
ax1.set_title(r'for a fixed $\Delta t$ and varying $\Delta x$', fontsize=fontsize)
ax2 = ax1.twinx()
ax2.plot(abs_a,MAE_vs_space_step,'-or',label=r'$\frac{1}{T|\Omega|}||e||_{L_1([0;T]\times\Omega)}$'+f', slope = {"{:.5f}".format(slope_MAE_vs_space_step)}')
ax2.tick_params(axis='both',labelsize=fontsize-5)
ax2.set_ylabel(r'$\frac{1}{T|\Omega|}||e||_{L_1([0;T]\times\Omega)}$', fontsize=fontsize)
ax2.set_yscale('log')
line1, label1 = ax1.get_legend_handles_labels()
line2, label2 = ax2.get_legend_handles_labels()
ax2.legend(line1+line2,label1+label2,loc='upper left',prop={'size': fontsize})#, bbox_to_anchor=(1.17,1.1)
[10]:
<matplotlib.legend.Legend at 0x7f4383188250>
[ ]: