Balanced Electric 6x6 - A Harmonic Improvement to Electrical Wiring?

Key Verbiage: Fault Tolerance

#!/usr/bin/env python3
"""
===============================================================================
BALANCED-ELECTRIC-5 — FULL SIX-PHASE MACHINE / LOAD / FAULT TEST
===============================================================================

Purpose
-------
A self-contained numerical A/B test between:

    1) conventional balanced 3-phase
    2) a six-phase machine with six independent phase branches

The test is deliberately conservative:

    * same machine envelope
    * same active copper volume
    * same total copper resistance budget
    * same DC/source voltage
    * same conductor current limit
    * same thermal limit
    * same target mechanical output
    * explicit electrical -> mechanical power accounting
    * no voltage/current multiplication by simply adding waveforms
    * no hidden free energy
    * deterministic fault injection

The six-phase machine is NOT forced through the earlier A+F/B+E/C+D
pair-sum topology. That topology is retained only as an optional phasor
diagnostic. The main machine comparison preserves all six phases.

MODEL
-----
This is an engineering screening model, not an FEA package and not a
replacement for a manufactured-machine validation.

Each phase is represented by a sinusoidal winding coupled to an idealized
rotating rotor. The electromagnetic torque is obtained from instantaneous
phase current and the derivative of phase flux linkage with rotor angle:

    e_k = - d(lambda_k)/dt
    T_k = i_k * d(lambda_k)/d(theta)

The electrical model is:

    V_k = R_k i_k + L_k di_k/dt + e_k

The mechanical model is:

    J domega/dt = T_em - T_load - B omega

Thermal model:

    Cth dT/dt = P_Cu - (T - Tambient)/Rth

The controller is an ideal current-regulated sinusoidal drive with a
voltage/current limiter. The same controller law and limits are used for
both machines.

For the six-phase machine, the six phase axes are:

    0, 60, 120, 180, 240, 300 degrees

For the conventional machine:

    0, 120, 240 degrees

A phase-fault is represented by opening a branch and commanding zero current.
The controller attempts to continue producing the requested torque with the
remaining healthy phases, subject to the same current/voltage/thermal limits.

The script reports both:
    A) nominal equal-output operation
    B) fault survival / derating

It also evaluates the earlier proposed pairing:

    A+F, B+E, C+D

as a phasor-only diagnostic. It is NOT used as the six-phase machine's main
power path because simple voltage/current addition is not a physically valid
power-conserving machine connection by itself.

Dependencies
------------
Python 3.9+
numpy
matplotlib

Usage
-----
    py balanced-electric5.py

Optional:
    py balanced-electric5.py --seconds 0.60 --dt 2e-5
    py balanced-electric5.py --seconds 1.00 --dt 1e-5 --fault-duration 0.20
    py balanced-electric5.py --no-plots
    py balanced-electric5.py --save-dir results

Outputs
-------
    summary.txt
    nominal.csv
    faults.csv
    harmonics.csv
    energy_balance.csv
    topology_pairing_diagnostic.csv
    nominal_waveforms.png
    torque_comparison.png
    thermal_comparison.png
    fault_survival.png
    harmonic_spectrum.png

IMPORTANT
---------
Results are model results. Real motor performance requires electromagnetic
FEA, winding-factor/slotting data, inverter losses, iron-loss models,
mechanical measurements, insulation/protection design, and hardware tests.
===============================================================================
"""

from __future__ import annotations

import argparse
import csv
import math
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Tuple

import numpy as np

try:
    import matplotlib.pyplot as plt
except Exception:
    plt = None


# =============================================================================
# CONFIGURATION
# =============================================================================

@dataclass
class Config:
    # Simulation
    seconds: float = 0.60
    dt: float = 2.0e-5
    sample_plot_every: int = 10

    # Electrical source
    f_e: float = 60.0
    v_phase_rms: float = 120.0
    current_limit: float = 18.0
    voltage_limit: float = 240.0

    # Machine target
    pole_pairs: int = 2
    target_speed_rpm: float = 1800.0
    target_power_w: float = 5000.0

    # Per-conductor / copper fairness
    total_copper_volume: float = 1.0
    copper_resistance_total_3ph: float = 0.90
    copper_resistance_total_6ph: float = 0.90

    # Winding parameters.
    # These are screening values, scaled so the two systems have the same
    # total copper budget.
    inductance_phase_3ph: float = 0.012
    inductance_phase_6ph: float = 0.006
    flux_linkage_peak: float = 0.62

    # Mechanical
    inertia: float = 0.035
    viscous_b: float = 0.003

    # Thermal
    ambient_c: float = 25.0
    thermal_limit_c: float = 120.0
    thermal_Rth_C_per_W: float = 0.045
    thermal_Cth_J_per_C: float = 900.0

    # Controller
    current_loop_hz: float = 900.0
    max_current_slew_a_per_s: float = 18000.0
    speed_loop_hz: float = 25.0
    speed_kp_torque: float = 0.045
    speed_ki_torque: float = 0.65

    # Fault testing
    fault_duration_s: float = 0.20
    fault_settle_s: float = 0.08

    # Analysis
    fft_harmonics_max: int = 30
    rms_tail_fraction: float = 0.35


# =============================================================================
# BASIC UTILITIES
# =============================================================================

def rms(x: np.ndarray) -> float:
    return float(np.sqrt(np.mean(np.square(x))))


def pct(x: float) -> float:
    return 100.0 * x


def safe_div(a: float, b: float, default: float = 0.0) -> float:
    if abs(b) < 1e-15:
        return default
    return a / b


def fmt(x: float, digits: int = 6) -> str:
    if not math.isfinite(x):
        return "nan"
    return f"{x:.{digits}f}"


def phase_wrap_rad(x: float) -> float:
    return (x + math.pi) % (2.0 * math.pi) - math.pi


def phasor_from_samples(x: np.ndarray, theta: np.ndarray) -> complex:
    """Fundamental coefficient against electrical angle theta."""
    c = (2.0 / len(x)) * np.sum(x * np.exp(-1j * theta))
    return complex(c)


def positive_sequence(ph: np.ndarray) -> complex:
    """abc positive sequence, x_a + a*x_b + a^2*x_c convention."""
    a = np.exp(1j * 2.0 * math.pi / 3.0)
    return (ph[0] + a * ph[1] + a * a * ph[2]) / 3.0


def negative_sequence(ph: np.ndarray) -> complex:
    a = np.exp(1j * 2.0 * math.pi / 3.0)
    return (ph[0] + a * a * ph[1] + a * ph[2]) / 3.0


def zero_sequence(ph: np.ndarray) -> complex:
    return complex(np.mean(ph))


# =============================================================================
# MACHINE DEFINITION
# =============================================================================

@dataclass
class Machine:
    name: str
    axes: np.ndarray
    resistance: np.ndarray
    inductance: np.ndarray
    flux_peak: float
    current_limit: float
    voltage_limit: float
    pole_pairs: int

    @property
    def nph(self) -> int:
        return len(self.axes)

    @property
    def copper_resistance_sum(self) -> float:
        return float(np.sum(self.resistance))

    @property
    def copper_budget_relative(self) -> float:
        return self.copper_resistance_sum


def make_machine(cfg: Config, nph: int) -> Machine:
    axes = 2.0 * math.pi * np.arange(nph) / nph

    # Equal total copper resistance budget.
    # More phase branches => lower resistance per branch.
    if nph == 3:
        r = cfg.copper_resistance_total_3ph / nph
        L = cfg.inductance_phase_3ph
    else:
        r = cfg.copper_resistance_total_6ph / nph
        L = cfg.inductance_phase_6ph

    return Machine(
        name=f"{nph}-phase",
        axes=axes,
        resistance=np.full(nph, r, dtype=float),
        inductance=np.full(nph, L, dtype=float),
        flux_peak=cfg.flux_linkage_peak,
        current_limit=cfg.current_limit,
        voltage_limit=cfg.voltage_limit,
        pole_pairs=cfg.pole_pairs,
    )


# =============================================================================
# ELECTROMAGNETIC MODEL
# =============================================================================

def flux_linkage(machine: Machine, theta_m: float) -> np.ndarray:
    """
    Sinusoidal phase flux linkage.

    theta_m is mechanical rotor angle.
    Electrical angle = pole_pairs * theta_m.
    """
    theta_e = machine.pole_pairs * theta_m
    return machine.flux_peak * np.cos(theta_e - machine.axes)


def dflux_dtheta(machine: Machine, theta_m: float) -> np.ndarray:
    """
    d(lambda_k)/d(theta_m).
    """
    theta_e = machine.pole_pairs * theta_m
    return (
        machine.flux_peak
        * machine.pole_pairs
        * (-np.sin(theta_e - machine.axes))
    )


def back_emf(machine: Machine, theta_m: float, omega_m: float) -> np.ndarray:
    # Terminal-voltage convention: v = R i + L di/dt + d(lambda)/dt.
    # Therefore the source-side flux term is +d(lambda)/dt.  The negative
    # sign belongs to the separately defined induced/back-EMF quantity.
    return dflux_dtheta(machine, theta_m) * omega_m


def electromagnetic_torque(machine: Machine, theta_m: float, current: np.ndarray) -> float:
    return float(np.dot(current, dflux_dtheta(machine, theta_m)))


# =============================================================================
# CONTROL
# =============================================================================

@dataclass
class ControllerState:
    speed_integral: float = 0.0


def desired_current_vector(
    machine: Machine,
    theta_m: float,
    omega_m: float,
    torque_cmd: float,
) -> np.ndarray:
    """
    Minimum-norm current vector producing the requested instantaneous torque
    under the sinusoidal flux model.

    Since:
        T = i dot d(lambda)/d(theta)

    the minimum-norm solution is proportional to d(lambda)/d(theta).
    """
    g = dflux_dtheta(machine, theta_m)
    gg = float(np.dot(g, g))

    if gg < 1e-20 or torque_cmd == 0.0:
        return np.zeros(machine.nph)

    i = (torque_cmd / gg) * g

    # Enforce phase current limit.
    peak = float(np.max(np.abs(i)))
    if peak > machine.current_limit:
        i *= machine.current_limit / peak

    return i


def speed_controller(
    cfg: Config,
    controller: ControllerState,
    omega_m: float,
    target_omega: float,
    base_torque: float,
    dt: float,
    torque_cap: float,
) -> float:
    """Speed regulator around the torque required by the commanded load.

    The previous draft used only a speed-error torque command.  That meant a
    machine starting very near the target speed could initially receive almost
    no torque while a 5 kW load was already applied.  This version explicitly
    supplies the steady-state load torque and uses the speed loop only as the
    correction term.
    """
    error = target_omega - omega_m

    controller.speed_integral += error * dt
    controller.speed_integral = float(
        np.clip(controller.speed_integral, -torque_cap, torque_cap)
    )

    correction = (
        cfg.speed_kp_torque * error
        + cfg.speed_ki_torque * controller.speed_integral
    )

    torque = base_torque + correction
    return float(np.clip(torque, 0.0, torque_cap))


def load_torque_for_target_power(
    cfg: Config,
    omega_m: float,
) -> float:
    if abs(omega_m) < 1e-9:
        return 0.0
    return cfg.target_power_w / abs(omega_m)


# =============================================================================
# PHYSICAL VOLTAGE LIMITING
# =============================================================================

def voltage_limited_current_command(
    machine: Machine,
    theta_m: float,
    omega_m: float,
    current_actual: np.ndarray,
    current_target: np.ndarray,
    dt: float,
) -> Tuple[np.ndarray, np.ndarray]:
    """Current controller with an explicit, power-consistent voltage limit.

    The branch equation is evaluated as:

        v = R*i_new + L*di/dt + d(lambda)/dt

    with i_new = i + di*dt.  The allowable di is solved directly from the
    voltage inequality rather than applying a final voltage scaling that
    would otherwise break the electrical energy balance.
    """
    e = back_emf(machine, theta_m, omega_m)

    tau = 1.0 / (2.0 * math.pi * 900.0)
    raw_di = (current_target - current_actual) / max(tau, dt)

    # v = R*(i + di*dt) + L*di + e
    #   = (R*i + e) + (L + R*dt)*di
    base_v = machine.resistance * current_actual + e
    coeff = machine.inductance + machine.resistance * dt

    # Compute allowable derivative interval for each phase.
    lo = (-machine.voltage_limit - base_v) / np.maximum(coeff, 1e-15)
    hi = ( machine.voltage_limit - base_v) / np.maximum(coeff, 1e-15)
    di = np.clip(raw_di, lo, hi)

    i_new = current_actual + di * dt
    i_new = np.clip(i_new, -machine.current_limit, machine.current_limit)

    # Recompute di after the current hard limit so the reported voltage is
    # exactly the voltage implied by the actual current transition.
    di_actual = (i_new - current_actual) / max(dt, 1e-15)
    v = machine.resistance * i_new + machine.inductance * di_actual + e

    return i_new, v


# =============================================================================
# SIMULATION RESULT
# =============================================================================

@dataclass
class SimResult:
    name: str
    t: np.ndarray
    theta: np.ndarray
    omega: np.ndarray
    speed_rpm: np.ndarray
    current: np.ndarray
    voltage: np.ndarray
    torque: np.ndarray
    torque_load: np.ndarray
    copper_loss: np.ndarray
    electrical_power: np.ndarray
    mechanical_power: np.ndarray
    thermal_c: np.ndarray
    healthy: np.ndarray
    fault_name: str


# =============================================================================
# MAIN SIMULATION
# =============================================================================

def simulate(
    cfg: Config,
    machine: Machine,
    fault_phase: int | None = None,
    fault_name: str = "none",
    duration: float | None = None,
) -> SimResult:

    if duration is None:
        duration = cfg.seconds

    n = int(math.ceil(duration / cfg.dt)) + 1
    t = np.arange(n, dtype=float) * cfg.dt

    theta = np.zeros(n)
    omega = np.zeros(n)
    speed_rpm = np.zeros(n)

    current = np.zeros((n, machine.nph))
    voltage = np.zeros((n, machine.nph))
    torque = np.zeros(n)
    torque_load = np.zeros(n)
    copper_loss = np.zeros(n)
    electrical_power = np.zeros(n)
    mechanical_power = np.zeros(n)
    thermal_c = np.zeros(n)
    healthy = np.ones((n, machine.nph), dtype=bool)

    target_omega = 2.0 * math.pi * cfg.target_speed_rpm / 60.0

    # Start close to synchronous speed to make this an electrical-machine
    # operating-point comparison rather than a startup contest.
    omega[0] = target_omega * 0.985
    thermal_c[0] = cfg.ambient_c

    if fault_phase is not None:
        healthy[:, fault_phase] = False

    controller = ControllerState()

    # Torque cap deliberately generous but finite.
    torque_cap = max(
        cfg.target_power_w / max(target_omega, 1e-9) * 2.5,
        10.0,
    )

    for k in range(n - 1):
        th = theta[k]
        om = omega[k]
        i_now = current[k]

        # Load selected to represent target mechanical output at target speed.
        # Add the modeled viscous-friction torque so the commanded torque
        # corresponds to target shaft output rather than merely shaft+loss.
        tl = load_torque_for_target_power(cfg, target_omega)
        viscous_torque = cfg.viscous_b * target_omega
        base_torque = tl + viscous_torque
        torque_load[k] = tl

        # Speed loop.  The baseline is the load torque; the loop supplies only
        # the correction needed to hold target speed.
        t_cmd = speed_controller(
            cfg,
            controller,
            om,
            target_omega,
            base_torque,
            cfg.dt,
            torque_cap,
        )

        # Fault: remove phase completely.
        if fault_phase is not None:
            # We do not simply delete the phase from the machine. Its branch
            # remains electrically present but is opened, carrying zero current.
            t_cmd = max(t_cmd, 0.0)

        i_target = desired_current_vector(
            machine,
            th,
            om,
            t_cmd,
        )

        if fault_phase is not None:
            i_target[fault_phase] = 0.0

        i_new, v = voltage_limited_current_command(
            machine,
            th,
            om,
            i_now,
            i_target,
            cfg.dt,
        )

        if fault_phase is not None:
            i_new[fault_phase] = 0.0
            v[fault_phase] = 0.0

        current[k + 1] = i_new
        voltage[k] = v

        # Recompute actual electromagnetic torque from actual current.
        te = electromagnetic_torque(machine, th, i_new)

        # If a branch is open, the controller cannot produce arbitrary torque.
        # This is naturally reflected by the current vector.
        torque[k] = te

        # Mechanical dynamics.
        accel = (
            te
            - tl
            - cfg.viscous_b * om
        ) / cfg.inertia

        omega[k + 1] = max(0.0, om + accel * cfg.dt)
        theta[k + 1] = th + omega[k + 1] * cfg.dt

        speed_rpm[k] = omega[k] * 60.0 / (2.0 * math.pi)

        # Instantaneous electrical input.
        pe = float(np.dot(v, i_new))
        pc = float(np.sum(machine.resistance * np.square(i_new)))
        pm = te * om

        electrical_power[k] = pe
        copper_loss[k] = pc
        mechanical_power[k] = pm

        # Lumped thermal model.
        dT = (
            pc
            - (thermal_c[k] - cfg.ambient_c)
            / cfg.thermal_Rth_C_per_W
        ) / cfg.thermal_Cth_J_per_C

        thermal_c[k + 1] = thermal_c[k] + dT * cfg.dt

    speed_rpm[-1] = omega[-1] * 60.0 / (2.0 * math.pi)
    torque_load[-1] = torque_load[-2]
    torque[-1] = torque[-2]
    voltage[-1] = voltage[-2]
    copper_loss[-1] = copper_loss[-2]
    electrical_power[-1] = electrical_power[-2]
    mechanical_power[-1] = mechanical_power[-2]

    return SimResult(
        name=machine.name,
        t=t,
        theta=theta,
        omega=omega,
        speed_rpm=speed_rpm,
        current=current,
        voltage=voltage,
        torque=torque,
        torque_load=torque_load,
        copper_loss=copper_loss,
        electrical_power=electrical_power,
        mechanical_power=mechanical_power,
        thermal_c=thermal_c,
        healthy=healthy,
        fault_name=fault_name,
    )


# =============================================================================
# STEADY-STATE METRICS
# =============================================================================

@dataclass
class Metrics:
    name: str
    speed_rpm: float
    current_rms_total: float
    current_rms_max: float
    voltage_rms_total: float
    voltage_peak_max: float
    pin_w: float
    pout_w: float
    pcu_w: float
    efficiency: float
    pf: float
    torque_mean_nm: float
    torque_ripple_pct: float
    thermal_c: float
    thermal_margin_c: float
    current_utilization_pct: float
    voltage_utilization_pct: float
    power_ripple_pct: float
    energy_residual_pct: float
    fault_name: str


def tail(x: np.ndarray, frac: float) -> np.ndarray:
    start = int(len(x) * (1.0 - frac))
    return x[max(start, 0):]


def calculate_metrics(
    cfg: Config,
    machine: Machine,
    result: SimResult,
) -> Metrics:

    sl = slice(int(len(result.t) * (1.0 - cfg.rms_tail_fraction)), None)

    i_tail = result.current[sl]
    v_tail = result.voltage[sl]

    i_rms_each = np.sqrt(np.mean(i_tail * i_tail, axis=0))
    v_rms_each = np.sqrt(np.mean(v_tail * v_tail, axis=0))

    current_rms_total = float(np.sqrt(np.sum(i_rms_each ** 2)))
    voltage_rms_total = float(np.sqrt(np.sum(v_rms_each ** 2)))

    pin = float(np.mean(result.electrical_power[sl]))
    pcu = float(np.mean(result.copper_loss[sl]))
    pout = float(np.mean(result.mechanical_power[sl]))

    apparent = voltage_rms_total * current_rms_total
    pf = safe_div(pin, apparent)

    tq = result.torque[sl]
    tq_mean = float(np.mean(tq))
    tq_pp = float(np.max(tq) - np.min(tq))
    tq_ripple = safe_div(tq_pp, abs(tq_mean)) * 100.0

    p = result.electrical_power[sl]
    p_ripple = safe_div(
        float(np.max(p) - np.min(p)),
        max(abs(float(np.mean(p))), 1e-12),
    ) * 100.0

    # Approximate conservation residual:
    # Pin should approximately equal Pout + copper + mechanical viscous loss.
    visc = float(np.mean(cfg.viscous_b * result.omega[sl] ** 2))
    residual = pin - (pout + pcu + visc)
    residual_pct = safe_div(abs(residual), max(abs(pin), 1.0)) * 100.0

    thermal = float(np.max(result.thermal_c[sl]))
    current_util = (
        float(np.max(np.abs(i_tail))) / machine.current_limit * 100.0
    )
    voltage_util = (
        float(np.max(np.abs(v_tail))) / machine.voltage_limit * 100.0
    )

    return Metrics(
        name=machine.name,
        speed_rpm=float(np.mean(result.speed_rpm[sl])),
        current_rms_total=current_rms_total,
        current_rms_max=float(np.max(i_rms_each)),
        voltage_rms_total=voltage_rms_total,
        voltage_peak_max=float(np.max(np.abs(v_tail))),
        pin_w=pin,
        pout_w=pout,
        pcu_w=pcu,
        efficiency=safe_div(pout, pin),
        pf=pf,
        torque_mean_nm=tq_mean,
        torque_ripple_pct=tq_ripple,
        thermal_c=thermal,
        thermal_margin_c=cfg.thermal_limit_c - thermal,
        current_utilization_pct=current_util,
        voltage_utilization_pct=voltage_util,
        power_ripple_pct=p_ripple,
        energy_residual_pct=residual_pct,
        fault_name=result.fault_name,
    )


# =============================================================================
# HARMONIC ANALYSIS
# =============================================================================

def harmonic_amplitudes(
    x: np.ndarray,
    sample_rate: float,
    fundamental_hz: float,
    max_h: int,
) -> Dict[int, float]:
    n = len(x)
    window = np.hanning(n)
    y = (x - np.mean(x)) * window
    spec = np.fft.rfft(y)
    freqs = np.fft.rfftfreq(n, 1.0 / sample_rate)

    out = {}
    for h in range(1, max_h + 1):
        target = h * fundamental_hz
        idx = int(np.argmin(np.abs(freqs - target)))
        amp = 2.0 * abs(spec[idx]) / max(np.sum(window), 1e-12)
        out[h] = float(amp)
    return out


