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 LongwaveModePropagator
Throughout the examples, we'll also define LMP
as shorthand for LongwaveModePropagator
when accessing functions from the package that aren't exported.
const LMP = LongwaveModePropagator
Inputs
There are two primary ways to define LongwaveModePropagator.Input
s, 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::String
description::String
datetime::DateTime
segment_ranges::Vector{Float64}
: distance from transmitter to the beginning of eachHomogeneousWaveguide
segment in meters.hprimes::Vector{Float64}
: Wait's $h'$ parameter for eachHomogeneousWaveguide
segment.betas::Vector{Float64}
: Wait's $\beta$ parameter for eachHomogeneousWaveguide
segment.b_mags::Vector{Float64}
: magnetic field magnitude for eachHomogeneousWaveguide
segment.b_dips::Vector{Float64}
: magnetic field dip angles in radians for eachHomogeneousWaveguide
segment.b_azs::Vector{Float64}
: magnetic field azimuth in radians "east" of the propagation direction for eachHomogeneousWaveguide
segment.ground_sigmas::Vector{Float64}
: ground conductivity in Siemens per meter for eachHomogeneousWaveguide
segment.ground_epsrs::Vector{Int}
: ground relative permittivity for eachHomogeneousWaveguide
segment.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.0e6
Here 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)
end
TableInput
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::String
description::String
datetime::DateTime
segment_ranges::Vector{Float64}
: distance from transmitter to the beginning of eachHomogeneousWaveguide
segment in meters.altitude::Vector{Float64}
: altitude above ground in meters for which thedensity
andcollision_frequency
profiles are specified.density::Vector{Float64}
: electron density at eachaltitude
in $m⁻³$.collision_frequency::Vector{Float64}
: electron-ion collision frequency at eachaltitude
in $s⁻¹$.b_dips::Vector{Float64}
: magnetic field dip angles in radians for eachHomogeneousWaveguide
segment.b_azs::Vector{Float64}
: magnetic field azimuth in radians "east" of the propagation direction for eachHomogeneousWaveguide
segment.ground_sigmas::Vector{Float64}
: ground conductivity in Siemens per meter for eachHomogeneousWaveguide
segment.ground_epsrs::Vector{Int}
: ground relative permittivity for eachHomogeneousWaveguide
segment.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 ExponentialInput
s and TableInput
s create BasicOutput
s, but the BatchInput
creates a BatchOutput
.
The BasicOutput
contains fields for
name::String
description::String
datetime::DateTime
output_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.
output
BasicOutput("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 BasicOutput
s, 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.
cd
in 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 LongwaveModePropagator
propagate("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.