Skip to content

Geometry

The geometry subpackage describes the physical shape of every buried metallic object — rods, rings, strips, polylines, stars, mesh and grid-mesh electrodes — together with the wire-segment representation that the field solver consumes.

Physical background

A grounding electrode is a finite metal body buried in conducting soil. For frequencies below 1 kHz it can be treated as a thin-wire boundary along which the potential is enforced and the current distribution is unknown (or, in the cheaper image backends, prescribed as uniform). All standard geometries are reduced to a collection of straight cylindrical segments

\[ \Gamma_i = \{ \mathbf{x}(\xi) = \mathbf{x}_i^{(0)} + \xi\,(\mathbf{x}_i^{(1)} - \mathbf{x}_i^{(0)}) : \xi \in [0, 1] \},\qquad i = 1\ldots N, \]

each carrying a wire radius \(a_i\) and a material tag. The discretisation length \(\Delta s\) is set by :attr:groundfield.solver.engine.Engine.segment_length. The geometry layer is intentionally decoupled from the solver, so the same electrodes can be fed into the closed-form image backend, MoM, or FEM without modification.

For mesh and grid electrodes the segment count per wire is aligned to a whole multiple of the mesh-span count, so the grid crossings fall on segment endpoints rather than on the midpoint point sources. This avoids a discretisation trap in which a longitudinal and a transverse segment midpoint would coincide on a crossing — collapsing to the singularity clamp and inflating the grid resistance by ~2× for the uniform-current image backend. The effective \(\Delta s\) is therefore never coarser than requested and the grid resistance is stable for any segment_length (see notebooks/44_grid_discretization_robustness.ipynb).

Example

import groundfield as gf

world = gf.create_world(soil=gf.HomogeneousSoil(resistivity=100.0))

# Vertical driven rod
rod = gf.create_electrode(
    world, "rod", name="g1",
    position=(0.0, 0.0, 0.0), length=1.5, wire_radius=0.005,
)

# Buried ring earth at house-foundation depth
ring = gf.create_electrode(
    world, "ring", name="g2",
    center=(10.0, 0.0, 0.8), radius=2.0, wire_radius=0.005,
)

# Rectangular mesh (e.g. substation grid)
mesh = gf.create_electrode(
    world, "grid_mesh", name="g3",
    corner=(-10.0, -10.0, 0.5), size=(20.0, 20.0),
    n_x=11, n_y=11, wire_radius=0.005,
)

# Pre-solve resolution / segment-budget check
for warning in gf.check_segment_resolution(
    world, gf.create_engine(backend="image", segment_length=0.1)
):
    print("WARN:", warning)

For programmatic generation of large typical worlds (5 – 200 single-family houses, stochastic electrode mixes) see Generators.

Strip electrode concrete shell (new in 0.6.0)

:class:StripElectrode carries an optional concrete_shell_coefficient_ohm_m field (default 0.0 — historic behaviour). When set, the per-meter Sunde coefficient \(C = \rho_c/(2\pi)\,\ln(r_b/r_a)\) in Ω·m enters the image / image_2layer self-action: every segment of length \(\Delta s\) in this strip has its MoM diagonal augmented by \(C/\Delta s\), the radial voltage drop through the concrete shell. This is the V2 distributed path of ADR-0012. The field is populated automatically by :meth:~groundfield.generators.grounding.GroundingSystemSpec.build_at when a :class:~groundfield.generators.electrode_specs.FoundationElectrodeSpec with concrete_model="distributed" is materialised; it is not intended to be set by hand on isolated strip electrodes outside the generator pipeline (but does work there, see the closed-form verification in tests/test_concrete_encasement.py).

Star electrode (Sternerder)

:class:StarElectrode is the classic \(n\)-point star: n_arms equal horizontal wires of length \(L\) radiate from a common centre node at one depth, uniformly spaced by \(360^\circ / n\). All arms share the centre, so the star is a single galvanic cluster. Its closed-form grounding resistance follows Dwight (1936, Eqs. 23–26),

\[ R = \frac{\rho}{2\,n\,\pi\,L}\, \Bigl(\ln\tfrac{2L}{a} + \ln\tfrac{2L}{s} + c_0 + c_1\tfrac{s}{L} + c_2\tfrac{s^2}{L^2} + c_3\tfrac{s^4}{L^4}\Bigr), \qquad s = 2t, \]

with per-\(n\) coefficients \(c_k\) tabulated for \(n \in \{3, 4, 6, 8\}\) in :func:groundfield.references.dwight1936.n_point_star (\(a\) = wire radius, \(t\) = burial depth, \(s = 2t\) the image distance). The image backend reproduces this to ~1 % (see tests/test_dwight_references.py).

star = gf.create_electrode(
    world, "star", name="g4",
    center=(0.0, 0.0, 0.5), n_arms=4, arm_length=5.0,
    orientation_deg=0.0, wire_radius=0.005,
)

API reference

geometry

Geometries of electrodes and grounding systems.

This subpackage provides the 3-D geometry of the grounding system. An electrode is built from a set of wire segments (:class:Segment) with start and end points, wire radius, material, and a mapping to one or more electric nodes. The subpackage builds a mesh for the field solver and exposes the standard parametric geometries.

Contents

RodElectrode, RingElectrode, StripElectrode, MeshElectrode, GridMeshElectrode Parametric standard electrode geometries. Electrode Discriminated union over all electrode types.

Notes

The geometry layer is deliberately decoupled from the solver, so the same geometry can be fed into the image-charge backend, MoM, or FEM.

GridMeshElectrode

Bases: _ElectrodeBase

Rectangular meshed earth electrode with explicit n × m divisions.

