# Contains functions related to distribution networks but not specific to a particular model

## Lookup functions, to build the constraint selection logic

"Return whether the `f_bus` of branch `i` is the reference bus."
function is_frb_branch(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    return haskey(_PM.ref(pm, nw, :frb_branch), i)
end

"Return whether the `f_bus` of ne_branch `i` is the reference bus."
function is_frb_ne_branch(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    return haskey(_PM.ref(pm, nw, :frb_ne_branch), i)
end

"Return whether branch `i` is an OLTC."
function is_oltc_branch(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    return haskey(_PM.ref(pm, nw, :oltc_branch), i)
end

"Return whether ne_branch `i` is an OLTC."
function is_oltc_ne_branch(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    return haskey(_PM.ref(pm, nw, :oltc_ne_branch), i)
end

## Variables

function variable_oltc_branch_transform(pm::_PM.AbstractWModels; kwargs...)
    variable_oltc_branch_transform_magnitude_sqr_inv(pm; kwargs...)
end

"variable: `0 <= ttmi[l]` for `l` in `oltc_branch`es"
function variable_oltc_branch_transform_magnitude_sqr_inv(pm::_PM.AbstractPowerModel; nw::Int=_PM.nw_id_default, bounded::Bool=true, report::Bool=true)
    ttmi = _PM.var(pm, nw)[:ttmi] = JuMP.@variable(pm.model,
        [i in _PM.ids(pm, nw, :oltc_branch)], base_name="$(nw)_ttmi",
        lower_bound = 0.0,
        start = 1.0 / _PM.ref(pm,nw,:oltc_branch,i,"tap")^2
    )

    if bounded
        for (i, br) in _PM.ref(pm, nw, :oltc_branch)
            JuMP.set_lower_bound(ttmi[i], 1.0 / br["tm_max"]^2 )
            JuMP.set_upper_bound(ttmi[i], 1.0 / br["tm_min"]^2 )
        end
    end

    report && _PM.sol_component_value(pm, nw, :branch, :ttmi, _PM.ids(pm, nw, :oltc_branch), ttmi)
end

function variable_oltc_ne_branch_transform(pm::_PM.AbstractWModels; kwargs...)
    variable_oltc_ne_branch_transform_magnitude_sqr_inv(pm; kwargs...)
end

"variable: `0 <= ttmi_ne[l]` for `l` in `oltc_ne_branch`es"
function variable_oltc_ne_branch_transform_magnitude_sqr_inv(pm::_PM.AbstractPowerModel; nw::Int=_PM.nw_id_default, bounded::Bool=true, report::Bool=true)
    ttmi_ne = _PM.var(pm, nw)[:ttmi_ne] = JuMP.@variable(pm.model,
        [i in _PM.ids(pm, nw, :oltc_ne_branch)], base_name="$(nw)_ttmi_ne",
        lower_bound = 0.0,
        start = 1.0 / _PM.ref(pm,nw,:oltc_ne_branch,i,"tap")^2
    )

    if bounded
        for (i, br) in _PM.ref(pm, nw, :oltc_ne_branch)
            JuMP.set_lower_bound(ttmi_ne[i], 1.0 / br["tm_max"]^2 )
            JuMP.set_upper_bound(ttmi_ne[i], 1.0 / br["tm_min"]^2 )
        end
    end

    report && _PM.sol_component_value(pm, nw, :ne_branch, :ttmi, _PM.ids(pm, nw, :oltc_ne_branch), ttmi_ne)
end

## Constraint templates that group several other constraint templates, provided for convenience

function constraint_dist_branch_tnep(pm::_PM.AbstractBFModel, i::Int; nw::Int=_PM.nw_id_default)
    if isempty(ne_branch_ids(pm, i; nw = nw))
        if is_frb_branch(pm, i; nw = nw)
            if is_oltc_branch(pm, i; nw = nw)
                constraint_power_losses_oltc(pm, i; nw = nw)
                constraint_voltage_magnitude_difference_oltc(pm, i; nw = nw)
            else
                constraint_power_losses_frb(pm, i; nw = nw)
                constraint_voltage_magnitude_difference_frb(pm, i; nw = nw)
            end
        else
            _PM.constraint_power_losses(pm, i; nw = nw)
            _PM.constraint_voltage_magnitude_difference(pm, i; nw = nw)
        end
        _PM.constraint_voltage_angle_difference(pm, i; nw = nw)
        _PM.constraint_thermal_limit_from(pm, i; nw = nw)
        _PM.constraint_thermal_limit_to(pm, i; nw = nw)
    else
        expression_branch_indicator(pm, i; nw = nw)
        # constraint_branch_complementarity(pm, i; nw = nw) is best added once per year; if added here, redundant constraints would be generated
        if is_frb_branch(pm, i; nw = nw)
            if is_oltc_branch(pm, i; nw = nw)
                constraint_power_losses_oltc_on_off(pm, i; nw = nw)
                constraint_voltage_magnitude_difference_oltc_on_off(pm, i; nw = nw)
            else
                constraint_power_losses_frb_on_off(pm, i; nw = nw)
                constraint_voltage_magnitude_difference_frb_on_off(pm, i; nw = nw)
            end
        else
            constraint_power_losses_on_off(pm, i; nw = nw)
            constraint_voltage_magnitude_difference_on_off(pm, i; nw = nw)
        end
        _PM.constraint_voltage_angle_difference_on_off(pm, i; nw = nw)
        _PM.constraint_thermal_limit_from_on_off(pm, i; nw = nw)
        _PM.constraint_thermal_limit_to_on_off(pm, i; nw = nw)
    end
end

function constraint_dist_ne_branch_tnep(pm::_PM.AbstractBFModel, i::Int; nw::Int=_PM.nw_id_default)
    if ne_branch_replace(pm, i, nw = nw)
        if is_frb_ne_branch(pm, i, nw = nw)
            if is_oltc_ne_branch(pm, i, nw = nw)
                constraint_ne_power_losses_oltc(pm, i, nw = nw)
                constraint_ne_voltage_magnitude_difference_oltc(pm, i, nw = nw)
            else
                constraint_ne_power_losses_frb(pm, i, nw = nw)
                constraint_ne_voltage_magnitude_difference_frb(pm, i, nw = nw)
            end
        else
            constraint_ne_power_losses(pm, i, nw = nw)
            constraint_ne_voltage_magnitude_difference(pm, i, nw = nw)
        end
        _PM.constraint_ne_thermal_limit_from(pm, i, nw = nw)
        _PM.constraint_ne_thermal_limit_to(pm, i, nw = nw)
    else
        if is_frb_ne_branch(pm, i, nw = nw)
            if is_oltc_ne_branch(pm, i, nw = nw)
                Memento.error(_LOGGER, "addition of a candidate OLTC in parallel to an existing OLTC is not supported")
            else
                constraint_ne_power_losses_frb_parallel(pm, i, nw = nw)
                constraint_ne_voltage_magnitude_difference_frb_parallel(pm, i, nw = nw)
            end
        else
            constraint_ne_power_losses_parallel(pm, i, nw = nw)
            constraint_ne_voltage_magnitude_difference_parallel(pm, i, nw = nw)
        end
        constraint_ne_thermal_limit_from_parallel(pm, i, nw = nw)
        constraint_ne_thermal_limit_to_parallel(pm, i, nw = nw)
    end
    _PM.constraint_ne_voltage_angle_difference(pm, i, nw = nw)
end

## Constraint templates

"Defines voltage drop over a a branch whose `f_bus` is the reference bus"
function constraint_voltage_magnitude_difference_frb(pm::_PM.AbstractBFModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    g_sh_fr = branch["g_fr"]
    b_sh_fr = branch["b_fr"]
    tm = branch["tap"]

    constraint_voltage_magnitude_difference_frb(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr, tm)
end

"Defines voltage drop over a transformer branch that has an OLTC"
function constraint_voltage_magnitude_difference_oltc(pm::_PM.AbstractBFModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    g_sh_fr = branch["g_fr"]
    b_sh_fr = branch["b_fr"]

    constraint_voltage_magnitude_difference_oltc(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr)
end

"Defines branch flow model power flow equations for a branch whose `f_bus` is the reference bus"
function constraint_power_losses_frb(pm::_PM.AbstractBFModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    tm = branch["tap"]
    g_sh_fr = branch["g_fr"]
    g_sh_to = branch["g_to"]
    b_sh_fr = branch["b_fr"]
    b_sh_to = branch["b_to"]

    constraint_power_losses_frb(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm)
end

"Defines branch flow model power flow equations for a transformer branch that has an OLTC"
function constraint_power_losses_oltc(pm::_PM.AbstractBFModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    g_sh_fr = branch["g_fr"]
    g_sh_to = branch["g_to"]
    b_sh_fr = branch["b_fr"]
    b_sh_to = branch["b_to"]

    constraint_power_losses_oltc(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to)
end

""
function constraint_ne_power_losses_frb(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :ne_branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    tm = branch["tap"]
    g_sh_fr = branch["g_fr"]
    g_sh_to = branch["g_to"]
    b_sh_fr = branch["b_fr"]
    b_sh_to = branch["b_to"]

    vad_min = _PM.ref(pm, nw, :off_angmin)
    vad_max = _PM.ref(pm, nw, :off_angmax)

    constraint_ne_power_losses_frb(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, tm, vad_min, vad_max)
end

""
function constraint_ne_power_losses_oltc(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :ne_branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    g_sh_fr = branch["g_fr"]
    g_sh_to = branch["g_to"]
    b_sh_fr = branch["b_fr"]
    b_sh_to = branch["b_to"]

    vad_min = _PM.ref(pm, nw, :off_angmin)
    vad_max = _PM.ref(pm, nw, :off_angmax)

    constraint_ne_power_losses_oltc(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, g_sh_to, b_sh_fr, b_sh_to, vad_min, vad_max)
end

"""
Defines voltage drop over a branch, linking from and to side voltage magnitude
"""
function constraint_ne_voltage_magnitude_difference_frb(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :ne_branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    g_sh_fr = branch["g_fr"]
    b_sh_fr = branch["b_fr"]
    tm = branch["tap"]

    constraint_ne_voltage_magnitude_difference_frb(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr, tm)
end

"""
Defines voltage drop over a branch, linking from and to side voltage magnitude
"""
function constraint_ne_voltage_magnitude_difference_oltc(pm::_PM.AbstractPowerModel, i::Int; nw::Int=_PM.nw_id_default)
    branch = _PM.ref(pm, nw, :ne_branch, i)
    f_bus = branch["f_bus"]
    t_bus = branch["t_bus"]
    f_idx = (i, f_bus, t_bus)
    t_idx = (i, t_bus, f_bus)

    r = branch["br_r"]
    x = branch["br_x"]
    g_sh_fr = branch["g_fr"]
    b_sh_fr = branch["b_fr"]
    tm_min = branch["tm_min"]
    tm_max = branch["tm_max"]

    constraint_ne_voltage_magnitude_difference_oltc(pm, nw, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr, tm_min, tm_max)
end

## Constraint implementations not limited to a specific model type

""
function constraint_ne_voltage_magnitude_difference_frb(pm::_PM.AbstractBFAModel, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr, tm)
    branch = _PM.ref(pm, n, :ne_branch, i)
    fr_bus = _PM.ref(pm, n, :bus, f_bus)
    to_bus = _PM.ref(pm, n, :bus, t_bus)
    M_hi   =  1.0^2/tm^2 - to_bus["vmin"]^2
    M_lo   = -1.0^2/tm^2 + to_bus["vmax"]^2
    p_fr = _PM.var(pm, n, :p_ne, f_idx)
    q_fr = _PM.var(pm, n, :q_ne, f_idx)
    w_to = _PM.var(pm, n, :w, t_bus)
    z    = _PM.var(pm, n, :branch_ne, i)
    # w_fr is assumed equal to 1.0

    JuMP.@constraint(pm.model, (1.0/tm^2) - w_to <= 2*(r*p_fr + x*q_fr) + M_hi*(1-z) )
    JuMP.@constraint(pm.model, (1.0/tm^2) - w_to >= 2*(r*p_fr + x*q_fr) - M_lo*(1-z) )
end

""
function constraint_ne_voltage_magnitude_difference_oltc(pm::_PM.AbstractBFAModel, n::Int, i, f_bus, t_bus, f_idx, t_idx, r, x, g_sh_fr, b_sh_fr, tm_min, tm_max)
    branch = _PM.ref(pm, n, :ne_branch, i)
    fr_bus = _PM.ref(pm, n, :bus, f_bus)
    to_bus = _PM.ref(pm, n, :bus, t_bus)
    M_hi   =  1.0^2/tm_min^2 - to_bus["vmin"]^2
    M_lo   = -1.0^2/tm_max^2 + to_bus["vmax"]^2
    p_fr = _PM.var(pm, n, :p_ne, f_idx)
    q_fr = _PM.var(pm, n, :q_ne, f_idx)
    ttmi = _PM.var(pm, n, :ttmi_ne, i)
    w_to = _PM.var(pm, n, :w, t_bus)
    z    = _PM.var(pm, n, :branch_ne, i)
    # w_fr is assumed equal to 1.0 to preserve the linearity of the model

    JuMP.@constraint(pm.model, 1.0*ttmi - w_to <= 2*(r*p_fr + x*q_fr) + M_hi*(1-z) )
    JuMP.@constraint(pm.model, 1.0*ttmi - w_to >= 2*(r*p_fr + x*q_fr) - M_lo*(1-z) )
end


# include("time_parse.jl")
# include("multifrequency-opf.jl")
# include("utilities.jl")

# function to run multifrequency_opf on one case within the given time range.
#
# parameters:
# month_st, day_st, period_st, month_en, day_en, period_en: the parameters specifying the period to plot
# folder: the directory containing all subnetwork data, subnetworks.csv, interfaces.csv
# subne_folder: the number of subnet that is to be optimized (which directs to the sub-folder containing its data)
# output_folder: the folder specified to save the output files
# plot_results: choice of plot the optimization result (optimal value vs time points) directly after optimization, default false Below are the similar arguments as multifrequency-opf:
# obj::String: the objective function to use, from the following:
# "mincost": minimize generation cost
# "areagen": minimize generation in the areas specified in gen_areas
# "minredispatch": minimize the change in generator dispatch from the initial values defined in the network data
# gen_areas: integer array of all areas in which generation should be minimized if obj=="mincost"
# area_transfer: two areas with power transfer between them that should be saved and plotted. Results for P, Q, S, and loss are saved for power transfer between the two areas. Must have exactly two elements.
# print_results: if true, print the optimization results
###########################################################################################
# month_st, day_st, period_st, month_en, day_en, period_en: the
# folder::String:: the directory containing all subnetwork data, subnetworks.csv, interfaces.csv
# subne_folder: the number of subnet that is to be optimized (which directs to the sub-folder containing its data)
# output_folder: the folder specified to save the output files
# plot_results::Bool = true: choice of plot the optimization result (optimal value vs time points) directly after optimization, default false Below are the similar arguments as multifrequency-opf:
# obj::String: the objective function to use, from the following:
#     "mincost": minimize generation cost
#     "areagen": minimize generation in the areas specified in gen_areas
#     "minredispatch": minimize the change in generator dispatch from the initial values defined in the network data
# gen_areas: integer array of all areas in which generation should be minimized if obj=="mincost"
# area_transfer: two areas with power transfer between them that should be saved and plotted. Results for P, Q, S, and loss are saved for power transfer between the two areas. Must have exactly two elements.
# print_results::Bool: if true, print the optimization results
# function to run multifrequency-opf on the specified case with the specified range of time
function hour_opf(month_st, day_st, period_st, month_en, day_en, period_en,
      folder:: String,
      subnet_folder:: String,
      output_folder::String,
      obj::String,
      gen_areas = [],
      area_transfer = [],
      plot_results::Bool = true,
      print_results::Bool = false)
      # override_param::Dict{Any}=Dict(),
      # fix_f_override::Bool=false,
      # direct_pq::Bool=true,
      # master_subnet::Int64=1,
      # suffix::String=""
      dir = "$(folder)/"
      #file_name = "$(dir)/RTS_GMLC.m"

      # hour_data: store the data
      hour_data = Dict{Int64, Any}

      # time range
      start_t = DateTime(2020,month_st,day_st,period_st)
      end_t = DateTime(2020,month_en,day_en,period_en)
      range_t = start_t:Hour(1):end_t

      # input the corresponding data of the specific subnetwork
      sub_folder = "$(folder)/enum_net/$(subnet_folder)"
      println("reading the file $(sub_folder)")
      hour_data = hour_parse(month_st, day_st, period_st, month_en, day_en, period_en, sub_folder)

      # s = JSON.json(hour_data)
      # open("test_hour_cost.json", "w") do f
      # write(f, s)
      # end

      # dictionaries store the output of the optimization for each period.
      opt_net = Dict()
      res_summary_dict = Dict()
      solution_pm_dict = Dict()
      hour_mn_data = Dict()
      objective_val = Dict()

      # output binding constraints
      binding_constraints = Dict()

      # manually used enumerate_branch to make data of subnetworks from RTS_GMLC.m before
      # running the make_mn_data function
      for i in 1:size(range_t, 1)
            #for j in 1:119
                  #subnetwork_file = "$(dir)/enum_nets/br$(j)/subnetworks.csv"
                  #interface_file = "$(dir)/enum_nets/br$(j)/interfaces.csv"
                  subnetwork_file = "$(dir)/enum_net/$(subnet_folder)/subnetworks.csv"
                  interface_file = "$(dir)/enum_net/$(subnet_folder)/interfaces.csv"
                  subnetworks = DataFrame(index=Int64[], file=String[], variable_f=Bool[],
                                          f_base=Float64[], f_min=Float64[], f_max=Float64[])
                  interfaces = DataFrame(index=Int64[], subnet_index=Int64[], bus=Int64[],
                                         s_max=Float64[], p_max=Float64[], q_max=Float64[],
                                         loss_param=Float64[])

                  # files ready for input to make_mn_data
                  subnetworks = CSV.read(subnetwork_file, DataFrame)
                  interfaces = CSV.read(interface_file, DataFrame)
                  hour_mn_data[i] = VariableFrequencyOPF.make_mn_data(subnetworks, interfaces, hour_data[i])

                  (opt_net[i], res_summary_dict[i], solution_pm_dict[i]) =
                  VariableFrequencyOPF.multifrequency_opf(hour_mn_data[i], output_folder, obj, gen_areas, area_transfer, print_results)
                  # open("test_mn_cost_$(i).json", "w") do f
                  #  write(f, s1)
                  # end

                  # write the binding constriants to a new file (save binding constraints of each case individually)
                  binding_constraints_part = Dict()
                  open("$(output_folder)/binding_constraints.json", "r") do f
                        global binding_constraints_part
                        #bind = read(f, String)
                        binding_constraints_part = JSON.parse(f)
                        binding_constraints[i] = binding_constraints_part
                  end
                  #binding_constraints_json = JSON.json(binding_constraints)

            #end
      end

      # save files of outputs
      binding_constraints_json = JSON.json(binding_constraints)
      open("$(output_folder)/binding_constraints$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f1
            write(f1, binding_constraints_json)
      end

      opt_net_string = JSON.json(opt_net)
      open("$(output_folder)/opt_net_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
        write(f, opt_net_string)
      end

      res_summary_string = JSON.json(res_summary_dict)
      open("$(output_folder)/res_summary_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
        write(f, res_summary_string)
      end

      # solution_pm_string = JSON.json(solution_pm_dict)
      # open("$(output_folder)/solution_pm_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
      #   write(f, solution_pm_string)
      # end

      # plot of the results (optional, now we mostly use the function of multi_plot)
      # if plot_results == true
      #       objval = Array{Float64, 1}()
      #       objval_n = Array{Float64, 1}()
      #       size_obj = Array{DateTime, 1}()
      #       size_obj_n = Array{DateTime, 1}()
      #       for i in 1:size(range_t, 1)
      #             if res_summary_dict[i].status[1] in [LOCALLY_SOLVED, ALMOST_LOCALLY_SOLVED]
      #                   push!(objval, res_summary_dict[i]["objective"][1])
      #                   push!(size_obj, range_t[i])
      #             else
      #                   push!(objval_n, res_summary_dict[i]["objective"][1])
      #                   push!(size_obj_n, range_t[i])
      #             end
      #       end
      #       #size_obj_n = size(range_t, 1) - size_obj
      #       # below are the parts of plotting
      #       println(range_t)
      #       println(objval)
      #       gr()
      #       p = plot(size_obj, objval,  color = :blue, seriestype = :line, title = "optimal result plot",
      #                xlabel = "time/h", ylabel = "optimal $(obj)", right_margin = 10px)
      #       plot!(size_obj_n, objval_n, color = :red, seriestype = :scatter)
      #       # seriestype = :histogram
      #       #savefig(fn)
      #       #savefig(p, )
      #
      #       # save output plot file
      #       savefig(p, "$(output_folder)/opt_res_plot_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).pdf")
      # end

      return opt_net, res_summary_dict, solution_pm_dict
end

function hour_opf_regularized(month_st, day_st, period_st, month_en, day_en, period_en,
      folder:: String,
      subnet_folder:: String,
      output_folder::String,
      obj::String;
      gen_areas = [],
      area_transfer = [],
      gen_zones = [],
      zone_transfer = [],
      plot_results::Bool = true,
      print_results::Bool = false,
      regularize_f::Float64=0.0,
      ipopt_max_iter::Int64=20000
      )
      # override_param::Dict{Any}=Dict(),
      # fix_f_override::Bool=false,
      # direct_pq::Bool=true,
      # master_subnet::Int64=1,
      # suffix::String=""
      dir = "$(folder)/"
      #file_name = "$(dir)/RTS_GMLC.m"

      # hour_data: store the data
      hour_data = Dict{Int64, Any}

      if !isdir(output_folder)
            mkpath(output_folder)
      end
      # time range
      start_t = DateTime(2020,month_st,day_st,period_st)
      end_t = DateTime(2020,month_en,day_en,period_en)
      range_t = start_t:Hour(1):end_t

      # input the corresponding data of the specific subnetwork
      # sub_folder = "$(folder)/enum_net_new/$(subnet_folder)"
      sub_folder = subnet_folder
      println("reading the file $(sub_folder)")
      hour_data = hour_parse(month_st, day_st, period_st, month_en, day_en, period_en, sub_folder)

      # s = JSON.json(hour_data)
      # open("test_hour_cost.json", "w") do f
      # write(f, s)
      # end

      # dictionaries store the output of the optimization for each period.
      opt_net = Dict()
      res_summary_dict = Dict()
      solution_pm_dict = Dict()
      hour_mn_data = Dict()
      objective_val = Dict()

      # output binding constraints
      binding_constraints = Dict()

      # manually used enumerate_branch to make data of subnetworks from RTS_GMLC.m before
      # running the make_mn_data function
      for i in 1:size(range_t, 1)
            #for j in 1:119
                  #subnetwork_file = "$(dir)/enum_nets/br$(j)/subnetworks.csv"
                  #interface_file = "$(dir)/enum_nets/br$(j)/interfaces.csv"
                  # subnetwork_file = "$(dir)/enum_net_new/$(subnet_folder)/subnetworks.csv"
                  # interface_file = "$(dir)/enum_net_new/$(subnet_folder)/interfaces.csv"
                  subnetwork_file = "$(subnet_folder)/subnetworks.csv"
                  interface_file = "$(subnet_folder)/interfaces.csv"
                  subnetworks = DataFrame(index=Int64[], file=String[], variable_f=Bool[],
                                          f_base=Float64[], f_min=Float64[], f_max=Float64[])
                  interfaces = DataFrame(index=Int64[], subnet_index=Int64[], bus=Int64[],
                                         s_max=Float64[], p_max=Float64[], q_max=Float64[],
                                         loss_param=Float64[])

                  # files ready for input to make_mn_data
                  subnetworks = CSV.read(subnetwork_file, DataFrame)
                  interfaces = CSV.read(interface_file, DataFrame)
                  hour_mn_data[i] = VariableFrequencyOPF.make_mn_data(subnetworks, interfaces, hour_data[i])

                  override_param=Dict()
                  fix_f_override=false
                  direct_pq=true
                  master_subnet=1
                  start_vals=Dict{String, Dict}("sn"=>Dict())
                  uniform_gen_scaling=false
                  unbounded_pg=false
                  output_to_files=true
                  # regularize_f=regularize_f
                  # ipopt_max_iter = ipopt_max_iter

                  (opt_net[i], res_summary_dict[i], solution_pm_dict[i]) =
                  VariableFrequencyOPF.multifrequency_opf(
                        hour_mn_data[i],
                        output_folder,
                        obj,
                        gen_areas,
                        area_transfer,
                        gen_zones,
                        zone_transfer,
                        print_results,
                        override_param,
                        fix_f_override,
                        direct_pq,
                        master_subnet,
                        start_vals,
                        uniform_gen_scaling,
                        unbounded_pg,
                        output_to_files,
                        regularize_f = regularize_f,
                        ipopt_max_iter = ipopt_max_iter
                        )
                  # open("test_mn_cost_$(i).json", "w") do f
                  #  write(f, s1)
                  # end

                  # write the binding constriants to a new file (save binding constraints of each case individually)
                  binding_constraints_part = Dict()
                  open("$(output_folder)/binding_constraints.json", "r") do f
                        global binding_constraints_part
                        #bind = read(f, String)
                        binding_constraints_part = JSON.parse(f)
                        binding_constraints[i] = binding_constraints_part
                  end
                  #binding_constraints_json = JSON.json(binding_constraints)

            #end
      end

      # save files of outputs
      if !isdir("$(output_folder)/binding_constraints/$(subnet_folder)")
            mkpath("$(output_folder)/binding_constraints/$(subnet_folder)")
      end
      binding_constraints_json = JSON.json(binding_constraints)
      open("$(output_folder)/binding_constraints/$(subnet_folder)/($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f1
            write(f1, binding_constraints_json)
      end
      if !isdir("$(output_folder)/opt_net/$(subnet_folder)")
            mkpath("$(output_folder)/opt_net/$(subnet_folder)")
      end
      opt_net_string = JSON.json(opt_net)
      open("$(output_folder)/opt_net/$(subnet_folder)/($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
        write(f, opt_net_string)
      end
      if !isdir("$(output_folder)/res_summary/$(subnet_folder)")
            mkpath("$(output_folder)/res_summary/$(subnet_folder)")
      end
      res_summary_string = JSON.json(res_summary_dict)
      open("$(output_folder)/res_summary/$(subnet_folder)/($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
        write(f, res_summary_string)
      end

      return opt_net, res_summary_dict, solution_pm_dict
end

function hour_opf_modified(month_st, day_st, period_st, month_en, day_en, period_en,
      folder:: String,
      subnet_folder:: String,
      output_folder::String,
      obj::String,
      gen_areas = [],
      area_transfer = [],
      plot_results::Bool = true,
      print_results::Bool = false)
      # override_param::Dict{Any}=Dict(),
      # fix_f_override::Bool=false,
      # direct_pq::Bool=true,
      # master_subnet::Int64=1,
      # suffix::String=""
      dir = "$(folder)/"
      #file_name = "$(dir)/RTS_GMLC.m"

      # hour_data: store the data
      hour_data = Dict{Int64, Any}

      # time range
      start_t = DateTime(2020,month_st,day_st,period_st)
      end_t = DateTime(2020,month_en,day_en,period_en)
      range_t = start_t:Hour(1):end_t

      # input the corresponding data of the specific subnetwork
      sub_folder = "$(folder)/enum_net_new/$(subnet_folder)"
      println("reading the file $(sub_folder)")
      hour_data = hour_parse(month_st, day_st, period_st, month_en, day_en, period_en, sub_folder)

      # s = JSON.json(hour_data)
      # open("test_hour_cost.json", "w") do f
      # write(f, s)
      # end

      # dictionaries store the output of the optimization for each period.
      opt_net = Dict()
      res_summary_dict = Dict()
      solution_pm_dict = Dict()
      hour_mn_data = Dict()
      objective_val = Dict()

      # output binding constraints
      binding_constraints = Dict()

      # manually used enumerate_branch to make data of subnetworks from RTS_GMLC.m before
      # running the make_mn_data function
      for i in 1:size(range_t, 1)
            #for j in 1:119
                  #subnetwork_file = "$(dir)/enum_nets/br$(j)/subnetworks.csv"
                  #interface_file = "$(dir)/enum_nets/br$(j)/interfaces.csv"
                  subnetwork_file = "$(dir)/enum_net_new/$(subnet_folder)/subnetworks.csv"
                  interface_file = "$(dir)/enum_net_new/$(subnet_folder)/interfaces.csv"
                  subnetworks = DataFrame(index=Int64[], file=String[], variable_f=Bool[],
                                          f_base=Float64[], f_min=Float64[], f_max=Float64[])
                  interfaces = DataFrame(index=Int64[], subnet_index=Int64[], bus=Int64[],
                                         s_max=Float64[], p_max=Float64[], q_max=Float64[],
                                         loss_param=Float64[])

                  # files ready for input to make_mn_data
                  subnetworks = CSV.read(subnetwork_file, DataFrame)
                  interfaces = CSV.read(interface_file, DataFrame)
                  hour_mn_data[i] = VariableFrequencyOPF.make_mn_data(subnetworks, interfaces, hour_data[i])

                  (opt_net[i], res_summary_dict[i], solution_pm_dict[i]) =
                  VariableFrequencyOPF.multifrequency_opf(hour_mn_data[i], output_folder, obj, gen_areas, area_transfer, print_results)
                  # open("test_mn_cost_$(i).json", "w") do f
                  #  write(f, s1)
                  # end

                  # write the binding constriants to a new file (save binding constraints of each case individually)
                  binding_constraints_part = Dict()
                  open("$(output_folder)/binding_constraints.json", "r") do f
                        global binding_constraints_part
                        #bind = read(f, String)
                        binding_constraints_part = JSON.parse(f)
                        binding_constraints[i] = binding_constraints_part
                  end
                  #binding_constraints_json = JSON.json(binding_constraints)

            #end
      end

      # save files of outputs
      binding_constraints_json = JSON.json(binding_constraints)
      open("$(output_folder)/binding_constraints$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f1
            write(f1, binding_constraints_json)
      end

      opt_net_string = JSON.json(opt_net)
      open("$(output_folder)/opt_net_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
        write(f, opt_net_string)
      end

      res_summary_string = JSON.json(res_summary_dict)
      open("$(output_folder)/res_summary_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
        write(f, res_summary_string)
      end

      # solution_pm_string = JSON.json(solution_pm_dict)
      # open("$(output_folder)/solution_pm_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).json", "w") do f
      #   write(f, solution_pm_string)
      # end

      # plot of the results (optional, now we mostly use the function of multi_plot)
      # if plot_results == true
      #       objval = Array{Float64, 1}()
      #       objval_n = Array{Float64, 1}()
      #       size_obj = Array{DateTime, 1}()
      #       size_obj_n = Array{DateTime, 1}()
      #       for i in 1:size(range_t, 1)
      #             if res_summary_dict[i].status[1] in [LOCALLY_SOLVED, ALMOST_LOCALLY_SOLVED]
      #                   push!(objval, res_summary_dict[i]["objective"][1])
      #                   push!(size_obj, range_t[i])
      #             else
      #                   push!(objval_n, res_summary_dict[i]["objective"][1])
      #                   push!(size_obj_n, range_t[i])
      #             end
      #       end
      #       #size_obj_n = size(range_t, 1) - size_obj
      #       # below are the parts of plotting
      #       println(range_t)
      #       println(objval)
      #       gr()
      #       p = plot(size_obj, objval,  color = :blue, seriestype = :line, title = "optimal result plot",
      #                xlabel = "time/h", ylabel = "optimal $(obj)", right_margin = 10px)
      #       plot!(size_obj_n, objval_n, color = :red, seriestype = :scatter)
      #       # seriestype = :histogram
      #       #savefig(fn)
      #       #savefig(p, )
      #
      #       # save output plot file
      #       savefig(p, "$(output_folder)/opt_res_plot_$(subnet_folder)_($(month_st).$(day_st).$(period_st)__$(month_en).$(day_en).$(period_en)).pdf")
      # end

      return opt_net, res_summary_dict, solution_pm_dict
end


# This file is a part of JuliaFEM.
# License is MIT: see https://github.com/JuliaFEM/AbaqusReader.jl/blob/master/LICENSE

"""
    element_mapping

This mapping table contains information what node ids locally match
each side of element.
"""
const element_mapping = Dict(
    :Tet4 => Dict(
        :S1 => (:Tri3, [1, 3, 2]),
        :S2 => (:Tri3, [1, 2, 4]),
        :S3 => (:Tri3, [2, 3, 4]),
        :S4 => (:Tri3, [1, 4, 3])),
    :Tet10 => Dict(
        :S1 => (:Tri6, [1, 3, 2, 7, 6, 5]),
        :S2 => (:Tri6, [1, 2, 4, 5, 9, 8]),
        :S3 => (:Tri6, [2, 3, 4, 6, 10, 9]),
        :S4 => (:Tri6, [1, 4, 3, 8, 10, 7])),
    :Hex8 => Dict(
        :S1 => (:Quad4, [1, 2, 3, 4]),
        :S2 => (:Quad4, [5, 8, 7, 6]),
        :S3 => (:Quad4, [1, 5, 6, 2]),
        :S4 => (:Quad4, [2, 6, 7, 3]),
        :S5 => (:Quad4, [3, 7, 8, 4]),
        :S6 => (:Quad4, [4, 8, 5, 1])))

""" Given element code, element side and global connectivity, determine boundary
element. E.g. for Tet4 we have 4 sides S1..S4 and boundary element is of type Tri3.
"""
function create_surface_element(element_type::Symbol, element_side::Symbol,
    element_connectivity::Vector{Int64})

    if !haskey(element_mapping, element_type)
        error("Unable to find surface element for element of type ",
              "$element_type for side $element_side, update element ",
              "mapping table.")
    end

    if !haskey(element_mapping[element_type], element_side)
        error("Unable to find child element side mapping for element ",
              "of type $element_type for side $element_side, update ",
              "element mapping table.")
    end

    surfel, surfel_lconn = element_mapping[element_type][element_side]
    surfel_gconn = element_connectivity[surfel_lconn]
    return surfel, surfel_gconn
end

"""
    create_surface_elements(mesh, surface_name)

Create surface elements for `surface` using mesh `mesh`.
Mesh can be obtained by using `abaqus_read_mesh`.
"""
function create_surface_elements(mesh::Dict, surface_name::String)
    surface = mesh["surface_sets"][surface_name]
    elements = mesh["elements"]
    eltypes = mesh["element_types"]
    result = Tuple{Symbol, Vector{Integer}}[]
    for (elid, side) in surface
        surface_element = create_surface_element(eltypes[elid], side, elements[elid])
        push!(result, surface_element)
    end
    return result
end


using DiffEqJump: AbstractAggregatorAlgorithm, NullAggregator

PDMPProblem(prob,jumps::ConstantRateJump;kwargs...) = PDMPProblem(prob,JumpSet(jumps);kwargs...)
PDMPProblem(prob,jumps::VariableRateJump;kwargs...) = PDMPProblem(prob,JumpSet(jumps);kwargs...)
PDMPProblem(prob,jumps::RegularJump;kwargs...) = PDMPProblem(prob,JumpSet(jumps);kwargs...)
PDMPProblem(prob,jumps::MassActionJump;kwargs...) = PDMPProblem(prob,JumpSet(jumps);kwargs...)
PDMPProblem(prob,jumps::DiffEqJump.AbstractJump...;kwargs...) = PDMPProblem(prob,JumpSet(jumps...);kwargs...)

PDMPProblem(prob,aggregator::AbstractAggregatorAlgorithm,jumps::ConstantRateJump;kwargs...) = PDMPProblem(prob,aggregator,JumpSet(jumps);kwargs...)
PDMPProblem(prob,aggregator::AbstractAggregatorAlgorithm,jumps::VariableRateJump;kwargs...) = PDMPProblem(prob,aggregator,JumpSet(jumps);kwargs...)
PDMPProblem(prob,aggregator::AbstractAggregatorAlgorithm,jumps::RegularJump;kwargs...) = PDMPProblem(prob,aggregator,JumpSet(jumps);kwargs...)
PDMPProblem(prob,aggregator::AbstractAggregatorAlgorithm,jumps::MassActionJump;kwargs...) = PDMPProblem(prob,aggregator,JumpSet(jumps);kwargs...)
PDMPProblem(prob,aggregator::AbstractAggregatorAlgorithm,jumps::DiffEqJump.AbstractJump...;kwargs...) = PDMPProblem(prob,aggregator,JumpSet(jumps...);kwargs...)
PDMPProblem(prob,jumps::JumpSet;kwargs...) = PDMPProblem(prob,NullAggregator(),jumps;kwargs...)

struct DiffeqJumpWrapper
	diffeqpb
	jumps
	u
end

# encode the vector field
function (wrap::DiffeqJumpWrapper)(ẋ, xc, xd, p, t::Float64)
	wrap.diffeqpb.f(ẋ, xc, p, t)
	nothing
end

# encode the rate function
function (wrap::DiffeqJumpWrapper)(rate, xc, xd, p, t::Float64, issum::Bool)
	for ii in eachindex(rate)
		rate[ii] = wrap.jumps.variable_jumps[ii].rate(xc, p, t)
	end
	return sum(rate)
end

# encode the jump function
function (wrap::DiffeqJumpWrapper)(xc, xd, p, t::Float64, ind_reaction::Int64)
	# this is a hack to be able to call affect! from DiffEqJump which requires an integrator as an argument. But if the type of affect! is not enforced, it should work
	@inbounds for ii=1:length(wrap.u)
		wrap.u[ii] = xc[ii]
	end
	wrap.jumps.variable_jumps[ind_reaction].affect!(wrap)
	@inbounds for ii=1:length(wrap.u)
		xc[ii] = wrap.u[ii]
	end
	nothing
end

function PDMPProblem(prob, aggregator::AbstractAggregatorAlgorithm, jumps::JumpSet;
	save_positions = typeof(prob) <: DiffEqBase.AbstractDiscreteProblem ? (false,true) : (true, true), kwargs...)

	@assert isinplace(prob) "The current interface requires the ODE to be written inplace"
	@assert jumps.regular_jump == nothing
	@assert jumps.massaction_jump == nothing

	pb_wrapper = DiffeqJumpWrapper(prob, jumps, copy(prob.u0))

	# get PDMP characteristics
	F = (xdot,xc,xd,p,t::Float64) -> pb_wrapper(xdot,xc,xd,p,t)
	R = (rate,xc,xd,p,t::Float64,issum::Bool) -> pb_wrapper(rate,xc,xd,p,t,issum)
	Delta = (xc,xd,p,t::Float64,ind_reaction::Int) -> pb_wrapper(xc,xd,p,t,ind_reaction)

	xc0 = copy(prob.u0)

	xd0 = [0]
	tspan = prob.tspan
	p = prob.p

	# determine the number of reactions
	nb_reactions = length(jumps.variable_jumps)

	return PDMPProblem(F,R,Delta,nb_reactions,xc0,xd0,p,tspan)
end


struct TriangleMesh_2D <: LinearUnstructuredMesh_2D
    name::String
    points::Vector{Point_2D}
    edges::Vector{SVector{2, UInt32}}
    materialized_edges::Vector{LineSegment_2D}
    faces::Vector{SVector{3, UInt32}}
    materialized_faces::Vector{Triangle_2D}
    edge_face_connectivity::Vector{SVector{2, UInt32}}
    face_edge_connectivity::Vector{SVector{3, UInt32}}
    boundary_edges::Vector{Vector{UInt32}}
    face_sets::Dict{String, Set{UInt32}}
end

function TriangleMesh_2D(;
        name::String = "DefaultMeshName",
        points::Vector{Point_2D} = Point_2D[],
        edges::Vector{SVector{2, UInt32}} = SVector{2, UInt32}[],
        materialized_edges::Vector{LineSegment_2D} = LineSegment_2D[],
        faces::Vector{SVector{3, UInt32}} = SVector{3, U}[],
        materialized_faces::Vector{Triangle_2D} = Triangle_2D[],
        edge_face_connectivity::Vector{SVector{2, UInt32}} = SVector{2, UInt32}[],
        face_edge_connectivity ::Vector{SVector{3, UInt32}} = SVector{3, UInt32}[],
        boundary_edges::Vector{Vector{UInt32}} = Vector{UInt32}[],
        face_sets::Dict{String, Set{UInt32}} = Dict{String, Set{UInt32}}()
    )
        return TriangleMesh_2D(name,
                               points,
                               edges,
                               materialized_edges,
                               faces,
                               materialized_faces,
                               edge_face_connectivity,
                               face_edge_connectivity,
                               boundary_edges,
                               face_sets,
                              )
end

Base.broadcastable(mesh::TriangleMesh_2D) = Ref(mesh)

struct QuadrilateralMesh_2D <: LinearUnstructuredMesh_2D
    name::String
    points::Vector{Point_2D}
    edges::Vector{SVector{2, UInt32}}
    materialized_edges::Vector{LineSegment_2D}
    faces::Vector{SVector{4, UInt32}}
    materialized_faces::Vector{Quadrilateral_2D}
    edge_face_connectivity::Vector{SVector{2, UInt32}}
    face_edge_connectivity::Vector{SVector{4, UInt32}}
    boundary_edges::Vector{Vector{UInt32}}
    face_sets::Dict{String, Set{UInt32}}
end

function QuadrilateralMesh_2D(;
        name::String = "DefaultMeshName",
        points::Vector{Point_2D} = Point_2D[],
        edges::Vector{SVector{2, UInt32}} = SVector{2, UInt32}[],
        materialized_edges::Vector{LineSegment_2D} = LineSegment_2D[],
        faces::Vector{SVector{4, UInt32}} = SVector{4, U}[],
        materialized_faces::Vector{Quadrilateral_2D} = Quadrilateral_2D[],
        edge_face_connectivity::Vector{SVector{2, UInt32}} = SVector{2, UInt32}[],
        face_edge_connectivity ::Vector{SVector{4, UInt32}} = SVector{4, UInt32}[],
        boundary_edges::Vector{Vector{UInt32}} = Vector{UInt32}[],
        face_sets::Dict{String, Set{UInt32}} = Dict{String, Set{UInt32}}()
    )
        return QuadrilateralMesh_2D(name,
                                    points,
                                    edges,
                                    materialized_edges,
                                    faces,
                                    materialized_faces,
                                    edge_face_connectivity,
                                    face_edge_connectivity,
                                    boundary_edges,
                                    face_sets,
                                   )
end

Base.broadcastable(mesh::QuadrilateralMesh_2D) = Ref(mesh)

# Axis-aligned bounding box, in 2d a rectangle.
function boundingbox(mesh::LinearUnstructuredMesh_2D; boundary_shape::String="Unknown")
    # If the mesh does not have any quadratic faces, the bounding_box may be determined
    # entirely from the points.
    nsides = length(mesh.boundary_edges)
    if nsides !== 0
        boundary_edge_IDs = reduce(vcat, mesh.boundary_edges)
        point_IDs = reduce(vcat, mesh.edges[boundary_edge_IDs])
        return boundingbox(mesh.points[point_IDs])
    else
        return boundingbox(mesh.points)
    end
end

# A vector of SVectors, denoting the edge ID each face is connected to.
function face_edge_connectivity(mesh::QuadrilateralMesh_2D)
    if length(mesh.edges) === 0
        @error "Mesh does not have edges!"
    end
    # A vector of MVectors of zeros for each face
    # Each MVector is the length of the number of edges
    face_edge = [MVector{4, UInt32}(0, 0, 0, 0) for _ in eachindex(mesh.faces)]
    # For each face in the mesh, generate the edges.
    # Search for the index of the edge in the mesh.edges vector
    # Insert the index of the edge into the face_edge connectivity vector
    for i in eachindex(mesh.faces)
        for (j, edge) in enumerate(edges(mesh.faces[i]))
            face_edge[i][j] = searchsortedfirst(mesh.edges, SVector(edge.data))
        end
        if any(x->x === 0x00000000, face_edge[i])
            @error "Could not determine the face/edge connectivity of face $i"
        end
    end
    return [SVector(sort(conn).data) for conn in face_edge]
end

# A vector of SVectors, denoting the edge ID each face is connected to.
function face_edge_connectivity(mesh::TriangleMesh_2D)
    if length(mesh.edges) === 0
        @error "Mesh does not have edges!"
    end
    # A vector of MVectors of zeros for each face
    # Each MVector is the length of the number of edges
    face_edge = [MVector{3, UInt32}(0, 0, 0) for _ in eachindex(mesh.faces)]
    # For each face in the mesh, generate the edges.
    # Search for the index of the edge in the mesh.edges vector
    # Insert the index of the edge into the face_edge connectivity vector
    for i in eachindex(mesh.faces)
        for (j, edge) in enumerate(edges(mesh.faces[i]))
            face_edge[i][j] = searchsortedfirst(mesh.edges, SVector(edge.data))
        end
        if any(x->x === 0x00000000, face_edge[i])
            @error "Could not determine the face/edge connectivity of face $i"
        end
    end
    return [SVector(sort(conn).data) for conn in face_edge]
end


# Julia wrapper for header: gswteos-10.h
# Automatically generated using Clang.jl

function gsw_add_barrier(input_data, lon, lat, long_grid, lat_grid, dlong_grid, dlat_grid, output_data)
    ccall((:gsw_add_barrier, libgswteos), Cvoid, (Ptr{Cdouble}, Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}), input_data, lon, lat, long_grid, lat_grid, dlong_grid, dlat_grid, output_data)
end

function gsw_add_mean(data_in, data_out)
    ccall((:gsw_add_mean, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}), data_in, data_out)
end

function gsw_adiabatic_lapse_rate_from_ct(sa, ct, p)
    ccall((:gsw_adiabatic_lapse_rate_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_adiabatic_lapse_rate_ice(t, p)
    ccall((:gsw_adiabatic_lapse_rate_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_alpha(sa, ct, p)
    ccall((:gsw_alpha, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_alpha_on_beta(sa, ct, p)
    ccall((:gsw_alpha_on_beta, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_alpha_wrt_t_exact(sa, t, p)
    ccall((:gsw_alpha_wrt_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_alpha_wrt_t_ice(t, p)
    ccall((:gsw_alpha_wrt_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_beta_const_t_exact(sa, t, p)
    ccall((:gsw_beta_const_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_beta(sa, ct, p)
    ccall((:gsw_beta, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_cabbeling(sa, ct, p)
    ccall((:gsw_cabbeling, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_c_from_sp(sp, t, p)
    ccall((:gsw_c_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sp, t, p)
end

function gsw_chem_potential_water_ice(t, p)
    ccall((:gsw_chem_potential_water_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_chem_potential_water_t_exact(sa, t, p)
    ccall((:gsw_chem_potential_water_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_cp_ice(t, p)
    ccall((:gsw_cp_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_cp_t_exact(sa, t, p)
    ccall((:gsw_cp_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_ct_first_derivatives(sa, pt, ct_sa, ct_pt)
    ccall((:gsw_ct_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, pt, ct_sa, ct_pt)
end

function gsw_ct_first_derivatives_wrt_t_exact(sa, t, p, ct_sa_wrt_t, ct_t_wrt_t, ct_p_wrt_t)
    ccall((:gsw_ct_first_derivatives_wrt_t_exact, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, t, p, ct_sa_wrt_t, ct_t_wrt_t, ct_p_wrt_t)
end

function gsw_ct_freezing(sa, p, saturation_fraction)
    ccall((:gsw_ct_freezing, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_ct_freezing_first_derivatives(sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
    ccall((:gsw_ct_freezing_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
end

function gsw_ct_freezing_first_derivatives_poly(sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
    ccall((:gsw_ct_freezing_first_derivatives_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, ctfreezing_sa, ctfreezing_p)
end

function gsw_ct_freezing_poly(sa, p, saturation_fraction)
    ccall((:gsw_ct_freezing_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_ct_from_enthalpy(sa, h, p)
    ccall((:gsw_ct_from_enthalpy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, h, p)
end

function gsw_ct_from_enthalpy_exact(sa, h, p)
    ccall((:gsw_ct_from_enthalpy_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, h, p)
end

function gsw_ct_from_entropy(sa, entropy)
    ccall((:gsw_ct_from_entropy, libgswteos), Cdouble, (Cdouble, Cdouble), sa, entropy)
end

function gsw_ct_from_pt(sa, pt)
    ccall((:gsw_ct_from_pt, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt)
end

function gsw_ct_from_rho(rho, sa, p, ct, ct_multiple)
    ccall((:gsw_ct_from_rho, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), rho, sa, p, ct, ct_multiple)
end

function gsw_ct_from_t(sa, t, p)
    ccall((:gsw_ct_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_ct_maxdensity(sa, p)
    ccall((:gsw_ct_maxdensity, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_ct_second_derivatives(sa, pt, ct_sa_sa, ct_sa_pt, ct_pt_pt)
    ccall((:gsw_ct_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, pt, ct_sa_sa, ct_sa_pt, ct_pt_pt)
end

function gsw_deltasa_atlas(p, lon, lat)
    ccall((:gsw_deltasa_atlas, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), p, lon, lat)
end

function gsw_deltasa_from_sp(sp, p, lon, lat)
    ccall((:gsw_deltasa_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sp, p, lon, lat)
end

function gsw_dilution_coefficient_t_exact(sa, t, p)
    ccall((:gsw_dilution_coefficient_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_dynamic_enthalpy(sa, ct, p)
    ccall((:gsw_dynamic_enthalpy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_enthalpy_ct_exact(sa, ct, p)
    ccall((:gsw_enthalpy_ct_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_enthalpy_diff(sa, ct, p_shallow, p_deep)
    ccall((:gsw_enthalpy_diff, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p_shallow, p_deep)
end

function gsw_enthalpy(sa, ct, p)
    ccall((:gsw_enthalpy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_enthalpy_first_derivatives_ct_exact(sa, ct, p, h_sa, h_ct)
    ccall((:gsw_enthalpy_first_derivatives_ct_exact, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa, h_ct)
end

function gsw_enthalpy_first_derivatives(sa, ct, p, h_sa, h_ct)
    ccall((:gsw_enthalpy_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa, h_ct)
end

function gsw_enthalpy_ice(t, p)
    ccall((:gsw_enthalpy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_enthalpy_second_derivatives_ct_exact(sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
    ccall((:gsw_enthalpy_second_derivatives_ct_exact, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
end

function gsw_enthalpy_second_derivatives(sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
    ccall((:gsw_enthalpy_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, h_sa_sa, h_sa_ct, h_ct_ct)
end

function gsw_enthalpy_sso_0(p)
    ccall((:gsw_enthalpy_sso_0, libgswteos), Cdouble, (Cdouble,), p)
end

function gsw_enthalpy_t_exact(sa, t, p)
    ccall((:gsw_enthalpy_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_entropy_first_derivatives(sa, ct, eta_sa, eta_ct)
    ccall((:gsw_entropy_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, eta_sa, eta_ct)
end

function gsw_entropy_from_ct(sa, ct)
    ccall((:gsw_entropy_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_entropy_from_pt(sa, pt)
    ccall((:gsw_entropy_from_pt, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt)
end

function gsw_entropy_from_t(sa, t, p)
    ccall((:gsw_entropy_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_entropy_ice(t, p)
    ccall((:gsw_entropy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_entropy_part(sa, t, p)
    ccall((:gsw_entropy_part, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_entropy_part_zerop(sa, pt0)
    ccall((:gsw_entropy_part_zerop, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt0)
end

function gsw_entropy_second_derivatives(sa, ct, eta_sa_sa, eta_sa_ct, eta_ct_ct)
    ccall((:gsw_entropy_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, eta_sa_sa, eta_sa_ct, eta_ct_ct)
end

function gsw_fdelta(p, lon, lat)
    ccall((:gsw_fdelta, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), p, lon, lat)
end

function gsw_frazil_properties(sa_bulk, h_bulk, p, sa_final, ct_final, w_ih_final)
    ccall((:gsw_frazil_properties, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa_bulk, h_bulk, p, sa_final, ct_final, w_ih_final)
end

function gsw_frazil_properties_potential(sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
    ccall((:gsw_frazil_properties_potential, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
end

function gsw_frazil_properties_potential_poly(sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
    ccall((:gsw_frazil_properties_potential_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa_bulk, h_pot_bulk, p, sa_final, ct_final, w_ih_final)
end

function gsw_frazil_ratios_adiabatic(sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
    ccall((:gsw_frazil_ratios_adiabatic, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
end

function gsw_frazil_ratios_adiabatic_poly(sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
    ccall((:gsw_frazil_ratios_adiabatic_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, w_ih, dsa_dct_frazil, dsa_dp_frazil, dct_dp_frazil)
end

function gsw_geo_strf_dyn_height(sa, ct, p, p_ref, n_levels, dyn_height)
    ccall((:gsw_geo_strf_dyn_height, libgswteos), Ptr{Cdouble}, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cdouble, Cint, Ptr{Cdouble}), sa, ct, p, p_ref, n_levels, dyn_height)
end

function gsw_geo_strf_dyn_height_1(sa, ct, p, p_ref, n_levels, dyn_height, max_dp_i, interp_method)
    ccall((:gsw_geo_strf_dyn_height_1, libgswteos), Cint, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cdouble, Cint, Ptr{Cdouble}, Cdouble, Cint), sa, ct, p, p_ref, n_levels, dyn_height, max_dp_i, interp_method)
end

function gsw_geo_strf_dyn_height_pc(sa, ct, delta_p, n_levels, geo_strf_dyn_height_pc, p_mid)
    ccall((:gsw_geo_strf_dyn_height_pc, libgswteos), Ptr{Cdouble}, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, delta_p, n_levels, geo_strf_dyn_height_pc, p_mid)
end

function gsw_gibbs_ice(nt, np, t, p)
    ccall((:gsw_gibbs_ice, libgswteos), Cdouble, (Cint, Cint, Cdouble, Cdouble), nt, np, t, p)
end

function gsw_gibbs_ice_part_t(t, p)
    ccall((:gsw_gibbs_ice_part_t, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_gibbs_ice_pt0(pt0)
    ccall((:gsw_gibbs_ice_pt0, libgswteos), Cdouble, (Cdouble,), pt0)
end

function gsw_gibbs_ice_pt0_pt0(pt0)
    ccall((:gsw_gibbs_ice_pt0_pt0, libgswteos), Cdouble, (Cdouble,), pt0)
end

function gsw_gibbs(ns, nt, np, sa, t, p)
    ccall((:gsw_gibbs, libgswteos), Cdouble, (Cint, Cint, Cint, Cdouble, Cdouble, Cdouble), ns, nt, np, sa, t, p)
end

function gsw_gibbs_pt0_pt0(sa, pt0)
    ccall((:gsw_gibbs_pt0_pt0, libgswteos), Cdouble, (Cdouble, Cdouble), sa, pt0)
end

function gsw_grav(lat, p)
    ccall((:gsw_grav, libgswteos), Cdouble, (Cdouble, Cdouble), lat, p)
end

function gsw_helmholtz_energy_ice(t, p)
    ccall((:gsw_helmholtz_energy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_hill_ratio_at_sp2(t)
    ccall((:gsw_hill_ratio_at_sp2, libgswteos), Cdouble, (Cdouble,), t)
end

function gsw_ice_fraction_to_freeze_seawater(sa, ct, p, t_ih, sa_freeze, ct_freeze, w_ih)
    ccall((:gsw_ice_fraction_to_freeze_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, t_ih, sa_freeze, ct_freeze, w_ih)
end

function gsw_internal_energy(sa, ct, p)
    ccall((:gsw_internal_energy, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_internal_energy_ice(t, p)
    ccall((:gsw_internal_energy_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_ipv_vs_fnsquared_ratio(sa, ct, p, p_ref, nz, ipv_vs_fnsquared_ratio, p_mid)
    ccall((:gsw_ipv_vs_fnsquared_ratio, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cdouble, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, p_ref, nz, ipv_vs_fnsquared_ratio, p_mid)
end

function gsw_kappa_const_t_ice(t, p)
    ccall((:gsw_kappa_const_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_kappa(sa, ct, p)
    ccall((:gsw_kappa, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_kappa_ice(t, p)
    ccall((:gsw_kappa_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_kappa_t_exact(sa, t, p)
    ccall((:gsw_kappa_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_latentheat_evap_ct(sa, ct)
    ccall((:gsw_latentheat_evap_ct, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_latentheat_evap_t(sa, t)
    ccall((:gsw_latentheat_evap_t, libgswteos), Cdouble, (Cdouble, Cdouble), sa, t)
end

function gsw_latentheat_melting(sa, p)
    ccall((:gsw_latentheat_melting, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_linear_interp_sa_ct(sa, ct, p, np, p_i, npi, sa_i, ct_i)
    ccall((:gsw_linear_interp_sa_ct, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, np, p_i, npi, sa_i, ct_i)
end

function gsw_melting_ice_equilibrium_sa_ct_ratio(sa, p)
    ccall((:gsw_melting_ice_equilibrium_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_ice_equilibrium_sa_ct_ratio_poly(sa, p)
    ccall((:gsw_melting_ice_equilibrium_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_ice_into_seawater(sa, ct, p, w_ih, t_ih, sa_final, ct_final, w_ih_final)
    ccall((:gsw_melting_ice_into_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, w_ih, t_ih, sa_final, ct_final, w_ih_final)
end

function gsw_melting_ice_sa_ct_ratio(sa, ct, p, t_ih)
    ccall((:gsw_melting_ice_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, t_ih)
end

function gsw_melting_ice_sa_ct_ratio_poly(sa, ct, p, t_ih)
    ccall((:gsw_melting_ice_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, t_ih)
end

function gsw_melting_seaice_equilibrium_sa_ct_ratio(sa, p)
    ccall((:gsw_melting_seaice_equilibrium_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_seaice_equilibrium_sa_ct_ratio_poly(sa, p)
    ccall((:gsw_melting_seaice_equilibrium_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_melting_seaice_into_seawater(sa, ct, p, w_seaice, sa_seaice, t_seaice, sa_final, ct_final)
    ccall((:gsw_melting_seaice_into_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, w_seaice, sa_seaice, t_seaice, sa_final, ct_final)
end

function gsw_melting_seaice_sa_ct_ratio(sa, ct, p, sa_seaice, t_seaice)
    ccall((:gsw_melting_seaice_sa_ct_ratio, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, sa_seaice, t_seaice)
end

function gsw_melting_seaice_sa_ct_ratio_poly(sa, ct, p, sa_seaice, t_seaice)
    ccall((:gsw_melting_seaice_sa_ct_ratio_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, sa_seaice, t_seaice)
end

function gsw_nsquared(sa, ct, p, lat, nz, n2, p_mid)
    ccall((:gsw_nsquared, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, lat, nz, n2, p_mid)
end

function gsw_o2sol(sa, ct, p, lon, lat)
    ccall((:gsw_o2sol, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble), sa, ct, p, lon, lat)
end

function gsw_o2sol_sp_pt(sp, pt)
    ccall((:gsw_o2sol_sp_pt, libgswteos), Cdouble, (Cdouble, Cdouble), sp, pt)
end

function gsw_pot_enthalpy_from_pt_ice(pt0_ice)
    ccall((:gsw_pot_enthalpy_from_pt_ice, libgswteos), Cdouble, (Cdouble,), pt0_ice)
end

function gsw_pot_enthalpy_from_pt_ice_poly(pt0_ice)
    ccall((:gsw_pot_enthalpy_from_pt_ice_poly, libgswteos), Cdouble, (Cdouble,), pt0_ice)
end

function gsw_pot_enthalpy_ice_freezing(sa, p)
    ccall((:gsw_pot_enthalpy_ice_freezing, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_pot_enthalpy_ice_freezing_first_derivatives(sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
    ccall((:gsw_pot_enthalpy_ice_freezing_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
end

function gsw_pot_enthalpy_ice_freezing_first_derivatives_poly(sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
    ccall((:gsw_pot_enthalpy_ice_freezing_first_derivatives_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, pot_enthalpy_ice_freezing_sa, pot_enthalpy_ice_freezing_p)
end

function gsw_pot_enthalpy_ice_freezing_poly(sa, p)
    ccall((:gsw_pot_enthalpy_ice_freezing_poly, libgswteos), Cdouble, (Cdouble, Cdouble), sa, p)
end

function gsw_pot_rho_t_exact(sa, t, p, p_ref)
    ccall((:gsw_pot_rho_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, t, p, p_ref)
end

function gsw_pressure_coefficient_ice(t, p)
    ccall((:gsw_pressure_coefficient_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_pressure_freezing_ct(sa, ct, saturation_fraction)
    ccall((:gsw_pressure_freezing_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, saturation_fraction)
end

function gsw_pt0_cold_ice_poly(pot_enthalpy_ice)
    ccall((:gsw_pt0_cold_ice_poly, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt0_from_t(sa, t, p)
    ccall((:gsw_pt0_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_pt0_from_t_ice(t, p)
    ccall((:gsw_pt0_from_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_pt_first_derivatives(sa, ct, pt_sa, pt_ct)
    ccall((:gsw_pt_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, pt_sa, pt_ct)
end

function gsw_pt_from_ct(sa, ct)
    ccall((:gsw_pt_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_pt_from_entropy(sa, entropy)
    ccall((:gsw_pt_from_entropy, libgswteos), Cdouble, (Cdouble, Cdouble), sa, entropy)
end

function gsw_pt_from_pot_enthalpy_ice(pot_enthalpy_ice)
    ccall((:gsw_pt_from_pot_enthalpy_ice, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt_from_pot_enthalpy_ice_poly_dh(pot_enthalpy_ice)
    ccall((:gsw_pt_from_pot_enthalpy_ice_poly_dh, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt_from_pot_enthalpy_ice_poly(pot_enthalpy_ice)
    ccall((:gsw_pt_from_pot_enthalpy_ice_poly, libgswteos), Cdouble, (Cdouble,), pot_enthalpy_ice)
end

function gsw_pt_from_t(sa, t, p, p_ref)
    ccall((:gsw_pt_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, t, p, p_ref)
end

function gsw_pt_from_t_ice(t, p, p_ref)
    ccall((:gsw_pt_from_t_ice, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), t, p, p_ref)
end

function gsw_pt_second_derivatives(sa, ct, pt_sa_sa, pt_sa_ct, pt_ct_ct)
    ccall((:gsw_pt_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, pt_sa_sa, pt_sa_ct, pt_ct_ct)
end

function gsw_rho_alpha_beta(sa, ct, p, rho, alpha, beta)
    ccall((:gsw_rho_alpha_beta, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho, alpha, beta)
end

function gsw_rho(sa, ct, p)
    ccall((:gsw_rho, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_rho_first_derivatives(sa, ct, p, drho_dsa, drho_dct, drho_dp)
    ccall((:gsw_rho_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, drho_dsa, drho_dct, drho_dp)
end

function gsw_rho_first_derivatives_wrt_enthalpy(sa, ct, p, rho_sa, rho_h)
    ccall((:gsw_rho_first_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho_sa, rho_h)
end

function gsw_rho_ice(t, p)
    ccall((:gsw_rho_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_rho_second_derivatives(sa, ct, p, rho_sa_sa, rho_sa_ct, rho_ct_ct, rho_sa_p, rho_ct_p)
    ccall((:gsw_rho_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho_sa_sa, rho_sa_ct, rho_ct_ct, rho_sa_p, rho_ct_p)
end

function gsw_rho_second_derivatives_wrt_enthalpy(sa, ct, p, rho_sa_sa, rho_sa_h, rho_h_h)
    ccall((:gsw_rho_second_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, rho_sa_sa, rho_sa_h, rho_h_h)
end

function gsw_rho_t_exact(sa, t, p)
    ccall((:gsw_rho_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_rr68_interp_sa_ct(sa, ct, p, mp, p_i, mp_i, sa_i, ct_i)
    ccall((:gsw_rr68_interp_sa_ct, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, mp, p_i, mp_i, sa_i, ct_i)
end

function gsw_saar(p, lon, lat)
    ccall((:gsw_saar, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), p, lon, lat)
end

function gsw_sa_freezing_estimate(p, saturation_fraction, ct, t)
    ccall((:gsw_sa_freezing_estimate, libgswteos), Cdouble, (Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), p, saturation_fraction, ct, t)
end

function gsw_sa_freezing_from_ct(ct, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), ct, p, saturation_fraction)
end

function gsw_sa_freezing_from_ct_poly(ct, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_ct_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), ct, p, saturation_fraction)
end

function gsw_sa_freezing_from_t(t, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_t, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), t, p, saturation_fraction)
end

function gsw_sa_freezing_from_t_poly(t, p, saturation_fraction)
    ccall((:gsw_sa_freezing_from_t_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), t, p, saturation_fraction)
end

function gsw_sa_from_rho(rho, ct, p)
    ccall((:gsw_sa_from_rho, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), rho, ct, p)
end

function gsw_sa_from_sp_baltic(sp, lon, lat)
    ccall((:gsw_sa_from_sp_baltic, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sp, lon, lat)
end

function gsw_sa_from_sp(sp, p, lon, lat)
    ccall((:gsw_sa_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sp, p, lon, lat)
end

function gsw_sa_from_sstar(sstar, p, lon, lat)
    ccall((:gsw_sa_from_sstar, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sstar, p, lon, lat)
end

function gsw_sa_p_inrange(sa, p)
    ccall((:gsw_sa_p_inrange, libgswteos), Cint, (Cdouble, Cdouble), sa, p)
end

function gsw_seaice_fraction_to_freeze_seawater(sa, ct, p, sa_seaice, t_seaice, sa_freeze, ct_freeze, w_seaice)
    ccall((:gsw_seaice_fraction_to_freeze_seawater, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, sa_seaice, t_seaice, sa_freeze, ct_freeze, w_seaice)
end

function gsw_sigma0(sa, ct)
    ccall((:gsw_sigma0, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma1(sa, ct)
    ccall((:gsw_sigma1, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma2(sa, ct)
    ccall((:gsw_sigma2, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma3(sa, ct)
    ccall((:gsw_sigma3, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sigma4(sa, ct)
    ccall((:gsw_sigma4, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sound_speed(sa, ct, p)
    ccall((:gsw_sound_speed, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_sound_speed_ice(t, p)
    ccall((:gsw_sound_speed_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_sound_speed_t_exact(sa, t, p)
    ccall((:gsw_sound_speed_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_specvol_alpha_beta(sa, ct, p, specvol, alpha, beta)
    ccall((:gsw_specvol_alpha_beta, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, specvol, alpha, beta)
end

function gsw_specvol_anom_standard(sa, ct, p)
    ccall((:gsw_specvol_anom_standard, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_specvol(sa, ct, p)
    ccall((:gsw_specvol, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_specvol_first_derivatives(sa, ct, p, v_sa, v_ct, v_p)
    ccall((:gsw_specvol_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa, v_ct, v_p)
end

function gsw_specvol_first_derivatives_wrt_enthalpy(sa, ct, p, v_sa, v_h)
    ccall((:gsw_specvol_first_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa, v_h)
end

function gsw_specvol_ice(t, p)
    ccall((:gsw_specvol_ice, libgswteos), Cdouble, (Cdouble, Cdouble), t, p)
end

function gsw_specvol_second_derivatives(sa, ct, p, v_sa_sa, v_sa_ct, v_ct_ct, v_sa_p, v_ct_p)
    ccall((:gsw_specvol_second_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa_sa, v_sa_ct, v_ct_ct, v_sa_p, v_ct_p)
end

function gsw_specvol_second_derivatives_wrt_enthalpy(sa, ct, p, v_sa_sa, v_sa_h, v_h_h)
    ccall((:gsw_specvol_second_derivatives_wrt_enthalpy, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, v_sa_sa, v_sa_h, v_h_h)
end

function gsw_specvol_sso_0(p)
    ccall((:gsw_specvol_sso_0, libgswteos), Cdouble, (Cdouble,), p)
end

function gsw_specvol_t_exact(sa, t, p)
    ccall((:gsw_specvol_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_sp_from_c(c, t, p)
    ccall((:gsw_sp_from_c, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), c, t, p)
end

function gsw_sp_from_sa_baltic(sa, lon, lat)
    ccall((:gsw_sp_from_sa_baltic, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, lon, lat)
end

function gsw_sp_from_sa(sa, p, lon, lat)
    ccall((:gsw_sp_from_sa, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, p, lon, lat)
end

function gsw_sp_from_sk(sk)
    ccall((:gsw_sp_from_sk, libgswteos), Cdouble, (Cdouble,), sk)
end

function gsw_sp_from_sr(sr)
    ccall((:gsw_sp_from_sr, libgswteos), Cdouble, (Cdouble,), sr)
end

function gsw_sp_from_sstar(sstar, p, lon, lat)
    ccall((:gsw_sp_from_sstar, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sstar, p, lon, lat)
end

function gsw_sp_salinometer(rt, t)
    ccall((:gsw_sp_salinometer, libgswteos), Cdouble, (Cdouble, Cdouble), rt, t)
end

function gsw_spiciness0(sa, ct)
    ccall((:gsw_spiciness0, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_spiciness1(sa, ct)
    ccall((:gsw_spiciness1, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_spiciness2(sa, ct)
    ccall((:gsw_spiciness2, libgswteos), Cdouble, (Cdouble, Cdouble), sa, ct)
end

function gsw_sr_from_sp(sp)
    ccall((:gsw_sr_from_sp, libgswteos), Cdouble, (Cdouble,), sp)
end

function gsw_sstar_from_sa(sa, p, lon, lat)
    ccall((:gsw_sstar_from_sa, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sa, p, lon, lat)
end

function gsw_sstar_from_sp(sp, p, lon, lat)
    ccall((:gsw_sstar_from_sp, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), sp, p, lon, lat)
end

function gsw_t_deriv_chem_potential_water_t_exact(sa, t, p)
    ccall((:gsw_t_deriv_chem_potential_water_t_exact, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, t, p)
end

function gsw_t_freezing(sa, p, saturation_fraction)
    ccall((:gsw_t_freezing, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_t_freezing_first_derivatives_poly(sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
    ccall((:gsw_t_freezing_first_derivatives_poly, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
end

function gsw_t_freezing_first_derivatives(sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
    ccall((:gsw_t_freezing_first_derivatives, libgswteos), Cvoid, (Cdouble, Cdouble, Cdouble, Ptr{Cdouble}, Ptr{Cdouble}), sa, p, saturation_fraction, tfreezing_sa, tfreezing_p)
end

function gsw_t_freezing_poly(sa, p, saturation_fraction)
    ccall((:gsw_t_freezing_poly, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, p, saturation_fraction)
end

function gsw_t_from_ct(sa, ct, p)
    ccall((:gsw_t_from_ct, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_t_from_pt0_ice(pt0_ice, p)
    ccall((:gsw_t_from_pt0_ice, libgswteos), Cdouble, (Cdouble, Cdouble), pt0_ice, p)
end

function gsw_thermobaric(sa, ct, p)
    ccall((:gsw_thermobaric, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble), sa, ct, p)
end

function gsw_turner_rsubrho(sa, ct, p, nz, tu, rsubrho, p_mid)
    ccall((:gsw_turner_rsubrho, libgswteos), Cvoid, (Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Ptr{Cdouble}), sa, ct, p, nz, tu, rsubrho, p_mid)
end

function gsw_util_indx(x, n, z)
    ccall((:gsw_util_indx, libgswteos), Cint, (Ptr{Cdouble}, Cint, Cdouble), x, n, z)
end

function gsw_util_interp1q_int(nx, x, iy, nxi, x_i, y_i)
    ccall((:gsw_util_interp1q_int, libgswteos), Ptr{Cdouble}, (Cint, Ptr{Cdouble}, Ptr{Cint}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), nx, x, iy, nxi, x_i, y_i)
end

function gsw_util_linear_interp(nx, x, ny, y, nxi, x_i, y_i)
    ccall((:gsw_util_linear_interp, libgswteos), Ptr{Cdouble}, (Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}), nx, x, ny, y, nxi, x_i, y_i)
end

function gsw_util_sort_real(rarray, nx, iarray)
    ccall((:gsw_util_sort_real, libgswteos), Cvoid, (Ptr{Cdouble}, Cint, Ptr{Cint}), rarray, nx, iarray)
end

function gsw_util_xinterp1(x, y, n, x0)
    ccall((:gsw_util_xinterp1, libgswteos), Cdouble, (Ptr{Cdouble}, Ptr{Cdouble}, Cint, Cdouble), x, y, n, x0)
end

function gsw_util_pchip_interp(x, y, n, xi, yi, ni)
    ccall((:gsw_util_pchip_interp, libgswteos), Cint, (Ptr{Cdouble}, Ptr{Cdouble}, Cint, Ptr{Cdouble}, Ptr{Cdouble}, Cint), x, y, n, xi, yi, ni)
end

function gsw_z_from_p(p, lat, geo_strf_dyn_height, sea_surface_geopotential)
    ccall((:gsw_z_from_p, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), p, lat, geo_strf_dyn_height, sea_surface_geopotential)
end

function gsw_p_from_z(z, lat, geo_strf_dyn_height, sea_surface_geopotential)
    ccall((:gsw_p_from_z, libgswteos), Cdouble, (Cdouble, Cdouble, Cdouble, Cdouble), z, lat, geo_strf_dyn_height, sea_surface_geopotential)
end