File-based I/O
LongwaveModePropagator provides basic propagation capabilities interfacing through JSON files. Julia is still needed to run the model, but scenarios can otherwise be defined and analyzed from e.g. Matlab or Python.
Examples of writing and reading compatible JSON files are provided below for Matlab and Python.
Let's load the necessary packages.
using Dates
using JSON3
using LongwaveModePropagatorThroughout the examples, we'll also define LMP as shorthand for LongwaveModePropagator when accessing functions from the package that aren't exported.
const LMP = LongwaveModePropagatorInputs
There are two primary ways to define LongwaveModePropagator.Inputs, the abstract supertype for inputing information to the model.
ExponentialInput
The first is known as a ExponentialInput. It defines the ionosphere using Wait and Spies $h'$ and $\beta$ parameters.
It contains the fields
name::Stringdescription::Stringdatetime::DateTimesegment_ranges::Vector{Float64}: distance from transmitter to the beginning of eachHomogeneousWaveguidesegment in meters.hprimes::Vector{Float64}: Wait's $h'$ parameter for eachHomogeneousWaveguidesegment.betas::Vector{Float64}: Wait's $\beta$ parameter for eachHomogeneousWaveguidesegment.b_mags::Vector{Float64}: magnetic field magnitude for eachHomogeneousWaveguidesegment.b_dips::Vector{Float64}: magnetic field dip angles in radians for eachHomogeneousWaveguidesegment.b_azs::Vector{Float64}: magnetic field azimuth in radians "east" of the propagation direction for eachHomogeneousWaveguidesegment.ground_sigmas::Vector{Float64}: ground conductivity in Siemens per meter for eachHomogeneousWaveguidesegment.ground_epsrs::Vector{Int}: ground relative permittivity for eachHomogeneousWaveguidesegment.frequency::Float64: transmitter frequency in Hertz.output_ranges::Vector{Float64}: distances from the transmitter at which the field will be calculated.
The fields that are vectors allow the definition of a SegmentedWaveguide where each element of the vector is its own HomogeneousWaveguide segment. Single element vectors are treated as a single HomogeneousWaveguide.
To show the equivalent JSON format, we'll build a simple, homogeneous ionosphere ExponentialInput. It's defined as a mutable struct, so it is simple to specify the fields one by one.
input = ExponentialInput()
input.name = "basic"
input.description = "Test ExponentialInput"
input.datetime = DateTime("2020-12-29T05:00:00.000") # usually `Dates.now()`
input.segment_ranges = [0.0]
input.hprimes = [75]
input.betas = [0.35]
input.b_mags= [50e-6]
input.b_dips = [π/2]
input.b_azs = [0.0]
input.ground_sigmas = [0.001]
input.ground_epsrs = [4]
input.frequency = 24e3
input.output_ranges = collect(0:100e3:1000e3)11-element Vector{Float64}:
0.0
100000.0
200000.0
300000.0
400000.0
500000.0
600000.0
700000.0
800000.0
900000.0
1.0e6Here it is formatted as JSON.
json_str = JSON3.pretty(input){
"name": "basic",
"description": "Test ExponentialInput",
"datetime": "2020-12-29T05:00:00.0",
"segment_ranges": [
0
],
"hprimes": [
75
],
"betas": [
0.35
],
"b_mags": [
5.0e-5
],
"b_dips": [
1.5707963267948966
],
"b_azs": [
0
],
"ground_sigmas": [
0.001
],
"ground_epsrs": [
4
],
"frequency": 24000,
"output_ranges": [
0,
100000,
200000,
300000,
400000,
500000,
600000,
700000,
800000,
900000,
1000000
]
}Let's also save this to a file.
json_str = JSON3.write(input)
root_dir = dirname(dirname(pathof(LongwaveModePropagator)))
examples_dir = joinpath(root_dir, "examples")
filename = joinpath(examples_dir, "basic.json")
open(filename,"w") do f
write(f, json_str)
endTableInput
The second input is the TableInput. This type defines the ionosphere using a tabular input of number density and collision frequency as a function of altitude. These tables are then cubic spline interpolated when integrating the ionosphere reflection coefficient and wavefields. See also interpolating functions.
The fields of the TableInput are
name::Stringdescription::Stringdatetime::DateTimesegment_ranges::Vector{Float64}: distance from transmitter to the beginning of eachHomogeneousWaveguidesegment in meters.altitude::Vector{Float64}: altitude above ground in meters for which thedensityandcollision_frequencyprofiles are specified.density::Vector{Float64}: electron density at eachaltitudein $m⁻³$.collision_frequency::Vector{Float64}: electron-ion collision frequency at eachaltitudein $s⁻¹$.b_dips::Vector{Float64}: magnetic field dip angles in radians for eachHomogeneousWaveguidesegment.b_azs::Vector{Float64}: magnetic field azimuth in radians "east" of the propagation direction for eachHomogeneousWaveguidesegment.ground_sigmas::Vector{Float64}: ground conductivity in Siemens per meter for eachHomogeneousWaveguidesegment.ground_epsrs::Vector{Int}: ground relative permittivity for eachHomogeneousWaveguidesegment.frequency::Float64: transmitter frequency in Hertz.output_ranges::Vector{Float64}: distances from the transmitter at which the field will be calculated.
Again we'll construct a TableInput to look at the JSON
tinput = TableInput()
tinput.name = "table"
tinput.description = "Test TableInput"
tinput.datetime = DateTime("2020-12-29T05:00:00.000")
tinput.segment_ranges = [0.0]
tinput.altitude = collect(50e3:5e3:100e3)
tinput.density = [waitprofile.(tinput.altitude, 75, 0.3)]
tinput.collision_frequency = [electroncollisionfrequency.(tinput.altitude)]
tinput.b_mags= [50e-6]
tinput.b_dips = [π/2]
tinput.b_azs = [0.0]
tinput.ground_sigmas = [0.001]
tinput.ground_epsrs = [4]
tinput.frequency = 24e3
tinput.output_ranges = collect(0:100e3:1000e3)
json_str = JSON3.pretty(tinput){
"name": "table",
"description": "Test TableInput",
"datetime": "2020-12-29T05:00:00.0",
"segment_ranges": [
0
],
"altitude": [
50000,
55000,
60000,
65000,
70000,
75000,
80000,
85000,
90000,
95000,
100000
],
"density": [
[
4.374403183176109e6,
9.26061161145436e6,
1.9604714935292408e7,
4.150318184370078e7,
8.78622366525934e7,
1.8600435645316696e8,
3.9377122570138437e8,
8.33613691351424e8,
1.7647601984395177e9,
3.735997369413846e9,
7.909106493114021e9
]
],
"collision_frequency": [
[
1.0044012161884667e8,
4.744455400598282e7,
2.2411220422141008e7,
1.0586310933525776e7,
5.0006192019140925e6,
2.362125253978684e6,
1.1157889633644049e6,
527061.3862109144,
248966.17008735015,
117603.29151329474,
55551.86140313166
]
],
"b_mags": [
5.0e-5
],
"b_dips": [
1.5707963267948966
],
"b_azs": [
0
],
"ground_sigmas": [
0.001
],
"ground_epsrs": [
4
],
"frequency": 24000,
"output_ranges": [
0,
100000,
200000,
300000,
400000,
500000,
600000,
700000,
800000,
900000,
1000000
]
}BatchInput
Both the ExponentialInput and TableInput types can be collected together in a BatchInput which has fields for a name, description, datetime, and vector of Inputs. This is useful for keeping a set of scenarios together. See the test/IO.jl file for additional help on how these should be formatted.
Running the model from a JSON file
To run the model, propagate accepts a filename input (see the help for optional arguments). However, instead of returning a tuple of complex electric field, amplitude, and phase, it returns an Output type. Additionally, it saves the output to a JSON file.
output = propagate(filename);Outputs
There are only two LongwaveModePropagator.Output types: BasicOutput and BatchOutput. Both ExponentialInputs and TableInputs create BasicOutputs, but the BatchInput creates a BatchOutput.
The BasicOutput contains fields for
name::Stringdescription::Stringdatetime::DateTimeoutput_ranges::Vector{Float64}amplitude::Vector{Float64}phase::Vector{Float64}
where name and description are directly copied from the input and datetime is when the model was run.
outputBasicOutput("basic", "Test ExponentialInput", Dates.DateTime("2024-04-02T01:20:54.371"), [0.0, 100000.0, 200000.0, 300000.0, 400000.0, 500000.0, 600000.0, 700000.0, 800000.0, 900000.0, 1.0e6], [0.0, 69.0426964709796, 63.229422008559325, 60.32681629296636, 52.462443988783036, 56.88110839258877, 54.30206879139415, 47.3475754136759, 40.456022675636135, 40.88177155646609, 43.81360889374253], [0.7853981633974483, 1.997777979904867, 1.7446138134609641, 1.6652447901301357, 1.0873929894579495, 1.2320548033492473, 1.6639938989482779, 2.0613765776802224, 2.6906024367035863, 4.465145286872477, 5.0669236694253215])Not surprisingly, a BatchOutput is simply a container holding a Vector of BasicOutputs, as well as some additional metadata from the corresponding BatchInput.
JSON I/O from Matlab
Here's an example of how to encode the above ExponentialInput to JSON and decode the output using Matlab. It's also in the file examples/io.m.
% Matlab script
input.name = "basic";
input.description = "Test ExponentialInput";
input.datetime = '2020-12-28T21:06:50.501';
input.segment_ranges = {0.0};
input.hprimes = {75};
input.betas = {0.35};
input.b_mags = {50e-6};
input.b_dips = {pi/2};
input.b_azs = {0.0};
input.ground_sigmas = {0.001};
input.ground_epsrs = {4};
input.frequency = 24e3;
input.output_ranges = 0:100e3:1000e3;
json_str = jsonencode(input);
fid = fopen('basic_matlab.json', 'w');
fwrite(fid, json_str, 'char');
fclose(fid);Matlab was used to generate the file basic_matlab.json. We can confirm it's parsed correctly by using the internal LongwaveModePropagator function LongwaveModePropagator.parse, which attempts to parse JSON files into recognized input and output formats.
matlab_input = LMP.parse(joinpath(examples_dir, "basic_matlab.json"))ExponentialInput("basic", "Test ExponentialInput", Dates.DateTime("2020-12-28T21:06:50.501"), [0.0], [75.0], [0.35], [5.0e-5], [1.5707963267948966], [0.0], [0.001], [4], 24000.0, [0.0, 100000.0, 200000.0, 300000.0, 400000.0, 500000.0, 600000.0, 700000.0, 800000.0, 900000.0, 1.0e6])Let's run it.
matlab_output = propagate(joinpath(examples_dir, "basic_matlab.json"));It is possible to run Julia as a script, calling it directly from a terminal (see the docs), but it is probably easiest to just run the code from the REPL.
cdin a terminal to your working directory- Start up Julia:
julia - It's recommended to
] activate ., generating a new environment in this directory - If necessary, install LongwaveModePropagator.jl:
] add LongwaveModePropagator using LongwaveModePropagatorpropagate("basic_matlab.json")
If this has been done before in the same directory, then just do steps 1, 2, 5, 6.
Reading the results file from Matlab is relatively simple.
% Matlab script
filename = 'basic_matlab_output.json';
text = fileread(filename);
output = jsondecode(text);JSON I/O from Python
Here is similar code for Python, also available in the file io.py.
# Python script
import json
import datetime
import numpy as np
input = dict()
input['name'] = "basic"
input['description'] = "Test ExponentialInput"
input['datetime'] = datetime.datetime.now().isoformat()[:-3]
input['segment_ranges'] = [0.0]
input['hprimes'] = [75]
input['betas'] = [0.35]
input['b_mags'] = [50e-6]
input['b_dips'] = [np.pi/2]
input['b_azs'] = [0.0]
input['ground_sigmas'] = [0.001]
input['ground_epsrs'] = [4]
input['frequency'] = 24e3
input['output_ranges'] = np.arange(0, 1000e3, 100e3).tolist()
json_str = json.dumps(input)
with open('basic_python.json', 'w') as file:
file.write(json_str)Let's ensure that the JSON file is correctly parsed.
python_input = LMP.parse(joinpath(examples_dir, "basic_python.json"))ExponentialInput("basic", "Test ExponentialInput", Dates.DateTime("2020-12-29T11:35:11.989"), [0.0], [75.0], [0.35], [5.0e-5], [1.5707963267948966], [0.0], [0.001], [4], 24000.0, [0.0, 100000.0, 200000.0, 300000.0, 400000.0, 500000.0, 600000.0, 700000.0, 800000.0, 900000.0])And run the file.
python_output = propagate(joinpath(examples_dir, "basic_python.json"));To read the results:
with open('basic_python_output.json', 'r') as file:
output = json.load(file)This page was generated using Literate.jl.