Skip to main content

A package for solving unbalanced three-phase distribution system optimal power flow problems.

Project description

DistOPF

DistOPF provides an open-source, multi-phase, unbalanced, optimal power flow (OPF) tool for distribution systems to aid students and researchers. The tool aids users by providing:

  • Unbalanced multi-Phase OPF model generators usable with common Python solver packages such as CVXPY and SciPy;

  • A platform for creating and benchmarking new algorithms on a set of standard test systems;

  • An OpenDSS model importer allowing users to import power system models directly from the OpenDSS model format;

  • Model validation with OpenDSS;

  • Functions for visualizing results.

The tool is composed of four major parts, 1) model input system, 2) optimization model formulation, 3) OPF solver interface, and 4) solution output and visualization. Models are described using a set of CSV files that are read in as Pandas DataFrames. Buses are described in one CSV having columns for loads, base units, and voltage limits. Lines, switches, and transformers are described in a CSV having columns for each term in the upper diagonal impedance matrix. Regulators, capacitor banks, and generators each have their own CSV. To aid in model creation and validation, models can also be created using OpenDSS and converted to the tabular format. The tool provides classes and functions to make it easy to formulate and solve the power system for new users while being flexible for advanced users to create new models and algorithms. The tool has been used to solve a variety of problems including, conservation voltage reduction, power loss minimization, and generation curtailment minimization, where either generator real or reactive power injections are controlled.

Installation

pip install

pip install distopf

Developer Installation

To install the latest version from github:

  1. From the directory you want to keep your DistOPF files, run:

git clone https://github.com/nathantgray/distopf.git

  1. Create or activate the python environment you want to use.
  2. From the directory where the DistOPF package is stored, run:

pip install -e .

This installs your local DistOPF package the python environment you activated. The -e option enables editable mode, which allows you to directly edit the package and see changes immediately reflected in your environment without reinstalling.

Getting Started

Using provided cases:

Unconstrained Power Flow

import distopf as opf
case = opf.create_case(opf.CASES_DIR / "csv" / "ieee123")
result = case.run_pf()
result.plot_network().show(renderer="browser")

DER Curtailment Minimization

import distopf as opf
case = opf.create_case(opf.CASES_DIR / "csv" / "ieee123_30der")
result = case.run_opf(objective="curtail_min", control_variable="P", v_max=1.05, v_min=0.95, gen_mult=10)
result.plot_network().show(renderer="browser")

Optimization Wrappers

DistOPF supports multiple optimization wrappers for solving OPF problems:

Pyomo Wrapper — LinDistFlow (default)

The default Pyomo wrapper uses the LinDistFlow model (model_type="lindist").

  • Model: Linear approximation of power flow equations
  • Solver: Pyomo with linear solvers
  • Speed: Fast, suitable for real-time applications
  • Accuracy: Good for systems with small voltage deviations
import distopf as opf
case = opf.create_case(opf.CASES_DIR / "csv" / "ieee123_30der")
result = case.run_opf(wrapper="pyomo", objective="loss")  # model_type="lindist" is the default

Pyomo Wrapper — BranchFlow

The BranchFlow model type uses nonlinear power flow equations with IPOPT or MINLP solvers for higher accuracy. Use model_type="branchflow".

  • Model: Nonlinear power flow equations (exact)
  • Solver: IPOPT (continuous) or MINLP using Gurobi if installed (discrete controls)
  • Speed: Slower than linear, but more accurate
  • Accuracy: Nonlinear power flow representation

Continuous Optimization (IPOPT)

For continuous optimization without discrete controls:

import distopf as opf
case = opf.create_case(opf.CASES_DIR / "csv" / "ieee123_30der")
result = case.run_opf(
    model_type="branchflow",
    objective="loss",
    solver="ipopt",
)

Discrete Controls (MINLP)

Enable regulator tap optimization and capacitor switching with MINLP solvers:

