Skip to content
Snippets Groups Projects
Impact.jl 10.2 KiB
Newer Older
#* Functions for modelling the impact of hurricanes on power grids.
#*------------------------------------------------------------------------------

function sim_impact(
        seg_data::Dict{String,<:Any};
        γ::Float64
    )

    tl_failures = Array{Tuple{String,Int64},1}() # array for line failures
    
    ### Go through overhead transmission lines and simulate until failure
    for (i, tl) in seg_data 
        for (f, windloads) in tl["windloads"] # go through wind frames
            p_fail = min.(γ * windloads, 1)
            vals = rand(tl["N_seg"])
            if any(vals .< p_fail) # line gets destroyed
                push!(tl_failures, (i, f)) # save line ID and frame
                break
            end
        end
    end

    return tl_failures
end

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

#=
Calculates wind loads (wind force / breaking force) for overhead transmission lines in the network data dictionary and a given .nc file containing wind data. The overhead transmission lines are identified according to the chosen mode ((:sum or :single, see get_underground_tl) and divided into segments between towers. The results are returned in form of a dictionary (see calc_overhead_tl_segments).
=#
function calc_overhead_tl_windloads(
        network_data::Dict{String,<:Any},
        windfile::String, # .nc file containing wind data
        d_twrs = 161.0;
        mode = :sum # :sum or :single
    )

    ### Read wind data
    wind_lons, wind_lats, wind_speeds = get_windfield(windfile)

    ### Identify overhead lines and divide them into segments between towers
    overhead_tl_segments = calc_overhead_tl_segments(
        network_data, d_twrs, mode=mode
    )

    ### Assign location indices to segments that identify the local wind speed
    _assign_loc_indices!(overhead_tl_segments, wind_lons, wind_lats)

    ### Go through time steps in the wind data and calculate wind loads
    for f in 1:size(wind_speeds, 3)
        wind_frame = @view wind_speeds[:,:,f]
        _calc_seg_windloads!(overhead_tl_segments, wind_frame, f)
    end

    return overhead_tl_segments # dictionary with all information
end

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

#= 
Returns the longitude and latitude coordinates of the wind field together with the whole time series of wind speeds.
=#
function get_windfield(filepath::String)
    wind_lons = round.(ncread(filepath, "lon"), digits=6)
    wind_lats = round.(ncread(filepath, "lat"), digits=6)
    wind_speeds = ncread(filepath, "wind")

    return wind_lons, wind_lats, wind_speeds
end

function get_windfield(filepath::String, frame::Int64)
    wind_lons, wind_lats, wind_speeds = get_windfield(filepath)
    return wind_lons, wind_lats, wind_speeds[:, :, frame]
end

#=
Function that returns the number of frames (time steps) in the given .nc data file and the maximum wind speed.
=#
function get_winddata(filepath::String)
    wind = ncread(filepath, "wind")
    N_frames = size(wind, 3)
    max_ws = maximum(wind)
    return N_frames, max_ws
end

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

#=
Identifies overhead transmission lines in the network data dictionary according to the chosen mode (:sum or :single, see get_underground_tl) and divides them into segments between towers. The towers are assumed to be d_twrs (in m) apart and the resulting segments have a length (l_seg) close to d_twrs. The resulting segments are returned in form of a dictionary with entries explained in the code below.
=#
function calc_overhead_tl_segments(
        network_data::Dict{String,<:Any}, 
        d_twrs = 161.0;
        mode = :sum # :sum or :single
    )

    ### Get (string) indices of underground transmission lines
    underground_tl = get_underground_tl(network_data, mode=mode)
    
    unique_overhead_tl = [
        i for (i, b) in network_data["branch"] 
        if b["transformer"] == false && i  underground_tl 
        && b["source_id"][end] == "1 " # parallel circuits excluded
    ] # string indices of unique overhead transmission lines

    seg_data = Dict{String,Dict{String,Any}}() # dictionary for segment data

    for i in unique_overhead_tl
        branch = network_data["branch"][i]
        f_bus, t_bus = branch["f_bus"], branch["t_bus"]
        lon_f = network_data["bus"]["$f_bus"]["bus_lon"]
        lat_f = network_data["bus"]["$f_bus"]["bus_lat"]
        lon_t = network_data["bus"]["$t_bus"]["bus_lon"]
        lat_t = network_data["bus"]["$t_bus"]["bus_lat"]

        L, N_seg, l_seg, seg_lons, seg_lats = _calc_tl_segments(
            lon_f, lat_f, lon_t, lat_t, d_twrs
        ) # data for transmission line segments

        ### Add data to dictionary
        seg_data[i] = Dict(
            "f_bus" => f_bus, # index (Int64) of from-bus
            "lon_f" => lon_f, # longitude of from-bus
            "lat_f" => lat_f, # latitude of from-bus
            "t_bus" => t_bus, # index (Int64) of to-bus
            "lon_t" => lon_t, # longitude of to-bus
            "lat_t" => lat_t, # latitude of to-bus
            "L" => L, # calculated length of transmission line
            "N_seg" => N_seg, # number of segments between towers
            "l_seg" => l_seg, # actual length of segments (close to d_twrs)
            "seg_lons" => seg_lons, # longitudes of segment-half-way points
            "seg_lats" => seg_lats, # latitudes of segment-half-way points
            "windloads" => Dict{Int64,Array{Float64,1}}() # for wind loads
        )
    end

    return seg_data
end

#= 
Function that divides a transmission line (tl) between a "from-bus" with coordinates (lon_f, lat_f) in degrees and a "to-bus" (lon_t, lat_t) into segments of wanted length d_twrs (average distance between transmission towers in meter) and returns the half-way points (longitude and latitude coordinates) of all segments together with length L of the transmission line (calculated using the haversine formula), the number of segments (N_twrs+1), and the actual length of segments l_seg.
=#
function _calc_tl_segments(
        lon_f::Float64, lat_f::Float64, 
        lon_t::Float64, lat_t::Float64,
        d_twrs = 161.0
    )

    R = 6371000 # earth radius
    λf, φf, λt, φt = deg2rad.([lon_f, lat_f, lon_t, lat_t])

    ### Calculate haversine distance between "from-" and "to-bus"
    h = sin((φt-φf)/2)^2 + cos(φf) * cos(φt) * sin((λt-λf)/2)^2 # haversine
    δ = 2 * atan(sqrt(h), sqrt(1-h)) # angular distance in radians
    L = R * δ # transmission line length

    N_twrs = round(L/d_twrs) # number of transmission towers
    l_seg = L/N_twrs # actual length of line segments

    ### Calulate the fractions of the line at which the half-way points lie
    f = 1/N_twrs
    f_seg = [(i+0.5)*f for i in 0:N_twrs]

    a = sin.((1 .- f_seg)*δ)/sin(δ)
    b = sin.((f_seg*δ))/sin(δ)
    x = a*cos(φf)*cos(λf) + b*cos(φt)*cos(λt)
    y = a*cos(φf)*sin(λf) + b*cos(φt)*sin(λt)
    z = a*sin(φf) + b*sin(φt)

    ### Calculate longitudes and latitudes of all half-way points in radians
    λ_seg = atan.(y, x) 
    φ_seg = atan.(z, sqrt.(x.^2 + y.^2))

    return L, Int64(N_twrs+1), l_seg, rad2deg.(λ_seg), rad2deg.(φ_seg)
end


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

#=
Assigns location indices to transmission line segments that help to identify local wind speeds for the calculation of wind loads. The location indices are added to the data dictionary seg_data.
=#
function _assign_loc_indices!(
        seg_data::Dict{String,<:Any},
        wind_lons::Array{Float64,1}, # longitudes in wind field
        wind_lats::Array{Float64,1} # latitudes in wind field
    )

    ### Go through overhead transmission lines
    for tl_data in collect(values(seg_data))
        N_seg = tl_data["N_seg"]
        lon_indices = zeros(Int64, N_seg)
        lat_indices = zeros(Int64, N_seg)
        ### Go through segments and identify their position in the wind field
        for i in 1:N_seg
            seg_lon = tl_data["seg_lons"][i]
            seg_lat = tl_data["seg_lats"][i]
            lon_indices[i] = argmin(abs.(wind_lons .- seg_lon))
            lat_indices[i] = argmin(abs.(wind_lats .- seg_lat))
        end
        tl_data["loc_indices"] = [loc for loc in zip(lon_indices, lat_indices)]
    end

    return nothing
end

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

#=
Calculates wind loads for all overhead transmission line segments in the segment data dictionary containing location indices (see _assign_loc_indices!) and a given time step (frame) of the wind field. The results are added to seg_data.
=#
function _calc_seg_windloads!(
        seg_data::Dict{String,<:Any}, 
        wind_frame::SubArray, # slice of wind speed time series
        f::Int64 # wind frame number
    )

    ### Go through overhead transmission lines
    for (key, tl_data) in seg_data
        windloads = zeros(tl_data["N_seg"])
        ### Go through unique segment location indices and calculate wind loads
        for loc in unique(tl_data["loc_indices"])
            ws = wind_frame[CartesianIndex(loc)] # local wind speed
            wl = _calc_windload(ws, tl_data["l_seg"]) # local wind load
            ### Add this wind load to all segments with these location indices
            for i in findall(l -> l == loc, tl_data["loc_indices"])
                windloads[i] = wl
            end
        end
        tl_data["windloads"][f] = windloads # add result to dictionary
    end
    
    return nothing
end

#= 
Function that calculates the failure probabilities of transmission line segments according to the local wind speed and the length of the segments.
=#
function _calc_windload(v::Float64, l::Float64)
    ### Parameters in wind force design equation
    Q, k_z, C_f, d, F_brk = [0.613, 1.17, 0.9, 0.03, 152130]

    ### Gust response factor for line segment
    E_W = 4.9*sqrt(0.005)*(33/70)^(1/7)
    G_WRF = 1 + 2.7*E_W/sqrt(1 + 0.8/67.056*l)

    ### Convert wind speeds to an effective height of h = 70ft = 21.336m
    v *= 1.16 # conversion factor ln(21.336/0.1)/ln(100) ≈ 1.16 from 10m to h

    ### Calculate wind force according to ASCE design equation
    F_wind = Q * k_z * C_f * G_WRF * d * l * v^2
    ### Return wind load on all segment
    return F_wind / F_brk
end