I/O & CLI#
Starting with empymod v2.2.0
there are some basic saving and loading
routines together with a command line interface. This makes it possible to
model EM responses relatively straight forward from any other code.
I/O#
There are currently two saving and two loading routines, one each for inputs and one for data. «Input» in this context means all the parameters a modelling routine takes. The saving/loading routines are on one hand good for persistence and reproducibility, but on the other hand also necessary for the command-line interface (see section CLI).
{save;load}_input
#
Let’s look at a simple example. From the start, we collect the input parameters in a dictionary instead of providing them directly to the function.
In [1]: import empymod
...: import numpy as np
...:
...: # Define input parameters
...: inp = {
...: 'src': [[0, 0], [0, 1000], 250, [0, 90], 0],
...: 'rec': [np.arange(1, 6)*2000, np.zeros(5), 300, 0, 0],
...: 'freqtime': np.logspace(-1, 1, 3),
...: 'depth': [0, 300, 1500, 1600],
...: 'res': [2e14, 0.3, 1, 100, 1],
...: }
...:
...: # Model it
...: efield = empymod.bipole(**inp)
...:
:: empymod END; runtime = 0:00:00.010022 :: 2 kernel call(s)
We can now simply save this dictionary to disk via
In [2]: empymod.io.save_input('myrun.json', inp)
This will save the input parameters in the file myrun.json
(you can provide
absolute or relative paths in addition to the file name). The file name must
currently include .json
, as it is the only backend implemented so far. The
json-file is a plain text file, so you can open it with your favourite editor.
Let’s have a look at it:
In [3]: !cat myrun.json
{
"src": [[0, 0], [0, 1000], 250, [0, 90], 0],
"rec": [[2000, 4000, 6000, 8000, 10000], [0.0, 0.0, 0.0, 0.0, 0.0], 300, 0, 0],
"freqtime": [0.1, 1.0, 10.0],
"depth": [0, 300, 1500, 1600],
"res": [200000000000000.0, 0.3, 1, 100, 1]
}
As you can see, it is basically the dictionary written as json. You can therefore write your input parameter file with any program you want to.
These input files can then be loaded to run the exactly same simulation again.
In [4]: inp_loaded = empymod.io.load_input('myrun.json')
...: efield2 = empymod.bipole(**inp_loaded)
...: # Let's check if the result is indeed the same.
...: print(f"Result is identical: {np.allclose(efield, efield2, atol=0)}")
...:
:: empymod END; runtime = 0:00:00.010449 :: 2 kernel call(s)
Result is identical: True
{save;load}_data
#
These functions are to store or load data. Using the computation from above, we can store the data in one of the following two ways, either as json or as text file:
In [5]: empymod.io.save_data('mydata.json', efield)
...: empymod.io.save_data('mydata.txt', efield, info='some data info')
...:
Let’s have a look at the text file:
In [6]: !cat mydata.txt
# date: Wed Mar 12 21:12:31 2025 UTC
# version: empymod v2.5.1
# shape: (3, 5, 2)
# dtype: complex128
# info: some data info
# data
+1.239370086608510951e-11-1.191328970799973625e-11j, -8.928186543531344388e-12+6.419335909268558001e-12j
+5.944957267770631466e-13-2.180804388965618103e-12j, -4.810025505892779633e-13+1.038226574997207207e-12j
+6.954171128589677381e-15-6.105983115315724197e-13j, -2.628233843164725487e-14+2.012010774729701438e-13j
-3.001360038946117009e-14-2.128410404124548205e-13j, -3.342213699925170680e-15+4.914187333082157154e-14j
-1.955124416867077982e-14-8.213764539169536965e-14j, -2.657687263339889656e-15+1.605587109223421513e-14j
-1.639570785851709849e-12-4.978286614646098338e-13j, +1.056015773047575835e-12+3.097036345180775687e-13j
-6.018411231611660190e-14-1.091050213298094619e-13j, +4.639277127951717104e-14+7.995871597665439839e-14j
-1.838622338871940836e-14-3.668074994231800339e-14j, +9.986505196958873286e-15+1.763515715289324569e-14j
-8.729841441916748600e-15-1.647785331968349433e-14j, +3.385348212800950833e-15+5.875760098846425655e-15j
-4.829615243485968297e-15-8.449921740369727604e-15j, +1.440517267993927590e-15+2.438863993986193854e-15j
+1.509810346095964368e-14-1.472508226606001353e-15j, -1.262905972596958756e-14+1.608389215485403109e-15j
+1.820466577655163814e-15-2.015160158292079569e-16j, -1.174546846363916742e-15+1.319849276639994265e-16j
+5.388674548795952554e-16-5.893720802613286345e-17j, -2.517170092169496687e-16+2.772040814496012621e-17j
+2.272440960367133329e-16-2.473836663750933288e-17j, -8.200105661551344554e-17+8.961791974367268747e-18j
+1.163246545949783599e-16-1.263720799646163221e-17j, -3.404744413098884613e-17+3.707807858162149387e-18j
First is a header with the date, the version of empymod with which it was
generated, and the shape and type of the data. This is followed by a line of
additional information, which you can define by providing a string to the input
parameter info
. The columns are the sources (two in this case), and in the
rows there are first all receivers for the first frequency (or time), then all
receivers for the second frequency (or time), and so on.
The json file is very similar. Here we print just the first twenty lines as an example:
In [7]: !head -n 20 mydata.json
{
"date": "Wed Mar 12 21:12:31 2025 UTC",
"version": "empymod v2.5.1",
"shape": "(3, 5, 2)",
"dtype": "complex128",
"info": "",
"data": [
[
[
[
1.239370086608511e-11,
-8.928186543531344e-12
],
[
5.944957267770631e-13,
-4.81002550589278e-13
],
[
6.954171128589677e-15,
-2.6282338431647255e-14
The main difference, besides the structure, is that the json-format does not
support complex data. It lists therefore first all real parts, and then all
imaginary parts. If you load it with another json reader it will therefore
have the dimension (2, nfreqtime, nrec, nsrc)
, where the 2 stands for real
and imaginary parts. (Only for frequency-domain data of course, not for
time-domain data.)
To load it in Python simply use the corresponding functions:
In [8]: efield_json = empymod.io.load_data('mydata.json')
...: efield_txt = empymod.io.load_data('mydata.txt')
...: # Let's check they are the same as the original.
...: print(f"Json-data: {np.allclose(efield, efield_json, atol=0)}")
...: print(f"Txt-data : {np.allclose(efield, efield_txt, atol=0)}")
...:
Json-data: True
Txt-data : True
Caution#
There is a limitation to the save_input
-functionality: The data must be
three dimensional, (nfreqtime, nrec, nsrc)
. Now, in the above example that
is the case, we have 3 frequencies, 5 receivers, and 2 sources. However, if any
of these three quantities would be 1, empymod would by default squeeze the
dimension. To avoid this, you have to pass the keyword squeeze=False
to the
empymod-routine.
CLI#
The command-line interface is a simple wrapper for the main top-level modelling
routines empymod.model.bipole
, empymod.model.dipole
,
empymod.model.loop
, and empymod.model.analytical
. To call it
you must write a json-file containing all your input parameters as described in
the section I/O. The basic syntax of the CLI is
empymod <routine> <inputfile> <outputfile>
You can find some description as well by running the regular help
empymod --help
As an example, to reproduce the example given above in the I/O-section, run
empymod bipole myrun.json mydata.txt
If you do not specify the output file (the last argument) the result will be printed to the STDOUT.
Warning re runtime#
A warning with regards to runtime: The CLI has an overhead, as it has to load Python and empymod with all its dependencies each time (which is cached if running in Python). Currently, the overhead should be less than 1s, and it will come down further with changes happening in the dependencies. For doing some simple forward modelling that should not be significant. However, it would potentially be a bad idea to use the CLI for a forward modelling kernel in an inversion. The inversion would spend a significant if not most of its time starting Python and importing empymod over and over again.
Consult the following issue if you are interested in the overhead and its status: github.com/emsig/empymod/issues/162.