PowerModels.jl icon indicating copy to clipboard operation
PowerModels.jl copied to clipboard

Support or add `calc_branch_flow_nfa`

Open yasirroni opened this issue 5 months ago • 6 comments

In https://lanl-ansi.github.io/PowerModels.jl/stable/power-flow/#Branch-Flow-Values, there are calc_branch_flow_ac and calc_branch_flow_dc, but not for calc_branch_flow_nfa. But, this will not help I think, except calc_branch_flow_nfa just:

function calc_branch_flow_nfa(data::Dict{String,<:Any})
    pm_data = get_pm_data(data)
    
    return Dict(
        "branch" => Dict(
            i => Dict(
                "pf" => branch["pf"],
                "qf" => branch["qf"],
                "pt" => branch["pt"], 
                "qt" => branch["qt"]
            ) for (i, branch) in data["branch"]
        ),
        "per_unit" => pm_data["per_unit"],
        "baseMVA" => pm_data["baseMVA"]
    )
end

Usage is the same with the other:

update_data!(network, result["solution"])
flows = calc_branch_flow_nfa(network)

yasirroni avatar Jul 22 '25 22:07 yasirroni

I'm not sure I fully understand what you are trying to do. Would you like to be able to solve some problem (e.g. optimal power flow) with the NFAPowerModel and then afterwards calculate the branch flows?

As far as I can tell, the NFAPowerModel does not consider branches and their impedances in its model at all. Therefore, no branch flows can be calculated using this model (someone please correct me if I am wrong).

The code you are showing just seems to pass through data that was already in the network dictionary: It takes the data dictionary, passes it through get_pm_data (which just strips away some data), then takes the branch results which were already in the data dictionary and copies them into a new dictionary.

Which problem (e.g. OPF) are you specifically solving? Maybe you would want a different model instead of the NFAPowerModel? (perhaps a DC power flow model like DCPPowerModel?)

LKuhrmann avatar Jul 23 '25 05:07 LKuhrmann

Hi, @LKuhrmann. This is not an urgent bug, just a soft feature request.

NFAPowerModel still generate branch flow results because it also consider branch capacity.

Here is running matpower data (you might need to change path for this).

using PowerModels
using PrettyPrint
using Ipopt

network = PowerModels.parse_file("../../env/lib/python3.13/site-packages/matpower/data/case9.m")
pm = instantiate_model(network, NFAPowerModel, PowerModels.build_opf)
result = optimize_model!(pm, optimizer=Ipopt.Optimizer)

println(result["termination_status"])
if result["termination_status"] == LOCALLY_SOLVED
    println(result["objective"])
    PowerModels.print_summary(result["solution"])
end

function calc_branch_flow_nfa(data::Dict{String,<:Any})
    network = get_pm_data(data)
    
    return Dict(
        "branch" => Dict(
            i => Dict(
                "pf" => branch["pf"],
                "qf" => 0.0,
                "pt" => branch["pt"], 
                "qt" => 0.0
            ) for (i, branch) in data["branch"]
        ),
        "per_unit" => network["per_unit"],
        "baseMVA" => network["baseMVA"]
    )
end

update_data!(network, result["solution"])
flows = calc_branch_flow_nfa(network)
flows["branch"]

[:qf, :qt, :pf, :pt] is not exist in parse, but exist in result. I actually trying to use NFAPowerModel in Sienna, and in the middle of looking how I can extract the flow results.

yasirroni avatar Jul 23 '25 10:07 yasirroni

Hi! I see! I think I don't fully understand NFAPowerModel. Thanks for your example! I saw that NFAPowerModel doesn't implement constraint_ohms_yt_from and it doesn't solve for power flow (unrelated bug?). Also, there are only tests for OPB and OPF and they seem to have the same result in all cases. I then (incorrectly) assumed that it is a copper plate model, but that is not true.

If you want a dict that only contains "branch", "per_unit" and "baseMVA" as an excerpt from network, you can also use this one-liner: a = Dict((k,deepcopy(v)) for (k, v) in result["solution"] if k in ["branch", "per_unit", "baseMVA"])

In more general terms, perhaps there should be a function calc_branch_flow that gets the PowerModel and figures out how to calculate the branch flows itself. That would make the interface more Model agnostic.

LKuhrmann avatar Jul 23 '25 11:07 LKuhrmann

The branch power is available via PowerModel object. From previous network as data source and pm as PowerModel instance:

using JuMP

function get_branch_flow_df(vars, network)
    """
    Create a DataFrame of branch flows using network branch data.

    Parameters:
    - vars: JuMP variables dictionary (vars[:p])
    - network: Network data dictionary containing branch information

    Returns:
    - DataFrame with branch flows
    """
    data = []

    for (branch_id_str, branch_data) in network["branch"]
        branch_id = parse(Int, branch_id_str)
        f_bus = branch_data["f_bus"]
        t_bus = branch_data["t_bus"]

        pf = get(vars[:p], (branch_id, f_bus, t_bus), 0.0) |> value
        pt = get(vars[:p], (branch_id, t_bus, f_bus), 0.0) |> value

        push!(data, (id=branch_id, f_bus=f_bus, t_bus=t_bus, pf=pf, pt=pt))
    end

    return DataFrame(data) |> df -> sort!(df, :id)
end

vars = var(pm)
get_branch_flow_df(vars, network)

Something similar can be used to get all object that expose :p (NFAPowerModel using :p variable, I'm not sure for the other). But this is extracting from model variable directly, not from network data. The one from network data is already answered in my previous comment.


If PowerModel interested in this as PR, I'll try to make it. But, my previous PR is rejected as out of scope, thus I'll not make it before confirmation.

yasirroni avatar Jul 25 '25 08:07 yasirroni

Hi Folks! Let me provide some context to this.

First, the objective of calc_branch_flow_ac and calc_branch_flow_dc are to take as input the bus voltages and then compute as output the corresponding branch flow values, via the power flow equations (either the full AC or DC approximation variants).

The NFA model is short for "network flow approximation" it is also known as the "transportation model", https://lanl-ansi.github.io/PowerModels.jl/stable/formulation-details/#PowerModels.NFAPowerModel

In the NFA model there is no concept of bus voltages and power can flow freely on every branch of the network up to the branch flow limits. This model has some uses but is far removed from the physics of how power flows in the electric grid.

For these reasons, calc_branch_flow_nfa is not a well defined function following the conventions of the other two. If you would like to know suitable branch flows for the NFA model you should solve a OPF problem using the NFA formulation.

ccoffrin avatar Jul 26 '25 19:07 ccoffrin

Hi @ccoffrin,

Yeah, I already know it. It is just that NFAPowerModel is a valid approach used by engineer to firstly check power balance. Since it didn't use any voltage data, it would be better to give a separate calc_branch_flow_nfa so that someone didn't accidentally use calc_branch_flow_dc and use the "old" voltage angle data (which is not updated by update_data! as far as I know).

This is not urgent, and just to add something. The simplest implementation will be what I described previously: https://github.com/lanl-ansi/PowerModels.jl/issues/977#issuecomment-3106814773

yasirroni avatar Jul 27 '25 07:07 yasirroni