diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 000000000..13566b81b --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 000000000..6f62c9e9e --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/gprMax_optimized.iml b/.idea/gprMax_optimized.iml new file mode 100644 index 000000000..535ce23dd --- /dev/null +++ b/.idea/gprMax_optimized.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 000000000..3db929f43 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 000000000..105ce2da2 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..74d76b5b5 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..13e4a612c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..35eb1ddfb --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Copie pml cylikndrique.txt b/Copie pml cylikndrique.txt new file mode 100644 index 000000000..37b9ed9e1 --- /dev/null +++ b/Copie pml cylikndrique.txt @@ -0,0 +1,997 @@ +# Copyright (C) 2025: Quandela +# Authors: Quentin David +# +# This file is added to the modified code of gprMax allowing for cylindrical coordinate. +# +# gprMax is free software: you can redistribute it and/or modify +# it under the terms of the GNU GenRAl Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# gprMax is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU GenRAl Public License for more details. +# +# You should have received a copy of the GNU GenRAl Public License +# along with gprMax. If not, see . +# +#To get information as to why the PMLs are updated the way they are, please +# check https://repository.mines.edu/server/api/core/bitstreams/2cbef3b2-38af-4c25-bb9c-55bdd3abec74/content + +import numpy as np +cimport numpy as np +from cython.parallel import prange +from scipy.conftest import num_parallel_threads + +from gprMax.constants cimport floattype_t +#from scipy.constants import epsilon_0 as e0 +#from scipy.constants import mu_0 as mu0 +from libc.complex cimport I # cimport du 'I' complexe en C + +cdef double mu0 = 4 * 3.141592653589793 * 1e-7 + + +################## Conversion functions needed as we can't use operations on np.complex without gil ############################ + +from libc.stdlib cimport malloc, free +from libc cimport complex +from cython cimport boundscheck, wraparound, nonecheck + + +cdef extern from "complex.h": + pass # pour éviter certains warnings avec Cython + + +@boundscheck(False) +@wraparound(False) +@nonecheck(False) +cdef double complex*** alloc_and_copy_complex3D(np.complex128_t[:, :, ::1] arr): + """ + Alloue un tableau 3D C (double complex***) et copie les données d'un memoryview NumPy. + + Args: + arr (np.complex128_t[:, :, ::1]): Tableau NumPy d'entrée + + Returns: + double complex*** : Tableau C alloué et rempli + """ + cdef Py_ssize_t nx = arr.shape[0] + cdef Py_ssize_t ny = arr.shape[1] + cdef Py_ssize_t nz = arr.shape[2] + + cdef double complex*** out + cdef Py_ssize_t i, j, k + + # Allocation des pointeurs + out = malloc(nx * sizeof(double complex**)) + for i in range(nx): + out[i] = malloc(ny * sizeof(double complex*)) + for j in range(ny): + out[i][j] = malloc(nz * sizeof(double complex)) + + # Copie des données + for i in range(nx): + for j in range(ny): + for k in range(nz): + out[i][j][k] = arr[i, j, k] + + return out + +cdef void free_complex3D(double complex*** arr): + cdef Py_ssize_t nx = dest.shape[0] + cdef Py_ssize_t ny = dest.shape[1] + cdef Py_ssize_t i, j + for i in range(nx): + for j in range(ny): + free(arr[i][j]) + free(arr[i]) + free(arr) + + +@boundscheck(False) +@wraparound(False) +@nonecheck(False) +cdef void copy_complex3D_to_numpy(double complex*** src, + np.complex128_t[:, :, ::1] dest): + """ + Copie les données d’un tableau C (double complex***) dans un memoryview NumPy, + sans avoir besoin de spécifier les dimensions. + + Args: + src : tableau C (double complex***) + dest : memoryview NumPy déjà alloué + """ + cdef Py_ssize_t nx = dest.shape[0] + cdef Py_ssize_t ny = dest.shape[1] + cdef Py_ssize_t nz = dest.shape[2] + cdef Py_ssize_t i, j, k + + for i in range(nx): + for j in range(ny): + for k in range(nz): + dest[i, j, k] = src[i][j][k] + +################## Update of the PMLs in the r direction ############################### + +cpdef void initialize_constant_lists_rho( #OK pour les fomules des constantes + int rs, + int rf, + int nz, + int thickness_r, + float dr, + float dt, + int nthreads, + floattype_t[:, :, ::1] sigma, + floattype_t[:, :, ::1] alpha, + floattype_t[:, :, ::1] kappa, + floattype_t[:, :, ::1] return_omega, + floattype_t[:, :, ::1] return_Ksi_list, + floattype_t[:, :, ::1] return_Lambda_list, + floattype_t[:, :, ::1] return_Psi_list, + floattype_t[:, :, ::1] return_Theta_list, + floattype_t[:, :, ::1] return_alpha, + floattype_t[:, :, ::1] return_R_list, +): + """ + This function computes the Ksi_list and Lambda_list used in the PML updates. Only updated once ! + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + dr (float): spatial discretization + dt (float): timestep + nthreads (int): number of threads to use + sigma, alpha, kappa (memoryview): PML lists + return_omega (memoryview): return list with omega values + return_Ksi_list (memoryview): return list with Ksi_list values. + return_Lambda_list (memoryview): return list with Lambda_list values. + return_Psi_list (memoryview): return list with Psi_list values. + return_Theta_list (memoryview): return list with Theta_list values. + return_alpha (memoryview): return list with exp(-alpha * dt / e0) values. + return_R_list (memoryview): return list with R_list values. + + """ + cdef Py_ssize_t i, k, ii + cdef int nr + cdef float arg, alpha_term, sh, exp + + nr = thickness_r + + for k in prange(0, nz, nogil= True, schedule= 'static', num_threads = nthreads): + for i in range(0, nr): + arg = alpha[i,0,k]*kappa[i,0,k] + sigma[i,0,k] + alpha_term = alpha[i, 0, k] * dt / e0 + sh = np.sinh(alpha_term/2) + exp = np.exp(-alpha_term) + + return_omega[i, 0, k] = sigma[i, 0, k] * dr * (1 - exp) / alpha[i, 0, k] #OK + return_Ksi_list[i,0,k] = sigma[i,0,k] * dr * sh / e0 #OK + return_Lambda_list[i,0,k] = sigma[i,0,k] * (1 - np.exp(-arg*dt/(kappa[i,0,k]*e0))) / arg #OK + return_Psi_list[i,0,k] = sigma[i,0,k] * (1-exp) / alpha[i,0,k] #OK + return_Theta_list[i,0,k] = sigma[i,0,k] / e0 * sh #OK + return_alpha[i,0,k] = exp #OK + if i == 0: + return_R_list[i,0,k] = 0 + else: + return_R_list[i,0,k] = return_R_list[i-1,0,k] + sigma[i,0,k] * dr / e0 #OK + +cpdef void update_XQEphi_( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + np.complex128_t[:, :, ::1] EPhi, + np.complex128_t[:, :, :, ::1] XQEphi_, #XQEphi_[i,j,k] donne la matrice XQEphi_ au point (i,j,k) + np.complex128_t[:, :, ::1] Omega_term_list, + np.complex128_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQEphi_ from time n to time n+1 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + EPhi, Omega_term_list, alpha_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQEphi_: list to be updated + """ + cdef Py_ssize_t i, k, ii, iii + cdef int nr + + nr = thickness_r + + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + ii = rs + i + XQEphi_[i,0,k] = Omega_term_list[:,0,k] * EPhi[ii,0,k] + XQEphi_[i, 0, k] * alpha_term_list[:, 0, k] + +cpdef void update_XQEzs( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + np.complex128_t[:, :, ::1] Ezs, + np.complex128_t[:, :, :, ::1] XQEzs, #XQEzs[i,j,k] donne la matrice XQEzs au point (i,j,k) + np.complex128_t[:, :, ::1] Ksi_term_list, + np.complex128_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQEphi_ from time n to time n+1 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + Ezs, Ksi_term_list, alpha_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQEzs: list to be updated + """ + cdef Py_ssize_t i, k, ii, iii + cdef int nr + + nr = thickness_r + + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + ii = rs + i + XQEzs[i,0,k] = Ksi_term_list[:,0,k] * Ezs[ii,0,k] + XQEzs[i, 0, k] * alpha_term_list[:, 0, k] + +cpdef void update_XQHphi_( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + np.complex128_t[:, :, ::1] Hphi, + np.complex128_t[:, :, :, ::1] XQHphi_, #XQHphi[i,j,k] donne la matrice XQEzs au point (i,j,k) + np.complex128_t[:, :, ::1] Omega_term_list, + np.complex128_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQHphi_ from time n-1/2 to time n+1/2 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + HPhi, Omega_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQHphi_: list to be updated + """ + cdef Py_ssize_t i, k, ii + cdef int nr + + nr = thickness_r + + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + ii = rs + i + XQHphi_[i,0,k] = Omega_term_list[:,0,k] * Hphi[ii,0,k] + XQHphi_[i, 0, k] * alpha_term_list[:, 0, k] + +cpdef void update_XQHzs( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + np.complex128_t[:, :, ::1] Hzs, + np.complex128_t[:, :, :, ::1] XQHzs, #XQHzs[i,j,k] donne la matrice XQHzs au point (i,j,k) + np.complex128_t[:, :, ::1] Ksi_term_list, + np.complex128_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQHzs from time n-1/2 to time n+1/2 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + EPhi, Omega_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQEphi_: list to be updated + """ + cdef Py_ssize_t i, k, ii + cdef int nr + + nr = thickness_r + + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + ii = rs + i + XQHzs[i,0,k] = Ksi_term_list[:,0,k] * Hzs[ii,0,k] + XQHzs[i, 0, k] * alpha_term_list[:, 0, k] + + + +cpdef void E_update_r_slab( + int rs, + int m, + int nz, + int thickness_r, + float dr, + float dz, + float dt, + int nthreads, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er, + np.complex128_t[:, :, ::1] Ers, + np.complex128_t[:, :, ::1] QErs, + np.complex128_t[:, :, ::1] Ephi, + np.complex128_t[:, :, ::1] QEphi, + np.complex128_t[:, :, ::1] Ephi_, + np.complex128_t[:, :, :, ::1] XQEphi_, + np.complex128_t[:, :, ::1] Ez, + np.complex128_t[:, :, ::1] QEz, + np.complex128_t[:, :, ::1] Ezs, + np.complex128_t[:, :, :, ::1] XQEzs, #when called, the list is at the step n-1 + np.complex128_t[:, :, ::1] Hr, + np.complex128_t[:, :, ::1] Hrs, + np.complex128_t[:, :, ::1] QHrs, + np.complex128_t[:, :, ::1] Hphi, + np.complex128_t[:, :, ::1] QHphi, + np.complex128_t[:, :, ::1] Hphi_, + np.complex128_t[:, :, ::1] QHphi_, + np.complex128_t[:, :, ::1] XQHphi_, #when called, the list is at step n-3/2 + np.complex128_t[:, :, ::1] Hz, + np.complex128_t[:, :, ::1] QHz, + np.complex128_t[:, :, ::1] Hzs, + np.complex128_t[:, :, ::1] XQHzs, + floattype_t[:, :, ::1] alpha, + floattype_t[:, :, ::1] sigma, + floattype_t[:, :, ::1] kappa, + floattype_t[:, :, ::1] b, + floattype_t[:, :, ::1] Omega_term_list, + floattype_t[:, :, ::1] alpha_term_list, + floattype_t[:, :, ::1] Ksi_term_list, + floattype_t[:, :, ::1] Lambda_term_list, + floattype_t[:, :, ::1] R_term_list, + floattype_t[:, :, ::1] Psi_term_list, + floattype_t[:, :, ::1] Theta_term_list, + + ): + """ + + This function updates all the E fields inside the PML. + + Args: + rs, rf, zs, zf (int): locations of the fields to be updated + m (int): the argument in e^(i*m*phi) to ensure the symmetry + dr, dz (float): spatial discretization (no need for dphi as we use the symmetry) + dt (float): timestep in s + nz_tot (int): number of cells along the z axis for the whole domain + nthreads (int): number of threads to use + alpha, sigma, kappa, b (memoryviews): PML parameters + Er, Ephi, Ez, Hr, Hphi, Hz (memoryviews): fields in time domain + Ers, Ephi_, Ezs, Hrs, Hphi_, Hzs (memoryviews): fields used for PML updates + + """ + cdef Py_ssize_t i, k, ii, kk + cdef int nr + cdef floattype_t alpha_term, exp, sigma_term, kappa_term, denominateur_kappa_sigma, Theta_term + + nr = thickness_r + + for i in prange(0, nr, nogil= True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0,nz): + + sigma_term = sigma[i,0,k] / (2*e0) + kappa_term = kappa[i,0,k] / dt + denominateur_kappa_sigma = (sigma_term + kappa_term) + + #Er,Qers,Ers-> Utiliser liste Psi + if k == 0: #Boundary conditions + Er[ii,0,k] += (1j * m * Hz[ii,0,k] /(dr * (ii-0.5)) - (Hphi[ii,0,k] - 0)/dz)/e0 #OK + + else: + Er[ii,0,k] += (1j * m * Hz[ii,0,k] /(dr * (ii-0.5)) - (Hphi[ii,0,k] - Hphi[ii,0,k-1])/dz)/e0 #OK + QErs[i,0,k] = QErs[i,0,k]*alpha_term_list[i,0,k] + Psi_term_list[i,0,k] * Er[ii,0,k] #OK + Ers[i,0,k] = kappa[i,0,k] * Er[ii,0,k] + QErs[i,0,k] #OK + + #Ephi, QEphi + if k ==0: #Hrs[ii, 0, kk - 1] = 0 because proportionate to Hr + Ephi[ii, 0, k] = (((kappa_term - sigma_term) * Ephi[ii, 0, k] + QEphi[i, 0, k] * ( + 1 + alpha_term_list[i, 0, k]) + (Hrs[i, 0, k] - 0) / (dz * e0) - ( + Hz[ii, 0, k] - Hz[ii - 1, 0, k]) / (dr * e0)) + / (denominateur_kappa_sigma - Theta_term_list[i, 0, k])) #OK + else: + Ephi[ii,0,k] = (((kappa_term - sigma_term) * Ephi[ii,0,k] + QEphi[i,0,k] * (1 + alpha_term_list[i,0,k]) + + (Hrs[i,0,k] - Hrs[i,0,k-1])/(dz*e0) - (Hz[ii,0,k] - Hz[ii-1,0,k])/(dr * e0)) + / (denominateur_kappa_sigma - Theta_term_list[i,0,k])) #OK + + QEphi[i,0,k] = Theta_term_list[i,0,k] * Ephi[ii,0,k] + QEphi[i,0,k] * alpha_term_list[i,0,k] + + # We leave the first for statement to update XQEphi_ and XQEzs + update_XQEphi_(rs, nz, nthreads, thickness_r, Ephi, XQEphi_, Omega_term_list, alpha_term_list) + update_XQEzs(rs, nz, nthreads, thickness_r, Ezs, XQEzs, Ksi_term_list, alpha_term_list) + + for i in prange(0, nr, nogil= True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0,nz): + # Ephi_ + Ephi_[ii,0,k] = (b[i,0,k]*Ephi[ii,0,k] + #OK + np.sum(XQEphi_[i,0,k])) #This sum is in fact QEphi_ + + # Ezs + b_term = b[i,0,k] / dt + R_term = R_term_list[i,0,k] / 2 + denominateur_R_b = b_term + R_term + arg = (alpha[i,0,k] * kappa[i,0,k] + sigma[i,0,k]) * dt / (kappa[i,0,k] * e0) + QEzs_n = np.sum(XQEzs[i,0,k]) + + #I decided to take the derivative of Hphi_ as Hphi_(i+1) - Hphi_(i), which is different from the paper + # as it is easier to process Hphi_(rmax + 1), which is null because of boundary conditions, than Hphi_(i0-1), which has to be computed + if i == nr-1: + Ezs[i, 0, k] = ((b_term - R_term) * Ezs[i, 0, k] + QEzs_n + np.sum( + XQEzs[i, 0, k] * alpha_term_list[:, 0, k]) #OK + + (0 - Hphi_[i, 0, k]) / (dr * e0) - + 1j * m * Hrs[i, 0, k] / e0) / (denominateur_R_b - np.sum(Ksi_term_list[:, 0, k])) + else: + Ezs[i, 0, k] = ((b_term - R_term) * Ezs[i,0,k] + QEzs_n + np.sum(XQEzs[i,0,k] * alpha_term_list[:,0,k]) #OK + + (Hphi_[i+1,0,k] - Hphi_[i,0,k])/(dr * e0) - + 1j * m * Hrs[i,0,k] / e0 )/(denominateur_R_b - np.sum(Ksi_term_list[:,0,k])) + + #QEz + QEz[i,0,k] = Lambda_term_list[i,0,k] * Ezs[i,0,k] + QEz[i,0,k]*np.exp(-arg) #OK + + #Ez + Ez[i,0,k] = (Ezs[i,0,k] - QEz[i,0,k])/kappa[i,0,k] #OK + + + + + +cpdef void H_update_r_slab( + int rs, + int m, + int nz, + int thickness, + float dr, + float dz, + float dt, + int nthreads, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er, + np.complex128_t[:, :, ::1] Ers, + np.complex128_t[:, :, ::1] QErs, + np.complex128_t[:, :, ::1] Ephi, + np.complex128_t[:, :, ::1] QEphi, + np.complex128_t[:, :, ::1] Ephi_, + np.complex128_t[:, :, :, ::1] XQEphi_, + np.complex128_t[:, :, ::1] Ez, + np.complex128_t[:, :, ::1] QEz, + np.complex128_t[:, :, ::1] Ezs, + np.complex128_t[:, :, :, ::1] XQEzs, #when called, the list is at the step n-1 + np.complex128_t[:, :, ::1] Hr, + np.complex128_t[:, :, ::1] Hrs, + np.complex128_t[:, :, ::1] QHrs, + np.complex128_t[:, :, ::1] Hphi, + np.complex128_t[:, :, ::1] QHphi, + np.complex128_t[:, :, ::1] Hphi_, + np.complex128_t[:, :, ::1] QHphi_, + np.complex128_t[:, :, ::1] XQHphi_, #when called, the list is at step n-3/2 + np.complex128_t[:, :, ::1] Hz, + np.complex128_t[:, :, ::1] QHz, + np.complex128_t[:, :, ::1] Hzs, + np.complex128_t[:, :, ::1] XQHzs, + floattype_t[:, :, ::1] alpha, + floattype_t[:, :, ::1] sigma, + floattype_t[:, :, ::1] kappa, + floattype_t[:, :, ::1] b, + floattype_t[:, :, ::1] Omega_term_list, + floattype_t[:, :, ::1] alpha_term_list, + floattype_t[:, :, ::1] Ksi_term_list, + floattype_t[:, :, ::1] Lambda_term_list, + floattype_t[:, :, ::1] R_term_list, + floattype_t[:, :, ::1] Psi_term_list, + floattype_t[:, :, ::1] Theta_term_list, + ): + """ + + This function updates all the H fields inside the PML. + + Args: + rs, rf, zs, zf (int): locations of the fields to be updated + m (int): the argument in e^(i*m*phi) to ensure the symmetry + dr, dz (float): spatial discretization (no need for dphi as we use the symmetry) + dt (float): timestep in s + nz_tot (int): number of cells along the z axis for the whole domain + nthreads (int): number of threads to use + alpha, sigma, kappa, b (memoryviews): PML parameters + Er, Ephi, Ez, Hr, Hphi, Hz (memoryviews): fields in time domain + Ers, Ephi_, Ezs, Hrs, Hphi_, Hzs (memoryviews): fields used for PML updates + + """ + cdef Py_ssize_t i, k, ii, kk + cdef int nr + cdef floattype_t alpha_term, exp, sigma_term, kappa_term, denominateur_kappa_sigma, Theta_term + + nr = thickness + + for i in prange(0, nr, nogil= True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0,nz): + #Hr, QHrs, Hrs + if k ==0: + Hr[ii,0,k] += (Ephi[ii,0,k] - 0) / (dz * mu0) - 1j * m * Ez[ii,0,k] / ((ii-1) * dr * mu0) #OK + else: + Hr[ii,0,k] += (Ephi[ii,0,k] - Ephi[ii,0,k-1]) / (dz * mu0) - 1j * m * Ez[ii,0,k] / ((ii-1) * dr * mu0) #OK + QHrs[i,0,k] = Psi_term_list[i,0,k] * Hr[ii,0,k] + QHrs[i,0,k] * alpha_term_list[i,0,k] #OK + Hrs[i,0,k] = kappa[i,0,k] * Hr[ii,0,k] + QHrs[i,0,k] #OK + + #Hphi + sigma_term = sigma[i, 0, k] / (2 * e0) + kappa_term = kappa[i, 0, k] / dt + + if k == nz-1: #Ers is proportionate to Er + if i == nr-1: + Hphi[i, 0, k] = (((kappa_term - sigma_term) * Hphi[ii, 0, k] + (1 + alpha_term_list[i, 0, k]) * + QHphi[i, 0, k] + + (0 - Ez[ii, 0, k]) / (dr * mu0) - ( + 0 - Ers[i, 0, k]) / (dz * mu0)) / + (sigma_term + kappa_term - Theta_term_list[i, 0, k])) + else: + Hphi[i, 0, k] = (((kappa_term - sigma_term) * Hphi[ii, 0, k] + (1 + alpha_term_list[i, 0, k]) * + QHphi[i, 0, k] + + (Ez[ii + 1, 0, k] - Ez[ii, 0, k]) / (dr * mu0) - ( + 0 - Ers[i, 0, k]) / (dz * mu0)) / + (sigma_term + kappa_term - Theta_term_list[i, 0, k])) + else: + if i == nr-1: + Hphi[i, 0, k] = (((kappa_term - sigma_term) * Hphi[ii, 0, k] + (1 + alpha_term_list[i, 0, k]) * + QHphi[i, 0, k] + + (0 - Ez[ii, 0, k]) / (dr * mu0) - ( + Ers[i, 0, k + 1] - Ers[i, 0, k]) / (dz * mu0)) / + (sigma_term + kappa_term - Theta_term_list[i, 0, k])) + else: + Hphi[i,0,k] = (((kappa_term - sigma_term) * Hphi[ii,0,k] + (1 + alpha_term_list[i,0,k]) * QHphi[i,0,k] + + (Ez[ii+1,0,k] - Ez[ii,0,k])/ (dr * mu0) - (Ers[i,0,k+1] - Ers[i,0,k])/(dz * mu0))/ + (sigma_term + kappa_term - Theta_term_list[i,0,k])) #OK + + #QHphi + QHphi[i,0,k] = Theta_term_list[i,0,k] * Hphi[ii,0,k] + QHphi[i,0,k] * alpha_term_list[i,0,k] + + # We leave the for statement to update XQHphi_ and XQHzs + update_XQHphi_(rs, nz, nthreads, thickness_r, Hphi, XQHphi_, Omega_term_list, alpha_term_list) + update_XQHzs(rs, nz, nthreads, thickness_r, Hzs, XQHzs, Ksi_term_list, alpha_term_list) + for i in prange(0, nr, nogil= True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0,nz): + + #Hphi_ + Hphi_[i,0,k] = (b[i,0,k] * Hphi[ii,0,k] #OK + + np.sum(XQHphi_[i,0,k])) #QHphi_ + + #Hzs + b_term = b[i,0,k] / dt + R_term = R_term_list[i,0,k] / 2 + QHzs = np.sum(XQHzs) + + if i == nr-1: #Ephi_ is proportionate to Ephi at this point (at different times) + Hzs[i, 0, k] = ((b_term - R_term) * Hzs[i, 0, k] + ( + np.sum(XQHzs[i, 0, k] * alpha_term_list[:, 0, k]) + QHzs) / mu0 + + Ers[i, 0, k] * 1j * m / e0 - (0 - Ephi_[i, 0, k]) / (dr * e0)) / ( + b_term + R_term + - np.sum(Ksi_term_list[:, 0, k]) / mu0) + else: + Hzs[i,0,k] = ((b_term - R_term) * Hzs[i,0,k] + (np.sum(XQHzs[i,0,k] * alpha_term_list[:,0,k]) + QHzs)/mu0 + + Ers[i,0,k] * 1j * m / e0 - (Ephi_[i+1,0,k] - Ephi_[i,0,k])/(dr * e0)) / (b_term + R_term + -np.sum(Ksi_term_list[:,0,k])/mu0) #OK + + #QHz + QHz[i,0,k] = (Lambda_term_list[i,0,k] * Hzs[i,0,k] + + QHz[i,0,k] * np.exp(-(alpha[i,0,k] * kappa[i,0,k] + sigma[i,0,k])*dt/(kappa[i,0,k] * e0))) + + #Hz + Hz[i,0,k] = (Hzs[i,0,k] - QHz[i,0,k])/kappa[i,0,k] + +######################################################################################## + +################## Update of the PMLs in the z direction ############################### + +#For this part, it appears that the PML formulation for the z component is in fact the same as in cartesian + +cpdef void initialize_constant_lists_z( + int rs, + int thickness_z, + float dt, + int nthreads, + floattype_t[:, :, ::1] alpha_z, + floattype_t[:, :, ::1] sigma_z, + floattype_t[:, :, ::1] kappa_z, + floattype_t[:, :, ::1] Pi_term_list, + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + for k in range(0, 2*thickness_z, nogil= True, schedule='static', num_threads = nthreads): + for i in range(0,rs): + arg = alpha_z[i,0,k] * kappa_z[i,0,k] + sigma_z[i,0,k] + Pi_term_list[i,0,k] = (alpha_z[i,0,k] - arg)/(e0 * kappa_z[i,0,k]) + Delta_term_list[i,0,k] = (1 - kappa_z[i,0,k])/kappa_z[i,0,k] + Rho_term_list[i,0,k] = arg / (e0 * kappa_z[i,0,k]) + +cpdef void E_update_z_upper_slab( + int rs, #The PML will go from r=0 to r=rs + int zs, + int thickness, + float dr, + float dz, + float dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JEphi_np, + np.complex128_t[:, :, ::1] JEr_np, + np.complex128_t[:, :, ::1] QEphi_np, + np.complex128_t[:, :, ::1] QEr_np, + np.complex128_t[:, :, ::1] QJEphi_np, + np.complex128_t[:, :, ::1] QJEr_np, + floattype_t[:, :, ::1] Pi_term_list, + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef Py_ssize_t i, k, kk + cdef int nz, nr + cdef double complex*** Er, Ephi, Ez, Hr, Hphi, Hz, JHphi, JHr, QHphi, QHr, QJHphi, QJHr + + Er = alloc_and_copy_complex3D(Er_np) + Ephi = alloc_and_copy_complex3D(Ephi_np) + Ez = alloc_and_copy_complex3D(Ez_np) + Hr = alloc_and_copy_complex3D(Hr_np) + Hphi = alloc_and_copy_complex3D(Hphi_np) + Hz = alloc_and_copy_complex3D(Hz_np) + JHphi = alloc_and_copy_complex3D(JEphi_np) + JHr = alloc_and_copy_complex3D(JEr_np) + QEphi = alloc_and_copy_complex3D(QEphi_np) + QEr = alloc_and_copy_complex3D(QEr_np) + QJEphi = alloc_and_copy_complex3D(QJEphi_np) + QJEr = alloc_and_copy_complex3D(QJEr_np) + + nz = thickness + nr = rs + 1 + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + for k in range(0, nz): + kk = k + zs + #Updating the Q lists before updating the fields + QEr[i, 0, k + nz] += (Er[i, 0, kk + 1] - Er[i, 0, kk]) * dt / dz + QEphi[i, 0, k + nz] += (Ephi[i, 0, kk + 1] - Ephi[i, 0, kk]) * dt / dz + QJEr[i, 0, k + nz] += JEr[i, 0, k + nz] * dt + QJEphi[i, 0, k + nz] += JEphi[i, 0, k + nz] * dt + + #Updating the E-fields + Er[i, 0, kk] += (I * m * Hz[i, 0, kk] / ((i + 0.5) * dr) - (Hphi[i, 0, kk + 1] - Hphi[i, 0, kk]) / dz - + JEphi[i, 0, k]) / e0 + Ephi[i, 0, kk] += ((Hr[i, 0, kk + 1] - Hr[i, 0, kk]) / dz - (Hz[i + 1, 0, kk] - Hz[i, 0, kk]) / dr + JEphi[ + i, 0, k]) / e0 + Ez[i, 0, kk] += (((i + 1.5) * Hphi[i + 1, 0, kk] - (i + 0.5) * Hphi[i, 0, kk]) / ((i + 0.5) * dr) - I * m * Hr[ + i, 0, kk] / ((i + 0.5) * dr) + JEphi[i, 0, k]) / e0 + + #Updating the J fields + JEr[i, 0, k] = Pi_term_list[i, 0, k + nz] * QEr[i, 0, k] + Delta_term_list[i, 0, k + nz] * ( + Er[i, 0, kk + 1] - Er[i, 0, kk]) / dz - Rho_term_list[i, 0, k + nz] * QJEr[i, 0, k] + JEphi[i, 0, k] = Pi_term_list[i, 0, k + nz] * QEphi[i, 0, k] + Delta_term_list[i, 0, k + nz] * ( + Ephi[i, 0, kk + 1] - Ephi[i, 0, kk]) / dz - Rho_term_list[i, 0, k + nz] * QJEphi[i, 0, k] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JEphi, JEphi_np) + copy_complex3D_to_numpy(JEr, JEr_np) + copy_complex3D_to_numpy(QEphi, QEphi_np) + copy_complex3D_to_numpy(QEr, QEr_np) + copy_complex3D_to_numpy(QJEphi, QJEphi_np) + copy_complex3D_to_numpy(QJEr, QJEr_np) + + free_complex3D(Er) + free_complex3D(Ephi) + free_complex3D(Ez) + free_complex3D(Hr) + free_complex3D(Hphi) + free_complex3D(Hz) + free_complex3D(JEphi) + free_complex3D(JEr) + free_complex3D(QEphi) + free_complex3D(QEr) + free_complex3D(QJEphi) + free_complex3D(QJEr) + +cpdef void H_update_upper_slab( + int rs, #The PML will go from r=0 to r=rs + int zs, + int thickness, + float dr, + float dz, + float dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JHphi_np, + np.complex128_t[:, :, ::1] JHr_np, + np.complex128_t[:, :, ::1] QHphi_np, + np.complex128_t[:, :, ::1] QHr_np, + np.complex128_t[:, :, ::1] QJHphi_np, + np.complex128_t[:, :, ::1] QJHr_np, + floattype_t[:, :, ::1] Pi_term_list, #Pi_term_list[i,0,k] gives the Pi value at (i,0,k) if k <= nz-1, else at (i,0,nz-thickness+k) + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef Py_ssize_t i, k, kk + cdef int nz, nr + cdef double complex*** Er, Ephi, Ez, Hr, Hphi, Hz, JHphi, JHr, QHphi, QHr, QJHphi, QJHr + + Er = alloc_and_copy_complex3D(Er_np) + Ephi = alloc_and_copy_complex3D(Ephi_np) + Ez = alloc_and_copy_complex3D(Ez_np) + Hr = alloc_and_copy_complex3D(Hr_np) + Hphi = alloc_and_copy_complex3D(Hphi_np) + Hz = alloc_and_copy_complex3D(Hz_np) + JHphi = alloc_and_copy_complex3D(JHphi_np) + JHr = alloc_and_copy_complex3D(JHr_np) + QHphi = alloc_and_copy_complex3D(QHphi_np) + QHr = alloc_and_copy_complex3D(QHr_np) + QJHphi = alloc_and_copy_complex3D(QJHphi_np) + QJHr = alloc_and_copy_complex3D(QJHr_np) + + nz = thickness + nr = rs + 1 + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + for k in range(0, nz): + kk = k + zs + #Updating the Q lists before updating the fields + QHr[i, 0, k + nz] += (Hr[i, 0, kk + 1] - Hr[i, 0, kk]) * dt / dz + QHphi[i, 0, k + nz] += (Hphi[i, 0, kk + 1] - Hphi[i, 0, kk]) * dt / dz + QJHr[i, 0, k + nz] += JHr[i, 0, k + nz] * dt + QJHphi[i, 0, k + nz] += JHphi[i, 0, k + nz] * dt + + #Updating the E-fields + Hr[i, 0, kk] -= (I * m * Ez[i, 0, kk] / ((i + 1) * dr) - (Ephi[i, 0, kk + 1] - Ephi[i, 0, kk]) / dz - + JHphi[i, 0, k]) / mu0 + Hphi[i, 0, kk] -= ((Er[i, 0, kk + 1] - Er[i, 0, kk]) / dz - (Ez[i + 1, 0, kk] - Ez[i, 0, kk]) / dr + JHphi[ + i, 0, k]) / mu0 + Hz[i, 0, kk] -= ( + ((i + 2) * Ephi[i + 1, 0, kk] - (i + 1) * Ephi[i, 0, kk]) / ((i + 1) * dr) - I * m * Er[ + i, 0, kk] / ((i + 0.5) * dr) + JHphi[i, 0, k]) / mu0 + + #Updating the J fields + JHr[i, 0, k + nz] = Pi_term_list[i, 0, k + nz] * QHr[i, 0, k] + Delta_term_list[i, 0, k + nz] * ( + Hr[i, 0, kk + 1] - Hr[i, 0, kk]) / dz - Rho_term_list[i, 0, k + nz] * QJHr[i, 0, k] + JHphi[i, 0, k + nz] = Pi_term_list[i, 0, k + nz] * QHphi[i, 0, k] + Delta_term_list[i, 0, k + nz] * ( + Hphi[i, 0, kk + 1] - Hphi[i, 0, kk]) / dz - Rho_term_list[i, 0, k + nz] * QJHphi[i, 0, k] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JHphi, JHphi_np) + copy_complex3D_to_numpy(JHr, JHr_np) + copy_complex3D_to_numpy(QHphi, QHphi_np) + copy_complex3D_to_numpy(QHr, QHr_np) + copy_complex3D_to_numpy(QJHphi, QJHphi_np) + copy_complex3D_to_numpy(QJHr, QJHr_np) + + free_complex3D(Er) + free_complex3D(Ephi) + free_complex3D(Ez) + free_complex3D(Hr) + free_complex3D(Hphi) + free_complex3D(Hz) + free_complex3D(JHphi) + free_complex3D(JHr) + free_complex3D(QHphi) + free_complex3D(QHr) + free_complex3D(QJHphi) + free_complex3D(QJHr) + + +#For the lower slab, we just need to invert the direction of scanning z, as well as updating the indexes of term_lists + +cpdef void E_update_lower_slab( + int rs, #The PML will go from r=0 to r=rs + int zf, + int thickness, + float dr, + float dz, + float dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JEphi_np, + np.complex128_t[:, :, ::1] JEr_np, + np.complex128_t[:, :, ::1] QEphi_np, + np.complex128_t[:, :, ::1] QEr_np, + np.complex128_t[:, :, ::1] QJEphi_np, + np.complex128_t[:, :, ::1] QJEr_np, + floattype_t[:, :, ::1] kappa_z, #Not the same list as for the r update + floattype_t[:, :, ::1] sigma_z, #Not the same list as for the r update + floattype_t[:, :, ::1] alpha_z, #Not the same list as for the r update + floattype_t[:, :, ::1] Pi_term_list, + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef double complex*** Er, Ephi, Ez, Hr, Hphi, Hz, JEphi, JEr, QEphi, QEr, QJEphi, QJEr + + Er = alloc_and_copy_complex3D(Er_np) + Ephi = alloc_and_copy_complex3D(Ephi_np) + Ez = alloc_and_copy_complex3D(Ez_np) + Hr = alloc_and_copy_complex3D(Hr_np) + Hphi = alloc_and_copy_complex3D(Hphi_np) + Hz = alloc_and_copy_complex3D(Hz_np) + JHphi = alloc_and_copy_complex3D(JEphi_np) + JHr = alloc_and_copy_complex3D(JEr_np) + QEphi = alloc_and_copy_complex3D(QEphi_np) + QEr = alloc_and_copy_complex3D(QEr_np) + QJEphi = alloc_and_copy_complex3D(QJEphi_np) + QJEr = alloc_and_copy_complex3D(QJEr_np) + + cdef Py_ssize_t i, k, kk + cdef int nz, nr + + nz = thickness + nr = rs + 1 + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + for k in range(0, nz): + kk = nz - (k + 1) #The first PML cell is at kk = nz-1 + #Updating the Q lists before updating the fields + QEr[i, 0, k] += (Er[i, 0, kk + 1] - Er[i, 0, kk]) * dt / dz + QEphi[i, 0, k] += (Ephi[i, 0, kk + 1] - Ephi[i, 0, kk]) * dt / dz + QJEr[i, 0, k] += JEr[i, 0, k] * dt + QJEphi[i, 0, k] += JEphi[i, 0, k] * dt + + #Updating the E-fields + Er[i, 0, kk] += (I * m * Hz[i, 0, kk] / ((i + 0.5) * dr) - (Hphi[i, 0, kk + 1] - Hphi[i, 0, kk]) / dz - + JEphi[i, 0, k]) / e0 + Ephi[i, 0, kk] += ((Hr[i, 0, kk + 1] - Hr[i, 0, kk]) / dz - (Hz[i + 1, 0, kk] - Hz[i, 0, kk]) / dr + JEphi[ + i, 0, k]) / e0 + Ez[i, 0, kk] += (((i + 1.5) * Hphi[i + 1, 0, kk] - (i + 0.5) * Hphi[i, 0, kk]) / ((i + 0.5) * dr) - I * m * Hr[ + i, 0, kk] / ((i + 0.5) * dr) + JEphi[i, 0, k]) / e0 + + #Updating the J fields + JEr[i, 0, k] = Pi_term_list[i, 0, k] * QEr[i, 0, k] + Delta_term_list[i, 0, k] * ( + Er[i, 0, kk + 1] - Er[i, 0, kk]) / dz - Rho_term_list[i, 0, k] * QJEr[i, 0, k] + JEphi[i, 0, k] = Pi_term_list[i, 0, k] * QEphi[i, 0, k] + Delta_term_list[i, 0, k] * ( + Ephi[i, 0, kk + 1] - Ephi[i, 0, kk]) / dz - Rho_term_list[i, 0, k] * QJEphi[i, 0, k] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JEphi, JEphi_np) + copy_complex3D_to_numpy(JEr, JEr_np) + copy_complex3D_to_numpy(QEphi, QEphi_np) + copy_complex3D_to_numpy(QEr, QEr_np) + copy_complex3D_to_numpy(QJEphi, QJEphi_np) + copy_complex3D_to_numpy(QJEr, QJEr_np) + + free_complex3D(Er) + free_complex3D(Ephi) + free_complex3D(Ez) + free_complex3D(Hr) + free_complex3D(Hphi) + free_complex3D(Hz) + free_complex3D(JEphi) + free_complex3D(JEr) + free_complex3D(QEphi) + free_complex3D(QEr) + free_complex3D(QJEphi) + free_complex3D(QJEr) + + +cpdef void H_update_lower_slab( + int_t rs, #The PML will go from r=0 to r=rs + int_t zf, + int_t thickness, + float_t dr, + float_t dz, + float_t dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JHphi_np, + np.complex128_t[:, :, ::1] JHr_np, + np.complex128_t[:, :, ::1] QHphi_np, + np.complex128_t[:, :, ::1] QHr_np, + np.complex128_t[:, :, ::1] QJHphi_np, + np.complex128_t[:, :, ::1] QJHr_np, + floattype_t[:, :, ::1] Pi_term_list, #Pi_term_list[i,0,k] gives the Pi value at (i,0,k) if k <= nz-1, else at (i,0,nz-thickness+k) + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef Py_ssize_t i, k, kk + cdef int nz, nr + cdef double complex*** Er, Ephi, Ez, Hr, Hphi, Hz, JHphi, JHr, QHphi, QHr, QJHphi, QJHr + + Er = alloc_and_copy_complex3D(Er_np) + Ephi = alloc_and_copy_complex3D(Ephi_np) + Ez = alloc_and_copy_complex3D(Ez_np) + Hr = alloc_and_copy_complex3D(Hr_np) + Hphi = alloc_and_copy_complex3D(Hphi_np) + Hz = alloc_and_copy_complex3D(Hz_np) + JHphi = alloc_and_copy_complex3D(JHphi_np) + JHr = alloc_and_copy_complex3D(JHr_np) + QHphi = alloc_and_copy_complex3D(QHphi_np) + QHr = alloc_and_copy_complex3D(QHr_np) + QJHphi = alloc_and_copy_complex3D(QJHphi_np) + QJHr = alloc_and_copy_complex3D(QJHr_np) + + nz = thickness + nr = rs + 1 + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + for k in range(0, nz): + kk = nz - (k + 1) #The first PML cell is at kk = nz-1 + #Updating the Q lists before updating the fields + QHr[i, 0, k] += (Hr[i, 0, kk + 1] - Hr[i, 0, kk]) * dt / dz + QHphi[i, 0, k] += (Hphi[i, 0, kk + 1] - Hphi[i, 0, kk]) * dt / dz + QJHr[i, 0, k] += JHr[i, 0, k] * dt + QJHphi[i, 0, k] += JHphi[i, 0, k] * dt + + #Updating the E-fields + Hr[i, 0, kk] -= (I * m * Ez[i, 0, kk] / ((i + 1) * dr) - (Ephi[i, 0, kk + 1] - Ephi[i, 0, kk]) / dz - + JHphi[i, 0, k]) / mu0 + Hphi[i, 0, kk] -= ((Er[i, 0, kk + 1] - Er[i, 0, kk]) / dz - (Ez[i + 1, 0, kk] - Ez[i, 0, kk]) / dr + JHphi[ + i, 0, k]) / mu0 + Hz[i, 0, kk] -= ( + ((i + 2) * Ephi[i + 1, 0, kk] - (i + 1) * Ephi[i, 0, kk]) / ((i + 1) * dr) - I * m * Er[ + i, 0, kk] / ((i + 0.5) * dr) + JHphi[i, 0, k]) / mu0 + + #Updating the J fields + JHr[i, 0, k] = Pi_term_list[i, 0, k] * QHr[i, 0, k] + Delta_term_list[i, 0, k] * ( + Hr[i, 0, kk + 1] - Hr[i, 0, kk]) / dz - Rho_term_list[i, 0, k] * QJHr[i, 0, k] + JHphi[i, 0, k] = Pi_term_list[i, 0, k] * QHphi[i, 0, k] + Delta_term_list[i, 0, k] * ( + Hphi[i, 0, kk + 1] - Hphi[i, 0, kk]) / dz - Rho_term_list[i, 0, k] * QJHphi[i, 0, k] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JHphi, JHphi_np) + copy_complex3D_to_numpy(JHr, JHr_np) + copy_complex3D_to_numpy(QHphi, QHphi_np) + copy_complex3D_to_numpy(QHr, QHr_np) + copy_complex3D_to_numpy(QJHphi, QJHphi_np) + copy_complex3D_to_numpy(QJHr, QJHr_np) + + free_complex3D(Er) + free_complex3D(Ephi) + free_complex3D(Ez) + free_complex3D(Hr) + free_complex3D(Hphi) + free_complex3D(Hz) + free_complex3D(JHphi) + free_complex3D(JHr) + free_complex3D(QHphi) + free_complex3D(QHr) + free_complex3D(QJHphi) + free_complex3D(QJHr) + \ No newline at end of file diff --git "a/Modifications apport\303\251es.txt" "b/Modifications apport\303\251es.txt" new file mode 100644 index 000000000..e991298e9 --- /dev/null +++ "b/Modifications apport\303\251es.txt" @@ -0,0 +1,36 @@ +input_cmds_file: + Ajout de 'cylindrical', 'dr_dz', 'm', 'domain_cyl' aux liste singleusecmds et geometrycmds + Ajout d'une liste 'essentialcmds_cyl', d'un compteur et d'une erreur si incomplète + +input_cmd_funcs: + Ajout des fonctions cylindrical, dr_dz, domain_cyl, m au fichier + Ajout de la class Coordinate_cyl + +input_cmds_singleuse: + Ajout des variables de classe 'cylindrical', 'dr_dz', 'm', 'domain_cyl' + Ajout du calcul du dt en cylindrique + A ajouter: conditions sur les PMLs + +sources: + Ajout des coordonnées cylindriques pour le Hertzian dipole et la Electric dipole + Ajout d'erreur si cylindrique pour les autres sources + Pas de modification du gpu + +input_cmds_multiuse: + Ajout des cylindriques pour les sources + Ajout des cylindriques pour les receivers (sauf les arrays) + Materials reste inchangé + Voir les dispersions après avoir vérifié le run + Voir GeometryView et Snapshots + +grid: + Ajout des variables de classe nécessaires + Modification des fonctions d'initialisation + Ajout des cylindriques à memory_estimate_usage + +input_cmds_geometry: + A faire: modifier les formations de cylindres + A faire: Ajouter les conditions pour éviter les cylindriques sur les autres + +geometry_primirives_ext: + Ajout de la fonction build_cylinder_cyl, construisant une tranche de cylindre \ No newline at end of file diff --git a/gprMax/Fluxes.py b/gprMax/Fluxes.py new file mode 100644 index 000000000..5c53d3657 --- /dev/null +++ b/gprMax/Fluxes.py @@ -0,0 +1,554 @@ +import numpy as np +from gprMax.grid import FDTDGrid +from gprMax.constants import floattype +from gprMax.constants import complextype +from gprMax.utilities import round_value +import h5py +from gprMax._version import __version__ +from gprMax.Fluxes_ext import save_fields_fluxes as save_fields_fluxes_pyx +from gprMax.exceptions import CmdInputError, GeneralError +from gprMax.utilities import timer +from gprMax.input_cmds_geometry import process_geometrycmds + +from tqdm import tqdm +from colorama import init +from colorama import Fore +from colorama import Style +init() +from gprMax.fields_outputs import store_outputs +from gprMax.fields_updates_ext import update_electric +from gprMax.fields_updates_ext import update_magnetic +from gprMax.fields_updates_ext import update_electric_dispersive_multipole_A +from gprMax.fields_updates_ext import update_electric_dispersive_multipole_B +from gprMax.fields_updates_ext import update_electric_dispersive_1pole_A +from gprMax.fields_updates_ext import update_electric_dispersive_1pole_B +from gprMax.yee_cell_build_ext import build_magnetic_components, build_electric_components +from gprMax.materials import Material +from gprMax.materials import process_materials +from gprMax.utilities import get_terminal_width +import sys + + +from gprMax.receivers import gpu_initialise_rx_arrays +from gprMax.receivers import gpu_get_rx_array +from gprMax.snapshots import Snapshot +from gprMax.snapshots import gpu_initialise_snapshot_array +from gprMax.snapshots import gpu_get_snapshot_array +from gprMax.snapshots_gpu import kernel_template_store_snapshot +from gprMax.sources import gpu_initialise_src_arrays +from gprMax.source_updates_gpu import kernels_template_sources +from gprMax.fields_updates_gpu import kernels_template_fields +from gprMax.utilities import round32 +from importlib import import_module +from gprMax.utilities import human_size +from gprMax.fields_outputs import kernel_template_store_outputs + + + +from gprMax.constants import cudafloattype +from gprMax.constants import cudacomplextype + + +class Flux(object): + possible_normals = ['x', 'y', 'z'] + possible_direction = ['plus', 'minus'] + def __init__(self, G, normal, direction, bottom_left_corner, top_right_corner, wavelenghts): + self.bottom_left_corner = bottom_left_corner + self.top_right_corner = top_right_corner + self.normal = normal + self.direction = direction + self.set_number_cells(G) + self.wavelengths = wavelenghts + self.omega = 2*np.pi * 299792458 / wavelenghts + if G.scattering: + self.E_fft_transform_empty = np.zeros((len(wavelenghts), self.cells_number[0], self.cells_number[1], self.cells_number[2], self.cells_number[3]), dtype= complextype) + self.E_fft_transform_scatt = np.zeros((len(wavelenghts), self.cells_number[0], self.cells_number[1], self.cells_number[2], self.cells_number[3]), dtype= complextype) + self.H_fft_transform_empty = np.zeros((len(wavelenghts), self.cells_number[0], self.cells_number[1], self.cells_number[2], self.cells_number[3]), dtype= complextype) + self.H_fft_transform_scatt = np.zeros((len(wavelenghts), self.cells_number[0], self.cells_number[1], self.cells_number[2], self.cells_number[3]), dtype= complextype) + self.E_fft_transform = np.zeros((len(wavelenghts), self.cells_number[0], self.cells_number[1], self.cells_number[2], self.cells_number[3]), dtype= complextype) + self.H_fft_transform = np.zeros((len(wavelenghts), self.cells_number[0], self.cells_number[1], self.cells_number[2], self.cells_number[3]), dtype= complextype) + + + def set_number_cells(self, G: FDTDGrid): + """Defines the cells that are part of the surface flux.""" + + Nx = -self.bottom_left_corner[0] + self.top_right_corner[0] +1 + Ny = -self.bottom_left_corner[1] + self.top_right_corner[1] +1 + Nz = -self.bottom_left_corner[2] + self.top_right_corner[2] +1 + + if Nx <= 0 or Ny <= 0 or Nz <= 0: + raise AssertionError("Nx, Ny, and Nz must be positive") + + if self.normal == 'x': + assert self.bottom_left_corner[0] == self.top_right_corner[0], "For normal 'x', the two x coordinates must be equal" + elif self.normal == 'y': + assert self.bottom_left_corner[1] == self.top_right_corner[1], "For normal 'y', the two y coordinates must be equal" + elif self.normal == 'z': + assert self.bottom_left_corner[2] == self.top_right_corner[2], "For normal 'z', the two z coordinates must be equal" + + self.cells_range = {'x': np.arange(0, -int(self.bottom_left_corner[0]) + int(self.top_right_corner[0])) if -int(self.bottom_left_corner[0]) + int(self.top_right_corner[0]) != 0 else [0], + 'y': np.arange(0, -int(self.bottom_left_corner[1]) + int(self.top_right_corner[1])) if -int(self.bottom_left_corner[1]) + int(self.top_right_corner[1]) != 0 else [0], + 'z': np.arange(0, -int(self.bottom_left_corner[2]) + int(self.top_right_corner[2])) if -int(self.bottom_left_corner[2]) + int(self.top_right_corner[2]) != 0 else [0]} + + self.cells_number = (int(Nx), int(Ny), int(Nz), 3) + + def save_fields_fluxes(self, G: FDTDGrid, iteration): + if not G.scattering: + save_fields_fluxes_pyx( + G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz, self.omega, + self.E_fft_transform, self.H_fft_transform, + self.cells_range['x'][0], self.cells_range['y'][0], self.cells_range['z'][0], + len(self.cells_range['x']), len(self.cells_range['y']), len(self.cells_range['z']), len(self.wavelengths), + int(self.bottom_left_corner[0]), int(self.bottom_left_corner[1]), int(self.bottom_left_corner[2]), + G.dt, G.nthreads, iteration + ) + else: + if G.empty_sim: + save_fields_fluxes_pyx( + G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz, self.omega, + self.E_fft_transform_empty, self.H_fft_transform_empty, + self.cells_range['x'][0], self.cells_range['y'][0], self.cells_range['z'][0], + len(self.cells_range['x']), len(self.cells_range['y']), len(self.cells_range['z']), len(self.wavelengths), + int(self.bottom_left_corner[0]), int(self.bottom_left_corner[1]), int(self.bottom_left_corner[2]), + G.dt, G.nthreads, iteration + ) + else: + save_fields_fluxes_pyx( + G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz, self.omega, + self.E_fft_transform_scatt, self.H_fft_transform_scatt, + self.cells_range['x'][0], self.cells_range['y'][0], self.cells_range['z'][0], + len(self.cells_range['x']), len(self.cells_range['y']), len(self.cells_range['z']), len(self.wavelengths), + int(self.bottom_left_corner[0]), int(self.bottom_left_corner[1]), int(self.bottom_left_corner[2]), + G.dt, G.nthreads, iteration + ) + #Because the empty simulation goes first, we substract the fields here + self.E_fft_transform = self.E_fft_transform_scatt - self.E_fft_transform_empty + self.H_fft_transform = self.H_fft_transform_scatt - self.H_fft_transform_empty + + + def calculate_Poynting_frequency_flux(self, G, incident= False): + if not incident: + #Then calculate the Poynting vector + self.Poynting_frequency = np.cross(np.conjugate(self.E_fft_transform), self.H_fft_transform, axis=-1) + else: + self.Poynting_frequency = np.cross(np.conjugate(self.E_fft_transform_empty), self.H_fft_transform_empty, axis=-1) + + #Finally calculate the flux + self.Poynting_frequency_flux = [] + for f in range(len(self.omega)): + if self.normal == 'x': + self.Poynting_frequency_flux.append(np.sum(self.Poynting_frequency[f, :, :, :, 0] * G.dy * G.dz, axis=None)) + elif self.normal == 'y': + self.Poynting_frequency_flux.append(np.sum(self.Poynting_frequency[f, :, :, :, 1] * G.dx * G.dz, axis=None)) + elif self.normal == 'z': + self.Poynting_frequency_flux.append(np.sum(self.Poynting_frequency[f, :, :, :, 2] * G.dx * G.dy, axis=None)) + self.Poynting_frequency_flux = np.real(np.array(self.Poynting_frequency_flux, dtype=floattype)) + if self.direction == 'minus': + self.Poynting_frequency_flux *= -1 + if incident: + self.Poynting_frequency_flux_incident = np.copy(self.Poynting_frequency_flux) + self.Poynting_frequency_flux = None + + +def save_file_h5py(outputfile, G: FDTDGrid): + f = h5py.File(outputfile, 'w') + f.attrs['gprMax'] = __version__ + f.attrs['Title'] = G.title + f.attrs['Iterations'] = G.iterations + f.attrs['dt'] = G.dt + f.attrs['n_surfaces'] = len(G.fluxes) + wavelengths = G.fluxes[0].wavelengths + + if G.scattering: + title = '/scattering' + for i in range(len(G.fluxes)): + grp = f.create_group(title + '/incidents/incident' + str(i + 1)) + grp['values'] = G.fluxes[i].Poynting_frequency_flux_incident + grp['wavelengths'] = G.fluxes[i].wavelengths + grp['normal'] = G.fluxes[i].normal + grp['direction'] = G.fluxes[i].direction + grp['x_cells'], grp['y_cells'], grp['z_cells'], dimension = G.fluxes[i].cells_number + else: + title = '/fluxes' + for i in range(len(G.fluxes)): + grp = f.create_group(title + '/fluxes/flux' + str(i + 1)) + grp['values'] = G.fluxes[i].Poynting_frequency_flux + grp['wavelengths'] = G.fluxes[i].wavelengths + grp['normal'] = G.fluxes[i].normal + grp['direction'] = G.fluxes[i].direction + grp['x_cells'], grp['y_cells'], grp['z_cells'], dimension = G.fluxes[i].cells_number + grp = f.create_group(title + '/total_fluxes') + grp['values'] = G.total_flux + grp['wavelengths'] = wavelengths + grp = f.create_group('/constants') + grp['dt'] = G.dt + grp['dx'] = G.dx + grp['dy'] = G.dy + grp['dz'] = G.dz + + if G.scattering: + grp = f.create_group('/scattering/incident') + grp['values'] = G.fluxes[i].Poynting_frequency_flux_incident + grp['wavelengths'] = G.fluxes[i].wavelengths + grp['normal'] = G.fluxes[i].normal + grp['direction'] = G.fluxes[i].direction + grp['x_cells'], grp['y_cells'], grp['z_cells'], dimension = G.fluxes[i].cells_number + + + +def solve_scattering(currentmodelrun, modelend, G:FDTDGrid): + tstart = timer() + if len(G.scatteringgeometry) == 0: + raise GeneralError("No geometry input for the scattering geometry !") + box_settings = ''.join(G.box_fluxes_enumerate) if len(G.box_fluxes_enumerate) != 0 else 'None \n' + geometry_settings = '' + for key in G.scattering_geometrycmds: + lis = G.scattering_geometrycmds[key] + if len(lis) != 0: + geometry_settings += ' - ' + str(key) + ': ' + ' '.join(lis) + ' \n' + print(Fore.GREEN + "Scattering: \n Scattering geometry: \n" + geometry_settings + " Box settings: " + box_settings + Fore.RESET) + + #Run one simulation without the scattering geometry + if G.gpu is None: + solve_cpu_fluxes(currentmodelrun, modelend, G) + else: + solve_gpu_fluxes(currentmodelrun, modelend, G) + + #Initializing everything once again and adding the new geometries + G.initialise_geometry_arrays() + # if G.gpu is None: + G.initialise_field_arrays() + print(Fore.BLUE +'\n==================Scattering geometries : ' + ' '.join(G.scatteringgeometry) + '=================\n' + Fore.RESET) + process_geometrycmds(G.scatteringgeometry, G) + build_electric_components(G.solid, G.rigidE, G.ID, G) + build_magnetic_components(G.solid, G.rigidH, G.ID, G) + G.initialise_std_update_coeff_arrays() + G.initialise_dispersive_arrays() + process_materials(G) + G.empty_sim = False + + #Run the simulation with scattering geometries + if not G.gpu: + solve_cpu_fluxes(currentmodelrun, modelend, G) + # else: + # solve_gpu_fluxes(currentmodelrun, modelend, G) + + tsolve = timer() - tstart + return tsolve + +def solve_cpu_fluxes(currentmodelrun, modelend, G: FDTDGrid): + """ + Solving using FDTD method on CPU. Parallelised using Cython (OpenMP) for + electric and magnetic field updates, and PML updates. + + Args: + currentmodelrun (int): Current model run number. + modelend (int): Number of last model to run. + G (class): Grid class instance - holds essential parameters describing the model. + + Returns: + tsolve (float): Time taken to execute solving + """ + tsolvestart = timer() + message = "Running scattering simulation without scattering geometries" if G.empty_sim else "Running scattering simulation with scattering geometries" + for iteration in tqdm(range(G.iterations), desc= message + ', model ' + str(currentmodelrun) + '/' + str(modelend), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not G.progressbars): + # Store field component values for every receiver and transmission line + store_outputs(iteration, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz, G) + # Store any snapshots + if not G.empty_sim: + for snap in G.snapshots: + if snap.time == iteration + 1: + snap.store(G) + + # Update magnetic field components + update_magnetic(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsH, G.ID, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + + + # Update magnetic field components with the PML correction + for pml in G.pmls: + pml.update_magnetic(G) #No need to check for cylindrical mode here as there exists PML_cyl class with the same method + + # Update magnetic field components from sources + for source in G.transmissionlines + G.magneticdipoles: + if G.cylindrical: + raise CmdInputError("Magnetic dipole and transmission lines sources are not supported in cylindrical mode.") + source.update_magnetic(iteration, G.updatecoeffsH, G.ID, G.Hx, G.Hy, G.Hz, G) + + # Update electric field components + # All materials are non-dispersive so do standard update + if Material.maxpoles == 0: + update_electric(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsE, G.ID, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + + # If there are any dispersive materials do 1st part of dispersive update + # (it is split into two parts as it requires present and updated electric field values). + elif Material.maxpoles == 1: + assert not G.cylindrical, "Dispersive materials are not supported in cylindrical mode." + update_electric_dispersive_1pole_A(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsE, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + elif Material.maxpoles > 1: + assert not G.cylindrical, "Dispersive materials are not supported in cylindrical mode." + update_electric_dispersive_multipole_A(G.nx, G.ny, G.nz, G.nthreads, Material.maxpoles, G.updatecoeffsE, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + + # Update electric field components with the PML correction + for pml in G.pmls: + pml.update_electric(G) #No need to check for cylindrical mode here as there exists PML_cyl class with the same method + + # Update electric field components from sources (update any Hertzian dipole sources last) + + for source in G.voltagesources + G.transmissionlines + G.hertziandipoles: + source.update_electric(iteration, G.updatecoeffsE, G.ID, G.Ex, G.Ey, G.Ez, G) + + # If there are any dispersive materials do 2nd part of dispersive update + # (it is split into two parts as it requires present and updated electric + # field values). Therefore it can only be completely updated after the + # electric field has been updated by the PML and source updates. + if Material.maxpoles == 1: + update_electric_dispersive_1pole_B(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez) + elif Material.maxpoles > 1: + update_electric_dispersive_multipole_B(G.nx, G.ny, G.nz, G.nthreads, Material.maxpoles, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez) + + for flux in G.fluxes: + flux.save_fields_fluxes(G, iteration) + + tsolve = timer() - tsolvestart + return tsolve + +def solve_gpu_fluxes(currentmodelrun, modelend, G: FDTDGrid): + """Solving using FDTD method on GPU. Implemented using Nvidia CUDA. + + Args: + currentmodelrun (int): Current model run number. + modelend (int): Number of last model to run. + G (class): Grid class instance - holds essential parameters describing the model. + + Returns: + tsolve (float): Time taken to execute solving + memsolve (int): memory usage on final iteration in bytes + """ + + import pycuda.driver as drv + from pycuda.compiler import SourceModule + drv.init() + + # Suppress nvcc warnings on Windows + if sys.platform == 'win32': + compiler_opts = ['-w'] + else: + compiler_opts = None + + # Create device handle and context on specifc GPU device (and make it current context) + dev = drv.Device(G.gpu.deviceID) + ctx = dev.make_context() + + # Electric and magnetic field updates - prepare kernels, and get kernel functions + if Material.maxpoles > 0: + kernels_fields = SourceModule(kernels_template_fields.substitute(REAL=cudafloattype, COMPLEX=cudacomplextype, N_updatecoeffsE=G.updatecoeffsE.size, N_updatecoeffsH=G.updatecoeffsH.size, NY_MATCOEFFS=G.updatecoeffsE.shape[1], NY_MATDISPCOEFFS=G.updatecoeffsdispersive.shape[1], NX_FIELDS=G.nx + 1, NY_FIELDS=G.ny + 1, NZ_FIELDS=G.nz + 1, NX_ID=G.ID.shape[1], NY_ID=G.ID.shape[2], NZ_ID=G.ID.shape[3], NX_T=G.Tx.shape[1], NY_T=G.Tx.shape[2], NZ_T=G.Tx.shape[3]), options=compiler_opts) + else: # Set to one any substitutions for dispersive materials + kernels_fields = SourceModule(kernels_template_fields.substitute(REAL=cudafloattype, COMPLEX=cudacomplextype, N_updatecoeffsE=G.updatecoeffsE.size, N_updatecoeffsH=G.updatecoeffsH.size, NY_MATCOEFFS=G.updatecoeffsE.shape[1], NY_MATDISPCOEFFS=1, NX_FIELDS=G.nx + 1, NY_FIELDS=G.ny + 1, NZ_FIELDS=G.nz + 1, NX_ID=G.ID.shape[1], NY_ID=G.ID.shape[2], NZ_ID=G.ID.shape[3], NX_T=1, NY_T=1, NZ_T=1), options=compiler_opts) + update_e_gpu = kernels_fields.get_function("update_e") + update_h_gpu = kernels_fields.get_function("update_h") + + # Copy material coefficient arrays to constant memory of GPU (must be <64KB) for fields kernels + updatecoeffsE = kernels_fields.get_global('updatecoeffsE')[0] + updatecoeffsH = kernels_fields.get_global('updatecoeffsH')[0] + if G.updatecoeffsE.nbytes + G.updatecoeffsH.nbytes > G.gpu.constmem: + raise GeneralError('Too many materials in the model to fit onto constant memory of size {} on {} - {} GPU'.format(human_size(G.gpu.constmem), G.gpu.deviceID, G.gpu.name)) + else: + drv.memcpy_htod(updatecoeffsE, G.updatecoeffsE) + drv.memcpy_htod(updatecoeffsH, G.updatecoeffsH) + + # Electric and magnetic field updates - dispersive materials - get kernel functions and initialise array on GPU + if Material.maxpoles > 0: # If there are any dispersive materials (updates are split into two parts as they require present and updated electric field values). + update_e_dispersive_A_gpu = kernels_fields.get_function("update_e_dispersive_A") + update_e_dispersive_B_gpu = kernels_fields.get_function("update_e_dispersive_B") + G.gpu_initialise_dispersive_arrays() + + # Electric and magnetic field updates - set blocks per grid and initialise field arrays on GPU + G.gpu_set_blocks_per_grid() + G.gpu_initialise_arrays() + + # PML updates + if G.pmls: + # Prepare kernels + pmlmodulelectric = 'gprMax.pml_updates.pml_updates_electric_' + G.pmlformulation + '_gpu' + kernelelectricfunc = getattr(import_module(pmlmodulelectric), 'kernels_template_pml_electric_' + G.pmlformulation) + pmlmodulemagnetic = 'gprMax.pml_updates.pml_updates_magnetic_' + G.pmlformulation + '_gpu' + kernelmagneticfunc = getattr(import_module(pmlmodulemagnetic), 'kernels_template_pml_magnetic_' + G.pmlformulation) + kernels_pml_electric = SourceModule(kernelelectricfunc.substitute(REAL=cudafloattype, N_updatecoeffsE=G.updatecoeffsE.size, NY_MATCOEFFS=G.updatecoeffsE.shape[1], NX_FIELDS=G.nx + 1, NY_FIELDS=G.ny + 1, NZ_FIELDS=G.nz + 1, NX_ID=G.ID.shape[1], NY_ID=G.ID.shape[2], NZ_ID=G.ID.shape[3]), options=compiler_opts) + kernels_pml_magnetic = SourceModule(kernelmagneticfunc.substitute(REAL=cudafloattype, N_updatecoeffsH=G.updatecoeffsH.size, NY_MATCOEFFS=G.updatecoeffsH.shape[1], NX_FIELDS=G.nx + 1, NY_FIELDS=G.ny + 1, NZ_FIELDS=G.nz + 1, NX_ID=G.ID.shape[1], NY_ID=G.ID.shape[2], NZ_ID=G.ID.shape[3]), options=compiler_opts) + # Copy material coefficient arrays to constant memory of GPU (must be <64KB) for PML kernels + updatecoeffsE = kernels_pml_electric.get_global('updatecoeffsE')[0] + updatecoeffsH = kernels_pml_magnetic.get_global('updatecoeffsH')[0] + drv.memcpy_htod(updatecoeffsE, G.updatecoeffsE) + drv.memcpy_htod(updatecoeffsH, G.updatecoeffsH) + # Set block per grid, initialise arrays on GPU, and get kernel functions + for pml in G.pmls: + pml.gpu_initialise_arrays() + pml.gpu_get_update_funcs(kernels_pml_electric, kernels_pml_magnetic) + pml.gpu_set_blocks_per_grid(G) + + # Receivers + if G.rxs: + # Initialise arrays on GPU + rxcoords_gpu, rxs_gpu = gpu_initialise_rx_arrays(G) + # Prepare kernel and get kernel function + kernel_store_outputs = SourceModule(kernel_template_store_outputs.substitute(REAL=cudafloattype, NY_RXCOORDS=3, NX_RXS=6, NY_RXS=G.iterations, NZ_RXS=len(G.rxs), NX_FIELDS=G.nx + 1, NY_FIELDS=G.ny + 1, NZ_FIELDS=G.nz + 1), options=compiler_opts) + store_outputs_gpu = kernel_store_outputs.get_function("store_outputs") + + # Sources - initialise arrays on GPU, prepare kernel and get kernel functions + if G.voltagesources + G.hertziandipoles + G.magneticdipoles: + kernels_sources = SourceModule(kernels_template_sources.substitute(REAL=cudafloattype, N_updatecoeffsE=G.updatecoeffsE.size, N_updatecoeffsH=G.updatecoeffsH.size, NY_MATCOEFFS=G.updatecoeffsE.shape[1], NY_SRCINFO=4, NY_SRCWAVES=G.iterations, NX_FIELDS=G.nx + 1, NY_FIELDS=G.ny + 1, NZ_FIELDS=G.nz + 1, NX_ID=G.ID.shape[1], NY_ID=G.ID.shape[2], NZ_ID=G.ID.shape[3]), options=compiler_opts) + # Copy material coefficient arrays to constant memory of GPU (must be <64KB) for source kernels + updatecoeffsE = kernels_sources.get_global('updatecoeffsE')[0] + updatecoeffsH = kernels_sources.get_global('updatecoeffsH')[0] + drv.memcpy_htod(updatecoeffsE, G.updatecoeffsE) + drv.memcpy_htod(updatecoeffsH, G.updatecoeffsH) + if G.hertziandipoles: + srcinfo1_hertzian_gpu, srcinfo2_hertzian_gpu, srcwaves_hertzian_gpu = gpu_initialise_src_arrays(G.hertziandipoles, G) + update_hertzian_dipole_gpu = kernels_sources.get_function("update_hertzian_dipole") + if G.magneticdipoles: + srcinfo1_magnetic_gpu, srcinfo2_magnetic_gpu, srcwaves_magnetic_gpu = gpu_initialise_src_arrays(G.magneticdipoles, G) + update_magnetic_dipole_gpu = kernels_sources.get_function("update_magnetic_dipole") + if G.voltagesources: + srcinfo1_voltage_gpu, srcinfo2_voltage_gpu, srcwaves_voltage_gpu = gpu_initialise_src_arrays(G.voltagesources, G) + update_voltage_source_gpu = kernels_sources.get_function("update_voltage_source") + + # Snapshots - initialise arrays on GPU, prepare kernel and get kernel functions + if G.snapshots: + # Initialise arrays on GPU + snapEx_gpu, snapEy_gpu, snapEz_gpu, snapHx_gpu, snapHy_gpu, snapHz_gpu = gpu_initialise_snapshot_array(G) + # Prepare kernel and get kernel function + kernel_store_snapshot = SourceModule(kernel_template_store_snapshot.substitute(REAL=cudafloattype, NX_SNAPS=Snapshot.nx_max, NY_SNAPS=Snapshot.ny_max, NZ_SNAPS=Snapshot.nz_max, NX_FIELDS=G.nx + 1, NY_FIELDS=G.ny + 1, NZ_FIELDS=G.nz + 1), options=compiler_opts) + store_snapshot_gpu = kernel_store_snapshot.get_function("store_snapshot") + + # Iteration loop timer + iterstart = drv.Event() + iterend = drv.Event() + iterstart.record() + + for iteration in tqdm(range(G.iterations), desc='Running simulation, model ' + str(currentmodelrun) + '/' + str(modelend), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not G.progressbars): + + # Get GPU memory usage on final iteration + if iteration == G.iterations - 1: + memsolve = drv.mem_get_info()[1] - drv.mem_get_info()[0] + + # Store field component values for every receiver + if G.rxs: + store_outputs_gpu(np.int32(len(G.rxs)), np.int32(iteration), + rxcoords_gpu.gpudata, rxs_gpu.gpudata, + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, G.Hz_gpu.gpudata, + block=(1, 1, 1), grid=(round32(len(G.rxs)), 1, 1)) + + # Store any snapshots + for i, snap in enumerate(G.snapshots): + if snap.time == iteration + 1: + if not G.snapsgpu2cpu: + store_snapshot_gpu(np.int32(i), np.int32(snap.xs), + np.int32(snap.xf), np.int32(snap.ys), + np.int32(snap.yf), np.int32(snap.zs), + np.int32(snap.zf), np.int32(snap.dx), + np.int32(snap.dy), np.int32(snap.dz), + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, G.Hz_gpu.gpudata, + snapEx_gpu.gpudata, snapEy_gpu.gpudata, snapEz_gpu.gpudata, + snapHx_gpu.gpudata, snapHy_gpu.gpudata, snapHz_gpu.gpudata, + block=Snapshot.tpb, grid=Snapshot.bpg) + else: + store_snapshot_gpu(np.int32(0), np.int32(snap.xs), + np.int32(snap.xf), np.int32(snap.ys), + np.int32(snap.yf), np.int32(snap.zs), + np.int32(snap.zf), np.int32(snap.dx), + np.int32(snap.dy), np.int32(snap.dz), + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, G.Hz_gpu.gpudata, + snapEx_gpu.gpudata, snapEy_gpu.gpudata, snapEz_gpu.gpudata, + snapHx_gpu.gpudata, snapHy_gpu.gpudata, snapHz_gpu.gpudata, + block=Snapshot.tpb, grid=Snapshot.bpg) + gpu_get_snapshot_array(snapEx_gpu.get(), snapEy_gpu.get(), snapEz_gpu.get(), + snapHx_gpu.get(), snapHy_gpu.get(), snapHz_gpu.get(), 0, snap) + + # Update magnetic field components + update_h_gpu(np.int32(G.nx), np.int32(G.ny), np.int32(G.nz), + G.ID_gpu.gpudata, G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, + G.Hz_gpu.gpudata, G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, + G.Ez_gpu.gpudata, block=G.tpb, grid=G.bpg) + + # Update magnetic field components with the PML correction + for pml in G.pmls: + pml.gpu_update_magnetic(G) + + # Update magnetic field components for magetic dipole sources + if G.magneticdipoles: + update_magnetic_dipole_gpu(np.int32(len(G.magneticdipoles)), np.int32(iteration), + floattype(G.dx), floattype(G.dy), floattype(G.dz), + srcinfo1_magnetic_gpu.gpudata, srcinfo2_magnetic_gpu.gpudata, + srcwaves_magnetic_gpu.gpudata, G.ID_gpu.gpudata, + G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, G.Hz_gpu.gpudata, + block=(1, 1, 1), grid=(round32(len(G.magneticdipoles)), 1, 1)) + + # Update electric field components + # If all materials are non-dispersive do standard update + if Material.maxpoles == 0: + update_e_gpu(np.int32(G.nx), np.int32(G.ny), np.int32(G.nz), G.ID_gpu.gpudata, + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, G.Hz_gpu.gpudata, + block=G.tpb, grid=G.bpg) + # If there are any dispersive materials do 1st part of dispersive update + # (it is split into two parts as it requires present and updated electric field values). + else: + update_e_dispersive_A_gpu(np.int32(G.nx), np.int32(G.ny), np.int32(G.nz), + np.int32(Material.maxpoles), G.updatecoeffsdispersive_gpu.gpudata, + G.Tx_gpu.gpudata, G.Ty_gpu.gpudata, G.Tz_gpu.gpudata, G.ID_gpu.gpudata, + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, G.Hz_gpu.gpudata, + block=G.tpb, grid=G.bpg) + + # Update electric field components with the PML correction + for pml in G.pmls: + pml.gpu_update_electric(G) + + # Update electric field components for voltage sources + if G.voltagesources: + update_voltage_source_gpu(np.int32(len(G.voltagesources)), np.int32(iteration), + floattype(G.dx), floattype(G.dy), floattype(G.dz), + srcinfo1_voltage_gpu.gpudata, srcinfo2_voltage_gpu.gpudata, + srcwaves_voltage_gpu.gpudata, G.ID_gpu.gpudata, + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + block=(1, 1, 1), grid=(round32(len(G.voltagesources)), 1, 1)) + + # Update electric field components for Hertzian dipole sources (update any Hertzian dipole sources last) + if G.hertziandipoles: + update_hertzian_dipole_gpu(np.int32(len(G.hertziandipoles)), np.int32(iteration), + floattype(G.dx), floattype(G.dy), floattype(G.dz), + srcinfo1_hertzian_gpu.gpudata, srcinfo2_hertzian_gpu.gpudata, + srcwaves_hertzian_gpu.gpudata, G.ID_gpu.gpudata, + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + block=(1, 1, 1), grid=(round32(len(G.hertziandipoles)), 1, 1)) + + # If there are any dispersive materials do 2nd part of dispersive update (it is split into two parts as it requires present and updated electric field values). Therefore it can only be completely updated after the electric field has been updated by the PML and source updates. + if Material.maxpoles > 0: + update_e_dispersive_B_gpu(np.int32(G.nx), np.int32(G.ny), np.int32(G.nz), + np.int32(Material.maxpoles), G.updatecoeffsdispersive_gpu.gpudata, + G.Tx_gpu.gpudata, G.Ty_gpu.gpudata, G.Tz_gpu.gpudata, G.ID_gpu.gpudata, + G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, + block=G.tpb, grid=G.bpg) + + # Copy output from receivers array back to correct receiver objects + if G.rxs: + gpu_get_rx_array(rxs_gpu.get(), rxcoords_gpu.get(), G) + + # Copy data from any snapshots back to correct snapshot objects + if G.snapshots and not G.snapsgpu2cpu: + for i, snap in enumerate(G.snapshots): + gpu_get_snapshot_array(snapEx_gpu.get(), snapEy_gpu.get(), snapEz_gpu.get(), + snapHx_gpu.get(), snapHy_gpu.get(), snapHz_gpu.get(), i, snap) + + iterend.record() + iterend.synchronize() + tsolve = iterstart.time_till(iterend) * 1e-3 + + # Remove context from top of stack and delete + ctx.pop() + del ctx + + return tsolve, memsolve \ No newline at end of file diff --git a/gprMax/Fluxes_ext.pyx b/gprMax/Fluxes_ext.pyx new file mode 100644 index 000000000..fb9e5564a --- /dev/null +++ b/gprMax/Fluxes_ext.pyx @@ -0,0 +1,55 @@ +import numpy as np +cimport numpy as np +from cython.parallel import prange + +from gprMax.constants cimport floattype_t +from gprMax.constants cimport complextype_t +from scipy.constants import epsilon_0 as e0 +from scipy.constants import mu_0 as mu0 + +cpdef void save_fields_fluxes( + floattype_t[:, :, ::1] Ex, + floattype_t[:, :, ::1] Ey, + floattype_t[:, :, ::1] Ez, + floattype_t[:, :, ::1] Hx, + floattype_t[:, :, ::1] Hy, + floattype_t[:, :, ::1] Hz, + floattype_t[::1] omega, + complextype_t[:, :, :, :, ::1] E_omega, + complextype_t[:, :, :, :, ::1] H_omega, + int x_begin, + int y_begin, + int z_begin, + int Nx, + int Ny, + int Nz, + int Nf, + int bottom_x, + int bottom_y, + int bottom_z, + floattype_t dt, + int nthreads, + int iteration +): + + cdef Py_ssize_t i, j, k, ii, jj, kk, f + cdef floattype_t const= dt/np.sqrt(2*np.pi) + cdef floattype_t arg_exp = iteration*dt + cdef complextype_t fact + cdef floattype_t e=np.exp(1) + + for f in prange(0, Nf, nogil = True, schedule= 'static', num_threads= nthreads): + fact = e**(1j * arg_exp * omega[f]) * const + for i in range(x_begin, x_begin + Nx): + ii = bottom_x + i + for j in range(y_begin, y_begin + Ny): + jj = bottom_y + j + for k in range(z_begin, z_begin + Nz): + kk = bottom_z + k + E_omega[f, i, j, k, 0] += Ex[ii, jj, kk] * fact + E_omega[f, i, j, k, 1] += Ey[ii, jj, kk] * fact + E_omega[f, i, j, k, 2] += Ez[ii, jj, kk] * fact + + H_omega[f, i, j, k, 0] += Hx[ii, jj, kk] * fact + H_omega[f, i, j, k, 1] += Hy[ii, jj, kk] * fact + H_omega[f, i, j, k, 2] += Hz[ii, jj, kk] * fact \ No newline at end of file diff --git a/gprMax/Test_updates.py b/gprMax/Test_updates.py new file mode 100644 index 000000000..f85567a1c --- /dev/null +++ b/gprMax/Test_updates.py @@ -0,0 +1,23 @@ +from gprMax.fields_updates_ext import update_electric_cyl, update_magnetic_cyl +import numpy as np + +dr = 0.1e-6 +dz = 0.1e-6 +m = 2 +nthreads = 1 +nz = 1 +nphi = 1 +nr = 1 +Er = [0] +Ephi = [0] +Ez = [1] +Hr = [1] +Hphi = [0] +Hz = [0] + +update_coeffsE = np.array([[1.0000000e+00, 2.6638855e+02, 1.0000000e+00, 2.6638855e+02, 2.6638856e-05] for _ in range(6)]) +update_coeffsH = np.array([[1.0000000e+00, 1.8769575e-03, 1.0000000e+00, 1.8769575e-03, 1.8769575e-10] for _ in range(6)]) + +ID = np.array([[[[1 for _ in range(nz)] for _ in range(nphi)] for _ in range(nr)] for _ in range(6)]) + +update_electric_cyl(nr, nz, m, nthreads, dr, dz, update_coeffsE, ID, Er, Ephi, Ez, Hr, Hphi, Hz) \ No newline at end of file diff --git a/gprMax/fields_outputs.py b/gprMax/fields_outputs.py index e15dfca3a..2f593fffe 100644 --- a/gprMax/fields_outputs.py +++ b/gprMax/fields_outputs.py @@ -22,6 +22,7 @@ from gprMax._version import __version__ from gprMax.grid import Ix, Iy, Iz +import numpy as np def store_outputs(iteration, Ex, Ey, Ez, Hx, Hy, Hz, G): @@ -32,7 +33,6 @@ def store_outputs(iteration, Ex, Ey, Ez, Hx, Hy, Hz, G): Ex, Ey, Ez, Hx, Hy, Hz (memory view): Current electric and magnetic field values. G (class): Grid class instance - holds essential parameters describing the model. """ - for rx in G.rxs: for output in rx.outputs: # Store electric or magnetic field components @@ -47,7 +47,17 @@ def store_outputs(iteration, Ex, Ey, Ez, Hx, Hy, Hz, G): for tl in G.transmissionlines: tl.Vtotal[iteration] = tl.voltage[tl.antpos] tl.Itotal[iteration] = tl.current[tl.antpos] + +def store_outputs_cyl(iteration, Er, Ephi, Ez, Hr, Hphi, Hz, G): + for rx in G.rxs: + for output in rx.outputs: + # Store electric or magnetic field components + assert 'I' not in output, "Transmission lines are not supported in cylindrical mode." + field = locals()[output] + print(field[rx.rcoord_cyl, 0, rx.zcoord_cyl]) + rx.outputs[output][iteration] = np.real(field[rx.rcoord_cyl, 0, rx.zcoord_cyl] * np.exp(1j * G.m_cyl * rx.phicoord_cyl)) + # Store current component kernel_template_store_outputs = Template(""" @@ -97,47 +107,67 @@ def write_hdf5_outputfile(outputfile, G): outputfile (str): Name of the output file. G (class): Grid class instance - holds essential parameters describing the model. """ - + f = h5py.File(outputfile, 'w') f.attrs['gprMax'] = __version__ f.attrs['Title'] = G.title f.attrs['Iterations'] = G.iterations - f.attrs['nx_ny_nz'] = (G.nx, G.ny, G.nz) - f.attrs['dx_dy_dz'] = (G.dx, G.dy, G.dz) f.attrs['dt'] = G.dt - nsrc = len(G.voltagesources + G.hertziandipoles + G.magneticdipoles + G.transmissionlines) - f.attrs['nsrc'] = nsrc f.attrs['nrx'] = len(G.rxs) - f.attrs['srcsteps'] = G.srcsteps - f.attrs['rxsteps'] = G.rxsteps - - # Create group for sources (except transmission lines); add type and positional data attributes - srclist = G.voltagesources + G.hertziandipoles + G.magneticdipoles - for srcindex, src in enumerate(srclist): - grp = f.create_group('/srcs/src' + str(srcindex + 1)) - grp.attrs['Type'] = type(src).__name__ - grp.attrs['Position'] = (src.xcoord * G.dx, src.ycoord * G.dy, src.zcoord * G.dz) - - # Create group for transmission lines; add positional data, line resistance and - # line discretisation attributes; write arrays for line voltages and currents - for tlindex, tl in enumerate(G.transmissionlines): - grp = f.create_group('/tls/tl' + str(tlindex + 1)) - grp.attrs['Position'] = (tl.xcoord * G.dx, tl.ycoord * G.dy, tl.zcoord * G.dz) - grp.attrs['Resistance'] = tl.resistance - grp.attrs['dl'] = tl.dl - # Save incident voltage and current - grp['Vinc'] = tl.Vinc - grp['Iinc'] = tl.Iinc - # Save total voltage and current - f['/tls/tl' + str(tlindex + 1) + '/Vtotal'] = tl.Vtotal - f['/tls/tl' + str(tlindex + 1) + '/Itotal'] = tl.Itotal - - # Create group, add positional data and write field component arrays for receivers - for rxindex, rx in enumerate(G.rxs): - grp = f.create_group('/rxs/rx' + str(rxindex + 1)) - if rx.ID: - grp.attrs['Name'] = rx.ID - grp.attrs['Position'] = (rx.xcoord * G.dx, rx.ycoord * G.dy, rx.zcoord * G.dz) - - for output in rx.outputs: - f['/rxs/rx' + str(rxindex + 1) + '/' + output] = rx.outputs[output] + if not G.cylindrical: + f.attrs['nx_ny_nz'] = (G.nx, G.ny, G.nz) + f.attrs['dx_dy_dz'] = (G.dx, G.dy, G.dz) + f.attrs['srcsteps'] = G.srcsteps + f.attrs['rxsteps'] = G.rxsteps + nsrc = len(G.voltagesources + G.hertziandipoles + G.magneticdipoles + G.transmissionlines) + f.attrs['nsrc'] = nsrc + + # Create group for sources (except transmission lines); add type and positional data attributes + srclist = G.voltagesources + G.hertziandipoles + G.magneticdipoles + for srcindex, src in enumerate(srclist): + grp = f.create_group('/srcs/src' + str(srcindex + 1)) + grp.attrs['Type'] = type(src).__name__ + grp.attrs['Position'] = (src.xcoord * G.dx, src.ycoord * G.dy, src.zcoord * G.dz) + + # Create group for transmission lines; add positional data, line resistance and + # line discretisation attributes; write arrays for line voltages and currents + for tlindex, tl in enumerate(G.transmissionlines): + grp = f.create_group('/tls/tl' + str(tlindex + 1)) + grp.attrs['Position'] = (tl.xcoord * G.dx, tl.ycoord * G.dy, tl.zcoord * G.dz) + grp.attrs['Resistance'] = tl.resistance + grp.attrs['dl'] = tl.dl + # Save incident voltage and current + grp['Vinc'] = tl.Vinc + grp['Iinc'] = tl.Iinc + # Save total voltage and current + f['/tls/tl' + str(tlindex + 1) + '/Vtotal'] = tl.Vtotal + f['/tls/tl' + str(tlindex + 1) + '/Itotal'] = tl.Itotal + + # Create group, add positional data and write field component arrays for receivers + for rxindex, rx in enumerate(G.rxs): + grp = f.create_group('/rxs/rx' + str(rxindex + 1)) + if rx.ID: + grp.attrs['Name'] = rx.ID + grp.attrs['Position'] = (rx.xcoord * G.dx, rx.ycoord * G.dy, rx.zcoord * G.dz) + + for output in rx.outputs: + f['/rxs/rx' + str(rxindex + 1) + '/' + output] = rx.outputs[output] + else: + f.attrs['nr_nz'] = (G.nr_cyl, G.nz_cyl) + f.attrs['dr_dz'] = (G.dr_cyl, G.dz_cyl) + nsrc = len(G.voltagesources + G.hertziandipoles) + f.attrs['nsrc'] = nsrc + + srclist = G.voltagesources + G.hertziandipoles + for srcindex, src in enumerate(srclist): + grp = f.create_group('/srcs/src' + str(srcindex + 1)) + grp.attrs['Type'] = type(src).__name__ + grp.attrs['Position'] = (src.rcoord_cyl * G.dr_cyl, src.zcoord_cyl * G.dz_cyl) + + for rxindex, rx in enumerate(G.rxs): + grp = f.create_group('/rxs/rx' + str(rxindex + 1)) + if rx.ID: + grp.attrs['Name'] = rx.ID + grp.attrs['Position'] = (rx.rcoord_cyl * G.dr_cyl, rx.phicoord_cyl ,rx.zcoord_cyl * G.dz_cyl) + for output in rx.outputs: + f['/rxs/rx' + str(rxindex + 1) + '/' + output] = rx.outputs[output] \ No newline at end of file diff --git a/gprMax/fields_update_cylindrical V1 meep strategy.pyx b/gprMax/fields_update_cylindrical V1 meep strategy.pyx new file mode 100644 index 000000000..8775aa76e --- /dev/null +++ b/gprMax/fields_update_cylindrical V1 meep strategy.pyx @@ -0,0 +1,319 @@ +################################### +# Cylindrical coordinates updates # +################################### + +from libc.stdlib cimport malloc, free +from libc cimport complex +from cython cimport boundscheck, wraparound, nonecheck + +cdef extern from "complex.h": + double complex I + +cdef void free_complex3D(double complex*** arr, Py_ssize_t nx, Py_ssize_t ny): + cdef Py_ssize_t i, j + for i in range(nx): + for j in range(ny): + free(arr[i][j]) + free(arr[i]) + free(arr) + +cdef double complex*** alloc_and_copy_complex3D(np.complex128_t[:, :, ::1] arr): + """ + Alloue un tableau 3D C (double complex***) et copie les données d'un memoryview NumPy. + + Args: + arr (np.complex128_t[:, :, ::1]): Tableau NumPy d'entrée + + Returns: + double complex*** : Tableau C alloué et rempli + """ + cdef Py_ssize_t nx = arr.shape[0] + cdef Py_ssize_t ny = arr.shape[1] + cdef Py_ssize_t nz = arr.shape[2] + + cdef double complex*** out + cdef Py_ssize_t i, j, k + + # Allocation des pointeurs + out = malloc(nx * sizeof(double complex**)) + for i in range(nx): + out[i] = malloc(ny * sizeof(double complex*)) + for j in range(ny): + out[i][j] = malloc(nz * sizeof(double complex)) + + # Copie des données + for i in range(nx): + for j in range(ny): + for k in range(nz): + out[i][j][k] = arr[i, j, k] + + return out + +cdef void copy_complex3D_to_numpy(double complex*** src, + np.complex128_t[:, :, ::1] dest): + """ + Copie les données d’un tableau C (double complex***) dans un memoryview NumPy, + sans avoir besoin de spécifier les dimensions. + + Args: + src : tableau C (double complex***) + dest : memoryview NumPy déjà alloué + """ + cdef Py_ssize_t nx = dest.shape[0] + cdef Py_ssize_t ny = dest.shape[1] + cdef Py_ssize_t nz = dest.shape[2] + cdef Py_ssize_t i, j, k + + for i in range(nx): + for j in range(ny): + for k in range(nz): + dest[i, j, k] = src[i][j][k] + + +cpdef void update_electric_cyl( + int nr, + int nz, + int m, + int nthreads, + float dr, + float dz, + float eps, + foat sigma, + floattype_t[:, ::1] updatecoeffsE, #Courant factor is already included in dt + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np + ): + """This function updates the electric field components. + + Args: + nr, nz (int): Grid size in cells + nthreads (int): Number of threads to use + ID, E, H (memoryviews): Access to update coeffients, ID and field component arrays + """ + + cdef Py_ssize_t i, k, taille_i_fields, taille_j_fields + cdef int materialEr, materialEphi, materialEz + # It may seem like we are updated inside the pmls, and we are ! But those fields will be overwritten by the update + # of the pml fields after this function + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + + for i in prange(0, nr, nogil= True, schedule= 'static', num_threads= nthreads): + for k in range(0, nz): + materialEr = ID[0,i,0,k] + materialEphi = ID[1,i,0,k] + materialEz = ID[2,i,0,k] + + Er[i][0][k] = Er[i][0][k] * updatecoeffsE[materialEr, 0] + I * m / (i+0.5) * Hz[i][0][k] * updatecoeffsE[materialEr, 1] - (Hphi[i][0][k] - Hphi[i][0][k-1]) * updatecoeffsE[materialEr, 3] + + Ephi[i][0][k] = Ephi[i][0][k] * updatecoeffsE[materialEphi, 0] + (Hr[i][0][k] - Hr[i][0][k-1]) * updatecoeffsE[materialEphi,3] - (Hz[i][0][k] - Hz[i-1][0][k]) * updatecoeffsE[materialEphi, 1] + + Ez[i][0][k] = Ez[i][0][k] * updatecoeffsE[materialEz, 0] + (Hphi[i][0][k]*(i+0.5) - Hphi[i-1][0][k]*(i-0.5))/i * updatecoeffsE[materialEz, 1] - I * m / i * Hr[i][0][k] * updatecoeffsE[materialEz, 1] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) + +cpdef void update_magnetic_cyl( + int nr, + int nz, + int m, + int nthreads, + floattype_t[:, ::1] updatecoeffsH, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np + ): + """This function updates the electric field components. + + Args: + nr, nz (int): Grid size in cells + nthreads (int): Number of threads to use + ID, E, H (memoryviews): Access to update coeffients, ID and field component arrays + """ + + cdef Py_ssize_t i, k, taille_i_fields, taille_j_fields + cdef int materialHr, materialHphi, materialHz + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + + for i in prange(1, nr, nogil= True, schedule= 'static', num_threads= nthreads): + for k in range(1, nz): + materialHr = ID[3,i,0,k] + materialHphi = ID[4,i,0,k] + materialHz = ID[5,i,0,k] + + Hr[i][0][k] = Hr[i][0][k] * updatecoeffsH[materialHr, 0] + (Ephi[i][0][k+1] - Ephi[i][0][k]) * updatecoeffsH[materialHr, 3] - I * m / i * Ez[i+1][0][k] * updatecoeffsH[materialHr, 1] + + Hphi[i][0][k] = Hphi[i][0][k] * updatecoeffsH[materialHphi, 0] + (Ez[i+1][0][k] - Ez[i][0][k]) * updatecoeffsH[materialHphi,1] - (Er[i][0][k+1] - Er[i][0][k]) * updatecoeffsH[materialHphi, 3] + + Hz[i][0][k] = Hz[i][0][k] * updatecoeffsH[materialHz, 0] + I * m / (i+0.5) * Er[i][0][k] * updatecoeffsH[materialHz, 1] - (Ephi[i+1][0][k]*(i+1) - Ephi[i][0][k]*i)/(i+0.5) * updatecoeffsH[materialHz, 1] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) + +cpdef update_magnetic_origin( + int nr, + int nz, + int m, + int nthreads, + floattype_t dt, + floattype_t mu, + floattype_t dr, + floattype_t[:, ::1] updatecoeffsH, + floattype_t[:, ::1] updatecoeffsE, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np +): + + cdef Py_ssize_t i, k, taille_i_fields, taille_j_fields + cdef int materialHr, materialHphi, materialHz + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + + cdef floattype_t pi = np.pi + cdef floattype_t e = np.exp(1) + cdef int Nphi = int(nr//4 * 4) + print("update_magnetic_origin 1") + + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + Hr[0][0][k] = updatecoeffsE[ID[1,0,0,k],0] * Hr[0][0][k] + 0.5 * updatecoeffsE[ID[1,0,0,k], 1] * (e**(I*m*pi* 3 / 4 * Nphi/nr) - e**(I*m*pi / 4 * Nphi/nr)) * Ez[1][0][k] + 0.5 * updatecoeffsE[ID[1,0,0,k], 3] * (- e**(I * m * pi * 3 * Nphi / nr) + e**(I * m * pi * Nphi / nr)) * Ephi[0][0][k+1] + 0.5 * updatecoeffsE[ID[1,0,0,k], 3] * (e**(I * m * pi * 3 * Nphi / nr) - e**(I * m * pi * Nphi / nr)) * Ephi[0][0][k] + Hphi[0][0][k] = Hphi[0][0][k] * updatecoeffsH[ID[4,0,0,k], 0] + (Ez[1][0][k] - Ez[0][0][k]) * updatecoeffsH[ID[4,0,0,k],1] - (Er[0][0][k+1] - Er[0][0][k]) * updatecoeffsH[ID[4,0,0,k], 3] + Hz[0][0][k] += Hz[0][0][k] * updatecoeffsH[ID[5,0,0,k], 0] + dt / mu * (Er[0][0][k] * I * m - Ephi[1][0][k]) / (0.5 * dr) + print("update_magnetic_origin 2") + + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) + +cpdef update_electric_origin( + int nr, + int nz, + int m, + int nthreads, + floattype_t dt, + floattype_t mu, + floattype_t dr, + floattype_t[:, ::1] updatecoeffsH, + floattype_t[:, ::1] updatecoeffsE, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np +): + + cdef Py_ssize_t j, k, taille_i_fields, taille_j_fields + cdef int materialEr, materialEphi, materialEz + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + cdef floattype_t pi = np.pi + cdef floattype_t e = np.exp(1) + cdef int Nphi = int(nr//4 * 4) + cdef floattype_t m_coeff + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + print("update_electric_origin 1") + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + m_coeff = 4*updatecoeffsE[ID[2,0,0,k], 1] / Nphi + Er[0][0][k] = Er[0][0][k] * updatecoeffsE[ID[1,0,0,k], 0] + I * m / (0.5) * Hz[0][0][k] * updatecoeffsE[ID[1,0,0,k], 1] - (Hphi[0][0][k] - Hphi[0][0][k-1]) * updatecoeffsE[ID[1,0,0,k], 3] + with gil: + print("Passage 1 safe") + Ephi[0][0][k] = updatecoeffsE[ID[1,0,0,k], 0] * Ephi[0][0][k] + (Hr[0][0][k] - Hr[0][0][k-1]) * updatecoeffsE[ID[1,0,0,k], 3] - Hz[1][0][k] * e**(I * pi * Nphi / nr) * updatecoeffsE[ID[1,0,0,k], 1] + with gil: + print("Passage 2 safe") + Ez[0][0][k] *= updatecoeffsE[ID[2,0,0,k], 0] * Ez[0][0][k] + with gil: + print("Passage 3 safe") + for j in range(1, Nphi): + Ez[0][0][k] += m_coeff * Hphi[1][0][k+1] * e**(I * m * pi * j / Nphi ) + print("update_electric_origin 2") + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) \ No newline at end of file diff --git a/gprMax/fields_update_cylindrical V2 thesis application.pyx b/gprMax/fields_update_cylindrical V2 thesis application.pyx new file mode 100644 index 000000000..0a0254852 --- /dev/null +++ b/gprMax/fields_update_cylindrical V2 thesis application.pyx @@ -0,0 +1,98 @@ +from gprMax.constants import floattype_t, complextype_t +cimport numpy as np + +cpdef void update_electric( + floattype_t dr, + floattype_t dphi, + floattype_t dz, + int nr, + int nphi, + int nz, + floattype_t[:, :, ::1] coeffsupdateE, + floattype_t[:, : ,::1] coeffsupdateH, + complextype_t[:, :, ::1] Er, + complextype_t[:, :, ::1] Ephi, + complextype_t[:, :, ::1] Ez, + complextype_t[:, :, ::1] Hr, + complextype_t[:, :, ::1] Hphi, + complextype_t[:, :, ::1] Hz, + complextype_t[::1] sum_Hphi, + int m, + int nthreads +): + cdef Py_ssize_t i, j, k, j_update + if dphi != 0: + #k = 0, j = 0, r = 0 + materialEr = ID[0,0,0,0] + materialEphi = ID[1,0,0,0] + materialEz = ID[2,0,0,0] + Er[0,0,0] = coeffsupdateE[materialEr, 0] * Er[0,0,0] + coeffsupdateE[materialEr, 2] * (Hz[0,0,0] - Hz[0,nphi-1,0]) / (0.5) - updatecoeffsE[materialEr, 3]*(Hphi[0,0,0] - 0) + Ephi[0,0,0] = coeffsupdateE[materialEphi, 0] * Ephi[0,0,0] + coeffsupdateE[materialEphi, 3] * (Hr[0,0,0] - 0) - coeffsupdateE[materialEphi, 1] * (Hz[0,0,0] - Hz[0, nphi//2, 0]) + Ez[0,0,0] = coeffsupdateE[materialEz, 0] * Ez[0,0,0] + coeffsupdateE[materialEz, 1] * sum_Hphi[k] + + #First, let's take care of iterations involving r=0 and j=0 + for k in prange(1, nz, nogil= True, schedule= 'static', num_threads= nthreads): + materialEr = ID[0,0,0,k] + materialEphi = ID[1,0,0,k] + materialEz = ID[2,0,0,k] + Er[0,0,k] = coeffsupdateE[materialEr, 0] * Er[0,0,k] + coeffsupdateE[materialEr, 2] * (Hz[0,0,k] - Hz[0,nphi-1,k]) / (0.5) - updatecoeffsE[materialEr, 3]*(Hphi[0,0,k] - Hphi[0,0,k-1]) + Ephi[0,0,k] = coeffsupdateE[materialEphi, 0] * Ephi[0,0,k] + coeffsupdateE[materialEphi, 3] * (Hr[0,0,k] - Hr[0,0,k-1]) - coeffsupdateE[materialEphi, 1] * (Hz[0,0,k] - Hz[0, nphi//2, k]) + Ez[0,0,k] = coeffsupdateE[materialEz, 0] * Ez[0,0,k] + coeffsupdateE[materialEz, 1] * sum_Hphi[k] + + #Then with r = 0 and j != 0 + for j in prange(1, nr, nogil= True, schedule= 'static', num_threads= nthreads): + #k = 0 + materialEr = ID[0,0,j,0] + materialEphi = ID[1,0,j,0] + materialEz = ID[2,0,j,0] + Er[0,j,0] = coeffsupdateE[materialEr, 0] * Er[0,j,0] + coeffsupdateE[materialEr, 2] * (Hz[0,j,0] - Hz[0,j-1,0]) / (0.5) - updatecoeffsE[materialEr, 3]*(Hphi[0,j,0] - 0) + Ephi[0,j,0] = coeffsupdateE[materialEphi, 0] * Ephi[0,j,0] + coeffsupdateE[materialEphi, 3] * (Hr[0,j,k] - 0) - coeffsupdateE[materialEphi, 1] * (Hz[0,j,0] - Hz[0, j_update, 0]) + Ez[0,j,0] = coeffsupdateE[materialEz, 0] * Ez[0,j,0] + coeffsupdateE[materialEz, 1] * sum_Hphi[0] + j_update = (j + nphi // 2)% nphi + + #k != 0, j != 0, r = 0 + for k in range(1,nz): + materialEr = ID[0,0,j,k] + materialEphi = ID[1,0,j,k] + materialEz = ID[2,0,j,k] + Er[0,j,k] = coeffsupdateE[materialEr, 0] * Er[0,j,k] + coeffsupdateE[materialEr, 2] * (Hz[0,j,k] - Hz[0,j-1,k]) / (0.5) - updatecoeffsE[materialEr, 3]*(Hphi[0,j,k] - Hphi[0,j,k-1]) + Ephi[0,j,k] = coeffsupdateE[materialEphi, 0] * Ephi[0,j,k] + coeffsupdateE[materialEphi, 3] * (Hr[0,j,k] - Hr[0,j,k-1]) - coeffsupdateE[materialEphi, 1] * (Hz[0,j,k] - Hz[0, j_update, k]) + Ez[0,j,k] = coeffsupdateE[materialEz, 0] * Ez[0,j,k] + coeffsupdateE[materialEz, 1] * sum_Hphi[k] + + # And now, r != 0 + for i in prange(1, nr, nogil= True, schedule= 'static', num_threads= nthreads): + # k = 0, j = 0 + materialEr = ID[0,i,0,0] + materialEphi = ID[1,i,0,0] + materialEz = ID[2,i,0,0] + Er[i,0,0] = updatecoeffsE[materialEr, 0] * Er[i,0,0] + coeffsupdateE[materialEr, 2]*(Hz[i,0,k] - Hz[i,nphi-1,0])/(i+0.5) - coeffsupdateE[materialEr, 3] * Hphi[i,0,0] + Ephi[i,0,0] = updatecoeffsE[materialEphi, 0] * Ephi[i,0,0] + updatecoeffsE[materialEphi, 3] * Hr[i,0,0] - updatecoeffsE[materialEphi, 1] * (Hz[i,0,0] - Hz[i-1,0,0]) + Ez[i,0,0] = updatecoeffsE[materialEz, 0] * Ez[i,0,0] + updatecoeffsE[materialEz, 1] * ((i+0.5) * Hphi[i,0,0] - (i-0.5) * Hphi[i-1,0,0])/i - updatecoeffsE[materialEz, 2]*(Hr[i,0,0] - Hr[i,nphi-1,0])/i + # k = 0, j != 0 + for j in range(1, nphi): + materialEr = ID[0,i,j,0] + materialEphi = ID[1,i,j,0] + materialEz = ID[2,i,j,0] + Er[i,j,0] = updatecoeffsE[materialEr, 0] * Er[i,j,0] + coeffsupdateE[materialEr, 2]*(Hz[i,j,0] - Hz[i,j-1,0])/(i+0.5) - coeffsupdateE[materialEr, 3] * Hphi[i,j,0] + Ephi[i,j,0] = updatecoeffsE[materialEphi, 0] * Ephi[i,j,0] + updatecoeffsE[materialEphi, 3] * Hr[i,j,0] - updatecoeffsE[materialEphi, 1] * (Hz[i,j,0] - Hz[i-1,j,0]) + Ez[i,j,0] = updatecoeffsE[materialEz, 0] * Ez[i,j,0] + updatecoeffsE[materialEz, 1] * ((i+0.5) * Hphi[i,j,0] - (i-0.5) * Hphi[i-1,j,0])/i - updatecoeffsE[materialEz, 2]*(Hr[i,j,0] - Hr[i,j-1,0])/i + # j = 0, k != 0 + for k in range(1, nz): + materialEr = ID[0,i,0,k] + materialEphi = ID[1,i,0,k] + materialEz = ID[2,i,0,k] + Er[i,0,k] = updatecoeffsE[materialEr, 0] * Er[i,0,k] + coeffsupdateE[materialEr, 2]*(Hz[i,0,k] - Hz[i,nphi-1,k])/(i+0.5) - coeffsupdateE[materialEr, 3] * Hphi[i,0,k] + Ephi[i,0,k] = updatecoeffsE[materialEphi, 0] * Ephi[i,0,k] + updatecoeffsE[materialEphi, 3] * Hr[i,0,k] - updatecoeffsE[materialEphi, 1] * (Hz[i,0,k] - Hz[i-1,0,k]) + Ez[i,0,k] = updatecoeffsE[materialEz, 0] * Ez[i,0,k] + updatecoeffsE[materialEz, 1] * ((i+0.5) * Hphi[i,0,k] - (i-0.5) * Hphi[i-1,0,k])/i - updatecoeffsE[materialEz, 2]*(Hr[i,0,k] - Hr[i,nphi-1,k])/i + # j != 0, k != 0 + for j in range(1, nphi): + for k in range(1, nz): + materialEr = ID[0,i,j,k] + materialEphi = ID[1,i,j,k] + materialEz = ID[2,i,j,k] + Er[i,j,k] = updatecoeffsE[materialEr, 0] * Er[i,j,k] + coeffsupdateE[materialEr, 2]*(Hz[i,j,k] - Hz[i,j-1,k])/(i+0.5) - coeffsupdateE[materialEr, 3] * Hphi[i,j,k] + Ephi[i,j,k] = updatecoeffsE[materialEphi, 0] * Ephi[i,j,k] + updatecoeffsE[materialEphi, 3] * Hr[i,j,k] - updatecoeffsE[materialEphi, 1] * (Hz[i,j,k] - Hz[i-1,j,k]) + Ez[i,j,k] = updatecoeffsE[materialEz, 0] * Ez[i,j,k] + updatecoeffsE[materialEz, 1] * ((i+0.5) * Hphi[i,j,k] - (i-0.5) * Hphi[i-1,j,k])/i - updatecoeffsE[materialEz, 2]*(Hr[i,j,k] - Hr[i,j-1,k])/i + + else: + raise GeneralError("Cylindrical symmetry not yet implemented") \ No newline at end of file diff --git a/gprMax/fields_updates_ext.pyx b/gprMax/fields_updates_ext.pyx index 81367b1be..ca7cf27ee 100644 --- a/gprMax/fields_updates_ext.pyx +++ b/gprMax/fields_updates_ext.pyx @@ -22,6 +22,8 @@ from cython.parallel import prange from gprMax.constants cimport floattype_t from gprMax.constants cimport complextype_t +from scipy.constants import epsilon_0 as e0 +from scipy.constants import mu_0 as mu0 ############################################### @@ -410,3 +412,377 @@ cpdef void update_magnetic( Hx[i + 1, j, k] = updatecoeffsH[materialHx, 0] * Hx[i + 1, j, k] - updatecoeffsH[materialHx, 2] * (Ez[i + 1, j + 1, k] - Ez[i + 1, j, k]) + updatecoeffsH[materialHx, 3] * (Ey[i + 1, j, k + 1] - Ey[i + 1, j, k]) Hy[i, j + 1, k] = updatecoeffsH[materialHy, 0] * Hy[i, j + 1, k] - updatecoeffsH[materialHy, 3] * (Ex[i, j + 1, k + 1] - Ex[i, j + 1, k]) + updatecoeffsH[materialHy, 1] * (Ez[i + 1, j + 1, k] - Ez[i, j + 1, k]) Hz[i, j, k + 1] = updatecoeffsH[materialHz, 0] * Hz[i, j, k + 1] - updatecoeffsH[materialHz, 1] * (Ey[i + 1, j, k + 1] - Ey[i, j, k + 1]) + updatecoeffsH[materialHz, 2] * (Ex[i, j + 1, k + 1] - Ex[i, j, k + 1]) + + +################################### +# Cylindrical coordinates updates # +################################### + +from libc.stdlib cimport malloc, free +from libc cimport complex +from cython cimport boundscheck, wraparound, nonecheck + +cdef extern from "complex.h": + double complex I + +cdef void free_complex3D(double complex*** arr, Py_ssize_t nx, Py_ssize_t ny): + cdef Py_ssize_t i, j + for i in range(nx): + for j in range(ny): + free(arr[i][j]) + free(arr[i]) + free(arr) + +cdef double complex*** alloc_and_copy_complex3D(np.complex128_t[:, :, ::1] arr): + """ + Alloue un tableau 3D C (double complex***) et copie les données d'un memoryview NumPy. + + Args: + arr (np.complex128_t[:, :, ::1]): Tableau NumPy d'entrée + + Returns: + double complex*** : Tableau C alloué et rempli + """ + cdef Py_ssize_t nx = arr.shape[0] + cdef Py_ssize_t ny = arr.shape[1] + cdef Py_ssize_t nz = arr.shape[2] + + cdef double complex*** out + cdef Py_ssize_t i, j, k + + # Allocation des pointeurs + out = malloc(nx * sizeof(double complex**)) + for i in range(nx): + out[i] = malloc(ny * sizeof(double complex*)) + for j in range(ny): + out[i][j] = malloc(nz * sizeof(double complex)) + + # Copie des données + for i in range(nx): + for j in range(ny): + for k in range(nz): + out[i][j][k] = arr[i, j, k] + + return out + +cdef void copy_complex3D_to_numpy(double complex*** src, + np.complex128_t[:, :, ::1] dest): + """ + Copie les données d’un tableau C (double complex***) dans un memoryview NumPy, + sans avoir besoin de spécifier les dimensions. + + Args: + src : tableau C (double complex***) + dest : memoryview NumPy déjà alloué + """ + cdef Py_ssize_t nx = dest.shape[0] + cdef Py_ssize_t ny = dest.shape[1] + cdef Py_ssize_t nz = dest.shape[2] + cdef Py_ssize_t i, j, k + + for i in range(nx): + for j in range(ny): + for k in range(nz): + dest[i, j, k] = src[i][j][k] + + +cpdef void update_electric_cyl( + int nr, + int nz, + int m, + int nthreads, + float dr, + float dz, + floattype_t[:, ::1] updatecoeffsE, #Courant factor is already included in dt + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np + ): + """This function updates the electric field components. + + Args: + nr, nz (int): Grid size in cells + nthreads (int): Number of threads to use + ID, E, H (memoryviews): Access to update coeffients, ID and field component arrays + """ + + cdef Py_ssize_t i, k, taille_i_fields, taille_j_fields + cdef int materialEr, materialEphi, materialEz + #It may seem like we are updated inside the pmls, and we are ! But those fields will be overwritten by the update + # of the pml fields after this function + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + print(updatecoeffsE[ID[2,1,0,50],0] * Ez[1][0][50]) + print((Hphi[1][0][50]*(1+0.5) - Hphi[1-1][0][50]*(1-0.5))/(1-0.5) * updatecoeffsE[ID[2,1,0,50], 1]) + print((Hphi[1][0][50] - Hphi[1][0][50-1]) * updatecoeffsE[ID[0,1,0,50], 3]) + for i in prange(1, nr, nogil= True, schedule= 'static', num_threads= nthreads): + for k in range(1, nz): + materialEr = ID[0,i,0,k] + materialEphi = ID[1,i,0,k] + materialEz = ID[2,i,0,k] + + Er[i][0][k] = I * m / (i+0.5) * Hz[i][0][k] * updatecoeffsE[materialEr, 1] - (Hphi[i][0][k] - Hphi[i][0][k-1]) * updatecoeffsE[materialEr, 3] + Er[i][0][k] * updatecoeffsE[materialEr, 0] + + Ephi[i][0][k] = (Hr[i][0][k] - Hr[i][0][k-1]) * updatecoeffsE[materialEphi,3] - (Hz[i][0][k] - Hz[i-1][0][k]) * updatecoeffsE[materialEphi, 1] + Ephi[i][0][k] * updatecoeffsE[materialEphi, 0] + + Ez[i][0][k] = (Hphi[i][0][k]*(i+0.5) - Hphi[i-1][0][k]*(i-0.5))/(i-0.5) * updatecoeffsE[materialEz, 1] - I * m / (i+0.5) * Hr[i][0][k] * updatecoeffsE[materialEz, 1] + Ez[i][0][k] * updatecoeffsE[materialEz, 0] + + cdef floattype_t pi = np.pi + cdef floattype_t e = np.exp(1) + if np.abs(m) == 1: + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + Er[0][0][k] = Er[2][0][k] * e**(I * m * pi) + Ephi[0][0][k] = Ephi[2][0][k] * e**(I * m * pi) + Ez[0][0][k] = 0 + elif m == 0: + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + Er[0][0][k] = Er[2][0][k] + Ephi[0][0][k] = 0 + Ez[0][0][k] = Ez[2][0][k] + + else: + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + Er[0][0][k] = 0 + Ephi[0][0][k] = 0 + Ez[0][0][k] = 0 + + # for i in prange(1, nr, nogil= True, schedule= 'static', num_threads= nthreads): + # for k in range(1, nz): + # materialEr = ID[0,i,0,k] + # materialEphi = ID[1,i,0,k] + # materialEz = ID[2,i,0,k] + + # Er[i][0][k] = Er[i][0][k] * updatecoeffsE[materialEr, 0] + I * m / (i+0.5) * Hz[i][0][k] * updatecoeffsE[materialEr, 1] - (Hphi[i][0][k] - Hphi[i][0][k-1]) * updatecoeffsE[materialEr, 3] + + # Ephi[i][0][k] = Ephi[i][0][k] * updatecoeffsE[materialEphi, 0] + (Hr[i][0][k] - Hr[i][0][k-1]) * updatecoeffsE[materialEphi,3] - (Hz[i][0][k] - Hz[i-1][0][k]) * updatecoeffsE[materialEphi, 1] + + # Ez[i][0][k] = Ez[i][0][k] * updatecoeffsE[materialEz, 0] + (Hphi[i][0][k]*(i+0.5) - Hphi[i-1][0][k]*(i-0.5))/i * updatecoeffsE[materialEz, 1] - I * m / i * Hr[i][0][k] * updatecoeffsE[materialEz, 1] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) + +cpdef void update_magnetic_cyl( + int nr, + int nz, + int m, + int nthreads, + floattype_t[:, ::1] updatecoeffsH, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np + ): + """This function updates the electric field components. + + Args: + nr, nz (int): Grid size in cells + nthreads (int): Number of threads to use + ID, E, H (memoryviews): Access to update coeffients, ID and field component arrays + """ + + cdef Py_ssize_t i, k, taille_i_fields, taille_j_fields + cdef int materialHr, materialHphi, materialHz + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + + + for i in prange(0, nr, nogil= True, schedule= 'static', num_threads= nthreads): + for k in range(0, nz): + materialHr = ID[3,i,0,k] + materialHphi = ID[4,i,0,k] + materialHz = ID[5,i,0,k] + + Hr[i][0][k] = - I * m / (i+1) * Ez[i+1][0][k] * updatecoeffsH[materialHr, 1] + (Ephi[i][0][k+1] - Ephi[i][0][k]) * updatecoeffsH[materialHr, 3] + Hr[i][0][k] * updatecoeffsH[materialHr, 0] + + Hphi[i][0][k] = (Ez[i+1][0][k] - Ez[i][0][k]) * updatecoeffsH[materialHphi,1] - (Er[i][0][k+1] - Er[i][0][k]) * updatecoeffsH[materialHphi, 3] + Hphi[i][0][k] * updatecoeffsH[materialHphi, 0] + + Hz[i][0][k] = - (Ephi[i+1][0][k]*(i+1) - Ephi[i][0][k]*i)/(i+0.5) * updatecoeffsH[materialHz, 1] + I * m / (i+0.5) * Er[i][0][k] * updatecoeffsH[materialHz, 1] + Hz[i][0][k] * updatecoeffsH[materialHz, 0] + + if m == 0: + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + Hr[0][0][k] = 0 + elif np.abs(m) != 1: + for k in prange(0,nz,nogil=True, schedule= 'static', num_threads=nthreads): + Hr[0][0][k] = 0 + Hphi[0][0][k] = 0 + Hz[0][0][k] = 0 + + # for i in prange(1, nr, nogil= True, schedule= 'static', num_threads= nthreads): + # for k in range(1, nz): + # materialHr = ID[3,i,0,k] + # materialHphi = ID[4,i,0,k] + # materialHz = ID[5,i,0,k] + + # Hr[i][0][k] = Hr[i][0][k] * updatecoeffsH[materialHr, 0] + (Ephi[i][0][k+1] - Ephi[i][0][k]) * updatecoeffsH[materialHr, 3] - I * m / i * Ez[i+1][0][k] * updatecoeffsH[materialHr, 1] + + # Hphi[i][0][k] = Hphi[i][0][k] * updatecoeffsH[materialHphi, 0] + (Ez[i+1][0][k] - Ez[i][0][k]) * updatecoeffsH[materialHphi,1] - (Er[i][0][k+1] - Er[i][0][k]) * updatecoeffsH[materialHphi, 3] + + # Hz[i][0][k] = Hz[i][0][k] * updatecoeffsH[materialHz, 0] + I * m / (i+0.5) * Er[i][0][k] * updatecoeffsH[materialHz, 1] - (Ephi[i+1][0][k]*(i+1) - Ephi[i][0][k]*i)/(i+0.5) * updatecoeffsH[materialHz, 1] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) + +cpdef update_magnetic_origin( + int nr, + int nz, + int m, + int nthreads, + floattype_t dt, + floattype_t mu, + floattype_t dr, + floattype_t[:, ::1] updatecoeffsH, + floattype_t[:, ::1] updatecoeffsE, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np +): + + cdef Py_ssize_t i, k, taille_i_fields, taille_j_fields + cdef int materialHr, materialHphi, materialHz + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + + cdef floattype_t pi = np.pi + cdef floattype_t e = np.exp(1) + cdef int Nphi = int(nr//4 * 4) + print("update_magnetic_origin 1") + + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + Hr[0][0][k] = updatecoeffsE[ID[1,0,0,k],0] * Hr[0][0][k] + 0.5 * updatecoeffsE[ID[1,0,0,k], 1] * (e**(I*m*pi* 3 / 4 * Nphi/nr) - e**(I*m*pi / 4 * Nphi/nr)) * Ez[1][0][k] + 0.5 * updatecoeffsE[ID[1,0,0,k], 3] * (- e**(I * m * pi * 3 * Nphi / nr) + e**(I * m * pi * Nphi / nr)) * Ephi[0][0][k+1] + 0.5 * updatecoeffsE[ID[1,0,0,k], 3] * (e**(I * m * pi * 3 * Nphi / nr) - e**(I * m * pi * Nphi / nr)) * Ephi[0][0][k] + Hphi[0][0][k] = Hphi[0][0][k] * updatecoeffsH[ID[4,0,0,k], 0] + (Ez[1][0][k] - Ez[0][0][k]) * updatecoeffsH[ID[4,0,0,k],1] - (Er[0][0][k+1] - Er[0][0][k]) * updatecoeffsH[ID[4,0,0,k], 3] + Hz[0][0][k] += Hz[0][0][k] * updatecoeffsH[ID[5,0,0,k], 0] + dt / mu * (Er[0][0][k] * I * m - Ephi[1][0][k]) / (0.5 * dr) + print("update_magnetic_origin 2") + + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) + +cpdef update_electric_origin( + int nr, + int nz, + int m, + int nthreads, + floattype_t dt, + floattype_t mu, + floattype_t dr, + floattype_t[:, ::1] updatecoeffsH, + floattype_t[:, ::1] updatecoeffsE, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np +): + + cdef Py_ssize_t j, k, taille_i_fields, taille_j_fields + cdef int materialEr, materialEphi, materialEz + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + + cdef floattype_t pi = np.pi + cdef floattype_t e = np.exp(1) + cdef int Nphi = int(nr//4 * 4) + cdef floattype_t m_coeff + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + print("update_electric_origin 1") + for k in prange(1, nz, nogil=True, schedule= 'static', num_threads= nthreads): + m_coeff = 4*updatecoeffsE[ID[2,0,0,k], 1] / Nphi + Er[0][0][k] = Er[0][0][k] * updatecoeffsE[ID[1,0,0,k], 0] + I * m / (0.5) * Hz[0][0][k] * updatecoeffsE[ID[1,0,0,k], 1] - (Hphi[0][0][k] - Hphi[0][0][k-1]) * updatecoeffsE[ID[1,0,0,k], 3] + with gil: + print("Passage 1 safe") + Ephi[0][0][k] = updatecoeffsE[ID[1,0,0,k], 0] * Ephi[0][0][k] + (Hr[0][0][k] - Hr[0][0][k-1]) * updatecoeffsE[ID[1,0,0,k], 3] - Hz[1][0][k] * e**(I * pi * Nphi / nr) * updatecoeffsE[ID[1,0,0,k], 1] + with gil: + print("Passage 2 safe") + Ez[0][0][k] *= updatecoeffsE[ID[2,0,0,k], 0] * Ez[0][0][k] + with gil: + print("Passage 3 safe") + for j in range(1, Nphi): + Ez[0][0][k] += m_coeff * Hphi[1][0][k+1] * e**(I * m * pi * j / Nphi ) + print("update_electric_origin 2") + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + + free_complex3D(Er,taille_i_fields, taille_j_fields) + free_complex3D(Ephi,taille_i_fields, taille_j_fields) + free_complex3D(Ez,taille_i_fields, taille_j_fields) + free_complex3D(Hr,taille_i_fields, taille_j_fields) + free_complex3D(Hphi,taille_i_fields, taille_j_fields) + free_complex3D(Hz,taille_i_fields, taille_j_fields) \ No newline at end of file diff --git a/gprMax/geometry_outputs.py b/gprMax/geometry_outputs.py index c950cbc94..1ffb11804 100644 --- a/gprMax/geometry_outputs.py +++ b/gprMax/geometry_outputs.py @@ -131,8 +131,8 @@ def write_vtk(self, G, pbar): if self.fileext == '.vti': # Create arrays and add numeric IDs for PML, sources and receivers # (0 is not set, 1 is PML, srcs and rxs numbered thereafter) - self.srcs_pml = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) - self.rxs = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int8) + self.srcs_pml = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int16) + self.rxs = np.zeros((G.nx + 1, G.ny + 1, G.nz + 1), dtype=np.int16) for pml in G.pmls: self.srcs_pml[pml.xs:pml.xf, pml.ys:pml.yf, pml.zs:pml.zf] = 1 for index, src in enumerate(G.hertziandipoles + G.magneticdipoles + G.voltagesources + G.transmissionlines): diff --git a/gprMax/geometry_outputs_ext.pyx b/gprMax/geometry_outputs_ext.pyx index b55c0a3d1..01994f797 100644 --- a/gprMax/geometry_outputs_ext.pyx +++ b/gprMax/geometry_outputs_ext.pyx @@ -91,8 +91,8 @@ cpdef void define_normal_geometry( int dy, int dz, np.uint32_t[:, :, :] solid, - np.int8_t[:, :, :] srcs_pml, - np.int8_t[:, :, :] rxs, + np.int16_t[:, :, :] srcs_pml, + np.int16_t[:, :, :] rxs, np.uint32_t[:] solid_geometry, np.int8_t[:] srcs_pml_geometry, np.int8_t[:] rxs_geometry, diff --git a/gprMax/geometry_primitives_ext.pyx b/gprMax/geometry_primitives_ext.pyx index c8549e33e..91624e9e9 100644 --- a/gprMax/geometry_primitives_ext.pyx +++ b/gprMax/geometry_primitives_ext.pyx @@ -688,6 +688,23 @@ cpdef void build_box( for k in range(zs, zf + 1): ID[5, i, j, k] = numIDz +cpdef void build_cylinder_cyl( + int r_width, + int z_min, + int z_max, + int numID, + int numIDr, + int numIDphi, + int numIDz, + bint averaging, + np.uint32_t[:, :, ::1] solid, + np.int8_t[:, :, :, ::1] rigidE, + np.int8_t[:, :, :, ::1] rigidH, + np.uint32_t[:, :, :, ::1] ID + ): + #With cylindrical, a box will take the shape of the cut of a cylinder which is 1 cell wide + build_box(0, r_width, 0, 1, z_min, z_max, numID, numIDr, numIDphi, numIDz, averaging, solid, rigidE, rigidH, ID) + cpdef void build_cylinder( float x1, @@ -869,7 +886,6 @@ cpdef void build_cylinder( if build: build_voxel(i, j, k, numID, numIDx, numIDy, numIDz, averaging, solid, rigidE, rigidH, ID) - cpdef void build_sphere( int xc, int yc, diff --git a/gprMax/grid.py b/gprMax/grid.py index 89fb9cfbf..803267a5f 100644 --- a/gprMax/grid.py +++ b/gprMax/grid.py @@ -30,12 +30,11 @@ from gprMax.constants import complextype from gprMax.exceptions import GeneralError from gprMax.materials import Material -from gprMax.pml import PML +from gprMax.pml import PML, PML_cyl from gprMax.utilities import fft_power from gprMax.utilities import human_size from gprMax.utilities import round_value - class Grid(object): """Generic grid/mesh.""" @@ -51,6 +50,15 @@ def __init__(self, grid): self.k_max = self.nz - 1 self.grid = grid + + self.dr_cyl = 1 + self.dz_cyl = 1 + self.nr_cyl = 1 + self.nz_cyl = 1 + self.i_max_cyl = self.nr_cyl - 1 + self.j_max_cyl = 1 + self.k_max_cyl = self.nz_cyl - 1 + def n_edges(self): i = self.nx j = self.ny @@ -76,7 +84,7 @@ def calculate_coord(self, coord, val): co = round_value(float(val) / getattr(self, 'd' + coord)) return co - +cylindrical = False class FDTDGrid(Grid): """ Holds attributes associated with the entire grid. A convenient @@ -153,6 +161,31 @@ def __init__(self): self.srcsteps = [0, 0, 0] self.rxsteps = [0, 0, 0] self.snapshots = [] + self.fluxes = [] + self.total_flux = None + self.scattering = False + self.empty_sim = True + self.scattering_geometrycmds = None + self.scatteringgeometry = None + self.box_fluxes_enumerate = [] + + #Adding the cylindrical coordinates if rotational symmetry + #The fields will be described as follows: f(M) = g(r,z)*exp(im*phi) + # with i**2 = -1 + + self.cylindrical = False + self.dr_cyl = 0 + self.dz_cyl = 0 + self.nr_cyl = 0 + self.nz_cyl = 0 + self.m_cyl = 0 + self.pmlthickness_cyl = OrderedDict((key, 10) for key in PML_cyl.boundaryIDs_cyl) + + #def initialise_surface(self, corners: list[np.ndarray], center = None, radius= None): + # if (center is None) and (radius is None): + # self.surface_flux.append(Fluxes.SurfaceFlux(corners)) + # else: + # self.surface_flux.append(Fluxes.SurfaceFlux(cylindrical=True, center= center, radius= radius)) def initialise_geometry_arrays(self): """ @@ -162,20 +195,38 @@ def initialise_geometry_arrays(self): Solid and ID arrays are initialised to free_space (one); rigid arrays to allow dielectric smoothing (zero). """ - self.solid = np.ones((self.nx, self.ny, self.nz), dtype=np.uint32) - self.rigidE = np.zeros((12, self.nx, self.ny, self.nz), dtype=np.int8) - self.rigidH = np.zeros((6, self.nx, self.ny, self.nz), dtype=np.int8) - self.ID = np.ones((6, self.nx + 1, self.ny + 1, self.nz + 1), dtype=np.uint32) - self.IDlookup = {'Ex': 0, 'Ey': 1, 'Ez': 2, 'Hx': 3, 'Hy': 4, 'Hz': 5} + if not self.cylindrical: + self.solid = np.ones((self.nx, self.ny, self.nz), dtype=np.uint32) + self.rigidE = np.zeros((12, self.nx, self.ny, self.nz), dtype=np.int8) + self.rigidH = np.zeros((6, self.nx, self.ny, self.nz), dtype=np.int8) + self.ID = np.ones((6, self.nx + 1, self.ny + 1, self.nz + 1), dtype=np.uint32) + self.IDlookup = {'Ex': 0, 'Ey': 1, 'Ez': 2, 'Hx': 3, 'Hy': 4, 'Hz': 5} + else: + #We can deduce the phi dependency from just the values of the fields at one point + self.solid = np.ones((self.nr_cyl, 1, self.nz_cyl), dtype=np.uint32) + self.rigidE = np.zeros((12, self.nr_cyl, 1, self.nz_cyl), dtype=np.int8) + self.rigidH = np.zeros((6, self.nr_cyl, 1, self.nz_cyl), dtype=np.int8) + self.ID = np.ones((6, self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=np.uint32) + self.IDlookup = {'Er': 0, 'Ephi': 1, 'Ez': 2, 'Hr': 3, 'Hphi': 4, 'Hz': 5} + def initialise_field_arrays(self): """Initialise arrays for the electric and magnetic field components.""" - self.Ex = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) - self.Ey = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) - self.Ez = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) - self.Hx = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) - self.Hy = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) - self.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) + if not self.cylindrical: + self.Ex = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) + self.Ey = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) + self.Ez = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) + self.Hx = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) + self.Hy = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) + self.Hz = np.zeros((self.nx + 1, self.ny + 1, self.nz + 1), dtype=floattype) + + else: + self.Er_cyl = np.zeros((self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=np.complex128) + self.Ephi_cyl = np.zeros((self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=np.complex128) + self.Ez_cyl = np.zeros((self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=np.complex128) + self.Hr_cyl = np.zeros((self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=np.complex128) + self.Hphi_cyl = np.zeros((self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=np.complex128) + self.Hz_cyl = np.zeros((self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=np.complex128) def initialise_std_update_coeff_arrays(self): """Initialise arrays for storing update coefficients.""" @@ -184,53 +235,75 @@ def initialise_std_update_coeff_arrays(self): def initialise_dispersive_arrays(self): """Initialise arrays for storing coefficients when there are dispersive materials present.""" - self.Tx = np.zeros((Material.maxpoles, self.nx + 1, self.ny + 1, self.nz + 1), dtype=complextype) - self.Ty = np.zeros((Material.maxpoles, self.nx + 1, self.ny + 1, self.nz + 1), dtype=complextype) - self.Tz = np.zeros((Material.maxpoles, self.nx + 1, self.ny + 1, self.nz + 1), dtype=complextype) + if not self.cylindrical: + self.Tx = np.zeros((Material.maxpoles, self.nx + 1, self.ny + 1, self.nz + 1), dtype=complextype) + self.Ty = np.zeros((Material.maxpoles, self.nx + 1, self.ny + 1, self.nz + 1), dtype=complextype) + self.Tz = np.zeros((Material.maxpoles, self.nx + 1, self.ny + 1, self.nz + 1), dtype=complextype) + else: + #There is a symmetry + self.Tr_cyl = np.zeros((Material.maxpoles, self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=complextype) + self.T_phi = np.zeros((Material.maxpoles, self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=complextype) + self.Tz_cyl = np.zeros((Material.maxpoles, self.nr_cyl + 1, 1, self.nz_cyl + 1), dtype=complextype) self.updatecoeffsdispersive = np.zeros((len(self.materials), 3 * Material.maxpoles), dtype=complextype) def memory_estimate_basic(self): """Estimate the amount of memory (RAM) required to run a model.""" - - stdoverhead = 50e6 - - solidarray = self.nx * self.ny * self.nz * np.dtype(np.uint32).itemsize - - # 12 x rigidE array components + 6 x rigidH array components - rigidarrays = (12 + 6) * self.nx * self.ny * self.nz * np.dtype(np.int8).itemsize - - # 6 x field arrays + 6 x ID arrays - fieldarrays = (6 + 6) * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(floattype).itemsize - - # PML arrays - pmlarrays = 0 - for (k, v) in self.pmlthickness.items(): - if v > 0: - if 'x' in k: - pmlarrays += ((v + 1) * self.ny * (self.nz + 1)) - pmlarrays += ((v + 1) * (self.ny + 1) * self.nz) - pmlarrays += (v * self.ny * (self.nz + 1)) - pmlarrays += (v * (self.ny + 1) * self.nz) - elif 'y' in k: - pmlarrays += (self.nx * (v + 1) * (self.nz + 1)) - pmlarrays += ((self.nx + 1) * (v + 1) * self.nz) - pmlarrays += ((self.nx + 1) * v * self.nz) - pmlarrays += (self.nx * v * (self.nz + 1)) - elif 'z' in k: - pmlarrays += (self.nx * (self.ny + 1) * (v + 1)) - pmlarrays += ((self.nx + 1) * self.ny * (v + 1)) - pmlarrays += ((self.nx + 1) * self.ny * v) - pmlarrays += (self.nx * (self.ny + 1) * v) + ### Ajouter les coordonnées cylindriques + if self.cylindrical: + stdoverhead = 50e6 + + solidarray = self.nx * self.ny * self.nz * np.dtype(np.uint32).itemsize + + # 12 x rigidE array components + 6 x rigidH array components + rigidarrays = (12 + 6) * self.nx * self.ny * self.nz * np.dtype(np.int8).itemsize + + # 6 x field arrays + 6 x ID arrays + fieldarrays = (6 + 6) * (self.nx + 1) * (self.ny + 1) * (self.nz + 1) * np.dtype(floattype).itemsize + + # PML arrays + pmlarrays = 0 + for (k, v) in self.pmlthickness.items(): + if v > 0: + if 'x' in k: + pmlarrays += ((v + 1) * self.ny * (self.nz + 1)) + pmlarrays += ((v + 1) * (self.ny + 1) * self.nz) + pmlarrays += (v * self.ny * (self.nz + 1)) + pmlarrays += (v * (self.ny + 1) * self.nz) + elif 'y' in k: + pmlarrays += (self.nx * (v + 1) * (self.nz + 1)) + pmlarrays += ((self.nx + 1) * (v + 1) * self.nz) + pmlarrays += ((self.nx + 1) * v * self.nz) + pmlarrays += (self.nx * v * (self.nz + 1)) + elif 'z' in k: + pmlarrays += (self.nx * (self.ny + 1) * (v + 1)) + pmlarrays += ((self.nx + 1) * self.ny * (v + 1)) + pmlarrays += ((self.nx + 1) * self.ny * v) + pmlarrays += (self.nx * (self.ny + 1) * v) + + else: + stdoverhead = 50e6 + solidarray = self.nr_cyl * 1 * self.nz_cyl * np.dtype(np.uint32).itemsize + rigidarrays = (12 + 6) * self.nr_cyl * 1 * self.nz_cyl * np.dtype(np.int8).itemsize + fieldarrays = (6 + 6) * (self.nr_cyl + 1) * (1 + 1) * (self.nz_cyl + 1) * np.dtype(floattype).itemsize + # PML arrays + pmlarrays = 0 + for (k, v) in self.pmlthickness_cyl.items(): + if v > 0: + if 'r' in k: + pmlarrays += (v+1) * self.nz_cyl #Only a plan is being simulated + elif 'z' in k: + pmlarrays += (v+1) * self.nr_cyl self.memoryusage = int(stdoverhead + fieldarrays + solidarray + rigidarrays + pmlarrays) + def memory_check(self, snapsmemsize=0): """Check if the required amount of memory (RAM) is available on the host and GPU if specified. Args: snapsmemsize (int): amount of memory (bytes) required to store all requested snapshots """ - + ### Ajouter les coordonnées cylindriques # Check if model can be built and/or run on host if self.memoryusage > self.hostinfo['ram']: raise GeneralError('Memory (RAM) required ~{} exceeds {} detected!\n'.format(human_size(self.memoryusage), human_size(self.hostinfo['ram'], a_kilobyte_is_1024_bytes=True))) @@ -243,7 +316,8 @@ def memory_check(self, snapsmemsize=0): # If the required memory without the snapshots will fit on the GPU then transfer and store snaphots on host if snapsmemsize != 0 and self.memoryusage - snapsmemsize < self.gpu.totalmem: self.snapsgpu2cpu = True - +###################################################### +#GPU: add cylindrical def gpu_set_blocks_per_grid(self): """Set the blocks per grid size used for updating the electric and magnetic field arrays on a GPU.""" self.bpg = (int(np.ceil(((self.nx + 1) * (self.ny + 1) * (self.nz + 1)) / self.tpb[0])), 1, 1) @@ -271,7 +345,7 @@ def gpu_initialise_dispersive_arrays(self): self.Tz_gpu = gpuarray.to_gpu(self.Tz) self.updatecoeffsdispersive_gpu = gpuarray.to_gpu(self.updatecoeffsdispersive) - +######################################################### def dispersion_analysis(G): """ Analysis of numerical dispersion (Taflove et al, 2005, p112) - @@ -283,7 +357,7 @@ def dispersion_analysis(G): Returns: results (dict): Results from dispersion analysis """ - + ###Add cylindrical # Physical phase velocity error (percentage); grid sampling density; # material with maximum permittivity; maximum significant frequency; error message results = {'deltavp': False, 'N': False, 'material': False, 'maxfreq': [], 'error': ''} @@ -313,7 +387,7 @@ def dispersion_analysis(G): waveformvalues = np.zeros(G.iterations) for iteration in range(G.iterations): - waveformvalues[iteration] = waveform.calculate_value(iteration * G.dt, G.dt) + waveformvalues[iteration] = waveform.calculate_value(iteration * G.dt, G.dt, G.cylindrical) # Ensure source waveform is not being overly truncated before attempting any FFT if np.abs(waveformvalues[-1]) < np.abs(np.amax(waveformvalues)) / 100: @@ -399,17 +473,24 @@ def get_other_directions(direction): """Return the two other directions from x, y, z given a single direction Args: - direction (str): Component x, y or z + direction (str): Component x, y or z if cartesian, r, phi, z otherwise Returns: - (tuple): Two directions from x, y, z + (tuple): Two directions from x, y, z if cartesian, r, phi, z otherwise """ - - directions = {'x': ('y', 'z'), 'y': ('x', 'z'), 'z': ('x', 'y')} + # if not self.cylindrical: + directions = {'x': ('y', 'z'), + 'y': ('x', 'z'), + 'z': ('x', 'y'),} + # else: + # directions = {'r': ('phi', 'z'), + # 'phi': ('r', 'z'), + # 'z': ('r', 'phi')} return directions[direction] +###Ajouter les cylindriques en-dessous def Ix(x, y, z, Hx, Hy, Hz, G): """Calculates the x-component of current at a grid position. diff --git a/gprMax/input_cmd_funcs.py b/gprMax/input_cmd_funcs.py index 6cec28619..616d1aa87 100644 --- a/gprMax/input_cmd_funcs.py +++ b/gprMax/input_cmd_funcs.py @@ -36,7 +36,8 @@ """ Coordinate_tuple = namedtuple('Coordinate', ['x', 'y', 'z']) - +Coordinate_cyl_tuple = namedtuple('Coordinate_cyl', ['r', 'z']) +Coordinate_z_tuple = namedtuple('Coordinate_z', ['z']) class Coordinate(Coordinate_tuple): """Subclass of a namedtuple where __str__ outputs 'x y z'""" @@ -44,7 +45,17 @@ class Coordinate(Coordinate_tuple): def __str__(self): return '{:g} {:g} {:g}'.format(self.x, self.y, self.z) +class Coordinate_cyl(Coordinate_cyl_tuple): + """Subclass of a namedtuple where __str__ outputs 'r z'""" + + def __str__(self): + return '{:g} {:g}'.format(self.r, self.z) + +class Coordinate_z(Coordinate_z_tuple): + """Subclass of a namedtuple where __str__ outputs 'z'""" + def __str__(self): + return '{:g}'.format(self.z) def command(cmd, *parameters): """ Helper function. Prints the gprMax #: . None is ignored @@ -154,6 +165,63 @@ def rotate90_plate(xs, ys, xf, yf, rotate90origin): return xs, ys, xf, yf +def cylindrical(cyl= False): + """Prints the gprMax #cylindrical command + + Args: + cyl: True if there is cylindrical symmetry, False otherwise + + Returns: + True if cylindrical is mentionned, False otherwise + """ + + command('cylindrical',cyl) + + return cyl + +def domain_cyl(r, z): + """Prints the gprMax #domain command. + + Args: + r, z (float): Extent of the domain in the r and z directions. The phi direction is 2*pi. + + Returns: + domain_cyl (Coordinate_cyl): Namedtuple of the extent of the domain. + """ + domain_cyl = Coordinate_cyl(r, z) + command('domain_cyl',domain_cyl) + + return domain_cyl + +def dr_dz(r, z): + """Prints the gprMax #dr_dz command. + + Args: + r, z (float): Spatial resolution in the r, and z directions. + + Returns: + dr_dz (float): Tuple of the spatial resolutions. + """ + + dr_dz = Coordinate_cyl(r, z) + command('dr_dz',dr_dz) + + return dr_dz + +def m(m_phi): + """Prints the gprMax #m command. + + Args: + m_phi (int): integer set for the cylindrical symmetry: e^(im*phi) + + Returns: + m (int): the aforementioned integer""" + + m = m_phi + command('m',m) + + return m + def domain(x, y, z): """Prints the gprMax #domain command. @@ -367,6 +435,26 @@ def box(xs, ys, zs, xf, yf, zf, material, averaging='', rotate90origin=()): return s, f +def cylinder_cyl(r_width, z_min, z_max, material, averaging=''): + """ + + Args: + r_width (float): radius of the cylinder + z_min (float): lower bound of the cylinder + z_max (float): higher bound of the cylinder + material (str): material + averaging (str): Turn averaging on or off + + Returns: + s, f (tuple): 2 tuples for the start and finish coordinates + """ + + s = Coordinate_z(z_min) + f = Coordinate_cyl(r_width, z_max) + command('cylinder_cyl', s, f, material, averaging) + + return s, f + def sphere(x, y, z, radius, material, averaging=''): """Prints the gprMax #sphere command. @@ -471,7 +559,7 @@ def waveform(shape, amplitude, frequency, identifier): def hertzian_dipole(polarisation, f1, f2, f3, identifier, - t0=None, t_remove=None, dxdy=None, rotate90origin=()): + t0=None, t_remove=None, dxdy=None, rotate90origin=(), cylindrical= False): """Prints the #hertzian_dipole: polarisation, f1, f2, f3, identifier, [t0, t_remove] Args: @@ -500,7 +588,10 @@ def hertzian_dipole(polarisation, f1, f2, f3, identifier, f1, f2, xf, yf = rotate90_edge(f1, f2, xf, yf, polarisation, rotate90origin) polarisation = newpolarisation - c = Coordinate(f1, f2, f3) + if not cylindrical: + c = Coordinate(f1, f2, f3) + else: + c = Coordinate_cyl(f1,f2) # since command ignores None, this is safe: command('hertzian_dipole', polarisation, str(c), identifier, t0, t_remove) diff --git a/gprMax/input_cmds_file.py b/gprMax/input_cmds_file.py index 98a95eb47..288c1ee73 100644 --- a/gprMax/input_cmds_file.py +++ b/gprMax/input_cmds_file.py @@ -191,21 +191,33 @@ def check_cmd_names(processedlines, checkessential=True): # Essential commands neccessary to run a gprMax model essentialcmds = ['#domain', '#dx_dy_dz', '#time_window'] + #Essential commands in case of cylindrical + essentialcmds_cyl = ['#domain_cyl', '#dr_dz', '#time_window', '#m'] + # Commands that there should only be one instance of in a model - singlecmds = dict.fromkeys(['#domain', '#dx_dy_dz', '#time_window', '#title', '#messages', '#num_threads', '#time_step_stability_factor', '#pml_formulation', '#pml_cells', '#excitation_file', '#src_steps', '#rx_steps', '#taguchi', '#end_taguchi', '#output_dir'], None) + singlecmds = dict.fromkeys(['#domain', '#dx_dy_dz', '#time_window', '#title', '#messages', '#num_threads', '#time_step_stability_factor', '#pml_formulation', '#pml_cells', '#excitation_file', '#src_steps', '#rx_steps', '#taguchi', '#end_taguchi', '#output_dir', + '#domain_cyl', '#dr_dz', '#m', '#cylindrical'], None) # Commands that there can be multiple instances of in a model - these will be lists within the dictionary - multiplecmds = {key: [] for key in ['#geometry_view', '#geometry_objects_write', '#material', '#soil_peplinski', '#add_dispersion_debye', '#add_dispersion_lorentz', '#add_dispersion_drude', '#waveform', '#voltage_source', '#hertzian_dipole', '#magnetic_dipole', '#transmission_line', '#rx', '#rx_array', '#snapshot', '#pml_cfs', '#include_file']} + multiplecmds = {key: [] for key in ['#geometry_view', '#geometry_objects_write', '#material', '#soil_peplinski', '#add_dispersion_debye', '#add_dispersion_lorentz', '#add_dispersion_drude', '#waveform', '#voltage_source', '#hertzian_dipole', '#magnetic_dipole', '#transmission_line', '#rx', '#rx_array', '#snapshot', '#pml_cfs', '#include_file' + , '#flux', '#box_flux', '#plane_voltage_source']} + # Geometry object building commands that there can be multiple instances # of in a model - these will be lists within the dictionary - geometrycmds = ['#geometry_objects_read', '#edge', '#plate', '#triangle', '#box', '#sphere', '#cylinder', '#cylindrical_sector', '#fractal_box', '#add_surface_roughness', '#add_surface_water', '#add_grass'] + geometrycmds = ['#geometry_objects_read', '#edge', '#plate', '#triangle', '#box', '#sphere', '#cylinder', '#cylindrical_sector', '#fractal_box', '#add_surface_roughness', '#add_surface_water', '#add_grass', + '#cylindrical', '#cylinder_cyl'] + + scatteringcmds = ["#scattering", "#scattering_end"] + + scattering_geometrycmds = {key: [] for key in geometrycmds} # List to store all geometry object commands in order from input file geometry = [] - + scatteringgeometry = [] # Check if command names are valid, if essential commands are present, and # add command parameters to appropriate dictionary values or lists countessentialcmds = 0 + countessentialcmds_cyl = 0 lindex = 0 while(lindex < len(processedlines)): cmd = processedlines[lindex].split(':') @@ -219,14 +231,30 @@ def check_cmd_names(processedlines, checkessential=True): raise CmdInputError('There must be a space between the command name and parameters in ' + processedlines[lindex]) # Check if command name is valid - if cmdname not in essentialcmds and cmdname not in singlecmds and cmdname not in multiplecmds and cmdname not in geometrycmds: + if cmdname not in essentialcmds and cmdname not in singlecmds and cmdname not in multiplecmds and cmdname not in geometrycmds and cmdname not in scatteringcmds: raise CmdInputError('Your input file contains an invalid command: ' + cmdname) # Count essential commands if cmdname in essentialcmds: countessentialcmds += 1 + if cmdname in essentialcmds_cyl: + countessentialcmds_cyl += 1 + # Assign command parameters as values to dictionary keys + if cmdname == scatteringcmds[0]: + while cmdname != scatteringcmds[1]: + lindex += 1 + if lindex >= len(processedlines): raise CmdInputError("#scattering_end: is missing in the input file") + cmd = processedlines[lindex].split(':') + cmdname = cmd[0] + cmdparams = cmd[1] + if cmdname not in scatteringcmds and cmdname not in geometrycmds: + raise CmdInputError("When between #scattering: and #scattering_end:, only geometry commands are accepted. Fluxes must be defined outside.") + if cmdname in geometrycmds: + scattering_geometrycmds[cmdname].append(cmdparams.strip(' \t\n')) + scatteringgeometry.append(processedlines[lindex].strip(' \t\n')) + if cmdname in singlecmds: if singlecmds[cmdname] is None: singlecmds[cmdname] = cmd[1].strip(' \t\n') @@ -242,7 +270,9 @@ def check_cmd_names(processedlines, checkessential=True): lindex += 1 if checkessential: - if (countessentialcmds < len(essentialcmds)): + if (countessentialcmds < len(essentialcmds) and countessentialcmds_cyl == 0): raise CmdInputError('Your input file is missing essential commands required to run a model. Essential commands are: ' + ', '.join(essentialcmds)) + elif (countessentialcmds < len(essentialcmds_cyl) and countessentialcmds == 0): + raise CmdInputError('Your input file is missing essential commands required to run a model with cylindrical symmetry. Essential commands are: ' + ', '.join(essentialcmds_cyl)) - return singlecmds, multiplecmds, geometry + return singlecmds, multiplecmds, scattering_geometrycmds, geometry, scatteringgeometry diff --git a/gprMax/input_cmds_geometry.py b/gprMax/input_cmds_geometry.py index f38f8d9e8..592f30f28 100644 --- a/gprMax/input_cmds_geometry.py +++ b/gprMax/input_cmds_geometry.py @@ -26,7 +26,7 @@ from gprMax.constants import floattype from gprMax.input_cmds_file import check_cmd_names from gprMax.input_cmds_multiuse import process_multicmds -from gprMax.exceptions import CmdInputError +from gprMax.exceptions import CmdInputError, GeneralError from gprMax.fractals import FractalSurface from gprMax.fractals import FractalVolume from gprMax.fractals import Grass @@ -43,6 +43,7 @@ from gprMax.geometry_primitives_ext import build_sphere from gprMax.geometry_primitives_ext import build_voxels_from_array from gprMax.geometry_primitives_ext import build_voxels_from_array_mask +from gprMax.geometry_primitives_ext import build_cylinder_cyl from gprMax.materials import Material from gprMax.utilities import round_value from gprMax.utilities import get_terminal_width @@ -69,6 +70,8 @@ def process_geometrycmds(geometry, G): tmp = object.split() if tmp[0] == '#geometry_objects_read:': + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) != 6: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly five parameters') @@ -146,6 +149,8 @@ def process_geometrycmds(geometry, G): tqdm.write('Geometry objects from file (voxels only) {} inserted at {:g}m, {:g}m, {:g}m, with corresponding materials file {}.'.format(geofile, xs * G.dx, ys * G.dy, zs * G.dz, matfile)) elif tmp[0] == '#edge:': + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) != 8: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires exactly seven parameters') @@ -205,6 +210,8 @@ def process_geometrycmds(geometry, G): tqdm.write('Edge from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, tmp[7])) elif tmp[0] == '#plate:': + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) < 8: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least seven parameters') @@ -313,6 +320,8 @@ def process_geometrycmds(geometry, G): tqdm.write('Plate from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material(s) {} created.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, ', '.join(materialsrequested))) elif tmp[0] == '#triangle:': + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) < 12: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eleven parameters') @@ -402,7 +411,7 @@ def process_geometrycmds(geometry, G): m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) + m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) # Append the new material object to the materials list G.materials.append(m) @@ -432,7 +441,96 @@ def process_geometrycmds(geometry, G): else: tqdm.write('Triangle with coordinates {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m, {:g}m {:g}m {:g}m of material(s) {} created.'.format(x1, y1, z1, x2, y2, z2, x3, y3, z3, ', '.join(materialsrequested))) + elif tmp[0] == '#cylinder_cyl:': + if not G.cylindrical: + raise GeneralError(tmp[0], " implemented only in cylindrical") + if len(tmp) < 5: + raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least four parameters') + + # Isotropic case + elif len(tmp) == 5: + materialsrequested = [tmp[4]] + average_cyl_cyl = G.averagevolumeobjects + + # Isotropic case with no user specified averaging + elif len(tmp) == 6: + materialsrequested = [tmp[4]] + if tmp[5].lower() == 'y': + average_cyl_cyl = True + elif tmp[5].lower() == 'n': + average_cyl_cyl = False + else: + raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires averaging to be either y or n') + + #Uniaxial anisotropic case + elif len(tmp) == 7: + materialsrequested = tmp[4:] + + else: + raise CmdInputError("'" + ' '.join(tmp) + "'" + ' too many parameters have been given') + + r_width = round_value(float(tmp[1]) / G.dr_cyl) + z_min = round_value(float(tmp[2]) / G.dz_cyl) + z_max = round_value(float(tmp[3]) / G.dz_cyl) + + if r_width < 0 or r_width > G.nr_cyl: + raise CmdInputError("The r-direction should be greater than 0 and included in the domain") + if z_min < 0 or z_min > G.nz_cyl: + raise CmdInputError("The lower z-coordinate should be greater than 0 and included in the domain") + if z_max < 0 or z_max > G.nz_cyl: + raise CmdInputError("The higher z-coordinate should be greater than 0 and included in the domain") + + # Look up requested materials in existing list of material instances + print("Materials requested: ", materialsrequested) + for x in materialsrequested: + print(x) + materials = [y for x in materialsrequested for y in G.materials if y.ID == x] + print("Materials found: ", materials[0].er," ", materials[0].se," ", materials[0].mr," ", materials[0].sm) + + if len(materials) != len(materialsrequested): + notfound = [x for x in materialsrequested if x not in materials] + raise CmdInputError("'" + ' '.join(tmp) + "'" + ' material(s) {} do not exist'.format(notfound)) + + # Isotropic case + if len(materials) == 1: + averaging = materials[0].averagable and average_cyl_cyl + numID = numIDr = numIDphi= numIDz = materials[0].numID + print(materials[0].numID) + + # Uniaxial anisotropic case + elif len(materials) == 3: + averaging = False + numIDr = materials[0].numID + numIDphi = materials[1].numID + numIDz = materials[2].numID + requiredID = materials[0].ID + '+' + materials[1].ID + '+' + materials[2].ID + averagedmaterial = [x for x in G.materials if x.ID == requiredID] + if averagedmaterial: + numID = averagedmaterial.numID + else: + numID = len(G.materials) + m = Material(numID, requiredID) + m.type = 'dielectric-smoothed' + # Create dielectric-smoothed constituents for material + m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) + m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) + m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) + m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) + + # Append the new material object to the materials list + G.materials.append(m) + build_cylinder_cyl(r_width, z_min, z_max, numID, numIDr, numIDphi, numIDz, averaging, G.solid, G.rigidE, G.rigidH, G.ID) + + if G.messages: + if averaging: + dielectricsmoothing = 'on' + else: + dielectricsmoothing = 'off' + tqdm.write('Cylinder cut from r = 0m, z = {:g}m, to r = {:g}m, z = {:g}m of material(s) {} created, dielectric smoothing is {}.'.format(z_min * G.dz_cyl, r_width * G.dr_cyl, z_max * G.dz_cyl, ', '.join(materialsrequested), dielectricsmoothing)) + elif tmp[0] == '#box:': + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) < 8: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least seven parameters') @@ -510,7 +608,7 @@ def process_geometrycmds(geometry, G): m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) + m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) # Append the new material object to the materials list G.materials.append(m) @@ -525,6 +623,8 @@ def process_geometrycmds(geometry, G): tqdm.write('Box from {:g}m, {:g}m, {:g}m, to {:g}m, {:g}m, {:g}m of material(s) {} created, dielectric smoothing is {}.'.format(xs * G.dx, ys * G.dy, zs * G.dz, xf * G.dx, yf * G.dy, zf * G.dz, ', '.join(materialsrequested), dielectricsmoothing)) elif tmp[0] == '#cylinder:': + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) < 9: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least eight parameters') @@ -591,7 +691,7 @@ def process_geometrycmds(geometry, G): m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) + m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) # Append the new material object to the materials list G.materials.append(m) @@ -606,6 +706,8 @@ def process_geometrycmds(geometry, G): tqdm.write('Cylinder with face centres {:g}m, {:g}m, {:g}m and {:g}m, {:g}m, {:g}m, with radius {:g}m, of material(s) {} created, dielectric smoothing is {}.'.format(x1, y1, z1, x2, y2, z2, r, ', '.join(materialsrequested), dielectricsmoothing)) elif tmp[0] == '#cylindrical_sector:': + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) < 10: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least nine parameters') @@ -681,7 +783,7 @@ def process_geometrycmds(geometry, G): m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) + m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) # Append the new material object to the materials list G.materials.append(m) @@ -730,6 +832,9 @@ def process_geometrycmds(geometry, G): tqdm.write('Cylindrical sector with centre {:g}m, {:g}m, radius {:g}m, starting angle {:.1f} degrees, sector angle {:.1f} degrees, of material(s) {} created.'.format(ctr1, ctr2, r, (sectorstartangle / (2 * np.pi)) * 360, (sectorangle / (2 * np.pi)) * 360, ', '.join(materialsrequested))) elif tmp[0] == '#sphere:': + print("Sphere detected") + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) < 6: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least five parameters') @@ -791,13 +896,13 @@ def process_geometrycmds(geometry, G): m.er = np.mean((materials[0].er, materials[1].er, materials[2].er), axis=0) m.se = np.mean((materials[0].se, materials[1].se, materials[2].se), axis=0) m.mr = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) - m.sm = np.mean((materials[0].mr, materials[1].mr, materials[2].mr), axis=0) + m.sm = np.mean((materials[0].sm, materials[1].sm, materials[2].sm), axis=0) # Append the new material object to the materials list G.materials.append(m) build_sphere(xc, yc, zc, r, G.dx, G.dy, G.dz, numID, numIDx, numIDy, numIDz, averaging, G.solid, G.rigidE, G.rigidH, G.ID) - + print("Sphere should have been built !") if G.messages: if averaging: dielectricsmoothing = 'on' @@ -808,7 +913,8 @@ def process_geometrycmds(geometry, G): elif tmp[0] == '#fractal_box:': # Default is no dielectric smoothing for a fractal box averagefractalbox = False - + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if len(tmp) < 14: raise CmdInputError("'" + ' '.join(tmp) + "'" + ' requires at least thirteen parameters') elif len(tmp) == 14: @@ -1311,6 +1417,8 @@ def process_geometrycmds(geometry, G): # Apply any rough surfaces and add any surface water to the 3D mask array for surface in volume.fractalsurfaces: + if G.cylindrical: + raise GeneralError(tmp[0], " not yet implemented in cylindrical") if surface.surfaceID == 'xminus': for i in range(surface.fractalrange[0], surface.fractalrange[1]): for j in range(surface.ys, surface.yf): diff --git a/gprMax/input_cmds_multiuse.py b/gprMax/input_cmds_multiuse.py index 96a3321df..004f0e5dd 100644 --- a/gprMax/input_cmds_multiuse.py +++ b/gprMax/input_cmds_multiuse.py @@ -25,7 +25,7 @@ from gprMax.constants import z0 from gprMax.constants import floattype -from gprMax.exceptions import CmdInputError +from gprMax.exceptions import CmdInputError, GeneralError from gprMax.geometry_outputs import GeometryView from gprMax.geometry_outputs import GeometryObjects from gprMax.materials import Material @@ -60,6 +60,14 @@ def check_coordinates(x, y, z, name=''): s = "'{}: {} ' {} {}-coordinate is not within the model domain".format(cmdname, ' '.join(tmp), name, err.args[0]) raise CmdInputError(s) + def check_coordinates_cylindrical(r, z, name= ''): + try: + G.within_bounds(r_cyl=r, z_cyl=z) + except ValueError as err: + s = "'{}: {} ' {} {}-coordinate is not within the model domain".format(cmdname, ' '.join(tmp), name, + err.args[0]) + raise CmdInputError(s) + # Waveform definitions cmdname = '#waveform' if multicmds[cmdname] is not None: @@ -90,48 +98,83 @@ def check_coordinates(x, y, z, name=''): if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: tmp = cmdinstance.split() - if len(tmp) < 6: - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') - # Check polarity & position parameters - polarisation = tmp[0].lower() - if polarisation not in ('x', 'y', 'z'): - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') - if '2D TMx' in G.mode and (polarisation == 'y' or polarisation == 'z'): - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x in 2D TMx mode') - elif '2D TMy' in G.mode and (polarisation == 'x' or polarisation == 'z'): - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be y in 2D TMy mode') - elif '2D TMz' in G.mode and (polarisation == 'x' or polarisation == 'y'): - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be z in 2D TMz mode') - - xcoord = G.calculate_coord('x', tmp[1]) - ycoord = G.calculate_coord('y', tmp[2]) - zcoord = G.calculate_coord('z', tmp[3]) - resistance = float(tmp[4]) - - check_coordinates(xcoord, ycoord, zcoord) - if xcoord < G.pmlthickness['x0'] or xcoord > G.nx - G.pmlthickness['xmax'] or ycoord < G.pmlthickness['y0'] or ycoord > G.ny - G.pmlthickness['ymax'] or zcoord < G.pmlthickness['z0'] or zcoord > G.nz - G.pmlthickness['zmax']: - print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) - if resistance < 0: - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a source resistance of zero or greater') - - # Check if there is a waveformID in the waveforms list - if not any(x.ID == tmp[5] for x in G.waveforms): - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[5])) + + if not G.cylindrical: + polarisation = tmp[0].lower() + if polarisation not in ('x', 'y', 'z'): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x, y, or z') + if '2D TMx' in G.mode and (polarisation == 'y' or polarisation == 'z'): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be x in 2D TMx mode') + elif '2D TMy' in G.mode and (polarisation == 'x' or polarisation == 'z'): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be y in 2D TMy mode') + elif '2D TMz' in G.mode and (polarisation == 'x' or polarisation == 'y'): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be z in 2D TMz mode') + + if not G.cylindrical: + if len(tmp) < 6: + raise CmdInputError( + "'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') + resistance = float(tmp[4]) + if resistance < 0: + raise CmdInputError( + "'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a source resistance of zero or greater') + # Check if there is a waveformID in the waveforms list + if not any(x.ID == tmp[5] for x in G.waveforms): + raise CmdInputError("'" + cmdname + ': ' + ' '.join( + tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[5])) + + xcoord = G.calculate_coord('x', tmp[1]) + ycoord = G.calculate_coord('y', tmp[2]) + zcoord = G.calculate_coord('z', tmp[3]) + + check_coordinates(xcoord, ycoord, zcoord) + if xcoord < G.pmlthickness['x0'] or xcoord > G.nx - G.pmlthickness['xmax'] or ycoord < G.pmlthickness['y0'] or ycoord > G.ny - G.pmlthickness['ymax'] or zcoord < G.pmlthickness['z0'] or zcoord > G.nz - G.pmlthickness['zmax']: + print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) + + v = VoltageSource() + v.polarisation = polarisation + v.xcoord = xcoord + v.ycoord = ycoord + v.zcoord = zcoord + v.ID = v.__class__.__name__ + '(' + str(v.xcoord) + ',' + str(v.ycoord) + ',' + str(v.zcoord) + ')' + v.waveformID = tmp[5] + else: + if len(tmp) < 4: + raise CmdInputError( + "'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least four parameters') + rcoord_cyl = G.calculate_coord('r_cyl', tmp[0]) + zcoord_cyl = G.calculate_coord('z_cyl', tmp[1]) + resistance = float(tmp[2]) + if resistance < 0: + raise CmdInputError( + "'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a source resistance of zero or greater') + # Check if there is a waveformID in the waveforms list + if not any(x.ID == tmp[3] for x in G.waveforms): + raise CmdInputError("'" + cmdname + ': ' + ' '.join( + tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[5])) + + check_coordinates_cylindrical(rcoord_cyl, zcoord_cyl) + #To have a cylindrical symmetry, the source must be at r=0 + z_thickness = G.pmlthickness_cyl['z'] + if zcoord_cyl > G.nz_cyl - z_thickness or zcoord_cyl < z_thickness: + print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) + v = VoltageSource() + v.polarisation = 'z' #I went with this, so no circular Ephi or Er sources :) + v.zcoord_cyl = zcoord_cyl + v.ID = v.__class__.__name__ + '(' + '0' + ',' + str(v.zcoord_cyl) + ')' + v.waveformID = tmp[3] - v = VoltageSource() - v.polarisation = polarisation - v.xcoord = xcoord - v.ycoord = ycoord - v.zcoord = zcoord - v.ID = v.__class__.__name__ + '(' + str(v.xcoord) + ',' + str(v.ycoord) + ',' + str(v.zcoord) + ')' v.resistance = resistance - v.waveformID = tmp[5] - if len(tmp) > 6: + if (len(tmp) > 6 and not G.cylindrical) or (len(tmp) > 4 and G.cylindrical): # Check source start & source remove time parameters - start = float(tmp[6]) - stop = float(tmp[7]) + if G.cylindrical: + start = float(tmp[4]) + stop = float(tmp[5]) + else: + start = float(tmp[6]) + stop = float(tmp[7]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: @@ -152,7 +195,10 @@ def check_coordinates(x, y, z, name=''): v.calculate_waveform_values(G) if G.messages: - print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * G.dx, v.ycoord * G.dy, v.zcoord * G.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID)) + if not G.cylindrical: + print('Voltage source with polarity {} at {:g}m, {:g}m, {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.xcoord * G.dx, v.ycoord * G.dy, v.zcoord * G.dz, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID)) + else: + print('Voltage source with polarity {} at {:g}m, resistance {:.1f} Ohms,'.format(v.polarisation, v.zcoord_cyl * G.dz_cyl, v.resistance) + startstop + 'using waveform {} created.'.format(v.waveformID)) G.voltagesources.append(v) @@ -174,42 +220,68 @@ def check_coordinates(x, y, z, name=''): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be y in 2D TMy mode') elif '2D TMz' in G.mode and (polarisation == 'x' or polarisation == 'y'): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be z in 2D TMz mode') - - xcoord = G.calculate_coord('x', tmp[1]) - ycoord = G.calculate_coord('y', tmp[2]) - zcoord = G.calculate_coord('z', tmp[3]) - check_coordinates(xcoord, ycoord, zcoord) - if xcoord < G.pmlthickness['x0'] or xcoord > G.nx - G.pmlthickness['xmax'] or ycoord < G.pmlthickness['y0'] or ycoord > G.ny - G.pmlthickness['ymax'] or zcoord < G.pmlthickness['z0'] or zcoord > G.nz - G.pmlthickness['zmax']: - print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) - - # Check if there is a waveformID in the waveforms list - if not any(x.ID == tmp[4] for x in G.waveforms): - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) - - h = HertzianDipole() - h.polarisation = polarisation - - # Set length of dipole to grid size in polarisation direction - if h.polarisation == 'x': - h.dl = G.dx - elif h.polarisation == 'y': - h.dl = G.dy - elif h.polarisation == 'z': - h.dl = G.dz - - h.xcoord = xcoord - h.ycoord = ycoord - h.zcoord = zcoord - h.xcoordorigin = xcoord - h.ycoordorigin = ycoord - h.zcoordorigin = zcoord - h.ID = h.__class__.__name__ + '(' + str(h.xcoord) + ',' + str(h.ycoord) + ',' + str(h.zcoord) + ')' - h.waveformID = tmp[4] - - if len(tmp) > 5: + elif 'Cylindrical' in G.mode and (polarisation != 'z'): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' polarisation must be z in Cylindrical mode') + + if not G.cylindrical: + xcoord = G.calculate_coord('x', tmp[1]) + ycoord = G.calculate_coord('y', tmp[2]) + zcoord = G.calculate_coord('z', tmp[3]) + check_coordinates(xcoord, ycoord, zcoord) + if xcoord < G.pmlthickness['x0'] or xcoord > G.nx - G.pmlthickness['xmax'] or ycoord < G.pmlthickness['y0'] or ycoord > G.ny - G.pmlthickness['ymax'] or zcoord < G.pmlthickness['z0'] or zcoord > G.nz - G.pmlthickness['zmax']: + print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) + + # Check if there is a waveformID in the waveforms list + if not any(x.ID == tmp[4] for x in G.waveforms): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) + + h = HertzianDipole() + h.polarisation = polarisation + + # Set length of dipole to grid size in polarisation direction + if h.polarisation == 'x': + h.dl = G.dx + elif h.polarisation == 'y': + h.dl = G.dy + elif h.polarisation == 'z': + h.dl = G.dz + + h.xcoord = xcoord + h.ycoord = ycoord + h.zcoord = zcoord + h.xcoordorigin = xcoord + h.ycoordorigin = ycoord + h.zcoordorigin = zcoord + h.ID = h.__class__.__name__ + '(' + str(h.xcoord) + ',' + str(h.ycoord) + ',' + str(h.zcoord) + ')' + h.waveformID = tmp[4] + else: + zcoord_cyl = G.calculate_coord('z_cyl', tmp[1]) + check_coordinates_cylindrical(1, zcoord_cyl) + + # To have a cylindrical symmetry, the source must be at r=0 + if zcoord_cyl > G.pmlthickness_cyl['zmax'] or zcoord_cyl < G.pmlthickness_cyl['z0']: + print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) + + # Check if there is a waveformID in the waveforms list + if not any(x.ID == tmp[2] for x in G.waveforms): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(tmp[4])) + + h = HertzianDipole() + h.polarisation = 'z' + h.dl = G.dz_cyl + h.zcoord_cyl = zcoord_cyl + h.zcoordorigin_cyl = zcoord_cyl + h.ID = h.__class__.__name__ + '(' + '0' + ',' + str(h.zcoord_cyl) + ')' + h.waveformID = tmp[2] + + if (len(tmp) > 5 and not G.cylindrical) or (len(tmp) > 3 and G.cylindrical): # Check source start & source remove time parameters - start = float(tmp[5]) - stop = float(tmp[6]) + if G.cylindrical: + start = float(tmp[3]) + stop = float(tmp[4]) + else: + start = float(tmp[5]) + stop = float(tmp[6]) if start < 0: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' delay of the initiation of the source should not be less than zero') if stop < 0: @@ -232,6 +304,8 @@ def check_coordinates(x, y, z, name=''): if G.messages: if G.mode == '2D': print('Hertzian dipole is a line source in 2D with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * G.dx, h.ycoord * G.dy, h.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(h.waveformID)) + elif G.mode == 'cylindrical': + print('Hertzian dipole with polarity {} at r = 0m, {:g}m,'.format(h.polarisation, h.zcoord_cyl * G.dz_cyl) + startstop + 'using waveform {} created.'.format(h.waveformID)) else: print('Hertzian dipole with polarity {} at {:g}m, {:g}m, {:g}m,'.format(h.polarisation, h.xcoord * G.dx, h.ycoord * G.dy, h.zcoord * G.dz) + startstop + 'using waveform {} created.'.format(h.waveformID)) @@ -241,6 +315,8 @@ def check_coordinates(x, y, z, name=''): cmdname = '#magnetic_dipole' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise CmdInputError(cmdname + " not supported for cylindrical") tmp = cmdinstance.split() if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') @@ -310,6 +386,8 @@ def check_coordinates(x, y, z, name=''): cmdname = '#transmission_line' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise CmdInputError(cmdname + " not supported for cylindrical") tmp = cmdinstance.split() if len(tmp) < 6: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least six parameters') @@ -391,42 +469,82 @@ def check_coordinates(x, y, z, name=''): raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' has an incorrect number of parameters') # Check position parameters - xcoord = round_value(float(tmp[0]) / G.dx) - ycoord = round_value(float(tmp[1]) / G.dy) - zcoord = round_value(float(tmp[2]) / G.dz) - check_coordinates(xcoord, ycoord, zcoord) - if xcoord < G.pmlthickness['x0'] or xcoord > G.nx - G.pmlthickness['xmax'] or ycoord < G.pmlthickness['y0'] or ycoord > G.ny - G.pmlthickness['ymax'] or zcoord < G.pmlthickness['z0'] or zcoord > G.nz - G.pmlthickness['zmax']: - print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) + if not G.cylindrical: + xcoord = round_value(float(tmp[0]) / G.dx) + ycoord = round_value(float(tmp[1]) / G.dy) + zcoord = round_value(float(tmp[2]) / G.dz) + check_coordinates(xcoord, ycoord, zcoord) + if xcoord < G.pmlthickness['x0'] or xcoord > G.nx - G.pmlthickness['xmax'] or ycoord < G.pmlthickness['y0'] or ycoord > G.ny - G.pmlthickness['ymax'] or zcoord < G.pmlthickness['z0'] or zcoord > G.nz - G.pmlthickness['zmax']: + print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) + + r = Rx() + r.xcoord = xcoord + r.ycoord = ycoord + r.zcoord = zcoord + r.xcoordorigin = xcoord + r.ycoordorigin = ycoord + r.zcoordorigin = zcoord + + # If no ID or outputs are specified, use default + if len(tmp) == 3: + r.ID = r.__class__.__name__ + '(' + str(r.xcoord) + ',' + str(r.ycoord) + ',' + str(r.zcoord) + ')' + for key in Rx.defaultoutputs: + r.outputs[key] = np.zeros(G.iterations, dtype=floattype) + else: + r.ID = tmp[3] + # Get allowable outputs + if G.gpu is not None: + allowableoutputs = Rx.gpu_allowableoutputs + else: + allowableoutputs = Rx.allowableoutputs + # Check and add field output names + for field in tmp[4::]: + if field in allowableoutputs: + r.outputs[field] = np.zeros(G.iterations, dtype=floattype) + else: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' contains an output type that is not allowable. Allowable outputs in current context are {}'.format(allowableoutputs)) + + if G.messages: + print('Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * G.dx,r.ycoord * G.dy,r.zcoord * G.dz,', '.join(r.outputs))) - r = Rx() - r.xcoord = xcoord - r.ycoord = ycoord - r.zcoord = zcoord - r.xcoordorigin = xcoord - r.ycoordorigin = ycoord - r.zcoordorigin = zcoord - - # If no ID or outputs are specified, use default - if len(tmp) == 3: - r.ID = r.__class__.__name__ + '(' + str(r.xcoord) + ',' + str(r.ycoord) + ',' + str(r.zcoord) + ')' - for key in Rx.defaultoutputs: - r.outputs[key] = np.zeros(G.iterations, dtype=floattype) else: - r.ID = tmp[3] - # Get allowable outputs - if G.gpu is not None: - allowableoutputs = Rx.gpu_allowableoutputs + rcoord_cyl = round_value(float(tmp[0]) / G.dr_cyl) + phicoord_cyl = float(tmp[1]) + zcoord_cyl = round_value(float(tmp[2]) / G.dz_cyl) + + if rcoord_cyl > G.nr_cyl - G.pmlthickness_cyl['r'] or zcoord_cyl < G.pmlthickness_cyl['z'] or zcoord_cyl > G.nz_cyl - G.pmlthickness_cyl['z']: + print(Fore.RED + "WARNING: '" + cmdname + ': ' + ' '.join(tmp) + "'" + ' sources and receivers should not normally be positioned within the PML.' + Style.RESET_ALL) + + r = Rx() + r.rcoord_cyl = rcoord_cyl + r.phicoord_cyl = phicoord_cyl + r.zcoord_cyl = zcoord_cyl + r.rcoordorigin_cyl = rcoord_cyl + r.phicoordorigin_cyl = phicoord_cyl + r.zcoordorigin_cyl = zcoord_cyl + + if len(tmp) == 3: + r.ID = r.__class__.__name__ + '(' + str(r.rcoord_cyl) + ',' + str(r.phicoord_cyl) + ',' + str(r.zcoord_cyl) + ')' + for key in Rx.defaultoutputs_cyl: + r.outputs[key] = np.zeros(G.iterations, dtype=floattype) + else: - allowableoutputs = Rx.allowableoutputs - # Check and add field output names - for field in tmp[4::]: - if field in allowableoutputs: - r.outputs[field] = np.zeros(G.iterations, dtype=floattype) + r.ID = tmp[3] + # Get allowable outputs + if G.gpu is not None: + raise GeneralError("gpu computation not supported yet for cylindrical") + # allowableoutputs = Rx.gpu_allowableoutputs else: - raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' contains an output type that is not allowable. Allowable outputs in current context are {}'.format(allowableoutputs)) + allowableoutputs = Rx.allowableoutputs_cyl + # Check and add field output names + for field in tmp[4::]: + if field in allowableoutputs: + r.outputs[field] = np.zeros(G.iterations, dtype=floattype) + else: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' contains an output type that is not allowable. Allowable outputs in current context are {}'.format(allowableoutputs)) - if G.messages: - print('Receiver at {:g}m, {:g}m, {:g}m with output component(s) {} created.'.format(r.xcoord * G.dx, r.ycoord * G.dy, r.zcoord * G.dz, ', '.join(r.outputs))) + if G.messages: + print('Receiver at r = {:g}m, phi = {:g}rad, z = {:g}m with output component(s) {} created.'.format(r.rcoord_cyl * G.dr_cyl, r.phicoord_cyl, r.zcoord_cyl * G.dz_cyl, ', '.join(r.outputs))) G.rxs.append(r) @@ -434,6 +552,8 @@ def check_coordinates(x, y, z, name=''): cmdname = '#rx_array' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") tmp = cmdinstance.split() if len(tmp) != 9: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly nine parameters') @@ -499,6 +619,8 @@ def check_coordinates(x, y, z, name=''): cmdname = '#snapshot' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') @@ -659,8 +781,9 @@ def check_coordinates(x, y, z, name=''): cmdname = '#add_dispersion_drude' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") tmp = cmdinstance.split() - if len(tmp) < 5: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at least five parameters') if int(tmp[0]) < 0: @@ -694,6 +817,8 @@ def check_coordinates(x, y, z, name=''): cmdname = '#soil_peplinski' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") tmp = cmdinstance.split() if len(tmp) != 7: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires at exactly seven parameters') @@ -725,6 +850,8 @@ def check_coordinates(x, y, z, name=''): cmdname = '#geometry_view' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") tmp = cmdinstance.split() if len(tmp) != 11: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') @@ -775,6 +902,8 @@ def check_coordinates(x, y, z, name=''): cmdname = '#geometry_objects_write' if multicmds[cmdname] is not None: for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") tmp = cmdinstance.split() if len(tmp) != 7: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly seven parameters') @@ -807,6 +936,8 @@ def check_coordinates(x, y, z, name=''): if len(multicmds[cmdname]) > 2: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' can only be used up to two times, for up to a 2nd order PML') for cmdinstance in multicmds[cmdname]: + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") tmp = cmdinstance.split() if len(tmp) != 12: raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly twelve parameters') @@ -849,3 +980,211 @@ def check_coordinates(x, y, z, name=''): print('PML CFS parameters: alpha (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), kappa (scaling: {}, scaling direction: {}, min: {:g}, max: {:g}), sigma (scaling: {}, scaling direction: {}, min: {:g}, max: {}) created.'.format(cfsalpha.scalingprofile, cfsalpha.scalingdirection, cfsalpha.min, cfsalpha.max, cfskappa.scalingprofile, cfskappa.scalingdirection, cfskappa.min, cfskappa.max, cfssigma.scalingprofile, cfssigma.scalingdirection, cfssigma.min, cfssigma.max)) G.cfs.append(cfs) + + #Fluxes + cmdname = '#flux' + if multicmds[cmdname] is not None: + for cmdinstance in multicmds[cmdname]: + tmp = cmdinstance.split() + if G.cylindrical: + raise GeneralError(cmdname + " not supported yet for cylindrical") + if len(tmp) != 11: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires exactly eleven parameters') + if tmp[0] not in Flux.possible_normals: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a valid normal direction: {}'.format(', '.join(Flux.possible_normals))) + if tmp[1] not in Flux.possible_direction: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires a valid direction: {}'.format(', '.join(Flux.possible_direction))) + x1 = round_value(float(tmp[2]) / G.dx) + y1 = round_value(float(tmp[3]) / G.dy) + z1 = round_value(float(tmp[4]) / G.dz) + x2 = round_value(float(tmp[5]) / G.dx) + y2 = round_value(float(tmp[6]) / G.dy) + z2 = round_value(float(tmp[7]) / G.dz) + w_min = float(tmp[8]) + w_max = float(tmp[9]) + w_num = int(tmp[10]) + print(y2, ' ', G.ny) + if x1 < 0 or y1 < 0 or z1 < 0 or x2 < 0 or y2 < 0 or z2 < 0: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires positive values for the coordinates') + if x1 > G.nx or y1 > G.ny or z1 > G.nz or x2 > G.nx or y2 > G.ny or z2 > G.nz: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires values fitting in the domain') + if tmp[0] == 'x': + if x1 != x2: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires the x-coordinates to be equal for a flux normal to the x-axis') + else: + assert y1 < y2 and z1 < z2, "y1 should be less than y2 and z1 should be less than z2 to define a valid flux area" + elif tmp[0] == 'y': + if y1 != y2: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires the y-coordinates to be equal for a flux normal to the y-axis') + else: + assert x1 < x2 and z1 < z2, "x1 should be less than x2 and z1 should be less than z2 to define a valid flux area" + elif tmp[0] == 'z': + if z1 != z2: + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' requires the z-coordinates to be equal for a flux normal to the z-axis') + else: + assert x1 < x2 and y1 < y2, "x1 should be less than x2 and y1 should be less than y2 to define a valid flux area" + assert w_min > 0 and w_max > 0 and w_num > 0, "wavelengths and the number of wavelengths should be strictly positive" + flux = Flux(G, tmp[0], tmp[1], np.array([x1, y1, z1]), np.array([x2, y2, z2]), np.linspace(w_min, w_max, w_num, dtype= floattype)) + G.fluxes.append(flux) + + cmdname = '#box_flux' + if multicmds[cmdname] is not None: + from gprMax.Fluxes import Flux + for cmdinstance in multicmds[cmdname]: + tmp = cmdinstance.split() + if G.cylindrical: + raise CmdInputError(cmdname + " not yet supported with cylindrical coordinates") + if len(tmp) != 12: + raise CmdInputError(cmdname + " requires exactly 12 parameters: xcenter ycenter zcenter xminus xplus yminus yplus zminus zplus wavelength_min wavelength_max number_of_wavelengths") + xcenter = round_value(float(tmp[0]) / G.dx) + ycenter = round_value(float(tmp[1]) / G.dy) + zcenter = round_value(float(tmp[2]) / G.dz) + xminus = round_value(float(tmp[3]) / G.dx) + xplus = round_value(float(tmp[4]) / G.dx) + yminus = round_value(float(tmp[5]) / G.dy) + yplus = round_value(float(tmp[6]) / G.dy) + zminus = round_value(float(tmp[7]) / G.dz) + zplus = round_value(float(tmp[8]) / G.dz) + w_min = float(tmp[9]) + w_max = float(tmp[10]) + w_num = int(tmp[11]) + print(np.linspace(w_min, w_max, w_num, dtype= floattype)) + assert xcenter > 0 and ycenter > 0 and zcenter > 0 and xminus > 0 and xplus > 0 and yminus > 0 and yplus > 0 and zminus > 0 and zplus > 0, CmdInputError("Every parameter should be positive") + assert xcenter - xminus >= 0 and xcenter + xplus <= G.nx, "The x-faces of the box should be inside the domain" + if xcenter - xminus <= G.pmlthickness['x0'] or xcenter + xplus >= G.nx - G.pmlthickness['xmax']: + print(Fore.RED + "WARNING: '" + cmdname + "': x-faces should not be inside PMLs" + Style.RESET_ALL) + assert ycenter - yminus >= 0 and ycenter + yplus <= G.ny, "The y-faces of the box should be inside the domain" + if ycenter - yminus <= G.pmlthickness['y0'] or ycenter + yplus >= G.ny - G.pmlthickness['ymax']: + print(Fore.RED + "WARNING: '" + cmdname + "': y-faces should not be inside PMLs" + Style.RESET_ALL) + assert zcenter - zminus >= 0 and zcenter + zplus <= G.nz, "The z-faces of the box should be inside the domain" + if zcenter - zminus <= G.pmlthickness['z0'] or zcenter + zplus >= G.nz - G.pmlthickness['zmax']: + print(Fore.RED + "WARNING: '" + cmdname + "': z-faces should not be inside PMLs" + Style.RESET_ALL) + assert w_min > 0 and w_max > 0 and w_num > 0, "wavelengths and the number of wavelengths should be strictly positive" + + # We now set the 6 faces of the cube + + x_1 = xcenter + xplus + y_1 = ycenter + yplus + z_1 = zcenter + zplus + + x_2 = xcenter + xplus + y_2 = ycenter - yminus + z_2 = zcenter - zminus + + flux = Flux(G, 'x', 'plus', np.array([x_2, y_2, z_2]), np.array([x_1, y_1, z_1]), np.linspace(w_min, w_max, w_num, dtype= floattype)) + G.fluxes.append(flux) + + x_3 = xcenter - xminus + y_3 = ycenter - yminus + z_3 = zcenter + zplus + + flux = Flux(G, 'z', 'plus', np.array([x_3, y_3, z_3]), np.array([x_1, y_1, z_1]), np.linspace(w_min, w_max, w_num, dtype= floattype)) + G.fluxes.append(flux) + + x_4 = xcenter - xminus + y_4 = ycenter - yminus + z_4 = zcenter - zminus + + x_5 = xcenter - xminus + y_5 = ycenter + yplus + z_5 = zcenter + zplus + + flux = Flux(G, 'x', 'minus', np.array([x_4, y_4, z_4]), np.array([x_5, y_5, z_5]), np.linspace(w_min, w_max, w_num, dtype= floattype)) + G.fluxes.append(flux) + + x_6 = xcenter + xplus + y_6 = ycenter + yplus + z_6 = zcenter - zminus + + flux = Flux(G, 'z', 'minus', np.array([x_4, y_4, z_4]), np.array([x_6, y_6, z_6]), np.linspace(w_min, w_max, w_num, dtype= floattype)) + G.fluxes.append(flux) + + x_7 = xcenter + xplus + y_7 = ycenter - yminus + z_7 = zcenter + zplus + + flux = Flux(G, 'y', 'minus', np.array([x_4, y_4, z_4]), np.array([x_7, y_7, z_7]), np.linspace(w_min, w_max, w_num, dtype= floattype)) + G.fluxes.append(flux) + + x_8 = xcenter - xminus + y_8 = ycenter + yplus + z_8 = zcenter - zminus + + flux = Flux(G, 'y', 'plus', np.array([x_8, y_8, z_8]), np.array([x_1, y_1, z_1]), np.linspace(w_min, w_max, w_num, dtype= floattype)) + G.fluxes.append(flux) + + G.box_fluxes_enumerate.append("" + cmdname + ': ' + ' '.join(tmp) + '. \n') + + cmdname = '#plane_voltage_source' + if multicmds[cmdname] is not None: + from gprMax.Fluxes import Flux + for cmdinstance in multicmds[cmdname]: + tmp = cmdinstance.split() + if G.cylindrical: + raise CmdInputError(cmdname + " not yet supported with cylindrical coordinates") + if not (len(tmp) == 9 or len(tmp) == 11): + raise CmdInputError(cmdname + " requires exactly 9 parameters or 11 parameters") + component = tmp[0] + x1 = round_value(float(tmp[1]) / G.dx) + y1 = round_value(float(tmp[2]) / G.dy) + z1 = round_value(float(tmp[3]) / G.dz) + + x2 = round_value(float(tmp[4]) / G.dx) + y2 = round_value(float(tmp[5]) / G.dy) + z2 = round_value(float(tmp[6]) / G.dz) + + r = float(tmp[7]) + + waveform = tmp[8] + + start = 0 + stop = G.timewindow + + if len(tmp) == 11: + start = float(tmp[9]) + stop = float(tmp[10]) + + assert x1 <= x2 and y1 <= y2 and z1 <= z2, CmdInputError(cmdname + ": coordinates must verify x1 <= x2, y1 <= y2 and z1 <= z2") + + assert x1 >= 0 and y1 >= 0 and z1 >= 0 and x2 <= G.nx and y2 <= G.ny and z2 <= G.nz, CmdInputError(cmdname + " must be within the domain bounds") + + assert x1 == x2 or y1 == y2 or z1 == z2, CmdInputError(cmdname + " must be at most a surface, not a volume.") + + if x1 == x2: + if G.pmlthickness['x0'] >= x1 or G.nx - G.pmlthickness['xmax'] <= x1: + raise CmdInputError("The whole source plane cannot be included in PMLs !") + elif y1 == y2: + if G.pmlthickness['y0'] >= y1 or G.ny - G.pmlthickness['ymax'] <= y1: + raise CmdInputError("The whole source plane cannot be included in PMLs !") + elif z1 == z2: + if G.pmlthickness['z0'] >= z1 or G.nz - G.pmlthickness['zmax'] <= z1: + raise CmdInputError("The whole source plane cannot be included in PMLs !") + + if x1 >= G.pmlthickness['x0'] and x2 <= G.nx - G.pmlthickness['xmax'] and y1 >= G.pmlthickness['y0'] and y2 <= G.ny - G.pmlthickness['ymax'] and z1 >= G.pmlthickness['z0'] and z2 <= G.nz - G.pmlthickness['zmax']: + print(Fore.RED + "WARNING: '" + cmdname + "' shouldn't be inside pmls;" + Style.RESET_ALL) + + assert r >= 0, CmdInputError(cmdname + " requires a positive source resistance") + + # Check if there is a waveformID in the waveforms list + if not any(x.ID == waveform for x in G.waveforms): + raise CmdInputError("'" + cmdname + ': ' + ' '.join(tmp) + "'" + ' there is no waveform with the identifier {}'.format(waveform)) + + X = np.arange(x1, x2) if x1 != x2 else [x1] + Y = np.arange(y1, y2) if y1 != y2 else [y1] + Z = np.arange(z1, z2) if z1 != z2 else [z1] + + for i in X: + for j in Y: + for k in Z: + v = VoltageSource() + v.polarisation = component + v.xcoord = i + v.ycoord = j + v.zcoord = k + v.ID = v.__class__.__name__ + '(' + str(v.xcoord) + ',' + str(v.ycoord) + ',' + str(v.zcoord) + ')' + v.waveformID = waveform + v.resistance = r + G.voltagesources.append(v) + v.start = start + v.stop = stop + v.calculate_waveform_values(G) \ No newline at end of file diff --git a/gprMax/input_cmds_singleuse.py b/gprMax/input_cmds_singleuse.py index 7981d7f72..5b7556690 100644 --- a/gprMax/input_cmds_singleuse.py +++ b/gprMax/input_cmds_singleuse.py @@ -110,56 +110,131 @@ def process_singlecmds(singlecmds, G): if G.messages: if G.gpu is not None: print('GPU solving using: {} - {}'.format(G.gpu.deviceID, G.gpu.name)) + + #Cylindrical symmetry + cmd = '#cylindrical' + if singlecmds[cmd] is not None: + tmp = [x for x in singlecmds[cmd].split()] + if len(tmp) != 1: + raise CmdInputError(cmd + ' requires exactly one parameter') + elif tmp[0] not in ['True', 'False']: + raise CmdInputError(cmd + ' requires input values of either True or False') + else: + G.cylindrical = bool(tmp[0]) + else: + G.cylindrical = False + + #Saptial discretisation if cylindrical + if G.cylindrical: + cmd = '#dr_dz' + if singlecmds[cmd] is not None: + tmp = [float(x) for x in singlecmds[cmd].split()] + if len(tmp) != 2: + raise CmdInputError(cmd + ' requires exactly two parameters') + elif tmp[0] < 0: + raise CmdInputError(cmd + ' requires the r-coordinate to be greater than 0') + elif tmp[1] < 0: + raise CmdInputError(cmd + ' requires the z-coordinate to be greater than 0') + else: + G.dr_cyl = tmp[0] + G.dz_cyl = tmp[1] + if G.messages: + print('Spatial discretisation: {:g} x {:g} m'.format(G.dr_cyl, G.dz_cyl)) + + # Domain cylindrical + cmd = '#domain_cyl' + tmp = [float(x) for x in singlecmds[cmd].split()] + if len(tmp) != 2: + raise CmdInputError(cmd + ' requires exactly two parameters') + elif tmp[0] < 0: + raise CmdInputError(cmd + ' requires the r-coordinate to be greater than 0') + elif tmp[1] < 0: + raise CmdInputError(cmd + ' requires the z-coordinate to be greater than 0') + else: + G.nr_cyl = round_value(tmp[0] / G.dr_cyl) + G.nz_cyl = round_value(tmp[1] / G.dz_cyl) + if G.messages: + print('Domain size: {:g} x {:g} m ({:d} x {:d} = {:g} cells)'.format(tmp[0], tmp[1], + G.nr_cyl, G.nz_cyl, + (G.nr_cyl * G.nz_cyl))) + else: + raise CmdInputError(cmd + ' is required for 3D models in cylindrical coordinates') + # m + cmd = '#m' + if singlecmds[cmd] is not None: + tmp = [int(x) for x in singlecmds[cmd].split()] + if len(tmp) != 1: + raise CmdInputError(cmd + ' requires exactly one parameter') + else: + G.m_cyl = tmp[0] + else: + raise CmdInputError(cmd + ' is required for 3D models in cylindrical coordinates') + # Spatial discretisation cmd = '#dx_dy_dz' - tmp = [float(x) for x in singlecmds[cmd].split()] - if len(tmp) != 3: - raise CmdInputError(cmd + ' requires exactly three parameters') - if tmp[0] <= 0: - raise CmdInputError(cmd + ' requires the x-direction spatial step to be greater than zero') - if tmp[1] <= 0: - raise CmdInputError(cmd + ' requires the y-direction spatial step to be greater than zero') - if tmp[2] <= 0: - raise CmdInputError(cmd + ' requires the z-direction spatial step to be greater than zero') - G.dx = tmp[0] - G.dy = tmp[1] - G.dz = tmp[2] - if G.messages: - print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(G.dx, G.dy, G.dz)) + if singlecmds[cmd] is not None: + if G.cylindrical and len(tmp) != 0: + raise CmdInputError(cmd + ' cannot be used with cylindrical') + tmp = [float(x) for x in singlecmds[cmd].split()] + if len(tmp) != 3: + raise CmdInputError(cmd + ' requires exactly three parameters') + if tmp[0] <= 0: + raise CmdInputError(cmd + ' requires the x-direction spatial step to be greater than zero') + if tmp[1] <= 0: + raise CmdInputError(cmd + ' requires the y-direction spatial step to be greater than zero') + if tmp[2] <= 0: + raise CmdInputError(cmd + ' requires the z-direction spatial step to be greater than zero') + G.dx = tmp[0] + G.dy = tmp[1] + G.dz = tmp[2] + if G.messages: + print('Spatial discretisation: {:g} x {:g} x {:g}m'.format(G.dx, G.dy, G.dz)) + elif singlecmds[cmd] is None and not G.cylindrical: + raise CmdInputError(cmd + ' is required for 3D models in cartesian coordinates') # Domain cmd = '#domain' - tmp = [float(x) for x in singlecmds[cmd].split()] - if len(tmp) != 3: - raise CmdInputError(cmd + ' requires exactly three parameters') - G.nx = round_value(tmp[0] / G.dx) - G.ny = round_value(tmp[1] / G.dy) - G.nz = round_value(tmp[2] / G.dz) - if G.nx == 0 or G.ny == 0 or G.nz == 0: - raise CmdInputError(cmd + ' requires at least one cell in every dimension') - if G.messages: - print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz))) + if singlecmds[cmd] is not None: + if G.cylindrical: + raise CmdInputError(cmd + ' cannot be used with cylindrical') + tmp = [float(x) for x in singlecmds[cmd].split()] + if len(tmp) != 3: + raise CmdInputError(cmd + ' requires exactly three parameters') + G.nx = round_value(tmp[0] / G.dx) + G.ny = round_value(tmp[1] / G.dy) + G.nz = round_value(tmp[2] / G.dz) + if G.nx == 0 or G.ny == 0 or G.nz == 0: + raise CmdInputError(cmd + ' requires at least one cell in every dimension') + if G.messages: + print('Domain size: {:g} x {:g} x {:g}m ({:d} x {:d} x {:d} = {:g} cells)'.format(tmp[0], tmp[1], tmp[2], G.nx, G.ny, G.nz, (G.nx * G.ny * G.nz))) + elif singlecmds[cmd] is None and not G.cylindrical: + raise CmdInputError(cmd + ' is required for 3D models in cartesian coordinates') # Time step CFL limit (either 2D or 3D); switch off appropriate PMLs for 2D - if G.nx == 1: - G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) - G.mode = '2D TMx' - G.pmlthickness['x0'] = 0 - G.pmlthickness['xmax'] = 0 - elif G.ny == 1: - G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) - G.mode = '2D TMy' - G.pmlthickness['y0'] = 0 - G.pmlthickness['ymax'] = 0 - elif G.nz == 1: - G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) - G.mode = '2D TMz' - G.pmlthickness['z0'] = 0 - G.pmlthickness['zmax'] = 0 + if G.cylindrical: + G.dt = 1 / (c * np.sqrt(2*(1 / G.dr_cyl) * (1 / G.dr_cyl) + (1 / G.dz_cyl) * (1 / G.dz_cyl))) + G.mode = 'Cylindrical' + ### Ajouter les conditions sur les PMLs else: - G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) - G.mode = '3D' + if G.nx == 1: + G.dt = 1 / (c * np.sqrt((1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) + G.mode = '2D TMx' + G.pmlthickness['x0'] = 0 + G.pmlthickness['xmax'] = 0 + elif G.ny == 1: + G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dz) * (1 / G.dz))) + G.mode = '2D TMy' + G.pmlthickness['y0'] = 0 + G.pmlthickness['ymax'] = 0 + elif G.nz == 1: + G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy))) + G.mode = '2D TMz' + G.pmlthickness['z0'] = 0 + G.pmlthickness['zmax'] = 0 + else: + G.dt = 1 / (c * np.sqrt((1 / G.dx) * (1 / G.dx) + (1 / G.dy) * (1 / G.dy) + (1 / G.dz) * (1 / G.dz))) + G.mode = '3D' # Round down time step to nearest float with precision one less than hardware maximum. # Avoids inadvertently exceeding the CFL due to binary representation of floating point number. @@ -210,24 +285,41 @@ def process_singlecmds(singlecmds, G): cmd = '#pml_cells' if singlecmds[cmd] is not None: tmp = singlecmds[cmd].split() - if len(tmp) != 1 and len(tmp) != 6: - raise CmdInputError(cmd + ' requires either one or six parameter(s)') - if len(tmp) == 1: - for key in G.pmlthickness.keys(): - G.pmlthickness[key] = int(tmp[0]) + if not G.cylindrical: + if len(tmp) != 1 and len(tmp) != 6: + raise CmdInputError(cmd + ' requires either one or six parameter(s)') + if len(tmp) == 1: + for key in G.pmlthickness.keys(): + G.pmlthickness[key] = int(tmp[0]) + else: + G.pmlthickness['x0'] = int(tmp[0]) + G.pmlthickness['y0'] = int(tmp[1]) + G.pmlthickness['z0'] = int(tmp[2]) + G.pmlthickness['xmax'] = int(tmp[3]) + G.pmlthickness['ymax'] = int(tmp[4]) + G.pmlthickness['zmax'] = int(tmp[5]) + if 2 * G.pmlthickness['x0'] >= G.nx or 2 * G.pmlthickness['y0'] >= G.ny or 2 * G.pmlthickness['z0'] >= G.nz or 2 * G.pmlthickness['xmax'] >= G.nx or 2 * G.pmlthickness['ymax'] >= G.ny or 2 * G.pmlthickness['zmax'] >= G.nz: + raise CmdInputError(cmd + ' has too many cells for the domain size') else: - G.pmlthickness['x0'] = int(tmp[0]) - G.pmlthickness['y0'] = int(tmp[1]) - G.pmlthickness['z0'] = int(tmp[2]) - G.pmlthickness['xmax'] = int(tmp[3]) - G.pmlthickness['ymax'] = int(tmp[4]) - G.pmlthickness['zmax'] = int(tmp[5]) - if 2 * G.pmlthickness['x0'] >= G.nx or 2 * G.pmlthickness['y0'] >= G.ny or 2 * G.pmlthickness['z0'] >= G.nz or 2 * G.pmlthickness['xmax'] >= G.nx or 2 * G.pmlthickness['ymax'] >= G.ny or 2 * G.pmlthickness['zmax'] >= G.nz: - raise CmdInputError(cmd + ' has too many cells for the domain size') + if len(tmp) != 1 and len(tmp) != 2: + raise CmdInputError(cmd + ' requires either one or 2 parameter(s) in cylindrical coordinate') + if len(tmp) == 1: + if int(tmp[0]) < 2: + raise CmdInputError("The PML thickness in cylindrical coordinates should be greater than 1") + for key in G.pmlthickness_cyl.keys(): + G.pmlthickness_cyl[key] = int(tmp[0]) + else: + if int(tmp[0]) < 2 or int(tmp[1]) < 2: + raise CmdInputError("The PML thickness in cylindrical coordinates should be greater than 1") + G.pmlthickness_cyl['r'] = int(tmp[0]) + G.pmlthickness_cyl['z'] = int(tmp[1]) + # PML formulation cmd = '#pml_formulation' if singlecmds[cmd] is not None: + if G.cylindrical: + raise CmdInputError(cmd + ' cannot be used with cylindrical') tmp = singlecmds[cmd].split() if len(tmp) != 1: raise CmdInputError(cmd + ' requires exactly one parameter') @@ -239,6 +331,8 @@ def process_singlecmds(singlecmds, G): # src_steps cmd = '#src_steps' if singlecmds[cmd] is not None: + if G.cylindrical: + raise CmdInputError(cmd + ' cannot be used with cylindrical') tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') @@ -251,6 +345,8 @@ def process_singlecmds(singlecmds, G): # rx_steps cmd = '#rx_steps' if singlecmds[cmd] is not None: + if G.cylindrical: + raise CmdInputError(cmd + ' cannot be used with cylindrical') tmp = singlecmds[cmd].split() if len(tmp) != 3: raise CmdInputError(cmd + ' requires exactly three parameters') diff --git a/gprMax/materials.py b/gprMax/materials.py index 7aac97ef5..462c6e6c4 100644 --- a/gprMax/materials.py +++ b/gprMax/materials.py @@ -72,14 +72,29 @@ def calculate_update_coeffsH(self, G): Args: G (class): Grid class instance - holds essential parameters describing the model. """ + if not G.cylindrical: + HA = (m0 * self.mr / G.dt) + 0.5 * self.sm + HB = (m0 * self.mr / G.dt) - 0.5 * self.sm + self.DA = HB / HA + self.DBx = (1 / G.dx) * 1 / HA + self.DBy = (1 / G.dy) * 1 / HA + self.DBz = (1 / G.dz) * 1 / HA + self.srcm = 1 / HA + else: + assert G.cylindrical, "Can't call calculate_update_coeffsH with cylindrical if not in G.cylindrical" + # HA = (m0 * self.mr / G.dt) + 0.5 * self.sm #Same as in cartesian + # HB = (m0 * self.mr / G.dt) - 0.5 * self.sm #Same as in cartesian + # self.DA = HB / HA #Same as in cartesian + # self.DBr = (1 / G.dr_cyl) * 1 / HA + # self.DBphi = 1 #Symmetry --> im/r + # self.DBz = (1 / G.dz_cyl) * 1 / HA + # self.srcm = 1 / HA + self.DA = HB / HA #Same as in cartesian + self.DBr = G.dt/(self.mr * m0 * G.dr_cyl) + self.DBphi = G.dt/(m0 * self.mr * G.dphi_cyl * G.dr_cyl) + self.DBz = G.dt/(self.mr * m0 * G.dz_cyl) + self.srcm = 1 / HA - HA = (m0 * self.mr / G.dt) + 0.5 * self.sm - HB = (m0 * self.mr / G.dt) - 0.5 * self.sm - self.DA = HB / HA - self.DBx = (1 / G.dx) * 1 / HA - self.DBy = (1 / G.dy) * 1 / HA - self.DBz = (1 / G.dz) * 1 / HA - self.srcm = 1 / HA def calculate_update_coeffsE(self, G): """Calculates the electric update coefficients of the material. @@ -91,56 +106,79 @@ def calculate_update_coeffsE(self, G): # The implementation of the dispersive material modelling comes from the # derivation in: http://dx.doi.org/10.1109/TAP.2014.2308549 - if self.maxpoles > 0: - self.w = np.zeros(self.maxpoles, dtype=complextype) - self.q = np.zeros(self.maxpoles, dtype=complextype) - self.zt = np.zeros(self.maxpoles, dtype=complextype) - self.zt2 = np.zeros(self.maxpoles, dtype=complextype) - self.eqt = np.zeros(self.maxpoles, dtype=complextype) - self.eqt2 = np.zeros(self.maxpoles, dtype=complextype) - - for x in range(self.poles): - if 'debye' in self.type: - self.w[x] = self.deltaer[x] / self.tau[x] - self.q[x] = -1 / self.tau[x] - elif 'lorentz' in self.type: - # tau for Lorentz materials are pole frequencies - # alpha for Lorentz materials are the damping coefficients - wp2 = (2 * np.pi * self.tau[x])**2 - self.w[x] = -1j * ((wp2 * self.deltaer[x]) / np.sqrt(wp2 - self.alpha[x]**2)) - self.q[x] = -self.alpha[x] + (1j * np.sqrt(wp2 - self.alpha[x]**2)) - elif 'drude' in self.type: - # tau for Drude materials are pole frequencies - # alpha for Drude materials are the inverse of relaxation times - wp2 = (2 * np.pi * self.tau[x])**2 - self.se += wp2 / self.alpha[x] - self.w[x] = - (wp2 / self.alpha[x]) - self.q[x] = - self.alpha[x] - - self.eqt[x] = np.exp(self.q[x] * G.dt) - self.eqt2[x] = np.exp(self.q[x] * (G.dt / 2)) - self.zt[x] = (self.w[x] / self.q[x]) * (1 - self.eqt[x]) / G.dt - self.zt2[x] = (self.w[x] / self.q[x]) * (1 - self.eqt2[x]) - - EA = (e0 * self.er / G.dt) + 0.5 * self.se - (e0 / G.dt) * np.sum(self.zt2.real) - EB = (e0 * self.er / G.dt) - 0.5 * self.se - (e0 / G.dt) * np.sum(self.zt2.real) + if not G.cylindrical: + if self.maxpoles > 0: + self.w = np.zeros(self.maxpoles, dtype=complextype) + self.q = np.zeros(self.maxpoles, dtype=complextype) + self.zt = np.zeros(self.maxpoles, dtype=complextype) + self.zt2 = np.zeros(self.maxpoles, dtype=complextype) + self.eqt = np.zeros(self.maxpoles, dtype=complextype) + self.eqt2 = np.zeros(self.maxpoles, dtype=complextype) + + for x in range(self.poles): + if 'debye' in self.type: + self.w[x] = self.deltaer[x] / self.tau[x] + self.q[x] = -1 / self.tau[x] + elif 'lorentz' in self.type: + # tau for Lorentz materials are pole frequencies + # alpha for Lorentz materials are the damping coefficients + wp2 = (2 * np.pi * self.tau[x])**2 + self.w[x] = -1j * ((wp2 * self.deltaer[x]) / np.sqrt(wp2 - self.alpha[x]**2)) + self.q[x] = -self.alpha[x] + (1j * np.sqrt(wp2 - self.alpha[x]**2)) + elif 'drude' in self.type: + # tau for Drude materials are pole frequencies + # alpha for Drude materials are the inverse of relaxation times + wp2 = (2 * np.pi * self.tau[x])**2 + self.se += wp2 / self.alpha[x] + self.w[x] = - (wp2 / self.alpha[x]) + self.q[x] = - self.alpha[x] + + self.eqt[x] = np.exp(self.q[x] * G.dt) + self.eqt2[x] = np.exp(self.q[x] * (G.dt / 2)) + self.zt[x] = (self.w[x] / self.q[x]) * (1 - self.eqt[x]) / G.dt + self.zt2[x] = (self.w[x] / self.q[x]) * (1 - self.eqt2[x]) + + EA = (e0 * self.er / G.dt) + 0.5 * self.se - (e0 / G.dt) * np.sum(self.zt2.real) + EB = (e0 * self.er / G.dt) - 0.5 * self.se - (e0 / G.dt) * np.sum(self.zt2.real) + else: + EA = (e0 * self.er / G.dt) + 0.5 * self.se + EB = (e0 * self.er / G.dt) - 0.5 * self.se + + if self.ID == 'pec' or self.se == float('inf'): + self.CA = 0 + self.CBx = 0 + self.CBy = 0 + self.CBz = 0 + self.srce = 0 + else: + self.CA = EB / EA + self.CBx = (1 / G.dx) * 1 / EA + self.CBy = (1 / G.dy) * 1 / EA + self.CBz = (1 / G.dz) * 1 / EA + self.srce = 1 / EA else: + assert G.cylindrical, "Can't call calculate_update_coeffsH_cyl if not in cylindrical" + assert self.maxpoles == 0, "Dispersive materials not yet implemented in cylindrical coordinates" EA = (e0 * self.er / G.dt) + 0.5 * self.se EB = (e0 * self.er / G.dt) - 0.5 * self.se - - if self.ID == 'pec' or self.se == float('inf'): - self.CA = 0 - self.CBx = 0 - self.CBy = 0 - self.CBz = 0 - self.srce = 0 - else: - self.CA = EB / EA - self.CBx = (1 / G.dx) * 1 / EA - self.CBy = (1 / G.dy) * 1 / EA - self.CBz = (1 / G.dz) * 1 / EA - self.srce = 1 / EA + if self.ID == 'pec' or self.se == float('inf'): + self.CA = 0 + self.CBr = 0 + self.CBphi = 0 + self.CBz = 0 + self.srce = 0 + else: + # self.CA = EB / EA + # self.CBr = (1 / G.dr_cyl) * 1 / EA + # self.CBphi = 1 + # self.CBz = (1 / G.dz_cyl) * 1 / EA + # self.srce = 1 / EA + self.CA = EB / EA + self.CBr = (1 / G.dr_cyl) * 1 / EA + self.CBphi = (1 / G.dphi_cyl) * 1 / EA + self.CBz = (1 / G.dz_cyl) * 1 / EA + self.srce = 1 / EA def calculate_er(self, freq): """ @@ -197,11 +235,17 @@ def process_materials(G): material.calculate_update_coeffsH(G) # Store all update coefficients together - G.updatecoeffsE[material.numID, :] = material.CA, material.CBx, material.CBy, material.CBz, material.srce - G.updatecoeffsH[material.numID, :] = material.DA, material.DBx, material.DBy, material.DBz, material.srcm + if not G.cylindrical: + G.updatecoeffsE[material.numID, :] = material.CA, material.CBx, material.CBy, material.CBz, material.srce + G.updatecoeffsH[material.numID, :] = material.DA, material.DBx, material.DBy, material.DBz, material.srcm + else: + assert G.cylindrical, "Can't call calculate_update_coeffsH_cyl if not in cylindrical" + G.updatecoeffsE[material.numID, :] = material.CA, material.CBr, material.CBphi, material.CBz, material.srce + G.updatecoeffsH[material.numID, :] = material.DA, material.DBr, material.DBphi, material.DBz, material.srcm # Store coefficients for any dispersive materials if Material.maxpoles > 0: + assert not G.cylindrical, "Dispersive materials not yet implemented in cylindrical coordinates" z = 0 for pole in range(Material.maxpoles): G.updatecoeffsdispersive[material.numID, z:z + 3] = e0 * material.eqt2[pole], material.eqt[pole], material.zt[pole] diff --git a/gprMax/model_build_run.py b/gprMax/model_build_run.py index e52734ec4..7fba7fe2e 100644 --- a/gprMax/model_build_run.py +++ b/gprMax/model_build_run.py @@ -35,14 +35,20 @@ from gprMax.constants import complextype from gprMax.constants import cudafloattype from gprMax.constants import cudacomplextype -from gprMax.exceptions import GeneralError +from gprMax.exceptions import GeneralError, CmdInputError +from scipy.constants import mu_0 as mu0 from gprMax.fields_outputs import store_outputs +from gprMax.fields_outputs import store_outputs_cyl from gprMax.fields_outputs import kernel_template_store_outputs from gprMax.fields_outputs import write_hdf5_outputfile from gprMax.fields_updates_ext import update_electric from gprMax.fields_updates_ext import update_magnetic +from gprMax.fields_updates_ext import update_electric_cyl +from gprMax.fields_updates_ext import update_magnetic_cyl +from gprMax.fields_updates_ext import update_electric_origin +from gprMax.fields_updates_ext import update_magnetic_origin from gprMax.fields_updates_ext import update_electric_dispersive_multipole_A from gprMax.fields_updates_ext import update_electric_dispersive_multipole_B from gprMax.fields_updates_ext import update_electric_dispersive_1pole_A @@ -79,6 +85,9 @@ from gprMax.utilities import timer from gprMax.yee_cell_build_ext import build_electric_components from gprMax.yee_cell_build_ext import build_magnetic_components +from gprMax.yee_cell_build_CYLINDRICAL_ext import build_electric_components as build_electric_components_cyl +from gprMax.yee_cell_build_CYLINDRICAL_ext import build_magnetic_components as build_magnetic_components_cyl +from gprMax.Fluxes import Flux, save_file_h5py, solve_scattering def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usernamespace): @@ -146,7 +155,12 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern write_processed_file(processedlines, appendmodelnumber, G) # Check validity of command names and that essential commands are present - singlecmds, multicmds, geometry = check_cmd_names(processedlines) + singlecmds, multicmds, scattering_geometrycmds, geometry, scatteringgeometry = check_cmd_names(processedlines) + + if len(scatteringgeometry) != 0: + G.scattering = True + G.scattering_geometrycmds = scattering_geometrycmds + G.scatteringgeometry = scatteringgeometry # Create built-in materials m = Material(0, 'pec') @@ -172,6 +186,8 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern if G.gpu is None: print('\nMemory (RAM) required: ~{}\n'.format(human_size(G.memoryusage))) else: + if G.cylindrical: + raise GeneralError("gpu has yet to be supported with cylindrical") print('\nMemory (RAM) required: ~{} host + ~{} GPU\n'.format(human_size(G.memoryusage), human_size(G.memoryusage))) # Initialise an array for volumetric material IDs (solid), boolean @@ -196,16 +212,29 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern # Set default CFS parameters for PML if not given if not G.cfs: G.cfs = [CFS()] - if G.messages: - if all(value == G.pmlthickness['x0'] for value in G.pmlthickness.values()): - pmlinfo = str(G.pmlthickness['x0']) - else: - pmlinfo = '' - for key, value in G.pmlthickness.items(): - pmlinfo += '{}: {}, '.format(key, value) - pmlinfo = pmlinfo[:-2] + ' cells' - print('PML: formulation: {}, order: {}, thickness: {}'.format(G.pmlformulation, len(G.cfs), pmlinfo)) - pbar = tqdm(total=sum(1 for value in G.pmlthickness.values() if value > 0), desc='Building PML boundaries', ncols=get_terminal_width() - 1, file=sys.stdout, disable=not G.progressbars) + + if not G.cylindrical: + if G.messages: + if all(value == G.pmlthickness['x0'] for value in G.pmlthickness.values()): + pmlinfo = str(G.pmlthickness['x0']) + else: + pmlinfo = '' + for key, value in G.pmlthickness.items(): + pmlinfo += '{}: {}, '.format(key, value) + pmlinfo = pmlinfo[:-2] + ' cells' + print('PML: formulation: {}, order: {}, thickness: {}'.format(G.pmlformulation, len(G.cfs), pmlinfo)) + pbar = tqdm(total=sum(1 for value in G.pmlthickness.values() if value > 0), desc='Building PML boundaries', ncols=get_terminal_width() - 1, file=sys.stdout, disable=not G.progressbars) + else: + if G.messages: + if all(value == G.pmlthickness_cyl['r'] for value in G.pmlthickness_cyl.values()): + pmlinfo = str(G.pmlthickness_cyl['r']) + else: + pmlinfo = '' + for key, value in G.pmlthickness_cyl.items(): + pmlinfo += '{}: {}, '.format(key, value) + pmlinfo = pmlinfo[:-2] + ' cells' + print('PML: cylindrical mode, thicknes: {}'.format(pmlinfo)) + pbar = tqdm(total=sum(1 for value in G.pmlthickness_cyl.values() if value > 0), desc='Building PML boundaries', ncols=get_terminal_width() - 1, file=sys.stdout, disable=not G.progressbars) build_pmls(G, pbar) pbar.close() @@ -213,44 +242,52 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern # of every Yee cell if G.messages: print() pbar = tqdm(total=2, desc='Building main grid', ncols=get_terminal_width() - 1, file=sys.stdout, disable=not G.progressbars) - build_electric_components(G.solid, G.rigidE, G.ID, G) - pbar.update() - build_magnetic_components(G.solid, G.rigidH, G.ID, G) - pbar.update() - pbar.close() + print(Fore.BLUE + "Cylindrical ? " + str(G.cylindrical) + Fore.RESET) + if not G.cylindrical: + build_electric_components(G.solid, G.rigidE, G.ID, G) + pbar.update() + build_magnetic_components(G.solid, G.rigidH, G.ID, G) + pbar.update() + pbar.close() + else: + build_electric_components_cyl(G.solid, G.rigidE, G.ID, G) + pbar.update() + build_magnetic_components_cyl(G.solid, G.rigidH, G.ID, G) + pbar.update() + pbar.close() # Add PEC boundaries to invariant direction in 2D modes # N.B. 2D modes are a single cell slice of 3D grid - if '2D TMx' in G.mode: - # Ey & Ez components - G.ID[1, 0, :, :] = 0 - G.ID[1, 1, :, :] = 0 - G.ID[2, 0, :, :] = 0 - G.ID[2, 1, :, :] = 0 - elif '2D TMy' in G.mode: - # Ex & Ez components - G.ID[0, :, 0, :] = 0 - G.ID[0, :, 1, :] = 0 - G.ID[2, :, 0, :] = 0 - G.ID[2, :, 1, :] = 0 - elif '2D TMz' in G.mode: - # Ex & Ey components - G.ID[0, :, :, 0] = 0 - G.ID[0, :, :, 1] = 0 - G.ID[1, :, :, 0] = 0 - G.ID[1, :, :, 1] = 0 + if not G.cylindrical: + if '2D TMx' in G.mode: + # Ey & Ez components + G.ID[1, 0, :, :] = 0 + G.ID[1, 1, :, :] = 0 + G.ID[2, 0, :, :] = 0 + G.ID[2, 1, :, :] = 0 + elif '2D TMy' in G.mode: + # Ex & Ez components + G.ID[0, :, 0, :] = 0 + G.ID[0, :, 1, :] = 0 + G.ID[2, :, 0, :] = 0 + G.ID[2, :, 1, :] = 0 + elif '2D TMz' in G.mode: + # Ex & Ey components + G.ID[0, :, :, 0] = 0 + G.ID[0, :, :, 1] = 0 + G.ID[1, :, :, 0] = 0 + G.ID[1, :, :, 1] = 0 # Process any voltage sources (that have resistance) to create a new # material at the source location for voltagesource in G.voltagesources: voltagesource.create_material(G) - # Initialise arrays of update coefficients to pass to update functions G.initialise_std_update_coeff_arrays() # Initialise arrays of update coefficients and temporary values if # there are any dispersive materials - if Material.maxpoles != 0: + if Material.maxpoles != 0 and not G.cylindrical: # Update estimated memory (RAM) usage G.memoryusage += int(3 * Material.maxpoles * (G.nx + 1) * (G.ny + 1) * (G.nz + 1) * np.dtype(complextype).itemsize) G.memory_check() @@ -260,7 +297,7 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern G.initialise_dispersive_arrays() # Check there is sufficient memory to store any snapshots - if G.snapshots: + if G.snapshots and not G.cylindrical: snapsmemsize = 0 for snap in G.snapshots: # 2 x required to account for electric and magnetic fields @@ -281,15 +318,16 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern print(materialstable.table) # Check to see if numerical dispersion might be a problem - results = dispersion_analysis(G) - if results['error'] and G.messages: - print(Fore.RED + "\nWARNING: Numerical dispersion analysis not carried out as {}".format(results['error']) + Style.RESET_ALL) - elif results['N'] < G.mingridsampling: - raise GeneralError("Non-physical wave propagation: Material '{}' has wavelength sampled by {} cells, less than required minimum for physical wave propagation. Maximum significant frequency estimated as {:g}Hz".format(results['material'].ID, results['N'], results['maxfreq'])) - elif results['deltavp'] and np.abs(results['deltavp']) > G.maxnumericaldisp and G.messages: - print(Fore.RED + "\nWARNING: Potentially significant numerical dispersion. Estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq']) + Style.RESET_ALL) - elif results['deltavp'] and G.messages: - print("\nNumerical dispersion analysis: estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq'])) + if not G.cylindrical: + results = dispersion_analysis(G) + if results['error'] and G.messages: + print(Fore.RED + "\nWARNING: Numerical dispersion analysis not carried out as {}".format(results['error']) + Style.RESET_ALL) + elif results['N'] < G.mingridsampling: + raise GeneralError("Non-physical wave propagation: Material '{}' has wavelength sampled by {} cells, less than required minimum for physical wave propagation. Maximum significant frequency estimated as {:g}Hz".format(results['material'].ID, results['N'], results['maxfreq'])) + elif results['deltavp'] and np.abs(results['deltavp']) > G.maxnumericaldisp and G.messages: + print(Fore.RED + "\nWARNING: Potentially significant numerical dispersion. Estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq']) + Style.RESET_ALL) + elif results['deltavp'] and G.messages: + print("\nNumerical dispersion analysis: estimated largest physical phase-velocity error is {:.2f}% in material '{}' whose wavelength sampled by {} cells. Maximum significant frequency estimated as {:g}Hz".format(results['deltavp'], results['material'].ID, results['N'], results['maxfreq'])) # If geometry information to be reused between model runs else: @@ -363,20 +401,45 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern os.chdir(curdir) basename, ext = os.path.splitext(inputfilename) outputfile = os.path.join(outputdir, basename + appendmodelnumber + '.out') + outputfile_fluxes = os.path.join(outputdir, G.title + '_fluxes.out') if G.messages: print('\nOutput file: {}\n'.format(outputfile)) + if G.fluxes: + print('\nFluxes output file: {}\n'.format(outputfile_fluxes)) - # Main FDTD solving functions for either CPU or GPU - if G.gpu is None: - tsolve = solve_cpu(currentmodelrun, modelend, G) + + # Main FDTD solving functions for either CPU or GPU when no scattering + print("\n" + str(G.scattering) + "\n") + if not G.scattering: + if G.gpu is None: + tsolve = solve_cpu(currentmodelrun, modelend, G) + else: + tsolve, memsolve = solve_gpu(currentmodelrun, modelend, G) else: - tsolve, memsolve = solve_gpu(currentmodelrun, modelend, G) + if G.gpu: print(Fore.RED + "\nGPU is not yet supported for scattering, it will run on the CPU.") + tsolve = solve_scattering(currentmodelrun, modelend, G) + + # Calculate the fluxes through the different surfaces, then sum + if G.fluxes: + print('\nCalculating fluxes...') + for flux in G.fluxes: + if G.scattering: + flux.calculate_Poynting_frequency_flux(G, incident= True) + flux.calculate_Poynting_frequency_flux(G) + G.total_flux = G.fluxes[0].Poynting_frequency_flux + print(G.total_flux.shape) + for i in range(1, len(G.fluxes)): + G.total_flux += G.fluxes[i].Poynting_frequency_flux + print('Total flux: {}'.format(G.total_flux)) + save_file_h5py(outputfile_fluxes, G) + # Write an output file in HDF5 format write_hdf5_outputfile(outputfile, G) # Write any snapshots to file if G.snapshots: + assert not G.cylindrical, "Snapshots are not supported in cylindrical mode." # Create directory and construct filename from user-supplied name and model run number snapshotdir = os.path.join(G.inputdirectory, os.path.splitext(G.inputfilename)[0] + '_snaps' + appendmodelnumber) if not os.path.exists(snapshotdir): @@ -405,7 +468,7 @@ def run_model(args, currentmodelrun, modelend, numbermodelruns, inputfile, usern return tsolve -def solve_cpu(currentmodelrun, modelend, G): +def solve_cpu(currentmodelrun, modelend, G: FDTDGrid): """ Solving using FDTD method on CPU. Parallelised using Cython (OpenMP) for electric and magnetic field updates, and PML updates. @@ -418,59 +481,105 @@ def solve_cpu(currentmodelrun, modelend, G): Returns: tsolve (float): Time taken to execute solving """ - + rcoord = 0 + zcoord_cyl = 50 + xcoord = 50 + rcoord - 1 + ycoord = 50 + zcoord = zcoord_cyl tsolvestart = timer() - + # file = 0 + # if G.cylindrical: + # file = open(os.path.join(G.inputdirectory,'cylindrical.txt'), 'w') + # else: + # file = open(os.path.join(G.inputdirectory, 'cartesian.txt'), 'w') for iteration in tqdm(range(G.iterations), desc='Running simulation, model ' + str(currentmodelrun) + '/' + str(modelend), ncols=get_terminal_width() - 1, file=sys.stdout, disable=not G.progressbars): + # file.write("ITERATION: " + str(iteration) + " ==========================\n") # Store field component values for every receiver and transmission line - store_outputs(iteration, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz, G) - + # print('\n=============================================================') + if not G.cylindrical: + store_outputs(iteration, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz, G) + else: + store_outputs_cyl(iteration, G.Er_cyl, G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl, G) # Store any snapshots for snap in G.snapshots: if snap.time == iteration + 1: snap.store(G) # Update magnetic field components - update_magnetic(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsH, G.ID, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + if not G.cylindrical: + update_magnetic(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsH, G.ID, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + # file.write("UPDATE MAGNETIQUE\n Ex= "+ str(G.Ex[xcoord,ycoord,zcoord]) + "\n Ey= " + str(G.Ey[xcoord,ycoord,zcoord]) + "\n Ez= " + str(G.Ez[xcoord,ycoord,zcoord]) + "\n Hx= " + str(G.Hx[xcoord,ycoord,zcoord]) + "\n Hy= " + str(G.Hy[xcoord,ycoord,zcoord]) + "\n Hz= " + str(G.Hz[xcoord,ycoord,zcoord]) + "\n") + else: + update_magnetic_cyl(G.nr_cyl, G.nz_cyl, G.m_cyl, G.nthreads, G.updatecoeffsH, G.ID, G.Er_cyl, G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl) + # update_magnetic_origin(G.nr_cyl, G.nz_cyl, G.m_cyl, G.nthreads, G.dt, mu0, G.dr_cyl, G.updatecoeffsE, G.updatecoeffsH, G.ID, G.Er_cyl, G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl) + # file.write("UPDATE MAGNETIQUE\n Er_cyl= " + str(np.real(G.Er_cyl[rcoord, 0, zcoord_cyl])) + "\n Ephi_cyl= " + str(np.real(G.Ephi_cyl[rcoord, 0, zcoord_cyl])) + "\n Ez_cyl= " + str(np.real(G.Ez_cyl[rcoord, 0, zcoord_cyl])) + "\n Hr_cyl= " + str(np.real(G.Hr_cyl[rcoord, 0, zcoord_cyl])) + "\n Hphi_cyl= " + str(np.real(G.Hphi_cyl[rcoord, 0, zcoord_cyl])) + "\n Hz_cyl= " + str(np.real(G.Hz_cyl[rcoord, 0, zcoord_cyl])) + "\n") + # file.write("\n") # Update magnetic field components with the PML correction for pml in G.pmls: - pml.update_magnetic(G) + pml.update_magnetic(G) #No need to check for cylindrical mode here as there exists PML_cyl class with the same method + + # if G.cylindrical: + # file.write("UPDATE PML MAGNETIQUE\n Er_cyl= " + str(np.real(G.Er_cyl[rcoord, 0, zcoord_cyl])) + "\n Ephi_cyl= " + str(np.real(G.Ephi_cyl[rcoord, 0, zcoord_cyl])) + "\n Ez_cyl= " + str(np.real(G.Ez_cyl[rcoord, 0, zcoord_cyl])) + "\n Hr_cyl= " + str(np.real(G.Hr_cyl[rcoord, 0, zcoord_cyl])) + "\n Hphi_cyl[i]= " + str(np.real(G.Hphi_cyl[rcoord, 0, zcoord_cyl])) + " / Hphi_cyl[i-1]= " + str(np.real(G.Hphi_cyl[rcoord - 1, 0, zcoord_cyl])) + "\n Hz_cyl= " + str(np.real(G.Hz_cyl[rcoord, 0, zcoord_cyl])) + "\n") + # else: + # file.write("UPDATE PML MAGNETIQUE\n Ex= "+ str(G.Ex[xcoord,ycoord,zcoord]) + "\n Ey= " + str(G.Ey[xcoord,ycoord,zcoord]) + "\n Ez= " + str(G.Ez[xcoord,ycoord,zcoord]) + "\n Hx= " + str(G.Hx[xcoord,ycoord,zcoord]) + "\n Hy= " + str(G.Hy[xcoord,ycoord,zcoord]) + "\n Hz= " + str(G.Hz[xcoord,ycoord,zcoord]) + "\n") + # file.write("\n") # Update magnetic field components from sources for source in G.transmissionlines + G.magneticdipoles: + if G.cylindrical: + raise CmdInputError("Magnetic dipole and transmission lines sources are not supported in cylindrical mode.") source.update_magnetic(iteration, G.updatecoeffsH, G.ID, G.Hx, G.Hy, G.Hz, G) # Update electric field components # All materials are non-dispersive so do standard update if Material.maxpoles == 0: - update_electric(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsE, G.ID, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + if not G.cylindrical: + update_electric(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsE, G.ID, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) + # file.write("UPDATE ELECTRIQUE\n Ex= "+ str(G.Ex[xcoord,ycoord,zcoord]) + "\n Ey= " + str(G.Ey[xcoord,ycoord,zcoord]) + "\n Ez= " + str(G.Ez[xcoord,ycoord,zcoord]) + "\n Hx= " + str(G.Hx[xcoord,ycoord,zcoord]) + "\n Hy= " + str(G.Hy[xcoord,ycoord,zcoord]) + "\n Hz= " + str(G.Hz[xcoord,ycoord,zcoord]) + "\n") + else: + update_electric_cyl(G.nr_cyl, G.nz_cyl, G.m_cyl, G.nthreads, G.dr_cyl, G.dz_cyl, G.updatecoeffsE, G.ID, G.Er_cyl, G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl) + # update_electric_origin(G.nr_cyl, G.nz_cyl, G.m_cyl, G.nthreads, G.dt, mu0, G.dr_cyl, G.updatecoeffsE, G.updatecoeffsH, G.ID, G.Er_cyl, G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl) + # file.write("UPDATE ELECTRIQUE\n Er_cyl= " + str(np.real(G.Er_cyl[rcoord, 0, zcoord_cyl])) + "\n Ephi_cyl= " + str(np.real(G.Ephi_cyl[rcoord, 0, zcoord_cyl])) + "\n Ez_cyl= " + str(np.real(G.Ez_cyl[rcoord, 0, zcoord_cyl])) + "\n Hr_cyl= " + str(np.real(G.Hr_cyl[rcoord, 0, zcoord_cyl])) + "\n Hphi_cyl= " + str(np.real(G.Hphi_cyl[rcoord, 0, zcoord_cyl])) + "\n Hz_cyl= " + str(np.real(G.Hz_cyl[rcoord, 0, zcoord_cyl])) + "\n") + # file.write("\n") # If there are any dispersive materials do 1st part of dispersive update # (it is split into two parts as it requires present and updated electric field values). elif Material.maxpoles == 1: + assert not G.cylindrical, "Dispersive materials are not supported in cylindrical mode." update_electric_dispersive_1pole_A(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsE, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) elif Material.maxpoles > 1: + assert not G.cylindrical, "Dispersive materials are not supported in cylindrical mode." update_electric_dispersive_multipole_A(G.nx, G.ny, G.nz, G.nthreads, Material.maxpoles, G.updatecoeffsE, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez, G.Hx, G.Hy, G.Hz) # Update electric field components with the PML correction for pml in G.pmls: - pml.update_electric(G) + pml.update_electric(G) #No need to check for cylindrical mode here as there exists PML_cyl class with the same method # Update electric field components from sources (update any Hertzian dipole sources last) for source in G.voltagesources + G.transmissionlines + G.hertziandipoles: - source.update_electric(iteration, G.updatecoeffsE, G.ID, G.Ex, G.Ey, G.Ez, G) + assert len(G.transmissionlines) == 0 or not G.cylindrical, "Transmission lines are not supported in cylindrical mode." + if not G.cylindrical: + source.update_electric(iteration, G.updatecoeffsE, G.ID, G.Ex, G.Ey, G.Ez, G) + else: + source.update_electric(iteration, G.updatecoeffsE, G.ID, G.Er_cyl, G.Ephi_cyl, G.Ez_cyl, G) # If there are any dispersive materials do 2nd part of dispersive update # (it is split into two parts as it requires present and updated electric # field values). Therefore it can only be completely updated after the # electric field has been updated by the PML and source updates. if Material.maxpoles == 1: + assert not G.cylindrical, "Dispersive materials are not supported in cylindrical mode." update_electric_dispersive_1pole_B(G.nx, G.ny, G.nz, G.nthreads, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez) elif Material.maxpoles > 1: + assert not G.cylindrical, "Dispersive materials are not supported in cylindrical mode." update_electric_dispersive_multipole_B(G.nx, G.ny, G.nz, G.nthreads, Material.maxpoles, G.updatecoeffsdispersive, G.ID, G.Tx, G.Ty, G.Tz, G.Ex, G.Ey, G.Ez) + + for flux in G.fluxes: + flux.save_fields_fluxes(G, iteration) + tsolve = timer() - tsolvestart - + # file.close() return tsolve diff --git a/gprMax/pml.py b/gprMax/pml.py index cd1c36906..30785ef42 100644 --- a/gprMax/pml.py +++ b/gprMax/pml.py @@ -162,7 +162,7 @@ class PML(object): # xplus, yplus, zplus - absorption increases in positive direction of x-axis, y-axis, or z-axis directions = ['xminus', 'yminus', 'zminus', 'xplus', 'yplus', 'zplus'] - def __init__(self, G, ID=None, direction=None, xs=0, xf=0, ys=0, yf=0, zs=0, zf=0): + def __init__(self, G, ID=None, direction=None, xs=0, xf=0, ys=0, yf=0, zs=0, zf=0, rs_cyl = 0, rf_cyl = 0, zs_cyl = 0, zf_cyl = 0): """ Args: G (class): Grid class instance - holds essential parameters describing the model. @@ -363,6 +363,287 @@ def gpu_update_magnetic(self, G): self.update_magnetic_gpu(np.int32(self.xs), np.int32(self.xf), np.int32(self.ys), np.int32(self.yf), np.int32(self.zs), np.int32(self.zf), np.int32(self.HPhi1_gpu.shape[1]), np.int32(self.HPhi1_gpu.shape[2]), np.int32(self.HPhi1_gpu.shape[3]), np.int32(self.HPhi2_gpu.shape[1]), np.int32(self.HPhi2_gpu.shape[2]), np.int32(self.HPhi2_gpu.shape[3]), np.int32(self.thickness), G.ID_gpu.gpudata, G.Ex_gpu.gpudata, G.Ey_gpu.gpudata, G.Ez_gpu.gpudata, G.Hx_gpu.gpudata, G.Hy_gpu.gpudata, G.Hz_gpu.gpudata, self.HPhi1_gpu.gpudata, self.HPhi2_gpu.gpudata, self.HRA_gpu.gpudata, self.HRB_gpu.gpudata, self.HRE_gpu.gpudata, self.HRF_gpu.gpudata, floattype(self.d), block=G.tpb, grid=self.bpg) +class PML_cyl(object): + + boundaryIDs_cyl = ['r', 'z'] + directions_cyl = ['r', 'z'] + + def __init__(self, G, direction = None, rs_cyl = 0, rf_cyl = 0, zs_cyl = 0, zf_cyl = 0): + self.sigma_max = np.array([0,0]) #sigma_max[0] = sigma_max_rho, sigma_max[1] = sigma_max_z + self.kappa_max = np.array([5,5]) + self.alpha_max = np.array([0.2,0.2]) + self.m = 4 #Not the same as G.m_cyl: here, it's the power in alpha, sigma, kappa, b + + self.direction = direction + + self.rs_cyl = rs_cyl + self.rf_cyl = rf_cyl + self.zs_cyl_upper = zs_cyl + self.zf_cyl_lower = zf_cyl + self.nr_cyl = rf_cyl - rs_cyl + self.nz_cyl = zf_cyl + + assert G.cylindrical, "PML_cyl can only be called when in cylindrical mode" + assert self.direction in PML_cyl.directions_cyl, "PML_cyl direction must be one of: " + str(PML_cyl.directions_cyl) + + if self.direction[0] == 'r': + self.d_cyl = G.dr_cyl + self.thickness = self.nr_cyl + + self.alpha_rho = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.kappa_rho = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.sigma_rho = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.b_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + + self.Omega_term_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.alpha_term_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.Ksi_term_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.Lambda_term_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.R_term_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.Psi_term_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + self.Theta_term_list = np.zeros((self.thickness, 1, G.nz_cyl), floattype) + + self.Ers = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.QErs = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.QEphi = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.Ephi_ = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.XQEphi_ = np.zeros((self.thickness, 1, G.nz_cyl, self.thickness), np.complex128) + self.QEz = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.Ezs = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.XQEzs = np.zeros((self.thickness, 1, G.nz_cyl, self.thickness), np.complex128) + self.Hrs = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.QHrs = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.QHphi = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.Hphi_ = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.QHphi_ = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.XQHphi_ = np.zeros((self.thickness, 1, G.nz_cyl, self.thickness), np.complex128) + self.QHz = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.Hzs = np.zeros((self.thickness, 1, G.nz_cyl), np.complex128) + self.XQHzs = np.zeros((self.thickness, 1, G.nz_cyl, self.thickness), np.complex128) + + elif self.direction[0] == 'z': + self.d_cyl = G.dz_cyl + self.thickness = self.nz_cyl + + self.sigma_z = np.zeros((self.rs_cyl, 1, 2 * self.thickness), floattype) + self.kappa_z = np.zeros((self.rs_cyl, 1, 2 * self.thickness), floattype) + self.alpha_z = np.zeros((self.rs_cyl, 1, 2 * self.thickness), floattype) + + self.Pi_term_list = np.zeros((self.rs_cyl, 1, 2 * self.thickness), floattype) + self.Delta_term_list = np.zeros((self.rs_cyl, 1, 2 * self.thickness), floattype) + self.Rho_term_list = np.zeros((self.rs_cyl, 1, 2 * self.thickness), floattype) + + self.JEphi = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.JEr = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QEphi = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QEr = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QJEphi = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QJEr = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + + self.JHphi = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.JHr = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QHphi = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QHr = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QJHphi = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + self.QJHr = np.zeros((self.rs_cyl, 1, 2 * self.thickness), np.complex128) + + def calculate_sigma_max_cyl(self, G): + + self.sigma_max[0] = (self.m + 1) / (150 * np.pi * G.dr_cyl) + self.sigma_max[1] = (self.m + 1) / (150 * np.pi * G.dz_cyl) + + def update_sigma_cyl(self, G): + if self.direction[0] == 'z': + #Lower slab + for k in range(self.thickness): + value = self.sigma_max[1] * ((self.zf_cyl_lower - self.thickness - k)/self.thickness)**self.m + for j in range(self.rs_cyl): + self.sigma_z[j,0,k] = value + #Upper slab + for k in range(self.thickness): + value = self.sigma_max[1] * (k/self.thickness)**self.m + for j in range(self.rs_cyl): + self.sigma_z[j,0,k + self.thickness] = value + elif self.direction[0] == 'r': + for j in range(0,self.thickness): + value = self.sigma_max[0] * (j/self.thickness)**self.m + for k in range(G.nz_cyl): + self.sigma_rho[j,0,k] = value + else: + raise GeneralError("Only 'z' and 'r' directions are supported") + + def update_kappa_cyl(self, G): + kappa_mod = self.kappa_max - 1 + if self.direction[0] == 'z': + #Lower slab + for k in range(self.thickness): + value = (1 + kappa_mod[1] * (self.zf_cyl_lower - self.thickness - k)**self.m)/(self.thickness**self.m) + for j in range(self.rs_cyl): + self.kappa_z[j,0,k] = value + #Upper slab + for k in range(self.thickness): + value = (1 + kappa_mod[1] * k**self.m)/(self.thickness**self.m) + for j in range(self.rs_cyl): + self.kappa_z[j,0,k + self.thickness] = value + elif self.direction[0] == 'r': + for j in range(0,self.thickness): + value = (1 + kappa_mod[0] * j**self.m)/self.thickness**self.m + for k in range(G.nz_cyl): + self.kappa_rho[j,0,k] = value + else: + raise GeneralError("Only 'z' and 'r' directions are supported") + + def update_alpha_cyl(self, G): + if self.direction[0] == 'z': + #Lower slab + for k in range(1,self.thickness+1): + value = self.alpha_max[1] * (1 - (self.zf_cyl_lower - self.thickness - k)/self.thickness) + if value == 0: + raise GeneralError("Alpha cannot be zero in PML") + for j in range(self.rs_cyl): + self.alpha_z[j,0,k] = value + #Upper slab + for k in range(1,self.thickness+1): + value = self.alpha_max[1] * (1 - k/self.thickness) + for j in range(self.rs_cyl): + self.alpha_z[j,0,k + self.thickness] = value + elif self.direction[0] == 'r': + for j in range(0,self.thickness): + value = self.alpha_max[0] * (1 - j/self.thickness) + for k in range(G.nz_cyl): + self.alpha_rho[j,0,k] = value + else: + raise GeneralError("Only 'z' and 'r' directions are supported") + + def update_b_list(self, G): + if self.direction[0] == 'z': + return # No b_list in cylindrical PML in z direction + + elif self.direction[0] == 'r': + for j in range(0,self.thickness): + value = (j+self.rs_cyl) * G.dr_cyl + (self.kappa_max[0] - 1) / (self.m+1) * (j/self.thickness) ** (self.m+1) + for k in range(G.nz_cyl): + self.b_list[j,0,k] = value + print("b_list updated for cylindrical PML in r direction:", self.b_list) + + else: + raise GeneralError("Only 'z' and 'r' directions are supported for cylindrical PML") + + def update_term_list(self, G): + if self.direction[0] == 'z': + #Both slabs are processed by the same funciton at the same time + pmlmodule = 'gprMax.pml_updates.pml_updates_CYLINDRICAL_ext' + func = getattr(import_module(pmlmodule), 'initialize_constant_lists_z') + func(self.rs_cyl, self.thickness, G.dt, G.nthreads, self.alpha_z, self.sigma_z, self.kappa_z, self.Pi_term_list, self.Delta_term_list, self.Rho_term_list) + elif self.direction[0] == 'r': + #idem + pmlmodule = 'gprMax.pml_updates.pml_updates_CYLINDRICAL_ext' + func = getattr(import_module(pmlmodule), 'initialize_constant_lists_rho') + func(self.rs_cyl, self.rf_cyl, self.nz_cyl, self.thickness, G.dr_cyl, G.dt, G.nthreads, self.sigma_rho, + self.alpha_rho, self.kappa_rho, self.Omega_term_list, self.Ksi_term_list, self.Lambda_term_list, self.Psi_term_list, + self.Theta_term_list, self.alpha_term_list, self.R_term_list) + + def update_electric(self, G): + """This functions updates electric field components with the PML correction. + + Args: + G (class): Grid class instance - holds essential parameters describing the model. + """ + + pmlmodule = 'gprMax.pml_updates.pml_updates_CYLINDRICAL_ext' + if self.direction[0] == 'r': + func = getattr(import_module(pmlmodule), 'E_update_r_slab') + func(self.rs_cyl, + self.m, + G.nz_cyl, + self.thickness, + G.dr_cyl, + G.dz_cyl, + G.dt, + G.nthreads, + G.ID, + G.Er_cyl, + self.Ers, + self.QErs, + G.Ephi_cyl, + self.QEphi, + self.Ephi_, + self.XQEphi_, + G.Ez_cyl, + self.QEz, + self.Ezs, + self.XQEzs, + G.Hr_cyl, + self.Hrs, + self.QHrs, + G.Hphi_cyl, + self.QHphi, + self.Hphi_, + self.QHphi_, + self.XQHphi_, + G.Hz_cyl, + self.QHz, + self.Hzs, + self.XQHzs, + self.alpha_rho, + self.sigma_rho, + self.kappa_rho, + self.b_list, + self.Omega_term_list, + self.alpha_term_list, + self.Ksi_term_list, + self.Lambda_term_list, + self.R_term_list, + self.Psi_term_list, + self.Theta_term_list) + elif self.direction[0] == 'z': + func = getattr(import_module(pmlmodule), 'E_update_upper_slab') + func(self.rs_cyl, self.zs_cyl_upper, self.thickness, G.dr_cyl, G.dz_cyl, G.dt, self.m, G.nthreads, G.Er_cyl, + G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl, self.JEphi, self.JEr, self.QEphi, + self.QEr, self.QJEphi, self.QJEr, self.Pi_term_list, self.Delta_term_list, self.Rho_term_list) + + func = getattr(import_module(pmlmodule), 'E_update_lower_slab') + func(self.rs_cyl, self.zf_cyl_lower, self.thickness, G.dr_cyl, G.dz_cyl, G.dt, self.m, G.nthreads, G.Er_cyl, + G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl, self.JEphi, self.JEr, self.QEphi, + self.QEr, self.QJEphi, self.QJEr, self.Pi_term_list, self.Delta_term_list, self.Rho_term_list) + else: + raise GeneralError("Only 'z' and 'r' directions are supported for cylindrical PML") + + + def update_magnetic(self, G): + """This functions updates magnetic field components with the PML correction. + + Args: + G (class): Grid class instance - holds essential parameters describing the model. + """ + + pmlmodule = 'gprMax.pml_updates.pml_updates_CYLINDRICAL_ext' + if self.direction[0] == 'r': + func = getattr(import_module(pmlmodule), 'H_update_r_slab') + func(self.rs_cyl, self.m, G.nz_cyl, self.thickness, G.dr_cyl, G.dz_cyl, G.dt, G.nthreads, G.ID, G.Er_cyl, self.Ers, + self.QErs, G.Ephi_cyl, self.QEphi, self.Ephi_, self.XQEphi_, G.Ez_cyl, self.QEz, self.Ezs, self.XQEzs, G.Hr_cyl, + self.Hrs, self.QHrs, G.Hphi_cyl, self.QHphi, self.Hphi_, self.QHphi_, self.XQHphi_, G.Hz_cyl, self.QHz, self.Hzs, + self.XQHzs, self.alpha_rho, self.sigma_rho, self.kappa_rho, self.b_list, self.Omega_term_list, self.alpha_term_list, + self.Ksi_term_list, self.Lambda_term_list, self.R_term_list, self.Psi_term_list, self.Theta_term_list) + print("Ne devrait pas être nul: ", self.rs_cyl) + #func = getattr(import_module(pmlmodule), 'test') + elif self.direction[0] == 'z': + func = getattr(import_module(pmlmodule), 'H_update_upper_slab') + func(self.rs_cyl, + self.zs_cyl_upper, self.thickness, G.dr_cyl, G.dz_cyl, G.dt, self.m, G.nthreads, G.Er_cyl, + G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl, self.JHphi, self.JHr, self.QHphi, + self.QHr, self.QJHphi, self.QJHr, self.Pi_term_list, self.Delta_term_list, self.Rho_term_list) + + func = getattr(import_module(pmlmodule), 'H_update_lower_slab') + func(self.rs_cyl, self.zf_cyl_lower, self.thickness, G.dr_cyl, G.dz_cyl, G.dt, self.m, G.nthreads, G.Er_cyl, + G.Ephi_cyl, G.Ez_cyl, G.Hr_cyl, G.Hphi_cyl, G.Hz_cyl, self.JHphi, self.JHr, self.QHphi, + self.QHr, self.QJHphi, self.QJHr, self.Pi_term_list, self.Delta_term_list, self.Rho_term_list) + + #func = getattr(import_module(pmlmodule), 'test') + #func() + else: + raise GeneralError("Only 'z' and 'r' directions are supported for cylindrical PML") + def build_pmls(G, pbar): """ @@ -374,56 +655,79 @@ def build_pmls(G, pbar): G (class): Grid class instance - holds essential parameters describing the model. pbar (class): Progress bar class instance. """ - - for key, value in G.pmlthickness.items(): - if value > 0: - sumer = 0 # Sum of relative permittivities in PML slab - summr = 0 # Sum of relative permeabilities in PML slab - - if key[0] == 'x': - if key == 'x0': - pml = PML(G, ID=key, direction='xminus', xf=value, yf=G.ny, zf=G.nz) - elif key == 'xmax': - pml = PML(G, ID=key, direction='xplus', xs=G.nx - value, xf=G.nx, yf=G.ny, zf=G.nz) - G.pmls.append(pml) - for j in range(G.ny): - for k in range(G.nz): - numID = G.solid[pml.xs, j, k] - material = next(x for x in G.materials if x.numID == numID) - sumer += material.er - summr += material.mr - averageer = sumer / (G.ny * G.nz) - averagemr = summr / (G.ny * G.nz) - - elif key[0] == 'y': - if key == 'y0': - pml = PML(G, ID=key, direction='yminus', yf=value, xf=G.nx, zf=G.nz) - elif key == 'ymax': - pml = PML(G, ID=key, direction='yplus', ys=G.ny - value, xf=G.nx, yf=G.ny, zf=G.nz) - G.pmls.append(pml) - for i in range(G.nx): - for k in range(G.nz): - numID = G.solid[i, pml.ys, k] - material = next(x for x in G.materials if x.numID == numID) - sumer += material.er - summr += material.mr - averageer = sumer / (G.nx * G.nz) - averagemr = summr / (G.nx * G.nz) - - elif key[0] == 'z': - if key == 'z0': - pml = PML(G, ID=key, direction='zminus', zf=value, xf=G.nx, yf=G.ny) - elif key == 'zmax': - pml = PML(G, ID=key, direction='zplus', zs=G.nz - value, xf=G.nx, yf=G.ny, zf=G.nz) - G.pmls.append(pml) - for i in range(G.nx): + if not G.cylindrical: + for key, value in G.pmlthickness.items(): + if value > 0: + sumer = 0 # Sum of relative permittivities in PML slab + summr = 0 # Sum of relative permeabilities in PML slab + + if key[0] == 'x': + if key == 'x0': + pml = PML(G, ID=key, direction='xminus', xf=value, yf=G.ny, zf=G.nz) + elif key == 'xmax': + pml = PML(G, ID=key, direction='xplus', xs=G.nx - value, xf=G.nx, yf=G.ny, zf=G.nz) + G.pmls.append(pml) for j in range(G.ny): - numID = G.solid[i, j, pml.zs] - material = next(x for x in G.materials if x.numID == numID) - sumer += material.er - summr += material.mr - averageer = sumer / (G.nx * G.ny) - averagemr = summr / (G.nx * G.ny) - - pml.calculate_update_coeffs(averageer, averagemr, G) - pbar.update() + for k in range(G.nz): + numID = G.solid[pml.xs, j, k] + material = next(x for x in G.materials if x.numID == numID) + sumer += material.er + summr += material.mr + averageer = sumer / (G.ny * G.nz) + averagemr = summr / (G.ny * G.nz) + + elif key[0] == 'y': + if key == 'y0': + pml = PML(G, ID=key, direction='yminus', yf=value, xf=G.nx, zf=G.nz) + elif key == 'ymax': + pml = PML(G, ID=key, direction='yplus', ys=G.ny - value, xf=G.nx, yf=G.ny, zf=G.nz) + G.pmls.append(pml) + for i in range(G.nx): + for k in range(G.nz): + numID = G.solid[i, pml.ys, k] + material = next(x for x in G.materials if x.numID == numID) + sumer += material.er + summr += material.mr + averageer = sumer / (G.nx * G.nz) + averagemr = summr / (G.nx * G.nz) + + elif key[0] == 'z': + if key == 'z0': + pml = PML(G, ID=key, direction='zminus', zf=value, xf=G.nx, yf=G.ny) + elif key == 'zmax': + pml = PML(G, ID=key, direction='zplus', zs=G.nz - value, xf=G.nx, yf=G.ny, zf=G.nz) + G.pmls.append(pml) + for i in range(G.nx): + for j in range(G.ny): + numID = G.solid[i, j, pml.zs] + material = next(x for x in G.materials if x.numID == numID) + sumer += material.er + summr += material.mr + averageer = sumer / (G.nx * G.ny) + averagemr = summr / (G.nx * G.ny) + + pml.calculate_update_coeffs(averageer, averagemr, G) + pbar.update() + else: + for key, value in G.pmlthickness_cyl.items(): + value_r = 0 + if value > 0: + if key[0] == 'r': + assert value < G.nr_cyl, "PML thickness in r direction cannot be larger than the grid size in r direction" + pml = PML_cyl(G, direction= 'r', rs_cyl= G.nr_cyl - value, rf_cyl= G.nr_cyl, zf_cyl= G.nz_cyl) + pml.update_alpha_cyl(G) + pml.update_kappa_cyl(G) + pml.calculate_sigma_max_cyl(G) + pml.update_sigma_cyl(G) + pml.update_term_list(G) + G.pmls.append(pml) + value_r = value + elif key[0] == 'z': + assert value < G.nz_cyl, "PML thickness in z direction cannot be larger than the grid size in z direction" + pml = PML_cyl(G, direction= 'z', rf_cyl= G.nr_cyl - value_r,zs_cyl= value ,zf_cyl= G.nz_cyl - value) + pml.update_alpha_cyl(G) + pml.update_kappa_cyl(G) + pml.calculate_sigma_max_cyl(G) + pml.update_sigma_cyl(G) + pml.update_term_list(G) + G.pmls.append(pml) \ No newline at end of file diff --git a/gprMax/pml_updates/pml_updates_CYLINDRICAL_ext.pyx b/gprMax/pml_updates/pml_updates_CYLINDRICAL_ext.pyx new file mode 100644 index 000000000..de98ff873 --- /dev/null +++ b/gprMax/pml_updates/pml_updates_CYLINDRICAL_ext.pyx @@ -0,0 +1,1349 @@ +# Copyright (C) 2025: Quandela +# Authors: Quentin David +# +# This file is added to the modified code of gprMax allowing for cylindrical coordinate. +# +# gprMax is free software: you can redistribute it and/or modify +# it under the terms of the GNU GenRAl Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# gprMax is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU GenRAl Public License for more details. +# +# You should have received a copy of the GNU GenRAl Public License +# along with gprMax. If not, see . +# +#To get information as to why the PMLs are updated the way they are, please +# check https://repository.mines.edu/server/api/core/bitstreams/2cbef3b2-38af-4c25-bb9c-55bdd3abec74/content + +import numpy as np +cimport numpy as np +from cython.parallel import prange + +from gprMax.constants cimport floattype_t +#from scipy.constants import epsilon_0 as e0 +#from scipy.constants import mu_0 as mu0 + +from libc.stdio cimport printf + +cdef extern from "complex.h": + double complex I + +cdef double mu0 = 4 * 3.141592653589793 * 1e-7 +cdef double e0 = 8.854187817e-12 +cdef double e = 2.718281828459045 + + + +################## Conversion functions needed as we can't use operations on np.complex without gil ############################ + +from libc.stdlib cimport malloc, free +from libc cimport complex +from cython cimport boundscheck, wraparound, nonecheck + + + +cdef extern from "complex.h": + pass # pour éviter certains warnings avec Cython + + +@boundscheck(False) +@wraparound(False) +@nonecheck(False) +cdef double complex*** alloc_and_copy_complex3D(np.complex128_t[:, :, ::1] arr): + """ + Alloue un tableau 3D C (double complex***) et copie les données d'un memoryview NumPy. + + Args: + arr (np.complex128_t[:, :, ::1]): Tableau NumPy d'entrée + + Returns: + double complex*** : Tableau C alloué et rempli + """ + cdef Py_ssize_t nx = arr.shape[0] + cdef Py_ssize_t ny = arr.shape[1] + cdef Py_ssize_t nz = arr.shape[2] + + cdef double complex*** out + cdef Py_ssize_t i, j, k + + # Allocation des pointeurs + out = malloc(nx * sizeof(double complex**)) + for i in range(nx): + out[i] = malloc(ny * sizeof(double complex*)) + for j in range(ny): + out[i][j] = malloc(nz * sizeof(double complex)) + + # Copie des données + for i in range(nx): + for j in range(ny): + for k in range(nz): + out[i][j][k] = arr[i, j, k] + + return out + + +@boundscheck(False) +@wraparound(False) +@nonecheck(False) +cdef double complex**** alloc_and_copy_complex4D(np.complex128_t[:, :, :, ::1] arr): + """ + Alloue un tableau 4D C (double complex***) et copie les données d'un memoryview NumPy. + + Args: + arr (np.complex128_t[:, :, ::1]): Tableau NumPy d'entrée + + Returns: + double complex*** : Tableau C alloué et rempli + """ + cdef Py_ssize_t nx = arr.shape[0] + cdef Py_ssize_t ny = arr.shape[1] + cdef Py_ssize_t nz = arr.shape[2] + cdef Py_ssize_t n4 = arr.shape[3] + + cdef double complex**** out + cdef Py_ssize_t i, j, k, l + + # Allocation des pointeurs + out = malloc(nx * sizeof(double complex**)) + for i in range(nx): + out[i] = malloc(ny * sizeof(double complex*)) + for j in range(ny): + out[i][j] = malloc(nz * sizeof(double complex)) + for k in range(nz): + out[i][j][k] = malloc(n4 * sizeof(double complex)) + + # Copie des données + for i in range(nx): + for j in range(ny): + for k in range(nz): + for l in range(n4): + out[i][j][k][l] = arr[i, j, k, l] + + return out + +cdef void free_complex3D(double complex*** arr, Py_ssize_t nx, Py_ssize_t ny): + cdef Py_ssize_t i, j + for i in range(nx): + for j in range(ny): + free(arr[i][j]) + free(arr[i]) + free(arr) + +cdef void free_complex4D(double complex**** arr, Py_ssize_t nx, Py_ssize_t ny, Py_ssize_t nz): + cdef Py_ssize_t i, j, k + for i in range(nx): + for j in range(ny): + for k in range(nz): + free(arr[i][j][k]) + free(arr[i][j]) + free(arr[i]) + free(arr) + + +@boundscheck(False) +@wraparound(False) +@nonecheck(False) +cdef void copy_complex3D_to_numpy(double complex*** src, + np.complex128_t[:, :, ::1] dest): + """ + Copie les données d’un tableau C (double complex***) dans un memoryview NumPy, + sans avoir besoin de spécifier les dimensions. + + Args: + src : tableau C (double complex***) + dest : memoryview NumPy déjà alloué + """ + cdef Py_ssize_t nx = dest.shape[0] + cdef Py_ssize_t ny = dest.shape[1] + cdef Py_ssize_t nz = dest.shape[2] + cdef Py_ssize_t i, j, k + + for i in range(nx): + for j in range(ny): + for k in range(nz): + dest[i, j, k] = src[i][j][k] + +@boundscheck(False) +@wraparound(False) +@nonecheck(False) +cdef void copy_complex4D_to_numpy(double complex**** src, + np.complex128_t[:, :, :, ::1] dest): + """ + Copie les données d’un tableau C (double complex***) dans un memoryview NumPy, + sans avoir besoin de spécifier les dimensions. + + Args: + src : tableau C (double complex***) + dest : memoryview NumPy déjà alloué + """ + cdef Py_ssize_t nx = dest.shape[0] + cdef Py_ssize_t ny = dest.shape[1] + cdef Py_ssize_t nz = dest.shape[2] + cdef Py_ssize_t n4 = dest.shape[3] + cdef Py_ssize_t i, j, k, l + + for i in range(nx): + for j in range(ny): + for k in range(nz): + for l in range(n4): + dest[i, j, k, l] = src[i][j][k][l] + +cdef double complex sum3D_i(floattype_t[:, :, ::1] arr, Py_ssize_t j, Py_ssize_t k) noexcept nogil: + + cdef Py_ssize_t i, nr + cdef double complex out = 0 + + nr = arr.shape[0] + for i in range(0,nr): + out += arr[i,j,k] + return out + +cdef double complex sum4D_i(double complex**** arr,Py_ssize_t i, Py_ssize_t j, Py_ssize_t k, int nl) noexcept nogil : + + cdef Py_ssize_t l + cdef double complex out = 0 + + for l in range(0,nl): + out += arr[i][j][k][l] + return out + +cdef double complex sum_list_c(double complex**** arr, Py_ssize_t i, Py_ssize_t j, Py_ssize_t k, int n4) noexcept nogil: + + cdef Py_ssize_t l + cdef double complex out = 0 + for l in range(n4): + out += arr[i][j][k][l] + return out + +cdef double complex simple_sum(double complex* arr, int n) noexcept nogil: + cdef double complex out = 0 + cdef Py_ssize_t i + for i in range(n): + out += arr[i] + return out + +cdef double complex* Hadamard_product_i(double complex**** X_arr, floattype_t[:, :, ::1] const, Py_ssize_t i, Py_ssize_t j, Py_ssize_t k, int n4) noexcept nogil: + cdef double complex* out + cdef Py_ssize_t l + + out = malloc(n4 * sizeof(double complex)) + for l in range(n4): + out[l] = X_arr[i][j][k][l] * const[l, j, k] + + return out + +################## Update of the PMLs in the r direction ############################### +cpdef void initialize_constant_lists_rho( #OK pour les fomules des constantes + int rs, + int rf, + int nz, + int thickness_r, + float dr, + float dt, + int nthreads, + floattype_t[:, :, ::1] sigma, + floattype_t[:, :, ::1] alpha, + floattype_t[:, :, ::1] kappa, + floattype_t[:, :, ::1] return_omega, + floattype_t[:, :, ::1] return_Ksi_list, + floattype_t[:, :, ::1] return_Lambda_list, + floattype_t[:, :, ::1] return_Psi_list, + floattype_t[:, :, ::1] return_Theta_list, + floattype_t[:, :, ::1] return_alpha, + floattype_t[:, :, ::1] return_R_list, +): + """ + This function computes the Ksi_list and Lambda_list used in the PML updates. Only updated once ! + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + dr (float): spatial discretization + dt (float): timestep + nthreads (int): number of threads to use + sigma, alpha, kappa (memoryview): PML lists + return_omega (memoryview): return list with omega values + return_Ksi_list (memoryview): return list with Ksi_list values. + return_Lambda_list (memoryview): return list with Lambda_list values. + return_Psi_list (memoryview): return list with Psi_list values. + return_Theta_list (memoryview): return list with Theta_list values. + return_alpha (memoryview): return list with exp(-alpha * dt / e0) values. + return_R_list (memoryview): return list with R_list values. + + """ + cdef Py_ssize_t i, k, ii + cdef int nr + cdef float arg, alpha_term, sh, exp + + nr = thickness_r + + for k in prange(0, nz, nogil= True, schedule= 'static', num_threads = nthreads): + for i in range(0, nr): + arg = alpha[i,0,k]*kappa[i,0,k] + sigma[i,0,k] + alpha_term = alpha[i, 0, k] * dt / e0 + sh = (e**(alpha_term/2) - e**(-alpha_term/2))/2 + exp = e**(-alpha_term) + + return_alpha[i,0,k] = exp #OK + return_omega[i, 0, k] = sigma[i, 0, k] * dr * (1 - exp) / alpha[i, 0, k] #OK + return_Ksi_list[i,0,k] = sigma[i,0,k] * dr * sh / e0 #OK + return_Lambda_list[i,0,k] = sigma[i,0,k] * (1 - e**(-arg*dt/(kappa[i,0,k]*e0))) / arg #OK + return_Psi_list[i,0,k] = sigma[i,0,k] * (1-exp) / alpha[i,0,k] #OK + return_Theta_list[i,0,k] = sigma[i,0,k] / e0 * sh #OK + if i == 0: + return_R_list[i,0,k] = 0 + else: + return_R_list[i,0,k] = return_R_list[i-1,0,k] + sigma[i,0,k] * dr / e0 #OK + +cdef void update_XQEphi_( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + double complex*** EPhi, + double complex**** XQEphi_, #XQEphi_[i,j,k] donne la matrice XQEphi_ au point (i,j,k) + floattype_t[:, :, ::1] Omega_term_list, + floattype_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQEphi_ from time n to time n+1 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + EPhi, Omega_term_list, alpha_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQEphi_: list to be updated + """ + cdef Py_ssize_t i, k, ii, iii + cdef int nr + + nr = thickness_r + + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + ii = rs + i + for iii in range(0,nr): + XQEphi_[i][0][k][iii] = Omega_term_list[iii,0,k] * EPhi[ii][0][k] + XQEphi_[i][0][k][iii] * alpha_term_list[iii, 0, k] + +cdef void update_XQEzs( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + double complex*** Ezs, + double complex**** XQEzs, #XQEzs[i,j,k] donne la matrice XQEzs au point (i,j,k) + floattype_t[:, :, ::1] Ksi_term_list, + floattype_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQEphi_ from time n to time n+1 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + Ezs, Ksi_term_list, alpha_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQEzs: list to be updated + """ + cdef Py_ssize_t i, k, iii + cdef int nr + + nr = thickness_r + + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + for iii in range(0,nr): + XQEzs[i][0][k][iii] = Ksi_term_list[iii][0][k] * Ezs[i][0][k] + XQEzs[i][0][k][iii] * alpha_term_list[iii, 0, k] + +cdef void update_XQHphi_( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + double complex*** Hphi, + double complex**** XQHphi_, #XQHphi[i,j,k] donne la matrice XQEzs au point (i,j,k) + floattype_t[:, :, ::1] Omega_term_list, + floattype_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQHphi_ from time n-1/2 to time n+1/2 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + HPhi, Omega_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQHphi_: list to be updated + """ + cdef Py_ssize_t i, k, ii, iii + cdef int nr + + nr = thickness_r + + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + ii = rs + i + for iii in range(nr): + XQHphi_[i][0][k][iii] = Omega_term_list[iii,0,k] * Hphi[ii][0][k] + XQHphi_[i][0][k][iii] * alpha_term_list[iii,0,k] + +cdef void update_XQHzs( #OK + int rs, + int nz, + int nthreads, + int thickness_r, + double complex*** Hzs, + double complex**** XQHzs, #XQHzs[i,j,k] donne la matrice XQHzs au point (i,j,k) + floattype_t[:, :, ::1] Ksi_term_list, + floattype_t[:, :, ::1] alpha_term_list, +): + """ + This function updates XQHzs from time n-1/2 to time n+1/2 + + Args: + rs, rf (int): position of the PML along the r-axis + nz (int): number of cells along the z axis + nthreads (int): number of threads to use + EPhi, Omega_term_list (memoryview): lists required for the update. EPhi_ is taken at time n+1 + XQEphi_: list to be updated + """ + cdef Py_ssize_t i, k, iii + cdef int nr + + nr = thickness_r + for k in prange(0, nz, nogil = True, num_threads= nthreads, schedule= 'static'): + for i in range(0, nr): + for iii in range(nr): + XQHzs[i][0][k][iii] = Ksi_term_list[iii,0,k] * Hzs[i][0][k] + XQHzs[i][0][k][iii] * alpha_term_list[iii,0,k] + + + +cpdef void E_update_r_slab( + int rs, + int m, + int nz, + int thickness_r, + float dr, + float dz, + float dt, + int nthreads, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ers_np, + np.complex128_t[:, :, ::1] QErs_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] QEphi_np, + np.complex128_t[:, :, ::1] Ephi__np, + np.complex128_t[:, :, :, ::1] XQEphi__np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] QEz_np, + np.complex128_t[:, :, ::1] Ezs_np, + np.complex128_t[:, :, :, ::1] XQEzs_np, #when called, the list is at the step n-1 + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hrs_np, + np.complex128_t[:, :, ::1] QHrs_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] QHphi_np, + np.complex128_t[:, :, ::1] Hphi__np, + np.complex128_t[:, :, ::1] QHphi__np, + np.complex128_t[:, :, :, ::1] XQHphi__np, #when called, the list is at step n-3/2 + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] QHz_np, + np.complex128_t[:, :, ::1] Hzs_np, + np.complex128_t[:, :, :, ::1] XQHzs_np, + floattype_t[:, :, ::1] alpha, + floattype_t[:, :, ::1] sigma, + floattype_t[:, :, ::1] kappa, + floattype_t[:, :, ::1] b, + floattype_t[:, :, ::1] Omega_term_list, + floattype_t[:, :, ::1] alpha_term_list, + floattype_t[:, :, ::1] Ksi_term_list, + floattype_t[:, :, ::1] Lambda_term_list, + floattype_t[:, :, ::1] R_term_list, + floattype_t[:, :, ::1] Psi_term_list, + floattype_t[:, :, ::1] Theta_term_list, + + ): + """ + + This function updates all the E fields inside the PML. + + Args: + rs, rf, zs, zf (int): locations of the fields to be updated + m (int): the argument in e^(i*m*phi) to ensure the symmetry + dr, dz (float): spatial discretization (no need for dphi as we use the symmetry) + dt (float): timestep in s + nz_tot (int): number of cells along the z axis for the whole domain + nthreads (int): number of threads to use + alpha, sigma, kappa, b (memoryviews): PML parameters + Er, Ephi, Ez, Hr, Hphi, Hz (memoryviews): fields in time domain + Ers, Ephi_, Ezs, Hrs, Hphi_, Hzs (memoryviews): fields used for PML updates + + """ + cdef Py_ssize_t i, k, ii, kk + cdef int nr + cdef floattype_t sigma_term, kappa_term, denominateur_kappa_sigma, b_term, R_term, denominateur_R_b, arg + cdef Py_ssize_t taille_i_fields, taille_j_fields, taille_i_others, taille_j_others, taille_i_X, taille_j_X, taille_k_X, taille_l_X + cdef double complex QEzs_n + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + taille_i_others = Ers_np.shape[0] + taille_j_others = Ers_np.shape[1] + taille_i_X = XQEphi__np.shape[0] + taille_j_X = XQEphi__np.shape[1] + taille_k_X = XQEphi__np.shape[2] + taille_l_X = XQEphi__np.shape[3] + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ers = alloc_and_copy_complex3D(Ers_np) + cdef double complex*** QErs = alloc_and_copy_complex3D(QErs_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** QEphi = alloc_and_copy_complex3D(QEphi_np) + cdef double complex*** Ephi_ = alloc_and_copy_complex3D(Ephi__np) + cdef double complex**** XQEphi_ = alloc_and_copy_complex4D(XQEphi__np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** QEz = alloc_and_copy_complex3D(QEz_np) + cdef double complex*** Ezs = alloc_and_copy_complex3D(Ezs_np) + cdef double complex**** XQEzs = alloc_and_copy_complex4D(XQEzs_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hrs = alloc_and_copy_complex3D(Hrs_np) + cdef double complex*** QHrs = alloc_and_copy_complex3D(QHrs_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** QHphi = alloc_and_copy_complex3D(QHphi_np) + cdef double complex*** Hphi_ = alloc_and_copy_complex3D(Hphi__np) + cdef double complex*** QHphi_ = alloc_and_copy_complex3D(QHphi__np) + cdef double complex**** XQHphi_ = alloc_and_copy_complex4D(XQHphi__np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + cdef double complex*** QHz = alloc_and_copy_complex3D(QHz_np) + cdef double complex*** Hzs = alloc_and_copy_complex3D(Hzs_np) + cdef double complex**** XQHzs = alloc_and_copy_complex4D(XQHzs_np) + + + nr = thickness_r + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0, nz): + + sigma_term = sigma[i][0][k] / (2*e0) + kappa_term = kappa[i][0][k] / dt + denominateur_kappa_sigma = (sigma_term + kappa_term) + + #Er,Qers,Ers-> Utiliser liste Psi + if k == 0: #Boundary conditions + Er[ii][0][k] += (1j * m * Hz[ii][0][k] /(dr * (ii-0.5)) - (Hphi[ii][0][k] - 0)/dz)/e0 #OK + + else: + Er[ii][0][k] += (1j * m * Hz[ii][0][k] /(dr * (ii-0.5)) - (Hphi[ii][0][k] - Hphi[ii][0][k-1])/dz)/e0 #OK + QErs[i][0][k] = QErs[i][0][k]*alpha_term_list[i][0][k] + Psi_term_list[i][0][k] * Er[ii][0][k] #OK + Ers[i][0][k] = kappa[i][0][k] * Er[ii][0][k] + QErs[i][0][k] #OK + + #Ephi, QEphi + if k == 0: #Hrs[ii, 0, kk - 1] = 0 because proportionate to Hr + Ephi[ii][0][k] = (((kappa_term - sigma_term) * Ephi[ii][0][k] + QEphi[i][0][k] * ( + 1 + alpha_term_list[i][0][k]) + (Hrs[i][0][k] - 0) / (dz * e0) - ( + Hz[ii][0][k] - Hz[ii - 1][0][k]) / (dr * e0)) + / (denominateur_kappa_sigma - Theta_term_list[i][0][k])) #OK + else: + Ephi[ii][0][k] = (((kappa_term - sigma_term) * Ephi[ii][0][k] + QEphi[i][0][k] * (1 + alpha_term_list[i][0][k]) + + (Hrs[i][0][k] - Hrs[i][0][k-1])/(dz*e0) - (Hz[ii][0][k] - Hz[ii-1][0][k])/(dr * e0)) + / (denominateur_kappa_sigma - Theta_term_list[i][0][k])) #OK + + QEphi[i][0][k] = Theta_term_list[i][0][k] * Ephi[ii][0][k] + QEphi[i][0][k] * alpha_term_list[i][0][k] + + + # We leave the first for statement to update XQEphi_ and XQEzs + update_XQEphi_(rs, nz, nthreads, thickness_r, Ephi, XQEphi_, Omega_term_list, alpha_term_list) + update_XQEzs(rs, nz, nthreads, thickness_r, Ezs, XQEzs, Ksi_term_list, alpha_term_list) + + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0, nz): + # Ephi_ + Ephi_[i][0][k] = (b[i][0][k]*Ephi[ii][0][k] #OK + + sum_list_c(XQEphi_, i, 0, k, taille_l_X)) #This sum is in fact QEphi_ + + # Ezs + b_term = b[i][0][k] / dt + R_term = R_term_list[i][0][k] / 2 + denominateur_R_b = b_term + R_term + arg = (alpha[i][0][k] * kappa[i][0][k] + sigma[i][0][k]) * dt / (kappa[i][0][k] * e0) + QEzs_n = sum_list_c(XQEzs, i, 0, k, taille_l_X) + + + #I decided to take the derivative of Hphi_ as Hphi_(i+1) - Hphi_(i), which is different from the paper + # as it is easier to process Hphi_(rmax + 1), which is null because of boundary conditions, than Hphi_(i0-1), which has to be computed + if i == nr-1: + Ezs[i][0][k] = ((b_term - R_term) * Ezs[i][0][k] + QEzs_n + simple_sum(Hadamard_product_i( + XQEzs, alpha_term_list, i, 0, k, taille_l_X), taille_l_X) #OK + + (0 - Hphi_[i][0][k]) / (dr * e0) - + 1j * m * Hrs[i][0][k] / e0) / (denominateur_R_b - sum3D_i(Ksi_term_list, 0, k)) + else: + Ezs[i][0][k] = ((b_term - R_term) * Ezs[i][0][k] + QEzs_n + simple_sum(Hadamard_product_i(XQEzs, alpha_term_list, i, 0, k, taille_l_X), taille_l_X) #OK + + (Hphi_[i+1][0][k] - Hphi_[i][0][k])/(dr * e0) - + 1j * m * Hrs[i][0][k] / e0 )/(denominateur_R_b - sum3D_i(Ksi_term_list, 0, k)) + + #QEz + QEz[i][0][k] = Lambda_term_list[i][0][k] * Ezs[i][0][k] + QEz[i][0][k]*e**(-arg) #OK + + #Ez + Ez[ii][0][k] = (Ezs[i][0][k] - QEz[i][0][k])/kappa[i][0][k] #OK + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ers, Ers_np) + copy_complex3D_to_numpy(QErs, QErs_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(QEphi, QEphi_np) + copy_complex3D_to_numpy(Ephi_, Ephi__np) + copy_complex4D_to_numpy(XQEphi_, XQEphi__np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(QEz, QEz_np) + copy_complex3D_to_numpy(Ezs, Ezs_np) + copy_complex4D_to_numpy(XQEzs, XQEzs_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hrs, Hrs_np) + copy_complex3D_to_numpy(QHrs, QHrs_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(QHphi, QHphi_np) + copy_complex3D_to_numpy(Hphi_, Hphi__np) + copy_complex3D_to_numpy(QHphi_, QHphi__np) + copy_complex4D_to_numpy(XQHphi_, XQHphi__np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(QHz, QHz_np) + copy_complex3D_to_numpy(Hzs, Hzs_np) + copy_complex4D_to_numpy(XQHzs, XQHzs_np) + + free_complex3D(Er, taille_i_fields, taille_j_fields) + free_complex3D(Ers, taille_i_others, taille_j_others) + free_complex3D(QErs, taille_i_others, taille_j_others) + free_complex3D(Ephi, taille_i_fields, taille_j_fields) + free_complex3D(QEphi, taille_i_others, taille_j_others) + free_complex3D(Ephi_, taille_i_others, taille_j_others) + free_complex4D(XQEphi_, taille_i_X, taille_j_X, taille_k_X) + free_complex3D(Ez, taille_i_fields, taille_j_fields) + free_complex3D(QEz, taille_i_others, taille_j_others) + free_complex3D(Ezs, taille_i_others, taille_j_others) + free_complex4D(XQEzs, taille_i_X, taille_j_X, taille_k_X) + free_complex3D(Hr, taille_i_fields, taille_j_fields) + free_complex3D(Hrs, taille_i_others, taille_j_others) + free_complex3D(QHrs, taille_i_others, taille_j_others) + free_complex3D(Hphi, taille_i_fields, taille_j_fields) + free_complex3D(QHphi, taille_i_others, taille_j_others) + free_complex3D(Hphi_, taille_i_others, taille_j_others) + free_complex3D(QHphi_, taille_i_others, taille_j_others) + free_complex4D(XQHphi_, taille_i_X, taille_j_X, taille_k_X) + free_complex3D(Hz, taille_i_fields, taille_j_fields) + free_complex3D(QHz, taille_i_others, taille_j_others) + free_complex3D(Hzs, taille_i_others, taille_j_others) + free_complex4D(XQHzs, taille_i_X, taille_j_X, taille_k_X) + +cpdef str test(floattype_t[:, :, ::1] rs): + cdef str test_str = "Ca fonctionne !" + print(test_str) + return + +cpdef void H_update_r_slab( + int rs, + int m, + int nz, + int thickness_r, + floattype_t dr, + floattype_t dz, + floattype_t dt, + int nthreads, + np.uint32_t[:, :, :, ::1] ID, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ers_np, + np.complex128_t[:, :, ::1] QErs_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] QEphi_np, + np.complex128_t[:, :, ::1] Ephi__np, + np.complex128_t[:, :, :, ::1] XQEphi__np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] QEz_np, + np.complex128_t[:, :, ::1] Ezs_np, + np.complex128_t[:, :, :, ::1] XQEzs_np, #when called, the list is at the step n-1 + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hrs_np, + np.complex128_t[:, :, ::1] QHrs_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] QHphi_np, + np.complex128_t[:, :, ::1] Hphi__np, + np.complex128_t[:, :, ::1] QHphi__np, + np.complex128_t[:, :, :, ::1] XQHphi__np, #when called, the list is at step n-3/2 + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] QHz_np, + np.complex128_t[:, :, ::1] Hzs_np, + np.complex128_t[:, :, :, ::1] XQHzs_np, + floattype_t[:, :, ::1] alpha, + floattype_t[:, :, ::1] sigma, + floattype_t[:, :, ::1] kappa, + floattype_t[:, :, ::1] b, + floattype_t[:, :, ::1] Omega_term_list, + floattype_t[:, :, ::1] alpha_term_list, + floattype_t[:, :, ::1] Ksi_term_list, + floattype_t[:, :, ::1] Lambda_term_list, + floattype_t[:, :, ::1] R_term_list, + floattype_t[:, :, ::1] Psi_term_list, + floattype_t[:, :, ::1] Theta_term_list, + ): + """ + + This function updates all the H fields inside the PML. + + Args: + rs, rf, zs, zf (int): locations of the fields to be updated + m (int): the argument in e^(i*m*phi) to ensure the symmetry + dr, dz (float): spatial discretization (no need for dphi as we use the symmetry) + dt (float): timestep in s + nz_tot (int): number of cells along the z axis for the whole domain + nthreads (int): number of threads to use + alpha, sigma, kappa, b (memoryviews): PML parameters + Er, Ephi, Ez, Hr, Hphi, Hz (memoryviews): fields in time domain + Ers, Ephi_, Ezs, Hrs, Hphi_, Hzs (memoryviews): fields used for PML updates + + """ + cdef Py_ssize_t i, k, ii + cdef Py_ssize_t taille_i_fields, taille_j_fields, taille_i_others, taille_j_others, taille_i_X, taille_j_X, taille_k_X, taille_l_X + cdef int nr + cdef floattype_t sigma_term, kappa_term, denominateur_kappa_sigma, b_term, R_term + cdef double complex QHzs + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + taille_k_fields = Er_np.shape[2] + taille_i_others = Ers_np.shape[0] + taille_j_others = Ers_np.shape[1] + taille_k_others = Ers_np.shape[2] + taille_i_X = XQHzs_np.shape[0] + taille_j_X = XQHzs_np.shape[1] + taille_k_X = XQHzs_np.shape[2] + taille_l_X = XQHzs_np.shape[3] + + nr = thickness_r + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ers = alloc_and_copy_complex3D(Ers_np) + cdef double complex*** QErs = alloc_and_copy_complex3D(QErs_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** QEphi = alloc_and_copy_complex3D(QEphi_np) + cdef double complex*** Ephi_ = alloc_and_copy_complex3D(Ephi__np) + cdef double complex**** XQEphi_ = alloc_and_copy_complex4D(XQEphi__np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** QEz = alloc_and_copy_complex3D(QEz_np) + cdef double complex*** Ezs = alloc_and_copy_complex3D(Ezs_np) + cdef double complex**** XQEzs = alloc_and_copy_complex4D(XQEzs_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hrs = alloc_and_copy_complex3D(Hrs_np) + cdef double complex*** QHrs = alloc_and_copy_complex3D(QHrs_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** QHphi = alloc_and_copy_complex3D(QHphi_np) + cdef double complex*** Hphi_ = alloc_and_copy_complex3D(Hphi__np) + cdef double complex*** QHphi_ = alloc_and_copy_complex3D(QHphi__np) + cdef double complex**** XQHphi_ = alloc_and_copy_complex4D(XQHphi__np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + cdef double complex*** QHz = alloc_and_copy_complex3D(QHz_np) + cdef double complex*** Hzs = alloc_and_copy_complex3D(Hzs_np) + cdef double complex**** XQHzs = alloc_and_copy_complex4D(XQHzs_np) + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0, nz): + # Hr, QHrs, Hrs + if k == 0: + Hr[ii][0][k] += (Ephi[ii][0][k] - 0) / (dz * mu0) - 1j * m * Ez[ii][0][k] / ((ii - 1) * dr * mu0) # OK + else: + Hr[ii][0][k] += (Ephi[ii][0][k] - Ephi[ii][0][k - 1]) / (dz * mu0) - 1j * m * Ez[ii][0][k] / ((ii - 1) * dr * mu0) # OK + QHrs[i][0][k] = Psi_term_list[i][0][k] * Hr[ii][0][k] + QHrs[i][0][k] * alpha_term_list[i][0][k] # OK + Hrs[i][0][k] = kappa[i][0][k] * Hr[ii][0][k] + QHrs[i][0][k] # OK + + # Hphi + sigma_term = sigma[i][0][k] / (2 * e0) + kappa_term = kappa[i][0][k] / dt + + if k == nz - 1: # Ers is proportionate to Er + if i == nr - 1: + Hphi[ii][0][k] = (((kappa_term - sigma_term) * Hphi[ii][0][k] + (1 + alpha_term_list[i][0][k]) * + QHphi[i][0][k] + + (0 - Ez[ii][0][k]) / (dr * mu0) - ( + 0 - Ers[i][0][k]) / (dz * mu0)) / + (sigma_term + kappa_term - Theta_term_list[i][0][k])) + else: + Hphi[ii][0][k] = (((kappa_term - sigma_term) * Hphi[ii][0][k] + (1 + alpha_term_list[i][0][k]) * + QHphi[i][0][k] + + (Ez[ii + 1][0][k] - Ez[ii][0][k]) / (dr * mu0) - ( + 0 - Ers[i][0][k]) / (dz * mu0)) / + (sigma_term + kappa_term - Theta_term_list[i][0][k])) + else: + if i == nr - 1: + Hphi[ii][0][k] = (((kappa_term - sigma_term) * Hphi[ii][0][k] + (1 + alpha_term_list[i][0][k]) * + QHphi[i][0][k] + + (0 - Ez[ii][0][k]) / (dr * mu0) - ( + Ers[i][0][k + 1] - Ers[i][0][k]) / (dz * mu0)) / + (sigma_term + kappa_term - Theta_term_list[i][0][k])) + else: + Hphi[ii][0][k] = (((kappa_term - sigma_term) * Hphi[ii][0][k] + (1 + alpha_term_list[i][0][k]) * QHphi[i][0][k] + + (Ez[ii + 1][0][k] - Ez[ii][0][k]) / (dr * mu0) - (Ers[i][0][k + 1] - Ers[i][0][k]) / (dz * mu0)) / + (sigma_term + kappa_term - Theta_term_list[i][0][k])) # OK + + # QHphi + QHphi[i][0][k] = Theta_term_list[i][0][k] * Hphi[ii][0][k] + QHphi[i][0][k] * alpha_term_list[i][0][k] + + + # We leave the for statement to update XQHphi_ and XQHzs + update_XQHphi_(rs, nz, nthreads, thickness_r, Hphi, XQHphi_, Omega_term_list, alpha_term_list) + update_XQHzs(rs, nz, nthreads, thickness_r, Hzs, XQHzs, Ksi_term_list, alpha_term_list) + + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + ii = i + rs + for k in range(0, nz): + # Hphi_ + Hphi_[i][0][k] = (b[i][0][k] * Hphi[ii][0][k] # OK + + sum4D_i(XQHphi_, i, 0, k, taille_l_X)) # QHphi_ + + # Hzs + b_term = b[i][0][k] / dt + R_term = R_term_list[i][0][k] / 2 + QHzs = sum_list_c(XQHzs, i, 0, k, taille_l_X) + + if i == nr - 1: # Ephi_ is proportionate to Ephi at this point (at different times) + Hzs[i][0][k] = ((b_term - R_term) * Hzs[i][0][k] + ( + simple_sum(Hadamard_product_i(XQHzs, alpha_term_list, i, 0, k, taille_l_X), taille_l_X) + QHzs) / mu0 + + Ers[i][0][k] * 1j * m / e0 - (0 - Ephi_[i][0][k]) / (dr * e0)) / ( + b_term + R_term + - sum3D_i(Ksi_term_list, 0, k) / mu0) + else: + Hzs[i][0][k] = ((b_term - R_term) * Hzs[i][0][k] + (simple_sum(Hadamard_product_i(XQHzs, alpha_term_list, i, 0, k, taille_l_X), taille_l_X) + QHzs) / mu0 + + Ers[i][0][k] * 1j * m / e0 - (Ephi_[i + 1][0][k] - Ephi_[i][0][k]) / (dr * e0)) / (b_term + R_term - sum3D_i(Ksi_term_list, 0, k) / mu0) # OK + + # QHz + QHz[i][0][k] = (Lambda_term_list[i][0][k] * Hzs[i][0][k] + + QHz[i][0][k] * e**(-(alpha[i][0][k] * kappa[i][0][k] + sigma[i][0][k]) * dt / (kappa[i][0][k] * e0))) + + # Hz + Hz[ii][0][k] = (Hzs[i][0][k] - QHz[i][0][k]) / kappa[i][0][k] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ers, Ers_np) + copy_complex3D_to_numpy(QErs, QErs_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(QEphi, QEphi_np) + copy_complex3D_to_numpy(Ephi_, Ephi__np) + copy_complex4D_to_numpy(XQEphi_, XQEphi__np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(QEz, QEz_np) + copy_complex3D_to_numpy(Ezs, Ezs_np) + copy_complex4D_to_numpy(XQEzs, XQEzs_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hrs, Hrs_np) + copy_complex3D_to_numpy(QHrs, QHrs_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(QHphi, QHphi_np) + copy_complex3D_to_numpy(Hphi_, Hphi__np) + copy_complex3D_to_numpy(QHphi_, QHphi__np) + copy_complex4D_to_numpy(XQHphi_, XQHphi__np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(QHz, QHz_np) + copy_complex3D_to_numpy(Hzs, Hzs_np) + copy_complex4D_to_numpy(XQHzs, XQHzs_np) + + free_complex3D(Er, taille_i_fields, taille_j_fields) + free_complex3D(Ers, taille_i_others, taille_j_others) + free_complex3D(QErs, taille_i_others, taille_j_others) + free_complex3D(Ephi, taille_i_fields, taille_j_fields) + free_complex3D(QEphi, taille_i_others, taille_j_others) + free_complex3D(Ephi_, taille_i_others, taille_j_others) + free_complex4D(XQEphi_, taille_i_X, taille_j_X, taille_k_X) + free_complex3D(Ez, taille_i_fields, taille_j_fields) + free_complex3D(QEz, taille_i_others, taille_j_others) + free_complex3D(Ezs, taille_i_others, taille_j_others) + free_complex4D(XQEzs, taille_i_X, taille_j_X, taille_k_X) + free_complex3D(Hr, taille_i_fields, taille_j_fields) + free_complex3D(Hrs, taille_i_others, taille_j_others) + free_complex3D(QHrs, taille_i_others, taille_j_others) + free_complex3D(Hphi, taille_i_fields, taille_j_fields) + free_complex3D(QHphi, taille_i_others, taille_j_others) + free_complex3D(Hphi_, taille_i_others, taille_j_others) + free_complex3D(QHphi_, taille_i_others, taille_j_others) + free_complex4D(XQHphi_, taille_i_X, taille_j_X, taille_k_X) + free_complex3D(Hz, taille_i_fields, taille_j_fields) + free_complex3D(QHz, taille_i_others, taille_j_others) + free_complex3D(Hzs, taille_i_others, taille_j_others) + free_complex4D(XQHzs, taille_i_X, taille_j_X, taille_k_X) + + +######################################################################################## + +################## Update of the PMLs in the z direction ############################### + +#For this part, it appears that the PML formulation for the z component is in fact the same as in cartesian + +cpdef void initialize_constant_lists_z( + int rs, + int thickness_z, + floattype_t dt, + int nthreads, + floattype_t[:, :, ::1] alpha_z, + floattype_t[:, :, ::1] sigma_z, + floattype_t[:, :, ::1] kappa_z, + floattype_t[:, :, ::1] Pi_term_list, + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + cdef Py_ssize_t k,i + cdef floattype_t arg + for k in prange(0, 2*thickness_z, nogil= True, schedule='static', num_threads = nthreads): + for i in range(0,rs): + arg = alpha_z[i,0,k] * kappa_z[i,0,k] + sigma_z[i,0,k] + Pi_term_list[i,0,k] = (alpha_z[i,0,k] - arg)/(e0 * kappa_z[i,0,k]) + Delta_term_list[i,0,k] = (1 - kappa_z[i,0,k])/kappa_z[i,0,k] + Rho_term_list[i,0,k] = arg / (e0 * kappa_z[i,0,k]) + +cpdef void E_update_upper_slab( + int rs, #The PML will go from r=0 to r=rs + int zs, + int thickness, + floattype_t dr, + floattype_t dz, + floattype_t dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JEphi_np, + np.complex128_t[:, :, ::1] JEr_np, + np.complex128_t[:, :, ::1] QEphi_np, + np.complex128_t[:, :, ::1] QEr_np, + np.complex128_t[:, :, ::1] QJEphi_np, + np.complex128_t[:, :, ::1] QJEr_np, + floattype_t[:, :, ::1] Pi_term_list, + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef Py_ssize_t i,k,kk + cdef Py_ssize_t j = 0 + cdef Py_ssize_t taille_i_fields, taille_j_fields, taille_i_others, taille_j_others + cdef int nz, nr + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + taille_i_others = JEr_np.shape[0] + taille_j_others = JEr_np.shape[1] + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + cdef double complex*** JEphi = alloc_and_copy_complex3D(JEphi_np) + cdef double complex*** JEr = alloc_and_copy_complex3D(JEr_np) + cdef double complex*** QEphi = alloc_and_copy_complex3D(QEphi_np) + cdef double complex*** QEr = alloc_and_copy_complex3D(QEr_np) + cdef double complex*** QJEphi = alloc_and_copy_complex3D(QJEphi_np) + cdef double complex*** QJEr = alloc_and_copy_complex3D(QJEr_np) + + nz = thickness + nr = rs + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + for k in range(0, nz): + kk = k + zs + + # Updating the Q lists before updating the fields + QEr[i][j][k + nz] += (Er[i][j][kk + 1] - Er[i][j][kk]) * dt / dz + QEphi[i][j][k + nz] += (Ephi[i][j][kk + 1] - Ephi[i][j][kk]) * dt / dz + QJEr[i][j][k + nz] += JEr[i][j][k + nz] * dt + QJEphi[i][j][k + nz] += JEphi[i][j][k + nz] * dt + + # Updating the E-fields + Er[i][j][kk] += (I * m * Hz[i][j][kk] / ((i + 0.5) * dr) + - (Hphi[i][j][kk + 1] - Hphi[i][j][kk]) / dz + - JEphi[i][j][k]) / e0 + + Ephi[i][j][kk] += ((Hr[i][j][kk + 1] - Hr[i][j][kk]) / dz + - (Hz[i + 1][j][kk] - Hz[i][j][kk]) / dr + + JEphi[i][j][k]) / e0 + + Ez[i][j][kk] += (((i + 1.5) * Hphi[i + 1][j][kk] - (i + 0.5) * Hphi[i][j][kk]) / ((i + 0.5) * dr) + - I * m * Hr[i][j][kk] / ((i + 0.5) * dr) + + JEphi[i][j][k]) / e0 + + # Updating the J fields + JEr[i][j][k] = (Pi_term_list[i, j, k + nz] * QEr[i][j][k] + + Delta_term_list[i, j, k + nz] * (Er[i][j][kk + 1] - Er[i][j][kk]) / dz + - Rho_term_list[i, j, k + nz] * QJEr[i][j][k]) + + JEphi[i][j][k] = (Pi_term_list[i, j, k + nz] * QEphi[i][j][k] + + Delta_term_list[i, j, k + nz] * (Ephi[i][j][kk + 1] - Ephi[i][j][kk]) / dz + - Rho_term_list[i, j, k + nz] * QJEphi[i][j][k]) + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JEphi, JEphi_np) + copy_complex3D_to_numpy(JEr, JEr_np) + copy_complex3D_to_numpy(QEphi, QEphi_np) + copy_complex3D_to_numpy(QEr, QEr_np) + copy_complex3D_to_numpy(QJEphi, QJEphi_np) + copy_complex3D_to_numpy(QJEr, QJEr_np) + + free_complex3D(Er, taille_i_fields, taille_j_fields) + free_complex3D(Ephi, taille_i_fields, taille_j_fields) + free_complex3D(Ez, taille_i_fields, taille_j_fields) + free_complex3D(Hr, taille_i_fields, taille_j_fields) + free_complex3D(Hphi, taille_i_fields, taille_j_fields) + free_complex3D(Hz, taille_i_fields, taille_j_fields) + free_complex3D(JEphi, taille_i_others, taille_j_others) + free_complex3D(JEr, taille_i_others, taille_j_others) + free_complex3D(QEphi, taille_i_others, taille_j_others) + free_complex3D(QEr, taille_i_others, taille_j_others) + free_complex3D(QJEphi, taille_i_others, taille_j_others) + free_complex3D(QJEr, taille_i_others, taille_j_others) + +cpdef void H_update_upper_slab( + int rs, #The PML will go from r=0 to r=rs + int zs, + int thickness, + floattype_t dr, + floattype_t dz, + floattype_t dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JHphi_np, + np.complex128_t[:, :, ::1] JHr_np, + np.complex128_t[:, :, ::1] QHphi_np, + np.complex128_t[:, :, ::1] QHr_np, + np.complex128_t[:, :, ::1] QJHphi_np, + np.complex128_t[:, :, ::1] QJHr_np, + floattype_t[:, :, ::1] Pi_term_list, #Pi_term_list[i,0,k] gives the Pi value at (i,0,k) if k <= nz-1, else at (i,0,nz-thickness+k) + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef Py_ssize_t i, k, kk + cdef Py_ssize_t j = 0 + cdef Py_ssize_t taille_i_fields, taille_j_fields, taille_i_others, taille_j_others + cdef int nz, nr + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + taille_i_others = JHr_np.shape[0] + taille_j_others = JHr_np.shape[1] + + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + cdef double complex*** JHphi = alloc_and_copy_complex3D(JHphi_np) + cdef double complex*** JHr = alloc_and_copy_complex3D(JHr_np) + cdef double complex*** QHphi = alloc_and_copy_complex3D(QHphi_np) + cdef double complex*** QHr = alloc_and_copy_complex3D(QHr_np) + cdef double complex*** QJHphi = alloc_and_copy_complex3D(QJHphi_np) + cdef double complex*** QJHr = alloc_and_copy_complex3D(QJHr_np) + + nz = thickness + nr = rs + + for i in prange(0, nr, nogil=True, schedule='static', num_threads= nthreads): + for k in range(0, nz): + kk = k + zs + + # Updating the Q lists before updating the fields + QHr[i][j][k + nz] += (Hr[i][j][kk + 1] - Hr[i][j][kk]) * dt / dz + QHphi[i][j][k + nz] += (Hphi[i][j][kk + 1] - Hphi[i][j][kk]) * dt / dz + QJHr[i][j][k + nz] += JHr[i][j][k + nz] * dt + QJHphi[i][j][k + nz] += JHphi[i][j][k + nz] * dt + + # Updating the E-fields + Hr[i][j][kk] -= (I * m * Ez[i][j][kk] / ((i + 1) * dr) + - (Ephi[i][j][kk + 1] - Ephi[i][j][kk]) / dz + - JHphi[i][j][k]) / mu0 + + Hphi[i][j][kk] -= ((Er[i][j][kk + 1] - Er[i][j][kk]) / dz + - (Ez[i + 1][j][kk] - Ez[i][j][kk]) / dr + + JHphi[i][j][k]) / mu0 + + Hz[i][j][kk] -= (((i + 2) * Ephi[i + 1][j][kk] - (i + 1) * Ephi[i][j][kk]) / ((i + 1) * dr) + - I * m * Er[i][j][kk] / ((i + 0.5) * dr) + + JHphi[i][j][k]) / mu0 + + + # Updating the J fields + JHr[i][j][k + nz] = (Pi_term_list[i, j, k + nz] * QHr[i][j][k + nz] + + Delta_term_list[i, j, k + nz] * (Hr[i][j][kk + 1] - Hr[i][j][kk]) / dz + - Rho_term_list[i, j, k + nz] * QJHr[i][j][k + nz]) + + + JHphi[i][j][k + nz] = (Pi_term_list[i, j, k + nz] * QHphi[i][j][k + nz] + + Delta_term_list[i, j, k + nz] * (Hphi[i][j][kk + 1] - Hphi[i][j][kk]) / dz + - Rho_term_list[i, j, k + nz] * QJHphi[i][j][k + nz]) + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JHphi, JHphi_np) + copy_complex3D_to_numpy(JHr, JHr_np) + copy_complex3D_to_numpy(QHphi, QHphi_np) + copy_complex3D_to_numpy(QHr, QHr_np) + copy_complex3D_to_numpy(QJHphi, QJHphi_np) + copy_complex3D_to_numpy(QJHr, QJHr_np) + + free_complex3D(Er, taille_i_fields, taille_j_fields) + free_complex3D(Ephi, taille_i_fields, taille_j_fields) + free_complex3D(Ez, taille_i_fields, taille_j_fields) + free_complex3D(Hr, taille_i_fields, taille_j_fields) + free_complex3D(Hphi, taille_i_fields, taille_j_fields) + free_complex3D(Hz, taille_i_fields, taille_j_fields) + free_complex3D(JHphi, taille_i_others, taille_j_others) + free_complex3D(JHr, taille_i_others, taille_j_others) + free_complex3D(QHphi, taille_i_others, taille_j_others) + free_complex3D(QHr, taille_i_others, taille_j_others) + free_complex3D(QJHphi, taille_i_others, taille_j_others) + free_complex3D(QJHr, taille_i_others, taille_j_others) + + +#For the lower slab, we just need to invert the direction of scanning z, as well as updating the indexes of term_lists + +cpdef void E_update_lower_slab( + int rs, #The PML will go from r=0 to r=rs + int zf, + int thickness, + floattype_t dr, + floattype_t dz, + floattype_t dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JEphi_np, + np.complex128_t[:, :, ::1] JEr_np, + np.complex128_t[:, :, ::1] QEphi_np, + np.complex128_t[:, :, ::1] QEr_np, + np.complex128_t[:, :, ::1] QJEphi_np, + np.complex128_t[:, :, ::1] QJEr_np, + floattype_t[:, :, ::1] Pi_term_list, + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef Py_ssize_t i,k,kk + cdef Py_ssize_t j = 0 + cdef Py_ssize_t taille_i_fields, taille_j_fields, taille_i_others, taille_j_others + cdef int nz, nr + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + taille_i_others = JEr_np.shape[0] + taille_j_others = JEr_np.shape[1] + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + cdef double complex*** JEphi = alloc_and_copy_complex3D(JEphi_np) + cdef double complex*** JEr = alloc_and_copy_complex3D(JEr_np) + cdef double complex*** QEphi = alloc_and_copy_complex3D(QEphi_np) + cdef double complex*** QEr = alloc_and_copy_complex3D(QEr_np) + cdef double complex*** QJEphi = alloc_and_copy_complex3D(QJEphi_np) + cdef double complex*** QJEr = alloc_and_copy_complex3D(QJEr_np) + + + nz = thickness + nr = rs + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + for k in range(0, nz): + kk = nz - (k + 1) # The first PML cell is at kk = nz-1 + + # Updating the Q lists before updating the fields + QEr[i][j][k] += (Er[i][j][kk + 1] - Er[i][j][kk]) * dt / dz + QEphi[i][j][k] += (Ephi[i][j][kk + 1] - Ephi[i][j][kk]) * dt / dz + QJEr[i][j][k] += JEr[i][j][k] * dt + QJEphi[i][j][k] += JEphi[i][j][k] * dt + + # Updating the E-fields + Er[i][j][kk] += (I * m * Hz[i][j][kk] / ((i + 0.5) * dr) + - (Hphi[i][j][kk + 1] - Hphi[i][j][kk]) / dz + - JEphi[i][j][k]) / e0 + + Ephi[i][j][kk] += ((Hr[i][j][kk + 1] - Hr[i][j][kk]) / dz + - (Hz[i + 1][j][kk] - Hz[i][j][kk]) / dr + + JEphi[i][j][k]) / e0 + + Ez[i][j][kk] += (((i + 1.5) * Hphi[i + 1][j][kk] - (i + 0.5) * Hphi[i][j][kk]) + / ((i + 0.5) * dr) + - I * m * Hr[i][j][kk] / ((i + 0.5) * dr) + + JEphi[i][j][k]) / e0 + + # Updating the J fields + JEr[i][j][k] = (Pi_term_list[i, j, k] * QEr[i][j][k] + + Delta_term_list[i, j, k] * (Er[i][j][kk + 1] - Er[i][j][kk]) / dz + - Rho_term_list[i, j, k] * QJEr[i][j][k]) + + JEphi[i][j][k] = (Pi_term_list[i, j, k] * QEphi[i][j][k] + + Delta_term_list[i, j, k] * (Ephi[i][j][kk + 1] - Ephi[i][j][kk]) / dz + - Rho_term_list[i, j, k] * QJEphi[i][j][k]) + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JEphi, JEphi_np) + copy_complex3D_to_numpy(JEr, JEr_np) + copy_complex3D_to_numpy(QEphi, QEphi_np) + copy_complex3D_to_numpy(QEr, QEr_np) + copy_complex3D_to_numpy(QJEphi, QJEphi_np) + copy_complex3D_to_numpy(QJEr, QJEr_np) + + free_complex3D(Er, taille_i_fields, taille_j_fields) + free_complex3D(Ephi, taille_i_fields, taille_j_fields) + free_complex3D(Ez, taille_i_fields, taille_j_fields) + free_complex3D(Hr, taille_i_fields, taille_j_fields) + free_complex3D(Hphi, taille_i_fields, taille_j_fields) + free_complex3D(Hz, taille_i_fields, taille_j_fields) + free_complex3D(JEphi, taille_i_others, taille_j_others) + free_complex3D(JEr, taille_i_others, taille_j_others) + free_complex3D(QEphi, taille_i_others, taille_j_others) + free_complex3D(QEr, taille_i_others, taille_j_others) + free_complex3D(QJEphi, taille_i_others, taille_j_others) + free_complex3D(QJEr, taille_i_others, taille_j_others) + + +cpdef void H_update_lower_slab( + int rs, #The PML will go from r=0 to r=rs + int zf, + int thickness, + floattype_t dr, + floattype_t dz, + floattype_t dt, + int m, + int nthreads, + np.complex128_t[:, :, ::1] Er_np, + np.complex128_t[:, :, ::1] Ephi_np, + np.complex128_t[:, :, ::1] Ez_np, + np.complex128_t[:, :, ::1] Hr_np, + np.complex128_t[:, :, ::1] Hphi_np, + np.complex128_t[:, :, ::1] Hz_np, + np.complex128_t[:, :, ::1] JHphi_np, + np.complex128_t[:, :, ::1] JHr_np, + np.complex128_t[:, :, ::1] QHphi_np, + np.complex128_t[:, :, ::1] QHr_np, + np.complex128_t[:, :, ::1] QJHphi_np, + np.complex128_t[:, :, ::1] QJHr_np, + floattype_t[:, :, ::1] Pi_term_list, #Pi_term_list[i,0,k] gives the Pi value at (i,0,k) if k <= nz-1, else at (i,0,nz-thickness+k) + floattype_t[:, :, ::1] Delta_term_list, + floattype_t[:, :, ::1] Rho_term_list +): + + cdef Py_ssize_t i, k, kk + cdef Py_ssize_t taille_i_fields, taille_j_fields, taille_i_others, taille_j_others + cdef int nz, nr + + taille_i_fields = Er_np.shape[0] + taille_j_fields = Er_np.shape[1] + taille_i_others = JHr_np.shape[0] + taille_j_others = JHr_np.shape[1] + + + cdef double complex*** Er = alloc_and_copy_complex3D(Er_np) + cdef double complex*** Ephi = alloc_and_copy_complex3D(Ephi_np) + cdef double complex*** Ez = alloc_and_copy_complex3D(Ez_np) + cdef double complex*** Hr = alloc_and_copy_complex3D(Hr_np) + cdef double complex*** Hphi = alloc_and_copy_complex3D(Hphi_np) + cdef double complex*** Hz = alloc_and_copy_complex3D(Hz_np) + cdef double complex*** JHphi = alloc_and_copy_complex3D(JHphi_np) + cdef double complex*** JHr = alloc_and_copy_complex3D(JHr_np) + cdef double complex*** QHphi = alloc_and_copy_complex3D(QHphi_np) + cdef double complex*** QHr = alloc_and_copy_complex3D(QHr_np) + cdef double complex*** QJHphi = alloc_and_copy_complex3D(QJHphi_np) + cdef double complex*** QJHr = alloc_and_copy_complex3D(QJHr_np) + + nz = thickness + nr = rs + + for i in prange(0, nr, nogil=True, schedule='static', num_threads=nthreads): + for k in range(0, nz): + kk = nz - (k + 1) # The first PML cell is at kk = nz-1 + + # Updating the Q lists before updating the fields + QHr[i][0][k] += (Hr[i][0][kk + 1] - Hr[i][0][kk]) * dt / dz + QHphi[i][0][k] += (Hphi[i][0][kk + 1] - Hphi[i][0][kk]) * dt / dz + QJHr[i][0][k] += JHr[i][0][k] * dt + QJHphi[i][0][k] += JHphi[i][0][k] * dt + + # Updating the E-fields + Hr[i][0][kk] -= (I * m * Ez[i][0][kk] / ((i + 1) * dr) - (Ephi[i][0][kk + 1] - Ephi[i][0][kk]) / dz - + JHphi[i][0][k]) / mu0 + Hphi[i][0][kk] -= ((Er[i][0][kk + 1] - Er[i][0][kk]) / dz - (Ez[i + 1][0][kk] - Ez[i][0][kk]) / dr + + JHphi[i][0][k]) / mu0 + Hz[i][0][kk] -= (((i + 2) * Ephi[i + 1][0][kk] - (i + 1) * Ephi[i][0][kk]) / ((i + 1) * dr) - + I * m * Er[i][0][kk] / ((i + 0.5) * dr) + JHphi[i][0][k]) / mu0 + + # Updating the J fields + JHr[i][0][k] = Pi_term_list[i, 0, k] * QHr[i][0][k] + Delta_term_list[i, 0, k] * ( + Hr[i][0][kk + 1] - Hr[i][0][kk]) / dz - Rho_term_list[i, 0, k] * QJHr[i][0][k] + JHphi[i][0][k] = Pi_term_list[i, 0, k] * QHphi[i][0][k] + Delta_term_list[i, 0, k] * ( + Hphi[i][0][kk + 1] - Hphi[i][0][kk]) / dz - Rho_term_list[i, 0, k] * QJHphi[i][0][k] + + copy_complex3D_to_numpy(Er, Er_np) + copy_complex3D_to_numpy(Ephi, Ephi_np) + copy_complex3D_to_numpy(Ez, Ez_np) + copy_complex3D_to_numpy(Hr, Hr_np) + copy_complex3D_to_numpy(Hphi, Hphi_np) + copy_complex3D_to_numpy(Hz, Hz_np) + copy_complex3D_to_numpy(JHphi, JHphi_np) + copy_complex3D_to_numpy(JHr, JHr_np) + copy_complex3D_to_numpy(QHphi, QHphi_np) + copy_complex3D_to_numpy(QHr, QHr_np) + copy_complex3D_to_numpy(QJHphi, QJHphi_np) + copy_complex3D_to_numpy(QJHr, QJHr_np) + + free_complex3D(Er, taille_i_fields, taille_j_fields) + free_complex3D(Ephi, taille_i_fields, taille_j_fields) + free_complex3D(Ez, taille_i_fields, taille_j_fields) + free_complex3D(Hr, taille_i_fields, taille_j_fields) + free_complex3D(Hphi, taille_i_fields, taille_j_fields) + free_complex3D(Hz, taille_i_fields, taille_j_fields) + free_complex3D(JHphi, taille_i_others, taille_j_others) + free_complex3D(JHr, taille_i_others, taille_j_others) + free_complex3D(QHphi, taille_i_others, taille_j_others) + free_complex3D(QHr, taille_i_others, taille_j_others) + free_complex3D(QJHphi, taille_i_others, taille_j_others) + free_complex3D(QJHr, taille_i_others, taille_j_others) + \ No newline at end of file diff --git a/gprMax/receivers.py b/gprMax/receivers.py index b87af1e4b..4beac6080 100644 --- a/gprMax/receivers.py +++ b/gprMax/receivers.py @@ -27,8 +27,10 @@ class Rx(object): """Receiver output points.""" allowableoutputs = ['Ex', 'Ey', 'Ez', 'Hx', 'Hy', 'Hz', 'Ix', 'Iy', 'Iz'] + allowableoutputs_cyl = ['Er', 'Ephi', 'Ez', 'Hr', 'Hphi', 'Hz'] gpu_allowableoutputs = allowableoutputs[:-3] defaultoutputs = allowableoutputs[:-3] + defaultoutputs_cyl = allowableoutputs_cyl[:] def __init__(self): @@ -41,6 +43,13 @@ def __init__(self): self.ycoordorigin = None self.zcoordorigin = None + self.rcoord_cyl = None + self.phicoord_cyl = None + self.zcoord_cyl = None + self.rcoordorigin_cyl = None + self.phicoordorgin_cyl = None + self.zcoordorigin_cyl = None + def gpu_initialise_rx_arrays(G): """Initialise arrays on GPU for receiver coordinates and to store field components for receivers. diff --git a/gprMax/sources.py b/gprMax/sources.py index 3b9728353..0175d31ce 100644 --- a/gprMax/sources.py +++ b/gprMax/sources.py @@ -22,6 +22,7 @@ from gprMax.constants import c from gprMax.constants import floattype +from gprMax.exceptions import CmdInputError from gprMax.grid import Ix from gprMax.grid import Iy from gprMax.grid import Iz @@ -43,6 +44,12 @@ def __init__(self): self.start = None self.stop = None self.waveformID = None + self.is_integrated = False + + self.rcoord_cyl = 1 #To ensure the symmetry, the source must be at r = 0 + self.zcoord_cyl = None + self.rcoordorigin_cyl = 1 + self.zcoordorigin_cyl = None def calculate_waveform_values(self, G): """Calculates all waveform values for source for duration of simulation. @@ -52,10 +59,10 @@ def calculate_waveform_values(self, G): """ # Waveform values on timesteps - self.waveformvalues_wholestep = np.zeros((G.iterations), dtype=floattype) + self.waveformvalues_wholestep = np.zeros((G.iterations), dtype=np.complex128) # Waveform values on half timesteps - self.waveformvalues_halfstep = np.zeros((G.iterations), dtype=floattype) + self.waveformvalues_halfstep = np.zeros((G.iterations), dtype=np.complex128) waveform = next(x for x in G.waveforms if x.ID == self.waveformID) @@ -64,8 +71,8 @@ def calculate_waveform_values(self, G): if time >= self.start and time <= self.stop: # Set the time of the waveform evaluation to account for any delay in the start time -= self.start - self.waveformvalues_wholestep[iteration] = waveform.calculate_value(time, G.dt) - self.waveformvalues_halfstep[iteration] = waveform.calculate_value(time + 0.5 * G.dt, G.dt) + self.waveformvalues_wholestep[iteration] = waveform.calculate_value(time, G.dt, G.cylindrical) + self.waveformvalues_halfstep[iteration] = waveform.calculate_value(time + 0.5 * G.dt, G.dt, G.cylindrical) class VoltageSource(Source): @@ -85,40 +92,52 @@ def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G): iteration (int): Current iteration (timestep). updatecoeffsE (memory view): numpy array of electric field update coefficients. ID (memory view): numpy array of numeric IDs corresponding to materials in the model. - Ex, Ey, Ez (memory view): numpy array of electric field values. + Ex, Ey, Ez (memory view): numpy array of electric field values. If in cylindrical: Ex = Er, Ey = E_phi and Ez stays the same G (class): Grid class instance - holds essential parameters describing the model. """ - - if iteration * G.dt >= self.start and iteration * G.dt <= self.stop: - i = self.xcoord - j = self.ycoord - k = self.zcoord - componentID = 'E' + self.polarisation - - if self.polarisation == 'x': + if not G.cylindrical: + if iteration * G.dt >= self.start and iteration * G.dt <= self.stop: + i = self.xcoord + j = self.ycoord + k = self.zcoord + componentID = 'E' + self.polarisation + + if self.polarisation == 'x': + assert np.imag(self.waveformvalues_halfstep[iteration]) == 0, "No imaginary representation required in cartesian" + if self.resistance != 0: + Ex[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] + * self.waveformvalues_wholestep[iteration] + * (1 / (self.resistance * G.dy * G.dz))) + else: + Ex[i, j, k] = - self.waveformvalues_halfstep[iteration] / G.dx + + elif self.polarisation == 'y': + if self.resistance != 0: + Ey[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] + * self.waveformvalues_wholestep[iteration] + * (1 / (self.resistance * G.dx * G.dz))) + else: + Ey[i, j, k] = - self.waveformvalues_halfstep[iteration] / G.dy + + elif self.polarisation == 'z': + if self.resistance != 0: + Ez[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] + * self.waveformvalues_wholestep[iteration] + * (1 / (self.resistance * G.dx * G.dy))) + else: + Ez[i, j, k] = - self.waveformvalues_halfstep[iteration] / G.dz + else: + if iteration * G.dt >= self.start and iteration * G.dt <= self.stop: + i, j, k = self.rcoord_cyl, 0, self.zcoord_cyl + if i == 0: + i = 1 if self.resistance != 0: - Ex[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] - * self.waveformvalues_wholestep[iteration] - * (1 / (self.resistance * G.dy * G.dz))) + Ez[i, j, k] -= (updatecoeffsE[ID[G.IDlookup['Ez'], i, j, k], 4] + * self.waveformvalues_wholestep[iteration] + * (1 / (self.resistance * np.pi * G.dr_cyl * G.dr_cyl))) else: - Ex[i, j, k] = - self.waveformvalues_halfstep[iteration] / G.dx - - elif self.polarisation == 'y': - if self.resistance != 0: - Ey[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] - * self.waveformvalues_wholestep[iteration] - * (1 / (self.resistance * G.dx * G.dz))) - else: - Ey[i, j, k] = - self.waveformvalues_halfstep[iteration] / G.dy - - elif self.polarisation == 'z': - if self.resistance != 0: - Ez[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] - * self.waveformvalues_wholestep[iteration] - * (1 / (self.resistance * G.dx * G.dy))) - else: - Ez[i, j, k] = - self.waveformvalues_halfstep[iteration] / G.dz - + Ez[i, j, k] = - self.waveformvalues_halfstep[iteration] / G.dz_cyl + def create_material(self, G): """Create a new material at the voltage source location that adds the voltage source conductivity to the underlying parameters. @@ -128,9 +147,14 @@ def create_material(self, G): """ if self.resistance != 0: - i = self.xcoord - j = self.ycoord - k = self.zcoord + if not G.cylindrical: + i = self.xcoord + j = self.ycoord + k = self.zcoord + else: + i = self.rcoord_cyl + j = 1 + k = self.zcoord_cyl componentID = 'E' + self.polarisation requirednumID = G.ID[G.IDlookup[componentID], i, j, k] @@ -147,7 +171,10 @@ def create_material(self, G): elif self.polarisation == 'y': newmaterial.se += G.dy / (self.resistance * G.dx * G.dz) elif self.polarisation == 'z': - newmaterial.se += G.dz / (self.resistance * G.dx * G.dy) + if not G.cylindrical: + newmaterial.se += G.dz / (self.resistance * G.dx * G.dy) + else: + newmaterial.se += G.dz_cyl / (self.resistance * np.pi * G.dr_cyl * G.dr_cyl) G.ID[G.IDlookup[componentID], i, j, k] = newmaterial.numID G.materials.append(newmaterial) @@ -172,9 +199,14 @@ def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G): """ if iteration * G.dt >= self.start and iteration * G.dt <= self.stop: - i = self.xcoord - j = self.ycoord - k = self.zcoord + if not G.cylindrical: + i = self.xcoord + j = self.ycoord + k = self.zcoord + else: + i = self.rcoord_cyl + j = 1 + k = self.zcoord_cyl componentID = 'E' + self.polarisation if self.polarisation == 'x': @@ -188,10 +220,14 @@ def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G): * self.dl * (1 / (G.dx * G.dy * G.dz))) elif self.polarisation == 'z': - Ez[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] - * self.waveformvalues_wholestep[iteration] - * self.dl * (1 / (G.dx * G.dy * G.dz))) - + if not G.cylindrical: + Ez[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] + * self.waveformvalues_wholestep[iteration] + * self.dl * (1 / (G.dx * G.dy * G.dz))) + else: + Ez[i, j, k] -= (updatecoeffsE[ID[G.IDlookup[componentID], i, j, k], 4] + * self.waveformvalues_wholestep[iteration] + * self.dl * (1 / (np.pi * G.dr_cyl * G.dr_cyl * G.dz_cyl))) class MagneticDipole(Source): """A magnetic dipole is an additive source (magnetic current density).""" @@ -209,7 +245,8 @@ def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G): Hx, Hy, Hz (memory view): numpy array of magnetic field values. G (class): Grid class instance - holds essential parameters describing the model. """ - + if G.cylindrical: + raise CmdInputError("Cylindrical coordinates not supported for magnetic dipole") if iteration * G.dt >= self.start and iteration * G.dt <= self.stop: i = self.xcoord j = self.ycoord @@ -337,7 +374,8 @@ def calculate_incident_V_I(self, G): Args: G (class): Grid class instance - holds essential parameters describing the model. """ - + if G.cylindrical: + raise CmdInputError("Cylindrical coordinates not supported for transmission line") self.Vinc = np.zeros(G.iterations, dtype=floattype) self.Iinc = np.zeros(G.iterations, dtype=floattype) @@ -356,7 +394,8 @@ def update_abc(self, G): Args: G (class): Grid class instance - holds essential parameters describing the model. """ - + if G.cylindrical: + raise CmdInputError("Cylindrical coordinates not supported for transmission line") h = (c * G.dt - self.dl) / (c * G.dt + self.dl) self.voltage[0] = h * (self.voltage[1] - self.abcv0) + self.abcv1 self.abcv0 = self.voltage[0] @@ -369,7 +408,8 @@ def update_voltage(self, iteration, G): iteration (int): Current iteration (timestep). G (class): Grid class instance - holds essential parameters describing the model. """ - + if G.cylindrical: + raise CmdInputError("Cylindrical coordinates not supported for transmission line") # Update all the voltage values along the line self.voltage[1:self.nl] -= (self.resistance * (c * G.dt / self.dl) * (self.current[1:self.nl] - self.current[0:self.nl - 1])) @@ -388,7 +428,8 @@ def update_current(self, iteration, G): iteration (int): Current iteration (timestep). G (class): Grid class instance - holds essential parameters describing the model. """ - + if G.cylindrical: + raise CmdInputError("Cylindrical coordinates not supported for transmission line") # Update all the current values along the line self.current[0:self.nl - 1] -= ((1 / self.resistance) * (c * G.dt / self.dl) * (self.voltage[1:self.nl] - self.voltage[0:self.nl - 1])) @@ -407,7 +448,8 @@ def update_electric(self, iteration, updatecoeffsE, ID, Ex, Ey, Ez, G): Ex, Ey, Ez (memory view): numpy array of electric field values. G (class): Grid class instance - holds essential parameters describing the model. """ - + if G.cylindrical: + raise CmdInputError("Cylindrical coordinates not supported for transmission line") if iteration * G.dt >= self.start and iteration * G.dt <= self.stop: i = self.xcoord j = self.ycoord @@ -434,7 +476,8 @@ def update_magnetic(self, iteration, updatecoeffsH, ID, Hx, Hy, Hz, G): Hx, Hy, Hz (memory view): numpy array of magnetic field values. G (class): Grid class instance - holds essential parameters describing the model. """ - + if G.cylindrical: + raise CmdInputError("Cylindrical coordinates not supported for transmission line") if iteration * G.dt >= self.start and iteration * G.dt <= self.stop: i = self.xcoord j = self.ycoord diff --git a/gprMax/waveforms.py b/gprMax/waveforms.py index 008dd909a..b5f13cba7 100644 --- a/gprMax/waveforms.py +++ b/gprMax/waveforms.py @@ -54,7 +54,7 @@ def calculate_coefficients(self): self.chi = np.sqrt(2) / self.freq self.zeta = np.pi**2 * self.freq**2 - def calculate_value(self, time, dt): + def calculate_value(self, time, dt, cylindrical): """Calculates value of the waveform at a specific time. Args: @@ -71,6 +71,10 @@ def calculate_value(self, time, dt): if self.type == 'gaussian': delay = time - self.chi ampvalue = np.exp(-self.zeta * delay**2) + #if cylindrical: + #ampvalue *= np.exp(- 1j * 2 * np.pi * self.freq * time) + #else: + #ampvalue *= np.cos(2 * np.pi * self.freq * time) elif self.type == 'gaussiandot' or self.type == 'gaussianprime': delay = time - self.chi diff --git a/gprMax/yee_cell_build_cylindrical_ext.pyx b/gprMax/yee_cell_build_cylindrical_ext.pyx new file mode 100644 index 000000000..38ca0e07d --- /dev/null +++ b/gprMax/yee_cell_build_cylindrical_ext.pyx @@ -0,0 +1,240 @@ +# Copyright (C) 2015-2023: The University of Edinburgh +# Authors: Craig Warren and Antonis Giannopoulos +# +# This file is part of gprMax. +# +# gprMax is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# gprMax is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with gprMax. If not, see . + +import numpy as np +cimport numpy as np + +from gprMax.materials import Material +#We don't have to change those function to adapt to the cylindrical coordinates as each time j-1 is called, there is a statement +# if j != 0. +from gprMax.yee_cell_setget_rigid_ext cimport get_rigid_Ex +from gprMax.yee_cell_setget_rigid_ext cimport get_rigid_Ey +from gprMax.yee_cell_setget_rigid_ext cimport get_rigid_Ez +from gprMax.yee_cell_setget_rigid_ext cimport get_rigid_Hx +from gprMax.yee_cell_setget_rigid_ext cimport get_rigid_Hy +from gprMax.yee_cell_setget_rigid_ext cimport get_rigid_Hz + + +cpdef void create_electric_average(int i, int j, int k, int numID1, int numID2, int numID3, int numID4, int componentID, G): + """This function creates a new material by averaging the dielectric properties of the surrounding cells. + + Args: + i, j, k (int): Cell coordinates. + numID1, numID2, numID3, numID4 (int): Numeric IDs for materials in surrounding cells. + componentID (int): Numeric ID for electric field component. + G (class): Grid class instance - holds essential parameters describing the model. + """ + + # Make an ID composed of the names of the four materials that will be averaged + requiredID = G.materials[numID1].ID + '+' + G.materials[numID2].ID + '+' + G.materials[numID3].ID + '+' + G.materials[numID4].ID + + # Check if this material already exists + tmp = requiredID.split('+') + material = [x for x in G.materials if + x.ID.count(tmp[0]) == requiredID.count(tmp[0]) and + x.ID.count(tmp[1]) == requiredID.count(tmp[1]) and + x.ID.count(tmp[2]) == requiredID.count(tmp[2]) and + x.ID.count(tmp[3]) == requiredID.count(tmp[3])] + + if material: + G.ID[componentID, i, j, k] = material[0].numID + else: + # Create new material + newNumID = len(G.materials) + m = Material(newNumID, requiredID) + m.type = 'dielectric-smoothed' + # Create averaged constituents for material + m.er = np.mean((G.materials[numID1].er, G.materials[numID2].er, G.materials[numID3].er, G.materials[numID4].er), axis=0) + m.se = np.mean((G.materials[numID1].se, G.materials[numID2].se, G.materials[numID3].se, G.materials[numID4].se), axis=0) + m.mr = np.mean((G.materials[numID1].mr, G.materials[numID2].mr, G.materials[numID3].mr, G.materials[numID4].mr), axis=0) + m.sm = np.mean((G.materials[numID1].sm, G.materials[numID2].sm, G.materials[numID3].sm, G.materials[numID4].sm), axis=0) + + # Append the new material object to the materials list + G.materials.append(m) + + G.ID[componentID, i, j, k] = newNumID + + +cpdef void create_magnetic_average(int i, int j, int k, int numID1, int numID2, int componentID, G): + """This function creates a new material by averaging the dielectric properties of the surrounding cells. + + Args: + i, j, k (int): Cell coordinates. + numID1, numID2 (int): Numeric IDs for materials in surrounding cells. + componentID (int): Numeric ID for electric field component. + G (class): Grid class instance - holds essential parameters describing the model. + """ + + # Make an ID composed of the names of the two materials that will be averaged + requiredID = G.materials[numID1].ID + '+' + G.materials[numID2].ID + + # Check if this material already exists + tmp = requiredID.split('+') + material = [x for x in G.materials if + (x.ID.count(tmp[0]) == requiredID.count(tmp[0]) and + x.ID.count(tmp[1]) == requiredID.count(tmp[1])) or + (x.ID.count(tmp[0]) % 2 == 0 and x.ID.count(tmp[1]) % 2 == 0)] + + if material: + G.ID[componentID, i, j, k] = material[0].numID + else: + # Create new material + newNumID = len(G.materials) + m = Material(newNumID, requiredID) + m.type = 'dielectric-smoothed' + # Create averaged constituents for material + m.er = np.mean((G.materials[numID1].er, G.materials[numID2].er), axis=0) + m.se = np.mean((G.materials[numID1].se, G.materials[numID2].se), axis=0) + m.mr = np.mean((G.materials[numID1].mr, G.materials[numID2].mr), axis=0) + m.sm = np.mean((G.materials[numID1].sm, G.materials[numID2].sm), axis=0) + + # Append the new material object to the materials list + G.materials.append(m) + + G.ID[componentID, i, j, k] = newNumID + + +cpdef void build_electric_components(np.uint32_t[:, :, ::1] solid, np.int8_t[:, :, :, ::1] rigidE, np.uint32_t[:, :, :, ::1] ID, G): + """This function builds the electric field components in the ID array. + + Args: + solid, rigid, ID (memoryviews): Access to solid, rigid and ID arrays + G (class): Grid class instance - holds essential parameters describing the model. + """ + + cdef Py_ssize_t i, j, k + cdef int numID1, numID2, numID3, numID4, componentID + + # Er component + componentID = G.IDlookup['Er'] + for i in range(0, G.nr_cyl): + j = 0 + for k in range(1, G.nz_cyl): + + # If rigid is True do not average + if get_rigid_Ex(i, j, k, rigidE): + pass + else: + numID1 = solid[i, j, k] + numID4 = solid[i, j, k - 1] + + # If all values are the same no need to average + if numID1 == numID4: + ID[componentID, i, j, k] = numID1 + else: + # Averaging is required + # Create magnetic does the same as averaging in 2 dimensions. + create_magnetic_average(i, j, k, numID1, numID4, componentID, G) + + # Ephi component + componentID = G.IDlookup['Ephi'] + for i in range(1, G.nr_cyl): + j = 0 + for k in range(1, G.nz_cyl): + + # If rigid is True do not average + if get_rigid_Ey(i, j, k, rigidE): + pass + else: + numID1 = solid[i, j, k] + numID2 = solid[i - 1, j, k] + numID3 = solid[i - 1, j, k - 1] + numID4 = solid[i, j, k - 1] + + # If all values are the same no need to average + if numID1 == numID2 and numID1 == numID3 and numID1 == numID4: + ID[componentID, i, j, k] = numID1 + else: + # Averaging is required + # We do have four components to average + create_electric_average(i, j, k, numID1, numID2, numID3, numID4, componentID, G) + + # Ez component + componentID = G.IDlookup['Ez'] + for i in range(1, G.nr_cyl): + j = 0 + for k in range(0, G.nz_cyl): + + # If rigid is True do not average + if get_rigid_Ez(i, j, k, rigidE): + pass + else: + numID1 = solid[i, j, k] + numID2 = solid[i - 1, j, k] + + # If all values are the same no need to average + if numID1 == numID2 and numID1 == numID3 and numID1 == numID4: + ID[componentID, i, j, k] = numID1 + else: + # Averaging is required + create_magnetic_average(i, j, k, numID1, numID4, componentID, G) + + +cpdef void build_magnetic_components(np.uint32_t[:, :, ::1] solid, np.int8_t[:, :, :, ::1] rigidH, np.uint32_t[:, :, :, ::1] ID, G): + """This function builds the magnetic field components in the ID array. + + Args: + solid, rigid, ID (memoryviews): Access to solid, rigid and ID arrays + G (class): Grid class instance - holds essential parameters describing the model. + """ + + cdef Py_ssize_t i, j, k + cdef int numID1, numID2, componentID + + # Hr component + componentID = G.IDlookup['Hr'] + for i in range(1, G.nr_cyl): + j = 0 + for k in range(0, G.nz_cyl): + + # If rigid is True do not average + if get_rigid_Hx(i, j, k, rigidH): + pass + else: + numID1 = solid[i, j, k] + numID2 = solid[i - 1, j, k] + + # If all values are the same no need to average + if numID1 == numID2: + ID[componentID, i, j, k] = numID1 + else: + # Averaging is required + create_magnetic_average(i, j, k, numID1, numID2, componentID, G) + + # Hphi component: no averaging is needed as we only have 2 dimensions... + + + # Hz component + componentID = G.IDlookup['Hz'] + for i in range(0, G.nr_cyl): + j = 0 + for k in range(1, G.nz_cyl): + + # If rigid is True do not average + if get_rigid_Hz(i, j, k, rigidH): + pass + else: + numID1 = solid[i, j, k] + numID2 = solid[i, j, k - 1] + + # If all values are the same no need to average + if numID1 == numID2: + ID[5, i, j, k] = numID1 + else: + # Averaging is required + create_magnetic_average(i, j, k, numID1, numID2, componentID, G) diff --git a/setup.py b/setup.py index 2adb51e55..cf29aa6c7 100644 --- a/setup.py +++ b/setup.py @@ -175,7 +175,7 @@ compiler_directives={ 'boundscheck': False, 'wraparound': False, - 'initializedcheck': False, + 'initializedcheck': True, 'embedsignature': True, 'language_level': 3 }, diff --git a/tools/plot_source_wave.py b/tools/plot_source_wave.py index 4f8abb0a2..c9f759f59 100644 --- a/tools/plot_source_wave.py +++ b/tools/plot_source_wave.py @@ -78,7 +78,7 @@ def mpl_plot(w, timewindow, dt, iterations, fft=False): timeiter = np.nditer(time, flags=['c_index']) while not timeiter.finished: - waveform[timeiter.index] = w.calculate_value(timeiter[0], dt) + waveform[timeiter.index] = w.calculate_value(timeiter[0], dt, False) timeiter.iternext() print('Waveform characteristics...')