API Reference: AEM_drift.py¶
Provides Analytic Element Method (AEM) linesink potential computation and drift matrix construction for Universal Kriging with river-influence drift terms.
compute_linesink_potential(x, y, x1, y1, x2, y2, strength=1.0)¶
Source: AEM_drift.py
Computes the real-valued hydraulic potential at evaluation points (x, y) due to a single linesink segment from (x1, y1) to (x2, y2). Based on the analytic element formulation from Strack (1989).
Complex Potential Formula¶
The computation maps the physical segment to a normalised ZZ-space on [-1, 1]:
z = x + i·y # evaluation point (complex)
z1 = x1 + i·y1 # segment start (complex)
z2 = x2 + i·y2 # segment end (complex)
mid = (z1 + z2) / 2 # segment midpoint
half_L = (z2 - z1) / 2 # half-length vector
ZZ = (z - mid) / half_L # normalised coordinate ∈ [-1, 1]
carg = (ZZ+1)·ln(ZZ+1) - (ZZ-1)·ln(ZZ-1) + 2·ln(half_L) - 2
φ = (strength · L / 4π) · Re(carg) # real potential
where L = |z2 - z1| is the segment length.
Singularity Handling¶
The logarithm ln(ZZ ± 1) is singular at the segment endpoints (ZZ = +1 and ZZ = -1). The implementation perturbs ZZ by ±1e-10 when |ZZ ∓ 1| < 1e-10 to avoid NaN or Inf values:
mask = np.abs(ZZ - 1.0) < 1e-10 → ZZ[mask] += 1e-10
mask_neg = np.abs(ZZ + 1.0) < 1e-10 → ZZ[mask_neg] = -1.0 - 1e-10
Zero-Length Guard¶
If L < 1e-6, the function returns np.zeros_like(x) immediately without computation.
Strength Scaling¶
The potential scales linearly with strength: φ(strength=2) = 2 · φ(strength=1) exactly (before singularity perturbation).
Parameters¶
| Parameter | Type | Description |
|---|---|---|
x |
np.ndarray |
1-D array of X coordinates of evaluation points. |
y |
np.ndarray |
1-D array of Y coordinates of evaluation points. |
x1, y1 |
float |
Coordinates of the segment start point. |
x2, y2 |
float |
Coordinates of the segment end point. |
strength |
float |
Linesink strength (hydraulic resistance). Default 1.0. |
Returns¶
| Type | Description |
|---|---|
np.ndarray |
Real-valued potential φ at each evaluation point, shape (N,). |
Coordinate Space¶
Evaluation points and segment endpoints must be in the same coordinate space. When apply_anisotropy=True in compute_linesink_drift_matrix(), both are in model space. When apply_anisotropy=False, both are in raw space.
Reference¶
Strack, O. D. L. (1989). Groundwater Mechanics. Prentice Hall.
compute_linesink_drift_matrix(x_model, y_model, linesinks_gdf, group_col, transform_params, sill, strength_col='lval', rescaling_method='adaptive', apply_anisotropy=True, input_scaling_factors=None)¶
Source: AEM_drift.py
Constructs the AEM drift matrix for a set of evaluation points. Segments are grouped by group_col; each unique group value becomes one drift column (the sum of potentials from all segments in that group). Scaling factors are computed or reused to normalise each column relative to the variogram sill.
Parameters¶
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
x_model |
np.ndarray |
Yes | — | 1-D array of X coordinates of evaluation points. |
y_model |
np.ndarray |
Yes | — | 1-D array of Y coordinates of evaluation points. |
linesinks_gdf |
GeoDataFrame |
Yes | — | Shapefile data with linesink geometries and attributes. |
group_col |
str |
Yes | — | Column name used to group segments into named linesinks (e.g., "NAME"). Each unique value becomes one drift column. |
transform_params |
dict or None |
Yes | — | Transform parameter dict from get_transform_params(). Used to transform segment endpoints to model space when apply_anisotropy=True. Pass None for no transformation. |
sill |
float |
Yes | — | Variogram sill; used as the numerator in adaptive rescaling. |
strength_col |
str |
No | 'lval' |
Column name in linesinks_gdf containing per-segment strength values. |
rescaling_method |
str |
No | 'adaptive' |
'adaptive' or 'fixed'. Controls how scaling factors are computed during training. See Rescaling Methods. |
apply_anisotropy |
bool |
No | True |
If True, segment endpoints are transformed to model space via transform_params. If False, raw coordinates are used for AEM computation. See Coordinate Space Behaviour. |
input_scaling_factors |
dict or None |
No | None |
Pre-computed scaling factors from a prior training call (keyed by group value). When provided, these factors are reused instead of recomputed. Must be passed during prediction. |
Returns¶
| Type | Description |
|---|---|
(np.ndarray, list[str], dict) |
3-tuple: (drift_matrix, term_names, scaling_factors_used). |
| Return Element | Type | Description |
|---|---|---|
drift_matrix |
np.ndarray |
Shape (N_points, N_groups). Each column is the scaled potential for one linesink group. |
term_names |
list[str] |
Ordered list of group identifiers (values of group_col), one per column. |
scaling_factors_used |
dict |
Mapping of group_value → rescaling_factor applied to each column. Must be persisted and passed as input_scaling_factors during prediction. |
Rescaling Methods¶
Each group's raw potential column is multiplied by a scaling factor rescr to normalise it relative to the variogram sill:
| Method | Formula | When Applied |
|---|---|---|
| Adaptive (default) | rescr = sill / max(|φ|) if max(|φ|) > 1e-10, else rescr = 1.0 |
Training phase (no input_scaling_factors provided) |
| Fixed (KT3D-style) | rescr = sill / 0.0001 |
When rescaling_method='fixed' |
| Reuse | rescr = input_scaling_factors[group_id] |
Prediction phase (input_scaling_factors provided) |
Priority order: Reuse > Fixed > Adaptive.
Coordinate Space Behaviour¶
The apply_anisotropy flag controls which coordinate space is used for AEM computation:
apply_anisotropy |
Segment endpoints | Evaluation points | Use case |
|---|---|---|---|
True |
Transformed to model space via transform_params |
Model space (x_model, y_model) |
River geometry participates in anisotropy transformation |
False |
Raw coordinates (no transformation applied) | Raw space (caller must pass raw coords as x_model, y_model) |
KT3D-style: AEM computed in raw space, kriging in model space |
Note: When
apply_anisotropy=False, the caller inpredict_on_grid()automatically switches to passing raw grid coordinates (flat_x,flat_y) instead of model-space coordinates.
Critical Contract: Scaling Factor Persistence¶
Scaling factors computed during training must be reused during prediction. If they are not, the AEM drift columns at prediction points will be scaled differently from those at training points, corrupting the kriging system.
Training phase:
drift_matrix, term_names, scaling_factors = compute_linesink_drift_matrix(
x_train, y_train, linesinks_gdf, group_col,
transform_params, sill,
rescaling_method='adaptive'
# input_scaling_factors=None ← factors are computed here
)
# Persist scaling_factors for prediction
Prediction phase:
drift_pred, _, _ = compute_linesink_drift_matrix(
x_grid, y_grid, linesinks_gdf, group_col,
transform_params, sill,
rescaling_method='adaptive',
input_scaling_factors=scaling_factors # ← reuse training factors
)
In the full pipeline, scaling_factors is passed from training through predict_on_grid() via its scaling_factors parameter.
Segment Iteration¶
For each group, the function iterates over all segments in the group and over all vertex pairs within each segment's geometry:
for each group_id:
total_phi = 0
for each segment in group:
for each consecutive vertex pair (j, j+1):
total_phi += compute_linesink_potential(x_model, y_model, x1, y1, x2, y2, strength)
drift_matrix[:, i] = total_phi * rescr
Example¶
import geopandas as gpd
import numpy as np
from AEM_drift import compute_linesink_drift_matrix
from transform import get_transform_params
linesinks = gpd.read_file("rivers.shp")
x_train = np.array([100.0, 200.0, 300.0])
y_train = np.array([100.0, 150.0, 200.0])
transform_params = get_transform_params(x_train, y_train, angle=30.0, ratio=0.5)
# Training
drift_matrix, term_names, scaling_factors = compute_linesink_drift_matrix(
x_train, y_train,
linesinks, group_col="NAME",
transform_params=transform_params,
sill=1.5,
strength_col="resistance",
rescaling_method="adaptive",
apply_anisotropy=True,
)
# Prediction — reuse scaling_factors
x_grid = np.linspace(50, 350, 100)
y_grid = np.linspace(50, 250, 100)
drift_pred, _, _ = compute_linesink_drift_matrix(
x_grid, y_grid,
linesinks, group_col="NAME",
transform_params=transform_params,
sill=1.5,
strength_col="resistance",
rescaling_method="adaptive",
apply_anisotropy=True,
input_scaling_factors=scaling_factors,
)
Module Exports¶
AEM_drift.py does not define an explicit __all__. The public API consists of:
| Symbol | Description |
|---|---|
compute_linesink_potential() |
Single-segment potential at evaluation points. |
compute_linesink_drift_matrix() |
Full drift matrix for all linesink groups. |
See Also¶
docs/theory/aem-linesink.md— Theory, complex potential derivation, and segment grouping rationale.docs/api/drift.md— Polynomial drift; combined with AEM drift vianp.hstackin the main pipeline.docs/api/kriging.md—predict_on_grid()which orchestrates AEM drift reconstruction at prediction time.docs/api/transform.md—apply_transform()used internally to transform segment endpoints whenapply_anisotropy=True.