Skip to content

Quickstart

import groundfield as gf

# 1) Soil model: a representative two-layer soil
soil = gf.TwoLayerSoil(rho_1=100.0, rho_2=500.0, h_1=2.0)

# 2) Build a world and add a ring electrode around a substation
world = gf.create_world(soil=soil)
gf.create_electrode(
    world,
    "ring",
    name="g1",
    center=(0.0, 0.0, 0.8),
    radius=5.0,
    wire_radius=0.005,
)
gf.create_source(world, attached_to="g1", magnitude=1.0)

# 3) Configure the engine
engine = gf.create_engine(
    backend="image",                # auto-routes to image_2layer
    # 0.25 m gives ~126 segments on this 5 m ring; the cluster
    # impedance is converged to 5 digits (12.136 Ohm at 50 Hz, same
    # as at 0.05 m) while the contour plot below stays fast. Refine
    # only where the field gradient, not the impedance, matters.
    segment_length=0.25,
    frequencies=[50.0, 150.0, 250.0, 350.0],
)

# 3a) Sweeps that intentionally iterate non-monotonically must use
# the explicit opt-in to silence the EngineFrequencyOrderWarning:
#     engine_sweep = engine.with_frequencies(5000.0, 50.0,
#                                            preserve_order=True)
# The resulting Engine carries the list verbatim — the solver never
# sorts behind your back.

# 4) Run the simulation
result = world.solve(engine)

# 5) Evaluate
Z = result.cluster_impedance("g1")             # input impedance per frequency
import numpy as np
xs = np.linspace(0.5, 30.0, 120)
phi = result.potential(np.column_stack([xs, np.zeros_like(xs), np.zeros_like(xs)]))

# 6) Plots
gf.plot_potential_radial(result, around="g1", world=world,
                         depths=[0.0, 0.5, 1.0])
gf.plot_potential_contour(result, world=world, plane="xy", z=0.0)

Backends

Backend Soil model Method Status
image HomogeneousSoil image-charge sum, closed form implemented
image_2layer TwoLayerSoil (auto-dispatched from "image") Tagg/Sunde geometric image-charge series implemented
image_nlayer HomogeneousSoil, TwoLayerSoil, MultiLayerSoil dispatcher (delegates to image for \(n=1\), image_2layer for \(n=2\)) implemented
cim HomogeneousSoil or TwoLayerSoil (\(n \ge 3\) raises) closed-form self-kernel; reduces to image / image_2layer implemented
mom HomogeneousSoil or TwoLayerSoil Galerkin Method-of-Moments on segment level implemented
mom_sommerfeld any layered Galerkin MoM with direct Sommerfeld quadrature (reference engine) implemented
bem HomogeneousSoil or TwoLayerSoil (\(n \ge 3\) raises) collocation on the closed-form kernel; reduces to mom implemented
fem any layered Axisymmetric volume FEM with equivalent-hemisphere reduction implemented

Eight backends, six distinct computations

For \(n \le 2\) layers, cim returns values bit-identical to image_2layer and bem bit-identical to mom — both use the same closed-form self-kernels, and the complex-image fit that once distinguished cim no longer runs (see ADR-0002 and the cim / bem pages). Treat them as aliases, not as independent cross-checks. Both raise NotImplementedError for \(n \ge 3\); mom_sommerfeld is currently the only integral-equation path for three or more layers, with fem as the methodologically independent cross-check.

Engine.solve automatically forwards backend="image" to image_2layer for a TwoLayerSoil and to image_nlayer for a MultiLayerSoil, so notebooks written for the homogeneous case keep working when only the soil model is swapped. The full engine theory lives under Engine theory; the selection heuristic is recorded in ADR-0002.

Cross-engine validation

from groundfield import compare_engines

report = compare_engines(
    world,
    engines={
        "fine":   gf.create_engine(backend="image", segment_length=0.025),
        "coarse": gf.create_engine(backend="image", segment_length=0.10),
    },
    rel_tolerance=0.05,
)
print(report.summary())
assert report.is_consistent

The same pattern is used by tests/test_cross_engines_extended.py to enforce the methodological cross-checks between the closed-form image engines, the integral-equation engines (mom, mom_sommerfeld) and the volume-PDE engine (fem). bem and cim are deliberately absent from that list: for \(n \le 2\) they reduce to mom and image_2layer respectively, so pairing them adds no independent information.

Parameter sweeps

  • Vary soil.rho_1, soil.rho_2, soil.h_1 and the auxiliary electrode position to span the parameter space of interest.
  • The resulting rho-f curves feed the model reduction step in groundinsight.