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.
BasicInput
The first is known as a BasicInput. 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 BasicInput. It's defined as a mutable struct, so it is simple to specify the fields one by one.
input = BasicInput()
input.name = "basic"
input.description = "Test BasicInput"
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 BasicInput",
"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 linearly interpolated when integrating the ionosphere reflection coefficient and wavefields (so it's better if these tables are fairly densely sampled with respect to altitude).
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 BasicInput 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 BasicInputs 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 BasicInput", Dates.DateTime("2021-03-30T04:42:11.558"), [0.0, 100000.0, 200000.0, 300000.0, 400000.0, 500000.0, 600000.0, 700000.0, 800000.0, 900000.0, 1.0e6], [64.27133295779021, 89.04189498911508, 83.23008455595095, 80.32779387821108, 72.47017163379161, 76.88384886955764, 74.2993923250402, 67.3355757107638, 60.427401551918216, 60.89548728720238, 63.82664080710805], [0.0, 1.9976896617895836, 1.7439861968756596, 1.6644056550019461, 1.0867380623347644, 1.2317135189395227, 1.6636374309956607, 2.0611646518052975, 2.6917540013614225, 4.4676715464725065, 5.067363336321381])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 BasicInput 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 BasicInput";
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"))BasicInput("basic", "Test BasicInput", 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 BasicInput"
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"))BasicInput("basic", "Test BasicInput", 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.