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.

DC OPF

The DC OPF solver performs economic dispatch by minimizing total generation cost subject to linearized power balance constraints. It determines how much power each generator (solar, battery, grid import) should produce.

Formulation

The DC approximation assumes:

  1. Voltage magnitudes are close to nominal (Vm1V_m \approx 1 p.u.)

  2. Angle differences are small (sin(θiθj)θiθj\sin(\theta_i - \theta_j) \approx \theta_i - \theta_j)

  3. Reactive power and line losses are neglected

Objective Function

minPg,θk(ck(2)Pg,k2+ck(1)Pg,k+ck(0))+ϵiθi2\min_{P_g, \theta} \sum_k \left( c_k^{(2)} P_{g,k}^2 + c_k^{(1)} P_{g,k} + c_k^{(0)} \right) + \epsilon \sum_i \theta_i^2

where ck(2),ck(1),ck(0)c_k^{(2)}, c_k^{(1)}, c_k^{(0)} are quadratic, linear, and constant cost coefficients for generator kk, and ϵ\epsilon is a small angle regularization term.

Power Balance Constraints

Pg,iPd,i=jBijθjislackP_{g,i} - P_{d,i} = \sum_j B_{ij} \cdot \theta_j \quad \forall i \notin \text{slack}

where B=Im(Ybus)Vnom(i)Vnom(j)B = -\text{Im}(Y_{bus}) \cdot V_{nom}^{(i)} \cdot V_{nom}^{(j)} is the voltage-scaled susceptance matrix.

Split-Phase Node Exclusion

DC OPF is a positive-sequence single-phase approximation. The small-angle assumption (sinΔθΔθ\sin\Delta\theta \approx \Delta\theta) breaks down across center-tapped transformers where S2 windings operate at 180° from the primary — sin(π)π\sin(\pi) \approx \pi is a 300% error. The B-matrix would also be extremely ill-conditioned (1B:1 ratio) from the 60:1 turns ratios.

All split-phase (S1/S2) and neutral (N) nodes are excluded from the DC OPF formulation. Only A/B/C phase nodes participate in power balance constraints. Demand at excluded nodes is zeroed out (a small approximation since split-phase load is a small fraction of total).

Slack Bus Handling

Slack nodes serve as the angle reference (θslack=0\theta_{slack} = 0). When a slack node has an explicit generator, its power balance constraint is included so the grid import carries a cost. Without this, the optimizer would inject unlimited free power through the slack bus.

Generator Model

Each DCGenerator specifies:

from gdm_flow import DCGenerator

gen = DCGenerator(
    name="solar_pv_1",
    node=("load_bus", "A"),     # (bus_name, phase)
    p_min_w=0.0,                # Minimum output (W)
    p_max_w=5000.0,             # Maximum output (W)
    cost_quadratic=0.0,         # $/W²
    cost_linear=5.0,            # $/W
    cost_constant=0.0,          # $ fixed
)

Usage

from gdm_flow import solve_dc_opf_from_components

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

print(f"Success: {result.success}")
for name, dispatch in result.generator_dispatch_w.items():
    print(f"  {name}: {dispatch:.1f} W")

The convenience wrapper automatically:

Low-Level

from gdm_flow import DCGenerator, solve_dc_opf

generators = [
    DCGenerator("solar", ("bus_2", "A"), 0.0, 5000.0, cost_linear=5.0),
    DCGenerator("grid",  ("bus_1", "A"), 0.0, 1e6,    cost_linear=50.0),
]

demand = {("bus_2", "A"): 3000.0}  # 3 kW load

result = solve_dc_opf(
    system,
    generators=generators,
    demand_w=demand,
    slack_label=[("bus_1", "A")],
)

Result Object

DCOPFResult contains:

FieldTypeDescription
successboolWhether the optimizer converged
messagestrSolver status message
objectivefloatMinimized total generation cost
iterationsintNumber of optimizer iterations
generator_dispatch_wdict[str, float]Optimal dispatch per generator (W)
theta_raddict[BusPhaseLabel, float]Voltage angles (radians)
nodal_balance_wdict[BusPhaseLabel, float]Net nodal power balance (W)
slack_injection_wfloatTotal slack bus injection (W)
ybus_resultYBusResultY-bus and node indexing

Default Cost Structure

Generator Typecost_linearInterpretation
Solar PV5.0Cheap — dispatch first
Battery15.0Medium — dispatch second
Grid Import50.0Expensive — dispatch last

This cost hierarchy ensures the optimizer maximizes DER utilization before importing from the grid.

Limitations and Assumptions