VoltageLimits#

class popo.standards.VoltageLimits(name, lower_pu, upper_pu, coverage=1.0, extreme_lower_pu=None, extreme_upper_pu=None, reference='')#

Bases: object

A statutory voltage band and the rule for judging compliance with it.

Construct through the named constructors rather than directly, so that a study records which standard it applied instead of which numbers it chose.

Variables:
  • name – Human-readable identifier, carried into every report.

  • lower_pu – Lower bound of the normal band, in per unit of nominal.

  • upper_pu – Upper bound of the normal band, in per unit of nominal.

  • coverage – Share of samples that must lie inside the normal band. 1.0 admits no exceedance; EN 50160 sets 0.95 over a week.

  • extreme_lower_pu – Bound that no sample may break, where the standard defines one. None where the normal band is already absolute.

  • extreme_upper_pu – The same, above nominal.

  • reference – The clause or document the limits come from.

classmethod en_50160()#

European public distribution networks, EN 50160:2022.

95 % of ten-minute mean RMS values over each week must sit within \(U_n \pm 10\%\), and every value within +10 %/-15 %. The coverage rule is the substance of the standard, not a technicality: a feeder may sit below the band for part of a week and still comply.

Return type:

VoltageLimits

classmethod ansi_c84_1_range_a()#

North American service voltage, ANSI C84.1 Range A.

\(\pm 5\%\) of nominal, which utilities are expected to hold under normal conditions. There is no coverage allowance: Range B exists to describe the excursions Range A does not permit.

Return type:

VoltageLimits

classmethod ansi_c84_1_range_b()#

North American service voltage, ANSI C84.1 Range B.

The wider band, -8.3 % to +5.8 %, describing conditions that are infrequent and must be corrected within a reasonable time. Use it to classify how bad an excursion is, not as the operating target.

Return type:

VoltageLimits

check(voltage_pu)#

Judge a per-unit voltage series against this standard.

Parameters:

voltage_pu (ArrayLike) – Voltage samples in per unit of nominal, of any shape. Non-finite entries are dropped rather than counted as breaches.

Return type:

ComplianceReport

Returns:

A ComplianceReport carrying the verdict and what drove it.

Raises:

ValueError – If no finite sample is present, since a verdict on an empty series would be indistinguishable from a pass.

Examples

>>> limits = VoltageLimits.ansi_c84_1_range_a()
>>> limits.check([1.0, 0.99, 1.02]).compliant
True
violations(readings, value_col='vmag_pu')#

The readings outside this standard’s normal band, and how far outside.

Parameters:
  • readings (DataFrame) – One row per reading, e.g. per (bus, phase) from popo.powerflow.results.bus_voltages() or per (bus, step) from popo.powerflow.SolveReport.bus_records.

  • value_col (str) – Column holding the per-unit voltage.

Return type:

DataFrame

Returns:

The rows of readings outside [lower_pu, upper_pu], with a side column ("low" or "high") and a severity_pu column, the distance past the breached bound, always positive. Non-finite readings are not violations.

Examples

>>> limits = VoltageLimits.ansi_c84_1_range_a()
>>> limits.violations(pd.DataFrame({"vmag_pu": [0.94, 1.0, 1.07]}))["side"].tolist()
['low', 'high']
event_summary(traces)#

Per-trace counts, severities and durations of excursions outside the band.

A single worst value says how bad a trace got; it does not say whether that was one step or the whole afternoon. This counts the steps above and below the band, their mean severity, and the longest unbroken run, so a brief touch and a sustained excursion are told apart.

Parameters:

traces (Mapping[str, ArrayLike]) – One per-unit voltage series per name, e.g. per customer bus from popo.powerflow.SolveReport.voltage_traces(). A non-finite step, such as a diverged solve, counts as neither above nor below.

Return type:

DataFrame

Returns:

One row per trace – name, v_mean_pu, v_min_pu, v_max_pu, v_std_pu, then over_steps, over_severity_pu and over_longest_steps, and the same three for under.

Examples

>>> limits = VoltageLimits.ansi_c84_1_range_a()
>>> limits.event_summary({"a": [1.0, 1.06, 1.07, 1.0]})["over_longest_steps"].item()
2