def harmonic_metrics(
    cfg: Config,
    result: SimResult,
) -> List[Tuple[str, int, float]]:
    dt = cfg.dt
    fs = 1.0 / dt

    sl = slice(int(len(result.t) * (1.0 - cfg.rms_tail_fraction)), None)
    i = result.current[sl]

    rows = []
    for phase in range(i.shape[1]):
        amps = harmonic_amplitudes(
            i[:, phase],
            fs,
            cfg.f_e,
            cfg.fft_harmonics_max,
        )
        for h, amp in amps.items():
            rows.append((result.name, phase, h, amp))

    return rows


# =============================================================================
# SIX-PHASE / EARLIER PAIRING DIAGNOSTIC
# =============================================================================

def pairing_diagnostic() -> List[Dict[str, float | str]]:
    """
    Reproduce the earlier geometric pairing without pretending that pairwise
    voltage/current addition is a power-conserving machine connection.

    Six source phases:
        A=0 B=30 C=120 D=150 E=240 F=270

    Proposed:
        A+F, B+E, C+D

    We calculate phasor amplitudes and sequence content only.
    """
    names = ["A", "B", "C", "D", "E", "F"]
    deg = np.array([0.0, 30.0, 120.0, 150.0, 240.0, 270.0])
    z = np.exp(1j * np.deg2rad(deg))

    pairs = [(0, 5), (1, 4), (2, 3)]
    out = []

    ph = np.array([z[a] + z[b] for a, b in pairs], dtype=complex)

    # Normalize pair sum by sqrt(2), matching the earlier diagnostic.
    ph /= math.sqrt(2.0)

    p = positive_sequence(ph)
    n = negative_sequence(ph)
    z0 = zero_sequence(ph)

    for idx, (a, b) in enumerate(pairs):
        out.append({
            "pair": f"{names[a]}+{names[b]}",
            "amplitude": abs(ph[idx]),
            "phase_deg": math.degrees(math.atan2(ph[idx].imag, ph[idx].real)) % 360.0,
            "positive_sequence": abs(p),
            "negative_sequence": abs(n),
            "zero_sequence": abs(z0),
            "negative_positive_pct": safe_div(abs(n), abs(p)) * 100.0,
        })

    return out


# =============================================================================
# FAULT TESTS
# =============================================================================

@dataclass
class FaultSummary:
    architecture: str
    fault: str
    phase_index: int
    nominal_pout_w: float
    fault_pout_w: float
    retained_output_pct: float
    speed_rpm: float
    torque_ripple_pct: float
    max_current_util_pct: float
    max_thermal_c: float
    thermal_margin_c: float
    protection_flag: str


def run_faults(
    cfg: Config,
    machine: Machine,
    nominal_metrics: Metrics,
) -> List[FaultSummary]:

    rows = []

    for phase in range(machine.nph):
        fault_name = f"{machine.name}_phase_{phase+1}_OPEN"

        r = simulate(
            cfg,
            machine,
            fault_phase=phase,
            fault_name=fault_name,
            duration=cfg.fault_duration_s,
        )
        m = calculate_metrics(cfg, machine, r)

        retained = safe_div(m.pout_w, max(nominal_metrics.pout_w, 1e-9)) * 100.0

        # Protection flag is descriptive, not a pass/fail claim about a real
        # protection device.
        if m.thermal_c >= cfg.thermal_limit_c:
            protection = "THERMAL_LIMIT_REACHED"
        elif m.current_utilization_pct >= 99.9:
            protection = "CURRENT_LIMIT_ACTIVE"
        elif m.voltage_utilization_pct >= 99.9:
            protection = "VOLTAGE_LIMIT_ACTIVE"
        else:
            protection = "WITHIN_MODEL_LIMITS"

        rows.append(
            FaultSummary(
                architecture=machine.name,
                fault=fault_name,
                phase_index=phase + 1,
                nominal_pout_w=nominal_metrics.pout_w,
                fault_pout_w=m.pout_w,
                retained_output_pct=retained,
                speed_rpm=m.speed_rpm,
                torque_ripple_pct=m.torque_ripple_pct,
                max_current_util_pct=m.current_utilization_pct,
                max_thermal_c=m.thermal_c,
                thermal_margin_c=m.thermal_margin_c,
                protection_flag=protection,
            )
        )

    return rows


# =============================================================================
# CSV OUTPUT
# =============================================================================

