Characterising a location without its electrode¶
What does the network do at a given station, independently of the electrode installed there?
Adding an electrode is a rank-one change to the nodal matrix, so every nodal
voltage is a Möbius function of its admittance. Two solves with the electrode
removed therefore determine the response for every electrode, exactly and at
no further cost — measured against genuine solves from 0.05 Ω to 500 Ω and at a
complex value, the closed form agrees to 3e-15 relative.
The two extremes — no electrode and an ideal one — are the endpoints of that
curve, and both are exact limits rather than numerical stand-ins. Z_network,
the driving-point impedance with the local electrode removed, is the
site-independent number the whole analysis is built around.
See Concepts.
response ¶
Characterise a location in the network independently of the electrode there.
The idea this module implements: to describe what the network does at a given station, remove the station's own electrode and ask what is left. Whatever electrode is eventually installed there only ever appears as one shunt admittance, so the network's contribution can be stated once and the electrode varied afterwards -- including to its two extremes, an ideal electrode and none at all.
That intuition turns out to be exact rather than approximate. Adding a shunt
Y_B at bus b is a rank-one change to the nodal matrix, so by the
Sherman-Morrison identity every nodal voltage is a Möbius function of it:
.. math::
\underline{u}(Y_B) = \underline{u}_0
- \frac{Y_B\, \underline{z}\, u_{0,b}}{1 + Y_B Z_\text{net}}
with three site-independent objects, all obtained without knowing the electrode:
u_0
the nodal voltages of the fault solve with the electrode removed -- the
open-circuit response of the location;
z
the column Y_0^{-1} e_b, i.e. the voltage everywhere per ampere injected
at b, source-free;
Z_net = z_b
the driving-point impedance of everything except the local electrode --
the parallel impedance the network offers at that point.
Two solves therefore determine the response for every electrode, exactly and
at no further cost. Measured against real solves at Z_B from 0.05 Ω to 500 Ω
and at a complex value, the closed form agrees to 2e-15 relative.
The extremes
Z_B → ∞ (no electrode) and Z_B → 0 (ideal electrode) are the endpoints of
that curve and both are exact limits, not numerical stand-ins -- the ideal
electrode in particular, which the solver itself rejects because a zero impedance
is not a passive value it can invert.
The driving-point impedance is exactly Z_dp(Y_B) = 1/(Y_B + 1/Z_net), so it
runs monotonically from Z_net down to zero as the electrode improves. Over
all passive electrodes the largest attainable magnitude is not quite at the
open end: a purely reactive Y_B = -j\,Im(1/Z_net) cancels the network's
susceptance and gives |Z_dp| = 1/Re(1/Z_net). It exceeds |Z_net| by very
little in a typical cable network -- 0.1 % in the verification case -- but it is
the honest bound and is reported next to the two endpoints.
What the extremes do not bracket
ResultReductionFactor.value, the EPR-based reduction factor, is constant
along the whole curve. The closed form shows why: the voltage at the bus is
u_b(Y_B) = u_{0,b} / (1 + Y_B Z_\text{net}) with and without mutual coupling
alike, so the factor cancels out of the quotient exactly. That is the same
invariance the sensitivity study runs into, here derived rather than measured.
The three cases are extremes of the local quantities only -- the potential
rise at the bus itself and its driving-point impedance. Quantities transferred
to other buses are not bounded by them: the EPR at a remote bus and the
current-based reduction factor can both exceed the ideal value for a
passive, purely capacitive Z_B, because the phase of Y_B enters those
through a different combination than through 1 + Y_B Z_net alone. Use
:meth:BusResponse.sweep -- which costs no solve -- when the worst case of a
transfer quantity is what a study needs.
BusResponse ¶
Bases: BaseModel
Closed-form response of the network to the electrode at one bus.
Built by :func:bus_response. Evaluating it costs no solve.
Attributes:
| Name | Type | Description |
|---|---|---|
fault |
str
|
Fault the response was built for. |
bus |
str
|
Bus whose electrode is the free parameter. |
fault_bus |
str
|
Bus the fault sits on. Used to anchor which group of buses is named as feeding the soil when the earth-return current is split. |
frequencies |
list of float
|
Frequencies covered. |
z_network |
dict of float to complex
|
Driving-point impedance at |
u_open |
dict of float to dict of str to complex
|
Nodal voltages with the local electrode removed. |
z_column |
dict of float to dict of str to complex
|
Voltage at every bus per ampere injected at |
r_epr |
dict of float to float or None
|
The EPR-based reduction factor, carried through because it is constant along the whole curve. |
i_fault |
dict of float to complex
|
Fault current, the negated sum of source injections. Constant. |
admittance ¶
Translate an electrode spelling into a shunt admittance.
None and an infinite impedance both mean no electrode
(Y_B = 0); a zero impedance means an ideal one and is returned as
inf, which :meth:voltages handles as a limit rather than a
division.
Source code in src/groundinsight/analysis/response.py
driving_point ¶
Z_dp = 1 / (Y_B + 1/Z_net) -- the impedance seen at bus.
Source code in src/groundinsight/analysis/response.py
evaluate ¶
Everything the location does for one electrode, as one frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
z_bus
|
(complex, float or None)
|
The electrode. |
required |
label
|
str
|
Value of the |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per frequency: |
Source code in src/groundinsight/analysis/response.py
extremes ¶
The bracket: no electrode, ideal electrode, and the passive worst case.
Three rows per frequency, labelled "open", "ideal" and
"worst_passive". The first two are the endpoints of the curve; the
third is the reactive electrode that maximises the driving-point
magnitude (see :meth:worst_case_electrode).
Source code in src/groundinsight/analysis/response.py
sweep ¶
sweep(
z_values: Sequence[ElectrodeSpec],
*,
labels: Optional[Sequence[str]] = None
) -> pl.DataFrame
Evaluate many electrodes at once. No solve, whatever the length.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
z_values
|
sequence
|
Electrodes, in the spellings :meth: |
required |
labels
|
sequence of str
|
One label per value. Defaults to a rendering of each value. |
None
|
Source code in src/groundinsight/analysis/response.py
voltages ¶
Nodal voltages for one electrode at one frequency.
Both endpoints are evaluated as limits, so an ideal electrode gives
exactly zero at bus instead of a very small number.
Source code in src/groundinsight/analysis/response.py
worst_case_electrode ¶
The passive electrode that maximises |Z_dp|, and the value it gives.
Over the closed right half-plane of Y_B the magnitude of
1/(Y_B + Y_net) is largest where the imaginary parts cancel and the
real part is as small as it can be, i.e. at Y_B = -j*Im(Y_net). The
result exceeds |Z_net| only slightly in a cable network, but it is
the true bound rather than an assumed one.
Returns:
| Type | Description |
|---|---|
dict
|
|
Source code in src/groundinsight/analysis/response.py
bus_response ¶
Build the closed-form response of the network to the electrode at one bus.
Requires a solved fault: the assembled nodal system is reused, the bus's own shunt is taken back out of it, and two systems are solved once each -- the fault with the electrode removed, and a unit injection at the bus. Neither depends on the electrode, which is why the result covers every electrode.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
A network with |
required |
fault
|
str
|
The solved fault. |
required |
bus
|
str
|
Bus whose electrode is the free parameter. Defaults to the fault bus. |
None
|
Returns:
| Type | Description |
|---|---|
BusResponse
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the fault has not been solved, if the bus is unknown or inactive, or if the network without that electrode has no path to reference earth -- in which case there is nothing to characterise, because the location's behaviour is its own electrode. |
Examples:
>>> gi.run_fault(net, "F1")
>>> response = gi.bus_response(net, fault="F1")
>>> response.extremes()
>>> response.evaluate(7.5) # any electrode, no solve
Source code in src/groundinsight/analysis/response.py
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 | |