The subsurface

The subsurface of ARTS is always fully 3D with coordinates of altitude, geodetic latitude, and longitude.

The subsurface in ARTS is represented by three key components: the subsurface field, the subsurface field data, and the subsurface point.

The subsurface field contains all physical data of the entire subsurface. Each property of the subsurface field (temperature field, density field, etc.) is stored as a subsurface field data object, representing the full 3D field of that property. The local state of the subsurface field is represented by a subsurface point. A subsurface point effectively holds the local state of the subsurface at a specific location.

Key notations:

  • SubsurfaceField: The subsurface field. An instance is in this text named: subsurf_field. An example from the workspace is subsurface_field.

  • SubsurfaceData: The subsurface field data. An instance is in this text named: subsurf_data.

  • SubsurfacePoint: The local subsurface state. An instance is in this text named: subsurf_point.

Note

This text will also only deal with the core functionality of each of these three classes. For more advanced and supplementary functionality, please see the API documentation of the classes themselves. We will explicitly not cover file handling, data conversion, or other such topics in this text.

Warning

As the name of our model suggests, ARTS is primarily an atmospheric radiative transfer simulator. The subsurface functionality is relatively new and still very experimental. Use at own risk.

The full subsurface field

A subsurface field is represented by the class SubsurfaceField.

The subsurface field holds a collection of subsurface field data, SubsurfaceData, that can be accessed and modified through a dict-like interface. A subsurface field has a minimum altitude, bottom_depth, below which it is considered undefined. It has no maximum altitude, instead relying on an external ellipsoid and elevation field to define the surface of the planet above it. The ellipsoid and the elevation field are (often) part of the SurfaceField class and are described in The surface and planet. The subsurface field can be called as if it were a function taking altitude, geodetic latitude, and longitude coordinate arguments to compute or extract the local subsurface state, SubsurfacePoint.

The core operations on subsurf_field are:

  • subsurf_field[key]: Accessing relevant subsurface field data: SubsurfaceData. See Subsurface field/point data access for more information on what constitutes a valid key

  • subsurf_field.bottom_depth: The minimum altitude of the subsurface. This is the altitude below which the subsurface field is undefined.

  • subsurf_field(alt, lat, lon): Compute the local subsurface state (SubsurfacePoint) at the given coordinate. It is an error to call this with alt < subsurf_field.bottom_depth.

Shorthand graph for subsurf_field:

digraph g { bgcolor="#00000000"; rankdir = "TD"; ratio = auto; node [ color = "#0271BB" fontcolor = "white" style = "filled,rounded" shape = "rectangle" ]; "Named data" [ label = "subsurf_field" style = "filled" ]; "Access Operator" [ label = "subsurf_field[key]" shape = "ellipse" ]; "Data attribute" [ label = "subsurf_field.bottom_depth" shape = "ellipse" ]; "Call operator" [ label = "subsurf_field(alt, lat, lon)" shape = "ellipse" ]; "Single type of data" [ label = "SubsurfaceData" ]; "The altitude that defines the bottom of the subsurface"; "Point-wise state of the subsurface" [ label = "SubsurfacePoint" ]; "Named data" -> "Access Operator" [ arrowhead = "none" ]; "Named data" -> "Data attribute" [ arrowhead = "none" ]; "Named data" -> "Call operator" [ arrowhead = "none" ]; "Access Operator" -> "Single type of data"; "Call operator" -> "Point-wise state of the subsurface"; "Data attribute" -> "The altitude that defines the bottom of the subsurface"; }

A single subsurface point

A subsurface point holds the local state of the subsurface. This is required for local calculations of radiative transfer properties, such as absorption, scattering, emission, etc. A subsurface point is represented by an instance of SubsurfacePoint.

The main use on a subsurface point is to access the local, numerical state of the subsurface.

The core operations on subsurf_point are:

Shorthand graph for subsurf_point:

digraph g { bgcolor="#00000000"; rankdir = "TD"; ratio = auto; node [ color = "#0271BB" fontcolor = "white" style = "filled,rounded" shape = "rectangle" ]; "Named data" [ label = "subsurf_point" style = "filled" ]; "Access Operator" [ label = "subsurf_point[key]" shape = "ellipse" ]; "temperature" [ label = "subsurf_point.temperature" shape = "ellipse" ]; "density" [ label = "subsurf_point.density" shape = "ellipse" ]; "float" [ label = "float" ]; "Named data" -> "Access Operator" [ arrowhead = "none" ]; "Named data" -> "temperature" [ arrowhead = "none" ]; "Named data" -> "density" [ arrowhead = "none" ]; "Access Operator" -> "float"; "temperature" -> "float"; "density" -> "float"; }

Note

The subsurface point does not know where it is in the subsurface. This information is only available in the subsurface field. Positional data must be retained by the user if it is needed for calculations.

Subsurface field/point data access

The access operator subsurf_field[key] is used to get and set subsurface field data (SubsurfaceData) in the subsurface field through the use of types of keys. Likewise, the access operator subsurf_point[key] is used to get and set data in the subsurface point, though it deals with pure floating point data. Each type of key is meant to represent a different type of subsurface data. The following types of keys are available:

  • SubsurfaceKey: Basic subsurface data. Defines temperature [K] and density [kg/m³].

  • SubsurfacePropertyTag: Custom data that belongs to specific models of the subsurface.

Shorthand graph for key of different types:

digraph g { bgcolor="#00000000"; rankdir = "TD"; ratio = auto; node [ color = "#0271BB" fontcolor = "white" style = "filled,rounded" shape = "rectangle" ]; "a0" [ label = "key type" style = "filled" ]; "b0" [ label = "SubsurfaceKey" shape = "ellipse" ]; "b1" [ label = "SubsurfacePropertyTag" shape = "ellipse" ]; "c0" [ label = "T, rho" ]; "c1" [ label = "Custom data" ]; a0 -> b0 [ arrowhead = "none" ]; a0 -> b1 [ arrowhead = "none" ]; b0 -> c0; b1 -> c1; }

Tip

Both subsurf_field["temperature"] and subsurf_field[pyarts3.arts.SubsurfaceKey.temperature] will give the same SubsurfaceData back in python. This is because pyarts3.arts.SubsurfaceKey("temperature") == pyarts3.arts.SubsurfaceKey.temperature. The same is also true when accessing subsurf_point, though it gives floating point values.

Note

Using python str instead of the correct type may in very rare circumstances cause name-collisions. Such name-collisions cannot be checked for. If it happens to you, please use the appropriate key type manually to correct the problem.

Subsurface field data

The subsurface field data is a core component of the subsurface field. It is stored in an instance of SubsurfaceData. This type holds the entire subsurface data for a single subsurface property, such as the full 3D temperature field, the full 3D pressure field, etc. It also holds the logic for how to interpolate and extrapolate this data to any altitude, geodetic latitude, and longitude point. As such, subsurface field data can also be called as if it were a function taking altitude, geodetic latitude, and longitude to return the local floating point state of the subsurface property it holds.

These are the core operations on subsurf_data:

  • subsurf_data.data: The core data in variant form. See Data types for what it represents.

  • subsurf_data.alt_upp: The settings for how to extrapolate above the allowed altitude. What is “allowed” is defined by the data type.

  • subsurf_data.alt_low: The settings for how to extrapolate below the allowed altitude. What is “allowed” is defined by the data type.

  • subsurf_data.lat_upp: The settings for how to extrapolate above the allowed geodetic latitude. What is “allowed” is defined by the data type.

  • subsurf_data.lat_low: The settings for how to extrapolate below the allowed geodetic latitude. What is “allowed” is defined by the data type.

  • subsurf_data.lon_upp: The settings for how to extrapolate above the allowed longitude. What is “allowed” is defined by the data type.

  • subsurf_data.lon_low: The settings for how to extrapolate below the allowed longitude. What is “allowed” is defined by the data type.

  • subsurf_data(alt, lat, lon): Extract the floating point value of the data at one specific altitude, geodetic latitude, and longitude. Returns a single float. Cannot respect the bottom of the subsurface because it is not available to the data. Instead, will strictly respect the extrapolation settings.

Shorthand graph:

digraph g { bgcolor="#00000000"; rankdir = "TD"; ratio = auto; node [ color = "#0271BB" fontcolor = "white" style = "filled,rounded" shape = "rectangle" ]; "Named data" [ label = "subsurf_data" style = "filled" ]; "Data variant" [ label = "subsurf_data.data" shape = "ellipse" ]; "Extrapolation settings" [ label = <subsurf_data.alt_upp<BR/>subsurf_data.alt_low<BR/>subsurf_data.lat_upp<BR/>subsurf_data.lat_low<BR/>subsurf_data.lon_upp<BR/>subsurf_data.lon_low> shape = "ellipse" ]; "Call operator -> float" [ label = "subsurf_data(alt, lat, lon)" shape = "ellipse" ]; "The variant data" [ label = "The data type" ]; "Type of extrapolation" [ label = "Extrapolation settings" ]; "float" [ label = "Point-wise data; a float" ]; "Named data" -> "Data variant" [ arrowhead = "none" ]; "Named data" -> "Extrapolation settings" [ arrowhead = "none" ]; "Named data" -> "Call operator -> float" [ arrowhead = "none" ]; "Data variant" -> "The variant data"; "Extrapolation settings" -> "Type of extrapolation"; "Call operator -> float" -> "float"; }

Tip

An SubsurfaceData is implicitly constructible from each of the Data types described below. The extrapolation settings will be set to appropriate defaults when an implicit construction takes place. These default settings depend on the type and even available data.

Note

If the extrapolation settings or the data itself cannot be used to extract a value at a point using the call-operator, the SubsurfaceData will raise an exception. This is to ensure that the user is aware of the problem. Changing the extrapolation settings will likely fix the immediate problem, but be aware that the consequences of doing so might yield numerical differences from what was originally expected.

Extrapolation rules

The rules for extrapolation is governed by InterpolationExtrapolation. Please see its documentation for more information. Extrapolation happens only outside the grids of the data. Interpreting the data inside a grid is done on a type-by-type basis.

Data types

Below are the types of data that can be stored in the subsurface data. Each data type has its own rules for how to interpret, interpolate, and extrapolate the data.

Tip

Different subsurface field data types can be mixed in the same subsurface field. There are no restrictions on how many types can be used in the same subsurface field.

Numeric

Numeric data simply means that the subsurface contains constant data. Extrapolation rules are not relevant for this data type as it is constant everywhere. An example of using Numeric as subsurface field data is given in the following code block.

import matplotlib.pyplot as plt
import numpy as np
import pyarts3 as pyarts

subsurf_field = pyarts.arts.SubsurfaceField(bottom_depth=-1)
subsurf_field["t"] = 295
subsurf_field["rho"] = 0.977

fig = plt.figure(figsize=(14, 8))
fig, subs = pyarts.plots.SubsurfaceField.plot(subsurf_field, alts=np.linspace(-1, 0), fig=fig, keys=["t", "rho"])
subs[0].set_title("Temperature profile")
subs[1].set_title("Density profile")
subs[0].set_ylabel("Depth [m]")
subs[0].set_xlabel("Temperature [K]")
subs[1].set_xlabel("Density [kg/m$^3$]")
plt.show()

(Source code, svg, pdf)

_images/user-subsurface_field-1.svg

GeodeticField3

If the subsurface data is of the type GeodeticField3, the data is defined on a grid of altitude, geodetic latitude, and longitude. It interpolates linearly between the grid points when extracting point-wise data. For sake of this linear interpolation, longitude is treated as a cyclic coordinate between [-180, 180) - please ensure your grid is defined accordingly. This data type fully respects the rules of extrapolation outside its grid. An example of using GeodeticField3 as subsurface field data is given in the following code block.

Tip

It is possible to use any number of 1-long grids in a GeodeticField3 meant for use as a SubsurfaceData. The 1-long grids will by default apply the “nearest” interpolation rule for those grids, potentially reducing the subsurface data to a 1D profile if only the altitude is given, or even a constant if all three grids are 1-long.

Note

If the GeodeticField3 does not cover the full range of the subsurface, the extrapolation rules will be used to extrapolate it. By default, these rules are set to not allow any extrapolation. This can be changed by setting the extrapolation settings as needed. See headers Extrapolation rules and Subsurface field data for more information.

Warning

Even though the longitude grid is cyclic, only longitude values [-540, 540) are allowed when interpolating the field. This is because we need the interpolation to be very fast and this is only possible for single cycles of the longitude. Most algorithm will produce values [-360, 360] for the longitude, so this should in practice not be a problem for normal use-cases. Please still ensure that the grid is defined properly or the interpolation routines will fail.

NumericTernaryOperator

This operator (NumericTernaryOperator) represents that the subsurface property is purely a function of altitude, geodetic latitude, and longitude. The operator takes three arguments and returns a float. Extrapolation rules are not relevant for this data type as it is a function. An example of using NumericTernaryOperator as subsurface field data is given in the following code block.

import matplotlib.pyplot as plt
import numpy as np
import pyarts3 as pyarts

subsurf_field = pyarts.arts.SubsurfaceField(bottom_depth=-1)
subsurf_field["t"] = lambda alt, lat, lon: 295 + 5 * alt * 10
subsurf_field["rho"] = lambda alt, lat, lon: 0.977 - 0.001 * alt

fig = plt.figure(figsize=(14, 8))
fig, subs = pyarts.plots.SubsurfaceField.plot(subsurf_field, alts=np.linspace(-1, 0), fig=fig, keys=["t", "rho"])
subs[0].set_title("Temperature profile")
subs[1].set_title("Density profile")
subs[0].set_ylabel("Depth [m]")
subs[0].set_xlabel("Temperature [K]")
subs[1].set_xlabel("Density [kg/m$^3$]")
plt.show()

(Source code, svg, pdf)

_images/user-subsurface_field-2.svg

Tip

Any kind of python function-like object can be used as a NumericTernaryOperator. It must simply take three floats and return another float. If you want to pass in a custom class all you need is to define __call__(self, alt, lat, lon) for it.