Skip to content

Commit 85a2a4f

Browse files
feat(earthadmittance): add homogeneous earth admittance formulations
1 parent 5746684 commit 85a2a4f

1 file changed

Lines changed: 225 additions & 0 deletions

File tree

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
abstract type Homogeneous <: EarthAdmittanceFormulation end
2+
3+
struct Kernel{Tγ1, Tγ2, Tμ2}
4+
"Layer where the source conductor is placed."
5+
s::Int
6+
"Layer where the target conductor is placed."
7+
t::Int
8+
"Primary field propagation constant (0 = lossless, 1 = air, 2 = earth)."
9+
Γx::Int
10+
"Air propagation constant γ₁(ω, μ, σ, ε)."
11+
γ1::Tγ1
12+
"Earth propagation constant γ₂(ω, μ, σ, ε)."
13+
γ2::Tγ2
14+
"Earth magnetic-constant assumption μ₂(μ)."
15+
μ2::Tμ2
16+
end
17+
18+
struct Papadopoulos{Tγ1, Tγ2, Tμ2} <: Homogeneous
19+
kernel::Kernel{Tγ1, Tγ2, Tμ2}
20+
end
21+
22+
Papadopoulos(; s::Int = 2, t::Int = 2, Γx::Int = 2,
23+
γ1 = (ω, μ, σ, ε) -> sqrt(1im * ω * μ *+ 1im*ω*ε)),
24+
γ2 = (ω, μ, σ, ε) -> sqrt(1im * ω * μ *+ 1im*ω*ε)),
25+
μ2 = μ -> μ) =
26+
Papadopoulos(
27+
Kernel{typeof(γ1), typeof(γ2), typeof(μ2)}(s, t, Γx, γ1, γ2, μ2),
28+
)
29+
30+
get_description(::Papadopoulos) = "Papadopoulos"
31+
from_kernel(f::Papadopoulos) = f.kernel
32+
33+
34+
struct Pollaczek{Tγ1, Tγ2, Tμ2} <: Homogeneous
35+
kernel::Kernel{Tγ1, Tγ2, Tμ2}
36+
end
37+
38+
Pollaczek(; s::Int = 2, t::Int = 2, Γx::Int = 0,
39+
γ1 = (ω, μ, σ, ε) -> 1im * ω * sqrt* ε),
40+
γ2 = (ω, μ, σ, ε) -> zero(1im * ω),
41+
μ2 = μ -> oftype(μ, μ₀)) =
42+
Pollaczek(
43+
Kernel{typeof(γ1), typeof(γ2), typeof(μ2)}(s, t, Γx, γ1, γ2, μ2),
44+
)
45+
46+
get_description(::Pollaczek) = "Pollaczek"
47+
from_kernel(f::Pollaczek) = f.kernel
48+
49+
struct Images{Tγ1, Tγ2, Tμ2} <: Homogeneous
50+
kernel::Kernel{Tγ1, Tγ2, Tμ2}
51+
end
52+
53+
Images(; s::Int = 1, t::Int = 1, Γx::Int = 0,
54+
γ1 = (ω, μ, σ, ε) -> 1im * ω * sqrt* ε),
55+
γ2 = (ω, μ, σ, ε) -> zero(1im * ω),
56+
μ2 = μ -> oftype(μ, μ₀)) =
57+
Images(
58+
Kernel{typeof(γ1), typeof(γ2), typeof(μ2)}(s, t, Γx, γ1, γ2, μ2),
59+
)
60+
61+
get_description(::Images) = "Electrostatic images"
62+
from_kernel(f::Images) = f.kernel
63+
64+
# ρ, ε, μ = ws.rho_g, ws.eps_g, ws.mu_g
65+
# f(h, d, @view(ρ[:,k]), @view(ε[:,k]), @view(μ[:,k]), ws.freq[k])
66+
67+
# Functor implementation for all homogeneous earth impedance formulations.
68+
function (f::Homogeneous)(
69+
form::Symbol,
70+
h::AbstractVector{T},
71+
yij::T,
72+
rho_g::AbstractVector{T},
73+
eps_g::AbstractVector{T},
74+
mu_g::AbstractVector{T},
75+
freq::T,
76+
) where {T <: REALSCALAR}
77+
Base.@nospecialize form
78+
return form === :self ? f(Val(:self), h, yij, rho_g, eps_g, mu_g, freq) :
79+
form === :mutual ? f(Val(:mutual), h, yij, rho_g, eps_g, mu_g, freq) :
80+
throw(ArgumentError("Unknown earth admittance form: $form"))
81+
end
82+
83+
function (f::Homogeneous)(
84+
h::AbstractVector{T},
85+
yij::T,
86+
rho_g::AbstractVector{T},
87+
eps_g::AbstractVector{T},
88+
mu_g::AbstractVector{T},
89+
freq::T,
90+
) where {T <: REALSCALAR}
91+
return f(Val(:mutual), h, yij, rho_g, eps_g, mu_g, freq)
92+
end
93+
94+
@inline _to_σ(ρ) = isinf(ρ) ? zero(ρ) : (iszero(ρ) ? inv(zero(ρ)) : inv(ρ))
95+
@inline _not(s::Int) =
96+
(s == 1 || s == 2) ? (3 - s) :
97+
throw(ArgumentError("s must be 1 or 2"))
98+
99+
@inline _get_layer(z) =
100+
z > 0 ? 1 :
101+
(z < 0 ? 2 : throw(ArgumentError("Conductor at interface (h=0) is invalid")))
102+
103+
@noinline function _layer_mismatch(which::AbstractString, got::Int, expected::Int)
104+
throw(
105+
ArgumentError(
106+
"conductor $which is in layer $got but formulation expects layer $expected",
107+
),
108+
)
109+
end
110+
111+
@inline function validate_layers!(f::Homogeneous, h)
112+
@boundscheck length(h) == 2 || throw(ArgumentError("h must have length 2"))
113+
ℓ1 = _get_layer(h[1])
114+
ℓ2 = _get_layer(h[2])
115+
(ℓ1 == f.s) || _layer_mismatch("i (h[1])", ℓ1, f.s)
116+
(ℓ2 == f.t) || _layer_mismatch("j (h[2])", ℓ2, f.t)
117+
return nothing
118+
end
119+
120+
@inline function _bessel_diff(γs, d::T, D::T) where {T}
121+
zmax = max(abs(γs)*d, abs(γs)*D)
122+
zmax < T(1e-6) ? log(D/d) : (besselk(0, γs*d) - besselk(0, γs*D))
123+
end
124+
125+
126+
127+
@inline function (f::Homogeneous)(
128+
::Val{:mutual},
129+
h::AbstractVector{T},
130+
yij::T,
131+
rho_g::AbstractVector{T},
132+
eps_g::AbstractVector{T},
133+
mu_g::AbstractVector{T},
134+
freq::T,
135+
) where {T <: REALSCALAR}
136+
137+
validate_layers!(f, h)
138+
139+
s = f.s # index of source layer
140+
o = _not(s) # the other layer
141+
ω = 2π*freq
142+
nL = length(rho_g)
143+
μ = similar(mu_g);
144+
σ = similar(rho_g);
145+
@inbounds for i in 1:nL
146+
μ[i] = (i == 1) ? mu_g[i] : f.μ2(mu_g[i]) # μ₂ for earth layers
147+
σ[i] = _to_σ(rho_g[i])
148+
end
149+
150+
# construct propagation constants according to formulation assumptions
151+
γ = Vector{Complex{T}}(undef, nL)
152+
@inbounds for i in 1:nL
153+
γ[i] = (i == 1 ? f.γ1 : f.γ2)(ω, μ[i], σ[i], eps_g[i])
154+
end
155+
γ_s = γ[s];
156+
γ_o = γ[o]
157+
γs_2 = γ_s^2
158+
γo_2 = γ_o^2
159+
160+
# kx from struct: 0:none, 1:air, 2:source layer
161+
kx_2 = if f.Γx == 0 # precalc squared
162+
zero(γs_2)
163+
else
164+
= (f.Γx == 1) ? 1 : s
165+
oftype(γs_2, (ω^2) * μ[ℓ] * eps_g[ℓ])
166+
end
167+
168+
# --- Underground,"no capacitive coupling" ---
169+
# physics: source in EARTH (s=t=2), kx = 0, γ_earth ≈ 0
170+
# ⇒ Pe = 0
171+
if f.s == 2 && f.t == 2 && isapprox(γ_s, zero(γ_s))
172+
return zero(Complex{T})
173+
end
174+
175+
# unpack geometry
176+
@inbounds hi, hj = abs(h[1]), abs(h[2])
177+
dij = hypot(yij, hi - hj) # √(y^2 + (hi - hj)^2) - conductor-conductor
178+
Dij = hypot(yij, hi + hj) # √(y^2 + (hi + hj)^2) - conductor-image
179+
180+
# perfectly conducting earth term in Bessel form
181+
Λij = _bessel_diff(γ_s, dij, Dij)
182+
183+
# --- Overhead special case ---
184+
# physics: source in AIR (s=t=1), kx = 0, σ_air ≈ 0,
185+
# earth propagation constant negligible γ_earth ≈ 0
186+
# ⇒ Sij = Tij = 0, Pe = (jω)/(2π(σ_air+jωε_air)) * Λ ≡ (1/(2π ε0)) * Λ
187+
if f.s == 1 && f.Γx == 0 && isapprox(γ_o, zero(γ_o))
188+
return (1im*ω) / (2π*eps_g[1]) * Λij
189+
end
190+
191+
# precompute scalars for integrand
192+
a_s =::T) -> sqrt^2 + γs_2 + kx_2)
193+
a_o =::T) -> sqrt^2 + γo_2 + kx_2)
194+
μ_s = μ[s]
195+
μ_o = μ[o]
196+
H = hi + hj
197+
198+
# earth correction term
199+
Fij = (λ) -> begin
200+
as = a_s(λ);
201+
ao = a_o(λ)
202+
μ_o * exp(-as * H) / (as*μ_o + ao*μ_s)
203+
end
204+
205+
# G_ij(λ)
206+
# G = μ0 μ1 α1 (γ1² - γ0²) e^{-α1 H} / [ (α1 μ0 + α0 μ1)(α1 γ0² μ1 + α0 γ1² μ0) ]
207+
# here: 0→other (o), 1→source (s)
208+
Gij = (λ) -> begin
209+
as = a_s(λ);
210+
ao = a_o(λ)
211+
num = μ_o * μ_s * as * (γs_2 - γo_2) * exp(-as * H)
212+
den = (as*μ_o + ao*μ_s) * (as*γo_2*μ_s + ao*γs_2*μ_o)
213+
num / den
214+
end
215+
216+
# S_ij + T_ij in one go: 2∫₀^∞ (Fij+Gij) cos(yij λ) dλ
217+
integrand = (λ) -> (Fij(λ) + Gij(λ)) * cos(yij * λ)
218+
Iij, _ = quadgk(integrand, 0.0, Inf; rtol = 1e-8)
219+
Iij *= 2
220+
221+
_σ_s = σ[s] + 1im*ω*eps_g[s] # complex conductivity of source layer
222+
223+
return (1im * ω / (2π * _σ_s)) * (Λij + Iij)
224+
225+
end

0 commit comments

Comments
 (0)