Using the python backend

Here we illustrate some ways of using pymagglobal from within python. For explicit documentation, see here.

Creating a Model instance

The first step to work with pymagglobal models is to make them accessible by instancing the Model class. We therefore import Model and create an instance by passing the name of the model we want to access, in this case GGFMB:

[1]:
from pymagglobal import Model

ggfmb = Model('GGFMB')

We can access some information about the model, for example the maximum degree:

[2]:
print(ggfmb.l_max)
6

We can also use the valid_epoch method to check if an epoch is covered by the model:

[3]:
# pass the epoch as yr.

ggfmb.valid_epoch(-700_000)
[3]:
array(-700000)

valid_epoch returns the input, but throws a warning if the input is not covered by the model:

[4]:
# pass the epoch as yr.

ggfmb.valid_epoch(-2_000)
/opt/conda/envs/deploying/lib/python3.11/site-packages/pymagglobal/core.py:179: OutOfRangeWarning: Epochs [-2000] are outside of the models range [-898050.0, -698050.0]. The result will be extrapolated and not robust.
[4]:
array(-2000)

Using core functions

The most straight-forward way of extracting specific information from a model is to use the core functions of pymagglobal. Check the documentation to see which functions are available.

Let’s assume we want to get a local time series of the inclination at a specific location. We can use the local_curve function for that:

[5]:
import numpy as np

from pymagglobal import local_curve

# Honululu as lat, lon tuple
loc = (21.306944, -157.858333)

# Create an evenly spaced array over the ggfmb interval

times = np.linspace(
    ggfmb.t_min,
    ggfmb.t_max,
    501,
)
d, i, f = local_curve(times, loc, ggfmb)

We can now plot the result. Note that we convert the input to kiloyears before present for better readability:

[6]:
from matplotlib import pyplot as plt

from pymagglobal.utils import yr2lt

fig, ax = plt.subplots(1, 1, figsize=(10, 5))

ax.plot(
    yr2lt(times),
    i,
)
ax.invert_xaxis()
ax.set_xlabel('time [ka BP]')
ax.set_ylabel('inclination [deg.]');
_images/example_1_13_0.png

We might also be interested in a map of the radial component (Z) of the field at the Earth’s surface. We can use the field method to create such a map:

[7]:
from pymagglobal import field
from pymagglobal.utils import get_grid, lt2yr

# Set up a grid at the Earth's surface
# The epoch 780 ka BP is transformed to years using lt2yr
grid = get_grid(1000, t=lt2yr(780))
# for plotting purposes shift the array
grid[1] -= 180
# access is then straight-forward
n, e, z = field(grid, ggfmb)

We can plot the result using cartopy:

[8]:
from cartopy import crs as ccrs

proj = ccrs.Mollweide()

fig, ax = plt.subplots(1, 1, figsize=(10, 5), subplot_kw={'projection': proj})

plt_lat, plt_lon, _ = proj.transform_points(
    ccrs.Geodetic(),
    grid[1],
    90-grid[0],
).T

vmax = np.max(np.abs(z))
ax.tricontourf(
    plt_lat,
    plt_lon,
    z,
    vmax=vmax,
    vmin=-vmax,
    cmap='RdBu',
)
ax.set_global()
ax.coastlines();
/opt/conda/envs/deploying/lib/python3.11/site-packages/cartopy/io/__init__.py:241: DownloadWarning: Downloading: https://naturalearth.s3.amazonaws.com/110m_physical/ne_110m_coastline.zip
_images/example_1_17_1.png

Using model coefficients directly

If we are intersted in something that is not available directly via the core functions, we might be able to use model coefficients directly and calculate the quantity of interst. Here we show how to generate a map of the radial (Z) component of the field at the core-mantle boundary. There are two ways to do this. Either we scale the coefficients and use unscaled basis functions, or we might also have the basis functions include a scaling. To highlight several aspects of pymagglobal we pursue the first approach. Initially, we access the coefficients for the specific epoch at the Earth’s surface:

[9]:
from pymagglobal import coefficients

l, m, coeffs = coefficients(lt2yr(780), ggfmb)

Next we scale the coeffients to the core-mantle boundary (3480 km):

[10]:
from pymagglobal.utils import scaling, REARTH

coeffs_cmb = coeffs * scaling(REARTH, 3480, ggfmb.l_max)

