#!/usr/bin/env python3

# Copyright 2019-2020 Axel Huebl, Remi Lehe
#
# This file is part of WarpX.
#
# License: BSD-3-Clause-LBNL

"""
This script checks the dive cleaning and PML routines, by initializing a
Gaussian beam in the center of the simulation box, and let the error in
space-charge field propagate away and be absorbed in the PML.

This script verifies that the field at the end of the simulation corresponds
to the theoretical field of a Gaussian beam.
"""
import sys

import matplotlib

matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import scipy.constants as scc
from scipy.special import gammainc
import yt

yt.funcs.mylog.setLevel(0)

# Parameters from the Simulation
Qtot = -1.e-20
r0 = 2.e-6

# Open data file
filename = sys.argv[1]
ds = yt.load( filename )
# yt 4.0+ has rounding issues with our domain data:
# RuntimeError: yt attempted to read outside the boundaries
#               of a non-periodic domain along dimension 0.
if 'force_periodicity' in dir(ds): ds.force_periodicity()

# Extract data
ad0 = ds.covering_grid(level=0, left_edge=ds.domain_left_edge, dims=ds.domain_dimensions)
Ex_array = ad0[("mesh", "Ex")].to_ndarray().squeeze()
if ds.dimensionality == 2:
    # Rename the z dimension as y, so as to make this script work for 2d and 3d
    Ey_array = ad0[("mesh", "Ez")].to_ndarray().squeeze()
    E_array = ( Ex_array**2 + Ey_array**2 )**.5
    relative_tolerance = 0.1
elif ds.dimensionality == 3:
    Ey_array = ad0[("mesh", "Ey")].to_ndarray()
    Ez_array = ad0[("mesh", "Ez")].to_ndarray()
    E_array = ( Ex_array**2 + Ey_array**2 + Ez_array**2 )**.5
    relative_tolerance = 0.165

# Extract grid coordinates
Nx, Ny, Nz =  ds.domain_dimensions
xmin, ymin, zmin = ds.domain_left_edge.v
Lx, Ly, Lz = ds.domain_width.v
x = xmin + Lx/Nx*(0.5+np.arange(Nx))
y = ymin + Ly/Ny*(0.5+np.arange(Ny))
z = zmin + Lz/Nz*(0.5+np.arange(Nz))

# Compute theoretical field
if ds.dimensionality == 2:
    x_2d, y_2d = np.meshgrid(x, y, indexing='ij')
    r2 = x_2d**2 + y_2d**2
    factor = (Qtot/r0)/(2*np.pi*scc.epsilon_0*r2) * (1-np.exp(-r2/(2*r0**2)))
    Ex_th = x_2d * factor
    Ey_th = y_2d * factor
    E_th = ( Ex_th**2 + Ey_th**2 )**.5
elif ds.dimensionality == 3:
    x_2d, y_2d, z_2d = np.meshgrid(x, y, z, indexing='ij')
    r2 = x_2d**2 + y_2d**2 + z_2d**2
    factor = Qtot/(4*np.pi*scc.epsilon_0*r2**1.5) * gammainc(3./2, r2/(2.*r0**2))
    Ex_th = factor*x_2d
    Ey_th = factor*y_2d
    Ez_th = factor*z_2d
    E_th = ( Ex_th**2 + Ey_th**2 + Ez_th**2 )**.5

# Plot theory and data
def make_2d(arr):
    if arr.ndim == 3:
        return arr[:,:,Nz//2]
    else:
        return arr
plt.figure(figsize=(10,10))
plt.subplot(221)
plt.title('E: Theory')
plt.imshow(make_2d(E_th))
plt.colorbar()
plt.subplot(222)
plt.title('E: Simulation')
plt.imshow(make_2d(E_array))
plt.colorbar()
plt.subplot(223)
plt.title('E: Diff')
plt.imshow(make_2d(E_th-E_array))
plt.colorbar()
plt.subplot(224)
plt.title('E: Relative diff')
plt.imshow(make_2d((E_th-E_array)/E_th))
plt.colorbar()
plt.savefig('Comparison.png')

# Automatically check the results
def check(E, E_th, label):
    print( 'Relative error in %s: %.3f'%(
            label, abs(E-E_th).max()/E_th.max()))
    assert np.allclose( E, E_th, atol=relative_tolerance*E_th.max() )

check( Ex_array, Ex_th, 'Ex' )
check( Ey_array, Ey_th, 'Ey' )
if ds.dimensionality == 3:
    check( Ez_array, Ez_th, 'Ez' )
