Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Solver Comparison

This notebook runs all three GDM-Flow solvers on the same system and produces interactive Plotly plots to compare results.

from gdm.distribution import DistributionSystem

system = DistributionSystem.from_json("../examples/models/p5r.json")

Run All Solvers

from gdm_flow import (
    optimize_ac_power_flow_from_components,
    solve_dc_opf_from_components,
    solve_lindistflow,
)

ac = optimize_ac_power_flow_from_components(
    system,
    include_loads=True,
    include_solar=True,
    include_capacitor=True,
)

dc = solve_dc_opf_from_components(
    system,
    include_solar_generators=True,
    include_battery_generators=True,
    include_loads=True,
)

ldf = solve_lindistflow(system)

print(f"AC OPF:      success={ac.success}")
print(f"DC OPF:      success={dc.success}")
print(f"LinDistFlow: success={ldf.success}")

Source Power Comparison

import numpy as np

# AC source power
src_bus = system.get_source_bus().name
idx_map = ac.ybus_result.index_to_label
v = ac.voltage
ybus = ac.ybus_result.ybus
s = v * np.conj(ybus @ v)
src_idx = [i for i, lbl in enumerate(idx_map) if lbl[0] == src_bus]
ac_source_p = sum(s[i].real for i in src_idx)

# DC source power (grid generators only)
dc_source_p = sum(
    v for k, v in dc.generator_dispatch_w.items() if k.startswith("grid:")
)

# LDF source power
ldf_source_p = sum(ldf.p_net_w.values())

print(f"AC  source P: {ac_source_p:.1f} W")
print(f"DC  source P: {dc_source_p:.1f} W")
print(f"LDF source P: {ldf_source_p:.1f} W")
print(
    f"Max disagreement: {max(ac_source_p, dc_source_p, ldf_source_p) - min(ac_source_p, dc_source_p, ldf_source_p):.1f} W"
)

Voltage Profile Plot

import plotly.graph_objects as go

# Extract bus voltages from AC
ac_buses = {}
for i, (bus, phase) in enumerate(idx_map):
    key = f"{bus}/{phase}"
    ac_buses[key] = abs(v[i])

# Extract bus voltages from LDF
ldf_buses = {f"{bus}/{phase}": volt for (bus, phase), volt in ldf.voltage_v.items()}

# Common bus list
common = sorted(set(ac_buses) & set(ldf_buses))

fig = go.Figure()
fig.add_trace(go.Bar(name="AC OPF", x=common, y=[ac_buses[b] for b in common]))
fig.add_trace(go.Bar(name="LinDistFlow", x=common, y=[ldf_buses[b] for b in common]))
fig.update_layout(
    title="Voltage Profile Comparison (AC vs LinDistFlow)",
    xaxis_title="Bus / Phase",
    yaxis_title="Voltage (V)",
    barmode="group",
    height=500,
)
fig.show()

DC OPF Dispatch Breakdown

# Categorize generators
categories = {"Grid": 0, "Solar": 0, "Battery": 0}
for name, dispatch in dc.generator_dispatch_w.items():
    if name.startswith("grid:"):
        categories["Grid"] += dispatch
    elif name.startswith("solar:"):
        categories["Solar"] += dispatch
    elif name.startswith("battery:"):
        categories["Battery"] += dispatch

fig = go.Figure(
    go.Pie(
        labels=list(categories.keys()),
        values=list(categories.values()),
        hole=0.4,
        textinfo="label+value",
        texttemplate="%{label}<br>%{value:.0f} W",
    )
)
fig.update_layout(title="DC OPF Generation Dispatch", height=400)
fig.show()