import distopf as opf
case = opf.create_case(opf.CASES_DIR / "csv" / "ieee123")
result = case.run_opf(
    wrapper="pyomo",
    model_type="branchflow",
    objective="loss",
    control_regulators=True,      # Enable regulator tap control
    control_capacitors=True,       # Enable capacitor switching
    initialize="fbs",              # Recommended for discrete controls
    solver="gurobi",               # MINLP compatible solver 
)

Matrix BESS Wrapper (Multi-Period with Batteries)

The matrix_bess wrapper supports multi-period (time-series) optimization with battery energy storage. The shorthand wrapper="matrix_bess" also works as an alias.

import distopf as opf
case = opf.create_case(opf.CASES_DIR / "csv" / "ieee123_30der_batt")
result = case.run_opf(wrapper="matrix_bess", objective="loss")

Wrapper Comparison

Feature Matrix/Matrix BESS Pyomo
Model Type LinDistFlow (linear) LinDistFlow or Non-Linear BranchFlow
Formulation Matrix-based Algebraic equations
Solver API Scipy or CVXPY Pyomo
Prefered Solver HiGHs or Clarabel IPOPT, Gurobi, Knitro

Solver Requirements

  • IPOPT: Install via conda install -c conda-forge ipopt. On Ubuntu, apt-get install coinor-libipopt-dev only installs headers and shared libraries; it does not provide the ipopt executable that Pyomo's SolverFactory("ipopt") expects.

Result Fields

PowerFlowResult uses descriptive field names. Short aliases are also accepted for backward compatibility.

Field Name Alias
active_power_flows p_flows
reactive_power_flows q_flows
active_power_generation p_gens
reactive_power_generation q_gens
active_power_loads p_loads
reactive_power_loads q_loads
capacitor_reactive_power q_caps
battery_active_power p_bats
voltage_magnitudes voltages

Dual Variables

When running with duals=True, dual variables are accessible on the result object:

result = case.run_opf(wrapper="pyomo", objective="loss", duals=True)
result.dual_power_balance_p
result.dual_power_balance_q
result.dual_voltage_drop
result.dual_voltage_limits

Using a custom model.

Create CSVs formatted as shown below and store them in a single folder. The csv names must match exactly as shown. Column order is not important.

-your_model_directory
   -branch_data.csv
   -bus_data.csv
   -gen_data.csv
   -cap_data.csv
   -reg_data.csv
   -bat_data.csv
import distopf as opf
case = opf.create_case(
    data_path="path/to/your_model_directory",
)

Or load them as dataframes

import distopf as opf
import pandas as pd
branch_data = pd.read_csv("path/to/your_model_directory/branch_data.csv", header=0)
bus_data = pd.read_csv("path/to/your_model_directory/bus_data.csv", header=0)
gen_data = pd.read_csv("path/to/your_model_directory/gen_data.csv", header=0)
cap_data = pd.read_csv("path/to/your_model_directory/cap_data.csv", header=0)
reg_data = pd.read_csv("path/to/your_model_directory/reg_data.csv", header=0)
bat_data = pd.read_csv("path/to/your_model_directory/bat_data.csv", header=0)
schedules = pd.read_csv("path/to/your_model_directory/schedules.csv", header=0)  # Optional for multi-period cases
case = opf.Case(
    branch_data=branch_data,
    bus_data=bus_data,
    gen_data=gen_data,
    cap_data=cap_data,
    reg_data=reg_data,
    bat_data=bat_data,
    schedules=schedules
)

branch_data.csv

  • fb: From bus id number
  • tb: To bus id number
  • r: resistance in p.u.
  • x: reactance in p.u.
  • type: overhead_line, switch, transformer, etc.
  • name: other name of line
  • status: (for switches) OPEN or CLOSED
  • s_base: base VA
  • v_ln_base: base line-to-neutral voltage
  • z_base: base impedance