Defines an axis-aligned rectangle of size (dx, dy) at depth corner[2], divided into n_x × n_y internal meshes. The geometry consists of n_x + 1 longitudinal wires (running along the local \(x\) axis from corner to corner + (dx, 0)) and n_y + 1 transverse wires (running along the local \(y\) axis). All wires share the same depth.

Attributes:

Name Type Description
corner Point3D

Corner point (x, y, z) in metres (min-x, min-y, depth).

size tuple[float, float]

Extent (dx, dy) of the rectangle in metres.

n_x int

Number of meshes along \(x\) (must be ≥ 1). The number of longitudinal wires is n_y + 1.

n_y int

Number of meshes along \(y\) (must be ≥ 1). The number of transverse wires is n_x + 1.

Notes

Naming convention: n_x × n_y is the count of meshes (cells), not of wires. A grid with n_x = n_y = 1 is therefore a single closed rectangle (the perimeter wires only).

MeshElectrode

Bases: _ElectrodeBase

Rectangular mesh earth electrode (uniform spacing).

Backwards-compatible primitive that sets the inner mesh density via a single spacing parameter. For finer control over the column / row count, prefer :class:GridMeshElectrode.

Attributes:

Name Type Description
corner Point3D

Corner point (x, y, z) in metres (min-x, min-y, depth).

size tuple[float, float]

Extent (dx, dy) of the mesh in metres.

spacing float

Spacing between adjacent longitudinal / transverse wires in m.

PolylineElectrode

Bases: _ElectrodeBase

Buried wire following an arbitrary horizontal polyline.

Generalises :class:StripElectrode from a single straight wire to a chain of straight wires through vertices. With closed = True the last vertex is wired back to the first, which makes this the Ringerder along an arbitrary building outline — the DIN-18014 Streifenfundament that follows the real building perimeter instead of its bounding rectangle.

All vertices must lie at the same depth (the wire is horizontal), consistent with :class:StripElectrode.

Attributes:

Name Type Description
vertices list[Point3D]

Ordered polyline vertices (x, y, z) in metres, at least two. For closed = True do not repeat the first vertex at the end — the closing edge is implicit.

closed bool

Whether the polyline is closed into a ring (default True).

Notes

Motivation (AP1): approximating a building footprint by its oriented bounding rectangle (OMBR) overestimates the electrode perimeter for L- and U-shaped buildings and — worse — lets the rectangles of neighbouring buildings coincide or overlap, which drives distinct segments into the solver's 1 mm singularity clamp. Following the real polygon removes both artefacts by construction.

edges property

edges: list[tuple[Point3D, Point3D]]

Straight wires making up the polyline (closing edge included).

length property

length: float

Total wire length in metres (sum over all edges).

RingElectrode

Bases: _ElectrodeBase

Horizontal ring electrode (foundation / ring earth electrode).

Attributes:

Name Type Description
center Point3D

Ring centre (x, y, z) in metres. Typically buried 0.5 to 1.0 m below the surface.

radius float

Ring radius in metres.

RodElectrode

Bases: _ElectrodeBase

Vertical driven rod (Tiefenerder).

Attributes:

Name Type Description
position Point3D

Position of the head (top end) as (x, y, z) in metres. Typically the head sits just below the soil surface, e.g. z = 0.5.

length float

Rod length in metres (downwards along \(+z\)).

StarElectrode

Bases: _ElectrodeBase

Buried horizontal star electrode (Sternerder).

n_arms equal-length wires radiate from a common centre node at a single depth, spaced uniformly by \(360^\circ / n\). This is the classic \(n\)-point star of Dwight (1936, Eqs. 23–26); the closed-form grounding resistance is available as :func:groundfield.references.dwight1936.n_point_star for validation (Dwight covers 3, 4, 6 and 8 arms).

Attributes:

Name Type Description
center Point3D

Hub (x, y, z) in metres. Every arm shares this point and stays at z = center[2] — the wire is horizontal.

n_arms int

Number of radial arms (\(\ge 2\)).

arm_length float

Length \(L\) of a single arm in metres.

orientation_deg float

Azimuth of the first arm measured from the world +x axis in degrees; the remaining arms follow at multiples of \(360^\circ / n\).

concrete_shell_coefficient_ohm_m float

Optional per-segment radial concrete-shell coefficient, same semantics as on :class:StripElectrode (ADR-0012 V2).

Notes

Discretisation and the reaction-matrix assembly treat the star as n_arms straight wires sharing the centre node, so it forms a single galvanic cluster — exactly the physical Sternerder.

arm_tips property

arm_tips: list[Point3D]

Outer endpoint (x, y, z) of each arm.

edges property

edges: list[tuple[Point3D, Point3D]]

Straight wires making up the star (centre → each tip).

length property

length: float

Total wire length in metres (n_arms * arm_length).

StripElectrode

Bases: _ElectrodeBase

Horizontal straight strip earth electrode (Banderder).

A buried straight wire from start to end. Both end points must lie at the same depth (\(z_\text{start} = z_\text{end}\)); the in-plane direction is arbitrary, not restricted to the \(x\) or \(y\) axis. The strip's length is the Euclidean distance between the end points.

Attributes:

Name Type Description
start Point3D

Strip start (x, y, z) in metres.

end Point3D

Strip end (x, y, z) in metres. Must have the same \(z\) as start.

Notes

The strip is the canonical Banderder. For the image / mom family the discretisation produces one chain of wire segments (no doubled-wire approximation). Plausibility tests compare against :func:groundfield.references.dwight1936.horizontal_wire.

length property

length: float

Euclidean strip length in metres.