User-Defined Algorithm

To define a new algorithm, we need to define a module for the new algorithm that contains the main solve function in addition to three algorithm-specific functions. The three algorithm-specific are: initialize, build, and update. You can follow the example in the template file.

The module of xx algorithm should be defined and exported as xx_methods as follows:

"""
template for xx distributed algorithm
"""
module xx_methods
using ..PowerModelsADA

### functions ###

end
# export the algorithm methods module and call method
export xx_methods, solve_dopf_xx

The solve function is the main method to use the xx algorithm. The function takes the data, power flow formulation (model_type), JuMP solver object, and algorithm's parameters as required. The solve function should use the pre-defined algorithm flow as follows:

"solve distributed OPF using xx algorithm"
function solve_method(data, model_type::DataType, optimizer; 
    mismatch_method::String="norm", tol::Float64=1e-4, max_iteration::Int64=1000, 
    print_level::Int64=1, parameters...)

    solve_dopf(data, model_type, optimizer, xx_methods; 
    mismatch_method=mismatch_method, tol=tol, max_iteration=max_iteration, 
    print_level=print_level, parameters...)
end

The first algorithm-specific function is the initialize function. The function takes the area data file and adds to it the required parameters, counters, and shared variables. There are multiple built-in functions in PowerModelsADA that can be used to define the shared and received variables, as well as the dual variables. Note that the initialization function should include the initialize_dopf! to define the counters and convergence flags. We use kwargs with the ... to combine the algorithm's parameters and pass them to the initialize_method.

"initialize the xx algorithm"
function initialize_method(data::Dict{String, <:Any}, model_type::Type; kwargs...)

    # initiate primal and dual shared variables
    data["shared_variable"] = Dict(to_area=> variable_name=> variable_index=> value)
    data["received_variable"] = Dict(from_area=> variable_name=>variable_index=> value)

    # distributed algorithm settings
    initialize_dopf!(data, model_type; kwargs...)

    # xx parameters
    data["parameter"] = Dict("alpha"=> get(kwargs, :alpha, 1000))

end

The second function is the build function, which builds the PowerModels object of the subproblem. The subproblems typically have the same variables and constraints as the central OPF problem and differ in the objective functions. To build a subproblem with the same variables and constraints as the central OPF problem with a specific objective function, we need to define the objective function using the template shown below. The objective function definition takes the PowerModels object and returns a JuMP expression. You can use the internal helper function _var to obtain the JuMP model variables' object defined in the PowerModels object.

"build PowerModel using xx algorithm"
function build_method(pm::AbstractPowerModel)

    # define variables
    variable_opf(pm)

    # define constraints
    constraint_opf(pm)
  
    # define objective function
    objective_min_fuel_and_consensus!(pm, objective_function)
end

"set the xx algorithm objective"
function objective_function(pm::AbstractPowerModel)

    # to get the JuMP object of the active power of generator 1 use:
    pg1 = _var(pm, :pg, 1)

    ###
    objective = pg1
    ###
    return objective
end

The last function is to update the area dictionary after communicating the shared variables results with other areas.

"update the xx algorithm before each iteration"
function update_method(data::Dict{String, <:Any})

    ### update subproblem parameters for the next iteration
    ###

    ### you can use predefined function to calculate the mismatches, check convergence, save progress etc. 

    calc_mismatch!(data, central=true)
    update_flag_convergence!(data)
    save_solution!(data)
    update_iteration!(data)
end

The final step is defining the post-processing functions and global keys. The post-processing functions perform tasks to the PowerModels object after solving the subproblem. PowerModelsADA comes with two post-processing functions. The first function updates the solution dictionary, and the second function updates the shared variables dictionary. The global keys are the keys that are used in the data area dictionary (related to the xx algorithm) and should be explicitly given by extending the existing _pmada_global_keys set of strings.

post_processors = [update_solution!, update_shared_variable!]

push!(_pmada_global_keys, "shared_variable", "received_variable", "dual_variable")

This is a general way to define a distributed algorithm that is fully distributed with the same main algorithm flow as the pre-defined algorithms. For other algorithm flows, the solve function needs to be defined fully instead of using the pre-define function solve_dopf.