Skip to content
Snippets Groups Projects
Data.jl 5.15 KiB
Newer Older
#* Functions for handling the Network Data Dictionary (NDD) of PowerModels.jl
#*------------------------------------------------------------------------------

#=
Reads the geographic bus locations (longitude and latitude coordinates in degrees) from a CSV file and adds them to the NDD. Returns the resulting NDD, in which each bus has a "bus_lat" and "bus_lon" entry. The columns containg the longitude and latitude coordinates in the CSV file should be called "SubLongitude" and "SubLatitude", respectively.
=#
function add_locs!(
        network_data::Dict{String,<:Any}, 
        csvfile::String
    )

    pos = Dict{String,Any}(
        "baseMVA"  => 100.0,
        "bus" => Dict{String,Any}(), 
        "per_unit" => true
    )

    BusData::DataFrame = CSV.File(csvfile) |> DataFrame!
    N = size(BusData, 1) # number of buses
    sizehint!(pos["bus"], N)

    for bus in eachrow(BusData)
        pos["bus"][string(bus[:Number])] = Dict(
            "bus_lon" => bus[:SubLongitude], 
            "bus_lat" => bus[:SubLatitude]
        )
    end
    update_data!(network_data, pos) # add positions to network_data
    
    return network_data
end

#=
Returns a dictionary containing geographic bus locations that are included in the NDD. The dicitonary structure is bus-index => (lon, lat). If getlims=true, the minmimum and maximum longitude and latitude coordinates are returned as well. Can be useful for plotting the grid.
=#
function get_locs(network_data::Dict{String,<:Any}; getlims=false)
    pos = Dict(
        parse(Int64, i) => (b["bus_lon"], b["bus_lat"]) 
        for (i, b) in network_data["bus"]
    )

    ### Check whether to return lon- and lat-limits or not
    if getlims == true
        N = length(network_data["bus"]) # number of buses
        lats = [
            network_data["bus"]["$i"]["bus_lat"] 
            for i in keys(network_data["bus"])
Julian Stürmer's avatar
Julian Stürmer committed
        ] # latitudes
        lons = [
            network_data["bus"]["$i"]["bus_lon"] 
            for i in keys(network_data["bus"])
Julian Stürmer's avatar
Julian Stürmer committed
        ] # longitudes
        xlims = (minimum(lons), maximum(lons))
        ylims = (minimum(lats), maximum(lats))
        return pos, xlims, ylims
    else
        return pos
    end
end
Julian Stürmer's avatar
Julian Stürmer committed

#*------------------------------------------------------------------------------

function add_tl_voltages!(network_data::Dict{String,<:Any})
    voltages = Dict{String,Any}(
        "baseMVA"  => 100.0,
        "branch" => Dict{String,Any}(), 
        "per_unit" => true
    )
    L = length(network_data["branch"])
    sizehint!(voltages["branch"], L)

    for (i, branch) in network_data["branch"]
        if branch["transformer"] == true
            ### Transformers have a transmission line voltage of 0 kV
            voltages["branch"][i] = Dict(
                "tl_voltage" => 0. 
            )
        else
            ### Transmission lines get base voltage of from bus
            from = branch["f_bus"]
            voltages["branch"][i] = Dict(
                "tl_voltage" => network_data["bus"][string(from)]["base_kv"]
            )
        end
    end
    update_data!(network_data, voltages)

    return network_data
end

#*------------------------------------------------------------------------------

Julian Stürmer's avatar
Julian Stürmer committed
#=
Returns the bus types of all buses in the NDD in form an ordered dictionary. The different bus types are "Generator", "Load and generator", "Load" and "Empty bus". The ordering is fixed so that the dictionary can be used to plot the buses in a specific order.
Julian Stürmer's avatar
Julian Stürmer committed
=#
#? How to call "empty" buses? Load and generator buses in a substation are connected to these empty buses? Is there only one empty bus per substation?
Julian Stürmer's avatar
Julian Stürmer committed
function get_bustypes(network_data::Dict{String,<:Any})
    ### Active loads
    load = unique(
        [l["load_bus"] for l in collect(values(network_data["load"]))
        if l["status"] == 1]
Julian Stürmer's avatar
Julian Stürmer committed
    )
Julian Stürmer's avatar
Julian Stürmer committed
    ### Active generators
    gen = unique(
        [g["gen_bus"] for g in collect(values(network_data["gen"]))
        if g["gen_status"] == 1]
Julian Stürmer's avatar
Julian Stürmer committed
    )
    
    ### Buses with both load and generation
    load_and_gen = [i for i in load if i in gen]
    ### Slack bus
    slack = [
        b["index"] for b in collect(values(network_data["bus"])) 
        if b["bus_type"] == 3
    ]

Julian Stürmer's avatar
Julian Stürmer committed
    ### Remove buses with load and generation from pure loads and generators
    filter!(i -> i  load_and_gen, load)
    filter!(i -> i  load_and_gen, gen)
    ### Remove slack bus from other arrays
    filter!(i -> i  slack, load)
    filter!(i -> i  slack, gen)
    filter!(i -> i  slack, load_and_gen)

Julian Stürmer's avatar
Julian Stürmer committed
    
    ### Empty buses with neither load nor generation
    empty = unique(
        [b["index"] for b in collect(values(network_data["bus"]))
        if b["index"]  vcat(load, gen, load_and_gen, slack)]
Julian Stürmer's avatar
Julian Stürmer committed
    )

    ### Check whether the right number of bus types was obtained
    sum = (
        length(load) + length(gen) + length(load_and_gen) + length(empty) + length(slack)
    )
Julian Stürmer's avatar
Julian Stürmer committed
    N::Int64 = length(network_data["bus"]) # number of buses
    @assert sum == N "$sum bus types were obtained instead of $N"

    return OrderedDict(
        "Generator" => gen,
        "Load and generator" => load_and_gen,
        "Load" => load,
        "Slack" => slack,
        "Empty bus" => empty       
Julian Stürmer's avatar
Julian Stürmer committed
    )