def write_csv(path: Path, rows: List[Dict | Tuple], headers: List[str] | None = None):
    with path.open("w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)

        if headers:
            w.writerow(headers)

        for row in rows:
            if isinstance(row, dict):
                w.writerow([row.get(h, "") for h in headers or row.keys()])
            else:
                w.writerow(list(row))


def write_nominal_csv(
    path: Path,
    results: List[SimResult],
    every: int = 20,
):
    rows = []
    for r in results:
        for k in range(0, len(r.t), every):
            rows.append(
                [
                    r.name,
                    r.t[k],
                    r.speed_rpm[k],
                    r.torque[k],
                    r.electrical_power[k],
                    r.mechanical_power[k],
                    r.copper_loss[k],
                    r.thermal_c[k],
                    *r.current[k].tolist(),
                    *r.voltage[k].tolist(),
                ]
            )

    max_ph = max(r.current.shape[1] for r in results)

    headers = [
        "architecture",
        "time_s",
        "speed_rpm",
        "torque_Nm",
        "electrical_power_W",
        "mechanical_power_W",
        "copper_loss_W",
        "temperature_C",
    ]

    for p in range(max_ph):
        headers.append(f"I_phase_{p+1}_A")
    for p in range(max_ph):
        headers.append(f"V_phase_{p+1}_V")

    # Pad shorter 3-phase rows.
    padded = []
    for row in rows:
        base = row[:8]
        vals = row[8:]
        # row contains n current + n voltage.
        # infer n from architecture.
        nph = 3 if "3-phase" in row[0] else 6
        ii = vals[:nph]
        vv = vals[nph:]
        padded.append(
            base
            + ii + [""] * (max_ph - nph)
            + vv + [""] * (max_ph - nph)
        )

    with path.open("w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(headers)
        w.writerows(padded)


# =============================================================================
# REPORTING
# =============================================================================

def metrics_dict(m: Metrics) -> Dict[str, float | str]:
    return {
        "architecture": m.name,
        "fault": m.fault_name,
        "speed_rpm": m.speed_rpm,
        "total_current_rms_A": m.current_rms_total,
        "max_phase_current_rms_A": m.current_rms_max,
        "total_voltage_rms_V": m.voltage_rms_total,
        "max_voltage_V": m.voltage_peak_max,
        "input_power_W": m.pin_w,
        "output_power_W": m.pout_w,
        "copper_loss_W": m.pcu_w,
        "efficiency": m.efficiency,
        "power_factor_model": m.pf,
        "mean_torque_Nm": m.torque_mean_nm,
        "torque_ripple_pct": m.torque_ripple_pct,
        "thermal_C": m.thermal_c,
        "thermal_margin_C": m.thermal_margin_c,
        "current_utilization_pct": m.current_utilization_pct,
        "voltage_utilization_pct": m.voltage_utilization_pct,
        "power_ripple_pct": m.power_ripple_pct,
        "energy_residual_pct": m.energy_residual_pct,
    }


def print_metrics(m: Metrics):
    print(f"\n[{m.name}] {m.fault_name}")
    print(f"  speed                  {m.speed_rpm:12.3f} rpm")
    print(f"  total RMS current      {m.current_rms_total:12.4f} A")
    print(f"  max phase RMS current  {m.current_rms_max:12.4f} A")
    print(f"  total RMS voltage      {m.voltage_rms_total:12.4f} V")
    print(f"  max voltage            {m.voltage_peak_max:12.4f} V")
    print(f"  input power            {m.pin_w:12.4f} W")
    print(f"  mechanical output      {m.pout_w:12.4f} W")
    print(f"  copper loss            {m.pcu_w:12.4f} W")
    print(f"  efficiency             {100*m.efficiency:12.4f} %")
    print(f"  model PF               {m.pf:12.6f}")
    print(f"  mean torque            {m.torque_mean_nm:12.4f} Nm")
    print(f"  torque ripple          {m.torque_ripple_pct:12.4f} %")
    print(f"  max temperature        {m.thermal_c:12.4f} C")
    print(f"  thermal margin         {m.thermal_margin_c:12.4f} C")
    print(f"  current utilization    {m.current_utilization_pct:12.4f} %")
    print(f"  voltage utilization    {m.voltage_utilization_pct:12.4f} %")
    print(f"  input-power ripple     {m.power_ripple_pct:12.4f} %")
    print(f"  energy residual        {m.energy_residual_pct:12.6f} %")


def write_summary(
    path: Path,
    cfg: Config,
    m3: Metrics,
    m6: Metrics,
    faults: List[FaultSummary],
    pairing: List[Dict],
):
    lines = []

    lines.append("=" * 88)
    lines.append("BALANCED-ELECTRIC-5 — FULL SIX-PHASE MACHINE / LOAD / FAULT TEST")
    lines.append("=" * 88)
    lines.append("")
    lines.append("MODEL STATUS")
    lines.append("This is a physically constrained screening model, not FEA or hardware validation.")
    lines.append("The comparison preserves six independent phases rather than pair-summing them.")
    lines.append("Voltage/current addition from the earlier A+F/B+E/C+D topology is reported separately.")
    lines.append("")
    lines.append("FAIRNESS CONSTRAINTS")
    lines.append(f"Target mechanical power:       {cfg.target_power_w:.3f} W")
    lines.append(f"Target speed:                  {cfg.target_speed_rpm:.3f} rpm")
    lines.append(f"Source phase RMS voltage:      {cfg.v_phase_rms:.3f} V")
    lines.append(f"Per-conductor current limit:  {cfg.current_limit:.3f} A")
    lines.append(f"Voltage limit:                 {cfg.voltage_limit:.3f} V")
    lines.append(f"3-phase total copper R:        {cfg.copper_resistance_total_3ph:.6f} ohm")
    lines.append(f"6-phase total copper R:        {cfg.copper_resistance_total_6ph:.6f} ohm")
    lines.append(f"Thermal limit:                 {cfg.thermal_limit_c:.3f} C")
    lines.append("")
    lines.append("NOMINAL RESULTS")
    lines.append("")
    lines.append("Metric                         3-phase             6-phase")
    lines.append("-" * 70)

    def line(label, a, b, suffix=""):
        lines.append(
            f"{label:<30} {a:>14.5f}{suffix:<8} {b:>14.5f}{suffix}"
        )

    line("Speed rpm", m3.speed_rpm, m6.speed_rpm)
    line("Output W", m3.pout_w, m6.pout_w)
    line("Input W", m3.pin_w, m6.pin_w)
    line("Efficiency", 100*m3.efficiency, 100*m6.efficiency, "%")
    line("Copper loss W", m3.pcu_w, m6.pcu_w)
    line("Total RMS current A", m3.current_rms_total, m6.current_rms_total)
    line("Max phase RMS current A", m3.current_rms_max, m6.current_rms_max)
    line("Torque ripple", m3.torque_ripple_pct, m6.torque_ripple_pct, "%")
    line("Power ripple", m3.power_ripple_pct, m6.power_ripple_pct, "%")
    line("Max temperature C", m3.thermal_c, m6.thermal_c)
    line("Thermal margin C", m3.thermal_margin_c, m6.thermal_margin_c)
    line("Current utilization", m3.current_utilization_pct, m6.current_utilization_pct, "%")
    line("Voltage utilization", m3.voltage_utilization_pct, m6.voltage_utilization_pct, "%")
    line("Energy residual", m3.energy_residual_pct, m6.energy_residual_pct, "%")

    lines.append("")
    lines.append("FAULT SURVIVAL")
    lines.append("-" * 88)

    for f in faults:
        lines.append(
            f"{f.architecture:<10} {f.fault:<24} "
            f"retained={f.retained_output_pct:8.3f}% "
            f"speed={f.speed_rpm:8.2f} rpm "
            f"T_ripple={f.torque_ripple_pct:9.3f}% "
            f"Tmax={f.max_thermal_c:8.3f} C "
            f"{f.protection_flag}"
        )

    lines.append("")
    lines.append("EARLIER A+F / B+E / C+D PAIRING — PHASOR-ONLY DIAGNOSTIC")
    lines.append("-" * 88)

    for p in pairing:
        lines.append(
            f"{p['pair']:<8} amp={p['amplitude']:.9f} "
            f"phase={p['phase_deg']:.6f} deg "
            f"positive={p['positive_sequence']:.9f} "
            f"negative={p['negative_sequence']:.9f} "
            f"neg/pos={p['negative_positive_pct']:.3f}%"
        )

    lines.append("")
    lines.append("INTERPRETATION RULES")
    lines.append("-" * 88)
    lines.append("1. No result here proves superiority of one machine technology.")
    lines.append("2. Lower power ripple is not the same thing as higher efficiency.")
    lines.append("3. A phase-fault result is meaningful only inside this model's limits.")
    lines.append("4. The earlier pair-sum topology is not treated as a power-conserving machine connection.")
    lines.append("5. Real claims require FEA, inverter-loss modeling, thermal validation and hardware tests.")
    lines.append("6. The most useful comparison is equal output + equal copper + equal voltage/current/thermal limits.")

    path.write_text("\n".join(lines), encoding="utf-8")


# =============================================================================
# PLOTS
# =============================================================================

def plot_nominal(
    out: Path,
    r3: SimResult,
    r6: SimResult,
):
    if plt is None:
        return

    sl3 = slice(int(len(r3.t) * 0.65), None)
    sl6 = slice(int(len(r6.t) * 0.65), None)

    fig, ax = plt.subplots(figsize=(12, 6))
    ax.plot(r3.t[sl3], r3.torque[sl3], label="3-phase torque")
    ax.plot(r6.t[sl6], r6.torque[sl6], label="6-phase torque")
    ax.set_xlabel("Time (s)")
    ax.set_ylabel("Electromagnetic torque (Nm)")
    ax.set_title("Nominal Electromagnetic Torque")
    ax.grid(True, alpha=0.25)
    ax.legend()
    fig.tight_layout()
    fig.savefig(out / "torque_comparison.png", dpi=160)
    plt.close(fig)

    fig, ax = plt.subplots(figsize=(12, 6))
    ax.plot(r3.t[sl3], r3.speed_rpm[sl3], label="3-phase speed")
    ax.plot(r6.t[sl6], r6.speed_rpm[sl6], label="6-phase speed")
    ax.set_xlabel("Time (s)")
    ax.set_ylabel("Speed (rpm)")
    ax.set_title("Nominal Speed")
    ax.grid(True, alpha=0.25)
    ax.legend()
    fig.tight_layout()
    fig.savefig(out / "nominal_speed.png", dpi=160)
    plt.close(fig)

    fig, ax = plt.subplots(figsize=(12, 6))
    ax.plot(r3.t[sl3], r3.thermal_c[sl3], label="3-phase temperature")
    ax.plot(r6.t[sl6], r6.thermal_c[sl6], label="6-phase temperature")
    ax.set_xlabel("Time (s)")
    ax.set_ylabel("Winding temperature (C)")
    ax.set_title("Nominal Thermal Response")
    ax.grid(True, alpha=0.25)
    ax.legend()
    fig.tight_layout()
    fig.savefig(out / "thermal_comparison.png", dpi=160)
    plt.close(fig)

    fig, ax = plt.subplots(figsize=(12, 6))
    ax.plot(r3.t[sl3], r3.electrical_power[sl3], label="3-phase input power")
    ax.plot(r6.t[sl6], r6.electrical_power[sl6], label="6-phase input power")
    ax.plot(r3.t[sl3], r3.mechanical_power[sl3], "--", label="3-phase mechanical")
    ax.plot(r6.t[sl6], r6.mechanical_power[sl6], "--", label="6-phase mechanical")
    ax.set_xlabel("Time (s)")
    ax.set_ylabel("Power (W)")
    ax.set_title("Electrical Input / Mechanical Output")
    ax.grid(True, alpha=0.25)
    ax.legend()
    fig.tight_layout()
    fig.savefig(out / "power_comparison.png", dpi=160)
    plt.close(fig)


def plot_faults(
    out: Path,
    fault_rows: List[FaultSummary],
):
    if plt is None or not fault_rows:
        return

    names = []
    vals = []
    groups = []

    for r in fault_rows:
        names.append(r.fault.replace(r.architecture + "_", ""))
        vals.append(r.retained_output_pct)
        groups.append(r.architecture)

    x = np.arange(len(names))

    fig, ax = plt.subplots(figsize=(13, 6))
    for arch in sorted(set(groups)):
        xx = [i for i, g in enumerate(groups) if g == arch]
        yy = [vals[i] for i in xx]
        ax.plot(xx, yy, "o-", label=arch)

    ax.set_xticks(x)
    ax.set_xticklabels(names, rotation=45, ha="right")
    ax.set_ylabel("Retained output (%)")
    ax.set_title("Single-Phase Open Fault Survival")
    ax.grid(True, alpha=0.25)
    ax.legend()
    fig.tight_layout()
    fig.savefig(out / "fault_survival.png", dpi=160)
    plt.close(fig)


def plot_harmonics(
    out: Path,
    cfg: Config,
    r3: SimResult,
    r6: SimResult,
):
    if plt is None:
        return

    fs = 1.0 / cfg.dt

    fig, ax = plt.subplots(figsize=(12, 6))

    for r in [r3, r6]:
        sl = slice(int(len(r.t) * (1.0 - cfg.rms_tail_fraction)), None)
        i = r.current[sl]

        vals = []
        for h in range(1, cfg.fft_harmonics_max + 1):
            amps = []
            for p in range(i.shape[1]):
                a = harmonic_amplitudes(
                    i[:, p],
                    fs,
                    cfg.f_e,
                    cfg.fft_harmonics_max,
                )
                amps.append(a[h])
            vals.append(np.sqrt(np.sum(np.square(amps))))

        ax.stem(
            np.arange(1, cfg.fft_harmonics_max + 1),
            vals,
            markerfmt=".",
            basefmt=" ",
            label=r.name,
        )

    ax.set_xlabel("Harmonic order")
    ax.set_ylabel("Aggregate current harmonic amplitude")
    ax.set_title("Current Harmonic Spectrum")
    ax.grid(True, alpha=0.25)
    ax.legend()
    fig.tight_layout()
    fig.savefig(out / "harmonic_spectrum.png", dpi=160)
    plt.close(fig)


# =============================================================================
# MAIN
# =============================================================================

def parse_args() -> argparse.Namespace:
    p = argparse.ArgumentParser(
        description="Full six-phase vs conventional 3-phase machine/load/fault test."
    )
    p.add_argument("--seconds", type=float, default=0.60)
    p.add_argument("--dt", type=float, default=2.0e-5)
    p.add_argument("--fault-duration", type=float, default=0.20)
    p.add_argument("--save-dir", type=str, default="balanced-electric5-results")
    p.add_argument("--no-plots", action="store_true")
    return p.parse_args()


def main():
    args = parse_args()

    cfg = Config(
        seconds=args.seconds,
        dt=args.dt,
        fault_duration_s=args.fault_duration,
    )

    out = Path(args.save_dir)
    out.mkdir(parents=True, exist_ok=True)

    print("=" * 100)
    print("BALANCED-ELECTRIC-5 — FULL SIX-PHASE MACHINE / LOAD / FAULT TEST")
    print("=" * 100)
    print()
    print("This is a constrained engineering screening model.")
    print("It preserves six independent phases; it does NOT pair-sum them in the")
    print("main machine model. The earlier A+F/B+E/C+D connection is diagnostic only.")
    print()

    m3_machine = make_machine(cfg, 3)
    m6_machine = make_machine(cfg, 6)

    print("MACHINE CONFIGURATION")
    print(f"  3-phase axes: {np.rad2deg(m3_machine.axes)}")
    print(f"  6-phase axes: {np.rad2deg(m6_machine.axes)}")
    print(f"  total copper R, 3φ: {m3_machine.copper_resistance_sum:.6f} ohm")
    print(f"  total copper R, 6φ: {m6_machine.copper_resistance_sum:.6f} ohm")
    print(f"  target: {cfg.target_power_w:.1f} W @ {cfg.target_speed_rpm:.1f} rpm")
    print()

    print("RUNNING NOMINAL 3-PHASE...")
    r3 = simulate(cfg, m3_machine)
    m3 = calculate_metrics(cfg, m3_machine, r3)

    print("RUNNING NOMINAL 6-PHASE...")
    r6 = simulate(cfg, m6_machine)
    m6 = calculate_metrics(cfg, m6_machine, r6)

    print_metrics(m3)
    print_metrics(m6)

    print()
    print("=" * 100)
    print("DIRECT NOMINAL COMPARISON")
    print("=" * 100)

    def compare(label: str, a: float, b: float, unit: str = ""):
        delta = b - a
        rel = safe_div(delta, abs(a)) * 100.0
        print(
            f"{label:<28} 3φ={a:>12.5f}{unit:<5} "
            f"6φ={b:>12.5f}{unit:<5} "
            f"delta={delta:>12.5f}{unit:<5} ({rel:+.3f}%)"
        )

    compare("Output power", m3.pout_w, m6.pout_w, "W")
    compare("Input power", m3.pin_w, m6.pin_w, "W")
    compare("Efficiency", 100*m3.efficiency, 100*m6.efficiency, "%")
    compare("Copper loss", m3.pcu_w, m6.pcu_w, "W")
    compare("Total RMS current", m3.current_rms_total, m6.current_rms_total, "A")
    compare("Max phase RMS current", m3.current_rms_max, m6.current_rms_max, "A")
    compare("Torque ripple", m3.torque_ripple_pct, m6.torque_ripple_pct, "%")
    compare("Power ripple", m3.power_ripple_pct, m6.power_ripple_pct, "%")
    compare("Maximum temperature", m3.thermal_c, m6.thermal_c, "C")
    compare("Current utilization", m3.current_utilization_pct, m6.current_utilization_pct, "%")

    print()
    print("=" * 100)
    print("RUNNING SINGLE-PHASE OPEN FAULTS")
    print("=" * 100)

    fault_rows: List[FaultSummary] = []

    fault_rows.extend(run_faults(cfg, m3_machine, m3))
    fault_rows.extend(run_faults(cfg, m6_machine, m6))

    for f in fault_rows:
        print(
            f"{f.architecture:>8} | {f.fault:<20} | "
            f"retained={f.retained_output_pct:8.3f}% | "
            f"speed={f.speed_rpm:8.2f} rpm | "
            f"torque ripple={f.torque_ripple_pct:9.3f}% | "
            f"Tmax={f.max_thermal_c:8.3f} C | "
            f"{f.protection_flag}"
        )

    print()
    print("=" * 100)
    print("EARLIER A+F / B+E / C+D PAIRING DIAGNOSTIC")
    print("=" * 100)

    pairing = pairing_diagnostic()
    for p in pairing:
        print(
            f"{p['pair']:<8} "
            f"amp={p['amplitude']:.9f} "
            f"phase={p['phase_deg']:.6f}° "
            f"positive={p['positive_sequence']:.9f} "
            f"negative={p['negative_sequence']:.9f} "
            f"zero={p['zero_sequence']:.9f} "
            f"neg/pos={p['negative_positive_pct']:.3f}%"
        )

    # CSVs
    write_nominal_csv(out / "nominal.csv", [r3, r6], every=max(1, int(0.0002 / cfg.dt)))

    metrics_rows = [
        metrics_dict(m3),
        metrics_dict(m6),
    ]
    write_csv(
        out / "nominal_metrics.csv",
        metrics_rows,
        list(metrics_rows[0].keys()),
    )

    fault_dict_rows = [vars(x) for x in fault_rows]
    write_csv(
        out / "faults.csv",
        fault_dict_rows,
        list(fault_dict_rows[0].keys()) if fault_dict_rows else None,
    )

    harm_rows = harmonic_metrics(cfg, r3) + harmonic_metrics(cfg, r6)
    write_csv(
        out / "harmonics.csv",
        harm_rows,
        ["architecture", "phase", "harmonic", "amplitude"],
    )

    # Energy balance trace.
    eb_rows = []
    for r in [r3, r6]:
        every = max(1, int(0.0002 / cfg.dt))
        for k in range(0, len(r.t), every):
            visc = cfg.viscous_b * r.omega[k] ** 2
            residual = (
                r.electrical_power[k]
                - r.mechanical_power[k]
                - r.copper_loss[k]
                - visc
            )
            eb_rows.append([
                r.name,
                r.t[k],
                r.electrical_power[k],
                r.mechanical_power[k],
                r.copper_loss[k],
                visc,
                residual,
            ])

    write_csv(
        out / "energy_balance.csv",
        eb_rows,
        [
            "architecture",
            "time_s",
            "electrical_input_W",
            "mechanical_output_W",
            "copper_loss_W",
            "viscous_loss_W",
            "residual_W",
        ],
    )

    write_csv(
        out / "topology_pairing_diagnostic.csv",
        pairing,
        [
            "pair",
            "amplitude",
            "phase_deg",
            "positive_sequence",
            "negative_sequence",
            "zero_sequence",
            "negative_positive_pct",
        ],
    )

    write_summary(
        out / "summary.txt",
        cfg,
        m3,
        m6,
        fault_rows,
        pairing,
    )

    if not args.no_plots:
        if plt is None:
            print("\nmatplotlib unavailable: plots skipped.")
        else:
            plot_nominal(out, r3, r6)
            plot_faults(out, fault_rows)
            plot_harmonics(out, cfg, r3, r6)

    print()
    print("=" * 100)
    print("FILES WRITTEN")
    print("=" * 100)
    for p in sorted(out.iterdir()):
        print(f"  {p}")

    print()
    print("=" * 100)
    print("IMPORTANT INTERPRETATION")
    print("=" * 100)
    print("This test answers a much stronger question than the earlier waveform test:")
    print("whether a six-phase machine can preserve useful output under matched")
    print("copper/electrical/thermal constraints.")
    print()
    print("It does NOT establish commercial superiority. A real design still needs:")
    print("  * electromagnetic FEA")
    print("  * slot/winding-factor optimization")
    print("  * iron-loss and saturation modeling")
    print("  * inverter switching/conduction losses")
    print("  * detailed thermal network")
    print("  * insulation and fault-protection engineering")
    print("  * acoustic/vibration testing")
    print("  * hardware validation")
    print()
    print(f"Results directory: {out.resolve()}")


if __name__ == "__main__":
    main()

PARTIAL Yield (run the script yourself for file dump)

py balanced-electric5.py
====================================================================================================
BALANCED-ELECTRIC-5 — FULL SIX-PHASE MACHINE / LOAD / FAULT TEST
====================================================================================================

This is a constrained engineering screening model.
It preserves six independent phases; it does NOT pair-sum them in the
main machine model. The earlier A+F/B+E/C+D connection is diagnostic only.

MACHINE CONFIGURATION
  3-phase axes: [  0. 120. 240.]
  6-phase axes: [  0.  60. 120. 180. 240. 300.]
  total copper R, 3φ: 0.900000 ohm
  total copper R, 6φ: 0.900000 ohm
  target: 5000.0 W @ 1800.0 rpm

RUNNING NOMINAL 3-PHASE...
RUNNING NOMINAL 6-PHASE...

[3-phase] none
  speed                      1794.034 rpm
  total RMS current           18.0325 A
  max phase RMS current       10.4285 A
  total RMS voltage          306.8845 V
  max voltage                240.0000 V
  input power               5236.4108 W
  mechanical output         5133.9648 W
  copper loss                 97.5506 W
  efficiency                  98.0436 %
  model PF                   0.946243
  mean torque                 27.3272 Nm
  torque ripple                4.6799 %
  max temperature             25.0643 C
  thermal margin              94.9357 C
  current utilization         80.6097 %
  voltage utilization        100.0000 %
  input-power ripple          12.1191 %
  energy residual            1.928637 %

[6-phase] none
  speed                      1810.475 rpm
  total RMS current           12.7691 A
  max phase RMS current        5.2415 A
  total RMS voltage          411.9540 V
  max voltage                238.7292 V
  input power               5216.1482 W
  mechanical output         5190.3224 W
  copper loss                 24.4576 W
  efficiency                  99.5049 %
  model PF                   0.991607
  mean torque                 27.3764 Nm
  torque ripple                0.8279 %
  max temperature             25.0162 C
  thermal margin              94.9838 C
  current utilization         41.1033 %
  voltage utilization         99.4705 %
  input-power ripple           0.2001 %
  energy residual            2.041133 %

====================================================================================================
DIRECT NOMINAL COMPARISON
====================================================================================================
Output power                 3φ=  5133.96475W     6φ=  5190.32244W     delta=    56.35768W     (+1.098%)
Input power                  3φ=  5236.41082W     6φ=  5216.14818W     delta=   -20.26263W     (-0.387%)
Efficiency                   3φ=    98.04358%     6φ=    99.50489%     delta=     1.46131%     (+1.490%)
Copper loss                  3φ=    97.55062W     6φ=    24.45759W     delta=   -73.09304W     (-74.928%)
Total RMS current            3φ=    18.03250A     6φ=    12.76914A     delta=    -5.26336A     (-29.188%)
Max phase RMS current        3φ=    10.42853A     6φ=     5.24150A     delta=    -5.18703A     (-49.739%)
Torque ripple                3φ=     4.67993%     6φ=     0.82793%     delta=    -3.85199%     (-82.309%)
Power ripple                 3φ=    12.11914%     6φ=     0.20007%     delta=   -11.91907%     (-98.349%)
Maximum temperature          3φ=    25.06433C     6φ=    25.01618C     delta=    -0.04816C     (-0.192%)
Current utilization          3φ=    80.60970%     6φ=    41.10332%     delta=   -39.50638%     (-49.009%)

====================================================================================================
RUNNING SINGLE-PHASE OPEN FAULTS
====================================================================================================
 3-phase | 3-phase_phase_1_OPEN | retained=  60.311% | speed= 1414.33 rpm | torque ripple=  106.570% | Tmax=  25.017 C | WITHIN_MODEL_LIMITS
 3-phase | 3-phase_phase_2_OPEN | retained=  59.677% | speed= 1419.94 rpm | torque ripple=  108.204% | Tmax=  25.017 C | WITHIN_MODEL_LIMITS
 3-phase | 3-phase_phase_3_OPEN | retained=  61.672% | speed= 1417.69 rpm | torque ripple=  104.549% | Tmax=  25.017 C | WITHIN_MODEL_LIMITS
 6-phase | 6-phase_phase_1_OPEN | retained=  79.626% | speed= 1604.39 rpm | torque ripple=   43.301% | Tmax=  25.005 C | WITHIN_MODEL_LIMITS
 6-phase | 6-phase_phase_2_OPEN | retained=  78.504% | speed= 1605.63 rpm | torque ripple=   43.709% | Tmax=  25.005 C | WITHIN_MODEL_LIMITS
 6-phase | 6-phase_phase_3_OPEN | retained=  78.961% | speed= 1604.93 rpm | torque ripple=   43.568% | Tmax=  25.005 C | WITHIN_MODEL_LIMITS
 6-phase | 6-phase_phase_4_OPEN | retained=  79.626% | speed= 1604.39 rpm | torque ripple=   43.301% | Tmax=  25.005 C | WITHIN_MODEL_LIMITS
 6-phase | 6-phase_phase_5_OPEN | retained=  78.504% | speed= 1605.63 rpm | torque ripple=   43.709% | Tmax=  25.005 C | WITHIN_MODEL_LIMITS
 6-phase | 6-phase_phase_6_OPEN | retained=  78.961% | speed= 1604.93 rpm | torque ripple=   43.568% | Tmax=  25.005 C | WITHIN_MODEL_LIMITS

====================================================================================================
EARLIER A+F / B+E / C+D PAIRING DIAGNOSTIC
====================================================================================================
A+F      amp=1.000000000 phase=315.000000° positive=0.707106781 negative=0.707106781 zero=0.000000000 neg/pos=100.000%
B+E      amp=0.366025404 phase=315.000000° positive=0.707106781 negative=0.707106781 zero=0.000000000 neg/pos=100.000%
C+D      amp=1.366025404 phase=135.000000° positive=0.707106781 negative=0.707106781 zero=0.000000000 neg/pos=100.000%

====================================================================================================
FILES WRITTEN
====================================================================================================
  balanced-electric5-results\energy_balance.csv
  balanced-electric5-results\fault_survival.png
  balanced-electric5-results\faults.csv
  balanced-electric5-results\harmonic_spectrum.png
  balanced-electric5-results\harmonics.csv
  balanced-electric5-results\nominal.csv
  balanced-electric5-results\nominal_metrics.csv
  balanced-electric5-results\nominal_speed.png
  balanced-electric5-results\power_comparison.png
  balanced-electric5-results\summary.txt
  balanced-electric5-results\thermal_comparison.png
  balanced-electric5-results\topology_pairing_diagnostic.csv
  balanced-electric5-results\torque_comparison.png

====================================================================================================
IMPORTANT INTERPRETATION
====================================================================================================
This test answers a much stronger question than the earlier waveform test:
whether a six-phase machine can preserve useful output under matched
copper/electrical/thermal constraints.

It does NOT establish commercial superiority. A real design still needs:
  * electromagnetic FEA
  * slot/winding-factor optimization
  * iron-loss and saturation modeling
  * inverter switching/conduction losses
  * detailed thermal network
  * insulation and fault-protection engineering
  * acoustic/vibration testing
  * hardware validation
#!/usr/bin/env python3
"""
BALANCED-ELECTRIC-6 — EXACT USER TOPOLOGY, POWER-CONSERVING REDUCED MODEL
===============================================================================
Compares conventional 3-phase against the exact proposed topology:
    A=0°, B=30°, C=120°, D=150°, E=240°, F=270°
    U=A+F, V=B+E, W=C+D

The six physical windings are retained. The pair connection is represented by
an incidence matrix C and reduced terminal variables:
    i_branch = C i_pair
    v_pair   = C.T v_branch
This guarantees v_branch.T i_branch = v_pair.T i_pair.

Unlike the previous broken delivery, this file is ONLY the runnable program;
it never tries to write itself to /mnt/data. It creates output files next to
itself in balanced-electric6-output/.

This is an engineering screening model, not FEA or hardware certification.
It includes sinusoidal flux linkage, mutual inductance, copper loss, iron loss,
inverter loss, mechanical loss, thermal state, energy accounting, harmonics,
common-mode voltage, nominal operation, and open-pair/open-phase faults.

Requires: Python 3.10+, numpy. matplotlib is optional.
"""
from __future__ import annotations
import argparse, csv, math
from dataclasses import dataclass
from pathlib import Path
import numpy as np

try:
    import matplotlib.pyplot as plt
except Exception:
    plt = None

DEG=math.pi/180.0
TARGET_POWER=5000.0
TARGET_RPM=1800.0
OMEGA_TARGET=TARGET_RPM*2*math.pi/60
TOTAL_COPPER_R=0.90
CURRENT_LIMIT_RMS=18.0
TERMINAL_VOLTAGE_LIMIT_RMS=240.0
AMBIENT=25.0
THERMAL_LIMIT=120.0
THERMAL_RISE_PER_W=0.075
THERMAL_TAU=18.0
INERTIA=0.060
POLE_PAIRS=1
FLUX_PEAK=1.20
L3_SELF=0.012
L6_SELF=0.006
M6_NEAR=0.0012
M6_OPP=0.0005
IRON_BASE=38.0
IRON_SPEED=0.004
INV_FRAC=0.010
MECH_BASE=18.0
MECH_SPEED=0.0025
CONTROL_TAU=0.0008
DT_DEFAULT=4e-5
LOAD_TORQUE=TARGET_POWER/OMEGA_TARGET

AX3_DEG=np.array([0.,120.,240.])
AX6_DEG=np.array([0.,30.,120.,150.,240.,270.])
AX3=AX3_DEG*DEG
AX6=AX6_DEG*DEG
# [A B C D E F] = C [U V W]
C6=np.array([[1,0,0],[0,1,0],[0,0,1],[0,0,1],[0,1,0],[1,0,0]],float)

@dataclass
class System:
    name:str
    axes:np.ndarray
    C:np.ndarray
    R_branch:np.ndarray
    L_branch:np.ndarray
    R_term:np.ndarray
    L_term:np.ndarray
    flux_term_axes:np.ndarray
    flux_term_coeff:np.ndarray

@dataclass
class Result:
    system:System
    fault:str
    t:np.ndarray
    speed:np.ndarray
    theta:np.ndarray
    i_branch:np.ndarray
    v_branch:np.ndarray
    i_term:np.ndarray
    v_term:np.ndarray
    torque:np.ndarray
    pin:np.ndarray
    pout:np.ndarray
    pcu:np.ndarray
    piron:np.ndarray
    pinv:np.ndarray
    pmechloss:np.ndarray
    temp:np.ndarray
    cmv:np.ndarray
    summary:dict


def make_L(n,self_l,near,opp=0):
    M=np.zeros((n,n))
    for i in range(n):
        for j in range(n):
            if i==j: M[i,j]=self_l
            else:
                d=min((i-j)%n,(j-i)%n)
                if d==1: M[i,j]=near
                elif n==6 and d==3: M[i,j]=opp
                else: M[i,j]=near*0.35
    e=np.linalg.eigvalsh(M)
    if e.min()<=1e-9: M+=np.eye(n)*(1e-9-e.min()+1e-10)
    return M


def systems():
    r3=TOTAL_COPPER_R/3
    r6=TOTAL_COPPER_R/6
    L3=make_L(3,L3_SELF,0.0015)
    L6=make_L(6,L6_SELF,M6_NEAR,M6_OPP)
    s3=System('3-phase',AX3,np.eye(3),np.full(3,r3),L3,np.eye(3)*r3,L3,AX3,np.ones(3))
    # The pair terminal flux is C.T lambda_branch. It is generally not a
    # single sinusoid; keeping the six components is important.
    s6=System('USER-6',AX6,C6,np.full(6,r6),L6,C6.T@np.diag(np.full(6,r6))@C6,C6.T@L6@C6,AX6,np.ones(6))
    return s3,s6


def branch_flux(s,theta):
    return FLUX_PEAK*np.cos(POLE_PAIRS*theta-s.axes)

def branch_dflux(s,theta):
    return -FLUX_PEAK*POLE_PAIRS*np.sin(POLE_PAIRS*theta-s.axes)

def branch_emf(s,theta,omega):
    return branch_dflux(s,theta)*omega

def torque(s,theta,ib):
    return float(np.dot(ib,branch_dflux(s,theta)))


def pair_current_reference(s,theta,torque_cmd):
    g=s.C.T@branch_dflux(s,theta)
    gg=float(g@g)
    if gg<1e-14 or torque_cmd<=0: return np.zeros(3)
    ip=(torque_cmd/gg)*g
    # branch RMS current limit, because physical conductors carry pair current.
    ib=s.C@ip
    mx=float(np.max(np.abs(ib)))
    # Instantaneous peak limit corresponding to RMS conductor limit.
    lim=CURRENT_LIMIT_RMS*math.sqrt(2)
    if mx>lim: ip*=lim/mx
    return ip


def fft_thd(x,dt,f0):
    x=np.asarray(x,float); n=len(x)
    if n<128 or f0<=0:return 0.0
    y=x-np.mean(x); w=np.hanning(n); sp=np.fft.rfft(y*w); fr=np.fft.rfftfreq(n,dt)
    k=int(np.argmin(np.abs(fr-f0)))
    if k<1 or abs(sp[k])<1e-14:return 0.0
    bins=np.arange(2*k,len(sp),k); bins=bins[bins<len(sp)]
    return float(np.sqrt(np.sum(np.abs(sp[bins])**2))/abs(sp[k]))


def simulate(s,seconds,dt,fault_pair=None,fault_start=None,fault_duration=0.0):
    n=int(round(seconds/dt))+1; t=np.arange(n)*dt
    speed=np.zeros(n); theta=np.zeros(n)
    ib=np.zeros((n,6 if s.name=='USER-6' else 3)); vb=np.zeros_like(ib)
    it=np.zeros((n,3)); vt=np.zeros((n,3)); te=np.zeros(n)
    pin=np.zeros(n); pout=np.zeros(n); pcu=np.zeros(n); piron=np.zeros(n); pinv=np.zeros(n); pmech_loss_arr=np.zeros(n)
    temp=np.full(n,AMBIENT); cm=np.zeros(n)
    ip=np.zeros(3); omega=OMEGA_TARGET*0.985; th=0.0; T=AMBIENT
    if fault_start is None:fault_start=seconds*0.45
    fend=fault_start+fault_duration
    target=OMEGA_TARGET
    for k,tk in enumerate(t[:-1]):
        active=fault_pair is not None and fault_start<=tk<fend
        tl=LOAD_TORQUE
        # PI-like correction, with explicit load-torque feed-forward.
        corr=0.10*(target-omega)
        torque_cmd=float(np.clip(tl+corr,0,2*tl))
        ip_ref=pair_current_reference(s,th,torque_cmd)
        if active: ip_ref[fault_pair]=0.0
        eb=branch_emf(s,th,omega)
        et=s.C.T@eb
        # Reduced KVL: vt = Rterm*i + Lterm*di/dt + et.
        raw=(ip_ref-ip)/CONTROL_TAU
        base=s.R_term@ip+et
        coeff=np.diag(s.L_term)+s.R_term*dt
        # Diagonal approximation is used only for voltage limiter; actual
        # coupled L is retained in the voltage calculation below.
        # Conservative scalar scaling keeps the full vector within terminal limit.
        di=raw.copy()
        for _ in range(4):
            trial=ip+di*dt
            dtrial=(trial-ip)/dt
            vtrial=s.R_term@trial+s.L_term@dtrial+et
            mx=float(np.max(np.abs(vtrial)))
            lim=TERMINAL_VOLTAGE_LIMIT_RMS*math.sqrt(2)
            if mx>lim: di*=lim/mx
            else: break
        inew=ip+di*dt
        # Terminal current is also conductor current; instantaneous limit.
        limi=CURRENT_LIMIT_RMS*math.sqrt(2)
        ibtrial=s.C@inew; imax=float(np.max(np.abs(ibtrial)))
        if imax>limi:inew*=limi/imax
        if active:inew[fault_pair]=0.0
        di=(inew-ip)/dt
        vt_now=s.R_term@inew+s.L_term@di+et
        if active:vt_now[fault_pair]=0.0
        ib_now=s.C@inew; vb_now=np.linalg.lstsq(s.C.T,vt_now,rcond=None)[0] if s.name=='USER-6' else vt_now.copy()
        # For the series connection, branch voltages are not arbitrary: use the
        # physical branch KVL. Pair power equals branch power exactly by C.
        vb_phys=s.R_branch*ib_now+s.L_branch@ (s.C@di)+eb
        if s.name=='USER-6':
            # vb_phys sums to vt_now; numerical KVL defines physical branch volts.
            vb_now=vb_phys
        te_now=torque(s,th,ib_now)
        p_e=float(np.dot(vt_now,inew))
        p_c=float(np.sum(s.R_branch*ib_now**2))
        pir=IRON_BASE+IRON_SPEED*omega**2
        pml=MECH_BASE+MECH_SPEED*omega**2
        piv=INV_FRAC*abs(p_e)
        alpha=(te_now-tl-pml/max(omega,10))/INERTIA
        omega=max(0,omega+alpha*dt); th+=omega*dt
        heat=p_c+pir+piv
        Ttarget=AMBIENT+THERMAL_RISE_PER_W*heat
        T+=(Ttarget-T)*dt/THERMAL_TAU
        speed[k]=omega*60/(2*math.pi);theta[k]=th;ib[k+1]=ib_now;vb[k]=vb_now
        it[k+1]=inew;vt[k]=vt_now;te[k]=te_now;pin[k]=p_e;pout[k]=max(0,te_now*omega)
        pcu[k]=p_c;piron[k]=pir;pinv[k]=piv;pmech_loss_arr[k]=pml;temp[k]=T;cm[k]=np.mean(vb_now);ip=inew
    speed[-1]=omega*60/(2*math.pi);theta[-1]=th;te[-1]=te[-2];pin[-1]=pin[-2];pout[-1]=pout[-2];pcu[-1]=pcu[-2];piron[-1]=piron[-2];pinv[-1]=pinv[-2];pmech_loss_arr[-1]=pmech_loss_arr[-2];temp[-1]=temp[-2];vb[-1]=vb[-2];vt[-1]=vt[-2];ib[-1]=ib[-2];it[-1]=it[-2];cm[-1]=cm[-2]
    cut=max(1,n//2); sl=slice(cut,None)
    ir=np.sqrt(np.mean(ib[sl]**2,axis=0)); vr=np.sqrt(np.mean(vb[sl]**2,axis=0));
    I=rms_vec(np.sqrt(np.sum(ib[sl]**2,axis=1))); V=rms_vec(np.sqrt(np.sum(vb[sl]**2,axis=1)))
    Pin=float(np.mean(pin[sl])); Pout=float(np.mean(pout[sl])); tq=float(np.mean(te[sl]))
    Ein=float(np.trapezoid(pin[sl],t[sl])); Eout=float(np.trapezoid(pout[sl],t[sl])); Ec=float(np.trapezoid(pcu[sl],t[sl])); Ei=float(np.trapezoid(piron[sl],t[sl])); Ev=float(np.trapezoid(pinv[sl],t[sl])); Em=float(np.trapezoid(pmech_loss_arr[sl],t[sl]))
    w0=speed[cut]*2*math.pi/60;w1=speed[-1]*2*math.pi/60;dEk=.5*INERTIA*(w1*w1-w0*w0)
    dEm=.5*float(it[-1]@s.L_term@it[-1])-0.5*float(it[cut]@s.L_term@it[cut])
    # Electromagnetic energy closure: terminal electrical input must equal
    # copper dissipation + electromagnetic mechanical conversion + change in
    # magnetic stored energy. Iron/inverter/mechanical losses are modeled as
    # separate system-level losses and are therefore not double-counted here.
    Eem=float(np.trapezoid(pout[sl],t[sl]))
    accounted=Eout+Ec+dEm
    residual=100*(Ein-accounted)/max(abs(Ein),1e-12)
    f0=max(speed[-1],1)/60
    cmr=rms_vec(cm[sl])
    summ={'speed_rpm':float(speed[-1]),'total_rms_current_A':I,'max_phase_rms_current_A':float(np.max(ir)),
          'total_rms_voltage_V':V,'max_phase_rms_voltage_V':float(np.max(vr)),'input_power_W':Pin,'mechanical_output_W':Pout,
          'copper_loss_W':float(np.mean(pcu[sl])),'iron_loss_W':float(np.mean(piron[sl])),'inverter_loss_W':float(np.mean(pinv[sl])),
          'mechanical_loss_W':float(np.mean(pmech_loss_arr[sl])),'efficiency_pct':100*Pout/max(Pin,1e-12),
          'model_pf':Pin/max(I*V,1e-12),'mean_torque_Nm':tq,'torque_ripple_pct':100*float(np.std(te[sl]))/max(abs(tq),1e-12),
          'max_temperature_C':float(np.max(temp[sl])),'thermal_margin_C':THERMAL_LIMIT-float(np.max(temp[sl])),
          'current_utilization_pct':100*float(np.max(ir))/CURRENT_LIMIT_RMS,'voltage_utilization_pct':100*float(np.max(vr))/TERMINAL_VOLTAGE_LIMIT_RMS,
          'input_power_ripple_pct':100*float(np.std(pin[sl]))/max(abs(Pin),1e-12),
          'current_thd_pct':100*float(np.mean([fft_thd(ib[sl,j],dt,f0) for j in range(ib.shape[1])])),
          'voltage_thd_pct':100*float(np.mean([fft_thd(vb[sl,j],dt,f0) for j in range(vb.shape[1])])),
          'common_mode_rms_V':cmr,'energy_residual_pct':residual,'electromagnetic_conversion_J':Eem,'magnetic_energy_delta_J':dEm}
    return Result(s,'none' if fault_pair is None else f'{s.name}_pair_{fault_pair+1}_OPEN',t,speed,theta,ib,vb,it,vt,te,pin,pout,pcu,piron,pinv,pmech_loss_arr,temp,cm,summ)

def rms_vec(x):return float(np.sqrt(np.mean(np.square(x))))

def status(s):
    if s['thermal_margin_C']<0:return 'THERMAL_LIMIT'
    if s['max_phase_rms_current_A']>CURRENT_LIMIT_RMS*1.001:return 'CURRENT_LIMIT'
    if s['max_phase_rms_voltage_V']>TERMINAL_VOLTAGE_LIMIT_RMS*1.001:return 'VOLTAGE_LIMIT'
    return 'WITHIN_MODEL_LIMITS'

def print_summary(r):
    s=r.summary;print(f'\n[{r.system.name}] {r.fault}')
    rows=[('speed','speed_rpm','rpm'),('total RMS current','total_rms_current_A','A'),('max phase RMS current','max_phase_rms_current_A','A'),('total RMS voltage','total_rms_voltage_V','V'),('max phase RMS voltage','max_phase_rms_voltage_V','V'),('input power','input_power_W','W'),('mechanical output','mechanical_output_W','W'),('copper loss','copper_loss_W','W'),('iron loss','iron_loss_W','W'),('inverter loss','inverter_loss_W','W'),('mechanical loss','mechanical_loss_W','W'),('efficiency','efficiency_pct','%'),('model PF','model_pf',''),('mean torque','mean_torque_Nm','Nm'),('torque ripple','torque_ripple_pct','%'),('max temperature','max_temperature_C','C'),('thermal margin','thermal_margin_C','C'),('current utilization','current_utilization_pct','%'),('voltage utilization','voltage_utilization_pct','%'),('input-power ripple','input_power_ripple_pct','%'),('current THD','current_thd_pct','%'),('voltage THD','voltage_thd_pct','%'),('common-mode RMS','common_mode_rms_V','V'),('energy residual','energy_residual_pct','%')]
    for a,b,c in rows:print(f'  {a:24s} {s[b]:12.5f} {c}')

def compare(a,b):
    print('\n'+'='*100+'\nDIRECT NOMINAL COMPARISON\n'+'='*100)
    keys=[('Output power','mechanical_output_W','W'),('Input power','input_power_W','W'),('Efficiency','efficiency_pct','%'),('Copper loss','copper_loss_W','W'),('Iron loss','iron_loss_W','W'),('Inverter loss','inverter_loss_W','W'),('Total RMS current','total_rms_current_A','A'),('Max phase RMS current','max_phase_rms_current_A','A'),('Torque ripple','torque_ripple_pct','%'),('Power ripple','input_power_ripple_pct','%'),('Current THD','current_thd_pct','%'),('Voltage THD','voltage_thd_pct','%'),('Max temperature','max_temperature_C','C'),('Current utilization','current_utilization_pct','%'),('Voltage utilization','voltage_utilization_pct','%'),('Common-mode RMS','common_mode_rms_V','V'),('Energy residual','energy_residual_pct','%')]
    for lab,k,u in keys:
        x=a.summary[k];y=b.summary[k];d=y-x;p=100*d/max(abs(x),1e-12);print(f'{lab:26s}  3φ={x:12.5f}{u:2s}  USER-6={y:12.5f}{u:2s}  delta={d:12.5f}{u:2s}  ({p:+8.3f}%)')

def diagnostic():
    z=np.exp(1j*AX6);alpha=np.exp(1j*120*DEG);names=['A','B','C','D','E','F'];pairs=[(0,5),(1,4),(2,3)];out=[]
    for name,(p,q) in zip(['A+F','B+E','C+D'],pairs):
        x=np.zeros(3,dtype=complex);x[['A+F','B+E','C+D'].index(name)]=z[p]+z[q]
        pos=abs((x[0]+alpha*x[1]+alpha**2*x[2])/3);neg=abs((x[0]+alpha**2*x[1]+alpha*x[2])/3);zero=abs(np.sum(x)/3)
        out.append((name,abs(z[p]+z[q]),math.degrees(np.angle(z[p]+z[q]))%360,pos,neg,zero,100*neg/max(pos,1e-15)))
    return out

def save_csv(r,out):
    p=out/f'{r.system.name}_{r.fault}.csv'
    with p.open('w',newline='',encoding='utf-8') as f:
        w=csv.writer(f);w.writerow(['t_s','speed_rpm','theta_rad','torque_Nm','p_in_W','p_out_W','p_cu_W','p_iron_W','p_inv_W','p_mech_loss_W','temperature_C','common_mode_V'])
        for k in range(len(r.t)):w.writerow([r.t[k],r.speed[k],r.theta[k],r.torque[k],r.pin[k],r.pout[k],r.pcu[k],r.piron[k],r.pinv[k],r.pmechloss[k],r.temp[k],r.cmv[k]])

def make_plots(a,b,out):
    if plt is None:return
    plots=[('speed.png','Speed (rpm)',[(a.speed,'3-phase'),(b.speed,'USER-6')]),('torque.png','Torque (N·m)',[(a.torque,'3-phase'),(b.torque,'USER-6')]),('power.png','Power (W)',[(a.pin,'3-phase input'),(b.pin,'USER-6 input'),(a.pout,'3-phase shaft'),(b.pout,'USER-6 shaft')]),('temperature.png','Temperature (°C)',[(a.temp,'3-phase'),(b.temp,'USER-6')])]
    for fn,yl,series in plots:
        fig,ax=plt.subplots(figsize=(11,6))
        for y,l in series:ax.plot(a.t if len(y)==len(a.t) else b.t,y,label=l)
        ax.set_xlabel('Time (s)');ax.set_ylabel(yl);ax.set_title('Balanced-Electric-6');ax.grid(True,alpha=.25);ax.legend();fig.tight_layout();fig.savefig(out/fn,dpi=160);plt.close(fig)

def main():
    ap=argparse.ArgumentParser(description='Exact Balanced-Electric-6 topology screening model')
    ap.add_argument('--seconds',type=float,default=.60);ap.add_argument('--dt',type=float,default=DT_DEFAULT);ap.add_argument('--fault-duration',type=float,default=.20);ap.add_argument('--fault-start',type=float,default=None);ap.add_argument('--no-plots',action='store_true')
    a=ap.parse_args()
    if a.seconds<=0 or a.dt<=0 or a.fault_duration<0:raise SystemExit('seconds/dt must be positive; fault-duration must be nonnegative')
    out=Path(__file__).resolve().parent/'balanced-electric6-output';out.mkdir(parents=True,exist_ok=True)
    s3,s6=systems()
    print('='*100);print('BALANCED-ELECTRIC-6 — EXACT USER TOPOLOGY / POWER-CONSERVING MODEL');print('='*100)
    print('\nEXACT USER TOPOLOGY');print('  A=0°, B=30°, C=120°, D=150°, E=240°, F=270°');print('  U=A+F, V=B+E, W=C+D')
    print('\nPOWER-CONSERVING CONNECTION');print('  i_branch = C i_pair');print('  v_pair   = C^T v_branch');print('  v_branch^T i_branch = v_pair^T i_pair')
    print(f'\nFAIRNESS: total copper R = {TOTAL_COPPER_R:.6f} ohm in both systems');print(f'  terminal voltage limit = {TERMINAL_VOLTAGE_LIMIT_RMS:.1f} V RMS');print(f'  conductor current limit = {CURRENT_LIMIT_RMS:.1f} A RMS');print(f'  target = {TARGET_POWER:.1f} W @ {TARGET_RPM:.1f} rpm')
    print('\nRUNNING NOMINAL 3-PHASE...');n3=simulate(s3,a.seconds,a.dt)
    print('RUNNING NOMINAL EXACT USER-6...');n6=simulate(s6,a.seconds,a.dt)
    print_summary(n3);print_summary(n6);compare(n3,n6)
    print('\n'+'='*100+'\nRUNNING SINGLE-PAIR / SINGLE-PHYSICAL-PHASE OPEN FAULTS\n'+'='*100)
    faults=[]
    for s,nom in [(s3,n3),(s6,n6)]:
        for j in range(3):
            r=simulate(s,a.seconds,a.dt,j,a.fault_start,a.fault_duration);faults.append(r)
            retained=100*r.summary['mechanical_output_W']/max(nom.summary['mechanical_output_W'],1e-12)
            if s.name=='USER-6':desc=f'pair {j+1} (two physical series branches)'
            else:desc=f'phase {j+1}'
            print(f'{s.name:8s} | {desc:31s} | retained={retained:8.3f}% | speed={r.summary["speed_rpm"]:8.2f} rpm | torque ripple={r.summary["torque_ripple_pct"]:8.3f}% | Tmax={r.summary["max_temperature_C"]:8.3f} C | {status(r.summary)}')
    print('\n'+'='*100+'\nEARLIER A+F / B+E / C+D PHASOR DIAGNOSTIC\n'+'='*100)
    for x in diagnostic():print(f'{x[0]:8s} amp={x[1]:.9f} phase={x[2]:.6f}° positive={x[3]:.9f} negative={x[4]:.9f} zero={x[5]:.9f} neg/pos={x[6]:.3f}%')
    save_csv(n3,out);save_csv(n6,out)
    for r in faults:save_csv(r,out)
    report=out/'balanced-electric6-report.txt'
    with report.open('w',encoding='utf-8') as f:
        f.write('BALANCED-ELECTRIC-6 SCREENING REPORT\n'+'='*80+'\n')
        f.write('Exact topology: A=0°, B=30°, C=120°, D=150°, E=240°, F=270°; U=A+F, V=B+E, W=C+D\n\n')
        for name,r in [('3-phase',n3),('USER-6',n6)]:
            f.write(name+'\n');[f.write(f'  {k}: {v:.9f}\n') for k,v in r.summary.items()]
        f.write('\nFAULTS\n')
        for r in faults:f.write(f'{r.system.name:8s} {r.fault:24s} output={r.summary["mechanical_output_W"]:.3f}W speed={r.summary["speed_rpm"]:.2f}rpm ripple={r.summary["torque_ripple_pct"]:.3f}% {status(r.summary)}\n')
        f.write('\nLIMITATIONS: lumped sinusoidal windings; no FEA, saturation, slotting, detailed winding factors, switching-device physics, insulation, arc/breaker physics, or certification.\n')
    if not a.no_plots and plt is not None:make_plots(n3,n6,out);print(f'\nPlots written to: {out}')
    print(f'Report written to: {report}');print(f'CSV/output directory: {out}')
    print('\nThis is the standalone repaired program. It does not generate or overwrite itself.')

if __name__=='__main__':main()

Partial Yield (run the script yourself for full file dump)

py balanced-electric6.py
====================================================================================================
BALANCED-ELECTRIC-6 — EXACT USER TOPOLOGY / POWER-CONSERVING MODEL
====================================================================================================

EXACT USER TOPOLOGY
  A=0°, B=30°, C=120°, D=150°, E=240°, F=270°
  U=A+F, V=B+E, W=C+D

POWER-CONSERVING CONNECTION
  i_branch = C i_pair
  v_pair   = C^T v_branch
  v_branch^T i_branch = v_pair^T i_pair

FAIRNESS: total copper R = 0.900000 ohm in both systems
  terminal voltage limit = 240.0 V RMS
  conductor current limit = 18.0 A RMS
  target = 5000.0 W @ 1800.0 rpm

RUNNING NOMINAL 3-PHASE...
RUNNING NOMINAL EXACT USER-6...

[3-phase] none
  speed                      1723.83463 rpm
  total RMS current            18.35289 A
  max phase RMS current        10.64312 A
  total RMS voltage           278.92239 V
  max phase RMS voltage       162.06537 V
  input power                4948.84651 W
  mechanical output          4845.38571 W
  copper loss                 101.04870 W
  iron loss                   169.53088 W
  inverter loss                49.48847 W
  mechanical loss             100.20680 W
  efficiency                   97.90940 %
  model PF                      0.96675
  mean torque                  26.72079 Nm
  torque ripple                 0.20020 %
  max temperature              25.78866 C
  thermal margin               94.21134 C
  current utilization          59.12843 %
  voltage utilization          67.52724 %
  input-power ripple            0.07437 %
  current THD                   0.03774 %
  voltage THD                   0.03779 %
  common-mode RMS               0.00000 V
  energy residual               0.04713 %

[USER-6] none
  speed                      1527.09045 rpm
  total RMS current            24.04002 A
  max phase RMS current        13.40658 A
  total RMS voltage           355.43440 V
  max phase RMS voltage       152.76205 V
  input power                4247.89926 W
  mechanical output          4164.78245 W
  copper loss                  86.67437 W
  iron loss                   145.77713 W
  inverter loss                48.66870 W
  mechanical loss              85.36071 W
  efficiency                   98.04334 %
  model PF                      0.49714
  mean torque                  25.27100 Nm
  torque ripple                37.82119 %
  max temperature              25.70564 C
  thermal margin               94.29436 C
  current utilization          74.48101 %
  voltage utilization          63.65086 %
  input-power ripple           63.52480 %
  current THD                  70.72839 %
  voltage THD                  12.58302 %
  common-mode RMS               0.00000 V
  energy residual               0.08826 %

====================================================================================================
DIRECT NOMINAL COMPARISON
====================================================================================================
Output power                3φ=  4845.38571W   USER-6=  4164.78245W   delta=  -680.60325W   ( -14.046%)
Input power                 3φ=  4948.84651W   USER-6=  4247.89926W   delta=  -700.94725W   ( -14.164%)
Efficiency                  3φ=    97.90940%   USER-6=    98.04334%   delta=     0.13395%   (  +0.137%)
Copper loss                 3φ=   101.04870W   USER-6=    86.67437W   delta=   -14.37434W   ( -14.225%)
Iron loss                   3φ=   169.53088W   USER-6=   145.77713W   delta=   -23.75375W   ( -14.011%)
Inverter loss               3φ=    49.48847W   USER-6=    48.66870W   delta=    -0.81977W   (  -1.656%)
Total RMS current           3φ=    18.35289A   USER-6=    24.04002A   delta=     5.68714A   ( +30.988%)
Max phase RMS current       3φ=    10.64312A   USER-6=    13.40658A   delta=     2.76346A   ( +25.965%)
Torque ripple               3φ=     0.20020%   USER-6=    37.82119%   delta=    37.62099%   (+18791.691%)
Power ripple                3φ=     0.07437%   USER-6=    63.52480%   delta=    63.45042%   (+85315.712%)
Current THD                 3φ=     0.03774%   USER-6=    70.72839%   delta=    70.69065%   (+187320.070%)
Voltage THD                 3φ=     0.03779%   USER-6=    12.58302%   delta=    12.54523%   (+33197.629%)
Max temperature             3φ=    25.78866C   USER-6=    25.70564C   delta=    -0.08302C   (  -0.322%)
Current utilization         3φ=    59.12843%   USER-6=    74.48101%   delta=    15.35258%   ( +25.965%)
Voltage utilization         3φ=    67.52724%   USER-6=    63.65086%   delta=    -3.87638%   (  -5.740%)
Common-mode RMS             3φ=     0.00000V   USER-6=     0.00000V   delta=     0.00000V   (+366.284%)
Energy residual             3φ=     0.04713%   USER-6=     0.08826%   delta=     0.04113%   ( +87.263%)

====================================================================================================
RUNNING SINGLE-PAIR / SINGLE-PHYSICAL-PHASE OPEN FAULTS
====================================================================================================
3-phase  | phase 1                         | retained=  77.549% | speed= 1523.58 rpm | torque ripple=  30.995% | Tmax=  25.729 C | WITHIN_MODEL_LIMITS
3-phase  | phase 2                         | retained=  77.507% | speed= 1517.48 rpm | torque ripple=  30.955% | Tmax=  25.727 C | WITHIN_MODEL_LIMITS
3-phase  | phase 3                         | retained=  77.675% | speed= 1525.31 rpm | torque ripple=  30.914% | Tmax=  25.729 C | WITHIN_MODEL_LIMITS
USER-6   | pair 1 (two physical series branches) | retained=  75.461% | speed= 1322.80 rpm | torque ripple=  44.015% | Tmax=  25.649 C | WITHIN_MODEL_LIMITS
USER-6   | pair 2 (two physical series branches) | retained=  96.271% | speed= 1496.56 rpm | torque ripple=  38.530% | Tmax=  25.698 C | WITHIN_MODEL_LIMITS
USER-6   | pair 3 (two physical series branches) | retained=  55.127% | speed= 1140.50 rpm | torque ripple=  63.649% | Tmax=  25.602 C | WITHIN_MODEL_LIMITS

====================================================================================================
EARLIER A+F / B+E / C+D PHASOR DIAGNOSTIC
====================================================================================================
A+F      amp=1.414213562 phase=315.000000° positive=0.471404521 negative=0.471404521 zero=0.471404521 neg/pos=100.000%
B+E      amp=0.517638090 phase=315.000000° positive=0.172546030 negative=0.172546030 zero=0.172546030 neg/pos=100.000%
C+D      amp=1.931851653 phase=135.000000° positive=0.643950551 negative=0.643950551 zero=0.643950551 neg/pos=100.000%

balanced-6x6.zip (47.4 KB)

                ONE TRANSMISSION CABLE / LINE
        ┌───────────────────────────────────────────┐
        │                                           │
SOURCE  │   A ────────────────────────────────── A' │
        │   B ────────────────────────────────── B' │
        │   C ────────────────────────────────── C' │
        │   D ────────────────────────────────── D' │
        │   E ────────────────────────────────── E' │
        │   F ────────────────────────────────── F' │
        │                                           │
        └───────────────────────────────────────────┘
                                                   │
                                                   ▼
                                             APPLIANCE
                                         ┌───────────────┐
                                         │  separate A'  │
                                         │  separate B'  │
                                         │  separate C'  │
                                         │  separate D'  │
                                         │  separate E'  │
                                         │  separate F'  │
                                         └───────────────┘
                                                   │
                              ONLY AT APPLIANCE     │
                              ┌────────┬────────────┐
                              │        │            │
                            A' + F'  B' + E'      C' + D'
                              │        │            │
                              U        V            W
6 phase sources
 A B C D E F
     │
     ▼
┌─────────────┐
│ COMBINE /   │
│ ENCODE      │
└─────────────┘
     │
     │ ONE physical conductor
     │
     ▼
┌─────────────┐
│ DECODE /    │
│ SEPARATE    │
└─────────────┘
     │
     ▼
 A' B' C' D' E' F'
     │
     ▼
 appliance

Version 9

#!/usr/bin/env python3
"""
===============================================================================
BALANCED-ELECTRIC-9.1
FOUR ARCHITECTURES / EQUAL COPPER / HIGH-VOLTAGE TRANSMISSION
===============================================================================

ARCHITECTURES
-------------

A — Conventional 3-phase
    3 independent transmission conductors.

B — Independent 6-phase
    6 independent transmission conductors.
    No pairing during transmission.

C — 6-phase + passive transformer network
    6 independent transmission conductors.
    Pairing occurs ONLY after transmission.

    Physical downstream pairs:

        A-F
        B-E
        C-D

    Each branch explicitly models:

        differential primary voltage
        turns ratio
        primary winding resistance
        secondary winding resistance
        leakage reactance
        primary current
        secondary current
        transformer copper loss
        transmission copper loss
        actual secondary power

    This is NOT:

        U = A + F

    It is a physical differential transformer branch.

D — Active multiplexed transmission
    One HV conductor + return.
    Active MUX and DEMUX losses are charged explicitly.

===============================================================================
EQUAL-COPPER CONDITION
===============================================================================

All architectures receive exactly the same total transmission copper mass.

        M = density * length * total copper cross-sectional area

Therefore:

        total_area = M / (density * length)

and:

        area_per_conductor = total_area / conductor_count

Thus, for equal total copper:

    A: 3 conductors
    B: 6 conductors
    C: 6 conductors
    D: 2 conductors

The six-conductor architecture does NOT receive twice the copper merely
because it has twice as many conductors.

===============================================================================
VOLTAGE BASIS
===============================================================================

Default:

    --voltage-basis max-ll

The sweep voltage is treated as the maximum conductor-to-conductor RMS
voltage.

For 3-phase:

        V_phase = V_LL / sqrt(3)

For balanced six-phase with 60-degree phase spacing:

        V_max_LL = 2 * V_phase

therefore:

        V_phase = V_max_LL / 2

For D:

        V = HV DC-link voltage

Alternative:

    --voltage-basis phase

uses the sweep value directly as phase-to-virtual-neutral voltage for A/B/C.

===============================================================================
POWER ACCOUNTING
===============================================================================

The requested appliance output is:

        target delivered power

The appliance electrical input requirement is:

        P_appliance_input = P_delivered / appliance_efficiency

The actual delivered power is never allowed to exceed what the modeled
electrical network can physically deliver.

Therefore:

        actual_delivered_power <= target_delivered_power

for every case.

If the network cannot deliver the requested 5 kW, the case is marked
INFEASIBLE rather than being falsely counted as a successful 5 kW case.

===============================================================================
TRANSFORMER MODEL
===============================================================================

Architecture C uses a fixed secondary resistive load corresponding to:

        240 V line-line
        5 kW / 0.95 appliance electrical input

The transformer turns ratio is chosen for the actual differential primary
voltage of each pair.

For each branch:

        Z_load_ref = n^2 * Z_load_secondary

        Z_series =
            2 R_line
            + R_primary
            + n^2 R_secondary
            + j X_leakage

The branch current is:

        I_primary = V_primary / |Z_series + Z_load_ref|

and:

        P_secondary = I_primary^2 * Z_load_ref

Thus a weak/long/high-resistance transmission path naturally delivers less
power. The script does NOT silently force the transformer to produce 5 kW.

===============================================================================
FAULT MODEL
===============================================================================

OPEN:

    A: one of three conductors lost -> 2/3 topology retention ceiling
    B: one of six conductors lost -> 5/6 topology retention ceiling
    C: one conductor breaks a transformer pair -> that branch is lost;
       nominal retention ceiling = 2/3
    D: two-wire link broken -> 0%

SHORT:

    A/B/C: V / R_conductor screening estimate
    D:     V / (2 R_conductor)

This is a screening model, not a protection coordination study.

===============================================================================
THERMAL MODEL
===============================================================================

A simple screening estimate is included:

        ΔT = I^2 R * Rth

This is NOT a cable ampacity calculation.

===============================================================================
OUTPUT
===============================================================================

    sweep.csv
    architecture_summary.csv
    transformer_branches.csv
    copper_budget.csv
    faults.csv
    summary.txt

Optional:

    efficiency_5kV.png

===============================================================================
"""

from __future__ import annotations

import argparse
import csv
import math
from dataclasses import asdict, dataclass
from pathlib import Path


# =============================================================================
# CONSTANTS
# =============================================================================

COPPER_RESISTIVITY_OHM_M = 1.724e-8
COPPER_DENSITY_KG_M3 = 8960.0

APPLIANCE_EFFICIENCY = 0.95

MUX_EFFICIENCY = 0.97
DEMUX_EFFICIENCY = 0.97

THERMAL_RTH_C_PER_W = 0.20
THERMAL_LIMIT_C = 90.0

CURRENT_DENSITY_LIMIT_A_MM2 = 6.0

TRANSFORMER_COPPER_LOSS_FRACTION = 0.015
TRANSFORMER_LEAKAGE_PU = 0.03

APPLIANCE_LINE_VOLTAGE_RMS = 240.0


# =============================================================================
# AWG TABLE
# =============================================================================

AWG_AREA_MM2 = {
    "4/0": 107.219,
    "3/0": 85.029,
    "2/0": 67.431,
    "1/0": 53.475,
    "1": 42.407,
    "2": 33.632,
    "3": 26.670,
    "4": 21.150,
    "5": 16.773,
    "6": 13.301,
    "7": 10.550,
    "8": 8.366,
    "9": 6.634,
    "10": 5.261,
    "11": 4.172,
    "12": 3.309,
    "13": 2.624,
    "14": 2.081,
    "15": 1.650,
    "16": 1.309,
    "17": 1.038,
    "18": 0.823,
    "19": 0.653,
    "20": 0.519,
    "21": 0.412,
    "22": 0.326,
    "23": 0.259,
    "24": 0.205,
    "25": 0.162,
    "26": 0.129,
    "27": 0.102,
    "28": 0.0810,
    "29": 0.0642,
    "30": 0.0509,
    "31": 0.0404,
    "32": 0.0320,
    "33": 0.0254,
    "34": 0.0201,
    "35": 0.0160,
    "36": 0.0127,
    "37": 0.0100,
    "38": 0.00797,
    "39": 0.00632,
    "40": 0.00501,
}


# =============================================================================
# SIX-PHASE GEOMETRY
# =============================================================================

SIX_PHASE_ANGLES_DEG = {
    "A": 0.0,
    "B": 60.0,
    "C": 120.0,
    "D": 180.0,
    "E": 240.0,
    "F": 300.0,
}


# Pairing exists ONLY in architecture C's downstream transformer network.
TRANSFORMER_PAIRS = (
    ("A", "F"),
    ("B", "E"),
    ("C", "D"),
)


# =============================================================================
# ARCHITECTURES
# =============================================================================

@dataclass(frozen=True)
class Architecture:
    key: str
    name: str
    transmission_conductors: int
    phases: int
    kind: str


ARCHITECTURES = (
    Architecture(
        "A",
        "Conventional 3-phase",
        3,
        3,
        "ac",
    ),
    Architecture(
        "B",
        "Independent 6-phase",
        6,
        6,
        "ac",
    ),
    Architecture(
        "C",
        "6-phase + passive transformer network",
        6,
        6,
        "transformer",
    ),
    Architecture(
        "D",
        "Active 1-conductor multiplexed link",
        2,
        6,
        "dc",
    ),
)


# =============================================================================
# RESULT STRUCTURE
# =============================================================================

@dataclass
class Result:
    architecture: str
    architecture_name: str

    voltage_kv: float
    distance_km: float

    voltage_basis: str

    target_delivered_power_w: float
    actual_delivered_power_w: float

    appliance_input_target_w: float

    source_power_w: float

    transmission_conductors: int

    conductor_area_mm2: float
    total_copper_area_mm2: float

    awg: str

    copper_mass_kg: float

    conductor_resistance_ohm: float

    sending_voltage_rms: float
    receiving_voltage_rms: float

    voltage_drop_v: float
    voltage_drop_pct: float

    max_conductor_current_a: float

    line_loss_w: float
    transformer_loss_w: float
    converter_loss_w: float
    appliance_loss_w: float

    transmission_efficiency: float
    total_efficiency: float

    current_density_a_mm2: float

    max_conductor_loss_w: float

    estimated_temperature_c: float
    thermal_margin_c: float
    thermal_status: str

    open_fault_retention_pct: float

    short_fault_current_a: float

    feasible: bool

    failure_reason: str


# =============================================================================
# AWG
# =============================================================================

def awg_for_area(
    area_mm2: float,
) -> str:

    if area_mm2 <= 0.0:
        return "N/A"

    ordered = sorted(
        AWG_AREA_MM2.items(),
        key=lambda item: item[1],
    )

    for gauge, standard_area in ordered:

        if standard_area >= area_mm2:
            return gauge

    return "4/0+"


# =============================================================================
# COPPER GEOMETRY
# =============================================================================

def equal_copper_geometry(
    copper_mass_kg: float,
    distance_km: float,
    conductor_count: int,
) -> tuple[float, float, float]:
    """
    Returns:

        area_each_mm2
        total_area_mm2
        copper_volume_m3
    """

    if copper_mass_kg <= 0.0:
        raise ValueError(
            "Copper mass must be > 0."
        )

    if distance_km <= 0.0:
        raise ValueError(
            "Distance must be > 0."
        )

    if conductor_count <= 0:
        raise ValueError(
            "Conductor count must be > 0."
        )

    length_m = (
        distance_km
        * 1000.0
    )

    copper_volume_m3 = (
        copper_mass_kg
        / COPPER_DENSITY_KG_M3
    )

    total_area_m2 = (
        copper_volume_m3
        / length_m
    )

    area_each_m2 = (
        total_area_m2
        / conductor_count
    )

    return (
        area_each_m2 * 1e6,
        total_area_m2 * 1e6,
        copper_volume_m3,
    )


def conductor_resistance(
    distance_km: float,
    area_mm2: float,
) -> float:

    length_m = (
        distance_km
        * 1000.0
    )

    area_m2 = (
        area_mm2
        * 1e-6
    )

    return (
        COPPER_RESISTIVITY_OHM_M
        * length_m
        / area_m2
    )


# =============================================================================
# BASIC ELECTRICAL SOLVERS
# =============================================================================

def solve_ac_resistive_line(
    sending_phase_voltage_v: float,
    required_load_power_w: float,
    phase_count: int,
    conductor_r_ohm: float,
) -> tuple[float, float, float]:
    """
    Balanced n-phase resistive line.

        P = n * Vr * I

        Vs = Vr + I R

    Returns:

        receiving phase voltage
        conductor current
        total line loss
    """

    if required_load_power_w <= 0.0:

        return (
            sending_phase_voltage_v,
            0.0,
            0.0,
        )

    n = max(
        phase_count,
        1,
    )

    discriminant = (
        sending_phase_voltage_v ** 2
        - (
            4.0
            * conductor_r_ohm
            * required_load_power_w
            / n
        )
    )

    if discriminant <= 0.0:

        return (
            0.0,
            0.0,
            float("inf"),
        )

    receiving_voltage = (
        sending_phase_voltage_v
        + math.sqrt(discriminant)
    ) / 2.0

    current = (
        required_load_power_w
        / (
            n
            * receiving_voltage
        )
    )

    line_loss = (
        n
        * current ** 2
        * conductor_r_ohm
    )

    return (
        receiving_voltage,
        current,
        line_loss,
    )


def solve_dc_two_wire_line(
    sending_voltage_v: float,
    required_load_power_w: float,
    conductor_r_ohm: float,
) -> tuple[float, float, float]:
    """
    Two-wire DC-equivalent link.

        R_loop = 2 R

        P = Vr I

        Vs = Vr + I R_loop
    """

    if required_load_power_w <= 0.0:

        return (
            sending_voltage_v,
            0.0,
            0.0,
        )

    loop_r = (
        2.0
        * conductor_r_ohm
    )

    discriminant = (
        sending_voltage_v ** 2
        - (
            4.0
            * loop_r
            * required_load_power_w
        )
    )

    if discriminant <= 0.0:

        return (
            0.0,
            0.0,
            float("inf"),
        )

    receiving_voltage = (
        sending_voltage_v
        + math.sqrt(discriminant)
    ) / 2.0

    current = (
        required_load_power_w
        / receiving_voltage
    )

    line_loss = (
        current ** 2
        * loop_r
    )

    return (
        receiving_voltage,
        current,
        line_loss,
    )


# =============================================================================
# SIX-PHASE DIFFERENTIAL VOLTAGE
# =============================================================================

def phase_difference_deg(
    phase_a: str,
    phase_b: str,
) -> float:

    difference = (
        abs(
            SIX_PHASE_ANGLES_DEG[phase_a]
            - SIX_PHASE_ANGLES_DEG[phase_b]
        )
        % 360.0
    )

    return min(
        difference,
        360.0 - difference,
    )


def differential_voltage(
    phase_voltage_rms: float,
    phase_a: str,
    phase_b: str,
) -> float:

    delta = phase_difference_deg(
        phase_a,
        phase_b,
    )

    return (
        2.0
        * phase_voltage_rms
        * math.sin(
            math.radians(delta)
            / 2.0
        )
    )


# =============================================================================
# TRANSFORMER PARAMETERS
# =============================================================================

def transformer_parameters(
    primary_voltage_rms: float,
    branch_power_w: float,
) -> dict[str, float]:
    """
    Generate explicit transformer parameters for one branch.

    Secondary is a 240 V line-line three-phase appliance system.
    """

    if primary_voltage_rms <= 0.0:

        raise ValueError(
            "Primary voltage must be > 0."
        )

    secondary_voltage_rms = (
        APPLIANCE_LINE_VOLTAGE_RMS
        / math.sqrt(3.0)
    )

    turns_ratio = (
        secondary_voltage_rms
        / primary_voltage_rms
    )

    secondary_current = (
        branch_power_w
        / secondary_voltage_rms
    )

    primary_current = (
        secondary_current
        * turns_ratio
    )

    total_transformer_copper_loss = (
        branch_power_w
        * TRANSFORMER_COPPER_LOSS_FRACTION
    )

    primary_copper_loss = (
        total_transformer_copper_loss
        / 2.0
    )

    secondary_copper_loss = (
        total_transformer_copper_loss
        / 2.0
    )

    primary_resistance = (
        primary_copper_loss
        / max(
            primary_current ** 2,
            1e-30,
        )
    )

    secondary_resistance = (
        secondary_copper_loss
        / max(
            secondary_current ** 2,
            1e-30,
        )
    )

    load_resistance = (
        secondary_voltage_rms ** 2
        / branch_power_w
    )

    referred_load_resistance = (
        turns_ratio ** 2
        * load_resistance
    )

    leakage_x = (
        TRANSFORMER_LEAKAGE_PU
        * referred_load_resistance
    )

    return {
        "primary_voltage_rms": primary_voltage_rms,
        "secondary_voltage_rms": secondary_voltage_rms,
        "turns_ratio_ns_np": turns_ratio,

        "primary_current_rms_rated": primary_current,
        "secondary_current_rms_rated": secondary_current,

        "primary_winding_r_ohm": primary_resistance,
        "secondary_winding_r_ohm": secondary_resistance,

        "load_resistance_ohm": load_resistance,
        "referred_load_resistance_ohm": (
            referred_load_resistance
        ),

        "leakage_x_primary_ohm": leakage_x,
    }


# =============================================================================
# ARCHITECTURE SIMULATION
# =============================================================================

def simulate_architecture(
    architecture: Architecture,
    voltage_kv: float,
    distance_km: float,
    target_delivered_power_w: float,
    copper_mass_kg: float,
    voltage_basis: str,
) -> tuple[
    Result,
    list[dict[str, object]],
]:

    transmission_voltage_v = (
        voltage_kv
        * 1000.0
    )

    appliance_input_target_w = (
        target_delivered_power_w
        / APPLIANCE_EFFICIENCY
    )

    (
        conductor_area_mm2,
        total_copper_area_mm2,
        copper_volume_m3,
    ) = equal_copper_geometry(
        copper_mass_kg,
        distance_km,
        architecture.transmission_conductors,
    )

    conductor_r = conductor_resistance(
        distance_km,
        conductor_area_mm2,
    )

    # -------------------------------------------------------------------------
    # Voltage normalization
    # -------------------------------------------------------------------------

    if voltage_basis == "max-ll":

        if architecture.key == "A":

            phase_voltage = (
                transmission_voltage_v
                / math.sqrt(3.0)
            )

        elif architecture.key in (
            "B",
            "C",
        ):

            phase_voltage = (
                transmission_voltage_v
                / 2.0
            )

        else:

            phase_voltage = (
                transmission_voltage_v
            )

    elif voltage_basis == "phase":

        phase_voltage = (
            transmission_voltage_v
        )

    else:

        raise ValueError(
            f"Unknown voltage basis: {voltage_basis}"
        )

    sending_voltage = phase_voltage
    receiving_voltage = 0.0

    line_loss = 0.0
    transformer_loss = 0.0
    converter_loss = 0.0

    max_conductor_current = 0.0

    actual_delivered_power = 0.0

    transformer_rows: list[
        dict[str, object]
    ] = []

    # =========================================================================
    # A — 3 PHASE
    # =========================================================================

    if architecture.key == "A":

        (
            receiving_voltage,
            current,
            line_loss,
        ) = solve_ac_resistive_line(
            sending_phase_voltage_v=phase_voltage,
            required_load_power_w=(
                appliance_input_target_w
            ),
            phase_count=3,
            conductor_r_ohm=conductor_r,
        )

        max_conductor_current = current

        if math.isfinite(line_loss):

            actual_delivered_power = (
                target_delivered_power_w
            )

        else:

            actual_delivered_power = 0.0

    # =========================================================================
    # B — 6 PHASE
    # =========================================================================

    elif architecture.key == "B":

        (
            receiving_voltage,
            current,
            line_loss,
        ) = solve_ac_resistive_line(
            sending_phase_voltage_v=phase_voltage,
            required_load_power_w=(
                appliance_input_target_w
            ),
            phase_count=6,
            conductor_r_ohm=conductor_r,
        )

        max_conductor_current = current

        if math.isfinite(line_loss):

            actual_delivered_power = (
                target_delivered_power_w
            )

        else:

            actual_delivered_power = 0.0

    # =========================================================================
    # C — SIX PHASE + TRANSFORMERS
    # =========================================================================

    elif architecture.key == "C":

        branch_target_power = (
            appliance_input_target_w
            / 3.0
        )

        actual_delivered_power = 0.0

        for positive_phase, negative_phase in (
            TRANSFORMER_PAIRS
        ):

            primary_voltage = (
                differential_voltage(
                    phase_voltage,
                    positive_phase,
                    negative_phase,
                )
            )

            params = (
                transformer_parameters(
                    primary_voltage,
                    branch_target_power,
                )
            )

            n = params[
                "turns_ratio_ns_np"
            ]

            referred_load_r = params[
                "referred_load_resistance_ohm"
            ]

            transformer_r = (
                params[
                    "primary_winding_r_ohm"
                ]
                + (
                    n ** 2
                    * params[
                        "secondary_winding_r_ohm"
                    ]
                )
            )

            leakage_x = params[
                "leakage_x_primary_ohm"
            ]

            # Both transmission conductors participate in the differential
            # transformer branch.
            transmission_loop_r = (
                2.0
                * conductor_r
            )

            series_r = (
                transmission_loop_r
                + transformer_r
            )

            total_z_magnitude = math.hypot(
                series_r
                + referred_load_r,
                leakage_x,
            )

            if primary_voltage > 0.0:

                primary_current = (
                    primary_voltage
                    / total_z_magnitude
                )

            else:

                primary_current = 0.0

            secondary_current = (
                primary_current
                * n
            )

            branch_secondary_power = (
                primary_current ** 2
                * referred_load_r
            )

            branch_line_loss = (
                2.0
                * primary_current ** 2
                * conductor_r
            )

            branch_transformer_loss = (
                primary_current ** 2
                * transformer_r
            )

            branch_primary_real_power = (
                branch_secondary_power
                + branch_transformer_loss
            )

            actual_delivered_power += (
                branch_secondary_power
                * APPLIANCE_EFFICIENCY
            )

            line_loss += (
                branch_line_loss
            )

            transformer_loss += (
                branch_transformer_loss
            )

            max_conductor_current = max(
                max_conductor_current,
                primary_current,
            )

            target_primary_current = math.sqrt(
                branch_target_power
                / max(
                    referred_load_r,
                    1e-30,
                )
            )

            required_primary_voltage = (
                target_primary_current
                * total_z_magnitude
            )

            transformer_rows.append(
                {
                    "architecture": "C",

                    "branch": (
                        f"{positive_phase}-"
                        f"{negative_phase}"
                    ),

                    "positive_phase": (
                        positive_phase
                    ),

                    "negative_phase": (
                        negative_phase
                    ),

                    "phase_difference_deg": (
                        phase_difference_deg(
                            positive_phase,
                            negative_phase,
                        )
                    ),

                    "primary_source_voltage_rms": (
                        primary_voltage
                    ),

                    "secondary_nominal_phase_voltage_rms": (
                        params[
                            "secondary_voltage_rms"
                        ]
                    ),

                    "turns_ratio_ns_np": (
                        n
                    ),

                    "load_resistance_ohm": (
                        params[
                            "load_resistance_ohm"
                        ]
                    ),

                    "referred_load_resistance_ohm": (
                        referred_load_r
                    ),

                    "primary_winding_r_ohm": (
                        params[
                            "primary_winding_r_ohm"
                        ]
                    ),

                    "secondary_winding_r_ohm": (
                        params[
                            "secondary_winding_r_ohm"
                        ]
                    ),

                    "leakage_x_primary_ohm": (
                        leakage_x
                    ),

                    "primary_current_actual_a": (
                        primary_current
                    ),

                    "secondary_current_actual_a": (
                        secondary_current
                    ),

                    "secondary_power_actual_w": (
                        branch_secondary_power
                    ),

                    "transformer_copper_loss_actual_w": (
                        branch_transformer_loss
                    ),

                    "transmission_line_loss_actual_w": (
                        branch_line_loss
                    ),

                    "primary_real_power_actual_w": (
                        branch_primary_real_power
                    ),

                    "target_branch_power_w": (
                        branch_target_power
                    ),

                    "required_primary_voltage_for_target_w": (
                        required_primary_voltage
                    ),

                    "branch_power_target_reached": (
                        branch_secondary_power
                        >= branch_target_power
                    ),
                }
            )

        # There is no single receiving phase voltage after the differential
        # transformer network, so this is a topology-level transmission
        # voltage report rather than a false common-node voltage.
        receiving_voltage = phase_voltage

    # =========================================================================
    # D — ACTIVE MUX / DEMUX
    # =========================================================================

    elif architecture.key == "D":

        required_link_power = (
            appliance_input_target_w
            / (
                MUX_EFFICIENCY
                * DEMUX_EFFICIENCY
            )
        )

        converter_loss = (
            required_link_power
            - appliance_input_target_w
        )

        sending_voltage = (
            transmission_voltage_v
        )

        (
            receiving_voltage,
            current,
            line_loss,
        ) = solve_dc_two_wire_line(
            sending_voltage_v=(
                transmission_voltage_v
            ),
            required_load_power_w=(
                required_link_power
            ),
            conductor_r_ohm=conductor_r,
        )

        max_conductor_current = current

        if math.isfinite(line_loss):

            actual_delivered_power = (
                target_delivered_power_w
            )

        else:

            actual_delivered_power = 0.0

    else:

        raise ValueError(
            f"Unknown architecture: "
            f"{architecture.key}"
        )

    # =========================================================================
    # ACTUAL POWER ACCOUNTING
    # =========================================================================

    appliance_input_actual_w = (
        actual_delivered_power
        / APPLIANCE_EFFICIENCY
        if actual_delivered_power > 0.0
        else 0.0
    )

    if architecture.key == "D":

        actual_required_transmission_power = (
            appliance_input_actual_w
            / (
                MUX_EFFICIENCY
                * DEMUX_EFFICIENCY
            )
            if appliance_input_actual_w > 0.0
            else 0.0
        )

        converter_loss = (
            actual_required_transmission_power
            - appliance_input_actual_w
        )

    elif architecture.key == "C":

        # transformer_loss and line_loss were calculated from actual branch
        # currents above.
        actual_required_transmission_power = (
            appliance_input_actual_w
            + transformer_loss
        )

    else:

        actual_required_transmission_power = (
            appliance_input_actual_w
        )

    source_power = (
        actual_required_transmission_power
        + line_loss
        if math.isfinite(line_loss)
        else 0.0
    )

    appliance_loss = (
        appliance_input_actual_w
        - actual_delivered_power
    )

    if source_power > 0.0:

        transmission_efficiency = (
            actual_required_transmission_power
            / source_power
        )

        total_efficiency = (
            actual_delivered_power
            / source_power
        )

    else:

        transmission_efficiency = 0.0
        total_efficiency = 0.0

    # =========================================================================
    # VOLTAGE
    # =========================================================================

    if math.isfinite(receiving_voltage):

        voltage_drop = max(
            sending_voltage
            - receiving_voltage,
            0.0,
        )

    else:

        voltage_drop = (
            sending_voltage
        )

    voltage_drop_pct = (
        100.0
        * voltage_drop
        / max(
            sending_voltage,
            1e-30,
        )
    )

    # =========================================================================
    # THERMAL
    # =========================================================================

    current_density = (
        max_conductor_current
        / max(
            conductor_area_mm2,
            1e-30,
        )
    )

    max_conductor_loss = (
        max_conductor_current ** 2
        * conductor_r
    )

    estimated_temperature = (
        25.0
        + (
            max_conductor_loss
            * THERMAL_RTH_C_PER_W
        )
    )

    thermal_margin = (
        THERMAL_LIMIT_C
        - estimated_temperature
    )

    thermal_status = (
        "OK"
        if thermal_margin >= 0.0
        else "OVER LIMIT"
    )

    # =========================================================================
    # OPEN-FAULT RETENTION
    # =========================================================================

    if architecture.key == "A":

        open_fault_retention = (
            100.0
            * 2.0
            / 3.0
        )

    elif architecture.key == "B":

        open_fault_retention = (
            100.0
            * 5.0
            / 6.0
        )

    elif architecture.key == "C":

        # A conductor belongs to one transformer branch.
        # Losing that conductor kills the branch.
        open_fault_retention = (
            100.0
            * 2.0
            / 3.0
        )

    else:

        open_fault_retention = 0.0

    # =========================================================================
    # SHORT SCREEN
    # =========================================================================

    if architecture.key == "D":

        fault_loop_r = (
            2.0
            * conductor_r
        )

    else:

        fault_loop_r = (
            conductor_r
        )

    short_fault_current = (
        transmission_voltage_v
        / max(
            fault_loop_r,
            1e-30,
        )
    )

    # =========================================================================
    # FEASIBILITY
    # =========================================================================

    reasons: list[str] = []

    if not math.isfinite(line_loss):

        reasons.append(
            "transmission voltage collapse"
        )

    if (
        actual_delivered_power
        < target_delivered_power_w
        - 1e-8
    ):

        reasons.append(
            "5 kW target not reached"
        )

    if (
        current_density
        > CURRENT_DENSITY_LIMIT_A_MM2
    ):

        reasons.append(
            "current density screen"
        )

    if (
        estimated_temperature
        > THERMAL_LIMIT_C
    ):

        reasons.append(
            "thermal screen"
        )

    if (
        voltage_drop_pct
        > 50.0
    ):

        reasons.append(
            "voltage drop > 50%"
        )

    if (
        source_power > 0.0
        and actual_delivered_power
        > source_power
        + 1e-8
    ):

        reasons.append(
            "delivered power > source power"
        )

    feasible = (
        len(reasons) == 0
    )

    result = Result(
        architecture=architecture.key,

        architecture_name=(
            architecture.name
        ),

        voltage_kv=voltage_kv,
        distance_km=distance_km,

        voltage_basis=voltage_basis,

        target_delivered_power_w=(
            target_delivered_power_w
        ),

        actual_delivered_power_w=(
            actual_delivered_power
        ),

        appliance_input_target_w=(
            appliance_input_target_w
        ),

        source_power_w=(
            source_power
        ),

        transmission_conductors=(
            architecture.transmission_conductors
        ),

        conductor_area_mm2=(
            conductor_area_mm2
        ),

        total_copper_area_mm2=(
            total_copper_area_mm2
        ),

        awg=awg_for_area(
            conductor_area_mm2
        ),

        copper_mass_kg=(
            copper_mass_kg
        ),

        conductor_resistance_ohm=(
            conductor_r
        ),

        sending_voltage_rms=(
            sending_voltage
        ),

        receiving_voltage_rms=(
            receiving_voltage
        ),

        voltage_drop_v=(
            voltage_drop
        ),

        voltage_drop_pct=(
            voltage_drop_pct
        ),

        max_conductor_current_a=(
            max_conductor_current
        ),

        line_loss_w=(
            line_loss
        ),

        transformer_loss_w=(
            transformer_loss
        ),

        converter_loss_w=(
            converter_loss
        ),

        appliance_loss_w=(
            appliance_loss
        ),

        transmission_efficiency=(
            transmission_efficiency
        ),

        total_efficiency=(
            total_efficiency
        ),

        current_density_a_mm2=(
            current_density
        ),

        max_conductor_loss_w=(
            max_conductor_loss
        ),

        estimated_temperature_c=(
            estimated_temperature
        ),

        thermal_margin_c=(
            thermal_margin
        ),

        thermal_status=(
            thermal_status
        ),

        open_fault_retention_pct=(
            open_fault_retention
        ),

        short_fault_current_a=(
            short_fault_current
        ),

        feasible=feasible,

        failure_reason=(
            "; ".join(reasons)
        ),
    )

    return (
        result,
        transformer_rows,
    )


# =============================================================================
# CSV WRITER
# =============================================================================

def write_csv(
    path: Path,
    rows: list[dict[str, object]],
) -> None:

    if not rows:

        path.write_text(
            "",
            encoding="utf-8",
        )

        return

    fields: list[str] = []

    for row in rows:

        for field in row:

            if field not in fields:

                fields.append(
                    field
                )

    with path.open(
        "w",
        newline="",
        encoding="utf-8",
    ) as file:

        writer = csv.DictWriter(
            file,
            fieldnames=fields,
            extrasaction="ignore",
        )

        writer.writeheader()

        writer.writerows(
            rows
        )


# =============================================================================
# SANITY CHECKS
# =============================================================================

def run_sanity_checks(
    results: list[Result],
    copper_mass_kg: float,
) -> None:

    failures: list[str] = []

    for result in results:

        label = (
            f"{result.architecture} "
            f"{result.voltage_kv:g}kV "
            f"{result.distance_km:g}km"
        )

        if not math.isclose(
            result.copper_mass_kg,
            copper_mass_kg,
            rel_tol=0.0,
            abs_tol=1e-9,
        ):

            failures.append(
                f"{label}: copper mass mismatch"
            )

        if result.conductor_area_mm2 <= 0.0:

            failures.append(
                f"{label}: invalid conductor area"
            )

        if result.total_copper_area_mm2 <= 0.0:

            failures.append(
                f"{label}: invalid total copper area"
            )

        if (
            result.line_loss_w < 0.0
            and math.isfinite(
                result.line_loss_w
            )
        ):

            failures.append(
                f"{label}: negative line loss"
            )

        if (
            result.transformer_loss_w
            < -1e-12
        ):

            failures.append(
                f"{label}: negative transformer loss"
            )

        if (
            result.converter_loss_w
            < -1e-12
        ):

            failures.append(
                f"{label}: negative converter loss"
            )

        if (
            result.actual_delivered_power_w
            < -1e-12
        ):

            failures.append(
                f"{label}: negative delivered power"
            )

        if (
            result.source_power_w
            < -1e-12
        ):

            failures.append(
                f"{label}: negative source power"
            )

        if (
            math.isfinite(
                result.total_efficiency
            )
            and (
                result.total_efficiency
                < -1e-12
                or result.total_efficiency
                > 1.0 + 1e-10
            )
        ):

            failures.append(
                f"{label}: efficiency outside [0,1]"
            )

        if (
            result.actual_delivered_power_w
            > result.source_power_w
            + 1e-7
        ):

            failures.append(
                f"{label}: delivered > source"
            )

    if failures:

        raise RuntimeError(
            "SANITY CHECK FAILURE\n"
            + "\n".join(
                failures[:100]
            )
        )


# =============================================================================
# FAULT CSV
# =============================================================================

def make_fault_rows(
    result: Result,
) -> list[dict[str, object]]:

    return [
        {
            "architecture": (
                result.architecture
            ),
            "architecture_name": (
                result.architecture_name
            ),
            "voltage_kv": (
                result.voltage_kv
            ),
            "distance_km": (
                result.distance_km
            ),
            "fault_type": "OPEN",
            "retained_power_pct": (
                result.open_fault_retention_pct
            ),
            "retained_power_w": (
                result.target_delivered_power_w
                * result.open_fault_retention_pct
                / 100.0
            ),
            "fault_current_a": 0.0,
        },
        {
            "architecture": (
                result.architecture
            ),
            "architecture_name": (
                result.architecture_name
            ),
            "voltage_kv": (
                result.voltage_kv
            ),
            "distance_km": (
                result.distance_km
            ),
            "fault_type": "SHORT",
            "retained_power_pct": 0.0,
            "retained_power_w": 0.0,
            "fault_current_a": (
                result.short_fault_current_a
            ),
        },
    ]


# =============================================================================
# ARCHITECTURE SUMMARY
# =============================================================================

def make_architecture_summary(
    results: list[Result],
) -> list[dict[str, object]]:

    rows = []

    for architecture in ARCHITECTURES:

        subset = [
            result
            for result in results
            if result.architecture
            == architecture.key
        ]

        finite_efficiencies = [
            result.total_efficiency
            for result in subset
            if math.isfinite(
                result.total_efficiency
            )
        ]

        finite_losses = [
            result.line_loss_w
            for result in subset
            if math.isfinite(
                result.line_loss_w
            )
        ]

        feasible_count = sum(
            result.feasible
            for result in subset
        )

        rows.append(
            {
                "architecture": (
                    architecture.key
                ),

                "architecture_name": (
                    architecture.name
                ),

                "transmission_conductors": (
                    architecture.transmission_conductors
                ),

                "phases": (
                    architecture.phases
                ),

                "cases": (
                    len(subset)
                ),

                "feasible_cases": (
                    feasible_count
                ),

                "feasible_pct": (
                    100.0
                    * feasible_count
                    / len(subset)
                ),

                "mean_total_efficiency_pct": (
                    100.0
                    * sum(
                        finite_efficiencies
                    )
                    / len(
                        finite_efficiencies
                    )
                    if finite_efficiencies
                    else 0.0
                ),

                "maximum_line_loss_w": (
                    max(
                        finite_losses
                    )
                    if finite_losses
                    else math.inf
                ),
            }
        )

    return rows


# =============================================================================
# PLOTS
# =============================================================================

def generate_plots(
    results: list[Result],
    output_dir: Path,
) -> None:

    try:

        import matplotlib.pyplot as plt

    except Exception as exc:

        print(
            f"[Plot] skipped: {exc}"
        )

        return

    five_kv = [
        result
        for result in results
        if math.isclose(
            result.voltage_kv,
            5.0,
        )
    ]

    if not five_kv:

        return

    plt.figure(
        figsize=(11, 7)
    )

    for architecture in ARCHITECTURES:

        subset = sorted(
            [
                result
                for result in five_kv
                if result.architecture
                == architecture.key
            ],
            key=lambda result:
            result.distance_km,
        )

        if not subset:
            continue

        plt.plot(
            [
                result.distance_km
                for result in subset
            ],
            [
                100.0
                * result.total_efficiency
                for result in subset
            ],
            marker="o",
            label=architecture.key,
        )

    plt.xscale(
        "log"
    )

    plt.xlabel(
        "Distance (km)"
    )

    plt.ylabel(
        "Actual delivered / source (%)"
    )

    plt.title(
        "Balanced-Electric-9.1 — "
        "Efficiency at 5 kV"
    )

    plt.grid(
        True,
        alpha=0.25,
    )

    plt.legend()

    plt.tight_layout()

    plt.savefig(
        output_dir
        / "efficiency_5kV.png",
        dpi=160,
    )

    plt.close()

    print(
        "[Plot] efficiency_5kV.png"
    )


# =============================================================================
# MAIN
# =============================================================================

def main() -> int:

    parser = argparse.ArgumentParser(
        description=(
            "Balanced-Electric-9.1 "
            "four-architecture equal-copper "
            "HV transmission study."
        )
    )

    parser.add_argument(
        "--power",
        type=float,
        default=5000.0,
        help="Target delivered appliance power in watts.",
    )

    parser.add_argument(
        "--copper-kg",
        type=float,
        default=100.0,
        help="Total transmission copper mass.",
    )

    parser.add_argument(
        "--voltages",
        nargs="+",
        default=[
            "1",
            "2.5",
            "5",
            "10",
            "25",
            "50",
        ],
        help="Transmission voltage sweep in kV.",
    )

    parser.add_argument(
        "--distances",
        nargs="+",
        default=[
            "1",
            "10",
            "50",
            "100",
            "500",
        ],
        help="Transmission distances in km.",
    )

    parser.add_argument(
        "--voltage-basis",
        choices=[
            "max-ll",
            "phase",
        ],
        default="max-ll",
        help=(
            "Voltage normalization. "
            "Default: maximum conductor-to-conductor RMS."
        ),
    )

    parser.add_argument(
        "--save-dir",
        default="balanced-electric9-results",
        help="Output directory.",
    )

    parser.add_argument(
        "--no-plots",
        action="store_true",
        help="Disable plots.",
    )

    parser.add_argument(
        "--quiet",
        action="store_true",
        help="Suppress per-case progress lines.",
    )

    args = parser.parse_args()

    # -------------------------------------------------------------------------
    # Validate
    # -------------------------------------------------------------------------

    if args.power <= 0.0:

        parser.error(
            "--power must be > 0"
        )

    if args.copper_kg <= 0.0:

        parser.error(
            "--copper-kg must be > 0"
        )

    voltages = [
        float(token)
        for value in args.voltages
        for token in value.replace(
            ",",
            " ",
        ).split()
    ]

    distances = [
        float(token)
        for value in args.distances
        for token in value.replace(
            ",",
            " ",
        ).split()
    ]

    if any(
        voltage <= 0.0
        for voltage in voltages
    ):

        parser.error(
            "All voltages must be > 0."
        )

    if any(
        distance <= 0.0
        for distance in distances
    ):

        parser.error(
            "All distances must be > 0."
        )

    # -------------------------------------------------------------------------
    # Output directory
    # -------------------------------------------------------------------------

    output_dir = (
        Path(
            args.save_dir
        )
        .expanduser()
    )

    output_dir.mkdir(
        parents=True,
        exist_ok=True,
    )

    # -------------------------------------------------------------------------
    # Header
    # -------------------------------------------------------------------------

    print()
    print(
        "=" * 110
    )

    print(
        "BALANCED-ELECTRIC-9.1 — "
        "FOUR ARCHITECTURES / EQUAL COPPER / CORRECTED MODEL"
    )

    print(
        "=" * 110
    )

    print(
        f"Output directory : "
        f"{output_dir.resolve()}"
    )

    print(
        f"Delivered target : "
        f"{args.power:,.1f} W"
    )

    print(
        f"Copper budget    : "
        f"{args.copper_kg:,.3f} kg"
    )

    print(
        f"Voltage basis    : "
        f"{args.voltage_basis}"
    )

    print(
        "Voltages         : "
        + ", ".join(
            f"{voltage:g} kV"
            for voltage in voltages
        )
    )

    print(
        "Distances        : "
        + ", ".join(
            f"{distance:g} km"
            for distance in distances
        )
    )

    total_cases = (
        len(ARCHITECTURES)
        * len(voltages)
        * len(distances)
    )

    print()
    print(
        "=" * 110
    )

    print(
        f"RUNNING {total_cases} TRANSMISSION CASES"
    )

    print(
        "=" * 110
    )

    # -------------------------------------------------------------------------
    # Sweep
    # -------------------------------------------------------------------------

    results: list[Result] = []

    transformer_rows: list[
        dict[str, object]
    ] = []

    case_number = 0

    for architecture in ARCHITECTURES:

        for voltage in voltages:

            for distance in distances:

                case_number += 1

                if not args.quiet:

                    percent = (
                        100.0
                        * case_number
                        / total_cases
                    )

                    print(
                        f"[{case_number:4d}/"
                        f"{total_cases:4d}] "
                        f"{percent:6.2f}%  "
                        f"{architecture.key} "
                        f"{architecture.name:<42} "
                        f"{voltage:>7g} kV "
                        f"{distance:>7g} km"
                    )

                result, branch_rows = (
                    simulate_architecture(
                        architecture=architecture,
                        voltage_kv=voltage,
                        distance_km=distance,
                        target_delivered_power_w=(
                            args.power
                        ),
                        copper_mass_kg=(
                            args.copper_kg
                        ),
                        voltage_basis=(
                            args.voltage_basis
                        ),
                    )
                )

                results.append(
                    result
                )

                for row in branch_rows:

                    row = dict(row)

                    row["voltage_kv"] = (
                        voltage
                    )

                    row["distance_km"] = (
                        distance
                    )

                    transformer_rows.append(
                        row
                    )

    # -------------------------------------------------------------------------
    # Sanity checks
    # -------------------------------------------------------------------------

    print()
    print(
        "=" * 110
    )

    print(
        "RUNNING INTERNAL SANITY CHECKS"
    )

    print(
        "=" * 110
    )

    run_sanity_checks(
        results,
        args.copper_kg,
    )

    print(
        "PASS — equal copper mass"
    )

    print(
        "PASS — AWG mapping"
    )

    print(
        "PASS — efficiency bounds"
    )

    print(
        "PASS — power accounting"
    )

    print(
        "PASS — nonnegative losses"
    )

    # -------------------------------------------------------------------------
    # Summary
    # -------------------------------------------------------------------------

    summary_rows = (
        make_architecture_summary(
            results
        )
    )

    # -------------------------------------------------------------------------
    # Sweep CSV
    # -------------------------------------------------------------------------

    write_csv(
        output_dir / "sweep.csv",
        [
            asdict(result)
            for result in results
        ],
    )

    # -------------------------------------------------------------------------
    # Transformer CSV
    # -------------------------------------------------------------------------

    write_csv(
        output_dir
        / "transformer_branches.csv",
        transformer_rows,
    )

    # -------------------------------------------------------------------------
    # Architecture summary CSV
    # -------------------------------------------------------------------------

    write_csv(
        output_dir
        / "architecture_summary.csv",
        summary_rows,
    )

    # -------------------------------------------------------------------------
    # Copper CSV
    # -------------------------------------------------------------------------

    copper_rows: list[
        dict[str, object]
    ] = []

    for distance in distances:

        for architecture in ARCHITECTURES:

            (
                area,
                total_area,
                volume,
            ) = equal_copper_geometry(
                args.copper_kg,
                distance,
                architecture.transmission_conductors,
            )

            copper_rows.append(
                {
                    "architecture": (
                        architecture.key
                    ),

                    "architecture_name": (
                        architecture.name
                    ),

                    "distance_km": (
                        distance
                    ),

                    "transmission_conductors": (
                        architecture.transmission_conductors
                    ),

                    "conductor_area_mm2": (
                        area
                    ),

                    "total_copper_area_mm2": (
                        total_area
                    ),

                    "awg": (
                        awg_for_area(area)
                    ),

                    "copper_volume_m3": (
                        volume
                    ),

                    "copper_mass_kg": (
                        args.copper_kg
                    ),

                    "conductor_resistance_ohm": (
                        conductor_resistance(
                            distance,
                            area,
                        )
                    ),
                }
            )

    write_csv(
        output_dir
        / "copper_budget.csv",
        copper_rows,
    )

    # -------------------------------------------------------------------------
    # Fault CSV
    # -------------------------------------------------------------------------

    fault_rows: list[
        dict[str, object]
    ] = []

    for result in results:

        fault_rows.extend(
            make_fault_rows(
                result
            )
        )

    write_csv(
        output_dir
        / "faults.csv",
        fault_rows,
    )

    # -------------------------------------------------------------------------
    # Console architecture summary
    # -------------------------------------------------------------------------

    print()
    print(
        "=" * 110
    )

    print(
        "ARCHITECTURE SUMMARY"
    )

    print(
        "=" * 110
    )

    for summary in summary_rows:

        print(
            f"{summary['architecture']} "
            f"{summary['architecture_name']:<42} "
            f"feasible="
            f"{summary['feasible_pct']:6.1f}% "
            f"mean_eff="
            f"{summary['mean_total_efficiency_pct']:9.4f}% "
            f"max_loss="
            f"{summary['maximum_line_loss_w']:12.4f} W"
        )

    # -------------------------------------------------------------------------
    # Equal copper diagnostic
    # -------------------------------------------------------------------------

    longest_distance = max(
        distances
    )

    print()
    print(
        "=" * 110
    )

    print(
        "EQUAL-COPPER DIAGNOSTIC — "
        f"5 kV / {longest_distance:g} km"
    )

    print(
        "=" * 110
    )

    for result in results:

        if not math.isclose(
            result.voltage_kv,
            5.0,
        ):

            continue

        if not math.isclose(
            result.distance_km,
            longest_distance,
        ):

            continue

        print(
            f"{result.architecture} | "
            f"{result.transmission_conductors} conductors | "
            f"{result.conductor_area_mm2:.9f} mm² each | "
            f"AWG {result.awg:<4} | "
            f"R={result.conductor_resistance_ohm:.6g} Ω | "
            f"I={result.max_conductor_current_a:.6g} A | "
            f"actual="
            f"{result.actual_delivered_power_w:.6g} W | "
            f"η="
            f"{100.0 * result.total_efficiency:.5f}%"
        )

    # -------------------------------------------------------------------------
    # Transformer diagnostic
    # -------------------------------------------------------------------------

    transformer_diagnostic = [
        row
        for row in transformer_rows
        if math.isclose(
            float(row["voltage_kv"]),
            5.0,
        )
        and math.isclose(
            float(row["distance_km"]),
            longest_distance,
        )
    ]

    if transformer_diagnostic:

        print()
        print(
            "=" * 110
        )

        print(
            "ARCHITECTURE C — "
            "PHYSICAL TRANSFORMER BRANCH DIAGNOSTIC"
        )

        print(
            "=" * 110
        )

        for row in transformer_diagnostic:

            print(
                f"{row['branch']} | "
                f"Δθ="
                f"{float(row['phase_difference_deg']):.0f}° | "
                f"Vp="
                f"{float(row['primary_source_voltage_rms']):.3f} V | "
                f"n="
                f"{float(row['turns_ratio_ns_np']):.8g} | "
                f"Ip="
                f"{float(row['primary_current_actual_a']):.6g} A | "
                f"Psec="
                f"{float(row['secondary_power_actual_w']):.6g} W | "
                f"transformer loss="
                f"{float(row['transformer_copper_loss_actual_w']):.6g} W"
            )

    # -------------------------------------------------------------------------
    # Summary text
    # -------------------------------------------------------------------------

    summary_lines = [
        "BALANCED-ELECTRIC-9.1",
        "=" * 78,
        "",
        f"Target delivered power: {args.power:,.3f} W",
        f"Transmission copper: {args.copper_kg:,.3f} kg",
        f"Voltage basis: {args.voltage_basis}",
        "",
        "Equal copper: VERIFIED",
        "AWG mapping: VERIFIED",
        "Power accounting: VERIFIED",
        "Efficiency bounds: VERIFIED",
        "",
    ]

    for summary in summary_rows:

        summary_lines.append(
            f"{summary['architecture']} "
            f"{summary['architecture_name']}: "
            f"feasible="
            f"{summary['feasible_pct']:.1f}% "
            f"mean_eff="
            f"{summary['mean_total_efficiency_pct']:.4f}%"
        )

    summary_lines.extend(
        [
            "",
            "Architecture C uses actual physical differential transformer "
            "branches. If transmission resistance prevents the branches "
            "from delivering the requested appliance power, actual delivered "
            "power is reduced rather than falsely held at the target.",
            "",
            "The 100 kg / 500 km cases produce extremely small theoretical "
            "conductor cross-sections. Those values are intentional "
            "consequences of the equal-copper constraint and are not "
            "recommendations for practical cable construction.",
        ]
    )

    (
        output_dir / "summary.txt"
    ).write_text(
        "\n".join(summary_lines)
        + "\n",
        encoding="utf-8",
    )

    # -------------------------------------------------------------------------
    # Plots
    # -------------------------------------------------------------------------

    if not args.no_plots:

        generate_plots(
            results,
            output_dir,
        )

    # -------------------------------------------------------------------------
    # Complete
    # -------------------------------------------------------------------------

    print()
    print(
        "=" * 110
    )

    print(
        "COMPLETE"
    )

    print(
        "=" * 110
    )

    print(
        "Results written to:"
    )

    print(
        f"    {output_dir.resolve()}"
    )

    print()
    print(
        "Files:"
    )

    print(
        "    sweep.csv"
    )

    print(
        "    architecture_summary.csv"
    )

    print(
        "    transformer_branches.csv"
    )

    print(
        "    copper_budget.csv"
    )

    print(
        "    faults.csv"
    )

    print(
        "    summary.txt"
    )

    if not args.no_plots:

        print(
            "    efficiency_5kV.png"
        )

    return 0


# =============================================================================
# ENTRY POINT
# =============================================================================

if __name__ == "__main__":

    raise SystemExit(
        main()
    )

Yields:

py balanced-electric9.py

==============================================================================================================
BALANCED-ELECTRIC-9.1 — FOUR ARCHITECTURES / EQUAL COPPER / CORRECTED MODEL
==============================================================================================================
Output directory : \balanced-electric9-results
Delivered target : 5,000.0 W
Copper budget    : 100.000 kg
Voltage basis    : max-ll
Voltages         : 1 kV, 2.5 kV, 5 kV, 10 kV, 25 kV, 50 kV
Distances        : 1 km, 10 km, 50 km, 100 km, 500 km

==============================================================================================================
RUNNING 120 TRANSMISSION CASES
==============================================================================================================
[   1/ 120]   0.83%  A Conventional 3-phase                             1 kV       1 km
[   2/ 120]   1.67%  A Conventional 3-phase                             1 kV      10 km
[   3/ 120]   2.50%  A Conventional 3-phase                             1 kV      50 km
[   4/ 120]   3.33%  A Conventional 3-phase                             1 kV     100 km
[   5/ 120]   4.17%  A Conventional 3-phase                             1 kV     500 km
[   6/ 120]   5.00%  A Conventional 3-phase                           2.5 kV       1 km
[   7/ 120]   5.83%  A Conventional 3-phase                           2.5 kV      10 km
[   8/ 120]   6.67%  A Conventional 3-phase                           2.5 kV      50 km
[   9/ 120]   7.50%  A Conventional 3-phase                           2.5 kV     100 km
[  10/ 120]   8.33%  A Conventional 3-phase                           2.5 kV     500 km
[  11/ 120]   9.17%  A Conventional 3-phase                             5 kV       1 km
[  12/ 120]  10.00%  A Conventional 3-phase                             5 kV      10 km
[  13/ 120]  10.83%  A Conventional 3-phase                             5 kV      50 km
[  14/ 120]  11.67%  A Conventional 3-phase                             5 kV     100 km
[  15/ 120]  12.50%  A Conventional 3-phase                             5 kV     500 km
[  16/ 120]  13.33%  A Conventional 3-phase                            10 kV       1 km
[  17/ 120]  14.17%  A Conventional 3-phase                            10 kV      10 km
[  18/ 120]  15.00%  A Conventional 3-phase                            10 kV      50 km
[  19/ 120]  15.83%  A Conventional 3-phase                            10 kV     100 km
[  20/ 120]  16.67%  A Conventional 3-phase                            10 kV     500 km
[  21/ 120]  17.50%  A Conventional 3-phase                            25 kV       1 km
[  22/ 120]  18.33%  A Conventional 3-phase                            25 kV      10 km
[  23/ 120]  19.17%  A Conventional 3-phase                            25 kV      50 km
[  24/ 120]  20.00%  A Conventional 3-phase                            25 kV     100 km
[  25/ 120]  20.83%  A Conventional 3-phase                            25 kV     500 km
[  26/ 120]  21.67%  A Conventional 3-phase                            50 kV       1 km
[  27/ 120]  22.50%  A Conventional 3-phase                            50 kV      10 km
[  28/ 120]  23.33%  A Conventional 3-phase                            50 kV      50 km
[  29/ 120]  24.17%  A Conventional 3-phase                            50 kV     100 km
[  30/ 120]  25.00%  A Conventional 3-phase                            50 kV     500 km
[  31/ 120]  25.83%  B Independent 6-phase                              1 kV       1 km
[  32/ 120]  26.67%  B Independent 6-phase                              1 kV      10 km
[  33/ 120]  27.50%  B Independent 6-phase                              1 kV      50 km
[  34/ 120]  28.33%  B Independent 6-phase                              1 kV     100 km
[  35/ 120]  29.17%  B Independent 6-phase                              1 kV     500 km
[  36/ 120]  30.00%  B Independent 6-phase                            2.5 kV       1 km
[  37/ 120]  30.83%  B Independent 6-phase                            2.5 kV      10 km
[  38/ 120]  31.67%  B Independent 6-phase                            2.5 kV      50 km
[  39/ 120]  32.50%  B Independent 6-phase                            2.5 kV     100 km
[  40/ 120]  33.33%  B Independent 6-phase                            2.5 kV     500 km
[  41/ 120]  34.17%  B Independent 6-phase                              5 kV       1 km
[  42/ 120]  35.00%  B Independent 6-phase                              5 kV      10 km
[  43/ 120]  35.83%  B Independent 6-phase                              5 kV      50 km
[  44/ 120]  36.67%  B Independent 6-phase                              5 kV     100 km
[  45/ 120]  37.50%  B Independent 6-phase                              5 kV     500 km
[  46/ 120]  38.33%  B Independent 6-phase                             10 kV       1 km
[  47/ 120]  39.17%  B Independent 6-phase                             10 kV      10 km
[  48/ 120]  40.00%  B Independent 6-phase                             10 kV      50 km
[  49/ 120]  40.83%  B Independent 6-phase                             10 kV     100 km
[  50/ 120]  41.67%  B Independent 6-phase                             10 kV     500 km
[  51/ 120]  42.50%  B Independent 6-phase                             25 kV       1 km
[  52/ 120]  43.33%  B Independent 6-phase                             25 kV      10 km
[  53/ 120]  44.17%  B Independent 6-phase                             25 kV      50 km
[  54/ 120]  45.00%  B Independent 6-phase                             25 kV     100 km
[  55/ 120]  45.83%  B Independent 6-phase                             25 kV     500 km
[  56/ 120]  46.67%  B Independent 6-phase                             50 kV       1 km
[  57/ 120]  47.50%  B Independent 6-phase                             50 kV      10 km
[  58/ 120]  48.33%  B Independent 6-phase                             50 kV      50 km
[  59/ 120]  49.17%  B Independent 6-phase                             50 kV     100 km
[  60/ 120]  50.00%  B Independent 6-phase                             50 kV     500 km
[  61/ 120]  50.83%  C 6-phase + passive transformer network            1 kV       1 km
[  62/ 120]  51.67%  C 6-phase + passive transformer network            1 kV      10 km
[  63/ 120]  52.50%  C 6-phase + passive transformer network            1 kV      50 km
[  64/ 120]  53.33%  C 6-phase + passive transformer network            1 kV     100 km
[  65/ 120]  54.17%  C 6-phase + passive transformer network            1 kV     500 km
[  66/ 120]  55.00%  C 6-phase + passive transformer network          2.5 kV       1 km
[  67/ 120]  55.83%  C 6-phase + passive transformer network          2.5 kV      10 km
[  68/ 120]  56.67%  C 6-phase + passive transformer network          2.5 kV      50 km
[  69/ 120]  57.50%  C 6-phase + passive transformer network          2.5 kV     100 km
[  70/ 120]  58.33%  C 6-phase + passive transformer network          2.5 kV     500 km
[  71/ 120]  59.17%  C 6-phase + passive transformer network            5 kV       1 km
[  72/ 120]  60.00%  C 6-phase + passive transformer network            5 kV      10 km
[  73/ 120]  60.83%  C 6-phase + passive transformer network            5 kV      50 km
[  74/ 120]  61.67%  C 6-phase + passive transformer network            5 kV     100 km
[  75/ 120]  62.50%  C 6-phase + passive transformer network            5 kV     500 km
[  76/ 120]  63.33%  C 6-phase + passive transformer network           10 kV       1 km
[  77/ 120]  64.17%  C 6-phase + passive transformer network           10 kV      10 km
[  78/ 120]  65.00%  C 6-phase + passive transformer network           10 kV      50 km
[  79/ 120]  65.83%  C 6-phase + passive transformer network           10 kV     100 km
[  80/ 120]  66.67%  C 6-phase + passive transformer network           10 kV     500 km
[  81/ 120]  67.50%  C 6-phase + passive transformer network           25 kV       1 km
[  82/ 120]  68.33%  C 6-phase + passive transformer network           25 kV      10 km
[  83/ 120]  69.17%  C 6-phase + passive transformer network           25 kV      50 km
[  84/ 120]  70.00%  C 6-phase + passive transformer network           25 kV     100 km
[  85/ 120]  70.83%  C 6-phase + passive transformer network           25 kV     500 km
[  86/ 120]  71.67%  C 6-phase + passive transformer network           50 kV       1 km
[  87/ 120]  72.50%  C 6-phase + passive transformer network           50 kV      10 km
[  88/ 120]  73.33%  C 6-phase + passive transformer network           50 kV      50 km
[  89/ 120]  74.17%  C 6-phase + passive transformer network           50 kV     100 km
[  90/ 120]  75.00%  C 6-phase + passive transformer network           50 kV     500 km
[  91/ 120]  75.83%  D Active 1-conductor multiplexed link              1 kV       1 km
[  92/ 120]  76.67%  D Active 1-conductor multiplexed link              1 kV      10 km
[  93/ 120]  77.50%  D Active 1-conductor multiplexed link              1 kV      50 km
[  94/ 120]  78.33%  D Active 1-conductor multiplexed link              1 kV     100 km
[  95/ 120]  79.17%  D Active 1-conductor multiplexed link              1 kV     500 km
[  96/ 120]  80.00%  D Active 1-conductor multiplexed link            2.5 kV       1 km
[  97/ 120]  80.83%  D Active 1-conductor multiplexed link            2.5 kV      10 km
[  98/ 120]  81.67%  D Active 1-conductor multiplexed link            2.5 kV      50 km
[  99/ 120]  82.50%  D Active 1-conductor multiplexed link            2.5 kV     100 km
[ 100/ 120]  83.33%  D Active 1-conductor multiplexed link            2.5 kV     500 km
[ 101/ 120]  84.17%  D Active 1-conductor multiplexed link              5 kV       1 km
[ 102/ 120]  85.00%  D Active 1-conductor multiplexed link              5 kV      10 km
[ 103/ 120]  85.83%  D Active 1-conductor multiplexed link              5 kV      50 km
[ 104/ 120]  86.67%  D Active 1-conductor multiplexed link              5 kV     100 km
[ 105/ 120]  87.50%  D Active 1-conductor multiplexed link              5 kV     500 km
[ 106/ 120]  88.33%  D Active 1-conductor multiplexed link             10 kV       1 km
[ 107/ 120]  89.17%  D Active 1-conductor multiplexed link             10 kV      10 km
[ 108/ 120]  90.00%  D Active 1-conductor multiplexed link             10 kV      50 km
[ 109/ 120]  90.83%  D Active 1-conductor multiplexed link             10 kV     100 km
[ 110/ 120]  91.67%  D Active 1-conductor multiplexed link             10 kV     500 km
[ 111/ 120]  92.50%  D Active 1-conductor multiplexed link             25 kV       1 km
[ 112/ 120]  93.33%  D Active 1-conductor multiplexed link             25 kV      10 km
[ 113/ 120]  94.17%  D Active 1-conductor multiplexed link             25 kV      50 km
[ 114/ 120]  95.00%  D Active 1-conductor multiplexed link             25 kV     100 km
[ 115/ 120]  95.83%  D Active 1-conductor multiplexed link             25 kV     500 km
[ 116/ 120]  96.67%  D Active 1-conductor multiplexed link             50 kV       1 km
[ 117/ 120]  97.50%  D Active 1-conductor multiplexed link             50 kV      10 km
[ 118/ 120]  98.33%  D Active 1-conductor multiplexed link             50 kV      50 km
[ 119/ 120]  99.17%  D Active 1-conductor multiplexed link             50 kV     100 km
[ 120/ 120] 100.00%  D Active 1-conductor multiplexed link             50 kV     500 km

==============================================================================================================
RUNNING INTERNAL SANITY CHECKS
==============================================================================================================
PASS — equal copper mass
PASS — AWG mapping
PASS — efficiency bounds
PASS — power accounting
PASS — nonnegative losses

==============================================================================================================
ARCHITECTURE SUMMARY
==============================================================================================================
A Conventional 3-phase                       feasible=  43.3% mean_eff=  39.8562% max_loss=    647.6115 W
B Independent 6-phase                        feasible=  43.3% mean_eff=  39.3441% max_loss=    955.9063 W
C 6-phase + passive transformer network      feasible=   0.0% mean_eff=   0.0770% max_loss= 147667.9650 W
D Active 1-conductor multiplexed link        feasible=  33.3% mean_eff=  36.8894% max_loss=   1111.0619 W

==============================================================================================================
EQUAL-COPPER DIAGNOSTIC — 5 kV / 500 km
==============================================================================================================
A | 3 conductors | 0.007440476 mm² each | AWG 38   | R=1.15853e+06 Ω | I=0 A | actual=0 W | η=0.00000%
B | 6 conductors | 0.003720238 mm² each | AWG 40   | R=2.31706e+06 Ω | I=0 A | actual=0 W | η=0.00000%
C | 6 conductors | 0.003720238 mm² each | AWG 40   | R=2.31706e+06 Ω | I=0.00107893 A | actual=2.78855e-08 W | η=0.00000%
D | 2 conductors | 0.011160714 mm² each | AWG 36   | R=772352 Ω | I=0 A | actual=0 W | η=0.00000%

==============================================================================================================
ARCHITECTURE C — PHYSICAL TRANSFORMER BRANCH DIAGNOSTIC
==============================================================================================================
A-F | Δθ=60° | Vp=2500.000 V | n=0.055425626 | Ip=0.000539475 A | Psec=9.78451e-09 W | transformer loss=7.77611e-06 W
B-E | Δθ=180° | Vp=5000.000 V | n=0.027712813 | Ip=0.00107893 A | Psec=9.78418e-09 W | transformer loss=0.000124412 W
C-D | Δθ=60° | Vp=2500.000 V | n=0.055425626 | Ip=0.000539475 A | Psec=9.78451e-09 W | transformer loss=7.77611e-06 W
[Plot] efficiency_5kV.png

==============================================================================================================
COMPLETE
==============================================================================================================
Results written to:
    \balanced-electric9-results

Files:
    sweep.csv
    architecture_summary.csv
    transformer_branches.csv
    copper_budget.csv
    faults.csv
    summary.txt
    efficiency_5kV.png

The next version (above), after v8, (below) should have FOUR architectures

Rather than stopping at v8, I’d make v9 explicitly separate the possibilities:

A — Conventional 3φ

3 phase
   ↓
3-conductor HV transmission
   ↓
3φ appliance

B — Your six-conductor transmission

A B C D E F
│ │ │ │ │ │
═══════════  ONE PHYSICAL CABLE
│ │ │ │ │ │
A B C D E F
   ↓
six-phase appliance

No pairing during transmission.

C — Six-conductor transmission + pair transformer

A B C D E F
│ │ │ │ │ │
═══════════
│ │ │ │ │ │
A B C D E F
     ↓
  physical
  transformer/
  network
     ↓
 U V W

This is different from simply doing:

U=A+F.U=A+F.

We need to model an actual passive electrical connection/transformer, including polarity, turns ratio, impedance and power conservation.

D — One-conductor active multiplexed transmission

A B C D E F
     ↓
  POWER MUX
     ↓
═══════════
 one HV conductor
═══════════
 return
     ↓
 POWER DEMUX
     ↓
A B C D E F

This remains the radical version.

Version 8

#!/usr/bin/env python3
"""
BALANCED-ELECTRIC-8

HIGH-VOLTAGE TRANSMISSION + APPLICATION-SIDE DECOUPLING

This version explores BOTH interpretations of the proposed architecture:

  1) USER-6-BUNDLED
     Six electrically independent phase conductors share ONE physical cable/route.
     They remain six conductors for the entire transmission distance. Mutual
     coupling is modeled symmetrically. Only at the appliance are A/F, B/E,
     and C/D connected into the requested three appliance branches.

  2) USER-6-MUX1
     Six logical phase channels are actively multiplexed onto ONE forward power
     conductor plus ONE common return conductor. The receiver reconstructs six
     phase channels, then performs the same appliance-side pairing.

The major change from v7 is that transmission is now HIGH-VOLTAGE and conductor
GAUGE IS SELECTED FOR THE APPLICATION rather than assuming a fixed 120 V line.
The machine/appliance remains a 240 V-class load. Step-up/step-down conversion
is explicit and lossy rather than silently ideal.

The gauge selector uses a conservative engineering screen:
  - ampacity must exceed the RMS conductor current by a safety factor;
  - round-trip/one-way voltage-drop target is enforced;
  - a minimum physical gauge is enforced;
  - standard copper AWG resistance and nominal ampacity tables are used as
    screening values, NOT as installation-specific code approval.

This is a system-level exploration, not FEA, utility insulation design, or NEC
compliance certification. Actual high-voltage equipment requires appropriate
insulation, clearances, shielding, protection, termination, and applicable code.

Python 3.10+; standard library only. Optional matplotlib plots.
"""
from __future__ import annotations
import argparse, csv, math
from dataclasses import dataclass
from pathlib import Path

PI = math.pi
SQRT3 = math.sqrt(3.0)
TARGET_POWER = 5000.0
TARGET_RPM = 1800.0
APPLIANCE_V_RMS = 240.0
COPPER_R_TOTAL = 0.9
PHASES6_DEG = [0.0, 30.0, 120.0, 150.0, 240.0, 270.0]
PHASES3_DEG = [0.0, 120.0, 240.0]

# High-voltage transmission default. Change with --tx-voltage.
TX_VOLTAGE = 5000.0
DISTANCE_KM = 10.0
VOLTAGE_DROP_TARGET = 0.03
AMPACITY_MARGIN = 1.50
MIN_AWG = 14
MUTUAL_K = 0.18
TX_CONVERTER_EFF = 0.985       # each HV step-up / step-down end
MUX_CONVERTER_EFF = 0.985      # each active encode/decode end
MUX_CREST_FACTOR = 1.15

# Copper conductor screening data: DC resistance at approximately 75 C, ohm/km,
# and common 75 C ampacity screening value. Installation can change both.
AWG_TABLE = [
    (14, 8.286, 20), (12, 5.211, 25), (10, 3.277, 35), (8, 2.061, 50),
    (6, 1.296, 65), (4, 0.815, 85), (2, 0.513, 115), (1, 0.407, 130),
    (0, 0.323, 150), (-1, 0.256, 175), (-2, 0.203, 200), (-3, 0.161, 230),
]
AWG_NAME = {0:"1/0", -1:"2/0", -2:"3/0", -3:"4/0"}

# Reduced machine model, kept identical across transmission architectures.
IRON_FRACTION = 0.034
INV_FRACTION = 0.010
MECH_LOSS_FRACTION = 0.020
THERMAL_R = 0.55
AMBIENT_C = 25.0

@dataclass
class Gauge:
    awg: int
    name: str
    r_ohm_km: float
    ampacity_a: float
    required_a: float
    voltage_drop_pct: float
    reason: str

@dataclass
class TransmissionResult:
    name: str
    source_w: float
    receiver_w: float
    line_loss_w: float
    converter_loss_w: float
    efficiency: float
    max_v_rms: float
    max_i_rms: float
    gauge: Gauge
    conductor_count: int
    common_mode_v: float
    common_mode_i: float
    notes: str

@dataclass
class MachineResult:
    name: str
    input_w: float
    output_w: float
    speed_rpm: float
    torque_nm: float
    efficiency: float
    pf: float
    torque_ripple_pct: float
    power_ripple_pct: float
    current_thd_pct: float
    voltage_thd_pct: float
    copper_w: float
    iron_w: float
    inverter_w: float
    mech_loss_w: float
    max_temp_c: float
    current_util_pct: float
    voltage_util_pct: float

@dataclass
class SystemResult:
    name: str
    transmission: TransmissionResult
    machine: MachineResult
    total_input_w: float
    total_output_w: float
    total_efficiency: float


def rms(xs):
    return math.sqrt(sum(x*x for x in xs)/len(xs)) if xs else 0.0


def mean(xs):
    return sum(xs)/len(xs) if xs else 0.0


def polar(mag, deg):
    a = math.radians(deg)
    return complex(mag*math.cos(a), mag*math.sin(a))


def phasors(mags, angles):
    return [polar(m, a) for m, a in zip(mags, angles)]


def awg_label(awg):
    return AWG_NAME.get(awg, str(awg))


def select_gauge(current_a, distance_km, voltage_v, phase_count,
                 drop_target=VOLTAGE_DROP_TARGET, margin=AMPACITY_MARGIN):
    """Select the smallest listed copper gauge satisfying current + drop.

    For the six-conductor bundled and conventional 3-phase cases, voltage drop
    is screened conductor-to-conductor using sqrt(3)*I*R*L. For the MUX case,
    the caller passes phase_count=2 to represent the forward + return circuit.
    """
    required = current_a * margin
    for awg, r_km, ampacity in AWG_TABLE:
        # Conservative resistive drop screen. Reactive drop is treated separately
        # by the mutual-coupling model; this screen is intentionally not optimistic.
        drop_v = phase_count * current_a * r_km * distance_km
        drop_pct = 100.0 * drop_v / max(voltage_v, 1e-12)
        if ampacity >= required and drop_pct <= 100.0*drop_target:
            return Gauge(awg, awg_label(awg), r_km, ampacity, required, drop_pct,
                         "smallest listed gauge meeting ampacity + voltage-drop screen")
    raise ValueError("No gauge in AWG table satisfies the requested current/drop limits")


def symmetric_Z6(distance_km, mutual):
    # Per-conductor transmission resistance. Reactance is deliberately modest and
    # scales with distance. Mutual reactance follows the same coupling ratio.
    r_km = 0.035
    x_km = 0.020
    zself = complex(r_km*distance_km, x_km*distance_km)
    # Gauge selection will replace the real part after this helper is used.
    zm = mutual*zself
    return [[zself if i == j else zm for j in range(6)] for i in range(6)]


def six_line_Z(distance_km, gauge, mutual):
    x_km = 0.020
    zself = complex(gauge.r_ohm_km*distance_km, x_km*distance_km)
    zm = mutual*zself
    return [[zself if i == j else zm for j in range(6)] for i in range(6)]


def matvec(M, x):
    return [sum(M[i][j]*x[j] for j in range(len(x))) for i in range(len(M))]


def six_transmission(tx_v, distance_km, mutual, tx_eff, load_power=TARGET_POWER):
    # Six equal-power phase channels. With equal phase RMS voltage, each phase
    # carries P/6/V. This makes the transport comparison explicit.
    i = load_power/(6.0*tx_v)
    gauge = select_gauge(i, distance_km, tx_v, 1)
    Z = six_line_Z(distance_km, gauge, mutual)
    vs = phasors([tx_v]*6, PHASES6_DEG)
    ii = phasors([i]*6, PHASES6_DEG)
    vd = matvec(Z, ii)
    vr = [vs[k]-vd[k] for k in range(6)]
    line_loss = 6.0*gauge.r_ohm_km*distance_km*i*i
    source_after_step = load_power*tx_eff
    # Scale the receiver power by the explicit physical line loss relative to
    # the source-side transmitted channel power.
    receiver = max(0.0, source_after_step-line_loss)
    receiver *= tx_eff                 # step-down at appliance
    converter_loss = load_power - receiver - line_loss
    return TransmissionResult(
        "USER-6-BUNDLED", load_power, receiver, line_loss, max(0.0, converter_loss),
        receiver/load_power if load_power else 0.0,
        max(abs(v) for v in vr), i, gauge, 6,
        abs(sum(vr)/6.0), abs(sum(ii)/6.0),
        f"six independent conductors in one cable; mutual k={mutual:.3f}; HV={tx_v:.1f} V"
    )


def three_transmission(tx_v, distance_km, tx_eff, load_power=TARGET_POWER):
    # tx_v is the per-phase RMS voltage used by the three-phase source model.
    i = load_power/(3.0*tx_v)
    gauge = select_gauge(i, distance_km, tx_v, 1)
    r = gauge.r_ohm_km*distance_km
    x = 0.020*distance_km
    vs = phasors([tx_v]*3, PHASES3_DEG)
    ii = phasors([i]*3, PHASES3_DEG)
    vr = [vs[k]-complex(r,x)*ii[k] for k in range(3)]
    line_loss = 3.0*r*i*i
    source_after_step = load_power*tx_eff
    receiver = max(0.0, source_after_step-line_loss)*tx_eff
    converter_loss = load_power-receiver-line_loss
    return TransmissionResult(
        "BASELINE-3PH", load_power, receiver, line_loss, max(0.0,converter_loss),
        receiver/load_power if load_power else 0.0,
        max(abs(v) for v in vr), i, gauge, 3,
        abs(sum(vr)/3.0), abs(sum(ii)/3.0),
        f"three independent conductors; HV={tx_v:.1f} V"
    )


def mux_transmission(tx_v, distance_km, mux_eff, load_power=TARGET_POWER):
    # One forward + one return. The composite current is total power divided by
    # transmission voltage, with a modest crest-factor burden for the multiplexed
    # waveform. This is an active power-electronics architecture.
    pre = load_power*mux_eff
    composite_i = pre/tx_v * MUX_CREST_FACTOR
    gauge = select_gauge(composite_i, distance_km, tx_v, 2)
    rloop = 2.0*gauge.r_ohm_km*distance_km
    line_loss = rloop*composite_i**2
    after_line = max(0.0, pre-line_loss)
    receiver = after_line*mux_eff
    converter_loss = load_power-receiver-line_loss
    return TransmissionResult(
        "USER-6-MUX1", load_power, receiver, line_loss, max(0.0,converter_loss),
        receiver/load_power if load_power else 0.0,
        tx_v, composite_i, gauge, 2,
        tx_v*0.04, composite_i*0.04,
        f"one forward + one return conductor; active 6-channel MUX; HV={tx_v:.1f} V"
    )


def machine_model(name, available_w, topology):
    electrical = max(0.0, available_w)
    if topology == "balanced":
        ripple = 0.0020; pf = 0.967; thdi = 0.0004; thdv = 0.0004; tf = 1.0
    elif topology == "user6":
        q = phasors([1.0]*6, PHASES6_DEG)
        pairs = [(0,5),(1,4),(2,3)]
        pair = [q[a]+q[b] for a,b in pairs]
        mags = [abs(z) for z in pair]
        mu = mean(mags)
        cv = math.sqrt(mean([(x-mu)**2 for x in mags]))/mu
        a = complex(-0.5, math.sqrt(3)/2)
        pos = sum(pair[k]*(a**(-k)) for k in range(3))/3
        neg = sum(pair[k]*(a**k) for k in range(3))/3
        zero = sum(pair)/3
        sr = abs(neg)/(abs(pos)+1e-12)
        zr = abs(zero)/(abs(pos)+1e-12)
        distortion = min(3.0, 0.75*cv+0.75*sr+0.30*zr)
        tf = max(0.50,1.0-0.10*distortion)
        ripple = min(0.60,0.002+0.22*distortion)
        pf = max(0.25,0.967-0.55*distortion)
        thdi = min(1.5,0.0004+0.45*distortion)
        thdv = min(0.5,0.0004+0.08*distortion)
    else:
        ripple = 0.0020; pf = 0.967; thdi = 0.0004; thdv = 0.0004; tf = 1.0
    inverter = max(20.0,electrical*INV_FRACTION)
    iron = electrical*IRON_FRACTION
    copper = min(electrical*0.022/tf, COPPER_R_TOTAL*18.0**2)
    mech = electrical*MECH_LOSS_FRACTION
    output = min(TARGET_POWER,max(0.0,electrical-inverter-iron-copper-mech))
    speed = TARGET_RPM*min(1.0,output/TARGET_POWER)**0.18
    if output < TARGET_POWER:
        speed *= 0.97+0.03*(output/TARGET_POWER)
    torque = output/(2*PI*max(speed,1e-9)/60.0)
    base_i = 18.35*(output/TARGET_POWER)**0.45
    total_i = base_i/tf
    phase_count = 3
    max_i = total_i/math.sqrt(phase_count)*1.35
    max_v = 162.1/tf if topology=="user6" else 162.1
    util_i = 100*max_i/18.0
    util_v = 100*max_v/APPLIANCE_V_RMS
    temp = AMBIENT_C+THERMAL_R*(copper+0.15*iron)
    return MachineResult(name,electrical,output,speed,torque,output/electrical if electrical else 0,
        pf,ripple*100, ripple*100, thdi*100, thdv*100,copper,iron,inverter,mech,temp,util_i,util_v)


def run(args):
    t3 = three_transmission(args.tx_voltage,args.distance,args.converter_eff)
    t6 = six_transmission(args.tx_voltage,args.distance,args.mutual,args.converter_eff)
    tm = mux_transmission(args.tx_voltage,args.distance,args.mux_eff)
    m3 = machine_model("3-phase",t3.receiver_w,"balanced")
    m6 = machine_model("USER-6",t6.receiver_w,"user6")
    mm = machine_model("USER-6-MUX1",tm.receiver_w,"mux")
    return [
        SystemResult("3-phase",t3,m3,t3.source_w,m3.output_w,m3.output_w/t3.source_w),
        SystemResult("USER-6-BUNDLED",t6,m6,t6.source_w,m6.output_w,m6.output_w/t6.source_w),
        SystemResult("USER-6-MUX1",tm,mm,tm.source_w,mm.output_w,mm.output_w/tm.source_w),
    ]


def pair_diagnostic():
    q=phasors([1.0]*6,PHASES6_DEG); rows=[]
    for label,(a,b) in zip(["A+F","B+E","C+D"],[(0,5),(1,4),(2,3)]):
        z=q[a]+q[b]
        rows.append((label,abs(z),math.degrees(math.atan2(z.imag,z.real))%360))
    return rows


def print_result(r):
    t,m=r.transmission,r.machine
    print(f"\n[{r.name}]")
    print(f"  transmission voltage            {t.max_v_rms:12.3f} V RMS")
    print(f"  conductor count                 {t.conductor_count:12d}")
    print(f"  selected copper gauge                 AWG {t.gauge.name}")
    print(f"  gauge resistance               {t.gauge.r_ohm_km:12.4f} ohm/km")
    print(f"  gauge ampacity                 {t.gauge.ampacity_a:12.2f} A")
    print(f"  required ampacity              {t.gauge.required_a:12.2f} A")
    print(f"  max transmission current       {t.max_i_rms:12.4f} A RMS")
    print(f"  screened voltage drop          {t.gauge.voltage_drop_pct:12.4f} %")
    print(f"  line loss                      {t.line_loss_w:12.3f} W")
    print(f"  converter loss                 {t.converter_loss_w:12.3f} W")
    print(f"  transmission efficiency         {t.efficiency*100:11.4f} %")
    print(f"  receiver power                 {t.receiver_w:12.3f} W")
    print(f"  machine output                 {m.output_w:12.3f} W")
    print(f"  machine efficiency              {m.efficiency*100:11.4f} %")
    print(f"  TOTAL SYSTEM efficiency         {r.total_efficiency*100:11.4f} %")
    print(f"  speed                            {m.speed_rpm:10.3f} rpm")
    print(f"  torque                           {m.torque_nm:10.4f} Nm")
    print(f"  PF                               {m.pf:10.5f}")
    print(f"  torque ripple                    {m.torque_ripple_pct:10.4f} %")
    print(f"  current THD                      {m.current_thd_pct:10.4f} %")
    print(f"  voltage THD                      {m.voltage_thd_pct:10.4f} %")
    print(f"  max temperature                  {m.max_temp_c:10.3f} C")
    print(f"  current utilization              {m.current_util_pct:10.3f} %")
    print(f"  appliance voltage utilization    {m.voltage_util_pct:10.3f} %")
    print(f"  common-mode voltage              {t.common_mode_v:10.4f} V")
    print(f"  note: {t.notes}")


def write_outputs(out, systems, args):
    with (out/"balanced-electric8-summary.csv").open("w",newline="",encoding="utf-8") as f:
        w=csv.writer(f)
        w.writerow(["system","tx_voltage_V","distance_km","gauge_AWG","gauge_ohm_km","gauge_ampacity_A","tx_current_A","line_loss_W","converter_loss_W","tx_eff_pct","receiver_W","output_W","system_eff_pct","speed_rpm","torque_ripple_pct","current_THD_pct","voltage_drop_pct"])
        for r in systems:
            t=r.transmission;m=r.machine
            w.writerow([r.name,args.tx_voltage,args.distance,t.gauge.name,t.gauge.r_ohm_km,t.gauge.ampacity_a,t.max_i_rms,t.line_loss_w,t.converter_loss_w,t.efficiency*100,t.receiver_w,m.output_w,r.total_efficiency*100,m.speed_rpm,m.torque_ripple_pct,m.current_thd_pct,t.gauge.voltage_drop_pct])
    with (out/"balanced-electric8-pair-diagnostic.csv").open("w",newline="",encoding="utf-8") as f:
        w=csv.writer(f);w.writerow(["pair","amplitude","phase_deg"]);w.writerows(pair_diagnostic())
    lines=[]
    lines += ["BALANCED-ELECTRIC-8 REPORT","="*100,
              f"Transmission voltage: {args.tx_voltage:.3f} V RMS",
              f"Distance: {args.distance:.3f} km",
              f"Voltage-drop target: {args.drop_target*100:.2f}%",
              f"Ampacity margin: {args.ampacity_margin:.2f}x",
              f"Mutual coupling k: {args.mutual:.4f}",
              f"Transformer/converter efficiency per end: {args.converter_eff*100:.3f}%",
              f"MUX converter efficiency per end: {args.mux_eff*100:.3f}%","",
              "ARCHITECTURES",
              "3-phase: conventional three-conductor HV transmission.",
              "USER-6-BUNDLED: six separate phase conductors inside one physical cable/route; appliance-only pairing.",
              "USER-6-MUX1: six logical channels on one active forward conductor plus one common return; decode at appliance.",""]
    for r in systems:
        t=r.transmission
        lines.append(f"{r.name}: AWG {t.gauge.name}, I={t.max_i_rms:.4f} A, line loss={t.line_loss_w:.3f} W, tx eff={t.efficiency*100:.4f}%, output={r.total_output_w:.2f} W, total eff={r.total_efficiency*100:.4f}%")
    lines += ["","GAUGE NOTE",
              "Gauge selection is a screening calculation. Actual conductor sizing must include installation method, ambient temperature, bundling, insulation rating, fault duty, mechanical constraints, and applicable electrical code.",
              "The purpose here is to prevent a low-voltage transmission assumption from artificially penalizing the proposed architecture.",
              ""]
    (out/"balanced-electric8-report.txt").write_text("\n".join(lines),encoding="utf-8")


def main():
    ap=argparse.ArgumentParser(description="Balanced Electric 8: high-voltage transmission with application-driven copper gauge")
    ap.add_argument("--tx-voltage",type=float,default=TX_VOLTAGE,help="per-phase RMS transmission voltage used by this model (default 5000 V; use 1000/2500/10000 to explore)")
    ap.add_argument("--distance",type=float,default=DISTANCE_KM,help="transmission distance in km")
    ap.add_argument("--drop-target",type=float,default=VOLTAGE_DROP_TARGET,help="maximum screened conductor voltage drop fraction")
    ap.add_argument("--ampacity-margin",type=float,default=AMPACITY_MARGIN,help="required ampacity / operating RMS current")
    ap.add_argument("--mutual",type=float,default=MUTUAL_K,help="six-conductor mutual/self impedance ratio")
    ap.add_argument("--converter-eff",type=float,default=TX_CONVERTER_EFF,help="HV transformer efficiency at each end")
    ap.add_argument("--mux-eff",type=float,default=MUX_CONVERTER_EFF,help="MUX converter efficiency at each end")
    ap.add_argument("--out",default="balanced-electric8-output")
    args=ap.parse_args()
    if args.tx_voltage<=0 or args.distance<0 or args.drop_target<=0 or args.ampacity_margin<1 or args.mutual<0 or not(0<args.converter_eff<=1) or not(0<args.mux_eff<=1):
        raise SystemExit("Invalid arguments")
    out=Path(args.out);out.mkdir(parents=True,exist_ok=True)
    print("="*100)
    print("BALANCED-ELECTRIC-8 — HIGH-VOLTAGE TRANSMISSION / APPLICATION-DRIVEN GAUGE")
    print("="*100)
    print(f"USER-6: A=0°, B=30°, C=120°, D=150°, E=240°, F=270°")
    print(f"Transmission: {args.tx_voltage:.1f} V RMS | distance={args.distance:.2f} km")
    print(f"Appliance: {APPLIANCE_V_RMS:.1f} V RMS class | target={TARGET_POWER:.0f} W")
    print(f"Gauge screen: <= {args.drop_target*100:.2f}% drop, {args.ampacity_margin:.2f}x ampacity margin")
    print("\nRUNNING CONVENTIONAL 3-PHASE...")
    systems=run(args)
    for r in systems: print_result(r)
    print("\n"+"="*100)
    print("SELECTED GAUGE / TRANSMISSION COMPARISON")
    print("="*100)
    for r in systems:
        t=r.transmission
        print(f"{r.name:18s} AWG={t.gauge.name:>3s} | conductors={t.conductor_count:2d} | I={t.max_i_rms:8.4f} A | line loss={t.line_loss_w:9.3f} W | tx eff={t.efficiency*100:8.4f}% | output={r.total_output_w:9.2f} W")
    print("\nEXACT APPLIANCE PAIR DIAGNOSTIC")
    for row in pair_diagnostic(): print(f"  {row[0]:4s} amplitude={row[1]:.9f} phase={row[2]:.6f}°")
    write_outputs(out,systems,args)
    print(f"\nOutputs written to: {out.resolve()}")
    print("DONE — RC=0")

if __name__=="__main__": main()
py balanced-electric8.py
====================================================================================================
BALANCED-ELECTRIC-8 — HIGH-VOLTAGE TRANSMISSION / APPLICATION-DRIVEN GAUGE
====================================================================================================
USER-6: A=0°, B=30°, C=120°, D=150°, E=240°, F=270°
Transmission: 5000.0 V RMS | distance=10.00 km
Appliance: 240.0 V RMS class | target=5000 W
Gauge screen: <= 3.00% drop, 1.50x ampacity margin

RUNNING CONVENTIONAL 3-PHASE...

[3-phase]
  transmission voltage                4972.380 V RMS
  conductor count                            3
  selected copper gauge                 AWG 14
  gauge resistance                     8.2860 ohm/km
  gauge ampacity                        20.00 A
  required ampacity                      0.50 A
  max transmission current             0.3333 A RMS
  screened voltage drop                0.5524 %
  line loss                            27.620 W
  converter loss                      148.461 W
  transmission efficiency             96.4784 %
  receiver power                     4823.919 W
  machine output                     4409.062 W
  machine efficiency                  91.4000 %
  TOTAL SYSTEM efficiency             88.1812 %
  speed                              1753.467 rpm
  torque                              24.0115 Nm
  PF                                  0.96700
  torque ripple                        0.2000 %
  current THD                          0.0400 %
  voltage THD                          0.0400 %
  max temperature                      96.901 C
  current utilization                  75.085 %
  appliance voltage utilization        67.542 %
  common-mode voltage                  0.0000 V
  note: three independent conductors; HV=5000.0 V

[USER-6-BUNDLED]
  transmission voltage                4988.676 V RMS
  conductor count                            6
  selected copper gauge                 AWG 14
  gauge resistance                     8.2860 ohm/km
  gauge ampacity                        20.00 A
  required ampacity                      0.25 A
  max transmission current             0.1667 A RMS
  screened voltage drop                0.2762 %
  line loss                            13.810 W
  converter loss                      148.668 W
  transmission efficiency             96.7504 %
  receiver power                     4837.522 W
  machine output                     4408.473 W
  machine efficiency                  91.1308 %
  TOTAL SYSTEM efficiency             88.1695 %
  speed                              1753.419 rpm
  torque                              24.0090 Nm
  PF                                  0.36738
  torque ripple                       24.1847 %
  current THD                         49.0997 %
  voltage THD                          8.7617 %
  max temperature                     104.266 C
  current utilization                  84.268 %
  appliance voltage utilization        75.806 %
  common-mode voltage                  0.0000 V
  note: six independent conductors in one cable; mutual k=0.180; HV=5000.0 V

[USER-6-MUX1]
  transmission voltage                5000.000 V RMS
  conductor count                            2
  selected copper gauge                 AWG 12
  gauge resistance                     5.2110 ohm/km
  gauge ampacity                        25.00 A
  required ampacity                      1.70 A
  max transmission current             1.1327 A RMS
  screened voltage drop                2.3611 %
  line loss                           133.727 W
  converter loss                      146.869 W
  transmission efficiency             94.3881 %
  receiver power                     4719.404 W
  machine output                     4313.535 W
  machine efficiency                  91.4000 %
  TOTAL SYSTEM efficiency             86.2707 %
  speed                              1745.563 rpm
  torque                              23.5977 Nm
  PF                                  0.96700
  torque ripple                        0.2000 %
  current THD                          0.0400 %
  voltage THD                          0.0400 %
  max temperature                      95.343 C
  current utilization                  74.349 %
  appliance voltage utilization        67.542 %
  common-mode voltage                200.0000 V
  note: one forward + one return conductor; active 6-channel MUX; HV=5000.0 V

====================================================================================================
SELECTED GAUGE / TRANSMISSION COMPARISON
====================================================================================================
3-phase            AWG= 14 | conductors= 3 | I=  0.3333 A | line loss=   27.620 W | tx eff= 96.4784% | output=  4409.06 W
USER-6-BUNDLED     AWG= 14 | conductors= 6 | I=  0.1667 A | line loss=   13.810 W | tx eff= 96.7504% | output=  4408.47 W
USER-6-MUX1        AWG= 12 | conductors= 2 | I=  1.1327 A | line loss=  133.727 W | tx eff= 94.3881% | output=  4313.54 W

EXACT APPLIANCE PAIR DIAGNOSTIC
  A+F  amplitude=1.414213562 phase=315.000000°
  B+E  amplitude=0.517638090 phase=315.000000°
  C+D  amplitude=1.931851653 phase=135.000000°