In order to calculate the field components, we evaluate the basis functions at the grid specified before. The basis functions are derivatives of the spherical harmonics. The function dsh_basis takes array of shape (L, 3*N), where L is the number of Gauss coefficients, as third argument and fills it with the appropriate values. We calculate the number of coeffients from the maximum spherical harmonics degree using lmax2N. Note that by default, dsh_basis assumes that the coefficients are given at the Earth’s surface. I.e. if the grid is also at the Earth’s surface, no scaling is applied. If however the radial grid entries grid[2] differ from REARTH, dsh_basis automatically includes a scaling. Check the end of this notebook for the way to handle this scalings explicitly.

[11]:
from pymagglobal.utils import dsh_basis, lmax2N

base = np.empty((lmax2N(ggfmb.l_max), 3*grid.shape[1]))
dsh_basis(ggfmb.l_max, grid, base)
[11]:
array([[-5.60704472e-02,  0.00000000e+00, -1.99685363e+00, ...,
        -5.60704472e-02,  0.00000000e+00,  1.99685363e+00],
       [-9.98426815e-01, -1.22464680e-16,  1.12140894e-01, ...,
         9.98426815e-01,  1.22464680e-16,  1.12140894e-01],
       [-1.83408030e-16,  1.00000000e+00,  2.05999481e-17, ...,
        -6.11360102e-17,  1.00000000e+00, -6.86664937e-18],
       ...,
       [-2.80657566e-19,  1.14811120e-04,  2.20798743e-20, ...,
         6.31319979e-20, -1.14811120e-04,  4.96671655e-21],
       [ 2.23001917e-06,  1.64117338e-21, -1.46107722e-07, ...,
        -2.23001917e-06, -1.64117338e-21, -1.46107722e-07],
       [-2.18616901e-21, -2.23353293e-06,  1.43234721e-22, ...,
         5.46335203e-21, -2.23353293e-06,  3.57951147e-22]])

Calculating the field is then straight forward, we simply multiply the coefficients and the basis:

[12]:
nez_cmb = coeffs_cmb @ base

To access the radial component we reshape the field array:

[13]:
nez_cmb = nez_cmb.reshape(grid.shape[1], 3).T
z_cmb = nez_cmb[2]

We can again plot the results using cartopy:

[14]:
fig, ax = plt.subplots(1, 1, figsize=(10, 5), subplot_kw={'projection': proj})

vmax = np.max(np.abs(z_cmb))
ax.tricontourf(
    plt_lat,
    plt_lon,
    z_cmb,
    vmax=vmax,
    vmin=-vmax,
    cmap='RdBu',
)
ax.set_global()
ax.coastlines();
_images/example_1_29_0.png

In this case we could have also set the radius of the grid to the core-mantle boundary and use the field routine. This implicitly moves the scaling to the basis functions as mentioned above:

[15]:
grid_cmb = np.copy(grid)
grid_cmb[2] = 3480

_, _, z_cmb_2 = field(grid_cmb, ggfmb)

fig, ax = plt.subplots(1, 1, figsize=(10, 5), subplot_kw={'projection': proj})

ax.tricontourf(
    plt_lat,
    plt_lon,
    z_cmb_2,
    vmax=vmax,
    vmin=-vmax,
    cmap='RdBu',
)
ax.set_global()
ax.coastlines();
_images/example_1_31_0.png

The formally correct/explicit way of using dsh_basis like above would be to both move the grid to the core-mantle boundary and use dsh_basis with 3480 km as reference radius like so:

[16]:
base_cmb = np.empty((lmax2N(ggfmb.l_max), 3*grid_cmb.shape[1]))
dsh_basis(ggfmb.l_max, grid_cmb, base_cmb, R=3480)

nez_cmb_3 = coeffs_cmb @ base_cmb
nez_cmb_3 = nez_cmb_3.reshape(grid.shape[1], 3).T
z_cmb_3 = nez_cmb_3[2]
[17]:
fig, ax = plt.subplots(1, 1, figsize=(10, 5), subplot_kw={'projection': proj})

ax.tricontourf(
    plt_lat,
    plt_lon,
    z_cmb_3,
    vmax=vmax,
    vmin=-vmax,
    cmap='RdBu',
)
ax.set_global()
ax.coastlines();
_images/example_1_34_0.png

Due to the implicit scaling applied above, base_cmb and base are actually identical:

[18]:
assert(np.all(base == base_cmb))