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.014366 :: 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.09999999999999999, 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.015726 :: 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: Fri Mar 08 10:47:53 2024 UTC
# version: empymod v2.3.1.dev4+g0adb87d
# shape: (3, 5, 2)
# dtype: complex128
# info: some data info
# data
+1.239370086608510951e-11-1.191328970799973625e-11j, -8.928186543531344388e-12+6.419335909268559617e-12j
+5.944957267770631466e-13-2.180804388965618103e-12j, -4.810025505892780643e-13+1.038226574997207409e-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.657687263339890051e-15+1.605587109223421829e-14j
-1.639570785851709849e-12-4.978286614646098338e-13j, +1.056015773047575835e-12+3.097036345180776192e-13j
-6.018411231611660190e-14-1.091050213298094619e-13j, +4.639277127951717735e-14+7.995871597665441101e-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.440517267993927787e-15+2.438863993986194643e-15j
+1.509810346095964368e-14-1.472508226606001353e-15j, -1.262905972596958914e-14+1.608389215485403306e-15j
+1.820466577655163814e-15-2.015160158292079569e-16j, -1.174546846363916939e-15+1.319849276639994512e-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.404744413098885229e-17+3.707807858162150157e-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": "Fri Mar 08 10:47:53 2024 UTC",
  "version": "empymod v2.3.1.dev4+g0adb87d",
  "shape": "(3, 5, 2)",
  "dtype": "complex128",
  "info": "",
  "data": [
    [
      [
        [
          1.239370086608511e-11,
          -8.928186543531344e-12
        ],
        [
          5.944957267770631e-13,
          -4.810025505892781e-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.