bus_data.csv

  • id: unique id for each bus (integer starting at 1)
  • name: bus name
  • pl_a, ql_a, pl_b, ql_b, pl_c, ql_c: active and reactive loads p.u.
  • bus_type: SWING or PQ. SWING bus is voltage source
  • v_a, v_b, v_c: voltage magnitude p.u. (input parameter for SWING bus. Other not used as input)
  • v_ln_base: base line-to-neutral voltage (V)
  • s_base: base power (VA)
  • v_min, v_max: voltage magnitude limits (p.u.)
  • cvr_p, cvr_q: conservation voltage reduction parameters; alternative to ZIP model for voltage dependant loads. (set to 0 for no voltage dependence)
  • phases: phases at bus (e.g. "abc", "a", "ab", etc.)

gen_data.csv

  • id: bus id
  • name: generator name
  • pa, pb, pc: active power output (p.u.)
  • qa, qb, qc: reactive power output (p.u.)
  • s_base: base power (VA)
  • sa_max, sb_max, sc_max: rated maximum apparent power output (VA)
  • phases: generator phases (abc string) (this IS implemented)
  • qa_max, qb_max, qc_max: (not implemented) maximum reactive power output (p.u.)
  • qa_min, qb_min, qc_min: (not implemented) minimum reactive power output (p.u.)

cap_data.csv

  • id: bus id
  • name: capacitor name
  • q_a, q_b, q_c: nominal reactive power (p.u.)
  • phases: capacitor phases (abc string)

reg_data.csv

  • fb: From bus id number
  • tb: To bus id number
  • name: regulator name
  • tap_a, tap_b, tap_c: tap position (p.u.) -16 to +16; 0 is no tap change

Case Options

    Use `Case` or `create_case()` to create and run a case. Call `run_pf()` or `run_opf()` to obtain a `PowerFlowResult`.
    Parameters
    ----------
    config: str or dict
        Path to JSON config or dictionary with parameters to create case. Alternative to using **config.
    data_path: str or pathlib.Path
        Path to the directory containing the data CSVs or path to OpenDSS model. Will also accept names of
        cases include in package e.g. "ieee13", "ieee34", "ieee123".
    output_dir: str or pathlib.Path
        (default: "output") Directory to save results.
    branch_data : pd.DataFrame or None
        DataFrame containing branch data (r and x values, limits). Overrides data found from data_path.
    bus_data : pd.DataFrame or None
        DataFrame containing bus data (loads, voltages, limits). Overrides data found from data_path.
    gen_data : pd.DataFrame or None
        DataFrame containing generator/DER data. Overrides data found from data_path.
    cap_data : pd.DataFrame or None
        DataFrame containing capacitor data. Overrides data found from data_path.
    reg_data : pd.DataFrame or None
        DataFrame containing regulator data. Overrides data found from data_path.
    v_swing: Number or size-3 array
        Override substation voltage. Scalar or 3-phase array. Per Unit.
    v_min: Number
        Override all voltage minimum limits. Per Unit.
    v_max: Number
        Override all voltage maximum limits. Per Unit.
    gen_mult: Number
        Scale all generator outputs and ratings. Per Unit.
    load_mult:
        Scale all loads.
    cvr_p:
        CVR factor for voltage dependent loads. Active power component. cvr_p = (dP/P)/(dV/V)
        To convert from ZIP parameters, kz, ki, kp: cvr_p = 2kz + 1ki
    cvr_q:
        CVR factor for voltage dependent loads. Reactive power component.cvr_q = (dQ/Q)/(dV/V)
        To convert from ZIP parameters, kz, ki, kp: cvr_q = 2kz + 1ki
    control_variable: str
        Control variable for optimization. Options (case-insensitive):
            None: Power flow only with no optimization. `objective_function` options will be ignored.
            "P": Active power injections from generators. Active power outputs set in gen_data.csv will be ignored
                 and reactive power outputs set in gen_data static.
            "Q": Reactive power injections from generators.
                 Active power outputs set in gen_data.csv are constant and reactive power outputs set in
                 gen_data.csv will be ignored.
    objective_function: str or Callable
        Objective function for optimization. Options (case-insensitive):
            "gen_max": Maximize output of generators. Uses scipy.optimize.linprog.
            "load_min": Minimize total substation active power load. Uses scipy.optimize.linprog.
            "loss_min": Minimize total line active power losses. Quadratic. Uses CVXPY.
            "curtail_min": Minimize DER/Generator curtailment. Quadratic. Uses CVXPY.
            "target_p_3ph": Substation load tracks active power target on each phase. Quadratic. Uses CVXPY.
            "target_q_3ph": Substation load tracks reactive power target on each phase. Quadratic. Uses CVXPY.
            "target_p_total": Substation load tracks total active power. Quadratic. Uses CVXPY.
            "target_q_total": Substation load tracks total reactive power. Quadratic. Uses CVXPY.
    show_plots: bool
        (default False) If true, renders plots in browser
    save_results: bool
        (default False) If true, saves result data to CSVs in output_dir
    save_plots: bool
        (default False) If true, saves interactive plots as html to output folder
    save_inputs: bool
        (default False) If true, saves model CSV and other input parameters.
        NOTE CSVs include any modifications made by other parameters such as gen_mult, load_mult, v_max, v_min, or
        v_swing.

