AUBELLHOP: computation options

In the introduction, the four computation types were introduced: arrivals, rays, eigenrays, and transmission loss. When once-off simulations are being undertaken, the methods compute_arrivals(), compute_rays(), etc., are convenient wrapper functions. For complex scenarios, the more general compute() method allows multiple tasks, environmentals, and even Bellhop executables to be run with a single command.

1 Working files and cleanup

By default, compute() uses a temporary working directory and deletes all intermediate files after the run. If you want to keep the generated .env and output files, pass debug=True.

If you provide a fname_base, files are written to that base name (for example fname_base="demo" produces demo.env, demo.ray, etc.). In this case:

  • Existing files are not deleted by default.
  • You can explicitly remove old files by passing overwrite=True.
  • Passing debug=True prevents post-run cleanup.

2 Compatibility with Fortran .env files

One feature of the compute() function is to maintain interface compatibility with the original .env file Bellhop interface. Within a legacy .env file, the task to run is hard-coded and therefore

env = bh.Environment.from_file('myenv.env')
print(f"Running task {env['task']}")
results = compute(env)

should return identical results to

> bellhop.exe myenv

The Fortran results file is read into the Python variable results according to which task is specified.

3 Multiple tasks

To return results from multiple types of task, pass the name of each desired task in a list via the task=["<task1>", "<task2>"] option. The possible tasks are shown in Table 1. Note that Bellhop runs separately for each one of these computations — it does not provide a feature to re-use data from prior runs (which would seem like it should).

Table 1: Values allowed for the task= option for bellhop.compute (can be a string or list of strings).
Option task= value Description
"rays" All rays from a source
"eigenrays" All rays between a source and one receiver
"arrivals" All arrival data from the eigenrays
"coherent" Transmission loss from a source to a grid of receivers (coherent superposition)
"incoherent" (incoherent superposition)
"semicoherent" (semicoherent superposition)

When more than one task is provided, the output data is returned as a list of dictionaries:

Code
import aubellhop as bh
import aubellhop.plot as bhp

env = bh.Environment()
output, _ = bh.compute(env,task=["eigenrays","arrivals"])

bhp.plot_rays(output[0]['results'])
bhp.plot_arrivals(output[1]['results'])

Each dictionary has schema:

  • 'name': the name of the environment configuration (i.e., as produced by env['name'])
  • 'task': the name of the task (see Table 1)
  • 'model': model name used (matching bellhop.models())
  • 'results': the data structure produced by Bellhop for this computation

4 Multiple environments

When undertaking sensitivity studies it is very common to run multiple Bellhop simulations with varying environment parameters. The compute() function allows multiple environments to be passsed directly to do this automatically:

Code
import aubellhop as bh
import aubellhop.plot as bhp

env1 = bh.Environment()
env2 = env1.copy()
env2['receiver_depth'] = 2 * env1['receiver_depth']
output, _ = bh.compute(env=[env1, env2],task="arrivals")

bhp.plot_arrivals(output[0]['results'])
bhp.plot_arrivals(output[1]['results'])

Where there are multiple tasks and multiple environments, every combination is calculated; for instance env=[env1, env2] and task=["arrivals","eigenrays"] would result in four outputs. (The order is likely implementation-dependent so shouldn’t be relied upon.)

5 Multiple models

For researchers developing new Bellhop variants (for instance, bellhopcuda), it can be important for testing purposes to be able to run back-to-back simulations with different Bellhop implementations.

This style of execution is harder to demonstrate in a minimal example, but would be constructed with an approach such as:

Code
import aubellhop as bh
import pandas.testing as pdt

if "bellhopcuda" not in bh.Models.supported():
    bh.Models.new(name="bellhopcuda",exe="bellhop.exe") # in reality you would of course have a separate binary

env = bh.Environment()
output, _ = bh.compute(env,task="arrivals",model=["bellhop", "bellhopcuda"])

pdt.assert_frame_equal(output[0]['results'], output[1]['results'])
print("[no output to compare, both sets of results are identical]")
[no output to compare, both sets of results are identical]

Once again, you can define multiple models, environments, and tasks, and bellhop will automatically execute each combination.

6 Indexing multiple outputs

Because the ordering is potentially uncertain, compute also returns a DataFrame of result indices:

Code
import aubellhop as bh
import aubellhop.plot as bhp
env = bh.Environment()
output, ind_df = bh.compute(env,task=["arrivals", "eigenrays"])
print(ind_df)

arr_ind = ind_df[ind_df["task"]=="arrivals"].index[0]
# [0] => first one / only one in this case
arr = output[arr_ind]
bhp.plot_arrivals(arr['results'] )
                     name model       task
i                                         
0  bellhop/python default  None   arrivals
1  bellhop/python default  None  eigenrays

This DataFrame can be queried, filtered, etc., to extract results of interest rather than hard-coding numerical outputs.