Mesh grid for mode finding - Part 2

In Mesh grid for mode finding - Part 1 we used LongwaveModePropagator.trianglemesh to initialize the GRPF grid for mode finding several different ionospheres and transmit frequencies. In part 2 we'll look at where grpf can fail for certain scenarios.

Naturally we want to be able to use the coarsest initial mesh grid as possible because the reflection coefficient has to be integrated through the ionosphere for every node of the grid. This step is by far the most computationally expensive operation of all of LongwaveModePropagator.

First, let's load the packages needed in this example.

using Plots
using Plots.Measures
using RootsAndPoles

using LongwaveModePropagator
using LongwaveModePropagator: QE, ME, solvemodalequation, trianglemesh, defaultmesh

In the LongwaveModePropagator tests suite, the segmented_scenario is known to miss roots if we use the same trianglemesh parameters (with Δr = deg2rad(0.5)) as in part 1 of the example.

Here we define the HomogeneousWaveguide from the second half of the segmented_scenario known to have the missing roots.

frequency = Frequency(24e3)
electrons = Species(QE, ME, z->waitprofile(z, 80, 0.45), electroncollisionfrequency)
waveguide = HomogeneousWaveguide(BField(50e-6, π/2, 0), electrons, Ground(15, 0.001))
me = PhysicalModeEquation(frequency, waveguide)
title = "24 kHz\nh′: 80, β: 0.45"

First, let's simply compute the mode equation on a fine grid.

Δr = 0.2
x = 30:Δr:90
y = -10:Δr:0
mesh = x .+ 1im*y';

As in part 1, we also define a function to compute the modal equation phase.

function modeequationphase(me, mesh)
    phase = Vector{Float64}(undef, length(mesh))
    Threads.@threads for i in eachindex(mesh)
        f = solvemodalequation(deg2rad(mesh[i]), me)
        phase[i] = rad2deg(angle(f))
    end
    return phase
end

phase = modeequationphase(me, mesh);

heatmap(x, y, reshape(phase, length(x), length(y))';
        color=:twilight, clims=(-180, 180),
        xlims=(30, 90), ylims=(-10, 0),
        xlabel="real(θ)", ylabel="imag(θ)",
        title=title,
        right_margin=2mm)
┌ Warning: Instability detected. Aborting
└ @ SciMLBase ~/.julia/packages/SciMLBase/NjslX/src/integrator_interface.jl:626

Let's run the grpf with Δr = 0.5.

zbl = deg2rad(complex(30.0, -10.0))
ztr = deg2rad(complex(89.9, 0.0))
Δr = deg2rad(0.5)

mesh = trianglemesh(zbl, ztr, Δr)

params = LMPParams().grpfparams
roots, poles, quads, phasediffs, tess, g2f = grpf(θ->solvemodalequation(θ, me),
                                                  mesh, PlotData(), params);
z, edgecolors = getplotdata(tess, quads, phasediffs, g2f)

zdeg = rad2deg.(z)
rootsdeg = rad2deg.(roots)
polesdeg = rad2deg.(poles)

twilightquads = [
    colorant"#9E3D36",
    colorant"#C99478",
    colorant"#7599C2",
    colorant"#5C389E",
    colorant"#404040",
    RGB(0.0, 0.0, 0.0)
]

img = plot(real(zdeg), imag(zdeg); group=edgecolors, palette=twilightquads, linewidth=1.5,
           xlims=(30, 90), ylims=(-10, 0),
           xlabel="real(θ)", ylabel="imag(θ)", legend=false,
           title=title);
plot!(img, real(rootsdeg), imag(rootsdeg); color="red",
      seriestype=:scatter, markersize=5);
plot!(img, real(polesdeg), imag(polesdeg); color="red",
      seriestype=:scatter, markershape=:utriangle, markersize=5)

Upon inspection, it is clear that there is a root/pole visible at the upper right corner of the domain in the fine mesh above that is not identified by grpf. In fact, the algorithm did not even try to refine the mesh in this region. That is because the root and pole pair are too closely spaced for the algorithm to know they are there. The discretized Cauchy argument principle (DCAP), upon which GRPF is based, can only identify the presence of roots and poles if there are an unequal number of them. If the complex phase change around a tessellation contour is equal to 0, there may be no roots or poles in the contoured region, or there may be an equal number of roots and poles.

This run identified 20 roots and 19 poles. Let's run grpf again with a finer resolution of Δr = 0.2.

Δr = deg2rad(0.2)

mesh = trianglemesh(zbl, ztr, Δr)

roots, poles, quads, phasediffs, tess, g2f = grpf(θ->solvemodalequation(θ, me),
                                                  mesh, PlotData(), params);
z, edgecolors = getplotdata(tess, quads, phasediffs, g2f)

zdeg = rad2deg.(z)
rootsdeg = rad2deg.(roots)
polesdeg = rad2deg.(poles)

img = plot(real(zdeg), imag(zdeg); group=edgecolors, palette=twilightquads, linewidth=1.5,
           xlims=(30, 90), ylims=(-10, 0),
           xlabel="real(θ)", ylabel="imag(θ)", legend=false,
           title=title);
plot!(img, real(rootsdeg), imag(rootsdeg); color="red",
      seriestype=:scatter, markersize=5);
plot!(img, real(polesdeg), imag(polesdeg); color="red",
      seriestype=:scatter, markershape=:utriangle, markersize=5)

This higher resolution initial grid has identified 22 roots and 21 poles. Zooming in on the upper right region, we can see that the previously missing roots and poles are very closely spaced.

img = plot(real(zdeg), imag(zdeg); group=edgecolors, palette=twilightquads, linewidth=1.5,
           xlims=(80, 90), ylims=(-2, 0),
           xlabel="real(θ)", ylabel="imag(θ)", legend=false,
           title=title);
plot!(img, real(rootsdeg), imag(rootsdeg); color="red",
      seriestype=:scatter, markersize=5);
plot!(img, real(polesdeg), imag(polesdeg); color="red",
      seriestype=:scatter, markershape=:utriangle, markersize=5)

Roots are frequently located closely to poles in the upper right of the domain for a variety of ionospheres. To ensure they are captured, LongwaveModePropagator.defaultmesh uses a spacing of Δr = deg2rad(0.5) for most of the mesh, but a spacing of Δr = deg2rad(0.1) for the region with real greater than 75° and imaginary greater than -1.5°.

mesh = defaultmesh(frequency)
meshdeg = rad2deg.(mesh)

img = plot(real(meshdeg), imag(meshdeg); seriestype=:scatter,
           xlabel="real(θ)", ylabel="imag(θ)",
           size=(450,375), legend=false);
plot!(img, [30, 90], [0, 0]; color="red");
plot!(img, [80, 90], [-10, 0]; color="red")

Frequencies below 12 kHz

The LongwaveModePropagator.defaultmesh at frequencies below 12 kHz is rectangular from LMP.rectangulardomain, and uses a lower point density with Δr_coarse = deg2rad(1) and no fine mesh.

frequency = 1e3
mesh = defaultmesh(frequency)
meshdeg = rad2deg.(mesh)

img = plot(real(meshdeg), imag(meshdeg); seriestype=:scatter,
           xlabel="real(θ)", ylabel="imag(θ)",
           size=(450,375), legend=false)


This page was generated using Literate.jl.