Skip to content

Closed-form reference cases

Every result in this package is a nodal solve, and a nodal solve will happily return a number for a model that is wrong. These cases are the antidote: configurations whose answer is known in closed form from the standard treatment of grounding systems, run through the ordinary public API and compared.

The closed forms are derived in each case's docstring rather than quoted, so they can be checked line by line against whichever text you cite. They are the standard results of the German grounding literature (Oeding/Oswald, and the TU Graz and Kücherler treatments); attaching the exact clause and equation numbers of your editions is yours to do — the module does not claim a citation it cannot verify.

Each case names the boundary conditions under which its closed form holds. A deviation outside tolerance means either the model is wrong or a condition was not met, and in practice the second is the more common finding.

case quantity condition
line_ideal_bonding r = \|1 - Z_m/Z_s\| station electrodes negligible against the shield
line_finite_earthing r = \|(Z_s-Z_m)/(Z_s+Z_E)\| both ends earthed with a finite electrode
en50522_chain U_E = 3I_0 · Z_E · r read back from one solved fault
ladder_input_impedance Z_in = -Z'/2 + √(Z'²/4 + Z_e·Z') chain long enough to be semi-infinite
ladder_potential_decay u_n/u_0 = e^(-nγ), γ = arccosh(1 + Z'/2Z_e) far from either end
parallel_decomposition 1/Z_dp = 1/Z_local + Σ 1/Z_side cuts covering every branch at the station

reference

Closed-form reference cases the solver has to reproduce.

Every result in this package is a nodal solve, and a nodal solve will happily return a number for a model that is wrong. The cases below are the antidote: configurations whose answer is known in closed form from the standard treatment of grounding systems, run through the ordinary public API and compared. If the boundary conditions each case names are met, the solver has to land on the closed form — and where it does not, either the model or the assumption is at fault, which is exactly what one wants to find out before a study rests on it.

The closed forms are derived in the docstring of each case rather than quoted, so they can be checked line by line against whichever text you cite. They are the standard results of the German grounding literature (Oeding/Oswald, and the TU Graz and Kücherler treatments); attaching the exact clause and equation numbers of your editions is yours to do — this module does not claim a citation it cannot verify.

The cases

line_ideal_bonding The textbook reduction factor r = |1 - Z_m/Z_s|, valid where the station earths vanish against the shield impedance. line_finite_earthing The same line with real electrodes: r = |(Z_s - Z_m)/(Z_s + Z_E)|. The first case is its Z_E -> 0 limit, which is why the two must converge. en50522_chain U_E = 3*I_0 * Z_E * r -- the norm's own identity, checked as a closed loop through three independently computed quantities. ladder_input_impedance A chain of bonded stations is a ladder network, and a long one presents Z_in = -Z'/2 + sqrt(Z'^2/4 + Z_e*Z') at its end. ladder_potential_decay Along the same ladder the potential falls off as e^(-n*gamma) with gamma = arccosh(1 + Z'/(2*Z_e)) -- the propagation constant that decides how many stations a fault at one of them actually reaches. parallel_decomposition The driving-point impedance at a station is its own electrode in parallel with what every direction contributes, exactly.

Run them with :func:run_reference_cases, which returns one row per case with the closed form, the model value and the relative deviation.

ReferenceCase

ReferenceCase(
    name: str,
    quantity: str,
    conditions: str,
    tolerance: float,
    run: Callable[[], Tuple[float, float]],
)

One closed-form case and the model run that has to match it.

Attributes:

Name Type Description
name str

Short identifier, used as the row key.

quantity str

What is being compared, with its unit.

conditions str

The boundary conditions under which the closed form holds. A deviation outside tolerance means either the model is wrong or a condition here was not met -- the second is the more common finding.

tolerance float

Relative deviation still counted as agreement.

Source code in src/groundinsight/analysis/reference.py
def __init__(
    self,
    name: str,
    quantity: str,
    conditions: str,
    tolerance: float,
    run: Callable[[], Tuple[float, float]],
):
    self.name = name
    self.quantity = quantity
    self.conditions = conditions
    self.tolerance = tolerance
    self._run = run

evaluate

evaluate() -> Dict[str, object]

Run the case and return its row.

Source code in src/groundinsight/analysis/reference.py
def evaluate(self) -> Dict[str, object]:
    """Run the case and return its row."""
    closed_form, model = self._run()
    reference = abs(closed_form)
    deviation = (
        abs(model - closed_form) / reference if reference > 0 else float("nan")
    )
    return {
        "case": self.name,
        "quantity": self.quantity,
        "conditions": self.conditions,
        "closed_form": float(closed_form),
        "model": float(model),
        "rel_deviation": float(deviation),
        "tolerance": float(self.tolerance),
        "agrees": bool(deviation <= self.tolerance),
    }

run_reference_cases

run_reference_cases(
    cases: Optional[List[ReferenceCase]] = None,
) -> pl.DataFrame

Run the closed-form reference cases and report the comparison.

Parameters:

Name Type Description Default
cases list of ReferenceCase

Defaults to :data:REFERENCE_CASES.

None

Returns:

Type Description
DataFrame

One row per case: case, quantity, conditions, closed_form, model, rel_deviation, tolerance, agrees.

Examples:

>>> import groundinsight as gi
>>> gi.run_reference_cases()
Source code in src/groundinsight/analysis/reference.py
def run_reference_cases(
    cases: Optional[List[ReferenceCase]] = None,
) -> pl.DataFrame:
    """
    Run the closed-form reference cases and report the comparison.

    Parameters
    ----------
    cases : list of ReferenceCase, optional
        Defaults to :data:`REFERENCE_CASES`.

    Returns
    -------
    pl.DataFrame
        One row per case: ``case``, ``quantity``, ``conditions``,
        ``closed_form``, ``model``, ``rel_deviation``, ``tolerance``,
        ``agrees``.

    Examples
    --------
    >>> import groundinsight as gi  # doctest: +SKIP
    >>> gi.run_reference_cases()  # doctest: +SKIP
    """
    rows = [case.evaluate() for case in (cases or REFERENCE_CASES)]
    frame = pl.DataFrame(rows)
    failed = frame.filter(~pl.col("agrees"))
    if failed.height:
        logger.warning(
            "%d reference case(s) missed their closed form: %s. Either the model "
            "is wrong or a stated boundary condition was not met -- the second "
            "is the more common finding, so read the 'conditions' column first.",
            failed.height,
            ", ".join(failed["case"].to_list()),
        )
    return frame