OpenDSS Interface

You may also run using an OpenDSS model file as input.

import distopf as opf
case = opf.create_case(
    data_path="path/to/your_model_directory/model.dss",
)

Citing this tool

Gray, Nathan T., Dubey, Anamika, Reiman, Andrew P., "DistOPF: Advanced Solutions for Distribution Optimal Power Flow Analysis - DistOPF v0.2 Documentation," (2025), https://doi.org/10.2172/2999990

@techreport{osti_2999990,
  author       = {Gray, Nathan T. and Dubey, Anamika and Reiman, Andrew P. and Sadnan, Rabayet},
  title        = {DistOPF: Advanced Solutions for Distribution Optimal Power Flow Analysis - DistOPF v0.2 Documentation},
  institution  = {Pacific Northwest National Laboratory (PNNL), Richland, WA (United States)},
  doi          = {10.2172/2999990},
  url          = {https://www.osti.gov/biblio/2999990},
  place        = {United States},
  year         = {2025},
  month        = {03}}


R. Sadnan, N. Gray, A. Bose, A. Dubey and K. P. Schneider, "Scaling Distributed Optimal Renewable Energy Coordination in Unbalanced Distribution Systems," in IEEE Transactions on Sustainable Energy, vol. 17, no. 1, pp. 3-15, Jan. 2026, doi: 10.1109/TSTE.2024.3492976.

@ARTICLE{10745555,
  author={Sadnan, Rabayet and Gray, Nathan and Bose, Anjan and Dubey, Anamika and Schneider, Kevin P.},
  journal={IEEE Transactions on Sustainable Energy}, 
  title={Scaling Distributed Optimal Renewable Energy Coordination in Unbalanced Distribution Systems}, 
  year={2026},
  volume={17},
  number={1},
  pages={3-15},
  doi={10.1109/TSTE.2024.3492976}}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

distopf-0.4.0.tar.gz (18.6 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

distopf-0.4.0-py3-none-any.whl (18.5 MB view details)

Uploaded Python 3

File details

Details for the file distopf-0.4.0.tar.gz.

File metadata

  • Download URL: distopf-0.4.0.tar.gz
  • Upload date:
  • Size: 18.6 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for distopf-0.4.0.tar.gz
Algorithm Hash digest
SHA256 f75ef19c0cbd95e4813d285eba7f900cf9336de99c07a8b4ccf0fe5d4e4b32ab
MD5 47fcccd902045bc6be1ea4700473da4d
BLAKE2b-256 37a179514d6ed0c7fe50fadf1f414eb1aa6b086210d8f5ebfffcf48e3dcdadf0

See more details on using hashes here.

File details

Details for the file distopf-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: distopf-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 18.5 MB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.10.10 {"installer":{"name":"uv","version":"0.10.10","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for distopf-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b8fe32826935df5d8610ebb43c993bd221ad721f32d22fbc67aeb84b42b79af2
MD5 2138d664d5807bdc81158290e836f2de
BLAKE2b-256 9e649ca4c3f292c10e44224353f88bd18534ea71c7089ce316f342714126feb8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page