Closed dreme closed 4 months ago
@dreme, can you please check and specify the incident angle values for the plot for SE?
@yc1976Python, see updated plots with the correct θ labels. The line plots remain the same though.
Firstly, the field level input should be specified as S0 rather than E0 and S0 does not need to be included in the function outputs. Yong: OK. In the next code update, we can use S0 as the direct input and agree that no need to plot this in the outputs.
Secondly, the function should accept a numpy array input for z, rather than assuming z = np.linspace(-2, 0, 2001) Yong: Yes, this can be done with no problem. We can specify the number of points as per the formula discussed. We can either pass Nt as an input or pass the whole z array z = np.linspace(-2, 0, Nt) as an input for the function.
Thirdly, the SE and SE outputs are not aligning with the FEKO results - e.g. see below: Yong: Hi Vitas, the code in the main branch is the latest one and gives the correct result. The result can be plot from 0 to +2m. The code you attached is before the refactoring. One important point is that the array needs to be constructed as z = np.linspace(-2, 0, Nt) to be used for the function. Otherwise, it will give us wrong result
Below is the new with z arranged from 0 to 2m (f=1000MHz, at 0 and 89 degrees of incidence angles respectively).
@yc1976Python
I would still like to see S0 in the plots but there is no need to include S0 as an output of the compute_power_density
function since it is already known as an input to the function.
Probably better to pass the whole z array rather than Nt since this gives more flexibility (e.g. specifying different height range.
I'll download your latest code for the compute_power_density function from the main branch and check again.
@yc1976Python
I just tried the compute_power_density
code from the main
branch and it is still giving the same results as before. However, the plots that you displayed above look fine. Hmmm 😕
Here is the code that I'm running for the comparison of the compute_power_density
function to the FEKO results:
def compareScompute(thetas,gnd,er,σ,fMHz,pol,Stype):
'''Compare FEKO, planeEMR & planeEMRyong results for two layer case'''
E0 = 100
z = np.linspace(-2,0,2001)
fig, axes = plt.subplots(1,5, figsize=(10,6))
for θ, ax in zip(thetas,axes):
# Calculate compute_power_density SE & SH levels
Se, Sh, S0 = compute_power_density(gnd, E0, fMHz, θ, pol)
Scalc = Se if Stype=='SE' else Sh
# Get FEKO S levels from Sm dataframe
mask = (Sm.gnd==gnd) & (Sm.pol==pol) & (Sm.theta==θ) & \
(Sm.fMHz==fMHz) & (Sm.z <= 2) & (Sm['S type']==Stype)
df = Sm[mask]
# Create plot
ax.plot(df.S,df.z,'-',lw=4, alpha=0.7, label='FEKO',color='pink')
ax.plot(Scalc,-z,'--',lw=1,label='calculated',color='darkblue')
if θ==0: ax.set_ylabel('z (m)')
ax.set_xlabel('S (W/m²)')
ax.set_xlim(0, 60)
ax.set_ylim(0, 2.4)
ax.legend(fontsize=8)
ax.grid(ls='--')
ax.set_title(r'$\theta=$'+f'{θ}°')
plt.suptitle(f'Compare FEKO to compute_power_density\n{fMHz:g} MHz, {gnd} ground, {Stype}, {pol}')
plt.tight_layout()
plt.savefig(r'C:\Users\emeau\OneDrive\Desktop\compareS.svg')
Here also is the compute_power_density
function that I copied from main
import numpy as np
from scipy.constants import epsilon_0 as eps0, mu_0 as mu0
__all__ = ["compute_power_density"]
def compute_power_density(ground_type, E0, f, theta, pol):
"""Returns power density for a plane wave traveling through a
multilayered infinite planar medium where the first and last layers
have infinite thickness.
Parameters
----------
ground_type : string
Type of ground, either 'PEC Ground' or any other string for
real ground.
E0 : float
Electric field magnitude of the incident plane wave (V/m).
f : float
Frequency (Hz) of the plane wave.
theta : float
Incoming angle of the propagation vector (k) relative to the
normal of the first interface.
pol : {'TM', 'TE'}
Polarization of the plane wave.
'TM' = TM polarized wave (H parallel to interface)
'TE' = TE polarized wave (E parallel to interface)
Returns
-------
tuple
Containing the equivalent plane wave power density (SH, SE, S0)
Notes
-----
The algorithm is based on the theory found in Weng Cho Chew,
'Waves and Fields in Inhomogeneous Media,' IEEE PRESS Series on
Electromagnetic Waves. The code was adapted from a Matlab script
developed by Kimmo Karkkainen.
"""
# initialize settings
Z0 = np.sqrt(mu0 / eps0) # free-space impedance
z = np.linspace(-2, 0, 2001) # z-direction coordinates
zi = [0] # interface level between layer 1 and 2
epsr = [1, 1] # relative permittivities of layers 1 and 2
mur = [1, 1] # relative permeability of layers 1 and 2
# sigma = [0, 1e6] # lossless conditions
zi.append(1e9) # add a very large z value to act as infinity
N = len(epsr) # number of layers
w = 2.0 * np.pi * f * 1e6 # angular frequency
theta = np.deg2rad(theta)
S0 = (0.5*E0**2/Z0) * np.ones(len(z))
if ground_type == 'PEC Ground':
epsr = [1, 10]
sigma = [0, 1e6] # PEC Ground, lossless
else:
epsr = [1, 10]
sigma = [0, 0.1] # Real Ground
# wavenumber
eps = [er * eps0 + s / (1j * w) for er, s in zip(epsr, sigma)]
mu = [ur * mu0 for ur in mur]
K = [w * np.sqrt(e * mu0) for e in eps]
Kz = [0] * N
Kz[0] = K[0] * np.cos(theta)
Kt = K[0] * np.sin(theta)
for L in range(1, N):
SQ = K[L] ** 2 - Kt**2
SQr = np.real(SQ)
SQi = np.imag(SQ)
if (SQi == 0) & (SQr < 0):
Kz[L] = -np.sqrt(SQ)
else:
Kz[L] = np.sqrt(SQ)
# reflection and transmission coefficients for interfaces
R = np.empty((N, N), dtype=complex)
T = R.copy()
for k in range(N - 1):
if pol == "TM": # reflection coefficient for the H field
e1, e2 = eps[k + 1] * Kz[k], eps[k] * Kz[k + 1]
R[k, k + 1] = (e1 - e2) / (e1 + e2)
elif pol == "TE": # reflection coefficient for the E field
m1, m2 = mu[k + 1] * Kz[k], mu[k] * Kz[k + 1]
R[k, k + 1] = (m1 - m2) / (m1 + m2)
else:
raise Exception(f"pol ({pol}) must be either TE or TM")
R[k + 1, k] = -R[k, k + 1]
T[k, k + 1] = 1 + R[k, k + 1]
T[k + 1, k] = 1 + R[k + 1, k]
# generalized reflection coefficients for interfaces
gR = np.zeros(N, dtype=complex)
for k in range(N - 1)[::-1]:
thickness = zi[k + 1] - zi[k] # layer thickness
g = gR[k + 1] * np.exp(-2 * 1j * Kz[k + 1] * thickness)
gR[k] = (R[k, k + 1] + g) / (1 + R[k, k + 1] * g)
# amplitudes
A = np.zeros(N, dtype=complex)
if pol == "TM": # amplitude of the H field in layer 1
A[0] = np.abs(K[0] / (w * mu[0]))
else: # amplitude of the E field in layer 1
A[0] = 1
for k in range(1, N): # amplitudes in other layers
A[k] = (
A[k - 1]
* np.exp(-1j * Kz[k - 1] * zi[k - 1])
* T[k - 1, k]
/ (1 - R[k, k - 1] * gR[k]
* np.exp(-2 * 1j * Kz[k] * (zi[k] - zi[k - 1])))
/ np.exp(-1j * Kz[k] * zi[k - 1])
)
# form a vector that tells in which layer the calculation points are located
zl = []
layer = 0
for zp in z:
while zp >= zi[layer]:
layer += 1
zl.append(layer)
# E-field and H-field components
Azl = np.array([A[z] for z in zl])
Kzzl = np.array([Kz[z] for z in zl])
Kzl = np.array([K[z] for z in zl])
gRzl = np.array([gR[z] for z in zl])
epszl = np.array([eps[z] for z in zl])
zizl = np.array([zi[z] for z in zl])
sigmazl = np.array([sigma[z] for z in zl])
if pol == "TM": # the forward and backward Hf and Hb have only y component
Hf = Azl * np.exp(-1j * Kzzl * z)
Hb = Azl * gRzl * np.exp(-2 * 1j * Kzzl * zizl + 1j * Kzzl * z)
Ex = Z0 * np.cos(theta) * (Hf - Hb)
Ez = (-Z0) * np.sin(theta) * (Hf + Hb)
SH = 0.5 * E0**2 * Z0 * (np.abs(Hf + Hb)) ** 2
SE = 0.5 * E0**2 * (1 / Z0) * (np.abs(Ex) ** 2 + np.abs(Ez) ** 2)
else: # the forward and backward Ef and Eb have only y component
Ef = Azl * np.exp(-1j * Kzzl * z)
Eb = Azl * gRzl * np.exp(-2 * 1j * Kzzl * zizl + 1j * Kzzl * z)
Hx = -(1 / Z0) * np.cos(theta) * (Ef - Eb)
Hz = (1 / Z0) * np.sin(theta) * (Ef + Eb)
SH = 0.5 * E0**2 * Z0 * (np.abs(Hx) ** 2 + np.abs(Hz) ** 2)
SE = 0.5 * E0**2 * (1 / Z0) * (np.abs(Ef + Eb)) ** 2
return SH, SE, S0
@dreme Hi Vitas, your three comments are addressed and please have a look and let me know if you want me to pull a request.
The
compute_power_density
function needs some corrections:Firstly, the field level input should be specified as
S0
rather thanE0
andS0
does not need to be included in the function outputs.Secondly, the function should accept a numpy array input for
z
, rather than assumingz = np.linspace(-2, 0, 2001)
Thirdly, the SE and SE outputs are not aligning with the FEKO results - e.g. see below:
For another comparator, the
planeEMRyong
function below can be used as it has been validated against the FEKO results: