Worked soil-profile examples¶
This page collects complete patterns for the stable neptoon.soil_profile
service. The examples progress from a normal local profile reconstruction to
explicit uncertainty analysis and a layered-soil/Windows multiprocessing setup.
Before using them, read Reconstructing a sparse in-situ soil-moisture profile for input assumptions and Understanding soil-profile uncertainty and error estimates for interpretation of the reported error budget.
Example 1: three in-situ depths with weather forcing¶
Assume a pandas DataFrame indexed by timestamps and containing volumetric water content at 5, 20, and 50 cm, together with daily air temperature, relative humidity, and precipitation:
from neptoon.soil_profile import (
ProfileInputColumns,
SparseProfileRequest,
SparseSoilProfileService,
)
request = SparseProfileRequest(
latitude_deg=53.10,
sensor_columns={
5.0: "sm_5cm",
20.0: "sm_20cm",
50.0: "sm_50cm",
},
input_columns=ProfileInputColumns(
temperature_mean="air_temperature_c",
relative_humidity="relative_humidity_percent",
precipitation="precipitation_mm",
),
texture_class="sandy_loam",
bulk_density_g_cm3=1.42,
measurement_std_by_depth={
5.0: 0.012,
20.0: 0.015,
50.0: 0.020,
},
)
result = SparseSoilProfileService().reconstruct(data, request)
profile = result.soil_moisture
uncertainty = result.uncertainty
lower_90, upper_90 = result.get_interval(0.90)
diagnostics = result.diagnostics
The input soil-moisture columns must contain volumetric fractions such as 0.24,
not percentages such as 24. Sensor depths are in centimetres. The three
measurement standard deviations in this example deliberately increase with depth
to represent different calibration/representativeness quality; use values based
on the actual sensors and installation.
A compact inspection of the result can preserve the observational-support class:
long_profile = result.to_long_frame().merge(
result.layer_metadata[["layer", "centre_cm", "support"]],
on="layer",
how="left",
)
print(long_profile.head())
For an operational run, also inspect at least the water-balance, innovation, and sensor-gap diagnostics described in the uncertainty guide.
Example 2: time-varying measurement error and uncertainty decomposition¶
Some sensor pipelines provide a daily quality-derived uncertainty estimate. Map those columns separately from the soil-moisture observations:
from neptoon.soil_profile import (
ProfileInputColumns,
SparseProfileRequest,
SparseSoilProfileService,
)
request = SparseProfileRequest(
latitude_deg=50.82,
sensor_columns={
5.0: "theta_5",
15.0: "theta_15",
30.0: "theta_30",
},
measurement_std_columns={
5.0: "theta_5_std",
15.0: "theta_15_std",
30.0: "theta_30_std",
},
input_columns=ProfileInputColumns(
temperature_mean="temperature_c",
temperature_min="temperature_min_c",
temperature_max="temperature_max_c",
relative_humidity="rh_percent",
precipitation="rain_mm",
vegetation_cover="vegetation_fraction",
),
texture_class="loam",
texture_class_probabilities={
"sandy_loam": 0.20,
"loam": 0.60,
"silt_loam": 0.20,
},
bulk_density_g_cm3=1.38,
model_overrides={
"decompose_uncertainty": True,
"ensemble_size": 64,
"hydraulic_parameter_groups": 8,
"quantify_model_choice_uncertainty": True,
},
)
result = SparseSoilProfileService().reconstruct(data, request)
Daily measurement-error values take precedence over a constant depth-specific or global fallback. Non-finite or non-positive daily values fall back to the configured constant. Positive daily values are bounded internally to the supported 0.005-0.12 m³/m³ range.
The individual uncertainty products can then be summarized without confusing standard deviations and variances:
import pandas as pd
budget = pd.DataFrame(
{
"total": result.uncertainty.mean(axis=1),
"statistical": result.uncertainty_statistical.mean(axis=1),
"hydraulic": result.uncertainty_hydraulic_parameters.mean(axis=1),
"structure": result.uncertainty_model_structure.mean(axis=1),
"model_choice": result.uncertainty_model_choice.mean(axis=1),
}
)
print(budget.tail())
quantify_model_choice_uncertainty=True runs a complete alternative transport
reconstruction and therefore approximately doubles runtime. In this mode,
uncertainty_model_structure already includes the model-choice term in
quadrature for backward-compatible result semantics; model_choice above is a
separate attribution diagnostic and must not be added to structure again when
reconstructing the total.
For production uncertainty validation, remove a complete probe depth from
sensor_columns for multi-week blocks and score the reconstructed value against
that withheld series. Randomly hiding isolated points is not a substitute because
it does not test event memory, missing-sensor uncertainty growth, or smoothing.
Example 3: layered hydraulic priors and Windows-safe process parallelism¶
A site with an independently known restrictive subsoil can supply a separate hydraulic prior for that depth interval. Process parallelism is opt-in and must follow Python's guarded-entry-point convention on Windows.
from neptoon.soil_profile import (
HydraulicProfilePrior,
ProfileInputColumns,
SparseProfileRequest,
SparseSoilProfileService,
)
def reconstruct_site(data):
request = SparseProfileRequest(
latitude_deg=53.10,
sensor_columns={
5.0: "sm_5cm",
20.0: "sm_20cm",
60.0: "sm_60cm",
},
input_columns=ProfileInputColumns(
temperature_mean="temperature_c",
relative_humidity="rh_percent",
precipitation="precipitation_mm",
),
texture_class="sandy_loam",
bulk_density_g_cm3=1.40,
hydraulic_profile_priors=(
HydraulicProfilePrior(
top_depth_cm=35.0,
bottom_depth_cm=90.0,
texture_class="clay_loam",
lower_interface_conductivity_multiplier=0.10,
name="restrictive subsoil",
provenance="independent soil survey",
),
),
model_overrides={
"ensemble_parallel_backend": "process",
"ensemble_max_workers": 4,
"ensemble_process_start_method": "spawn",
},
)
return SparseSoilProfileService().reconstruct(data, request)
if __name__ == "__main__":
result = reconstruct_site(data)
print(result.layer_metadata[
[
"layer",
"support",
"hydraulic_horizon_name",
"hydraulic_horizon_provenance",
]
])
The process workers perform deterministic member forecasts only. Random forcing, parameter-group setup, process noise, analysis, diagnostics, and result ordering remain controlled by the parent process so the configured random seed has the same scientific meaning across worker counts. The process backend should be benchmarked on the target machine; small ensembles can be faster in serial mode.
The horizon in this example is an externally supplied prior. The model does
not infer a clay layer merely because the deep sensor is wet, and the provenance
field is retained in layer_metadata so downstream validation can distinguish
measured prior information from assimilated moisture observations.
Example 4: standalone YAML and exported uncertainty tables¶
For command-independent profile reconstruction, use the dedicated compact YAML format. It is intentionally separate from Neptoon's main CRNS sensor/process configuration schemas.
input:
csv: sparse_profile_input.csv
timestamp_column: timestamp
timezone: UTC
site:
latitude_deg: 53.10
texture_class: sandy_loam
bulk_density_g_cm3: 1.42
sensors:
- depth_cm: 5
column: sm_5cm
measurement_std: 0.012
- depth_cm: 20
column: sm_20cm
measurement_std: 0.015
- depth_cm: 50
column: sm_50cm
measurement_std: 0.020
weather:
temperature_mean: air_temperature_c
relative_humidity: relative_humidity_percent
precipitation: precipitation_mm
model:
maximum_depth_cm: 80
smoother_lag_days: 30
overrides:
decompose_uncertainty: true
hydraulic_parameter_groups: 8
output:
directory: profile_output
Run it from Python:
from neptoon.soil_profile import SparseSoilProfileService
result = SparseSoilProfileService().reconstruct_from_yaml("profile.yaml")
The output directory contains the profile, total uncertainty, separated
statistical/hydraulic/structural uncertainty tables, diagnostics, and layer
metadata. The compact YAML interface currently accepts a constant
measurement_std per sensor; use the Python SparseProfileRequest API when
daily measurement_std_columns are required. The repository also contains the
copyable examples/configuration_recipes/sparse_soil_profile.yaml,
sparse_soil_profile_uncertainty.yaml, and
sparse_soil_profile_layered_windows.yaml recipes.
Choosing between the examples¶
| Situation | Start with |
|---|---|
| Normal pandas workflow with known probe depths | Example 1 |
| Quality-dependent observation errors or uncertainty research | Example 2 |
| Independently known hydraulic horizons or Windows process workers | Example 3 |
| File-based reproducible batch setup | Example 4 |
For every case, keep the scientific validation question separate from whether the code executed successfully. A successful reconstruction means the numerical and configuration contracts were satisfied; it does not establish that deep extrapolation or the nominal uncertainty interval is calibrated for the site.