diff --git a/.gitignore b/.gitignore index 381e0b6..3d7c8aa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,49 @@ -*.jl.cov -*.jl.*.cov -*.jl.mem -deps/deps.jl +src_ampl/*.dat +!src_ampl/minlp.dat +src_ampl/*/*.dat +doc/build/* +*.log +*.sol +profile_data/* +function_files/import_poly_dat.jl +Notes.md +src/Modeler/QCQP_ampl.jl +src_ampl/ampl_triang_carres.run +test.jl +/test_GOC2.dat +/case14.dat +/test_case14_scenario1.dat +/test_case14_scenario1_cc_active_power.dat +/case14_scenario1_with_cc_active_power.dat +/export_real_case14_scenario1_with_cc_active_power.dat +/export_real_case14_scenario1_without_cc_active_power.dat +/export_Smax_case14_scenario1.dat +src_ampl/minlp.dat +/WB2.dat +/src_ampl/minlp.dat +/export_cc_active_power_case14_scenario1.dat +/export_cc_reactive_power_case14_scenario1.dat +/Phase_0_IEEE14_scenario_1_real.dat +/case14_scenario1_only_voltage_variables.dat +/IEEE14_real.dat +Knitro_runs +instances/* +/instances/GOC/Phase_0_Feas179/scenario_1/pscopf_data.mat +/instances/GOC/Phase_0_Feas179/scenario_1/pscopf_data.mat +/instances/GOC/Phase_0_Feas179/scenario_1/pscopf_data.mat +instances/GOC/Phase_0_Feas179/scenario_1/pscopf_data.mat +RTS96_GOC_Knitro_solcomp.xlsx +instances/GOC/Phase_0_Feas179/scenario_1/pscopf_data.mat +instances/GOC/Phase_0_Feas179/scenario_1/pscopf_data.mat +instances/GOC/Phase_0_Feas179/scenario_1/pscopf_data.mat +src_ampl/minlp.dat +src_modeler/Modeler/QCQP_ampl.jl +/WB2.temp +/case9.temp +/GOCdata.csv +/GOCdata_v2.xlsx +/src_ampl/minlp.dat +/dev/resultsROADEF_Phase_0_IEEE14.csv +/dev/resultsROADEF_Phase_0_Modified_IEEE14.csv +/dev/resultsROADEF_Phase_0_Modified_RTS96.csv +/dev/resultsROADEF_Phase_0_RTS96.csv diff --git a/README.md b/README.md index ba37d1e..9bc423b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,98 @@ -# mathprogcomplex -Mathematical programming tool box for optimization in complex variables +# PowSysMod +The `PowSysMod` environment enables the construction of polynomial optimization problems in complex variables for power systems. It uses the `PolynomialOptim` environment. The environment can adapt to several input formats (Matpower, GOC, IIDM) if the user defines the proper functions (a read function and elements of the power network along with their parameters and constraints). Once the optimization problem is built, the problem can be exported in real numbers and in text files (to be treated by AMPL for example). It is also possible to convert the problem in a JuMP model in real numbers (cartesian or polar form). + + + + +## PolynomialOptim +The `PolynomialOptim` environment provides a structure and methods for working with **complex polynomial optimization problems** subject to **polynomial constraints**. It is based on a polynomial environment that allows to work on polynomial objects with natural operations (+, -, \*, conj, |.|). + +The base type is `Variable`, from which `Exponents`, `Monomial`, and `Polynomial` can be constructed by calling the respective constructors or with algebraic operations (+, -, \*, conj, |.|). The `Point` type holds the variables at which polynomials can be evaluated. + +Available functions on `Polynomial`, `Mononomial`, `Variable` types: +- isconst, isone +- evaluate +- abs2, conj +- is_homogeneous: tests if p(exp(iϕ)X) = p(X) ∀X∈R^n, Φ∈R (phase invariant equation) +- cplx2real: convert the provided object to a tuple of real and imaginary part, expressed with real and imaginary part variables. + +```julia +include(joinpath("src_PolynomialOptim", "PolynomialOptim.jl")) + +a = Variable("a", Complex) +b = Variable("b", Real) +p = a*conj(a) + b + 2 + +print(p) +# (2.0) + b + conj(a) * a + +pt = Point([a, b], [1+2im, 1+im]) +print(pt) +# a 1 + 2im +# b 1 + +pt = pt - Point([a], [1]) +print(pt) +# a 0 + 2im +# b 1 + +val = evaluate(p, pt) # 7 + 0im + +p_real, p_imag = cplx2real(p) +pt_r = cplx2real(pt) +val_real = evaluate(p_real, pt_r) # 7 + 0im +val_imag = evaluate(p_imag, pt_r) # 0 +``` + + +#### Polynomial problems +A `Constraint` structure holding a `Polynomial` and complex upper and lower bounds is defined to build the `Problem` type made of: +- several `Variables` +- a `Polynomial` objective, +- several constraints names along with their corresponding `Constraint` + +#### Implemented methods +- get_objective, set_objective! +- get_variables, get_variabletype, has_variable, add_variable! +- get_constraint, get_constraints, has_constraint, constraint_type, add_constraint!, rm_constraint! +- get_slacks, get_minslack +- pb_cplx2real: converts the problem variables, objective and constraints to real expressions function of real and imaginary part of the original problem variables. + +```julia +include(joinpath("src_PolynomialOptim", "PolynomialOptim.jl")) + +a = Variable("a", Complex) +b = Variable("b", Real) +p_obj = abs2(a) + abs2(b) + 2 +p_cstr1 = 3a + b + 2 +p_cstr2 = abs2(b) + 5a*b + 2 + +pb = Problem() + +add_variable!(pb, a); add_variable!(pb, b) + +set_objective!(pb, p_obj) + +add_constraint!(pb, "Cstr 1", p_cstr1 << 3+5im) # -∞ ≤ (2.0) + b^2 + (5.0) * a * b ≤ 2 + 6im +add_constraint!(pb, "Cstr 2", 2-im << p_cstr2 << 3+7im) # -∞ ≤ (2.0) + b^2 + (5.0) * a * b ≤ 2 + 6im +add_constraint!(pb, "Cstr 3", p_cstr2 == 0) # -∞ ≤ (2.0) + b^2 + (5.0) * a * b ≤ 2 + 6im + +print(pb) +# ▶ variables: a b +# ▶ objective: (2.0) + conj(a) * a + b^2 +# ▶ constraints: +# Cstr 1: -∞ < (2.0) + (3.0) * a + b < 3 + 5im +# Cstr 2: 2 - 1im < (2.0) + (5.0) * a * b + b^2 < 3 + 7im +# Cstr 3: (2.0) + (5.0) * a * b + b^2 = 0 + +pt_sol = Point([a, b], [1, 1+2im]) +print("slack by constraint at given point:\n", get_slacks(pb, pt_sol)) +# slack by constraint at given point: +# Cstr 1 -3.0 + 0.0im +# Cstr 2 -5 + 1im +# Cstr 3 -8 + 0im +``` diff --git a/dev/build_OPF_profile.jl b/dev/build_OPF_profile.jl new file mode 100644 index 0000000..be79bd8 --- /dev/null +++ b/dev/build_OPF_profile.jl @@ -0,0 +1,108 @@ +""" + Profiling for all matpower instances + To be adapted... +""" +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +struct Measurement + nb_buses + time + bytes + gctime + nbpoolallocs + nbmalloc + summarysize +end + +## Input +files_m = [split(name, ".")[1] for name in readdir(joinpath("instances", "matpower"))] +files_dat = [split(name, ".")[1] for name in readdir(joinpath("instances", "matpower_QCQP"))] + +instances = intersect(files_m, files_dat) + +nb_repeat = 3 + +instancename = sort(collect(instances))[1] + +data_OPFpbs = Dict{String, Measurement}() +data_Problems = Dict{String, Measurement}() +data_errors = Dict{String, Float64}() + +instnb = 1 +for instancename in sort(collect(instances)) + println("\n-- Working on $instancename ($instnb/$(length(instances)))") + nb_buses = parse(matchall(r"\d+", instancename)[1]) + + ## Building OPF_problems + t = bytes = gctime = nbpoolallocs = nbmalloc = sumsize = 0 + OPFpbs = build_OPFproblems(MatpowerSimpleInput, [joinpath("instances", "matpower", "$instancename.m")]) + for i=1:nb_repeat + print("$i ") + OPFpbs, t_, bytes_, gctime_, gc_ = @timed build_OPFproblems(MatpowerSimpleInput, [joinpath("instances", "matpower", "$instancename.m")]) + t += t_/nb_repeat + bytes += bytes_/nb_repeat + gctime += gctime_/nb_repeat + nbpoolallocs += gc_.poolalloc/nb_repeat + nbmalloc += gc_.malloc/nb_repeat + sumsize += Base.summarysize(OPFpbs)/nb_repeat + end + data_OPFpbs[instancename] = Measurement(nb_buses, t, bytes, gctime, nbpoolallocs, nbmalloc, sumsize) + + for (busname, busdata) in OPFpbs["BaseCase"].ds.bus + if haskey(busdata, "Gen_reference") + OPFpbs["BaseCase"].mc.node_formulations[busname]["Gen_reference"] = :None + end + end + + ## Building OPF problem + t = bytes = gctime = nbpoolallocs = nbmalloc = sumsize = 0 + pb = build_Problem!(OPFpbs, "BaseCase") + for i=1:nb_repeat + print("$i ") + pb, t_, bytes_, gctime_, gc_ = @timed build_Problem!(OPFpbs, "BaseCase") + t += t_/nb_repeat + bytes += bytes_/nb_repeat + gctime += gctime_/nb_repeat + nbpoolallocs += gc_.poolalloc/nb_repeat + nbmalloc += gc_.malloc/nb_repeat + sumsize += Base.summarysize(pb)/nb_repeat + end + data_Problems[instancename] = Measurement(nb_buses, t, bytes, gctime, nbpoolallocs, nbmalloc, sumsize) + + export_to_dat(pb, "my$instancename.dat") + filename_dat = joinpath("instances", "matpower_QCQP", "$instancename.dat") + error, _ = compare_dat("my$instancename.dat", filename_dat) + rm("my$instancename.dat") + data_errors[instancename] = error + + instnb+=1 +end + +# output +filename = "buildOPFpbs_$(Dates.format(now(), "yy_u_dd_HH_MM")).csv" +touch(filename) + +open(filename, "w") do outfile + print(outfile, "instancename;nb_buses;time;bytes;gctime;nbpoolallocs;nbmalloc;summarysize;dat_error\n") + + insancenames_ord = sort(collect(keys(data_OPFpbs)), by=x->data_OPFpbs[x].nb_buses) + + for instancename in insancenames_ord + measure = data_OPFpbs[instancename] + print(outfile, "$instancename;$(measure.nb_buses);$(measure.time);$(measure.bytes);$(measure.gctime);$(measure.nbpoolallocs);$(measure.nbmalloc);$(measure.summarysize);$(data_errors[instancename])\n") + end +end + +filename = "build_Problem_$(Dates.format(now(), "yy_u_dd_HH_MM")).csv" +touch(filename) + +open(filename, "w") do outfile + print(outfile, "instancename;nb_buses;time;bytes;gctime;nbpoolallocs;nbmalloc;summarysize;dat_error\n") + + insancenames_ord = sort(collect(keys(data_Problems)), by=x->data_Problems[x].nb_buses) + + for instancename in insancenames_ord + measure = data_Problems[instancename] + print(outfile, "$instancename;$(measure.nb_buses);$(measure.time);$(measure.bytes);$(measure.gctime);$(measure.nbpoolallocs);$(measure.nbmalloc);$(measure.summarysize);$(data_errors[instancename])\n") + end +end diff --git a/dev/collect_data_instances.jl b/dev/collect_data_instances.jl new file mode 100644 index 0000000..278c30b --- /dev/null +++ b/dev/collect_data_instances.jl @@ -0,0 +1,142 @@ +""" + +Construct a csv file containing data for all instances +""" + +@everywhere ROOT = pwd() +@everywhere include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) +@everywhere include("para_fct.jl")) + + +# NOTE: Launch in console using 'julia -p nbparathreads compute_GOC_all_scenario.jl' +# where the nb of cores is likely a good value for nbparathreads + +instances_path = joinpath("instances", "GOC") +folders = readdir(instances_path) + +##empty dictionaries which will contain data to export in csv +instances_nb_scenario_data = Dict{String,Int64}() +instances_nb_contingency_data = Dict{String,Int64}() +instances_nb_bus_data = Dict{String,Int64}() +instances_nb_links_data = Dict{String,Int64}() +instances_nb_violated_constraints = Dict{String, Float64}() +instances_nb_cc_Qgen_active = Dict{String, Float64}() + + +##loop on instances to get data +epsilon = 1e-3 +for folder in folders + instances_nb_scenario_data[folder] = get_nb_scenarios(instances_path,folder) + scenario = readdir(joinpath(instances_path, folder))[1] + instances_nb_contingency_data[folder] = get_nb_contingencies(folder,scenario) + instances_nb_bus_data[folder] = get_nb_bus(folder, scenario) + instances_nb_links_data[folder] = get_nb_links(folder,scenario) + instances_nb_cc_Qgen_active[folder] = mean(get_nb_cc_reactive_power_active(folder, scenario, epsilon) for scenario in readdir(joinpath(instances_path, folder)) if scenario !="scorepara.csv") + #instances_nb_violated_constraints[folder] = mean(get_feasibility(folder, scenario, epsilon) for scenario in readdir(joinpath(instances_path, folder))) +end +##create csv file +touch("GOCdata.csv") +f = open("GOCdata.csv", "w") +write(f, "Instance;Nb_scenarios;Nb_contingencies;Nb_bus;Nb_links;Mean nb of violated constraints by GOC solution at $(epsilon);Mean nb of reactive Qgen contingency constraints at $(epsilon)\n") +for (folder, nb_scenarios) in instances_nb_scenario_data + nb_cont = instances_nb_contingency_data[folder] + nb_bus = instances_nb_bus_data[folder] + nb_links = instances_nb_links_data[folder] + # nb_vc = instances_nb_violated_constraints[folder] + nb_vc = "NONE" + nb_cc_act = instances_nb_cc_Qgen_active[folder] + write(f, "$(folder);$(nb_scenarios);$(nb_cont);$(nb_bus);$(nb_links);$(nb_vc);$(nb_cc_act)\n") +end +close(f) + + + +##functions to get specific data +""" + get_nb_scenarios(instances_path,folder) + +Returns the number of scenarios for an instance `folder` in `instances_path`. +""" +function get_nb_scenarios(instances_path,folder) + folder_path = joinpath(instances_path, folder) + scenarios = sort(filter(x->!ismatch(r"\.", x), readdir(folder_path)), by=x->parse(split(x, "_")[2])) + nb_scenarios = length(scenarios) + return nb_scenarios +end + +""" + get_nb_bus(folder, scenario) + +Returns the number of bus for a `scenario` in instance `folder`. +""" +function get_nb_bus(folder, scenario) + power_data = getpowerdata([folder, scenario]) + try + nb_bus = Int(power_data["totalbus"]) + return nb_bus + catch + warn("power_data coming from .mat file does not have key totalbus") + return 0.0 + end +end + +""" + get_nb_contingencies(folder,scenario) + +Returns the number of contingencies for a `scenario` in instance `folder`. +""" +function get_nb_contingencies(folder,scenario) + contingency_data = getcontingencydata([folder, scenario]) + nb_contingency = size(contingency_data,1) + return nb_contingency +end + +""" + get_nb_links(folder, scenario) + +Returns the number of links for a `scenario` in instance `folder`. +""" +function get_nb_links(folder, scenario) + power_data = getpowerdata([folder, scenario]) + try + nb_links = Int(power_data["totalbranch"]) + return nb_links + catch + warn("power_data coming from .mat file does not have key totalbranch") + return 0.0 + end +end + +""" + get_feasibility(folder, scenario, epsilon) + +Returns the number of violated constraints at `epsilon` (except balance constraints) for a `scenario` in instance `folder`. +""" +function get_feasibility(folder, scenario, epsilon) + try + violations = test_feasibility_GOC([folder, scenario],epsilon) + nb_violated_constraints = 0 + for (scenario, dict_infeas) in violations + nb_violated_constraints += length(dict_infeas) + end + return nb_violated_constraints + + catch + return -1.0 + end +end + + +""" + get_nb_cc_reactive_power_active(folder, scenario,epsilon) + +Returns the number of active constraints of type contingency Qgen coupling constraints at `epsilon` for a `scenario` in instance `folder`. +""" +function get_nb_cc_reactive_power_active(folder, scenario,epsilon) + try + return nb_active_constraints_in_scenario_cc_Qgen([folder, scenario], epsilon) + catch + println([folder, scenario]) + return -1.0 + end +end diff --git a/dev/compare_dats.jl b/dev/compare_dats.jl new file mode 100644 index 0000000..5731d95 --- /dev/null +++ b/dev/compare_dats.jl @@ -0,0 +1,17 @@ +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +if length(ARGS) != 2 + error("Expected input:\n julia compare_dats.jl file1.dat file2.dat") +end + +file1, file2 = ARGS + +maxerror, errors = compare_dat(file1, file2, 1e-10) + +count = 0 +for key in keys(errors) + count += length(errors[key]) +end + +println("Max error on coeff: $maxerror") +println("Nb of coeffs with error > 1e-10: $(count)") diff --git a/dev/compute_GOC_all_scenario.jl b/dev/compute_GOC_all_scenario.jl new file mode 100644 index 0000000..f532e7a --- /dev/null +++ b/dev/compute_GOC_all_scenario.jl @@ -0,0 +1,40 @@ +@everywhere ROOT = pwd() +@everywhere include(joinpath(ROOT,"src_PowSysMod", "PowSysMod_body.jl")) +@everywhere include("para_fct.jl") + +# NOTE: Launch in console using 'julia -p nbparathreads compute_GOC_all_scenario.jl' +# where the nb of cores is likely a good value for nbparathreads + +folder = "Phase_0_IEEE14" +folder = "Phase_0_RTS96" +folder_path = joinpath("instances", "GOC", folder) + +scenarios = sort(filter(x->!ismatch(r"\.", x), readdir(folder_path)), by=x->parse(split(x, "_")[2])) + +date = Dates.format(now(), "yy_u_dd_HH_MM_SS") +save_folder = joinpath("knitro_runs", "para_run_$date") +mkdir(save_folder) + +println("----------> Start para jobs") + +# r = build_and_solve_GOC(folder, scenarios[1]) +r = pmap(build_and_solve_GOC, [folder for i=1:length(scenarios)], scenarios, [save_folder for i=1:length(scenarios)]) + +println("----------> para jobs done") + +println("length scenarios : $(length(r))") + + +# r = Dict(r) +# +# tasks_th = Dict() +# for (key, val) in r +# if !haskey(tasks_th, val["thread_id"]) +# tasks_th[val["thread_id"]] = 0 +# end +# tasks_th[val["thread_id"]] += 1 +# end +# println("id count:") +# for (k, v) in tasks_th +# println("$k → $v") +# end diff --git a/dev/compute_GOC_scenario.jl b/dev/compute_GOC_scenario.jl new file mode 100644 index 0000000..623bb9c --- /dev/null +++ b/dev/compute_GOC_scenario.jl @@ -0,0 +1,91 @@ +ROOT = pwd() +include(joinpath(ROOT, "src_PowSysMod", "PowSysMod_body.jl")) + +# folder = "Phase_0_IEEE14" +# folder = "Phase_0_Feas179" +# folder = "Phase_0_RTS96" +folder = "Phase_0_IEEE14_1Scenario" +scenario = "scenario_1" +filenames = [folder, scenario] +filenames_path = joinpath("data_GOC", folder, scenario) +instance_path = filenames_path + +## Build global pb +OPFpbs = load_OPFproblems(GOCInput, instance_path) +introduce_Sgenvariables!(OPFpbs) +pb_global = build_globalpb(OPFpbs) + +## Build solution +global_point = solution_point(instance_path) + +## Add binary variables value +# bin_point = create_bin_point(OPFpbs, global_point) + +println("minslack of GOC global solution: $(get_minslack(pb_global, global_point))") + +# evaluate(pb.constraints["Scen1_3_Gen1_CC_Supper"].p, global_point) +# evaluate(pb.constraints["Scen1_3_Gen1_CC_Slower"].p, global_point) + +get_minslack(pb_global, global_point) + +pb_sansCC = Problem() +pb_sansCC.variables = copy(pb_global.variables) +pb_sansCC.objective = copy(pb_global.objective) +for (cstrname, cstr) in pb_global.constraints + if !ismatch(r"Pgen|Qgen", cstrname) + add_constraint!(pb_sansCC, cstrname, cstr) + end +end +println("minslack of GOC solution without coupling constraints: $(get_minslack(pb_sansCC, global_point))") + +## Export problem +amplexportpath = joinpath("knitro_runs", "$(folder[9:end])_$(scenario)_move") + +pb_global_real = pb_cplx2real(pb_global) +global_point_real = cplx2real(global_point) + +export_to_dat(pb_global_real, amplexportpath, global_point) + + +## Knitro call and solution import +run_knitro(amplexportpath, joinpath(pwd(), "src_ampl")) +pt_knitro, pt_GOC = read_Knitro_output(amplexportpath, pb_global_real) + + +length(global_point_real) +length(pt_knitro) + +println("Minslack for GOC point : $(get_minslack(pb_global_real, pt_GOC))") +println("Minslack for Knitro point : $(get_minslack(pb_global_real, pt_knitro))") + +println("Objective for GOC point : $(get_objective(pb_global_real, pt_GOC))") +println("Objective for Knitro point : $(get_objective(pb_global_real, pt_knitro))") + +println("||pt_GOC - global_point||_2 = ", norm(pt_GOC - global_point, 2)) +println("||pt_GOC - global_point||_Inf = ", norm(pt_GOC - global_point, Inf)) + +# slacks = sort(collect(get_slacks(pb_global_real, pt_GOC)), by=x->real(x[2])) +# violated_cstrs = collect(filter(x->real(x[2])<-1e-4, slacks)) +# println("GOC 1e-4 Violated_constraints : $(length(violated_cstrs))") +# +# slacks = sort(collect(get_slacks(pb_global_real, pt_knitro)), by=x->real(x[2])) +# violated_cstrs = collect(filter(x->real(x[2])<-1e-4, slacks)) +# println("Knitro 1e-4 Violated_constraints : $(length(violated_cstrs))") + + +# bc_volt_vars, bc_bin_vars, bc_prod_vars = get_splitted_Cpt(pt_knitro, "BaseCase") +# sc_volt_vars, sc_bin_vars, sc_prod_vars = get_splitted_Cpt(pt_knitro, "Scen1") +# sc_delta_var = get_delta_var(pt_knitro, "Scen1") +# haskey(pt_knitro, Variable("BaseCase_3_Gen1_Sgen", Complex)) +# +# +# using Plots +# +# plot_Volt_vars(pt_knitro, keys(OPFpbs)) +# # plot_Sgen_vars(pt_knitro, ["BaseCase"]) +# plot_Sgen_vars(pt_knitro, keys(OPFpbs)) +# +# plot_ViVj_vars(pt_knitro, ["BaseCase"]) +# plot_ViVj_vars(pt_knitro, keys(OPFpbs)) + +# scatter!(real(ptsVbc), imag(ptsVbc), linealpha=0.1, marker=:circle, series_annotations=[text(matchall(r"", x[1])[1]) for (x,y) in bc_volt_vars]) diff --git a/dev/compute_all_slacks.jl b/dev/compute_all_slacks.jl new file mode 100644 index 0000000..2ef24c9 --- /dev/null +++ b/dev/compute_all_slacks.jl @@ -0,0 +1,28 @@ +""" + Check feasibility of Matpower solution +""" + +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) +include("read_QCQP.jl") + +instances = [name[1:end-4] for name in readdir("instances")] + +for instance_name in instances + # Building complex and real problems + filepath = abspath(joinpath("instances", instance_name*".dat")) + pb = read_QCQP(filepath) + pb_R = pb_cplx2real(pb) + + # Building complex and real solutions + filepath = abspath(joinpath("solutions", instance_name*".sol")) + pt_sol_C, pt_sol_R = build_QCQP_sol(filepath) + + # Compute and build minSlack and objective for both cplx and real problems + minSlack, minCstrName = get_minslack(pb, pt_sol_C) + val = get_objective(pb, pt_sol_C) + @printf("%19s min slack: %.3e at %10s\t objective value : %.10e + im %.10e\n", instance_name, minSlack, minCstrName[1], real(val), imag(val)) + + minSlack_R, minCstrName_R = get_minslack(pb_R, pt_sol_R) + val_R = get_objective(pb_R, pt_sol_R) + @printf("%19s min slack: %.3e at %10s\t objective value : %.10e + im %.10e\n", "C2R "*instance_name, minSlack_R, minCstrName_R[1], real(val_R), imag(val_R)) +end diff --git a/dev/para_fct.jl b/dev/para_fct.jl new file mode 100644 index 0000000..c57cde8 --- /dev/null +++ b/dev/para_fct.jl @@ -0,0 +1,41 @@ +""" + build_and_solve_GOC(instance::String, scenario::String, save_folder::String) + +Build and solve GOC instance in `instance`\`scenario`, save logs in `save_folder` and returns information as minslack of solution points. +""" + +function build_and_solve_GOC(instance::String, scenario::String, save_folder::String) + filenames_path = joinpath("data_GOC", instance, scenario) + filenames = vcat(instance, scenario, readdir(filenames_path)) + + ## Build global pb + OPFpbs = load_OPFproblems(GOCInput, filenames_path) + introduce_Sgenvariables!(OPFpbs) + pb_global = build_globalpb!(OPFpbs) + ## Reading GOC solution + global_point = solution_point(filenames_path) + ## Export problem + date = Dates.format(now(), "yy_u_dd_HH_MM_SS") + amplexportpath = joinpath(save_folder, "$(date)_$(instance[8:end])_$(scenario)") + + export_dat_GOC_problem(amplexportpath, OPFpbs, pb_global, global_point) + + pb_global_real = pb_cplx2real(pb_global) + + ## Knitro call and solution import + pt_strat1, pt_strat2 = get_knitro_solutions(amplexportpath) + + ## Results: + Data = Dict() + Data["minslack_GOC"] = get_minslack(pb_global, global_point) + Data["minslack_strat1"] = get_minslack(pb_global_real, pt_strat1)[1] + Data["minslack_strat2"] = get_minslack(pb_global_real, pt_strat1)[2] + Data["thread_id"] = myid() + + println("Minslack of GOC global solution: $(get_minslack(pb_global, global_point))") + println("Minslack for strat 1 : $(get_minslack(pb_global_real, pt_strat1))") + println("Minslack for strat 2 : $(get_minslack(pb_global_real, pt_strat2))") + println("====X $instance, $scenario done job id: $(myid())") + + return (instance, scenario) => Data +end diff --git a/dev/polyproblem_to_jump.jl b/dev/polyproblem_to_jump.jl new file mode 100644 index 0000000..cb00f15 --- /dev/null +++ b/dev/polyproblem_to_jump.jl @@ -0,0 +1,154 @@ +using JuMP, Clp, Ipopt + +ROOT=pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + + + +""" + get_JuMP_cartesian_model(problem_poly::Problem, mysolver) + +Return JuMP cartesian model associated to `mysolver` defined by `problem_poly`, a polynomial problem in complex numbers + +# Example +```jldoctest +V1 = Variable("VOLT_1",Complex) +V2 = Variable("VOLT_2",Complex) +p_obj = V1*conj(V2) + (2+im)*abs2(V1) + 1+2im +p_ctr1 = abs2(V1) +p_ctr2 = 3im * V1*conj(V2) + abs2(V1) + +problem_poly=Problem() +add_variable!(problem_poly,V1) +add_variable!(problem_poly,V2) +add_constraint!(problem_poly, "ctr1", 0.95^2 << p_ctr1 << 1.05^2) +add_constraint!(problem_poly, "ctr2", p_ctr2==0) +set_objective!(problem_poly, p_obj) +print(problem_poly) +mysolver = ClpSolver() +m = get_JuMP_cartesian_model(problem_poly, mysolver) +print(m) +``` +""" +function get_JuMP_cartesian_model(problem_poly::Problem, mysolver) + pb_poly_real = pb_cplx2real(problem_poly) + m = Model(solver = mysolver) + variables_jump = Dict{String, JuMP.Variable}() + for (varname, vartype) in pb_poly_real.variables + if vartype<:Real + variables_jump["$varname"] = @variable(m, varname, basename="$varname") + end + end + ctr_jump = Dict{String,JuMP.ConstraintRef}() + for (ctr, modeler_ctr) in pb_poly_real.constraints + polynome = modeler_ctr.p + lb = modeler_ctr.lb + ub = modeler_ctr.ub + + for value in values(polynome) + if imag(value)!=0 + error("Polynom coefficients have to be real numbers") + end + end + ctr_jump[ctr] = @NLconstraint(m, lb <= sum(coeff*prod(variables_jump["$var"]^(exp[1]) for (var,exp) in monome) for (monome,coeff) in polynome) <= ub) + end + polynome_obj = pb_poly_real.objective + @NLobjective(m,Min,sum(coeff*prod(variables_jump["$var"]^(exp[1]) for (var,exp) in monome) for (monome,coeff) in polynome_obj)) + return m +end + +###TEST +# V1 = Variable("VOLT_1",Complex) +# V2 = Variable("VOLT_2",Complex) +# p_obj = V1*conj(V2) + (2+im)*abs2(V1) + 1+2im +# p_ctr1 = abs2(V1) +# p_ctr2 = 3im * V1*conj(V2) + abs2(V1) +# +# problem_poly=Problem() +# add_variable!(problem_poly,V1) +# add_variable!(problem_poly,V2) +# add_constraint!(problem_poly, "ctr1", 0.95^2 << p_ctr1 << 1.05^2) +# add_constraint!(problem_poly, "ctr2", p_ctr2==0) +# set_objective!(problem_poly, p_obj) +# print(problem_poly) +# mysolver = IpoptSolver() +# m = get_JuMP_cartesian_model(problem_poly, mysolver) +# print(m) + + + +""" + get_JuMP_polar_model(pb::Problem, mysolver) + +Return JuMP polar model associated to `mysolver` defined by `pb`, a polynomial problem in complex numbers + +# Example +```jldoctest +V1 = Variable("VOLT_1",Complex) +V2 = Variable("VOLT_2",Complex) +p_obj = V1*conj(V2) + (2+im)*abs2(V1) + 1+2im +p_ctr1 = abs2(V1) +p_ctr2 = Polynomial((3+4im) * V1*conj(V2)) +pb=Problem() +add_variable!(pb,V1) +add_variable!(pb,V2) +add_constraint!(pb, "ctr1", 0 << p_ctr1 << 100) +# add_constraint!(pb, "ctr2", p_ctr2==3+4im) +set_objective!(pb, p_obj) +print(pb) +mysolver = IpoptSolver() +m = get_JuMP_polar_model(pb, mysolver) +print(m) +``` +""" +function get_JuMP_polar_model(pb::Problem, mysolver) + m = Model(solver = mysolver) + jump_vars = Dict{String, JuMP.Variable}() + for (varname, vartype) in pb.variables + mod = "ρ_$varname" + jump_vars["ρ_$varname"] = @variable(m, basename="ρ_$varname") + jump_vars["θ_$varname"] = @variable(m, basename="θ_$varname") + end + ctr_jump = Dict{String,JuMP.ConstraintRef}() + for (ctr, modeler_ctr) in pb.constraints + p = modeler_ctr.p + lb = modeler_ctr.lb + ub = modeler_ctr.ub + ps = Dict{Exponent, Tuple{Real, Real}}(exp=>(real(coeff), imag(coeff)) for (exp, coeff) in p) + ## Real part of constraint + @NLconstraint(m, real(lb) <= sum( coeff[1] * prod(jump_vars["ρ_$var"]^(exp[1]+exp[2]) for (var,exp) in mon) * cos(sum( (exp[1]-exp[2])*jump_vars["θ_$var"] for (var,exp) in mon)) + - coeff[2] * prod(jump_vars["ρ_$var"]^(exp[1]+exp[2]) for (var,exp) in mon) * sin(sum( (exp[1]-exp[2])*jump_vars["θ_$var"] for (var,exp) in mon)) + for (mon, coeff) in ps) <= real(ub)) + + ## Imag part of constraint + @NLconstraint(m, imag(lb) <= sum( coeff[2] * prod(jump_vars["ρ_$var"]^(exp[1]+exp[2]) for (var,exp) in mon) * cos(sum( (exp[1]-exp[2])*jump_vars["θ_$var"] for (var,exp) in mon)) + + coeff[1] * prod(jump_vars["ρ_$var"]^(exp[1]+exp[2]) for (var,exp) in mon) * sin(sum( (exp[1]-exp[2])*jump_vars["θ_$var"] for (var,exp) in mon)) + for (mon, coeff) in ps) <= imag(ub)) + + polynome_obj = pb.objective + ps = Dict{Exponent, Tuple{Real, Real}}(exp=>(real(coeff), imag(coeff)) for (exp, coeff) in polynome_obj) + lb_real, ub_real = real(lb), real(ub) + @NLobjective(m, :Min, lb_real <= sum( coeff[1] * prod(jump_vars["ρ_$var"]^(exp[1]+exp[2]) for (var,exp) in mon) * cos(sum( (exp[1]-exp[2])*jump_vars["θ_$var"] for (var,exp) in mon)) + - coeff[2] * prod(jump_vars["ρ_$var"]^(exp[1]+exp[2]) for (var,exp) in mon) * sin(sum( (exp[1]-exp[2])*jump_vars["θ_$var"] for (var,exp) in mon)) + for (mon, coeff) in ps) <= ub_real) + end + return m +end + + +####TEST +# V1 = Variable("VOLT_1",Complex) +# V2 = Variable("VOLT_2",Complex) +# p_obj = V1*conj(V2) + (2+im)*abs2(V1) + 1+2im +# p_ctr1 = abs2(V1) +# p_ctr2 = Polynomial((3+4im) * V1*conj(V2)) +# pb=Problem() +# add_variable!(pb,V1) +# add_variable!(pb,V2) +# add_constraint!(pb, "ctr1", 0 << p_ctr1 << 100) +# # add_constraint!(pb, "ctr2", p_ctr2==3+4im) +# set_objective!(pb, p_obj) +# print(pb) +# mysolver = IpoptSolver() +# m = get_JuMP_polar_model(pb, mysolver) +# print(m) diff --git a/dev/read_QCQP.jl b/dev/read_QCQP.jl new file mode 100644 index 0000000..a63c855 --- /dev/null +++ b/dev/read_QCQP.jl @@ -0,0 +1,86 @@ +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +function add_to_dict!(dict, key, val) + if !haskey(dict, key) + dict[key] = 0 + end + dict[key] += val +end + + +""" +pb = read_QCQP(filepath::String) + +Read a QCQP file (e.g. WB2.dat) and builds a corresponding Problem type object. +""" +function read_QCQP(filepath::String) + pb = Problem() + + data = readdlm(filepath) + line_ind = 1 + while data[line_ind, 1] == "VAR_TYPE" + add_variable!(pb, Variable(data[line_ind, 3], Complex)) + line_ind += 1 + end + + obj = Polynomial() + while data[line_ind, 2] == "OBJ" + obj = obj + build_monomial_from_line(data[line_ind, :], pb) + line_ind += 1 + end + set_objective!(pb, obj) + + ## Add constraints + while line_ind < size(data, 1) + line = data[line_ind, :] + current_cstrName = String(line[2]) + current_p = Polynomial() + current_lb = 0 + current_ub = 0 + while string(line[2]) == current_cstrName && line_ind <= size(data, 1) + if line[1] == "UB" + current_ub = line[5] + im*line[6] + elseif line[1] == "LB" + current_lb = line[5] + im*line[6] + else + current_p = current_p + build_monomial_from_line(line, pb) + end + line_ind += 1 + if line_ind <= size(data, 1) + line = data[line_ind, :] + end + end + add_constraint!(pb, current_cstrName, current_lb << current_p << current_ub) + end + pb +end + +function build_monomial_from_line(line::Vector{Any}, pb::Problem) + var1 = read_var(String(line[3]), pb) + var2 = read_var(String(line[4]), pb) + (line[5] + im * line[6]) * conj(var1) * var2 +end + + +""" +var = read_var(varName, pb) + +Return the Problem variable corresponding to varName if it exists in pb, or else +warn, add it to pb and return the variable. +""" +function read_var(varName::String, pb::Problem) + var = Variable() + if varName == "NONE" + var = Variable("1", Complex) + elseif !has_variable(pb, varName) + var = Variable(varName, Complex) + warn("Variable ", varName, " is not known in the current Problem. Adding ", var, ".") + add_variable!(pb, var) + else + var = Variable(varName, pb.variables[varName]) + end + var +end + + +extractWord(l, x) = String(matchall(r"\S+", l)[x]) diff --git a/dev/test_IIDM.jl b/dev/test_IIDM.jl new file mode 100644 index 0000000..379b142 --- /dev/null +++ b/dev/test_IIDM.jl @@ -0,0 +1,102 @@ +# using ComplexModeler, Modeler, Poly, +using LightXML +# using ComplexModeler +ROOT=pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +filename = joinpath("instances", "IIDM", "pfLille_10postes.xml") +OPFpbs = build_OPFproblems(IIDMInput, [filename]) + +scenario = "BaseCase" +pb = build_Problem!(OPFpbs, scenario) +# print(pb) + +println("-----------------------------------------------------------------------------") +voltage = Point() +for (busname, buselems) in OPFpbs["BaseCase"].ds.bus + voltage[OPFpbs["BaseCase"].mp.node_vars[busname]["Volt"]] = buselems["MeasureVolt"].V +end + +## Testing nodal power balance +for cstrname in keys(pb.constraints) + if ismatch(r"Cstr_", cstrname) + delete!(pb.constraints, cstrname) + end +end + +cstrs = get_constraints(pb, voltage) + +slacks = get_slacks(pb, voltage) +println("minslack : ", get_minslack(pb, voltage)) + +# ordered_slacks = sort([ [k v] for (k, v) in slacks], by=x->abs(x[2])) +# for k in sort(collect(keys(slacks)), by=x->x[1], rev=true) +# println("→ $k \t $(slacks[k]) \t$(pb.constraints[k[1]].lb) < \t $(cstrs[k]) < \t$(pb.constraints[k[1]].ub)") +# end + +## Power at orig and dest of lines. +# line_power = Dict{Link, Array{Complex128}}() +# for link in get_links(OPFpbs, "BaseCase") +# link_elem = OPFpbs["BaseCase"].ds.link[link] +# length(link_elem) == 1 || warn("link $link has several lines.") +# for (line_name, line) in link_elem +# line_power[link] = [line.S_orig line.S_dest] +# end +# end + +## Testing nodal power balance +ds = OPFpbs["BaseCase"].ds +gs = OPFpbs["BaseCase"].gs +mc = OPFpbs["BaseCase"].mc + +pb_nodal_balance = Problem() +for (busname, bus_elems) in ds.bus + bus_elems_formulations = mc.node_formulations[busname] + bus_elems_var = gs.node_vars[busname] + + cstrname, Snode, lb, ub = get_Snodal(busname, bus_elems, bus_elems_formulations, bus_elems_var) + S_balance = Snode + get_Slinks_in(busname, ds, gs, mc) + get_Slinks_out(busname, ds, gs, mc) + + if cstrname == "_" + cstrname = "LOAD_" + end + add_constraint!(pb_nodal_balance, cstrname*string(busname), lb << S_balance << ub) +end + +get_constraints(pb_nodal_balance, voltage) +slacks = get_slacks(pb_nodal_balance, voltage) +minslack, _ = get_minslack(pb_nodal_balance, voltage) +println("minslack on nodal powerbalance: $minslack") +# for k in sort(collect(keys(slacks)), by=x->x[1], rev=true) +# println("→ $k \t $(slacks[k]) \t$(pb_nodal_balance.constraints[k[1]].lb) < \t $(cstrs[k]) < \t$(pb_nodal_balance.constraints[k[1]].ub)") +# end + +## Testing line power balance +line_power = Point() +pb_line_balance = Problem() +for link in get_links(OPFpbs, "BaseCase") + for (elementname, elem) in ds.link[link] + # elementname = collect(keys(ds.link[link]))[1] + # element = ds.link[link][elementname] + elem_formulation = OPFpbs["BaseCase"].mc.link_formulations[link][elementname] + link_vars = OPFpbs["BaseCase"].gs.link_vars[link] + Sor = Sorig(element, link, elementname, elem_formulation, link_vars) + Sde = Sdest(element, link, elementname, elem_formulation, link_vars) + + add_constraint!(pb_line_balance, "", element.S_orig << Sor << element.S_orig) + evaluate(Sor, voltage) + evaluate(Sde, voltage) + + element.S_dest + end +end + +link = collect(keys(ds.link))[1] + +export_to_dat(pb, "my$instance.dat") +sort_dat("my$instance.dat", "my$(instance)_sorted.dat") +# sort_dat("$instance.dat", "$(instance)_sorted.dat") + +err, dict = compare_dat("my$instance.dat", filename_dat) + +println("error = $err, $(length(dict))") diff --git a/dev/test_IIDM_2020_read.jl b/dev/test_IIDM_2020_read.jl new file mode 100644 index 0000000..2ecc78b --- /dev/null +++ b/dev/test_IIDM_2020_read.jl @@ -0,0 +1,93 @@ +ROOT=pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +path = joinpath("instances", "IIDM", "etat_reseau") +data_branches = readdlm(joinpath(path, "eod2020_network_branches.txt")) +data_buses = readdlm(joinpath(path, "eod2020_network_buses.txt")) +data_generators = readdlm(joinpath(path, "eod2020_network_generators.txt")) +data_limits = readdlm(joinpath(path, "eod2020_network_limits.txt")) +data_loads = readdlm(joinpath(path, "eod2020_network_loads.txt")) +data_substations = readdlm(joinpath(path, "eod2020_network_substations.txt")) + +bus = Dict{String, Dict{String, Any}}() + +for i=1:size(data_buses, 1) + busid = Int(data_buses[i, 1]) + substationid = Int(data_buses[i, 2]) + V = data_buses[i, 4] # p.u. + θ = data_buses[i, 5] # radians + P, Q = data_buses[i, 6:7] + basekV, minV, maxV = data_substations[substationid, 4:6] + + busname = "BUS_$busid" + data_buses[i, 3] == 0 || warn("Buses - $busname : parameter cc!=0 ($(data_buses[i, 3])), not handled") + data_buses[i, 8] == 0 || warn("Buses - $busname : parameter fault!=0 ($(data_buses[i, 8])), not handled") + data_buses[i, 9] == 0 || warn("Buses - $busname : parameter curative!=0 ($(data_buses[i, 9])), not handled") + + bus[busname] = Dict{String, Any}() + bus[busname]["Volt"] = IIDMVolt(busname, busid, string(substationid), basekV, minV, maxV, V, θ, P, Q) +end + +for i=1:size(data_loads, 2) + busid = data_loads[i, 2] + P, Q = data_loads[i, 4:5] + busname = "BUS_$busid" + + data_loads[i, 6] == 0 || warn("Loads - $busname : parameter fault!=0 ($(data_loads[i, 6])), not handled") + data_loads[i, 7] == 0 || warn("Loads - $busname : parameter curative!=0 ($(data_loads[i, 7])), not handled") + + bus_elems = bus["$busname"] + nb = count([ismatch(r"Load_", x) for x in keys(bus_elems)]) + bus_elems["Load_$(nb+1)"] = IIDMLoad(string(nb+1), P+im*Q) +end + +for i=1:size(data_generators, 1) + busid = data_generators[i, 2] + minP, maxP = data_generators[i, 5:6] + volt_regulator_on, targetV = data_generators[i, 11:12] + minQmaxP, minQminP, maxQmaxP, maxQminP = data_generators[i, 7:10] + S_bounds = Set([(minP+im*minQminP, minP+im*maxQminP), (maxP+im*minQmaxP, maxP+im*maxQmaxP)]) + busname = "BUS_$busid" + + data_generators[i, 2] == data_generators[i, 3] || warn("Generators - $busname : 'bus' is not 'con. bus', not handled") + data_generators[i, 15] == 0 || warn("Loads - $busname : parameter fault!=0 ($(data_generators[i, 15])), not handled") + data_generators[i, 16] == 0 || warn("Loads - $busname : parameter curative!=0 ($(data_generators[i, 16])), not handled") + + bus_elems = bus["$busname"] + nb = count([ismatch(r"Gen_", x) for x in keys(bus_elems)]) + bus_elems["Gen_$(nb+1)"] = IIDMGeneratorFuel(string(nb+1), minP, maxP, targetV, volt_regulator_on, S_bounds) +end + + +link = Dict{Link, Dict{String, IIDMLine_π}}() +for i=1:size(data_branches, 1) + busid1, busid2 = data_branches[i, 2:3] + r, x, g1, g2, b1, b2 = data_branches[i, 7:12] + p1, p2, q1, q2 = data_branches[i, 16:19] + + + linkname = Link("BUS_$busid1", "BUS_$busid2") + if !haskey(link, linkname) + link[linkname] = Dict{String, IIDMLine_π}() + end + link_elems = link[link_elems] + + nb = count([ismatch(r"Branch_", x) for x in keys(bus_elems)]) + bus_elems["Branch_$(nb+1)"] = IIDMLine_π(r, x, g1+im*b1, g2+im*b2, p1+im*q1, p2+im*q2, basekV_orig, basekV_dest, lim) +end + +find(data_branches[:, 13] .!= 1) +find(data_branches[:, 14] .!= -1) +find(data_branches[:, 15] .!= -1) + + +data = readdlm(filename) + +find(data[:, 4] .== -1) + + +path = joinpath("instances", "IIDM", "etat_reseau") + +data = readdlm(filename) + +find(data[:, 1] .!= 0) diff --git a/dev/test_IIDMtxt.jl b/dev/test_IIDMtxt.jl new file mode 100644 index 0000000..de42294 --- /dev/null +++ b/dev/test_IIDMtxt.jl @@ -0,0 +1,102 @@ +# using ComplexModeler, Modeler, Poly, +using LightXML +# using ComplexModeler +ROOT=pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +filenames = sort([joinpath("instances", "IIDM", "etat_reseau", file) for file in readdir(joinpath("instances", "IIDM", "etat_reseau"))]) + +@time OPFpbs = build_OPFproblems(IIDMeodInput, filenames) + +OPFpbs["BaseCase"].ds.bus["BUS_8"] + +scenario = "BaseCase" +pb = build_Problem!(OPFpbs, scenario) +# print(pb) + +voltage = Point() +for (busname, buselems) in OPFpbs["BaseCase"].ds.bus + if !haskey(buselems, "VoltMeasure") + warn("$busname has no volt measure") + else + voltage[OPFpbs["BaseCase"].mp.node_vars[busname]["Volt"]] = buselems["VoltMeasure"].V + end +end + +## Testing nodal power balance +println("$(length(pb.constraints)) constraints for initial OPF") +for cstrname in keys(pb.constraints) + if ismatch(r"Cstr_", cstrname) + delete!(pb.constraints, cstrname) + end +end +println("$(length(pb.constraints)) constraints for power balance only OPF") + +cstrs = get_constraints(pb, voltage) + +slacks = get_slacks(pb, voltage) +println("minslack : ", get_minslack(pb, voltage)) + +ordered_slacks = sort([ [k abs(v)] for (k, v) in slacks], by=x->abs(x[2])) +# sort(collect(keys(slacks)), by=x->x[1], rev=true) +for (k, v) in ordered_slacks + @printf("→ %18s %.0e + %.0eim %.0e + %.0e < %.0e+%.0e im < %.0e + %.0e\n", k, real(slacks[k]), imag(slacks[k]), real(pb.constraints[k[1]].lb), imag(pb.constraints[k[1]].lb), real(cstrs[k]), imag(cstrs[k]), real(pb.constraints[k[1]].ub), imag(pb.constraints[k[1]].ub)) + # println("→ $k \t $(slacks[k]) \t$(pb.constraints[k[1]].lb) < \t $(cstrs[k]) < \t$(pb.constraints[k[1]].ub)") +end + +## Power at orig and dest of lines. +# line_power = Dict{Link, Array{Complex128}}() +# for link in get_links(OPFpbs, "BaseCase") +# link_elem = OPFpbs["BaseCase"].ds.link[link] +# length(link_elem) == 1 || warn("link $link has several lines.") +# for (line_name, line) in link_elem +# line_power[link] = [line.S_orig line.S_dest] +# end +# end + +# ## Testing nodal power balance +# ds = OPFpbs["BaseCase"].ds +# gs = OPFpbs["BaseCase"].gs +# mc = OPFpbs["BaseCase"].mc +# +# pb_nodal_balance = Problem() +# for (busname, bus_elems) in ds.bus +# bus_elems_formulations = mc.node_formulations[busname] +# bus_elems_var = gs.node_vars[busname] +# +# cstrname, Snode, lb, ub = get_Snodal(busname, bus_elems, bus_elems_formulations, bus_elems_var) +# S_balance = Snode + get_Slinks_in(busname, ds, gs, mc) + get_Slinks_out(busname, ds, gs, mc) +# +# if cstrname == "_" +# cstrname = "LOAD_" +# end +# add_constraint!(pb_nodal_balance, cstrname*string(busname), lb << S_balance << ub) +# end +# +# get_constraints(pb_nodal_balance, voltage) +# slacks = get_slacks(pb_nodal_balance, voltage) +# minslack, _ = get_minslack(pb_nodal_balance, voltage) +# println("minslack on nodal powerbalance: $minslack") +# # for k in sort(collect(keys(slacks)), by=x->x[1], rev=true) +# # println("→ $k \t $(slacks[k]) \t$(pb_nodal_balance.constraints[k[1]].lb) < \t $(cstrs[k]) < \t$(pb_nodal_balance.constraints[k[1]].ub)") +# # end + +# ## Testing line power balance +# line_power = Point() +# pb_line_balance = Problem() +# for link in get_links(OPFpbs, "BaseCase") +# for (elementname, elem) in ds.link[link] +# # elementname = collect(keys(ds.link[link]))[1] +# # element = ds.link[link][elementname] +# elem_formulation = OPFpbs["BaseCase"].mc.link_formulations[link][elementname] +# link_vars = OPFpbs["BaseCase"].gs.link_vars[link] +# Sor = Sorig(element, link, elementname, elem_formulation, link_vars) +# Sde = Sdest(element, link, elementname, elem_formulation, link_vars) +# +# add_constraint!("", element.S_orig << Sor << element.S_orig) +# evaluate(Sor, voltage) +# evaluate(Sde, voltage) +# +# element.S_dest +# end +# end diff --git a/dev/test_IIDMxml.jl b/dev/test_IIDMxml.jl new file mode 100644 index 0000000..77338cb --- /dev/null +++ b/dev/test_IIDMxml.jl @@ -0,0 +1,91 @@ +# using ComplexModeler, Modeler, Poly, +using LightXML +# using ComplexModeler +ROOT=pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +filename = joinpath("instances", "IIDM", "pfLille_10postes.xml") +OPFpbs = build_OPFproblems(IIDMInput, [filename]) + +scenario = "BaseCase" +pb = build_Problem!(OPFpbs, scenario) +# print(pb) + +voltage = Point() +for (busname, buselems) in OPFpbs["BaseCase"].ds.bus + voltage[OPFpbs["BaseCase"].mp.node_vars[busname]["Volt"]] = buselems["MeasureVolt"].V +end + +## Testing nodal power balance +for cstrname in keys(pb.constraints) + if ismatch(r"Cstr_", cstrname) + delete!(pb.constraints, cstrname) + end +end + +cstrs = get_constraints(pb, voltage) + +slacks = get_slacks(pb, voltage) +println("minslack : ", get_minslack(pb, voltage)) + +# ordered_slacks = sort([ [k v] for (k, v) in slacks], by=x->abs(x[2])) +# for k in sort(collect(keys(slacks)), by=x->x[1], rev=true) +# println("→ $k \t $(slacks[k]) \t$(pb.constraints[k[1]].lb) < \t $(cstrs[k]) < \t$(pb.constraints[k[1]].ub)") +# end + +## Power at orig and dest of lines. +# line_power = Dict{Link, Array{Complex128}}() +# for link in get_links(OPFpbs, "BaseCase") +# link_elem = OPFpbs["BaseCase"].ds.link[link] +# length(link_elem) == 1 || warn("link $link has several lines.") +# for (line_name, line) in link_elem +# line_power[link] = [line.S_orig line.S_dest] +# end +# end + +# ## Testing nodal power balance +# ds = OPFpbs["BaseCase"].ds +# gs = OPFpbs["BaseCase"].gs +# mc = OPFpbs["BaseCase"].mc +# +# pb_nodal_balance = Problem() +# for (busname, bus_elems) in ds.bus +# bus_elems_formulations = mc.node_formulations[busname] +# bus_elems_var = gs.node_vars[busname] +# +# cstrname, Snode, lb, ub = get_Snodal(busname, bus_elems, bus_elems_formulations, bus_elems_var) +# S_balance = Snode + get_Slinks_in(busname, ds, gs, mc) + get_Slinks_out(busname, ds, gs, mc) +# +# if cstrname == "_" +# cstrname = "LOAD_" +# end +# add_constraint!(pb_nodal_balance, cstrname*string(busname), lb << S_balance << ub) +# end +# +# get_constraints(pb_nodal_balance, voltage) +# slacks = get_slacks(pb_nodal_balance, voltage) +# minslack, _ = get_minslack(pb_nodal_balance, voltage) +# println("minslack on nodal powerbalance: $minslack") +# # for k in sort(collect(keys(slacks)), by=x->x[1], rev=true) +# # println("→ $k \t $(slacks[k]) \t$(pb_nodal_balance.constraints[k[1]].lb) < \t $(cstrs[k]) < \t$(pb_nodal_balance.constraints[k[1]].ub)") +# # end + +# ## Testing line power balance +# line_power = Point() +# pb_line_balance = Problem() +# for link in get_links(OPFpbs, "BaseCase") +# for (elementname, elem) in ds.link[link] +# # elementname = collect(keys(ds.link[link]))[1] +# # element = ds.link[link][elementname] +# elem_formulation = OPFpbs["BaseCase"].mc.link_formulations[link][elementname] +# link_vars = OPFpbs["BaseCase"].gs.link_vars[link] +# Sor = Sorig(element, link, elementname, elem_formulation, link_vars) +# Sde = Sdest(element, link, elementname, elem_formulation, link_vars) +# +# add_constraint!("", element.S_orig << Sor << element.S_orig) +# evaluate(Sor, voltage) +# evaluate(Sde, voltage) +# +# element.S_dest +# end +# end diff --git a/dev/test_Poly.jl b/dev/test_Poly.jl new file mode 100644 index 0000000..0abacae --- /dev/null +++ b/dev/test_Poly.jl @@ -0,0 +1,92 @@ +""" + test_Poly + +""" +ROOT = pwd() +include(joinpath(ROOT, "src_PolynomialOptim", "PolynomialOptim.jl")) + +x = Variable("x", Complex) +y = Variable("y", Complex) +z = Variable("z", Real) +b = Variable("b", Bool) + +## Polynomial algebra +pt = Point(Dict(x=>2, y=>1+im, z=>0+im, b=>2.1)) +print(pt) + +p1 = x^2 + 3*y + conj(x) + conj(z) + b +println(p1) +evaluate(p1, pt) == 10 + 3im + +p2 = y^6 - y^6 + (-y*x*b) / 4 + π*conj(b) +println(p2) + + +## Poly operators +@assert evaluate(x, pt) == 2 +@assert evaluate(y, pt) == 1+im +@assert evaluate(conj(y), pt) == 1-im +@assert evaluate(z, pt) == 0 +@assert evaluate(b, pt) == 1 + +@assert evaluate(x*y, pt) == 2+2im +@assert evaluate(p2, pt) == (-1-im)/2 + π + +@assert evaluate(real(y), pt) == 1 +@assert evaluate(imag(y), pt) == 1 + +@assert evaluate(x*y*conj(y), pt) == 2*(1+im)*(1-im) +@assert evaluate(x*y + conj(y), pt) == 3+im +@assert evaluate(x*y + 1 + 1im, pt) == 3+3im + + + +## Point algebra +pt = Point(Dict(x=>2, y=>1+im, z=>0+im, b=>2.1)) +pt1 = Point(Dict(z=>0+im)) +pt2 = Point(Dict(x=>3, y=>-1+2im, b=>-1)) +@assert merge(pt, pt1, pt2) == Point(Dict(x=>5, y=>3im, z=>0+im, b=>1)) + +@assert pt + pt1 - 3*pt2 == Point(Dict(x=>-7, y=>4-5im, z=>0+im, b=>1)) + +@assert norm(pt2, Inf) == 3 +@assert norm(pt2, 1) == 6 +@assert norm(pt2, 2) == √(14) + + +## Poly cplx2real +p = x + y +println("p: $p") + +p1 = copy(p) +println("p1: $p1") + +add!(p1, y*z) +println("p: $p") +println("p1: $p1") + +real_p, imag_p = cplx2real(p1) + + +expo = Exponent(Dict(x=>Degree(1,0), y=>Degree(1,0), z=>Degree(2,0))) +println(expo) +real_p, imag_p = cplx2real(expo) +println(expo) + +println(real_p) +println(imag_p) + + +println("p1: $p1") +println("real part: $real_p") +println("imag part: $imag_p") + + +### Exponent generation... +x = Variable("x", Complex) +y = Variable("y", Complex) +z = Variable("z", Complex) +μ = Variable("μ", Complex) + +input_vars = [x, y, z, μ] +d = 3 diff --git a/dev/test_feasibility_GOC.jl b/dev/test_feasibility_GOC.jl new file mode 100644 index 0000000..8a0b0bc --- /dev/null +++ b/dev/test_feasibility_GOC.jl @@ -0,0 +1,38 @@ +"""Test +Test to launch in complex-modeler/src_test folder for example D:\repo\complex-modeler\src_test>julia test_feasibility_GOC.jl +# Arguments +- instances path +- instance folder +- scenario folder +- epsilon +Test to check feasibility of solution point given by GOC of VOLTM, Genbounds, Smax and coupling constraints +Print the names of the constraints violated along with a message + +""" +function read_arguments(ARGS) + if length(ARGS)!=4 + error("# Arguments + - instances path + - instance folder + - scenario folder + - epsilon") + else + instance_path = joinpath(pwd(),ARGS[1],ARGS[2],ARGS[3]) + epsilon = float(ARGS[4]) + return instance_path, epsilon + end + +end + +instance_path, epsilon = read_arguments(ARGS) + + +ROOT = pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +violations = test_feasibility_GOC(instance_path,epsilon) +for (scenario, dict) in violations + for (ctrname, msg) in dict + @printf("%30s %50s\n", ctrname, msg) + end +end diff --git a/doc/ExpandingComplexModeler.md b/doc/ExpandingComplexModeler.md new file mode 100644 index 0000000..620b322 --- /dev/null +++ b/doc/ExpandingComplexModeler.md @@ -0,0 +1,35 @@ +# Expanding ComplexModeler + +### Reading the new files(s) +In `/src/ComplexModeler/read_functions/`, a function `read_input(intput_type::T, filenames::Array{String})` allowing to build the datastructure holding all relevant numerical values has to be implemented. It must return : +- baseMVA::Float64: a float value allowing to convert the power quantities from p.u. (per unit) to MVA (MegaVolt Ampere), +- bus::Dict{String, MyType1}(): a dictionnary associating a bus name to the corresponding numerical values (load, generator, ...), stored in a custom datatype MyType1, +- link::Dict{Link, MyType2}(): a dictionnary associating a link to the corresponding numerical values (resistance, reactance, ...), stored in a custom data type, +- node_elems::Dict{String, Set{Any}}(): a dictionnary associating each bus to a set of "labels" that describes it. Each "label" is a custom julia type, corresponding to a nodal element such as generator, load, shunt, ... +- link_elems::Dict{Link, Set{Any}}(): a dictionnary associating each link to a set of "labels" that describes it. Each "label" is a custom julia type, corresponding to a nodal element such as Line_π, NullImpedance, ... + +A template including the function prototype, return arguments, and location of custom data storage type is provided. + +### Accessing the data +In order to ease access to the stored data, accessors can be implemented in `/src/ComplexModeler/base_accessors/`. It is likely these functions will receive as arguments the bus or link, the OPFProblem structure holding all the data and the current scenario, e.g. +```julia +function get_Sload(bus::String, OPFpbs::OPFProblems, scenario::String) +``` + +### Building variables, power balance and constraints by element +To each nodal or link element introduced by the new data format, such as null impedance lines, new generator types, etc corresponds a new julia type, derived from `AbstractNodeLabel` or `AbstractLinkLabel` --- these types are used in node_elems and link_elems to describe the network. To each of these types must be associated a number of functions allowing to determine: +- their contribution to each nodal power balance (if any) +- what new variables they require +- the supplemental constraints (if any) + +These functions have to be implemented in `/src/ComplexModeler/structural_fcts/`, a template is also provided (`template_elements.jl`). + +**TODO: provide explicit API for adding data in GS (new variables)** + +```julia +type MyType <: AbstractNodeLabel end + +function create_vars!(elem::T, bus::String, OPFpbs::OPFProblems, scenario::String) where T <: Type{MyType} + return +end +``` diff --git a/doc/make.jl b/doc/make.jl new file mode 100644 index 0000000..ac486ad --- /dev/null +++ b/doc/make.jl @@ -0,0 +1,15 @@ +push!(LOAD_PATH,"D:\\repo\\complex-modeler\\src_PowSysMod") +using Documenter, PowSysMod + +makedocs( + #options + format = :html, + sitename = "Documentation PowSysMod", + pages = [ + "Main functions" => "main_functions.md" + "General structures" => "general_structures.md" + "MatpowerInput" => "indexmatpower.md" + "GOCInput" => "indexGOC.md" + "PolynomialOptim" => "polynomialoptim_structures.md" + ] +) diff --git a/doc/src/general_structures.md b/doc/src/general_structures.md new file mode 100644 index 0000000..aa4cd49 --- /dev/null +++ b/doc/src/general_structures.md @@ -0,0 +1,7 @@ +# PowSysMod.jl Documentation of types + +```@autodocs +Modules = [PowSysMod] +Pages = ["PowSysMod_body.jl"] +Order = [:type] +``` diff --git a/doc/src/indexGOC.md b/doc/src/indexGOC.md new file mode 100644 index 0000000..f12b426 --- /dev/null +++ b/doc/src/indexGOC.md @@ -0,0 +1,76 @@ +# Read functions +```@autodocs +Modules = [PowSysMod] +Pages = ["GOC_read.jl"] +Orders = [:type, :functions] +``` + +# Elements of the power network + +## Node labels + +#### GOCVolt +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCVolt.jl"] +Orders = [:type, :functions] +``` + +#### GOCLoad +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCLoad.jl"] +Orders = [:type, :functions] +``` + +#### GOCGenerator +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCGenerator.jl"] +Orders = [:type, :functions] +``` + +#### GOCShunt +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCShunt.jl"] +Orders = [:type, :functions] +``` + +## Link labels + +#### GOCLineπ_notransformer +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCLineπ_notransformer.jl"] +Orders = [:type, :functions] +``` + +#### GOCLineπ_withtransformer +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCLineπ_withtransformer.jl"] +Orders = [:type, :functions] +``` + +#### GOCNullImpedance_notransformer +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCNullImpedance_notransformer.jl"] +Orders = [:type, :functions] +``` + +#### GOCNullImpedance_withtransformer +```@autodocs +Modules = [PowSysMod] +Pages = ["GOCNullImpedance_withtransformer.jl"] +Orders = [:type, :functions] +``` + + +# Read solution data +```@autodocs +Modules = [PowSysMod] +Pages = ["GOC_read_solutions.jl"] +Orders = [:type, :functions] +``` diff --git a/doc/src/indexmatpower.md b/doc/src/indexmatpower.md new file mode 100644 index 0000000..77c9c1b --- /dev/null +++ b/doc/src/indexmatpower.md @@ -0,0 +1,47 @@ +# Read functions +```@autodocs +Modules = [PowSysMod] +Pages = ["read_matpower.jl"] +Orders = [:type, :functions] +``` + +# Elements of the power network + +## Node labels + +#### GOCVolt +```@autodocs +Modules = [PowSysMod] +Pages = ["MatpowerVolt.jl"] +Orders = [:type, :functions] +``` + +#### MatpowerLoad +```@autodocs +Modules = [PowSysMod] +Pages = ["MatpowerLoad.jl"] +Orders = [:type, :functions] +``` + +#### MatpowerGenerator +```@autodocs +Modules = [PowSysMod] +Pages = ["MatpowerGenerator.jl"] +Orders = [:type, :functions] +``` + +#### MatpowerShunt +```@autodocs +Modules = [PowSysMod] +Pages = ["MatpowerShunt.jl"] +Orders = [:type, :functions] +``` + +## Link labels + +#### MatpowerLine_π +```@autodocs +Modules = [PowSysMod] +Pages = ["MatpowerLine_π.jl"] +Orders = [:type, :functions] +``` diff --git a/doc/src/main_functions.md b/doc/src/main_functions.md new file mode 100644 index 0000000..3b51cb0 --- /dev/null +++ b/doc/src/main_functions.md @@ -0,0 +1,31 @@ +# PowSysMod.jl Documentation + +## Building one OPF problem for one or several scenarios +Two functions have to be applied. +The first one `load_OPFproblems` converts data contained in `instance_path` in a specific entry `input_type` format into generic data of type OPFProblems. + +```@docs +load_OPFproblems(input_type, instance_path::String) +``` + +OPFProblems is represented thanks to three structures : DataSource, GridStructure, MathematicalProgramming. + +The second one `build_globalpb` constructs the OPF problem for all scenarios from generic data `OPFpbs`. + +```@docs +build_globalpb!(OPFpbs) +``` +A variant: `build_Problem` constructs the OPF problem for a given scenario `scenario` from generic data `OPFpbs`. + +```@docs +build_Problem!(OPFpbs, scenario::String) +``` + +```@repl +include(joinpath(pwd(),"..","..","src_PowSysMod", "PowSysMod_body.jl")) +OPFpbs = load_OPFproblems(MatpowerInput, joinpath(pwd(),"..","..","data_Matpower","matpower","WB2.m")) +OPF = build_Problem!(OPFpbs, "BaseCase") +print(OPF) +OPF = build_globalpb!(OPFpbs) +print(OPF) +``` diff --git a/doc/src/polynomialoptim_structures.md b/doc/src/polynomialoptim_structures.md new file mode 100644 index 0000000..7071c20 --- /dev/null +++ b/doc/src/polynomialoptim_structures.md @@ -0,0 +1,22 @@ +# PowSysMod.jl Documentation of types + +```@autodocs +Modules = [PowSysMod] +Pages = ["compare_dat.jl", +"export_dat.jl", +"export_polynomials.jl", +"Modeler_accessors.jl", +"Modeler_constructors.jl", +"Modeler_cplx2real.jl", +"PolynomialOptim.jl", +"polynomial_dat.jl", +"Poly_Cplx2Real.jl", +"Poly_operators.jl", +"Poly_order.jl", +"Poly_point_algebra.jl", +"Poly_poly_algebra.jl", +"Poly_print.jl", +"utils_ampl.jl", +"utils_Poly.jl"] +Orders = [:type, :functions] +``` diff --git a/examples/WB2.jl b/examples/WB2.jl new file mode 100644 index 0000000..93de38f --- /dev/null +++ b/examples/WB2.jl @@ -0,0 +1,58 @@ +using Poly +using Modeler + +## Build problem objects +V1 = Variable("VOLT_1", Complex) +V2 = Variable("VOLT_2", Complex) + +obj= 192 * V1*conj(V1) - (96.2+481im) * conj(V2)*V1 + (-96.2+481im) * conj(V1)*V2 + + +Load2 = (-96.2 -481im) * conj(V1)*V2 + ( 96.2 +481im) * conj(V2)*V2 +Load2_lb = -350 + 350im; Load2_ub = -350 + 350im +Unit1 = ( 96.2 +481im) * conj(V1)*V1 + (-96.2 -481im) * conj(V2)*V1 +Unit1_lb = -400im; Unit1_ub = 600 + 400im +Voltm1 = abs2(V1) +Voltm1_lb = 0.90; Voltm1_ub = 1.10 +Voltm2 = abs2(V2) +Voltm2_lb = 0.90; Voltm2_ub = 1.05 + +## Assemble problem +pb = Problem() +add_variable!(pb, V1) +add_variable!(pb, V2) + +set_objective!(pb, obj) + +add_constraint!(pb, "UNIT_1", Unit1_lb << Unit1 << Unit1_ub) +add_constraint!(pb, "LOAD_2", Load2_lb << Load2 << Load2_ub) +add_constraint!(pb, "VOLTM_1", Voltm1_lb << Voltm1 << Voltm1_ub) +add_constraint!(pb, "VOLTM_2", Voltm2_lb << Voltm2 << Voltm2_ub) + +println(pb) + +## Applying at a local solution +pt = Point([V1, V2], [-0.635304 + 0.706321im, 0.380961 + 0.898656im]) + +println("⟶ Constraints at Knitro local OPF solution:") +for (cstrName, cstr) in get_constraints(pb) + println(cstrName, ": ", cstr.lb, " ≤ ", evaluate(cstr.p, pt), " ≤ ", cstr.ub) +end + +println("⟶ Objective value: ", evaluate(pb.objective, pt)) +println("⟶ Corresponding slacks: ") +print(get_slacks(pb, pt)) +println("... and min slack: ", get_minslack(pb, pt)) + + + +## In real variables: +pb_R = pb_cplx2real(pb) +pt_R = Point([Variable("VOLT_1_r", Real), Variable("VOLT_1_i", Real), Variable("VOLT_2_r", Real), Variable("VOLT_2_i", Real)], [-0.635304, 0.706321, 0.380961, 0.898656]) + +println("\n⟶ Constraints at Knitro local OPF solution, real problem:") +for (cstrName, cstr) in get_constraints(pb_R) + println(cstrName, ": ", cstr.lb, " ≤ ", evaluate(cstr.p, pt_R), " ≤ ", cstr.ub) +end + +println("⟶ Objective value, real problem: ", evaluate(pb_R.objective, pt_R)) diff --git a/src_GOC/GOCGenerator.jl b/src_GOC/GOCGenerator.jl new file mode 100644 index 0000000..f20b36e --- /dev/null +++ b/src_GOC/GOCGenerator.jl @@ -0,0 +1,173 @@ +""" + mutable struct GOCGenerator <: AbstractNodeLabel + +Mutable structure descendant of AbstractNodeLabel + +# Fields +- `busname::String`: identify the bus associated to the generator +- `id::String`: identify the generator for the bus `busname` +- `power_min::Complex128`: Smin = Pmin + im Qmin +- `power_max::Complex128`: Smax = Pmax + im Qmax +- `participation_factor::Float64`: coefficient to define active power coupling constraints +- `dict_obj_coeffs::Dict{Int64,Float64}`: dictionary for objective coefficients: degree => coeff +""" +mutable struct GOCGenerator <: AbstractNodeLabel + busname::String + id::String + power_min::Complex128 #Smin = Pmin + i Qmin + power_max::Complex128 #Smax = Pmax + i Qmax + participation_factor::Float64 + dict_obj_coeffs::Dict{Int64,Float64} # dict : degree => coeff +end + + + +""" + create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCGenerator + +Create variables for generator `elemid` at `bus` in `bus_vars` if necessary.\n +If `elem_formulation == :NewVar`, create Sgen variable for the generator\n +If `elem_formulation == :GOCcoupling`, create Sgen variable for the generator, binary variables for comparison of voltages and create delta variable\n +Return nothing +""" +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCGenerator + if elem_formulation == :NewVar + bus_vars[elemid] = Variable(variable_name("Sgen", bus, elemid, scenario), Complex) + elseif elem_formulation == :GOCcoupling + bus_vars[elemid] = Variable(variable_name("Sgen", bus, elemid, scenario), Complex) + + # Binary variables for coupling constraints + Vbc_inf_Vsc = get_binInf_varname(basecase_scenario_name(), scenario, bus) + bus_vars[Vbc_inf_Vsc] = Variable(Vbc_inf_Vsc, Bool) + + Vsc_inf_Vbc = get_binInf_varname(scenario, basecase_scenario_name(), bus) + bus_vars[Vsc_inf_Vbc] = Variable(Vsc_inf_Vbc, Bool) + + # Scaling factor for current scenario + delta_varname = get_delta_varname(scenario) + bus_vars[delta_varname] = Variable(delta_varname, Real) + end + + return +end + +""" + [cstrname, polynom, lb, ub] = Snodal(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: GOCGenerator + +Return the polynomial contribution in power of generator `elemid` at `bus`(name, value, lower bound, upper bound). Will be used to construct power balance constraints in polynomial problem.\n +If `elem_formulation == :NbMinVar`, return generator bounds `["UNIT", Polynomial() ,Smin, Smax]`\n +If `elem_formulation == :NewVar` or `:GOCCoupling`, return -Sgen `["UNIT", -Sgen, 0, 0]`\n +Return no contribution `["", Polynomial(), 0, 0]` otherwise +""" +function Snodal(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: GOCGenerator + cstrname = "UNIT" + if elem_formulation == :NbMinVar + lb = element.power_min + ub = element.power_max + return [cstrname, Polynomial(), lb, ub] + elseif elem_formulation == :NewVar || elem_formulation == :GOCcoupling + return [cstrname, Polynomial(-bus_vars[elemid]), 0, 0] + end + return ["", Polynomial(), 0, 0] +end + +""" + constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCGenerator + +Return all the constraints defined by generator `elemid` at `bus`. Will be used to construct constraints in polynomial problem.\n +If `elem_formulation == :NewVar`, return generator bounds : "Genbounds" => Smin <= Sgen <= Smax\n +If `elem_formulation == :GOCCoupling`, return generator bounds defined by coupling constraints\n +Return empty dictionary otherwise +""" +function constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCGenerator + cstrs = Dict{String, Constraint}() + if elem_formulation == :NewVar + lb = element.power_min + ub = element.power_max + cstrname = get_GenBounds_cstrname() + cstrs[cstrname] = lb << bus_vars[elemid] << ub + end + + ## Active / Reactive formulation + if elem_formulation == :GOCcoupling + # Problem variables and parameters + Sgen = bus_vars[elemid] + + Volt_sc = bus_vars[volt_name()] + Volt_bc = OPFpbs[basecase_scenario_name()].mp.node_vars[bus][volt_name()] + + Vbc_inf_Vsc = bus_vars[get_binInf_varname(basecase_scenario_name(), scenario, bus)] + Vsc_inf_Vbc = bus_vars[get_binInf_varname(scenario, basecase_scenario_name(), bus)] + + Pmin, Qmin = real(element.power_min), imag(element.power_min) + Pmax, Qmax = real(element.power_max), imag(element.power_max) + ϵ, M = get_GOC_Volt_ϵ(), get_GOC_BigM() + + ## Definition of binary variables, upper constraint + ccname_bindef_upper = get_VoltBinDef_upper() + cstrs[ccname_bindef_upper] = (abs2(Volt_sc) - abs2(Volt_bc) - (Vbc_inf_Vsc * (-ϵ) + (1-Vbc_inf_Vsc) * M)) << 0 + ## Definition of binary variables, lower constraint + ccname_bindef_lower = get_VoltBinDef_lower() + cstrs[ccname_bindef_lower] = (abs2(Volt_sc) - abs2(Volt_bc) - (Vsc_inf_Vbc * ϵ + (1-Vsc_inf_Vbc) * (-M))) >> 0 + ## Definition of binary variables, complementarity + ccname_compl = get_VoltBinDef_complement() + cstrs[ccname_compl] = (Vbc_inf_Vsc + Vsc_inf_Vbc) << 1 + + + P0 = real(OPFpbs[basecase_scenario_name()].mp.node_vars[bus][elemid]) + Pgen, Qgen = real(Sgen), imag(Sgen) + deltavar = bus_vars[get_delta_varname(scenario)] + α = element.participation_factor + + ## Coupling constraint active : + cstrname = get_CC_active_cstrname() + cstrs[cstrname] = (Pgen - (P0 + α * deltavar)) == 0 + + ## Coupling constraint reactive : upper bound + ccname_upper = get_CC_reactiveupper_cstrname() + cstrs[ccname_upper] = ((Qgen - Vbc_inf_Vsc * (Qmin - Qmax) - Qmax)) << 0 + + ## Coupling constraint reactive : lower bound + ccname_lower = get_CC_reactivelower_cstrname() + cstrs[ccname_lower] = ((Qgen - Vsc_inf_Vbc * (Qmax - Qmin) - Qmin)) >> 0 + end + + return cstrs +end + + +""" + cost(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, Snode::Polynomial, lb, ub) where T <: GOCGenerator + +Return the polynomial contribution in objective generator `elemid` at `bus`. +""" +function cost(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, Snode::Polynomial, lb, ub) where T <: GOCGenerator + Sgen = gencost = Polynomial() + if elem_formulation == :NbMinVar + _, S, lb_gen, ub_gen = Snodal(element, bus, elemid, elem_formulation, bus_vars) + abs((lb-lb_gen) - (ub-ub_gen)) ≤ 5e-5 || warn("Power missing at $bus... $(abs((lb-lb_gen) - (ub-ub_gen)))") + Sgen = Snode - S - (ub-ub_gen) + # Sgen = add(Snode, -S - (ub-ub_gen)) + elseif elem_formulation == :NewVar + Sgen::Polynomial = bus_vars[elemid] + # Sgen = Polynomial(bus_vars[elemid]) + end + add!(Sgen, conj(Sgen)) + Sactive = (Sgen)*0.5 + Sexp = 1 + Polynomial() + dict_coeffs = element.dict_obj_coeffs + degrees_sorted = sort(collect(keys(dict_coeffs))) + #quartic objective + imax = 3 + #quadratic objective :TODO + #imax = 2 + if imax != 3 + warning("Objective is troncated to $imax degree") + end + for i=1:imax + degree = degrees_sorted[i] ## NOTE: NOT normal behaviour, required for linear cost... + add!(gencost, dict_coeffs[degree]*Sexp) + Sexp = Sexp*Sactive + end + return gencost +end diff --git "a/src_GOC/GOCLine\317\200_notransformer.jl" "b/src_GOC/GOCLine\317\200_notransformer.jl" new file mode 100644 index 0000000..9d23118 --- /dev/null +++ "b/src_GOC/GOCLine\317\200_notransformer.jl" @@ -0,0 +1,113 @@ +""" + struct GOCLineπ_notransformer <: AbstractLinkLabel + +Structure descendant of AbstractLinkLabel + +# Fields +- `link::Link` +- `id::String` +- `resistance::Float64`: coefficient rs +- `reactance::Float64`: coefficient xs +- `susceptance::Float64`: coefficient bc +- `power_magnitude_max::Float64`: Smax + +""" +struct GOCLineπ_notransformer <: AbstractLinkLabel + link::Link + id::String + resistance::Float64 #rs + reactance::Float64 #xs + susceptance::Float64 #bc + power_magnitude_max::Float64 #Smax +end + +""" + create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCLineπ_notransformer + +Create voltage variables for origin and destination of `link` in `link_vars`. +Return nothing. +""" +function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCLineπ_notransformer + origid, destid = link.orig, link.dest + link_vars["Volt_orig"] = Variable(variable_name("VOLT", origid, "", scenario), Complex) + link_vars["Volt_dest"] = Variable(variable_name("VOLT", destid, "", scenario), Complex) + return +end + + +""" + Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_notransformer + +Return the polynomial power at the origin of the line depending quadratically on the voltages : +```julia +rs = element.resistance +xs = element.reactance +bc = element.susceptance +ys = 1/(rs+im*xs) +Yff = ys+im*bc/2 +Yft = -ys +Sorig = (Yff*link_variables["Volt_orig"] + Yft*link_variables["Volt_dest"]) * baseMVA +``` +""" +function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_notransformer + return get_baseMVA(link.orig) * link_vars["Volt_orig"] * conj(get_IorigGOC(element, link_vars)) +end + +""" + Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_notransformer + +Return the polynomial power at the destination of the line depending quadratically on the voltages : +```julia +rs = element.resistance +xs = element.reactance +bc = element.susceptance +ys = 1/(rs+im*xs) +Ytf = -ys +Ytt = ys+im*bc/2 +Sdest = (Ytf*link_variables["Volt_orig"] + Ytt*link_variables["Volt_dest"]) * baseMVA +``` +""" +function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_notransformer + return get_baseMVA(link.dest) * link_vars["Volt_dest"] * conj(get_IdestGOC(element, link_vars)) +end + + +## 3. Constraints creation +function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCLineπ_notransformer + cstrs = Dict{String, Constraint}() + + Sor = Sorig(element, link, elemid, elem_formulation, link_vars) + Sde = Sdest(element, link, elemid, elem_formulation, link_vars) + Smax = element.power_magnitude_max + + cstrs[get_Smax_orig_cstrname()] = (abs2(real(Sor)) + abs2(imag(Sor))) << Smax^2 + cstrs[get_Smax_dest_cstrname()] = (abs2(real(Sde)) + abs2(imag(Sde))) << Smax^2 + + cstrs[get_Smax_orig_cstrname()].precond = :sqrt + cstrs[get_Smax_dest_cstrname()].precond = :sqrt + + return cstrs +end + + + +## Utils functions +function get_IorigGOC(element::T, link_variables::Dict{String, Variable}) where T<: GOCLineπ_notransformer + rs = element.resistance + xs = element.reactance + bc = element.susceptance + ys = 1/(rs+im*xs) + Yff = ys+im*bc/2 + Yft = -ys + return Yff*link_variables["Volt_orig"] + Yft*link_variables["Volt_dest"] +end + +function get_IdestGOC(element::T, link_variables::Dict{String, Variable}) where T<: GOCLineπ_notransformer + rs = element.resistance + xs = element.reactance + bc = element.susceptance + ys = 1/(rs+im*xs) + Ytf = -ys + Ytt = ys+im*bc/2 + return Ytf*link_variables["Volt_orig"] + Ytt*link_variables["Volt_dest"] +end diff --git "a/src_GOC/GOCLine\317\200_withtransformer.jl" "b/src_GOC/GOCLine\317\200_withtransformer.jl" new file mode 100644 index 0000000..e8c11d7 --- /dev/null +++ "b/src_GOC/GOCLine\317\200_withtransformer.jl" @@ -0,0 +1,124 @@ +""" + struct GOCLineπ_withtransformer <: AbstractLinkLabel + +Structure descendant of AbstractLinkLabel + +# Fields +- `link::Link` +- `id::String` +- `resistance::Float64`: coefficient rs +- `reactance::Float64`: coefficient xs +- `susceptance::Float64`: coefficient bc +- `transfo_ratio::Float64`: ratio τ +- `transfo_phase::Float64`: phase θ +- `power_magnitude_max::Float64`: Smax + +""" +struct GOCLineπ_withtransformer <: AbstractLinkLabel + link::Link + id::String + resistance::Float64 #rs + reactance::Float64 #xs + susceptance::Float64 #bc + transfo_ratio::Float64 #τ + transfo_phase::Float64 #θ + power_magnitude_max::Float64 #Smax +end + +""" + create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCLineπ_withtransformer + +Create voltage variables for origin and destination of `link` in `link_vars`. +Return nothing. +""" +function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCLineπ_withtransformer + origid, destid = link.orig, link.dest + link_vars["Volt_orig"] = Variable(variable_name("VOLT", origid, "", scenario), Complex) + link_vars["Volt_dest"] = Variable(variable_name("VOLT", destid, "", scenario), Complex) + return +end + + +""" + Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_withtransformer + +Return the polynomial power at the origin of the line depending quadratically on the voltages : +```julia +τ = element.transfo_ratio +θ = element.transfo_phase *pi/180 +rs = element.resistance +xs = element.reactance +bc = element.susceptance +ys = 1/(rs+im*xs) +Yff = (ys+im*bc/2)/(τ^2) +Yft = -ys*1/(τ*exp(-im*θ)) +Sorig = (Yff*link_variables["Volt_orig"] + Yft*link_variables["Volt_dest"]) * baseMVA +``` +""" +function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_withtransformer + return get_baseMVA(link.orig) * link_vars["Volt_orig"] * conj(get_IorigGOC(element, link_vars)) +end + + +""" + Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_withtransformer + +Return the polynomial power at the destination of the line depending quadratically on the voltages : +```julia +τ = element.transfo_ratio +θ = element.transfo_phase *pi/180 +rs = element.resistance +xs = element.reactance +bc = element.susceptance +ys = 1/(rs+im*xs) +Ytf = -ys*1/(τ*exp(im*θ)) +Ytt = ys+im*bc/2 +Sdest = (Ytf*link_variables["Volt_orig"] + Ytt*link_variables["Volt_dest"]) * baseMVA +``` +""" +function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCLineπ_withtransformer + return get_baseMVA(link.dest) * link_vars["Volt_dest"] * conj(get_IdestGOC(element, link_vars)) +end + + +## 3. Constraints creation +function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T<:GOCLineπ_withtransformer + cstrs = Dict{String, Constraint}() + + Sor = Sorig(element, link, elemid, elem_formulation, link_vars) + Sde = Sdest(element, link, elemid, elem_formulation, link_vars) + Smax = element.power_magnitude_max + + cstrs[get_Smax_orig_cstrname()] = (abs2(real(Sor)) + abs2(imag(Sor))) << Smax^2 + cstrs[get_Smax_dest_cstrname()] = (abs2(real(Sde)) + abs2(imag(Sde))) << Smax^2 + + cstrs[get_Smax_orig_cstrname()].precond = :sqrt + cstrs[get_Smax_dest_cstrname()].precond = :sqrt + + return cstrs +end + +##Util functions +function get_IorigGOC(element::T, link_variables::Dict{String, Variable}) where T<: GOCLineπ_withtransformer + τ = element.transfo_ratio + θ = element.transfo_phase *pi/180 + rs = element.resistance + xs = element.reactance + bc = element.susceptance + ys = 1/(rs+im*xs) + Yff = (ys+im*bc/2)/(τ^2) + Yft = -ys*1/(τ*exp(-im*θ)) + return Yff*link_variables["Volt_orig"] + Yft*link_variables["Volt_dest"] +end + +function get_IdestGOC(element::T, link_variables::Dict{String, Variable}) where T<: GOCLineπ_withtransformer + τ = element.transfo_ratio + θ = element.transfo_phase *pi/180 + rs = element.resistance + xs = element.reactance + bc = element.susceptance + ys = 1/(rs+im*xs) + Ytf = -ys*1/(τ*exp(im*θ)) + Ytt = ys+im*bc/2 + return Ytf*link_variables["Volt_orig"] + Ytt*link_variables["Volt_dest"] +end diff --git a/src_GOC/GOCLoad.jl b/src_GOC/GOCLoad.jl new file mode 100644 index 0000000..01c2f75 --- /dev/null +++ b/src_GOC/GOCLoad.jl @@ -0,0 +1,67 @@ +""" + struct GOCLoad <: AbstractNodeLabel + +Structure descendant of AbstractNodeLabel + +# Fields +- `busname::String` +- `id::String`: necessary if several loads at a bus +- `load::Complex128`: Sd = Pd + im Qd + +""" +struct GOCLoad <: AbstractNodeLabel + busname::String + id::String #label + load::Complex128 #Sd = Pd+im*Qd +end + +""" + create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCLoad + +Create load variable at `bus` for `elemid` in `bus_vars` if `elem_formulation == : NewVar`. \n +Return nothing +""" +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCLoad + if elem_formulation == :NewVar + bus_vars[elemid] = Variable(variable_name("Sload", bus, elemid, scenario), Complex) + end + return +end + + +""" + [cstrname, polynom, lb, ub] = Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: GOCLoad + +Return the polynomial contribution in power of load `elemid` at `busid`. Will be used to construct power balance constraints in polynomial problem.\n +If `elem_formulation == :NbMinVar`, return value of Sload `[cstrname, Sload, 0, 0]`\n +If `elem_formulation == :NewVar`, return variable Sload `[cstrname, Polynomial(Sload), 0, 0]`\n +Return `["", Polynomial(), 0, 0]` otherwise. +""" +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: GOCLoad + cstrname = "LOAD" + if elem_formulation == :NbMinVar + Sload = element.load + return [cstrname, Sload, 0, 0] + elseif elem_formulation == :NewVar + return [cstrname, Polynomial(bus_vars[elemid]), 0, 0] + else + return ["", Polynomial(), 0, 0] + end +end + + +""" + constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCLoad + +Return a constraint to define load variable for `elemid` at `bus` if `elem_formulation == : NewVar`: `"LOAD"=> Sload==value` \n +Return empty dictionary of constraints otherwise. + +""" +function constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCLoad + if elem_formulation == :NewVar + cstrname = get_LoadDef_cstrname() + Sload = element.load + return Dict{String, Constraint}(cstrname => Polynomial(bus_vars[elemid]) == Sload) + end + return Dict{String, Constraint}() +end diff --git a/src_GOC/GOCNullImpedance_notransformer.jl b/src_GOC/GOCNullImpedance_notransformer.jl new file mode 100644 index 0000000..05a33b8 --- /dev/null +++ b/src_GOC/GOCNullImpedance_notransformer.jl @@ -0,0 +1,60 @@ +""" + struct GOCNullImpedance_notransformer <: AbstractLinkLabel + +Structure descendant of AbstractLinkLabel + +# Fields +- `link::Link` +- `id::String` +- `susceptance::Float64`: coefficient bc +- `power_magnitude_max::Float64`: Smax + +""" +struct GOCNullImpedance_notransformer <: AbstractLinkLabel + link::Link + id::String + susceptance::Float64 #bc + power_magnitude_max::Float64 #Smax +end + +""" + create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCNullImpedance_notransformer + +Create voltage variables and power variables for origin and destination of `link` in `link_vars`. +Return nothing. +""" +function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCNullImpedance_notransformer + origid, destid = link.orig, link.dest + link_vars["Volt_orig"] = Variable(variable_name("VOLT", origid, "", scenario), Complex) + link_vars["Volt_dest"] = Variable(variable_name("VOLT", destid, "", scenario), Complex) + + link_vars[elemid*"_Sorig"] = Variable(variable_name("Sorig", origid, "", scenario), Complex) + link_vars[elemid*"_Sdest"] = Variable(variable_name("Sdest", destid, "", scenario), Complex) + #elem_formulation modified ? + return +end + + +""" + Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_notransformer + +Return power variable Sorig * baseMVA. +""" +function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_notransformer + return get_baseMVA(link.orig)*link_vars[elemid*"_Sorig"] +end + +""" + Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_notransformer + +Return power variable Sdest * baseMVA. +""" +function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_notransformer + return get_baseMVA(link.dest)*link_vars[elemid*"_Sdest"] +end + + +## 3. Constraints creation +# function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MyType +# return Dict{String, Constraint}() +# end diff --git a/src_GOC/GOCNullImpedance_withtransformer.jl b/src_GOC/GOCNullImpedance_withtransformer.jl new file mode 100644 index 0000000..7f6db5d --- /dev/null +++ b/src_GOC/GOCNullImpedance_withtransformer.jl @@ -0,0 +1,64 @@ +""" + struct GOCNullImpedance_withtransformer <: AbstractLinkLabel + +Structure descendant of AbstractLinkLabel + +# Fields +- `link::Link` +- `id::String` +- `susceptance::Float64`: coefficient bc +- `transfo_ratio::Float64`: ratio τ +- `transfo_phase::Float64`: phase θ +- `power_magnitude_max::Float64`: Smax + +""" +struct GOCNullImpedance_withtransformer <: AbstractLinkLabel + link::Link + id::String + susceptance::Float64 #bc + transfo_ratio::Float64 #τ + transfo_phase::Float64 #θ + power_magnitude_max::Float64 #Smax +end + +""" + create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCNullImpedance_withtransformer + +Create voltage variables and power variables for origin and destination of `link` in `link_vars`. +Return nothing. +""" +function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: GOCNullImpedance_withtransformer + origid, destid = link.orig, link.dest + link_vars["Volt_orig"] = Variable(variable_name("VOLT", origid, "", scenario), Complex) + link_vars["Volt_dest"] = Variable(variable_name("VOLT", destid, "", scenario), Complex) + + link_vars[elemid*"_Sorig"] = Variable(variable_name("Sorig", origid, "", scenario), Complex) + link_vars[elemid*"_Sdest"] = Variable(variable_name("Sdest", destid, "", scenario), Complex) + #elem_formulation modified ? + return +end + + +""" + Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_withtransformer + +Return power variable Sorig * baseMVA. +""" +function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_withtransformer + return get_baseMVA(link.orig)*link_vars[elemid*"_Sorig"] +end + +""" + Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_withtransformer + +Return power variable Sdest * baseMVA. +""" +function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:GOCNullImpedance_withtransformer + return get_baseMVA(link.dest)*link_vars[elemid*"_Sdest"] +end + + +## 3. Constraints creation +# function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MyType +# return Dict{String, Constraint}() +# end diff --git a/src_GOC/GOCShunt.jl b/src_GOC/GOCShunt.jl new file mode 100644 index 0000000..36e9f0b --- /dev/null +++ b/src_GOC/GOCShunt.jl @@ -0,0 +1,67 @@ +""" + struct GOCShunt <: AbstractNodeLabel + +Structure descendant of AbstractNodeLabel + +# Fields +- `busname::String` +- `id::String` +- `shunt::Complex128`: shunt = Gs + im Bs + +""" +struct GOCShunt <: AbstractNodeLabel + busname::String + id::String #label + shunt::Complex128 # coeffS = Gs + im*Bs +end + +""" + create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCShunt + +Create shunt variable for shunt `elemid` at `bus` in `bus_vars` if `elem_formulation == :NewVar`. \n +Return nothing +""" +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCShunt + if elem_formulation == :NewVar + bus_vars[elemid] = Variable(variable_name("Sshunt", bus, elemid, scenario), Complex) + end + return +end + + +""" + [cstrname, polynom, lb, ub] = Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: GOCShunt + +Return the polynomial power contribution for shunt `elemid` at `busid`. Will be used to construct power balance constraints in polynomial problem.\n +If `elem_formulation == :NbMinVar`, return expression of Sshunt depending on the voltage `[cstrname, conj(shunt_value)|V|^2, 0, 0]`\n +If `elem_formulation == :NewVar`, return variable Sshunt `[cstrname, Polynomial(Shunt), 0, 0]`\n +Return `["", Polynomial(), 0, 0]` otherwise. +""" +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: GOCShunt + cstrname = "SHUNT" + if elem_formulation == :NbMinVar + p = conj(element.shunt) * abs2(bus_vars[volt_name()]) + return [cstrname, p, 0, 0] + elseif elem_formulation == :NewVar + return [cstrname,Polynomial(bus_vars[elemid]), 0, 0] + end + return ["", Polynomial(), 0, 0] + +end + + +""" + constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCShunt + +Return a constraint to define shuntvariable for `elemid` at `bus` if `elem_formulation == : NewVar`: `"SHUNT"=> Sshunt-conj(shunt_value)|V|^2==0` \n +Return empty dictionary of constraints otherwise. + +""" +function constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCShunt + if elem_formulation == :NewVar + cstrname = get_ShuntDef_cstrname() + Sshunt = element.shunt* abs2(bus_vars[volt_name()]) + return Dict{String, Constraint}(cstrname => Polynomial(Sshunt - bus_vars[elemid]) == 0) + end + return Dict{String, Constraint}() +end diff --git a/src_GOC/GOCVolt.jl b/src_GOC/GOCVolt.jl new file mode 100644 index 0000000..4619291 --- /dev/null +++ b/src_GOC/GOCVolt.jl @@ -0,0 +1,51 @@ +""" + struct GOCVolt <: AbstractNodeLabel + +Structure descendant of AbstractNodeLabel + +# Fields +- `busname::String` +- `baseKV::Float64` +- `baseMVA::Float64` +- `voltage_magnitude_min::Float64`: Vmin +- `voltage_magnitude_max::Float64`: Vmax +""" +struct GOCVolt <: AbstractNodeLabel + busname::String + baseKV::Float64 + baseMVA::Float64 ##TODO: remove + voltage_magnitude_min::Float64 #Vmin + voltage_magnitude_max::Float64 #Vmax +end + + +""" + create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCVolt + +Create voltage variable for `bus` in `bus_vars`.\n +Return nothing. +""" +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: GOCVolt + bus_vars[elemid] = Variable(variable_name("VOLT", bus, "", scenario), Complex) + return +end + + +## 2. Power balance +# function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MyType +# return ["", Polynomial(), 0, 0] +# end + + +""" + constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCVolt + +Return the constraint of bounds on voltage magnitude at `bus`: "VOLTM" => Vmin^2 <= |V|^2 <= Vmax^2 + +""" +function constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: GOCVolt + cstrname = get_VoltM_cstrname() + Vmin = element.voltage_magnitude_min + Vmax = element.voltage_magnitude_max + return Dict{String, Constraint}(cstrname => Vmin^2 << abs2(bus_vars[volt_name()]) << Vmax^2) +end diff --git a/src_GOC/GOC_files.jl b/src_GOC/GOC_files.jl new file mode 100644 index 0000000..b260fbc --- /dev/null +++ b/src_GOC/GOC_files.jl @@ -0,0 +1,13 @@ +##file to include in ComplexModeler.jl which contains all files associated to GOC + +include("GOCGenerator.jl") +include("GOCLoad.jl") +include("GOCShunt.jl") +include("GOCVolt.jl") +include("GOCLineπ_notransformer.jl") +include("GOCLineπ_withtransformer.jl") +include("GOCNullImpedance_notransformer.jl") +include("GOCNullImpedance_withtransformer.jl") +include("GOC_read.jl") +include("GOC_read_solutions.jl") +include("GOCcheck_feasibility.jl") diff --git a/src_GOC/GOC_read.jl b/src_GOC/GOC_read.jl new file mode 100644 index 0000000..d3b137a --- /dev/null +++ b/src_GOC/GOC_read.jl @@ -0,0 +1,474 @@ +type GOCInput <: AbstractInput end + + +##TODO +function get_baseMVA(bus::String) + return 100 +end + + +""" + read_input(input_type::T, instance_path::String) where T<:Type{GOCInput} + +Read instance in `instance_path` depending on `input_type`.\n +Return a structure OPFProblems.\n +For `GOCInput`, read files pscopf_data.mat, contingency.csv and generator.csv, store them in arrays and use information to fulfill a DataSource structure for the basecase scenario. Finally take into account contingencies. + +""" +function read_input(input_type::T, instance_path::String) where T<:Type{GOCInput} + #read + power_data,generator_data,contingency_data = getdataGOC(instance_path) + ##info in bus + bus,index = read_data_bus(power_data) + ##info in generator + gendata_dict = generator_data_to_dict(generator_data,index) + ##info in generator + bus = add_generator_data!(power_data,gendata_dict,bus,index) + ##info in branch + link = read_branch_data(power_data,index) + #info basecase + ds = DataSource(bus,link) + node_linksin, node_linksout = Dict{String, Set{Link}}(), Dict{String, Set{Link}}() + node_vars = Dict{String, Dict{String, Variable}}() + link_vars = Dict{Link, Dict{String, Variable}}() + gs = GridStructure("BaseCase", node_linksin, node_linksout) + node_formulations = Dict{String, Dict{Tuple{Type, String}, Symbol}}() + link_formulations = Dict{Link, Dict{Tuple{Type, String}, Symbol}}() + mp = MathematicalProgramming(node_formulations, link_formulations, node_vars,link_vars) + ##read scenarios + OPFproblems = scenarios_data(ds, gs, mp, power_data, contingency_data,index) + return OPFproblems +end + + +######################### +## Utils functions +######################### +""" + getdataGOC(instance_path) + +Read 3 files (pscopf_data.mat,generator.csv,contingency.csv) in `instance_path` and returns 3 arrays of data (power_data,generator_data,contingency_data) +Return an error if one the three files does not exist in path_folder. +""" +function getdataGOC(instance_path) ##String instance+scenario + power_data = getpowerdata(instance_path) + generator_data = getgeneratordata(instance_path) + contingency_data = getcontingencydata(instance_path) + return power_data,generator_data,contingency_data +end + +function getpowerdata(instance_path) + path_folder = instance_path + if !("pscopf_data.mat" ∈ readdir(path_folder)) + error("missing file : pscopf_data.mat must be in $path_folder") + end + try + return read(matopen(joinpath(path_folder,"pscopf_data.mat"))) + catch + warn("Not possible to read matopen(pscopf_data.mat)") + return + end +end + +function getgeneratordata(instance_path) + path_folder = instance_path + if !("generator.csv" ∈ readdir(path_folder)) + error("missing file : generator.csv must be in $path_folder") + end + try + return readdlm(joinpath(path_folder,"generator.csv"),',',skipstart=1) + catch + warn("Not possible to readdlm generator.csv") + return + end + +end + +function getcontingencydata(instance_path) + path_folder = instance_path + if !("contingency.csv" ∈ readdir(path_folder)) + error("missing file : contingency.csv must be in $path_folder") + end + try + return readdlm(joinpath(path_folder,"contingency.csv"),',',skipstart=1) + catch + warn("Not possible to readdlm contingency.csv") + end +end + + + +######################### +function get_bus_data(bus_data, listofkeys, id) + listofvalues = [] + for key in listofkeys + if !haskey(bus_data, key) + error("bus_data dictionary does not have a key $key") + else + append!(listofvalues, bus_data[key][id]) + end + end + return listofvalues +end + +function get_bus_index(power_data) + nb_bus = Int(power_data["totalbus"]) + all_busdata_key = filter(x->ismatch(r"all_busdata", x), collect(keys(power_data)))[1] + raw_bus_data = power_data[all_busdata_key] + nb_lines = size(raw_bus_data,1) + if nb_lines!=nb_bus + error("number of lines of all_busdata not equal to number of bus in power_data at key totalbus") + end + index = Dict{Int64,Int64}() + for line in 1:nb_lines + id_bus = Int(raw_bus_data[line,1]) + index[id_bus] = line + end + return index +end + + +""" + read_data_bus(power_data) + +Reads data in `power_data` to construct the DataSource field bus without generator data (only VOLTM, load and shunt data) +""" +function read_data_bus(power_data) + nb_bus = Int(power_data["totalbus"]) + index = get_bus_index(power_data) + bus_data = power_data["bus"] + # all_busName_key = filter(x->ismatch(r"all_busName", x), collect(keys(power_data)))[1] + # busname_data = power_data[all_busName_key] + all_busdata_key = filter(x->ismatch(r"all_busdata", x), collect(keys(power_data)))[1] + raw_bus_data = power_data[all_busdata_key] + nb_lines = size(raw_bus_data,1) + if nb_lines!=length(bus_data["BaseKV"]) + error("sizes of all_busName and bus not equal") + end + bus = Dict(bus_name(line) => Dict{String,Any}() for line in 1:nb_bus) + for line in 1:nb_lines + id_bus = Int(raw_bus_data[line,1]) + # index[id_bus] = line + busname = bus_name(index[id_bus]) + baseKV = bus_data["BaseKV"][line] + baseMVA = 1.0 + voltage_magnitude_min , voltage_magnitude_max = get_bus_data(bus_data, ["VoltageMagnitudeMin", "VoltageMagnitudeMax"], line) + bus[busname][volt_name()] = GOCVolt(busname, baseKV, baseMVA, voltage_magnitude_min, voltage_magnitude_max) + end + + all_loadID_key = filter(x->ismatch(r"all_loadID", x), collect(keys(power_data)))[1] + load_id = power_data[all_loadID_key] + all_loaddata_key = filter(x->ismatch(r"all_loaddata", x), collect(keys(power_data)))[1] + load_data = power_data[all_loaddata_key] + + for line in 1:size(load_data,1) + id_bus = Int(load_data[line,1]) + busname = bus_name(index[id_bus]) + load = load_data[line,5] + im * load_data[line,6] + id_load = id_elem(load_id,line) + loadname = load_name(id_load) + bus[busname][loadname] = GOCLoad(busname,loadname,load) + end + + all_fixedshuntID_key = filter(x->ismatch(r"all_fixedshuntID", x), collect(keys(power_data)))[1] + shunt_id = power_data[all_fixedshuntID_key] + all_fixedshuntdata_key = filter(x->ismatch(r"all_fixedshuntdata", x), collect(keys(power_data)))[1] + shunt_data = power_data[all_fixedshuntdata_key] + for line in 1:size(shunt_data,1) + id_bus = Int(shunt_data[line,1]) + busname = bus_name(index[id_bus]) + shunt = shunt_data[line,3] + im * shunt_data[line,4] + id_shunt = id_elem(shunt_id,line) + shuntname = shunt_name(id_shunt) + bus[busname][shuntname] = GOCShunt(busname,shuntname,shunt) + end + + return bus,index +end + +function id_elem(array_id,line) + if typeof(array_id) == String + temp_string = array_id + else + temp_string = array_id[line] + if typeof(temp_string)==Float64 + temp_string = Int(temp_string) + end + end + return temp_string + #return matchall(r"\S+" ,temp_string)[1] +end + +######################### +function get_generator_data(generator_data, listofkeys, id) + listofvalues = [] + for key in listofkeys + if !haskey(generator_data, key) + error("generator_data dictionary does not have a key $key") + else + append!(listofvalues, generator_data[key][id]) + end + end + return listofvalues +end + + +function generator_data_to_dict(generator_data,index) ##conversion array to dict + generator_data_dict = Dict{String, Dict{String,Dict{Int64,Float64}}}() + for line in 1:size(generator_data,1) + bus_id = Int(generator_data[line,1]) + busname = bus_name(index[bus_id]) + gen_id = generator_data[line,2] + gen_id = remove_simple_quotes_if_present(gen_id) + if typeof(gen_id)==Float64 + gen_id = Int(gen_id) + end + genname = generator_name(gen_id) + if !haskey(generator_data_dict, busname) + generator_data_dict[busname] = Dict{String,Dict{Int64,Float64}}() + end + if !haskey(generator_data_dict[busname],genname) + generator_data_dict[busname][genname] = Dict{Int64,Float64}() + end + term_id = Int(generator_data[line,3]) + value = generator_data[line,4] + if term_id!=9 + generator_data_dict[busname][genname][term_id] = value + end + end + return generator_data_dict +end + +function remove_simple_quotes_if_present(id) + if typeof(id)==SubString{String} + if id[end-1]==' ' + return id[2:(end-2)] + else + return id[2:(end-1)] + end + else + if id[end]==' ' + return id[1:(end-1)] + else + return id + end + end +end + +""" + add_generator_data!(power_data,generator_data_dict,bus,index) + +Add generator data contained in `power_data` in DataSource field `bus` depending on `generator_data_dict` and `index`.\n +Return `bus`. + +""" +function add_generator_data!(power_data,generator_data_dict,bus,index) + nb_bus = Int(power_data["totalbus"]) + generator_data = power_data["generator"] + for gen in 1:length(generator_data["BusNum"]) + bus_id = Int(get_generator_data(generator_data,["BusNum"],gen)[1]) + # all_busName_key = filter(x->ismatch(r"all_busName", x), collect(keys(power_data)))[1] + # busname_data = power_data[all_busName_key] + busname = bus_name(index[bus_id]) + gen_id, Pmin, Qmin, Pmax, Qmax, participation_factor = get_generator_data(generator_data, ["UnitID","RealPowerMin","ReactivePowerMin","RealPowerMax","ReactivePowerMax", "ParticipationFactor"], gen) + power_min = Pmin + im*Qmin + power_max = Pmax + im*Qmax + # cost_degrees = generator_data["RealPowerCostExponent"][gen,:] + # cost_coeffs = generator_data["RealPowerCostCoefficient"][gen,:] + gen_id = remove_simple_quotes_if_present(gen_id) + if typeof(gen_id)==Float64 + gen_id = Int(gen_id) + end + generatorname = generator_name(gen_id) + dict_obj_coeffs = Dict{Int64,Float64}() + for (degree, value) in generator_data_dict[busname][generatorname] + if (degree ∈ [0,1,2]) + dict_obj_coeffs[degree] = value + end + end + if length(dict_obj_coeffs)<3 + #warn("A 2-order polynom cost with less than 3 values for generator $generatorname at bus $busname, default value 0") + if !haskey(dict_obj_coeffs,0) dict_obj_coeffs[0] = 0 end + if !haskey(dict_obj_coeffs,1) dict_obj_coeffs[1] = 0 end + if !haskey(dict_obj_coeffs,2) dict_obj_coeffs[2] = 0 end + end + bus[busname][generatorname] = GOCGenerator(busname,generatorname,power_min,power_max,participation_factor,dict_obj_coeffs) + end + return bus +end + +######################### +function get_branch_data(branch_data, listofkeys, id) + listofvalues = [] + for key in listofkeys + if !haskey(branch_data, key) + error("branch_data dictionary does not have a key $key") + else + append!(listofvalues, branch_data[key][id]) + end + end + return listofvalues +end + +""" + read_branch_data(power_data, index) + +Read branch data contained in `power_data` to fulfill DataSource field `link` depending on `index`. \n +Return dictionary `link`. +""" +function read_branch_data(power_data, index) + nb_branch = Int(power_data["totalbranch"]) + nb_bus = Int(power_data["totalbus"]) + # all_busName_key = filter(x->ismatch(r"all_busName", x), collect(keys(power_data)))[1] + # busname_data = power_data[all_busName_key] + all_lineCKT_key = filter(x->ismatch(r"all_lineCKT", x), collect(keys(power_data)))[1] + lines_data = power_data[all_lineCKT_key] + nb_lines = length(lines_data) + link = Dict{Link, Dict{String,Any}}() + branch_data = power_data["branch"] + + #lines without transformer + for branch in 1:nb_lines + origin = Int(get_branch_data(branch_data, ["Origin"], branch)[1]) + destination = Int(get_branch_data(branch_data, ["Destination"], branch)[1]) + linkname = Link(bus_name(index[origin]),bus_name(index[destination])) + branch_id = remove_simple_quotes_if_present(lines_data[branch]) + if typeof(branch_id)==Float64 + branch_id = Int(branch_id) + end + resistance, reactance, susceptance, power_magnitude_max = get_branch_data(branch_data, ["SeriesResistance","SeriesReactance","ChargingSusceptance","PowerMagnitudeMax"], branch) + if !haskey(link, linkname) + link[linkname] = Dict{String,Any}() + end + if resistance == reactance == 0 + println(linkname, " nullimpedance line without transformer") + name_line = nullimpedance_notransformer_name(branch_id) + link[linkname][name_line] = GOCNullImpedance_notransformer(linkname,name_line,susceptance, power_magnitude_max) + else + name_line = linepi_notransformer_name(branch_id) + link[linkname][name_line] = GOCLineπ_notransformer(linkname,name_line,resistance,reactance,susceptance, power_magnitude_max) + end + end + + all_transformerCKT_key = filter(x->ismatch(r"all_transformerCKT", x), collect(keys(power_data)))[1] + transformername_data = power_data[all_transformerCKT_key] + + #lines with transformers + for branch in (nb_lines+1):nb_branch + origin = Int(get_branch_data(branch_data, ["Origin"], branch)[1]) + destination = Int(get_branch_data(branch_data, ["Destination"], branch)[1]) + linkname = Link(bus_name(index[origin]),bus_name(index[destination])) + branch_id = remove_simple_quotes_if_present(transformername_data[branch-nb_lines]) + if typeof(branch_id)==Float64 + branch_id = Int(branch_id) + end + resistance, reactance, susceptance, power_magnitude_max = get_branch_data(branch_data, ["SeriesResistance","SeriesReactance","ChargingSusceptance","PowerMagnitudeMax"], branch) + transfo_ratio, transfo_phase = get_branch_data(branch_data, ["TapRatio", "PhaseShift"], branch) + if !haskey(link, linkname) + link[linkname] = Dict{String,Any}() + end + if resistance == reactance == 0 + println(linkname, " nullimpedance line with transformer") + name_line = nullimpedance_withtransformer_name(branch_id) + link[linkname][name_line] = GOCNullImpedance_withtransformer(linkname,name_line,susceptance, transfo_ratio,transfo_phase, power_magnitude_max) + else + name_line = linepi_withtransformer_name(branch_id) + link[linkname][name_line] = GOCLineπ_withtransformer(linkname,name_line,resistance,reactance,susceptance, transfo_ratio,transfo_phase, power_magnitude_max) + end + end + + return link +end + +######################### +""" + scenarios_data(ds,gs,mp,power_data, contingency_data,index) + +Use dictionaries `ds`, `gs` and `mp` to create `Scenario` structures for each contingency contained in `contingency_data`.\n +Return an `OPFproblems` structure : "scenario" => Scenario + +""" +function scenarios_data(ds,gs,mp,power_data, contingency_data,index) + output = Dict("BaseCase" => Scenario(ds,gs,mp)) + ds = output["BaseCase"].ds + nb_contingencies = size(contingency_data,1) + nb_bus = Int(power_data["totalbus"]) + for ct in 1:nb_contingencies + id_contingency = contingency_data[ct,1] + scenario_name = scenarioname(id_contingency) + type_contingency = contingency_data[ct,2] + #B, T or G (Branch, Transformer, Generator) + gs_scenario = copy(gs) + gs_scenario.scenario = scenario_name + ds_scenario_bus = Dict(busname => Dict{String,Any}() for busname in keys(ds.bus)) + ds_scenario_link = Dict(linkname => Dict{String,Any}() for linkname in keys(ds.link)) + ds_scenario = DataSource(ds_scenario_bus,ds_scenario_link) + if type_contingency == "G" + println(type_contingency) + bus_id = contingency_data[ct,3] + busname = bus_name(index[bus_id]) + gen_num = contingency_data[ct,4] + CID = contingency_data[ct,5] + if typeof(CID)==SubString{String} + gen_ID = CID[2:(end-1)] + else + gen_ID = string(CID) + end + for (bus,dict) in ds.bus + if bus==busname + for (buslabel,data) in dict + if buslabel != generator_name(gen_ID) + ds_scenario.bus[bus][buslabel] = ds.bus[bus][buslabel] + end + end + else + for (buslabel,data) in dict + ds_scenario.bus[bus][buslabel] = ds.bus[bus][buslabel] + end + end + end + for (linkname,dict) in ds_scenario.link + for (linklabel,data) in ds.link[linkname] + ds_scenario.link[linkname][linklabel] = ds.link[linkname][linklabel] + end + end + elseif type_contingency=="B" || type_contingency=="T" + #println("Type of contingency $ct is $type_contingency") + origin = Int(contingency_data[ct,3]) + destination = Int(contingency_data[ct,4]) + link_to_remove = Link(bus_name(index[origin]),bus_name(index[destination])) + CID = contingency_data[ct,5] + if typeof(CID)==SubString{String} + id_line = CID[2:(end-1)] + else + id_line = string(CID) + end + for (busname,dict) in ds.bus + for (buslabel,data) in dict + ds_scenario.bus[busname][buslabel] = ds.bus[busname][buslabel] + end + end + for (linkname,dict) in ds_scenario.link + if linkname.orig == link_to_remove.orig && linkname.dest == link_to_remove.dest + #println("link to remove : ", link_to_remove) + for (linklabel,data) in ds.link[linkname] + if !contains(linklabel,id_line) + ds_scenario.link[linkname][linklabel] = ds.link[linkname][linklabel] + end + end + if isempty(ds_scenario.link[linkname]) + delete!(ds_scenario.link,linkname) + end + else + for (linklabel,data) in ds.link[linkname] + ds_scenario.link[linkname][linklabel] = ds.link[linkname][linklabel] + end + end + end + end + output[scenario_name] = Scenario(ds_scenario,gs_scenario,mp) + end + + + return output +end diff --git a/src_GOC/GOC_read_solutions.jl b/src_GOC/GOC_read_solutions.jl new file mode 100644 index 0000000..d77d3e3 --- /dev/null +++ b/src_GOC/GOC_read_solutions.jl @@ -0,0 +1,303 @@ +function solution_point(instance_path::String) + solution1 = readdlm(joinpath(instance_path,"solution1.txt"), ',') + solution2 = readdlm(joinpath(instance_path,"solution2.txt"), ',') + basecase_generator_solution = read_solution1(solution1) + contingency_generator_solution, bus_solution, delta_solution, line_flow_solution = read_solution2(solution2) + power_data= getpowerdata(instance_path) + index = get_bus_index(power_data) + participation_factors = create_participation_factors_dict(instance_path, index) + global_point = create_global_point(solution1, solution2, participation_factors, index) + # Add delta solution + delta_point = create_delta_solution(solution2) + global_point = merge(global_point, delta_point) + return global_point +end + + +function create_voltagesolutionpoint(bus_solution, index) + point = Point() + for i in 1:size(bus_solution,1) + num_bus = index[bus_solution[i,2]] + value = bus_solution[i,3]*exp(im*bus_solution[i,4]*pi/180) + if bus_solution[i,1] == 0 + add_coord!(point, Variable(variable_name("VOLT", bus_name(num_bus), "",basecase_scenario_name()), Complex), value) + else + id_contingency = bus_solution[i,1] + scenario_name = scenarioname(id_contingency) + add_coord!(point, Variable(variable_name("VOLT", bus_name(num_bus), "", scenario_name), Complex), value) + end + end + return point +end + + +function gen_solution_basecase(basecase_generator_solution,index) + point = Point() + for i in 1:size(basecase_generator_solution,1) + num_bus = index[basecase_generator_solution[i,1]] + gen_id = basecase_generator_solution[i,2] + if typeof(gen_id)!=Int64 + gen_id = matchall(r"\d+" ,gen_id)[1] + else + gen_id = Int(gen_id) + end + Pg = basecase_generator_solution[i,3] + Qg = basecase_generator_solution[i,4] + add_coord!(point, Variable(variable_name("Sgen", bus_name(num_bus), generator_name(gen_id), basecase_scenario_name()), Complex), Pg + im*Qg) + end + return point +end + +function gen_solution_contingencies(basecase_generator_solution,contingency_generator_solution,delta_solution, participation_factors, index) + point = Point() + + ##reactive power + for i in 1:size(contingency_generator_solution,1) + id_contingency = contingency_generator_solution[i,1] + scenario_name = scenarioname(id_contingency) + gen_id = contingency_generator_solution[i,4] + num_bus = index[Int(contingency_generator_solution[i,3])] + if typeof(gen_id)!=Int64 + gen_id = matchall(r"\d+" ,gen_id)[1] + else + gen_id = Int(gen_id) + end + Qg = contingency_generator_solution[i,5] + add_coord!(point, Variable(variable_name("Sgen", bus_name(num_bus), generator_name(gen_id), scenario_name), Complex), im*Qg) + end + + + #active power + for l in 1:size(delta_solution,1) + id_contingency = delta_solution[l,1] + scenario_name = scenarioname(id_contingency) + for i in 1:size(basecase_generator_solution,1) + num_bus = index[Int(basecase_generator_solution[i,1])] + gen_id = basecase_generator_solution[i,2] + if typeof(gen_id)!=Int64 + gen_id = matchall(r"\d+" ,gen_id)[1] + else + gen_id = Int(gen_id) + end + Pg = basecase_generator_solution[i,3] + + var = Variable(variable_name("Sgen",bus_name(num_bus), generator_name(gen_id), scenario_name), Complex) + + add_coord!(point, var, Pg + delta_solution[l,2] * participation_factors[bus_name(num_bus)][generator_name(gen_id)]) + end + end + return point +end + + + +function create_participation_factors_dict(instance_path, index) + path_folder = instance_path + if !("generator.csv" ∈ readdir(path_folder)) + error("missing file : generator.csv must be in $path_folder") + end + generator_data = readdlm(joinpath(path_folder,"generator.csv"),',',skipstart=1) + + participation_factors = Dict{String,Dict{String,Float64}}() + for line in 1:size(generator_data,1) + term_id = generator_data[line,3] + if term_id == 9 + num_bus = index[Int(generator_data[line,1])] + gen_id = generator_data[line,2] + if typeof(gen_id)!=Float64 + gen_id = matchall(r"\d+" ,gen_id)[1] + else + gen_id = Int(gen_id) + end + value = generator_data[line,4] + if !haskey(participation_factors,bus_name(num_bus)) + participation_factors[bus_name(num_bus)] = Dict{String,Float64}() + end + participation_factors[bus_name(num_bus)][generator_name(gen_id)] = value + end + end + return participation_factors +end + +function create_delta_solution(solution2) + point = Point() + contingency_generator_solution, bus_solution, delta_solution, line_flow_solution = read_solution2(solution2) + for i in 1:size(delta_solution,1) + scenario = scenarioname(delta_solution[i,1]) + add_coord!(point, Variable(get_delta_varname(scenario),Real), delta_solution[i,2]) + end + return point +end + + +function create_global_point(solution1,solution2, participation_factors,index) + basecase_generator_solution = read_solution1(solution1) + contingency_generator_solution, bus_solution, delta_solution, line_flow_solution = read_solution2(solution2) + voltage_point = create_voltagesolutionpoint(bus_solution,index) + genpoint = gen_solution_basecase(basecase_generator_solution,index) + + genpoint_contingencies = gen_solution_contingencies(basecase_generator_solution,contingency_generator_solution,delta_solution, participation_factors,index) + + return merge(voltage_point, genpoint, genpoint_contingencies) +end + + +function nb_active_constraints_in_scenario_cc_Qgen(filenames, epsilon::Float64) + filenames_path = joinpath("instances","GOC", filenames[1], filenames[2]) + solution1 = readdlm(joinpath(filenames_path,"solution1.txt"), ',') + solution2 = readdlm(joinpath(filenames_path,"solution2.txt"), ',') + gen_solution_basecase = read_solution1(solution1) + ~, bus_solution, ~, ~ = read_solution2(solution2) + power_data = getpowerdata(filenames) + index = get_bus_index(power_data) + #bus_id = Int(generator_data[line,1]) + generators = collect(gen_solution_basecase[:,1]) + generators = [ index[gen] for gen in generators] + #println(length(generators)) + module_v_basecase = Dict{Int64, Float64}() + module_v_scenarios = Dict{String,Dict{Int64, Float64}}() + for i in 1:size(bus_solution,1) + num_bus = index[bus_solution[i,2]] + if bus_solution[i,1] == 0 + module_v_basecase[num_bus] = bus_solution[i,3] + else + id_contingency = bus_solution[i,1] + scenario_name = scenarioname(id_contingency) + if !haskey(module_v_scenarios, scenario_name) + module_v_scenarios[scenario_name] = Dict{Int64, Float64}() + end + module_v_scenarios[scenario_name][num_bus] = bus_solution[i,3] + end + end + nb = 0 + for (scenario, dict_modules) in module_v_scenarios + for num_bus in generators + if abs(dict_modules[num_bus] - module_v_basecase[num_bus]) > epsilon + nb += 1 + end + end + end + + return nb +end +# filenames = ["Phase_0_IEEE14_1Scenario", "scenario_1"] +# filenames = ["Phase_0_RTS96", "scenario_10"] +# nb_active_constraints_in_scenario_cc_Qgen(filenames, 1e-4) +# folder = "Phase_0_IEEE14" +# scenario = "scenario_10" +# epsilon = 1e-4 +# get_nb_cc_reactive_power_active(folder, scenario, epsilon) + +########################################## +#READ +######################################### + +function read_solution1(solution1::Array{Any,2}) + nb_lines = size(solution1,1) + #description_line = Array{Any}() + line_start = 0 + line_end = 0 + for l in 1:nb_lines + if solution1[l,1] == "--generation dispatch" + #description_line = solution1[l+1,1:4] + line_start = l + 2 + break + end + end + i = line_start + while solution1[i,1] != "--end of generation dispatch" + if i==nb_lines + error("no --end of generation dispatch in input solution1") + else + i +=1 + end + end + line_end = i-1 + basecase_generator_solution = solution1[line_start:line_end,1:4] + return basecase_generator_solution +end + +# +# basecase_generator_solution_description = ["bus id", "unit id", "pg(MW)", "qg(MVar)"] +# contingency_generator_solution_description = ["conID", "genID", "busID", "unitID", "q(MW)"] +# contingency_bus_solution_description = ["contingency id", "bus id", "v(pu)", "theta(deg)"] +# contingency_delta_solution_description = ["contingency id", "Delta(MW)"] +# contingency_line_flow_solution_description = ["contingency id", "line id", "origin bus id", "destination bus id", "circuit id", "p_origin(MW)", "q_origin(MVar)", "p_destination(MW)", "q_destination(MVar)"] +# + + + + +function read_solution2(solution2::Array{Any,2}) + nblines = size(solution2,1) + + i = 1 + while solution2[i,1] != "--contingency generator" + if i==nblines + error("no --contingency generator in input solution2") + else + i +=1 + end + end + #println(solution2[i+1,1:5]) + line_start_ctg, line_end_ctg = findnumarray(i+2, solution2) + + i = line_end_ctg + 1 + while solution2[i,1] != "--bus" + if i==nblines + error("no --bus in input solution2") + else + i +=1 + end + end + #println(solution2[i+1,1:4]) + line_start_bus, line_end_bus = findnumarray(i+2, solution2) + + i = line_end_bus + 1 + while solution2[i,1] != "--Delta" + if i==nblines + error("no --Delta in input solution2") + else + i +=1 + end + end + #println(solution2[i+1,1:2]) + line_start_delta, line_end_delta = findnumarray(i+2, solution2) + + i = line_end_delta + 1 + while solution2[i,1] != "--line flow" + if i==nblines + error("no --line flow in input solution2") + else + i +=1 + end + end + #println(solution2[i+1,1:9]) + line_start_line, line_end_line = findnumarray(i+2, solution2) + + if line_end_line + 1 == nblines + contingency_generator_solution = solution2[line_start_ctg:line_end_ctg,1:5] + bus_solution = solution2[line_start_bus:line_end_bus,1:4] + delta_solution = solution2[line_start_delta:line_end_delta,1:2] + line_flow_solution = solution2[line_start_line:line_end_line,1:9] + + return contingency_generator_solution, bus_solution, delta_solution, line_flow_solution + else + error() + end +end + +function findnumarray(line_start, data) + i = line_start + name = data[i-2,1][3:end] + nb_lines = size(data,1) + while data[i,1]!="--end of "*name + if i==nb_lines + error("no --end of $name in input solution2") + else + i +=1 + end + end + line_end = i-1 + line_start, line_end +end diff --git a/src_GOC/GOCcheck_feasibility.jl b/src_GOC/GOCcheck_feasibility.jl new file mode 100644 index 0000000..311be87 --- /dev/null +++ b/src_GOC/GOCcheck_feasibility.jl @@ -0,0 +1,355 @@ +""" + check_feasibility(elem::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point) where T<:AbstractNodeLabel + +Evaluate the constraints defined by `elemid` of type `elem` at `bus` for `scenario` (depending on `elem_formulation`) on `point` and return a dictionary of infeasible constraints Dict(ctrname => message with information) +Return nothing if there is not violated constraints. + +""" + + +###Genbounds constraints +function check_feasibility(element::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:GOCGenerator + if elem_formulation == :NewVar + lb = element.power_min + ub = element.power_max + cstrname = get_cstrname(scenario, bus, elemid, "Genbounds") + varname = variable_name("Sgen", bus, elemid, scenario) + value = point[Variable(varname,Complex)] + #println(lb, " <= ", value, " <= ", ub) + slack_left = value - lb + slack_right = ub - value + min_slack = min(real(slack_left),real(slack_right),imag(slack_left),imag(slack_right)) + if min_slack < -epsilon + #infeasibility + message = "Power generated not in diagram PQ." + if real(slack_left) < - epsilon + message = message*" Pgen < Pmin" + elseif imag(slack_left) < - epsilon + message = message*" Qgen < Qmin" + elseif real(slack_right) < - epsilon + message = message*" Pgen > Pmax" + elseif imag(slack_left) < - epsilon + message = message*" Qgen > Qmax" + end + return Dict(cstrname => message) + end + end +end + +# ##test +# scenario = "BaseCase" +# bus = "BUS_1" +# ds_bus = OPFpbs[scenario].ds.bus[bus] +# elemid = "Generator_1" +# elem = ds_bus[elemid] +# elem_formulation = OPFpbs[scenario].mp.node_formulations[bus][elemid] +# point = global_point +# point_infeasible = copy(global_point) +# point_infeasible[Variable(variable_name("Sgen", bus, elemid, scenario), Complex)] += 1000 +# +# check_feasibility(elem, bus, elemid, elem_formulation, scenario, point,1e-6) +# check_feasibility(elem, bus, elemid, elem_formulation, scenario, point_infeasible,1e-6) + +###VOLTM constraints +function check_feasibility(element::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:GOCVolt + cstrname = get_cstrname(scenario, bus, elemid, "VOLTM") + Vmin = element.voltage_magnitude_min + Vmax = element.voltage_magnitude_max + varname = variable_name("VOLT", bus, "", scenario) + value = point[Variable(varname,Complex)] + module_value = value * conj(value) + if abs(imag(module_value)) > 1e-15 + warn("Imaginary part of $(varname) module is not zero") + end + #println(Vmin^2, " <= ", module_value, " <= ", Vmax^2) + slack_left = module_value - Vmin^2 + slack_right = Vmax^2 - module_value + if real(slack_left) > - epsilon + if real(slack_right) > - epsilon + ##feasibility at epsilon OK + return + else + return Dict(cstrname => "Voltage magnitude above Vmax") + end + else + return Dict( cstrname => "Voltage magnitude under Vmin") + end +end + +##test +# scenario = "BaseCase" +# bus = "BUS_1" +# ds_bus = OPFpbs[scenario].ds.bus[bus] +# elemid = "VoltGOC" +# elem = ds_bus[elemid] +# elem_formulation = OPFpbs[scenario].mp.node_formulations[bus][elemid] +# point = global_point +# +# check_feasibility(elem, bus, elemid, elem_formulation, scenario, point, 1e-6) + + +# function check_feasibility(elem::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, point::Point) where T<:AbstractLinkLabel +# return +# end +# +""" + check_feasibility(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, point::Point, epsilon::Float64) where T<:AbstractLinkLabel + +Evaluate the constraints defined by `elemid` of type `elem` at `link` for `scenario` (depending on `elem_formulation`) on `point` and return a dictionary of infeasible constraints Dict(ctrname => message with information) +Return nothing if there is not violated constraints. + +""" + +##Smax constraints TODO: define for all types of abstract link ??? +function check_feasibility(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, point::Point, epsilon::Float64) where T<:GOCLineπ_notransformer + origid, destid = link.orig, link.dest + ctr_name_orig = get_cstrname(scenario, link, elemid, "Smaxorig") + ctr_name_dest = get_cstrname(scenario, link, elemid, "Smaxdest") + Sor = Sorig(element, link, elemid, elem_formulation, link_vars) + Sde = Sdest(element, link, elemid, elem_formulation, link_vars) + Smax = element.power_magnitude_max + slack_Sorig = Smax^2 - evaluate(abs2(Sor), point) + slack_Sdest = Smax^2 - evaluate(abs2(Sde), point) + if abs(imag(slack_Sorig)) > 1e-15 || abs(imag(slack_Sdest)) > 1e-15 + warn("Imaginary part of Sorig or Sdest module is not zero") + end + ctr_slacks = Dict(ctr_name_orig => real(slack_Sorig), ctr_name_dest => real(slack_Sdest)) + min_violation_ctr = minimum(ctr_slacks) + if min_violation_ctr[2] > - epsilon + return + else + if real(slack_Sorig) < - epsilon + if real(slack_Sdest) < - epsilon + return Dict(ctr_name_orig => "Power magnitude at origin above Smax", ctr_name_dest => "Power magnitude at destination") + else + return Dict(ctr_name_orig => "Power magnitude at origin is above Smax") + end + else + return Dict(ctr_name_dest => "Power magnitude at destination Smax") + end + end +end + +function check_feasibility(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, point::Point, epsilon::Float64) where T<:GOCLineπ_withtransformer + origid, destid = link.orig, link.dest + ctr_name_orig = get_cstrname(scenario, link, elemid, "Smaxorig") + ctr_name_dest = get_cstrname(scenario, link, elemid, "Smaxdest") + Sor = Sorig(element, link, elemid, elem_formulation, link_vars) + Sde = Sdest(element, link, elemid, elem_formulation, link_vars) + Smax = element.power_magnitude_max + slack_Sorig = Smax^2 - evaluate(abs2(Sor), point) + slack_Sdest = Smax^2 - evaluate(abs2(Sde), point) + if abs(imag(slack_Sorig)) > 1e-15 || abs(imag(slack_Sdest)) > 1e-15 + warn("Imaginary part of Sorig or Sdest module is not zero") + end + ctr_slacks = Dict(ctr_name_orig => real(slack_Sorig), ctr_name_dest => real(slack_Sdest)) + min_violation_ctr = minimum(ctr_slacks) + if min_violation_ctr[2] > - epsilon + return + else + if real(slack_Sorig) < - epsilon + if real(slack_Sdest) < - epsilon + return Dict(ctr_name_orig => "Power magnitude at origin above Smax", ctr_name_dest => "Power magnitude at destination") + else + return Dict(ctr_name_orig => "Power magnitude at origin is above Smax") + end + else + return Dict(ctr_name_dest => "Power magnitude at destination Smax") + end + end +end + +#test +# scenario = "BaseCase" +# link = collect(keys(OPFpbs[scenario].ds.link))[1] +# ds_link = OPFpbs[scenario].ds.link[link] +# elemid = linepi_notransformer_name("BL") +# elem = ds_link[elemid] +# link_vars = OPFpbs[scenario].mp.link_vars[link] +# elem_formulation = OPFpbs[scenario].mp.link_formulations[link][elemid] +# point = global_point +# check_feasibility(elem, link, elemid, elem_formulation, link_vars, scenario, point, 1e-6) + +""" + check_feasibility_cc_active_power(elem::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:AbstractNodeLabel + +Evaluate the coupling constraints about active power defined by `elemid` of type `elem` at `bus` for `scenario` (depending on `elem_formulation`) on `point` and return a dictionary of infeasible constraints Dict(ctrname => message with information) +Return nothing if the elem is not a generator or if there is not violated constraints. + +""" +function check_feasibility_cc_active_power(elem::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:AbstractNodeLabel + return +end + +function check_feasibility_cc_active_power(elem::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:GOCGenerator + if !is_basecase_scenario(scenario) + cc_name = cc_active_power(scenario, bus, elemid) + part_factor = elem.participation_factor + delta_name = delta_varname(scenario) + sgen_bc_varname = variable_name("Sgen", bus, elemid, basecase_scenario_name()) + sgen_scen_varname = variable_name("Sgen", bus, elemid, scenario) + delta = point[Variable(delta_name, Real)] + Sgen_basecase = point[Variable(sgen_bc_varname,Complex)] + Sgen_scenario = point[Variable(sgen_scen_varname,Complex)] + diff = real(Sgen_scenario - Sgen_basecase - delta*part_factor) + if abs(diff) < epsilon + #feasibility + return + else + return Dict(cc_name => "Active power generated in contingency not equal to the one in Basecase plus delta * participation factor") + end + else + #warn("No active power coupling constraints for BaseCase scenario") + return + end +end + +# scenario = "Scenario1" +# check_feasibility_cc_active_power(elem, bus, elemid, elem_formulation, scenario, point,1e-15) +""" + check_feasibility_cc_reactive_power(element::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:AbstractNodeLabel + +Evaluate the coupling constraints about reactive power defined by `elemid` of type `element` at `bus` for `scenario` (depending on `elem_formulation`) on `point` and return a dictionary of infeasible constraints Dict(ctrname => message with information) +Return nothing if the elem is not a generator or if there is not violated constraints. + +""" +function check_feasibility_cc_reactive_power(element::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:AbstractNodeLabel + return +end + +function check_feasibility_cc_reactive_power(element::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:GOCGenerator + if !is_basecase_scenario(scenario) + cc_name = cc_reactive_power(scenario, bus, elemid) + sgen_scen_varname = variable_name("Sgen", bus, elemid, scenario) + volt_bc_name = variable_name("VOLT", bus, "", basecase_scenario_name()) + volt_scen_name = variable_name("VOLT", bus, "", scenario) + Sgen_scenario = point[Variable(sgen_scen_varname,Complex)] + V_basecase = point[Variable(volt_bc_name,Complex)] + V_scenario = point[Variable(volt_scen_name,Complex)] + Qmin = imag(element.power_min) + Qmax = imag(element.power_max) + #println(abs(V_basecase)," ", abs(V_scenario)) + + if -epsilon <= abs(V_scenario) - abs(V_basecase) <= epsilon + ##Vscen = Vbc => no constraint + #println("Vscenario = Vbasecase") + return + elseif abs(V_scenario) - abs(V_basecase) < - epsilon + #Vscen < Vbc => Qscen = Qmax + println(bus, elemid, " Vscenario < Vbasecase") + slack = Qmax - imag(Sgen_scenario) + println(Qmax, imag(Sgen_scenario)) + println(slack) + if abs(slack) > epsilon + return Dict(cc_name => "Vscenario < Vbasecase, reactive power generated should be equal to Qmax") + end + + elseif abs(V_scenario) - abs(V_basecase) > epsilon + #Vscn > Vbc => Qscen = Qmin + println(bus, elemid, " Vscenario > Vbasecase") + slack = imag(Sgen_scenario) - Qmin + println(Qmin, imag(Sgen_scenario)) + println(slack) + if abs(slack) > epsilon + return Dict(cc_name => "Vscenario > Vbasecase, reactive power generated should be equal to Qmin") + end + end + + else + #warn("No reactive power coupling constraints for BaseCase scenario") + return + end +end + +# scenario = "Scenario1" +# bus = "BUS_1" +# ds_bus = OPFpbs[scenario].ds.bus[bus] +# elemid = "Generator_1" +# elem = ds_bus[elemid] +# elem_formulation = OPFpbs[scenario].mp.node_formulations[bus][elemid] +# point = global_point +# check_feasibility_cc_reactive_power(elem, bus, elemid, elem_formulation, scenario, point,1e-7) + + +## TODO:: +# function check_feasibility_power_balance(bus::String, ds::DataSource, gs::GridStructure, mp::MathematicalProgramming, scenario::String, point::Point, epsilon::Float64) +# +# bus_elems_formulations = mp.node_formulations[bus] +# bus_elems_var = mp.node_vars[bus] +# cstrnames, Snode, lb, ub = get_Snodal(busid, ds.bus[bus], bus_elems_formulations, bus_elems_var) +# S_balance = Snode + get_Slinks_in(busid, ds, gs, mp) + get_Slinks_out(busid, ds, gs, mp) +# +# +# end +""" + test_feasibility_GOC(filenames, epsilon::Float64) + +Check feasibility at `epsilon` for instance defined by `filenames` +Return a dictonary of violated constraints by scenario : dict( scenario => dict(ctrname => message)) + +""" + +function test_feasibility_GOC(instance_path, epsilon::Float64) + OPFpbs = load_OPFproblems(GOCInput, instance_path) + introduce_Sgenvariables!(OPFpbs) + pb_global = build_globalpb(OPFpbs) + global_point = solution_point(instance_path) + dict_scenario_infeasible_ctr = Dict{String, Dict{String,String}}() + for (scenario, OPFpb) in OPFpbs + dict_scenario_infeasible_ctr[scenario] = check_feasibility(scenario, OPFpb, global_point,epsilon) + end + return dict_scenario_infeasible_ctr +end + +""" + check_feasibility(scenario::String, OPFpb::Scenario, point::Point, epsilon::Float64) + +Checks feasibility at `epsilon` for each constraint generated for in the scenario `scenario` constructed from `OPFpb` evaluated at `point`. +Returns a dictionary of constraints violated Dict: ctrname => message of error +""" +function check_feasibility(scenario::String, OPFpb::Scenario, point::Point, epsilon::Float64) + ds = OPFpb.ds + mp = OPFpb.mp + + dict_infeasible_ctr = Dict{String, String}() + for (bus, elems) in ds.bus + bus_elems_formulations = mp.node_formulations[bus] + for (elemid, elem) in elems + elem_formulation = bus_elems_formulations[elemid] + dict2 = check_feasibility(elem, bus, elemid, elem_formulation, scenario, point, epsilon) + if typeof(dict2) != Void + merge!(dict_infeasible_ctr, dict2) + end + dict3 = check_feasibility_cc_active_power(elem, bus, elemid, elem_formulation, scenario, point, epsilon) + if typeof(dict3) != Void + merge!(dict_infeasible_ctr, dict3) + end + dict4 = check_feasibility_cc_reactive_power(elem, bus, elemid, elem_formulation, scenario, point, epsilon) + if typeof(dict4) != Void + merge!(dict_infeasible_ctr, dict4) + end + end + end + + for (link, elems) in ds.link + link_elems_formulations = mp.link_formulations[link] + link_vars = mp.link_vars[link] + for (elemid, elem) in elems + elem_formulation = link_elems_formulations[elemid] + dict2 = check_feasibility(elem, link, elemid, elem_formulation, link_vars, scenario, point, epsilon) + if typeof(dict2) != Void + merge!(dict_infeasible_ctr, dict2) + end + end + end + return dict_infeasible_ctr +end + + +function is_basecase_scenario(scenario::String) + if scenario == basecase_scenario_name() + return true + else + return false + end +end diff --git a/src_IIDM/IIDMGenerator.jl b/src_IIDM/IIDMGenerator.jl new file mode 100644 index 0000000..49ba740 --- /dev/null +++ b/src_IIDM/IIDMGenerator.jl @@ -0,0 +1,77 @@ +## Element type +struct IIDMGenerator <: AbstractNodeLabel + id::String + minP::Float64 # MW + maxP::Float64 # MW + targetV::Float64 # p.u. + volt_regulator_on::Bool + S_bounds::Array{Tuple{Complex, Complex}} # MW+im*MVar +end + +## 1. Variables creation +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: IIDMGenerator + bus_vars[elemid] = Variable(variable_name("Sgen", bus, elemid, scenario), Complex) + return +end + +# function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MyType +# return +# end + +## 2. Power balance +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: IIDMGenerator + cstrname = "UNIT" + return [cstrname, Polynomial(bus_vars[elemid]), 0, 0] +end + +# function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:MyType +# return Polynomial() +# end + +# function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:MyType +# return Polynomial() +# end + +## 3. Constraints creation +function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: IIDMGenerator + bounds = sort(element.S_bounds, by=x->real(x[1])) + length(bounds) == 2 || warn("$(length(bounds))x2 ≠ 4 points for power domain of $elemid, $busid") + + Sgen = bus_vars[elemid] + constraints = Dict{String, Constraint}() + + constraints["Cstr_$(busid)_$(elemid)_real"] = real(bounds[1][1]) << Sgen << real(bounds[2][1]) + + a, θ = bounds[1][2], angle(bounds[2][2] - bounds[1][2]) + p = real((Sgen-a)*exp(im*(π/2-θ))) + constraints["Cstr_$(busid)_$(elemid)_upper"] = 0 << p << Inf + + a, θ = bounds[1][1], angle(bounds[2][1] - bounds[1][1]) + p = real((Sgen-a)*exp(im*(-π/2-θ))) + constraints["Cstr_$(busid)_$(elemid)_lower"] = 0 << p << Inf + + return constraints +end + +# function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MyType +# return Dict{String, Constraint}() +# end + + +## 4. Generator cost +function cost(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_elems_var::Dict{String, Variable}, Snode::Polynomial, lb, ub) where T <: IIDMGenerator + Sgen = gencost = Polynomial() + if elem_formulation == :NbMinVar + _, S, lb_gen, ub_gen = Snodal(element, bus, elemid, elem_formulation, bus_elems_var) + abs((lb-lb_gen) - (ub-ub_gen)) ≤ 5e-5 || warn("Power missing at $bus... $(abs((lb-lb_gen) - (ub-ub_gen)))") + Sgen = add(Snode, -S - (ub-ub_gen)) + elseif elem_formulation == :NewVar + Sgen = Polynomial(bus_elems_var[elemid]) + else + warn("No cost contribution for gen $elemid, $bus") + end + + add!(Sgen, conj(Sgen)) + gencost = (Sgen)*0.5 + return gencost +end diff --git "a/src_IIDM/IIDMLine_\317\200.jl" "b/src_IIDM/IIDMLine_\317\200.jl" new file mode 100644 index 0000000..ce95461 --- /dev/null +++ "b/src_IIDM/IIDMLine_\317\200.jl" @@ -0,0 +1,79 @@ +## Element type +struct IIDMLine_π <: AbstractLinkLabel + resistance::Float64 # pu + reactance::Float64 # pu + susceptance_in::Complex # pu + susceptance_out::Complex # pu + basekV_orig::Float64 # kV + basekV_dest::Float64 # kV + permanent_limit::Float64 +end + + +## 1. Variables creation +# function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MyType +# return +# end + +function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: IIDMLine_π + orig, dest = link.orig, link.dest + link_vars["Volt_orig"] = Variable(variable_name("VOLT", orig, "", scenario), Complex) + link_vars["Volt_dest"] = Variable(variable_name("VOLT", dest, "", scenario), Complex) + return +end + + +## 2. Power balance +# function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MyType +# return ["", Polynomial(), 0, 0] +# end + +function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: IIDMLine_π + return link_vars["Volt_orig"] * conj(get_IorigMP(element, link_vars)) +end + +function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: IIDMLine_π + return link_vars["Volt_dest"] * conj(get_IdestMP(element, link_vars)) +end + + +## 3. Constraints creation +# function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MyType +# return [[], [], [], []] +# end + +# function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MyType +# return [[], [], [], []] +# end + + +## 4. Measures +# function add_measure(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T +# return Dict{String, Constraint}() +# end + +function add_measure(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: IIDMLine_π + measure_equations = Dict{String, Constraint}() + measure_equations["Sorig_$(link.orig)→$(link.dest)"] = Sorig(element, link, elemid, elem_formulation, link_vars) == element.S_orig + measure_equations["Sdest_$(link.orig)→$(link.dest)"] = Sdest(element, link, elemid, elem_formulation, link_vars) == element.S_dest + return measure_equations +end + + + +## Utils functions +function get_IorigMP(element::IIDMLine_π, link_variables::Dict{String, Variable}) + rs = element.resistance + xs = element.reactance + bc_in = element.susceptance_in + ys = 1/(rs+im*xs) + return (ys + bc_in)*link_variables["Volt_orig"] - ys*link_variables["Volt_dest"] +end + +function get_IdestMP(element::IIDMLine_π, link_variables::Dict{String, Variable}) + rs = element.resistance + xs = element.reactance + bc_out = element.susceptance_out + ys = 1/(rs+im*xs) + return -ys*link_variables["Volt_orig"] + (ys + bc_out)*link_variables["Volt_dest"] +end diff --git "a/src_IIDM/IIDMLine_\317\200Measure.jl" "b/src_IIDM/IIDMLine_\317\200Measure.jl" new file mode 100644 index 0000000..857e5e6 --- /dev/null +++ "b/src_IIDM/IIDMLine_\317\200Measure.jl" @@ -0,0 +1,5 @@ +## Element type +struct IIDMLine_πMeasure <: AbstractLinkLabel + S_orig::Complex128 # MW + im*MVar + S_dest::Complex128 # MW + im*MVar +end diff --git a/src_IIDM/IIDMLoad.jl b/src_IIDM/IIDMLoad.jl new file mode 100644 index 0000000..dfb5588 --- /dev/null +++ b/src_IIDM/IIDMLoad.jl @@ -0,0 +1,55 @@ +## Element types +struct IIDMLoad <: AbstractNodeLabel + id::String + load::Complex128 # MW + im*MVar +end + + +## 1. Variables creation +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: IIDMLoad + if elem_formulation == :NewVar + bus_vars[elemid] = Variable(variable_name("Sload", bus, elemid, scenario), Complex) + end + return +end + +# function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MyType +# return +# end + + +## 2. Power balance +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: IIDMLoad + cstrname = "LOAD" + if elem_formulation == :NbMinVar + Sload = element.load + return [cstrname, Polynomial(), -Sload, -Sload] + elseif elem_formulation == :NewVar + return [cstrname, Polynomial(bus_vars[elemid]), 0, 0] + end + return ["", Polynomial(), 0, 0] +end + +# function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:MyType +# return Polynomial() +# end + +# function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:MyType +# return Polynomial() +# end + + +## 3. Constraints creation +function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: IIDMLoad + if elem_formulation == :NewVar + cstrname = "Cstr_$(busid)_$(elemid)" + Sload = element.load + p = Polynomial(bus_vars[elemid]) + return Dict{String, Constraint}(cstrname=> p == Sload) + end + return Dict{String, Constraint}() +end + +# function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MyType +# return [[], [], [], []] +# end diff --git a/src_IIDM/IIDMVolt.jl b/src_IIDM/IIDMVolt.jl new file mode 100644 index 0000000..a6cc79e --- /dev/null +++ b/src_IIDM/IIDMVolt.jl @@ -0,0 +1,58 @@ +## Element type +struct IIDMVolt <: AbstractNodeLabel + busname::String + busid::Int + substationname::String + nomV::Float64 # kV + minV::Float64 # p.u. + maxV::Float64 # p.u. +end + +## 1. Variables creation +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: IIDMVolt + bus_vars[elemid] = Variable(variable_name("VOLT", bus, "", scenario), Complex) + return +end + +# function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MyType +# return +# end + + +## 2. Power balance +# function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MyType +# return ["", Polynomial(), 0, 0] +# end + +# function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:MyType +# return Polynomial() +# end + +# function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T<:MyType +# return Polynomial() +# end + + +## 3. Constraints creation +function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: IIDMVolt + cstrname = "VOLTM_$busid" + Vmin = element.minV + Vmax = element.maxV + nomV = element.nomV + p = abs2(bus_vars["Volt"]) + return Dict{String, Constraint}(cstrname=> Vmin^2 << p << Vmax^2) +end + +# function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MyType +# return [[], [], [], []] +# end + + +# ## 4. Measure accessor +# function add_measure(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: IIDMVolt +# return Dict("V_$(busid)" => abs2((1/element.nomV)*bus_vars["Volt"]) == element.V) +# end +# +# # function add_measure(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T +# # return Dict{String, Constraint}() +# # end diff --git a/src_IIDM/IIDMVoltMeasure.jl b/src_IIDM/IIDMVoltMeasure.jl new file mode 100644 index 0000000..b6966c5 --- /dev/null +++ b/src_IIDM/IIDMVoltMeasure.jl @@ -0,0 +1,5 @@ +## Element type +struct IIDMVoltMeasure <: AbstractNodeLabel + V::Complex # p.u. + S::Complex # MW + im*MVar +end diff --git a/src_IIDM/IIDM_files.jl b/src_IIDM/IIDM_files.jl new file mode 100644 index 0000000..c5cb466 --- /dev/null +++ b/src_IIDM/IIDM_files.jl @@ -0,0 +1,12 @@ +##file to include in ComplexModeler.jl which contains all files associated to GOC + +include("read_IIDM.jl") +include("read_IIDMeod.jl") + +include("IIDMGenerator.jl") +include("IIDMLine_π.jl") +include("IIDMLoad.jl") +include("IIDMVolt.jl") + +include("IIDMVoltMeasure.jl") +include("IIDMLine_πMeasure.jl") diff --git a/src_IIDM/read_IIDM.jl b/src_IIDM/read_IIDM.jl new file mode 100644 index 0000000..f286355 --- /dev/null +++ b/src_IIDM/read_IIDM.jl @@ -0,0 +1,98 @@ +type IIDMInput <: AbstractInput end + +## Read inputs +function read_input(intput_type::T, instance_path::String) where T<:Type{IIDMInput} + data = parse_file(instance_path) + + ## Cllecting bus information + bus = Dict{String, Dict{String, Any}}() + ind_bus = 1 + for substation in root(data)["substation"] + substationname = attribute(substation, "id") + busind = ind_bus + ind_bus += 1 + + voltagelvl = substation["voltageLevel"][1] + length(substation["voltageLevel"])==1 || warn("Substation $substationname has several voltageLevel, accounting for $(attribute(voltagelvl, "id")) only") + + busname = attribute(voltagelvl, "id") + length(voltagelvl["busBreakerTopology"][1]["bus"]) == 1 || warn("Substation has several buses, accounting for $busname only..") + basekV = parse(attribute(voltagelvl, "nominalV")) + minV = parse(attribute(voltagelvl, "lowVoltageLimit")) + maxV = parse(attribute(voltagelvl, "highVoltageLimit")) + + busBreakerTopology = voltagelvl["busBreakerTopology"][1]["bus"][1] + V, θ = parse(attribute(busBreakerTopology, "v")), parse(attribute(busBreakerTopology, "angle"))*π/180 + + bus[busname] = Dict{String, Any}() + bus[busname]["Volt"] = IIDMVolt(busname, busind, substationname, basekV, minV, maxV) + bus[busname]["MeasureVolt"] = IIDMVoltMeasure(V*exp(im*θ), NaN) + + for cur_load in voltagelvl["load"] + load_id = attribute(cur_load, "id") + load0 = parse(attribute(cur_load, "p0")) + im*parse(attribute(cur_load, "q0")) + load = parse(attribute(cur_load, "p")) + im*parse(attribute(cur_load, "q")) + + abs(load-load0) < 1e-5 || warn("$busname $load_id : load - load0 = $(load-load0)") + + bus[busname]["LOAD_$load_id"] = IIDMLoad(load_id, load) + end + + for cur_gen in voltagelvl["generator"] + gen_id = attribute(cur_gen, "id") + minP = parse(attribute(cur_gen, "minP")) + maxP = parse(attribute(cur_gen, "maxP")) + targetV = parse(attribute(cur_gen, "targetV")) + volt_regulator_on = parse(attribute(cur_gen, "voltageRegulatorOn")) + + attribute(cur_gen["property"][1], "value") == "FUEL" || warn("generator $gen_id of $busname not of expected type FUEL.") + capabilityCurve = cur_gen["reactiveCapabilityCurve"][1] + minP = parse(attribute(pt, "p")) + minQminP, maxQminP = parse(attribute(pt, "minQ")), parse(attribute(pt, "maxQ")) + maxP = parse(attribute(pt, "p")) + minQmaxP, maxQmaxP = parse(attribute(pt, "minQ")), parse(attribute(pt, "maxQ")) + S_bounds = [(minP+im*minQminP, minP+im*maxQminP), (maxP+im*minQmaxP, maxP+im*maxQmaxP)] + + bus[busname]["GEN_$gen_id"] = IIDMGenerator(gen_id, minP, maxP, targetV, volt_regulator_on, S_bounds) + end + end + + + ## Collecting link information + link = Dict{Link, Dict{String, Any}}() + for line in root(data)["line"] + orig = attribute(line, "voltageLevelId1") + dest = attribute(line, "voltageLevelId2") + linkname = Link(orig, dest) + lineid = attribute(line, "id") + r = parse(Float64, attribute(line, "r")) + x = parse(Float64, attribute(line, "x")) + G1 = parse(Float64, attribute(line, "g1")) + im*parse(Float64, attribute(line, "b1")) + G2 = parse(Float64, attribute(line, "g2")) + im*parse(Float64, attribute(line, "b2")) + S1 = parse(Float64, attribute(line, "p1")) + im*parse(Float64, attribute(line, "q1")) + S2 = parse(Float64, attribute(line, "p2")) + im*parse(Float64, attribute(line, "q2")) + basekV_orig = bus["$orig"]["Volt"].nomV + basekV_dest = bus["$dest"]["Volt"].nomV + lim = parse(Float64, attribute(line["currentLimits1"][1], "permanentLimit")) + + if !haskey(link, linkname) + link[linkname] = Dict{String, Any}() + end + link[linkname][lineid] = IIDMLine_π(r, x, G1, G2, basekV_orig, basekV_dest, lim) + link[linkname]["Measure_$lineid"] = IIDMLine_πMeasure(S1, S2) + end + + + ds = DataSource(bus, link) + + ## Building GridStructure + node_linksin = node_linksout = Dict{String, Set{Link}}() + node_vars = Dict{String, Dict{String, Variable}}() + link_vars = Dict{Link, Dict{String, Variable}}() + gs = GridStructure("BaseCase", node_linksin, node_linksout) + + node_formulations = Dict{String, Dict{Tuple{Type, String}, Symbol}}() + link_formulations = Dict{Link, Dict{Tuple{Type, String}, Symbol}}() + mp = MathematicalProgramming(node_formulations, link_formulations, node_vars, link_vars) + return OPFProblems("BaseCase"=>Scenario(ds, gs, mp)) +end diff --git a/src_IIDM/read_IIDMeod.jl b/src_IIDM/read_IIDMeod.jl new file mode 100644 index 0000000..3b79436 --- /dev/null +++ b/src_IIDM/read_IIDMeod.jl @@ -0,0 +1,100 @@ +type IIDMeodInput <: AbstractInput end + +################################################################################ +################################################################################ +## Untested + +## Read inputs +function read_input(intput_type::T, filenames::Array{String}) where T<:Type{IIDMeodInput} + data_branches = readdlm(filenames[1]) + data_buses = readdlm(filenames[2]) + data_generators = readdlm(filenames[3]) + data_limits = readdlm(filenames[4]) + data_loads = readdlm(filenames[5]) + data_substations = readdlm(filenames[6]) + + bus = Dict{String, Dict{String, Any}}() + + for i=1:size(data_buses, 1) + busid = Int(data_buses[i, 1]) + substationid = Int(data_buses[i, 2]) + V = data_buses[i, 4] # pu + θ = data_buses[i, 5] # radians + P, Q = data_buses[i, 6:7] # MW, MVar + basekV, minV, maxV = data_substations[substationid, 4:6] # kV, pu, pu + + busname = "BUS_$busid" + data_buses[i, 3] == 0 || warn("Buses - $busname : parameter cc!=0 ($(data_buses[i, 3])), not handled") + data_buses[i, 8] == 0 || warn("Buses - $busname : parameter fault!=0 ($(data_buses[i, 8])), not handled") + data_buses[i, 9] == 0 || warn("Buses - $busname : parameter curative!=0 ($(data_buses[i, 9])), not handled") + + bus[busname] = Dict{String, Any}() + bus_elems = bus[busname] + bus_elems["Volt"] = IIDMVolt(busname, busid, string(substationid), basekV, minV, maxV) + bus_elems["VoltMeasure"] = IIDMVoltMeasure(V*exp(im*θ), P+im*Q) + end + + for i=1:size(data_loads, 2) + busid = data_loads[i, 2] + P, Q = data_loads[i, 4:5] #MW, MVar + busname = "BUS_$busid" + + data_loads[i, 6] == 0 || warn("Loads - $busname : parameter fault!=0 ($(data_loads[i, 6])), not handled") + data_loads[i, 7] == 0 || warn("Loads - $busname : parameter curative!=0 ($(data_loads[i, 7])), not handled") + + bus_elems = bus["$busname"] + nb = count([ismatch(r"Load_", x) for x in keys(bus_elems)]) + bus_elems["Load_$(nb+1)"] = IIDMLoad(string(nb+1), P+im*Q) + end + + for i=1:size(data_generators, 1) + busid = data_generators[i, 2] + minP, maxP = data_generators[i, 5:6] # MW + volt_regulator_on, targetV = data_generators[i, 11:12] # bool, pu + minQmaxP, minQminP, maxQmaxP, maxQminP = data_generators[i, 7:10] # MVar + S_bounds = [(minP+im*minQminP, minP+im*maxQminP), (maxP+im*minQmaxP, maxP+im*maxQmaxP)] + busname = "BUS_$busid" + + data_generators[i, 2] == data_generators[i, 3] || warn("Generators - $busname : 'bus' is not 'con. bus', not handled") + data_generators[i, 15] == 0 || warn("Loads - $busname : parameter fault!=0 ($(data_generators[i, 15])), not handled") + data_generators[i, 16] == 0 || warn("Loads - $busname : parameter curative!=0 ($(data_generators[i, 16])), not handled") + + bus_elems = bus["$busname"] + nb = count([ismatch(r"Gen_", x) for x in keys(bus_elems)]) + bus_elems["Gen_$(nb+1)"] = IIDMGenerator(string(nb+1), minP, maxP, targetV, volt_regulator_on, S_bounds) + end + + + link = Dict{Link, Dict{String, Any}}() + for i=1:size(data_branches, 1) + busid1, busid2 = data_branches[i, 2:3] + r, x, g1, g2, b1, b2 = data_branches[i, 7:12] # pu + p1, p2, q1, q2 = data_branches[i, 16:19] # MW, MW, MVar, MVar + + + linkname = Link("BUS_$busid1", "BUS_$busid2") + if !haskey(link, linkname) + link[linkname] = Dict{String, Any}() + end + link_elems = link[linkname] + + basekV_dest = basekV_orig = lim = 0 + + nb = count([ismatch(r"Branch_", x) for x in keys(link_elems)]) + link_elems["IIDMLine_π_$(nb+1)"] = IIDMLine_π(r, x, g1+im*b1, g2+im*b2, basekV_orig, basekV_dest, lim) + link_elems["IIDMLine_πMeasure_$(nb+1)"] = IIDMLine_πMeasure(p1+im*q1, p2+im*q2) + end + + ds = DataSource(bus, link) + + ## Building GridStructure + node_linksin = node_linksout = Dict{String, Set{Link}}() + node_vars = Dict{String, Dict{String, Variable}}() + link_vars = Dict{Link, Dict{Type, Variable}}() + gs = GridStructure("BaseCase", node_linksin, node_linksout) + + node_formulations = Dict{String, Dict{Tuple{Type, String}, Symbol}}() + link_formulations = Dict{Link, Dict{Tuple{Type, String}, Symbol}}() + mp = MathematicalProgramming(node_formulations, link_formulations, node_vars, link_vars) + return OPFProblems("BaseCase"=>Scenario(ds, gs, mp)) +end diff --git a/src_Matpower/MatpowerGenerator.jl b/src_Matpower/MatpowerGenerator.jl new file mode 100644 index 0000000..e402e8d --- /dev/null +++ b/src_Matpower/MatpowerGenerator.jl @@ -0,0 +1,114 @@ +""" + mutable struct MatpowerGenerator <: AbstractNodeLabel + +Mutable structure descendant of AbstractNodeLabel + +# Fields +- `power_min::Complex128` +- `power_max::Complex128` +- `cost_degree::Int64` +- `cost_coeffs::Array{Float64}` +- `gen_status::Bool`: generator on/off + +""" +mutable struct MatpowerGenerator <: AbstractNodeLabel + power_min::Complex128 #Smin = Pmin + i Qmin + power_max::Complex128 #Smax = Pmax + i Qmax + cost_degree::Int64 # degree of the cost polynomial + cost_coeffs::Array{Float64} # cost polynomial coeffs from higher to lower degree + gen_status::Bool +end + + +""" + create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerGenerator + +Create power variables n for generator `elemid` at `bus` in `bus_vars` if `elem_formulation == :NewVar`\n +Return nothing + +""" +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerGenerator + if elem_formulation == :NewVar + bus_vars[elemid] = Variable(variable_name("Sgen", bus, elemid, scenario), Complex) + end + return +end + +""" +[cstrname, polynom, lb, ub] = Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MatpowerGenerator + +Return the polynomial contribution in power of generator `elemid` at `bus`(name, value, lower bound, upper bound). Will be used to construct power balance constraints in polynomial problem.\n +If `elem_formulation == :NbMinVar`, return generator bounds `["UNIT", Polynomial() ,Smin, Smax]`\n +If `elem_formulation == :NewVar`, return -Sgen `["UNIT", -Sgen, 0, 0]`\n +Return no contribution `["", Polynomial(), 0, 0]` otherwise +""" +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MatpowerGenerator + cstrname = "UNIT" + if elem_formulation == :NbMinVar + lb = element.power_min + ub = element.power_max + if !element.gen_status ## generator is off + lb = ub = 0 + end + return [cstrname, Polynomial(), lb, ub] + elseif elem_formulation == :NewVar + return [cstrname, Polynomial(-bus_vars[elemid]), 0, 0] + end + return ["", Polynomial(), 0, 0] # Raise error here ? Keep doing nothing ? One would expect a generator exists if this is called. +end + + +""" + constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MatpowerGenerator + +Return all the constraints defined by generator `elemid` at `bus`. Will be used to construct constraints in polynomial problem.\n +If `elem_formulation == :NewVar`, return generator bounds : "Genbounds" => Smin <= Sgen <= Smax\n +Return empty dictionary otherwise +""" +function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MatpowerGenerator + if elem_formulation == :NewVar + lb = element.power_min + ub = element.power_max + if !element.gen_status + ub = lb = 0 + end + cstrname = get_GenBounds_cstrname() + p = Polynomial(-bus_vars[elemid]) + return Dict{String, Constraint}(cstrname => lb << p << ub) + end + return Dict{String, Constraint}() +end + + +""" + cost(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_elems_vars::Dict{String, Variable}, Snode::Polynomial, lb, ub) where T <: MatpowerGenerator + +Return the polynomial contribution in objective generator `elemid` at `bus`. +""" +function cost(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_elems_var::Dict{String, Variable}, Snode::Polynomial, lb, ub) where T <: MatpowerGenerator + Sgen = Polynomial() + gencost = Polynomial() + if elem_formulation == :NbMinVar + _, S, lb_gen, ub_gen = Snodal(element, bus, elemid, elem_formulation, bus_elems_var) + abs((lb-lb_gen) - (ub-ub_gen)) ≤ 5e-5 || warn("Power missing at $bus... $(abs((lb-lb_gen) - (ub-ub_gen)))") + Sgen = add(Snode, -S - (ub-ub_gen)) + elseif elem_formulation == :NewVar + Sgen = Polynomial(bus_elems_var[elemid]) + else + warn("No cost contribution for gen $elemid, $bus") + end + + add!(Sgen, conj(Sgen)) + Sactive = (Sgen)*0.5 + Sexp = 1 + Polynomial() + d = element.cost_degree + degrees = element.cost_coeffs + for i=1:2 ## NOTE: NOT normal behaviour, required for linear cost... + add!(gencost, degrees[d-i+1] * Sexp) + Sexp = Sexp*Sactive + end + if !element.gen_status + gencost = 0 + end + return gencost +end diff --git "a/src_Matpower/MatpowerLine_\317\200.jl" "b/src_Matpower/MatpowerLine_\317\200.jl" new file mode 100644 index 0000000..057622b --- /dev/null +++ "b/src_Matpower/MatpowerLine_\317\200.jl" @@ -0,0 +1,115 @@ +""" + struct MatpowerLine_π <: AbstractLinkLabel + +Structure descendant of AbstractLinkLabel + +# Fields +- `baseMVA::Float64` +- `resistance::Float64`: coefficient rs +- `reactance::Float64`: coefficient xs +- `susceptance::Float64`: coefficient bc +- `transfo_ratio::Float64`: ratio τ +- `transfo_phase::Float64`: phase θ + +""" +struct MatpowerLine_π <: AbstractLinkLabel + baseMVA::Float64 + resistance::Float64 #rs + reactance::Float64 #xs + susceptance::Float64 #bc + transfo_ratio::Float64 #τ + transfo_phase::Float64 #θ +end + + +""" + create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerLine_π + +Create voltage variables at origin and destination of `link` in `link_vars`. \n +Return nothing. +""" +function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerLine_π + origid, destid = link.orig, link.dest + link_vars["Volt_orig"] = Variable(variable_name("VOLT", origid, "", scenario), Complex) + link_vars["Volt_dest"] = Variable(variable_name("VOLT", destid, "", scenario), Complex) + return +end + + +""" + Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MatpowerLine_π + +Return the polynomial power at the origin of the line `link` depending quadratically on the voltages : +```julia +τ = element.transfo_ratio +θ = element.transfo_phase *pi/180 +rs = element.resistance +xs = element.reactance +ys = 1/(rs+im*xs) +Yff = (ys+im*bc/2)/(τ^2) +Yft = -ys*1/(τ*exp(-im*θ)) +Sorig = Yff*link_variables["Volt_orig"] + Yft*link_variables["Volt_dest"] +``` +""" +function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MatpowerLine_π + return element.baseMVA * link_vars["Volt_orig"] * conj(get_IorigMP(element, link_vars)) +end + +""" + Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MatpowerLine_π + +Return the polynomial power at the destination of the line `link` depending quadratically on the voltages : +```julia +τ = element.transfo_ratio +θ = element.transfo_phase *pi/180 +rs = element.resistance +xs = element.reactance +ys = 1/(rs+im*xs) +Ytf = -ys*1/(τ*exp(im*θ)) +Ytt = ys+im*bc/2 +Sdest = Ytf*link_variables["Volt_orig"] + Ytt*link_variables["Volt_dest"] +``` +""" +function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T <: MatpowerLine_π + return element.baseMVA * link_vars["Volt_dest"] * conj(get_IdestMP(element, link_vars)) +end + + +## 3. Constraints creation +# function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MyType +# return Dict{String, Constraint}() +# end + + +## Utils functions +function get_IorigMP(element::MatpowerLine_π, link_variables::Dict{String, Variable}) + τ = element.transfo_ratio + θ = element.transfo_phase *pi/180 + if τ == 0 + τ = 1 + end + + rs = element.resistance + xs = element.reactance + bc = element.susceptance + ys = 1/(rs+im*xs) + Yff = (ys+im*bc/2)/(τ^2) + Yft = -ys*1/(τ*exp(-im*θ)) + return Yff*link_variables["Volt_orig"] + Yft*link_variables["Volt_dest"] +end + +function get_IdestMP(element::MatpowerLine_π, link_variables::Dict{String, Variable}) + τ = element.transfo_ratio + θ = element.transfo_phase *pi/180 + if τ == 0 + τ = 1 + end + + rs = element.resistance + xs = element.reactance + bc = element.susceptance + ys = 1/(rs+im*xs) + Ytf = -ys*1/(τ*exp(im*θ)) + Ytt = ys+im*bc/2 + return Ytf*link_variables["Volt_orig"] + Ytt*link_variables["Volt_dest"] +end diff --git a/src_Matpower/MatpowerLoad.jl b/src_Matpower/MatpowerLoad.jl new file mode 100644 index 0000000..9115c0d --- /dev/null +++ b/src_Matpower/MatpowerLoad.jl @@ -0,0 +1,63 @@ +""" + struct MatpowerLoad <: AbstractNodeLabel + +Structure descendant of AbstractNodeLabel + +# Fields +- `load::Complex128` +""" +struct MatpowerLoad <: AbstractNodeLabel + load::Complex128 #Sd = Pd+im*Qd +end + + +""" + create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerLoad + +Create load variable at `bus` for `elemid` in `bus_vars` if `elem_formulation == : NewVar`. \n +Return nothing. +""" +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerLoad + if elem_formulation == :NewVar + bus_vars[elemid] = Variable(variable_name("Sload", bus, elemid, scenario), Complex) + end + return +end + + +""" + [cstrname, polynom, lb, ub] = Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MatpowerLoad + +Return the polynomial contribution in power of load `elemid` at `busid`. Will be used to construct power balance constraints in polynomial problem.\n +If `elem_formulation == :NbMinVar`, return value of Sload `[cstrname, Sload, 0, 0]`\n +If `elem_formulation == :NewVar`, return variable Sload `[cstrname, Polynomial(Sload), 0, 0]`\n +Return `["", Polynomial(), 0, 0]` otherwise. +""" +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MatpowerLoad + cstrname = "LOAD" + if elem_formulation == :NbMinVar + Sload = element.load + return [cstrname, Sload, 0, 0] + elseif elem_formulation == :NewVar + return [cstrname, Polynomial(bus_vars[elemid]), 0, 0] + end + return ["", Polynomial(), 0, 0] +end + + +""" + constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MatpowerLoad + +Return a constraint to define load variable for `elemid` at `busid` if `elem_formulation == : NewVar`: `"LOAD"=> Sload==value` \n +Return empty dictionary of constraints otherwise. + +""" +function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MatpowerLoad + if elem_formulation == :NewVar + cstrname = get_LoadDef_cstrname() + Sload = element.load + p = bus_vars[elemid] + return Dict{String, Constraint}(cstrname => Polynomial(bus_vars[elemid]) == Sload) + end + return Dict{String, Constraint}() +end diff --git a/src_Matpower/MatpowerShunt.jl b/src_Matpower/MatpowerShunt.jl new file mode 100644 index 0000000..53e7167 --- /dev/null +++ b/src_Matpower/MatpowerShunt.jl @@ -0,0 +1,36 @@ +""" + struct MatpowerShunt <: AbstractNodeLabel + +Structure descendant of AbstractNodeLabel + +# Fields +- `Yshunt::Complex128`: Yshunt = Gs + im Bs +""" +struct MatpowerShunt <: AbstractNodeLabel + Yshunt::Complex128 # coeffS = Gs + im*Bs +end + + +## 1. Variables creation +# function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MyType +# return +# end + + +""" + [cstrname, polynom, lb, ub] = Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MatpowerShunt + +Return the polynomial power contribution for shunt `elemid` at `busid`. Will be used to construct power balance constraints in polynomial problem.\n +Return expression of Sshunt depending on the voltage `["SHUNT", conj(shunt_value)|V|^2, 0, 0]`\n + +""" +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MatpowerShunt + p = conj(element.Yshunt) * abs2(bus_vars[volt_name()]) + return ["SHUNT", p, 0, 0] +end + + +## 3. Constraints creation +# function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MyType + # return Dict{String, Constraint}() +# end diff --git a/src_Matpower/MatpowerVolt.jl b/src_Matpower/MatpowerVolt.jl new file mode 100644 index 0000000..f70ad15 --- /dev/null +++ b/src_Matpower/MatpowerVolt.jl @@ -0,0 +1,49 @@ +""" + struct MatpowerVolt <: AbstractNodeLabel + +Structure descendant of AbstractNodeLabel + +# Fields +- `busname::String` +- `busid::Int64` +- `voltage_magnitude_min::Float64`: Vmin +- `voltage_magnitude_max::Float64`: Vmax +""" +struct MatpowerVolt <: AbstractNodeLabel + busname::String + busid::Int64 + voltage_magnitude_min::Float64 #Vmin + voltage_magnitude_max::Float64 #Vmax +end + + +""" + create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerVolt + +Create voltage variable for `bus` in `bus_vars`.\n +Return nothing. +""" +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T <: MatpowerVolt + bus_vars[elemid] = Variable(variable_name("VOLT", bus, "", scenario), Complex) + return +end + + +## 2. Power balance +# function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T <: MyType +# return ["", Polynomial(), 0, 0] +# end + + +""" + constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MatpowerVolt + +Return the constraint of bounds on voltage magnitude at `busid`: "VOLTM" => Vmin^2 <= |V|^2 <= Vmax^2 + +""" +function constraint(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T <: MatpowerVolt + cstrname = get_VoltM_cstrname() + Vmin = element.voltage_magnitude_min + Vmax = element.voltage_magnitude_max + return Dict{String, Constraint}(cstrname => Vmin^2 << abs2(bus_vars[volt_name()]) < MatpowerVolt(busname, id, data[i,13], data[i,12])) + + ## Matpower Load + load = data[i,3] + im*data[i,4] + if load != 0 + bus[busname]["Load"] = MatpowerLoad(load) + end + + ## Matpower Shunt + shunt = data[i,5] + im*data[i,6] + if shunt != 0 + bus[busname]["Shunt"] = MatpowerShunt(shunt) + end + + bus_id_line[data[i,1]] = id + bus_id_name[data[i,1]] = busname + end + + ## Adding bus generator information + gen2bus = Dict{Int, Int}() + line2busgen = Dict{Int, Tuple{Int, Int}}() + + i_debut, i_fin = find_numarray(i_fin+1, data) + checkfor(data, i_debut-1, "mpc.gen") + + genind, prevgen = 0, data[i_debut, 1] + for i=i_debut:i_fin + gen2bus[i-i_debut+1] = bus_id_line[data[i,1]] + busname = bus_id_name[data[i,1]] + + if data[i, 1] == prevgen + genind += 1 + else + prevgen = data[i, 1] + genind = 1 + end + line2busgen[i-i_debut+1] = (data[i, 1], genind) + + gen_status = true + if data[i, 8] ≤ 0 #generator is off + gen_status = false + end + + S_min = data[i,10] + im*data[i,5] #Smin = Pmin + i Qmin + S_max = data[i,9] + im*data[i,4] #Smax = Pmax + i Qmax + + bus[busname]["Gen_$genind"] = MatpowerGenerator(S_min, S_max, -1, [-1], gen_status) + end + + ## building link information + i_debut, i_fin = find_numarray(i_fin+1, data) + checkfor(data, i_debut-1, "mpc.branch") + for i=i_debut:i_fin + linkname = Link(bus_id_name[data[i,1]], bus_id_name[data[i,2]]) + if data[i, 11] == 0 + warn("$link $(linkname.orig)⟶$(linkname.dest) breaker out of service !") + else + rs, xs, bc = data[i,3:5] + τ, θ = data[i, 9:10] + if !haskey(link, linkname) + link[linkname] = Dict{String, MatpowerLine_π}() + end + + nb_elem = length(link[linkname]) + link[linkname]["LinkMP_$(nb_elem+1)"] = MatpowerLine_π(baseMVA, rs, xs, bc, τ, θ) + end + end + + i_debut, i_fin = find_numarray(i_fin+1, data) + if data[i_debut-1, 1] == "mpc.areas" + warn("Not loading mpc.areas data.") + i_debut = i_fin+1 + i_debut, i_fin = find_numarray(i_fin+1, data) + end + + bus_line_id = Dict([val=>key for (key,val) in bus_id_line]) + + ## Adding generator cost information + checkfor(data, i_debut-1, "mpc.gencost") + genind, cur_genind = 0, data[i_debut, 1] + for i=i_debut:i_fin + buslineid, genid = line2busgen[i-i_debut+1] + busid = bus_id_line[buslineid] + + busname = "BUS_$busid" + + cost_degree = data[i, 4] + cost_coeffs = data[i, 5:(5+cost_degree-1)] + if haskey(bus[busname], "Gen_$genid") + bus[busname]["Gen_$genid"].cost_degree == -1 || warn("read_intput(): $busname, gen $genid already has a defined generator cost.") + bus[busname]["Gen_$genid"].cost_degree = cost_degree + bus[busname]["Gen_$genid"].cost_coeffs = cost_coeffs + else + println("Gen_$genid not found at $busname") + end + end + + ## Removing generators from PQ buses + for (busname, bus_elems) in bus + i = bus_elems["Volt"].busid + if bustype[i] == 1 + for (elemid, elem) in bus_elems + if typeof(elem) == MatpowerGenerator + delete!(bus_elems, elemid) + end + end + end + end + + ds = DataSource(bus, link) + + ## Building GridStructure + node_linksin = node_linksout = Dict{String, Set{Link}}() + node_vars = Dict{String, Dict{String, Variable}}() + link_vars = Dict{Link, Dict{String, Variable}}() + gs = GridStructure(basecase_scenario_name(), node_linksin, node_linksout) + + node_formulations = Dict{String, Dict{Tuple{Type, String}, Symbol}}() + link_formulations = Dict{Link, Dict{Tuple{Type, String}, Symbol}}() + mp = MathematicalProgramming(node_formulations, link_formulations, node_vars, link_vars) + return OPFProblems(basecase_scenario_name()=>Scenario(ds, gs, mp)) +end + + +## Utils functions +function find_numarray(i_start, data) + i_debut = i_start + while !isa(data[i_debut, 1], Int) + i_debut+=1 + end + i_fin=i_debut + while !isa(data[i_fin,1], SubString) + i_fin += 1 + end + i_debut, i_fin-1 +end + +checkfor(data, line_ind, name) = (data[line_ind, 1] == name) || error("Expected ", name, " at line ", line_ind, ", got ", data[line_ind,1], " instead.") + +function load_matpower(filename) + instance_name = split(filename, '.')[1] + + touch(instance_name*".temp") + f = open(filename) + out = open(instance_name*".temp", "w") + + # removing all ';' at end of lines + while !eof(f) + line = readline(f) + if length(line) > 0 && line[1] != '%' && line[1] != 'f' + s = split(line, ";") + println(out, s[1]) + end + end + close(f) + close(out) + + data = readdlm(instance_name*".temp") + rm(instance_name*".temp") + return data +end diff --git a/src_Matpower/read_matpower_simple.jl b/src_Matpower/read_matpower_simple.jl new file mode 100644 index 0000000..de593cb --- /dev/null +++ b/src_Matpower/read_matpower_simple.jl @@ -0,0 +1,154 @@ +type MatpowerSimpleInput <: AbstractInput end + + +""" + read_input(input_type::T, instance_path::String) where T<:Type{MatpowerSimpleInput} + +Read instance in `instance_path` depending on `input_type`.\n +Return a structure OPFProblems. +""" + +function read_input(intput_type::T, instance_path::String) where T<:Type{MatpowerSimpleInput} + data = load_matpower(instance_path) + + # DataStructure and Gridstructure data: + bus = Dict{String, Dict{String, Any}}() + link = Dict{Link, Dict{String, MatpowerLine_π}}() + + bus_id_line=Dict{Int, Int}() + bus_id_name=Dict{Int, String}() + + checkfor(data, 2, "mpc.baseMVA") + baseMVA = data[2,3] + + ## Building bus load and shunt information + i_debut, i_fin = find_numarray(1, data) + bustype = data[i_debut:i_fin, 2] # whether bus is 1:"PQ" (generators are not accounted for) or 2:"PV" + checkfor(data, i_debut-1, "mpc.bus") + for i=i_debut:i_fin + id = i-i_debut+1 + busname = bus_name(id) + ## Adding MatpowerVolt structure (for each bus) + bus[busname] = Dict(volt_name() => MatpowerVolt(busname, id, data[i,13], data[i,12])) + + ## Matpower Load + load = data[i,3] + im*data[i,4] + if load != 0 + bus[busname]["Load"] = MatpowerLoad(load) + end + + ## Matpower Shunt + shunt = data[i,5] + im*data[i,6] + if shunt != 0 + bus[busname]["Shunt"] = MatpowerShunt(shunt) + end + + bus_id_line[data[i,1]] = id + bus_id_name[data[i,1]] = busname + end + + ## Adding bus generator information + gen2bus = Dict{Int, Int}() + line2busgen = Dict{Int, Tuple{Int, Int}}() + + i_debut, i_fin = find_numarray(i_fin+1, data) + checkfor(data, i_debut-1, "mpc.gen") + + genind, prevgen = 0, 0 + for i=i_debut:i_fin + gen2bus[i-i_debut+1] = bus_id_line[data[i,1]] + busname = bus_id_name[data[i,1]] + + S_min = S_max = 0 + if data[i, 1] == prevgen + genind += 1 + S_min = bus[busname]["Gen"].power_min + S_max = bus[busname]["Gen"].power_max + else + prevgen = data[i, 1] + genind = 1 + end + line2busgen[i-i_debut+1] = (data[i, 1], genind) + + if data[i, 8] > 0 #generator is on + S_min += data[i,10] + im*data[i,5] #Smin = Pmin + i Qmin + S_max += data[i,9] + im*data[i,4] #Smax = Pmax + i Qmax + end + bus[busname]["Gen"] = MatpowerGenerator(S_min, S_max, -1, [-1]) + end + + ## building link information + i_debut, i_fin = find_numarray(i_fin+1, data) + checkfor(data, i_debut-1, "mpc.branch") + for i=i_debut:i_fin + linkname = Link(bus_id_name[data[i,1]], bus_id_name[data[i,2]]) + if data[i, 11] != 0 #Breaker is functional + rs, xs, bc = data[i,3:5] + τ, θ = data[i, 9:10] + if !haskey(link, linkname) + link[linkname] = Dict{String, MatpowerLine_π}() + end + + nb_elem = length(link[linkname]) + link[linkname]["LinkMP_$(nb_elem+1)"] = MatpowerLine_π(baseMVA, rs, xs, bc, τ, θ) + end + end + + i_debut, i_fin = find_numarray(i_fin+1, data) + if data[i_debut-1, 1] == "mpc.areas" + warn("Not loading mpc.areas data.") + i_debut = i_fin+1 + i_debut, i_fin = find_numarray(i_fin+1, data) + end + + bus_line_id = Dict([val=>key for (key,val) in bus_id_line]) + + ## Adding generator cost information + checkfor(data, i_debut-1, "mpc.gencost") + genind, cur_genind = 0, data[i_debut, 1] + for i=i_debut:i_fin + buslineid, _ = line2busgen[i-i_debut+1] + busid = bus_id_line[buslineid] + + busname = "BUS_$busid" + + cost_degree = data[i, 4] + cost_coeffs = data[i, 5:(5+cost_degree-1)] + if haskey(bus[busname], "Gen") + bus[busname]["Gen"].cost_degree = cost_degree + bus[busname]["Gen"].cost_coeffs = cost_coeffs + else + warn("Gen not found at $busname") + end + end + + ## Removing generators from PQ buses + for (busname, bus_elems) in bus + i = bus_elems["Volt"].busid + if bustype[i] == 1 + for (elemid, elem) in bus_elems + if typeof(elem) == MatpowerGenerator + delete!(bus_elems, elemid) + end + end + end + end + + ## Adding null generator to reference bus + refind = find(bustype .== 3) + length(refind) == 1 || warn("/! refind = $refind") + bus["BUS_$(refind[1])"]["Gen_reference"] = MatpowerGenerator(0,0,3,[0 0 0]) + + ds = DataSource(bus, link) + + ## Building GridStructure + node_linksin = node_linksout = Dict{String, Set{Link}}() + node_vars = Dict{String, Dict{String, Variable}}() + link_vars = Dict{Link, Dict{String, Variable}}() + gs = GridStructure(basecase_scenario_name(), node_linksin, node_linksout, node_vars, link_vars) + + node_formulations = Dict{String, Dict{Tuple{Type, String}, Symbol}}() + link_formulations = Dict{Link, Dict{Tuple{Type, String}, Symbol}}() + mc = ModellingChoice(node_formulations, link_formulations) + return OPFProblems(basecase_scenario_name()=>Scenario(ds, gs, mc)) +end diff --git a/src_PolynomialOptim/Modeler_accessors.jl b/src_PolynomialOptim/Modeler_accessors.jl new file mode 100644 index 0000000..9034470 --- /dev/null +++ b/src_PolynomialOptim/Modeler_accessors.jl @@ -0,0 +1,109 @@ +## objective +get_objective(pb::Problem) = pb.objective + +""" +get_objective(pb::Problem, pt::Point) + +Return the polynomial objective evaluated at `pt`. +""" +get_objective(pb::Problem, pt::Point) = evaluate(pb.objective, pt) + +set_objective!(pb::Problem, p::Polynomial) = pb.objective = p + + +## variables +get_variables(pb::Problem) = pb.variables + +function get_variabletype(pb::Problem, varName::String) + if !haskey(pb.variables, varName) + error("get_variable(): Current Problem has no variable named ", varName) + end + pb.variables[varName] +end + +has_variable(pb::Problem, var::Variable) = haskey(pb.variables, var.name) && pb.variables[var.name] == var.kind + +function add_variable!(pb::Problem, x::Variable) + if haskey(pb.variables, x.name) && !(x.kind <: pb.variables[x.name]) + error("add_variable!(): Attempting to add variable ", x.name, " (", x.kind, ") when ", x, " (", get_variabletype(pb, x.name), ") already exists.") + end + pb.variables[x.name] = x.kind +end + +function add_variable!(pb::Problem, x::Pair{String, Type}) + return add_variable!(pb, Variable(x[1], x[2])) +end + +## constraints +has_constraint(pb::Problem, cstrName::String) = haskey(pb.constraints, cstrName) + +get_constraints(pb::Problem) = pb.constraints + +""" + cstr = get_constraint(pb::Problem, cstrname::String) + +Return the `cstrname` constraint from `pb`. +""" +get_constraint(pb::Problem, cstrname::String) = pb.constraints[cstrname] + +""" + cstr = get_constraint(pb::Problem, pt::Point) + +Return the point (dict) of constraints names to their body's value when +evaluated at `pt`. +""" +function get_constraints(pb::Problem, pt::Point) + img = Point() + for (cstrName, cstr) in pb.constraints + img[Variable(cstrName, Complex)] = evaluate(cstr.p, pt) + end + return img +end + +""" + add_constraint!(pb::Problem, cstrName::String, cstr::Constraint) + +Add the constraint `cstr` under the `cstrname` name in the `pb` problem. +""" +function add_constraint!(pb::Problem, cstrName::String, cstr::Constraint) + if haskey(pb.constraints, cstrName) + warn("add_constraint!(): A constraint with that name already exists ($cstrName)") + end + if real(cstr.ub) < real(cstr.lb) || imag(cstr.ub) < imag(cstr.lb) + warn("add_constraint!(): ", cstrName, " Lower bound is higher than upper bound ($(cstr.lb) - $(cstr.ub))") + end + pb.constraints[cstrName] = cstr +end + +rm_constraint!(pb::Problem, cstrName::String) = pop!(pb.constraints, cstrName) + +""" + pt = get_slacks(pb::Problem, pt::Point) + +Return a point associating a constraint name to its slack, i.e. the minimum +algebraic distance between the real and imaginary parts of the body's value at +`pt` and its bounds. +""" +function get_slacks(pb::Problem, pt::Point) + var_arr = Variable[] + val_arr = Complex[] + for (cstrName, cstr) in pb.constraints + val = evaluate(cstr.p, pt) + push!(var_arr, Variable(cstrName, Complex)) + push!(val_arr, min(real(val-cstr.lb), real(cstr.ub-val)) + min(imag(val-cstr.lb), imag(cstr.ub-val))*im) + end + return Point(var_arr, val_arr) +end + +function get_minslack(pb::Problem, pt::Point) + minSlack = +Inf + minCstrName = "" + slacks = get_slacks(pb, pt) + for (cstrName, slack) in slacks + if minSlack > min(real(slack), imag(slack)) + minSlack = min(real(slack), imag(slack)) + minCstrName = cstrName + end + end + return minSlack, minCstrName +end diff --git a/src_PolynomialOptim/Modeler_constructors.jl b/src_PolynomialOptim/Modeler_constructors.jl new file mode 100644 index 0000000..16af114 --- /dev/null +++ b/src_PolynomialOptim/Modeler_constructors.jl @@ -0,0 +1,61 @@ +# Constraint +function <<(p::T, bound::Number) where T<: AbstractPolynomial + return Constraint(p, -Inf-im*Inf, bound) +end + +function <<(bound::Number, p::T) where T<: AbstractPolynomial + return Constraint(p, bound, Inf+im*Inf) +end + +function >>(p::T, bound::Number) where T<:AbstractPolynomial + return bound << p +end + +function >>(bound::Number, p::T) where T<:AbstractPolynomial + return p << bound +end + +function <<(bound::Number, cstr::Constraint) + if real(bound) > real(cstr.ub) || imag(bound) > imag(cstr.ub) + warn("<<(bnd, cstr): Creating a constraint with lower bound ", bound, " and upper bound ", cstr.ub) + end + return Constraint(cstr.p, bound, cstr.ub) +end +>>(bound::Number, cstr::Constraint) = cstr << bound + +function <<(cstr::Constraint, bound::Number) + if real(cstr.lb) > real(bound) || imag(cstr.lb) > imag(bound) + warn("<<(cstr, bnd): Creating a constraint with lower bound ", cstr.lb, " and upper bound ", bound) + end + return Constraint(cstr.p, cstr.lb, bound) +end +>>(cstr::Constraint, bound::Number) = bound << cstr + + +function ==(p::T, x::Number) where T<:AbstractPolynomial + return Constraint(p, x, x) +end + +function Base.print(io::IO, cstr::Constraint) + if cstr.lb == cstr.ub + print(io, cstr.p, " = ", cstr.ub) + else + print(io, cstr.lb, " < ", cstr.p, " < ", cstr.ub) + end +end + +# Problem +Problem() = Problem(Polynomial(), Dict{String, Constraint}(), Dict{String, Type}()) + +function Base.print(io::IO, pb::Problem) + print(io, "▶ variables: ") + for (varName, typ) in sort(collect(pb.variables), by=x->x[1]) + print(io, Variable(varName, typ), " ") + end + println(io, "\n▶ objective: ", pb.objective) + println(io, "▶ constraints: ") + for (cstrName, cstr) in sort(collect(pb.constraints), by=x->x[1]) + @printf(io, "%10s: ", cstrName) + println(io, cstr) + end +end diff --git a/src_PolynomialOptim/Modeler_cplx2real.jl b/src_PolynomialOptim/Modeler_cplx2real.jl new file mode 100644 index 0000000..974be35 --- /dev/null +++ b/src_PolynomialOptim/Modeler_cplx2real.jl @@ -0,0 +1,37 @@ +""" + pb = pb_cplx2real(pb_C::Problem) + +Convert a complex Polynomial Optimization Problem into a real polynomial +problem in real variables. +""" +function pb_cplx2real(pb_C::Problem) + pb = Problem() + for (varName, varType) in get_variables(pb_C) + if varType <: Complex + varName_real, varName_imag = varname_cplx2real(varName) + add_variable!(pb, Variable(varName_real, Real)) + add_variable!(pb, Variable(varName_imag, Real)) + else + add_variable!(pb, Variable(varName, varType)) + end + end + + realPart, imagPart = cplx2real(pb_C.objective) + set_objective!(pb, realPart) + + for (cstrName, cstr) in get_constraints(pb_C) + realPart, imagPart = cplx2real(cstr.p) + cstrName_real, cstrName_imag = varname_cplx2real(cstrName) + if length(realPart) != 0 + cstrreal = real(cstr.lb) << realPart << real(cstr.ub) + cstr.precond != :none && (cstrreal.precond = cstr.precond) + add_constraint!(pb, cstrName_real, cstrreal) + end + if length(imagPart) != 0 + cstrimag = imag(cstr.lb) << imagPart << imag(cstr.ub) + cstr.precond != :none && (cstrimag.precond = cstr.precond) + add_constraint!(pb, cstrName_imag, cstrimag) + end + end + return pb +end diff --git a/src_PolynomialOptim/Poly_Cplx2Real.jl b/src_PolynomialOptim/Poly_Cplx2Real.jl new file mode 100644 index 0000000..0ff291b --- /dev/null +++ b/src_PolynomialOptim/Poly_Cplx2Real.jl @@ -0,0 +1,115 @@ +# Conversion of all complex variables to real ones in the Poly structures + +""" + realPart, imagPart = cplx2real(expo::Exponent) + + Convert a complex Exponent in complex variables into `realPart` and + `imagPart` polynomials of twice as many variables, real and imag parts of + `expo` variables. Done recursively with the `cplx2real_rec` function. +""" +function cplx2real(expo::Exponent) + vars, inds = collect(keys(expo.expo)), collect(values(expo.expo)) + return cplx2real_rec(vars, inds, Polynomial()+1, Polynomial()+0, length(expo)+1, Degree(0,0)) +end + +""" +realPart, imagPart = cplx2real_rec(vars::Array{Variable}, degs::Array{Degree}, realPart::Polynomial, imagPart::Polynomial, cur_ind::Int, cur_deg::Degree) + +Transform recursively the complex exponent represented by the `vars` and `degs` +arrays into its real and imag parts, functions of its imag and real part +variables. +`cur_ind` decreases to 0, `cur_deg` decreases to Degree(0,0) for each step of +cur_ind. Terminaison case is reached at 0, Degree(0,0). +Initial arrays `vars` and `degs` are read only. + +### Arguments +- vars::Array{Variable} +- degs::Array{Degree} +- realPart::Polynomial +- imagPart::Polynomial +- cur_ind::Int +- cur_deg::Degree +""" +function cplx2real_rec(vars::Array{Variable}, degs::Array{Degree}, realPart::Polynomial, imagPart::Polynomial, cur_ind::Int, cur_deg::Degree) + ## Final case: + if cur_ind == 1 && cur_deg == Degree(0,0) + return (realPart, imagPart) + ## One less variable to deal with: + elseif cur_deg == Degree(0,0) + return cplx2real_rec(vars, degs, realPart, imagPart, cur_ind-1, degs[cur_ind-1]) + ## Recursion rule, decrease the current variable exponent until it reaches Degree(0,0): + else + var = vars[cur_ind] + if iscomplex(var) + var_real, var_imag = varname_cplx2real(var.name) + var_R, var_I = Variable(var_real, Real), Variable(var_imag, Real) + if cur_deg.explvar > 0 + return cplx2real_rec(vars, degs, var_R * realPart - var_I * imagPart, var_R * imagPart + var_I * realPart, cur_ind, Degree(cur_deg.explvar-1, cur_deg.conjvar)) + elseif cur_deg.conjvar > 0 + cur_deg.explvar == 0 || warn("cur_deg.explvar should be 0 (and not $(cur_deg.explvar)), set to this value") + return cplx2real_rec(vars, degs, var_R * realPart + var_I * imagPart, var_R * imagPart - var_I * realPart, cur_ind, Degree(0, cur_deg.conjvar-1)) + end + elseif isbool(var) + return cplx2real_rec(vars, degs, var*realPart, var*imagPart, cur_ind, Degree(0,0)) + else + return cplx2real_rec(vars, degs, Exponent(Dict(var=>cur_deg))*realPart, Exponent(Dict(var=>cur_deg))*imagPart, cur_ind, Degree(0,0)) + end + end +end + +""" + realPart, imagPart = cplx2real(pol::Polynomial) + + Convert a complex polynomial in complex variables into `realPart` and + `imagPart` polynomials of twice as many variables, real and imag parts of + `pol` variables. +""" +function cplx2real(pol::Polynomial) + realPart = Polynomial() + imagPart = Polynomial() + + for (expo, λ) in pol + realexpo, imagexpo = cplx2real(expo) + + realPart += realexpo*real(λ) - imagexpo*imag(λ) + imagPart += imagexpo*real(λ) + realexpo*imag(λ) + end + return (realPart, imagPart) +end + + +function cplx2real(pt_C::Point) + pt = Point() + for (var, val) in pt_C + if var.kind <: Complex + var_real, var_imag = varname_cplx2real(var.name) + if real(val) != 0 + pt[Variable(var_real, Real)] = real(val) + end + if imag(val) != 0 + pt[Variable(var_imag, Real)] = imag(val) + end + else + if val != 0 + pt[var] = real(val) + end + end + end + return pt +end + + +function real2cplx(pt::Point) + ptC = Point() + for (var, val) in pt + if ismatch(r"(_Re|_Im)", var.name) + if ismatch(r"_Re", var.name) + varname = var.name[1:(end-3)] + ptC[Variable(varname, Complex)] = val + im*pt[Variable(varname*"_Im", Real)] + end + else + ptC[var] = val + end + end + return ptC +end diff --git a/src_PolynomialOptim/Poly_operators.jl b/src_PolynomialOptim/Poly_operators.jl new file mode 100644 index 0000000..a690361 --- /dev/null +++ b/src_PolynomialOptim/Poly_operators.jl @@ -0,0 +1,151 @@ +################################## +## Internal operations +################################## + +""" + add_to_dict!(dict::Dict{Any, V}, key, val::V) where V<:Number + + *Sparsely* add `val` to the `key` entry of `dict` dictionnary. That is creates + the entry if needed, deletes it if the resulting value is null. +""" +function add_to_dict!(dict::Dict{U, Number}, key::U, val::T) where T<:Number where U + if !haskey(dict, key) + dict[key] = 0 + end + dict[key] += val + if dict[key] == 0 + delete!(dict, key) + end +end + +function setindex!(pt::Point, val::Number, var::Variable) + pt.coords[var] = val +end + +## Empty constructors +Point() = Point(Dict{Variable, Number}()) +Exponent() = Exponent(Dict{Variable, Degree}()) +Polynomial() = Polynomial(Dict{Exponent, Number}()) + +Exponent(x::Variable) = Exponent(Dict{Variable, Degree}(x=>Degree(1,0))) +function Point(vars::Array{Variable}, vals::Array{<:Number}) + if length(vars) != length(vals) + error("Point(): input arrays must have same size.") + end + + pt = Point() + for i=1:length(vars) + var, val = vars[i], vals[i] + if isreal(var) val = real(val) end + if isbool(var) && val != 0 + val = Int((val/abs(val)+1)/2) + end + add_coord!(pt, vars[i], val) + end + return pt +end + +## copy methods +function copy(p::Polynomial) + pdict = copy(p.poly) + return Polynomial(pdict) +end + +## convert functions +function convert(::Type{Polynomial}, expo::Exponent) + return Polynomial(Dict{Exponent, Number}(expo=>1.0)) +end + +function convert(::Type{Polynomial}, x::Variable) + return Polynomial(Dict{Exponent, Number}(Exponent(Dict(x=>Degree(1,0)))=>1.0)) +end + +function convert(::Type{Polynomial}, λ::Number) + return Polynomial(Dict{Exponent, Number}(Exponent()=>λ)) +end + +function convert(::Type{Point}, pt::Dict{Variable, T}) where T + return Point(convert(Dict{Variable, Number}, pt)) +end + +## +isreal(x::Variable) = x.kind <: Real +isbool(x::Variable) = x.kind <: Bool +iscomplex(x::Variable) = x.kind <: Complex + + +function conj(d::Degree) + return Degree(d.conjvar, d.explvar) +end + +function conj(x::Variable) + if x.kind<:Complex + return Polynomial(Dict{Exponent, Number}(Exponent(Dict(x=>Degree(0,1)))=>1.0)) + else + return Polynomial(Dict{Exponent, Number}(Exponent(Dict(x=>Degree(1,0)))=>1.0)) + end +end + +function conj(expo::Exponent) + expodict = Dict{Variable, Degree}() + for (var, deg) in expo.expo + expodict[var] = conj(deg) + end + return Exponent(expodict) +end + +function conj(p::Polynomial) + pdict = Dict{Exponent, Number}() + for (expo, λ) in p + pdict[conj(expo)] = conj(λ) + end + return Polynomial(pdict) +end + + +## real, imag +real(p::Polynomial) = (p + conj(p))/2 +imag(p::Polynomial) = (p - conj(p))/(2im) + +function real(p::T) where T<:AbstractPolynomial + return (p + conj(p))/2 +end + +function imag(p::T) where T<:AbstractPolynomial + return (p + -conj(p))/(2im) +end + +function abs2(p::T) where T<:AbstractPolynomial + return p*conj(p) +end + + + +## Evaluate +function evaluate(p::Polynomial, pt::Point) + res=0 + for (expo, λ) in p + res += λ*evaluate(expo, pt) + end + return res +end + +function evaluate(expo::Exponent, pt::Point) + res=1 + for (var, deg) in expo.expo + if haskey(pt, var) + res *= (evaluate(var, pt)^deg.explvar) * (conj(evaluate(var, pt))^deg.conjvar) + end + end + return res +end + +function evaluate(x::Variable, pt::Point) + if !haskey(pt, x) + return 0 + elseif x.kind<:Complex + return pt[x] + else + return real(pt[x]) + end +end diff --git a/src_PolynomialOptim/Poly_order.jl b/src_PolynomialOptim/Poly_order.jl new file mode 100644 index 0000000..fa04bea --- /dev/null +++ b/src_PolynomialOptim/Poly_order.jl @@ -0,0 +1,106 @@ +############################# +## Degree +############################# +==(d1::Degree, d2::Degree) = (d1.explvar==d2.explvar) && (d1.conjvar==d2.conjvar) +!=(d1::Degree, d2::Degree) = !(d1 == d2) + +hash(deg::Degree, h::UInt) = hash(deg.explvar, hash(deg.conjvar, h)) + +function isless(deg1::Degree, deg2::Degree) + return (deg1.explvar exp2[var] + return true + elseif exp1[var] == exp2[var] + continue + else + return false + end + end + else + return false + end + return false +end + +############################# +## Polynomial +############################# +function ==(p1::Polynomial, p2::Polynomial) + if (length(p1) != length(p2)) || (p1.degree != p2.degree) + return false + end + for (exponent, coeff) in p1 + if !haskey(p2, exponent) || (coeff != p2.poly[exponent]) + return false + end + end + true +end +!=(pol1::Polynomial, pol2::Polynomial) = !(pol1 == pol2) + +# NOTE: order on polynomials ? + + + +############################# +## Point +############################# +function ==(pt1::Point, pt2::Point) + if length(pt1) != length(pt2) + return false + end + for (var1, val1) in pt1 + if !haskey(pt2, var1) || pt2[var1] != val1 + return false + end + end + true +end +!=(pt1::Point, pt2::Point) = !(pt1 == pt2) diff --git a/src_PolynomialOptim/Poly_point_algebra.jl b/src_PolynomialOptim/Poly_point_algebra.jl new file mode 100644 index 0000000..60f0030 --- /dev/null +++ b/src_PolynomialOptim/Poly_point_algebra.jl @@ -0,0 +1,100 @@ +""" + add_coord!(pt::Point, var::Variable, val::Number) + + Sparsely add `val` to the `var` coordinate of `pt`. +""" +function add_coord!(pt::Point, var::Variable, val::Number) + return add_to_dict!(pt.coords, var, val) +end + +## Overloads for transparent iteration on `coords` attribute +start(pt::Point) = start(pt.coords) +next(pt::Point, state) = next(pt.coords, state) +done(pt::Point, state) = done(pt.coords, state) +length(pt::Point) = length(pt.coords) +haskey(pt::Point, key) = haskey(pt.coords, key) +keys(pt::Point) = keys(pt.coords) +values(pt::Point) = values(pt.coords) +getindex(pt::Point, var::Variable) = pt.coords[var] +function setindex!(pt::Point, var::Variable, val::Number) + if val != 0 + setindex!(pt.coords, var, val) + end + return +end + + +function add!(pt1::Point, pt2::Point) + for (var, val) in pt2.coords + add_coord!(pt1, var, val) + end +end + +function add(pt1::Point, pt2::Point) + pt_coord = copy(pt1.coords) + pt = Point(pt_coord) + add!(pt, pt2) + return pt +end + ++(pt1::Point, pt2::Point) = add(pt1, pt2) +-(pt1::Point, pt2::Point) = pt1 + (-1)*pt2 + +function merge(x::Point ...) + pt_res = Point() + for pt in x + add!(pt_res, pt) + end + return pt_res +end + +function *(pt1::Point, λ::Number) + pt = Point() + if λ == 0 + return pt + end + for (var, val) in pt1 + pt[var] = λ*val + end + return pt +end +*(λ::Number, pt1::Point) = pt1*λ + + +function norm(pt::Point, p::Real = 2) + p > 0 || error("norm(): p should be positive ($p ≤ 0).") + if p == Inf + maxi = -Inf + for (var, val) in pt + # iscomplex(var) && warn("norm(): var $(var) is not real, splitting real and imag parts.") + if !iscomplex(var) && (val > maxi) + maxi = val + elseif iscomplex(var) && max(real(val), imag(val)) > maxi + maxi = max(real(val), imag(val)) + end + end + return maxi + elseif p == 1 + s = 0 + for (var, val) in pt + if !iscomplex(var) + s += abs(val) + else + # warn("norm(): var $(var) is not real, splitting real and imag parts.") + s += abs(real(val)) + abs(imag(val)) + end + end + return s + else + s = 0 + for (var, val) in pt + if !iscomplex(var) + s += val^p + else + # warn("norm(): var $(var) is not real, splitting real and imag parts.") + s += real(val)^p + imag(val)^p + end + end + return s^(1/p) + end +end diff --git a/src_PolynomialOptim/Poly_poly_algebra.jl b/src_PolynomialOptim/Poly_poly_algebra.jl new file mode 100644 index 0000000..98e1cf4 --- /dev/null +++ b/src_PolynomialOptim/Poly_poly_algebra.jl @@ -0,0 +1,159 @@ +## Polynomial iterator +start(poly::Polynomial) = start(poly.poly) +next(poly::Polynomial, state) = next(poly.poly, state) +done(poly::Polynomial, state) = done(poly.poly, state) +length(poly::Polynomial) = length(poly.poly) +haskey(poly::Polynomial, key) = haskey(poly.poly, key) +keys(poly::Polynomial) = keys(poly.poly) +values(poly::Polynomial) = values(poly.poly) +getindex(poly::Polynomial, expo::Exponent) = poly.poly[expo] + +## Exponent iterator +start(expo::Exponent) = start(expo.expo) +next(expo::Exponent, state) = next(expo.expo, state) +done(expo::Exponent, state) = done(expo.expo, state) +length(expo::Exponent) = length(expo.expo) +haskey(expo::Exponent, key) = haskey(expo.expo, key) +keys(expo::Exponent) = keys(expo.expo) +values(expo::Exponent) = values(expo.expo) +getindex(expo::Exponent, var::Variable) = expo.expo[var] + +################################## +## Addition +################################## +function add!(p::Polynomial, p1::T) where T<:AbstractPolynomial + return add!(p, convert(Polynomial, p1)) +end + +function add!(p::Polynomial, p1::Polynomial) + for (cur_expo, λ) in p1 + λ != 0 || continue + add_to_dict!(p.poly, cur_expo, λ) + end + p.degree.explvar = max(p.degree.explvar, p1.degree.explvar) + p.degree.conjvar = max(p.degree.conjvar, p1.degree.conjvar) +end + +function add(p1::Polynomial, p2::Polynomial) + p = copy(p1) + add!(p, p2) + return p +end + +function +(p1::T, p2::U) where T<:AbstractPolynomial where U<:AbstractPolynomial + return add(convert(Polynomial, p1), convert(Polynomial, p2)) +end +function +(p1::Number, p2::T) where T<:AbstractPolynomial + return add(convert(Polynomial, p1), convert(Polynomial, p2)) +end +function +(p1::T, p2::Number) where T<:AbstractPolynomial + return add(convert(Polynomial, p1), convert(Polynomial, p2)) +end + +################################## +## Product +################################## +function product(p1::Polynomial, p2::Polynomial) + p = Polynomial() + for (expo1, λ1) in p1 + λ1 != 0 || continue + for (expo2, λ2) in p2 + λ2 != 0 || continue + expoprod = product(expo1, expo2) + add_to_dict!(p.poly, expoprod, λ1 * λ2) + end + end + p.degree.explvar = p1.degree.explvar+p2.degree.explvar + p.degree.conjvar = p1.degree.conjvar+p2.degree.conjvar + return p +end + +function product(exp1::Exponent, exp2::Exponent) + function add_expod!(expod, expod1) + for (var, deg) in expod1 + if !haskey(expod, var) + expod[var] = Degree(0,0) + end + expod[var].explvar += deg.explvar + expod[var].conjvar += deg.conjvar + if (expod[var].explvar, expod[var].conjvar) == (0,0) + delete!(expod, var) + end + end + return expod + end + + expod = Dict{Variable, Degree}() + add_expod!(expod, exp1.expo) + add_expod!(expod, exp2.expo) + return Exponent(expod) +end + +function *(p1::T, p2::U) where T<:AbstractPolynomial where U<:AbstractPolynomial + return product(convert(Polynomial, p1), convert(Polynomial, p2)) +end +function *(p1::Number, p2::T) where T<:AbstractPolynomial + return product(convert(Polynomial, p1), convert(Polynomial, p2)) +end +function *(p1::T, p2::Number) where T<:AbstractPolynomial + return product(convert(Polynomial, p1), convert(Polynomial, p2)) +end + +### Soustraction +function -(p::T) where T<:AbstractPolynomial + return -1 * convert(Polynomial, p) +end + +function -(p1::T, p2::U) where T<:AbstractPolynomial where U<:AbstractPolynomial + return p1 + (-p2) +end +function -(p1::Number, p2::T) where T<:AbstractPolynomial + return p1 + (-p2) +end +function -(p1::T, p2::Number) where T<:AbstractPolynomial + return p1 + (-p2) +end + +## Division +function divide(p1::Polynomial, p2::Polynomial) + if length(p2) != 1 + error("/(::Polynomial, ::Polynomial): Only allowed for monomial divisor ($(length(p2))-monomial polynomial here).") + end + expo, λ = collect(p2)[1] + if expo.degree != Degree(0,0) + error("/(::Polynomial, ::Polynomial): Only allowed for constant divisor ($(expo.degree)-degree monomial here).") + end + if λ == 0 + error("/(::Polynomial, ::Polynomial): Only allowed for non null constant divisor.") + end + return p1 * (1/λ) +end + +function /(p1::T, p2::U) where T<:AbstractPolynomial where U<:AbstractPolynomial + return divide(convert(Polynomial, p1), convert(Polynomial, p2)) +end +function /(p1::Number, p2::T) where T<:AbstractPolynomial + return divide(convert(Polynomial, p1), convert(Polynomial, p2)) +end +function /(p1::T, p2::Number) where T<:AbstractPolynomial + return divide(convert(Polynomial, p1), convert(Polynomial, p2)) +end + + +## Exponent +function powpoly(p::Polynomial, d::Int) + if length(p) != 1 + error("^(::Polynomial, ::Int): Only supported for monomials ($(length(p))-monomial polynomial here).") + end + expo, λ = collect(p)[1] + + expod = Dict{Variable, Degree}() + for (var, deg) in expo.expo + expod[var] = Degree(deg.explvar*d, deg.conjvar*d) + end + return Polynomial(Dict{Exponent, Number}(Exponent(expod)=>λ^d)) +end + +function ^(p::T, d::Int) where T<:AbstractPolynomial + return powpoly(convert(Polynomial, p), d) +end diff --git a/src_PolynomialOptim/Poly_print.jl b/src_PolynomialOptim/Poly_print.jl new file mode 100644 index 0000000..ee1dcaa --- /dev/null +++ b/src_PolynomialOptim/Poly_print.jl @@ -0,0 +1,66 @@ +varname_cplx2real(varname::String) = (varname*"_Re",varname*"_Im") + +function Base.show(io::IO, x::Variable) + print(io, x.name) +end + +function Base.show(io::IO, d::Degree) + print(io, "(", d.explvar, ",", d.conjvar, ")") +end + +function Base.print(io::IO, exp::Exponent) + if exp == Exponent() + print(io, "1") + else + expo = exp.expo + sortedCollec = sort(collect(expo), by=(x)->x[1].name) + i=length(sortedCollec) + for (var, deg) in sortedCollec + if var.kind <: Complex && deg.conjvar>0 + print(io, "conj(", var, ")") + if deg.conjvar > 1 + print(io, "^", deg.conjvar) + end + if deg.explvar > 0 + print(io, " * ") + end + end + if deg.explvar == 1 + print(io, var) + elseif deg.explvar > 1 + print(io, var, "^", deg.explvar) + end + if i > 1 + print(io, " * ") + end + i -= 1 + end + end +end + +function Base.print(io::IO, P::Polynomial) + poly = P.poly + i = length(poly) + sorted_keys = sort(collect(keys(poly))) + for expo in sorted_keys + λ = poly[expo] + if λ != 0 + print(io, "(", λ, ")") + end + if expo.degree != Degree(0,0) + print(io, "*") + print(io, expo) + if i > 1 + print(io, " + ") + end + i -= 1 + end + end +end + + +function Base.print(io::IO, pt::Point) + for (var, val) in sort(collect(pt), by=x->x[1].name) + println(io, var, " ", val) + end +end diff --git a/src_PolynomialOptim/PolynomialOptim.jl b/src_PolynomialOptim/PolynomialOptim.jl new file mode 100644 index 0000000..991739b --- /dev/null +++ b/src_PolynomialOptim/PolynomialOptim.jl @@ -0,0 +1,255 @@ +# module Poly + +import Base: ==, !=, isless, isconst, isreal, isnull, isequal +import Base: +, -, *, /, ^, conj, conj!, abs2, norm, real, imag +import Base: show, print, convert, copy, hash, merge +import Base: start, next, done, length, setindex!, getindex, haskey, keys, values + + +abstract type AbstractPolynomial end + + +""" + Degree(explvar::Int, conjvar::Int) + +Define a mathematical degree, which is used to define a variable exponent to +`explvar` and its conjugate exponent to `conjvar`. + +### Attributes +- `explvar`: Int +- `conjvar`: Int +""" +mutable struct Degree + explvar::Int + conjvar::Int +end + + +""" + Variable(varname::String, Complex) + +Define a mathematical variable `varname` of a certain mathematical kind, +`Complex` here. + +### Attributes +- `name`: String +- `kind`: a type among `Complex`, `Real` and `Bool` +""" +mutable struct Variable <: AbstractPolynomial + name::String + kind::Type + + function Variable(name, kind) + if kind ∉ Set([Complex, Real, Bool]) || typeof(name) ∉ Set([String, SubString{String}]) + error("Variable() : attempting to define a variable $name of type $kind, supported types are {Complex, Real, Bool}") + end + return new(String(name), kind) + end +end + + +""" + Exponent(expo::Dict{Variable, Degree}) + +Define a mathematical exponent, that is a product of `Variable` and conjugated +`Variable`. + +### Attributes +- `expo` : a dictionary associating `Variable` objects to a `Degree` objects, +their exponent and the exponent of their conjugates. +- `degree`: a `Degree` object indicating the global degree of the exponent : +the sum of the explicit variables degrees and of the conjugated variables +degrees. + +### Exemple +```julia +julia > a, b = Variable("a", Complex), Variable("b", Complex) +julia > Exponent(Dict(a=>Degree(1,0), b=>Degree(1,1))) == a*b*conj(b) +``` +""" +struct Exponent <: AbstractPolynomial + expo::Dict{Variable, Degree} + degree::Degree + + function Exponent(expo::Dict{Variable, Degree}) + degexpl, degconj = 0,0 + for (var, degree) in expo + ((degree.explvar < 0) || (degree.conjvar < 0)) && error("Exponent(): Expected non negative exponent for variable $var (got $degree)") + (isreal(var) && degree.conjvar != 0) && error("Exponent(): Expected nul conj exponent for real variable $var (got $degree)") + (isbool(var) && degree.explvar ∉ Set([0,1])) && error("Exponent(): Expected boolean exponent for bool $var (got $degree)") + if degree != Degree(0,0) + degexpl += degree.explvar + degconj += degree.conjvar + else + delete!(expo, var) + end + end + return new(expo, Degree(degexpl, degconj)) + end +end + + +""" + Polynomial(poly::Dict{Exponent, Number}, degree::Degree) + +Define a mathematical polynomial, that is a linear combinaison of product of +`Variable` and conjugated `Variable`. + +### Attributes +- `poly` : a dictionary associating an `Exponent` to a a number. +- `degree`: a `Degree` object indicating the global degree of the polynomial : +the maximum of the explicit exponent degrees and of the conjugated exponent +degrees. + +### Exemple +```julia +julia > a, b = Variable("a", Complex), Variable("b", Complex) +julia > p = a*b*conj(b) + (2+3im) * b^3 +julia > p.poly = Dict(a*b*conj(b)=>1, b^3=>2+3im) +julia > p.degree = (3,1) +``` +""" +struct Polynomial <: AbstractPolynomial + poly::Dict{Exponent, Number} + degree::Degree + + function Polynomial(poly::Dict{Exponent, Number}) + degexpl, degconj = 0, 0 + for (expo, λ) in poly + if λ!=0 + degexpl = max(degexpl, expo.degree.explvar) + degconj = max(degconj, expo.degree.conjvar) + else + delete!(poly, expo) + end + end + return new(poly, Degree(degexpl, degconj)) + end +end + +""" + Point(coords::Dict{Variable, Number}) + +Define a mathematical point, that is a pairing of variables and numbers. + +### Attributes +- `coords` : a dictionary associating a `Variable` to a a number. + +### Exemple +```julia +julia > a, b = Variable("a", Complex), Variable("b", Complex) +julia > pt = Point(Dict(a=>π, b=>e+7im)) +``` +""" +struct Point + coords::Dict{Variable, Number} + + function Point(dict::Dict) + dict_pt = Dict{Variable, Number}() + for (var, val) in dict + if !isa(var, Variable) || !isa(val, Number) + error("Point(): Expected pair of (Variable, Number), got ($var, $val) of type ($typeof(var), $typeof(val)) instead.") + end + if isbool(var) + booled = 0 + if val != 0 + booled = Int((val/abs(val) + 1) / 2) + end + (booled ≠ val) && warn("Point(): $var is $(var.kind), provided value is $val, $booled stored.") + add_to_dict!(dict_pt, var, booled) + elseif isreal(var) + realed = real(val) + (realed ≠ val) && warn("Point(): $var is $(var.kind), provided value is $val, $realed stored.") + add_to_dict!(dict_pt, var, realed) + else + add_to_dict!(dict_pt, var, val) + end + end + return new(dict_pt) + end +end + + +""" + Constraint(p::AbstractPolynomial, lb::Number, ub::Number) + +Define a mathematical complex constraint, from a polynomial body and two complex +bounds. Equivalent to the two real inequality produced from real and imag part +of `p`, `lb` and `ub`. + +### Attributes +- `p::Polynomial` : the polynomial body of the constraint. +- `lb::Number` : complex lower bound. +- `ub::Number` : complex upper bound. +- `precond::Symbol` : a symbol describing the preconditioning method to be +applied for this constraint. Default value is `:none`. + +### Exemple +```julia +julia > a, b = Variable("a", Complex), Variable("b", Complex) +julia > cstr = abs2(a^2) + abs2(3*b) << 25 +julia > cstr.precond = :sqrt +``` +""" +mutable struct Constraint + p::Polynomial + lb::Number + ub::Number + precond::Symbol + + Constraint(p::AbstractPolynomial, lb::Number, ub::Number) = new(p, lb, ub, :none) +end + + +""" + Problem() + +Define an empty mathematical polynomial optimization problem, characterized by +a polynomial `objective`, a dictionnary of `constraints` and a dictionnary of +used variables `variables`. + +### Attributes +- `objective::Polynomial` : polynomial criterion. +- `constraints::Dict{String, Constraint}` : dictionnary of constraint name to +constraint. +- `variables::Dict{String, Type}` : dictionnary of variable name to type. + +### Exemple +```julia +julia > x, y, z = ... +julia > pb = Problem() +julia > set_objective(pb, abs2(x+y+z)) +julia > add_constraint!(pb, abs2(x) << 1) +julia > add_constraint!(pb, y << 0.5+1im) +julia > add_constraint!(pb, 0-1im << z << 1+0im) +``` +""" +mutable struct Problem + objective::Polynomial + constraints::Dict{String, Constraint} + variables::Dict{String, Type} +end + + +include("Modeler_accessors.jl") +include("Modeler_constructors.jl") +include("Modeler_cplx2real.jl") +include("Poly_Cplx2Real.jl") +include("Poly_operators.jl") +include("Poly_order.jl") +include("Poly_point_algebra.jl") +include("Poly_poly_algebra.jl") +include("Poly_print.jl") +include("utils_ampl.jl") +include("utils_dat_compare.jl") +include("utils_dat_export.jl") +include("utils_dat_import.jl") +include("utils_Poly.jl") + +# export Variable, Point, Exponent, Monomial, Polynomial +# export isconst, isone, is_homogeneous +# export evaluate, abs2, conj, add!, add +# export cplx2real, real, imag +# export norm + +#end diff --git a/src_PolynomialOptim/utils_Poly.jl b/src_PolynomialOptim/utils_Poly.jl new file mode 100644 index 0000000..c3b65d0 --- /dev/null +++ b/src_PolynomialOptim/utils_Poly.jl @@ -0,0 +1,20 @@ +""" + exponents = get_exponents(variables, dmax::Int) + +Compute the set of all exponents in `variables` variables, of degree up to +`dmax`. +""" +function compute_exponents(variables, dmax::Int) + cur_order = Set{Exponent}([Exponent()]) + result = copy(cur_order) + prev_order = Set{Exponent}() + for i=1:d + prev_order = copy(cur_order) + cur_order = Set{Exponent}() + for var in input_vars + union!(cur_order, Set([product(Exponent(var), elt) for elt in prev_order])) + end + union!(result, cur_order) + end + return result +end diff --git a/src_PolynomialOptim/utils_ampl.jl b/src_PolynomialOptim/utils_ampl.jl new file mode 100644 index 0000000..228df45 --- /dev/null +++ b/src_PolynomialOptim/utils_ampl.jl @@ -0,0 +1,110 @@ +# """ +# pt_sol = get_knitro_sol(filename) +# +# Compute a local optima of the filename complex QCQP. +# """ +# function get_knitro_sol(filename::String) +# open(joinpath("src_ampl", "minlp.dat"), "w") do f +# println(f, "param: KEYS: LEFT RIGHT := include \"$(joinpath("..", "$filename"))\";") +# end +# +# cd("src_ampl") +# +# instancename = split(filename, "\\")[end] +# instancename = split(instancename, ".")[1] +# out_log = "Knitro_$(instancename).log" +# run(`cmd /c ampl minlp.run '>' $out_log`) +# cd("..") +# +# sol_cplx = read_Knitro_output("Knitro_sol.txt") +# return sol_cplx +# end + + +""" +sol = read_Knitro_output(filepath) + +Return the point corresponding to Knitro output stored in the filepath text +file. +""" +function read_Knitro_output(filepath::String) + filepath = joinpath("src_ampl", "Knitro_sol.txt") + data = readdlm(filepath) + + sol_cplx = Point() + + for i=1:size(data, 1) + sol_cplx[Variable(data[i, 1], Complex)] = data[i, 2] + im*data[i, 3] + end + + return sol_cplx +end + + + +""" + run_knitro(pb_path::String, src_ampl_path::String) + +Run knitro on the `real_minlp.run`, `real_minlp.run` template scripts from the +`src_ampl_path` folder and `pb_path` files ("real_minlp_instance.dat" and +"real_minlp_precond_cstrs.dat"). +""" +function run_knitro(pb_path::String, src_ampl_path::String) + root = pwd() + date = Dates.format(now(), "yy_u_dd_HH_MM_SS") + outlog = "Knitro_$(date).log" + + cd(pb_path) + + open("real_minlp.run", "w") do f + println(f, "include $(joinpath(src_ampl_path, "real_minlp.run"));") + end + + open("real_minlp.mod", "w") do f + println(f, "include $(joinpath(src_ampl_path, "real_minlp.mod"));") + end + + try + run(`cmd /c ampl real_minlp.run '>' $(outlog)`) + catch + warn("AMPL/Knitro failed, returning.") + end + + cd(root) +end + +""" +pt_knitro, pt_GOC = read_Knitro_output(pb_path, pb) + +Read knitro output files at `pb_path` and return points according to variables +types in pb. +""" +function read_Knitro_output(pb_path::String, pb::Problem) + function build_pt(vararray, valarray) + pt = Point() + for i=1:length(vararray) + varname, vartype = vararray[i], get_variabletype(pb, String(vararray[i])) + val = valarray[i] + if (vartype == Bool) + val = round(val) + end + pt[Variable(varname, vartype)] = val + end + return pt + end + + root = pwd() + cd(pb_path) + + ## Read Knitro solution + files = filter(x->ismatch(r".csv", x), readdir(pwd())) + length(files) == 1 || warn("get_knitro_solutions(): $(length(files)) .csv files found in $(pb_path).\nExtracting solution from $(files[1]).") + + sol_data = readdlm(files[1], ';') + + pt_knitro = build_pt(sol_data[:, 1], sol_data[:, 2]) + pt_GOC = build_pt(sol_data[:, 1], sol_data[:, 3]) + + cd(root) + return pt_knitro, pt_GOC +end diff --git a/src_PolynomialOptim/utils_dat_compare.jl b/src_PolynomialOptim/utils_dat_compare.jl new file mode 100644 index 0000000..2062b31 --- /dev/null +++ b/src_PolynomialOptim/utils_dat_compare.jl @@ -0,0 +1,80 @@ +""" +max_err, errors = compare_dat(file1, file2, epsilon=1e-10) + +Compute max_err, the maximum difference between common lines of both files, and +a dict associating an error to the line(s) where it occured. +""" +function compare_dat(file1::String, file2::String; epsilon = 1e-10, display_level=0) + temp1, temp2 = "sorted_dat1.temp", "sorted_dat2.temp" + + sort_dat(file1, temp1) + sort_dat(file2, temp2) + + data1, data2 = readdlm(temp1), readdlm(temp2) + + rm(temp1) + rm(temp2) + + lines1 = Set([data1[i, 1]*data1[i, 2]*data1[i, 3]*data1[i, 4] for i=1:size(data1, 1)]) + lines2 = Set([data2[i, 1]*data2[i, 2]*data2[i, 3]*data2[i, 4] for i=1:size(data2, 1)]) + + if size(data1, 1) != size(data2, 1) + warn("dat files have different number of lines ($(size(data1, 1)) and $(size(data2, 1)))") + end + + if lines1 != lines2 + warn("dat files have different key entries\nfile1 \\ file2: $(length(setdiff(lines1, lines2)))\nfile2 \\ file1: $(length(setdiff(lines2, lines1)))") + end + + if display_level>0 + println("nfile1 \\ file2: $(sort(collect(setdiff(lines1, lines2)))) \nfile2 \\ file1: $(sort(collect(setdiff(lines2, lines1))))") + end + + errors = Dict{Float64, Any}() + for i=1:min(size(data1, 1), size(data2, 1)) + if data1[i, 1:4] == data2[i, 1:4] + cur_error = max(abs(data1[i, 5]-data2[i, 5]), abs(data1[i, 6]-data2[i, 6])) + if cur_error > epsilon + if !haskey(errors, cur_error) + errors[cur_error] = Set() + end + push!(errors[cur_error], (i, data1[i, 1:4])) + end + else + # warn("Compare_dat(): line $i - mismatched on line entry") + end + end + + max_coeff_err = 0 + if length(errors) > 0 + max_coeff_err = maximum(collect(keys(errors))) + end + return max_coeff_err, errors +end + + +""" +sort_dat(filename, outfile) + +Writes an outfile dat file with lexicographically sorted lines from filename. +""" +function sort_dat(filename, outfile) + lines = Set{String}() + + open(filename, "r") do f + while !eof(f) + line = readline(f) + push!(lines, line) + end + end + + if isfile(outfile) + rm(outfile) + end + + open(outfile, "a") do f + for line in sort(collect(lines)) + println(f, line) + end + end +end diff --git a/src_PolynomialOptim/utils_dat_export.jl b/src_PolynomialOptim/utils_dat_export.jl new file mode 100644 index 0000000..5ebb39e --- /dev/null +++ b/src_PolynomialOptim/utils_dat_export.jl @@ -0,0 +1,308 @@ +function get_varsconj(exp::Exponent) + varconj = Vector{Variable}() + for (var, expo) in exp + for i=1:expo.conjvar + push!(varconj, var) + end + end + varconj +end +function get_varslin(exp::Exponent) + varlin = Vector{Variable}() + for (var, expo) in exp + for i=1:expo.explvar + push!(varlin, var) + end + end + varlin +end + +function print_string(io, strng, len) + print(io, " "^(len-length(strng)), strng, " ") +end + +function print_dat_line(io, linetype, cstrname, var1, var2, val1, val2, maxvarlen, maxcstrlen) + @printf(io, "%8s ", linetype) + print_string(io, cstrname, maxcstrlen) + print_string(io, var1, maxvarlen) + print_string(io, var2, maxvarlen) + @printf(io, "% 23.16e % 23.16e\n", val1, val2) +end + +function print_quad_expo(io, expo::Exponent, cat::String, coeff, maxvarlen, maxcstrlen) + vars_conj = get_varsconj(expo) + vars_lin = get_varslin(expo) + if length(vars_conj) == 1 && length(vars_lin) == 1 + print_dat_line(io, "QUAD", cat, vars_conj[1].name, vars_lin[1].name, real(coeff), imag(coeff), maxvarlen, maxcstrlen) + elseif length(vars_conj) == 0 && length(vars_lin) == 2 && vars_lin[1].kind <: Real && vars_lin[2].kind <: Real + print_dat_line(io, "QUAD", cat, vars_lin[1].name, vars_lin[2].name, real(coeff), imag(coeff), maxvarlen, maxcstrlen) + elseif length(vars_conj) == 1 && length(vars_lin) == 0 + print_dat_line(io, "LIN", cat, vars_conj[1].name, "NONE", real(coeff), imag(coeff), maxvarlen, maxcstrlen) + elseif length(vars_conj) == 0 && length(vars_lin) == 1 + print_dat_line(io, "LIN", cat, "NONE", vars_lin[1].name, real(coeff), imag(coeff), maxvarlen, maxcstrlen) + elseif length(vars_conj) == 0 && length(vars_lin) == 0 + print_dat_line(io, "CONST", cat, "NONE", "NONE", real(coeff), imag(coeff), maxvarlen, maxcstrlen) + else + warn("export_to_dat(): Exponent $expo not supported.") + end +end + +function print_constraint(io::IO, cstrname::String, cstr::Constraint, maxvarlen::Int, maxcstrlen::Int, expos::Dict{Exponent, String}) + print_poly!(io, cstr.p, cstrname, maxvarlen, maxcstrlen, expos) + + if ismatch(r"_Re", cstrname) + if real(cstr.lb) != -Inf + print_dat_line(io, "LB", cstrname, "NONE", "NONE", real(cstr.lb), imag(cstr.lb), maxvarlen, maxcstrlen) + end + if real(cstr.ub) != Inf + print_dat_line(io, "UB", cstrname, "NONE", "NONE", real(cstr.ub), imag(cstr.ub), maxvarlen, maxcstrlen) + end + elseif ismatch(r"_Im", cstrname) + if imag(cstr.lb) != -Inf + print_dat_line(io, "LB", cstrname, "NONE", "NONE", real(cstr.lb), imag(cstr.lb), maxvarlen, maxcstrlen) + end + if imag(cstr.ub) != Inf + print_dat_line(io, "UB", cstrname, "NONE", "NONE", real(cstr.ub), imag(cstr.ub), maxvarlen, maxcstrlen) + end + else + if imag(cstr.lb) != -Inf-im*Inf + print_dat_line(io, "LB", cstrname, "NONE", "NONE", real(cstr.lb), imag(cstr.lb), maxvarlen, maxcstrlen) + end + if imag(cstr.ub) != +Inf+im*Inf + print_dat_line(io, "UB", cstrname, "NONE", "NONE", real(cstr.ub), imag(cstr.ub), maxvarlen, maxcstrlen) + end + end +end +nb_from_str(string::String) = parse(matchall(r"\d+", string)[1]) +get_scenario(string::String) = String(split(string, "_")[1]) + +function print_doc(io, filename) + println(io, "# $filename - exported $(now())") + println(io, "# Description of a QCQP optimization problem. 3 sections:") + println(io, "# - List of all variables in the problem, along with type, name, and possibly complex value.") + println(io, "# Grouped by \"VAR_TYPE\" tag (col 1). Type is \"C\" Complex, \"R\" Real, \"BOOL\" Boolean (col 2). ") + println(io, "# Name (col 3). Real and imag part of value (col 5 and 6 resp.).") + println(io, "# - Description of the objective function (one quadratic polynomial).") + println(io, "# Sum of monomials, grouped by \"OBJ\" (col 2), either:") + println(io, "# order 0 (\"CONST\" col 1) : real and imag part of coef. resp. at col 5 and 6.") + println(io, "# order 1 (\"LIN\" col 1) : real and imag part of coef. resp. at col 5 and 6, either:") + println(io, "# variable name in col 3 hence conjugate variable in polynomial,") + println(io, "# variable name in col 4 hence variable in polynomial.") + println(io, "# order 2 (\"QUAD\" col 1) : real and imag part of coef. resp. at col 5 and 6, ") + println(io, "# monomial is product of conjugate variable at col 3 and variable at col 4.") + println(io, "# - Description of the constraints : one polynomial and two complex numeric bounds.") + println(io, "# Constraints are grouped by name (col 2). Quadratic body is described as the objective.") + println(io, "# Lower bound (\"LB\" col 1) and upper bound (\"UB\" col 1) have their complex value in col 5 and 6.") + println(io, "#") +end + +""" + print_variables(io::IO, variables, pt::Point) + + Write a .dat description of `variables` variables to `io`. +""" +function print_variables(io::IO, variables, pt::Point, maxvarlen, maxcstrlen) + for varname in sort(collect(keys(variables))) + var = Variable(varname, variables[varname]) + if iscomplex(var) + var_type = "CPLX" + elseif isbool(var) + var_type = "BOOL" + elseif isreal(var) + var_type = "REAL" + else + error("Export_to_dat(): unsuported variable type $(var.kind)") + end + val = 0 + + if haskey(pt, var) + val = pt[var] + end + + print_dat_line(io, "VAR_TYPE", var_type, var.name, "NONE", real(val), imag(val), maxvarlen, maxcstrlen) + end +end + +""" + print_poly!(io::IO, p::AbstractPolynomial, cat::String, maxvarlen, maxcstrlen, expos::Dict{Exponent, String}) + +Print the `p` polynomial corresponding to the constraint or objective +`cat` (category) to `io`. Each of the polynomial's exponent is printed in a line, +either explicitly if its degree is 2 or less, or implicitly by defining an +exponent name in the `expos` dict if it required, and print the exponent name +with the coefficient (which essentially is a linear term). +""" +function print_poly!(io::IO, p::AbstractPolynomial, cat::String, maxvarlen, maxcstrlen, expos::Dict{Exponent, String}) + constval = 0 + + for expo in sort(collect(keys(p))) + coeff = p[expo] + + exp_globaldeg = expo.degree.explvar + expo.degree.conjvar + if exp_globaldeg > 2 + if !haskey(expos, expo) + expos[expo] = "MONO_$(length(expos))" + end + print_dat_line(io, "MONO", cat, expos[expo], "NONE", real(coeff), imag(coeff), maxvarlen, maxcstrlen) + elseif exp_globaldeg == 0 + constval = coeff + else + print_quad_expo(io, expo, cat, coeff, maxvarlen, maxcstrlen) + end + end + if constval != 0 + print_dat_line(io, "CONST", cat, "NONE", "NONE", real(constval), imag(constval), maxvarlen, maxcstrlen) + end +end + +""" + export_to_dat(pb_optim::Problem, outpath::String, pt::Point = Point()) + +Write the `pb_optim` problem to the `outpath` folder, with the dispensory initial +point `pt`. Output files are `real_minlp_instance.dat` and +`real_minlp_precond_cstrs.dat`. +""" +function export_to_dat(pb_optim::Problem, outpath::String, pt::Point = Point()) + ## Get max length varname + maxvarlen = -1 + for var in pb_optim.variables + if length(var[1]) > maxvarlen + maxvarlen = length(var[1]) + end + end + + ## Get max length dat constraint name + maxcstrlen = -1 + for cstrname in keys(pb_optim.constraints) + if length(cstrname) > maxcstrlen + maxcstrlen = length(cstrname) + end + end + + # Container for monomials definition, to be written lastly + expos = Dict{Exponent, String}() + precond_cstrs = Set{String}() + + isdir(outpath) || mkpath(outpath) + filename = joinpath(outpath, "real_minlp_instance.dat") + touch(filename) + outfile = open(filename, "w") + + ## Comment section + print_doc(outfile, filename) + + ## Print variables + variables = get_variables(pb_optim) + print_variables(outfile, variables, pt, maxvarlen, maxcstrlen) + + ## Print objective + print_poly!(outfile, pb_optim.objective, "OBJ", maxvarlen, maxcstrlen, expos) + + ## Print constraints + for cstrname in sort(collect(keys(pb_optim.constraints))) + cstr = pb_optim.constraints[cstrname] + print_constraint(outfile, cstrname, cstr, maxvarlen, maxcstrlen, expos) + + if cstr.precond != :none + push!(precond_cstrs, cstrname) + end + end + + ## Print collected monomials definition + for (expo, exponame) in expos + for (var, deg) in expo + print_dat_line(outfile, "MONO_DEF", exponame, var.name, "NONE", deg.explvar, deg.conjvar, maxvarlen, maxcstrlen) + end + end + close(outfile) + + ## Print constraints with preconditionning + filename = joinpath(outpath, "real_minlp_precond_cstrs.dat") + touch(filename) + outfile = open(filename, "w") + + print_string(outfile, "#cstrname", maxcstrlen) + @printf(outfile, "%10s\n", "Precondtype") + for cstrname in sort(collect(precond_cstrs)) + if get_constraint(pb_optim, cstrname).precond == :sqrt + print_string(outfile, cstrname, maxcstrlen) + @printf(outfile, "%10s\n", "SQRT") + else + warn("export_dat(): Unknown preconditionning for cstr $cstrname.") + end + end + close(outfile) +end + + + + +### +### +function export_matpower_to_dat(QCQP::Problem, filename::String, pt::Point = Point()) + ## Get max length varname + maxvarlen = -1 + for var in QCQP.variables + if length(var.name) > maxvarlen + maxvarlen = length(var.name) + end + end + + ## Sort constraints by type (voltm, unit and rest), and build dat constraint name + cstrs_keys = sort(collect(keys(QCQP.constraints))) + + cstr_keys = Set(keys(QCQP.constraints)) + voltm_keys = filter(x->ismatch(r"VOLTM", x), cstr_keys) + unit_keys = filter(x->ismatch(r"UNIT", x), cstr_keys) + load_keys = setdiff(cstr_keys, union(unit_keys, voltm_keys)) + id_to_loadkey = Dict(nb_from_str(str)=> (str, "LOAD_$(string(nb_from_str(str)))") for str in load_keys) + id_to_voltmkey = Dict(nb_from_str(str)=> (str, String(split(str, "_")[2])) for str in voltm_keys) + id_to_unitkey = Dict(nb_from_str(str)=> (str, "UNIT_$(string(nb_from_str(str)))") for str in unit_keys) + + ## Get max length dat constraint name + maxcstrlen = -1 + for (_, val) in union(id_to_loadkey, id_to_voltmkey, id_to_unitkey) + if length(val[2]) > maxcstrlen + maxcstrlen = length(val[2]) + end + end + + touch(filename) + outfile = open(filename, "w") + + ## Comment section + print_doc(outfile, filename) + + ## Print variables + variables = get_variables(QCQP) + print_variables(outfile, variables, pt, maxvarlen, maxcstrlen) + + ## Print objective + obj_keys = sort(collect(keys(QCQP.objective))) + const_printed = false + const_val = 0 + for expo in obj_keys + coeff = QCQP.objective[expo] + + if length(expo) != 0 + print_quad_expo(outfile, expo, "OBJ", coeff, maxvarlen, maxcstrlen) + else + const_val = coeff + end + end + print_dat_line(outfile, "CONST", "OBJ", "NONE", "NONE", real(const_val), imag(const_val), maxvarlen, maxcstrlen) + + for i in sort(collect(keys(id_to_loadkey))) + cstr = QCQP.constraints[id_to_loadkey[i][1]] + print_constraint(outfile, id_to_loadkey[i][2], cstr, maxvarlen, maxcstrlen, expos) + end + for i in sort(collect(keys(id_to_voltmkey))) + cstr = QCQP.constraints[id_to_voltmkey[i][1]] + print_constraint(outfile, id_to_voltmkey[i][2], cstr, maxvarlen, maxcstrlen, expos) + end + for i in sort(collect(keys(id_to_unitkey))) + cstr = QCQP.constraints[id_to_unitkey[i][1]] + print_constraint(outfile, id_to_unitkey[i][2], cstr, maxvarlen, maxcstrlen, expos) + end + close(outfile) +end diff --git a/src_PolynomialOptim/utils_dat_import.jl b/src_PolynomialOptim/utils_dat_import.jl new file mode 100644 index 0000000..8711bbb --- /dev/null +++ b/src_PolynomialOptim/utils_dat_import.jl @@ -0,0 +1,136 @@ +""" + pb, init_point = import_from_dat(instancepath::String, precondcstrspath::String) + +Build the polynomial optimization problem described by the `instancepath` file, +with the dispensory preconditionning descritpion `precondcstrspath` along with +the iniitial point possibly provided in the file (defaults value is null). +""" +function import_from_dat(instancepath::String, precondcstrspath::String) + init_point = Point() + variables = Dict{String, Variable}() + exponents = Dict{String, Exponent}() + pb = Problem() + + instance_str = open(instancepath) + l = jump_comments!(instance_str) + + + ## Collect and define variables + line = matchall(r"\S+", l) + while line[1] == "VAR_TYPE" && !eof(instance_str) + if line[2] == "REAL" + variables[line[3]] = Variable(line[3], Real) + elseif line[2] == "BOOL" + variables[line[3]] = Variable(line[3], Bool) + elseif line[2] == "CPLX" + variables[line[3]] = Variable(line[3], Complex) + else + error("import_to_dat(): Unknown variable type $(line[2]) for variable $(line[3]).") + end + + init_point[variables[line[3]]] = parse(line[5]) + im*parse(line[6]) + + l = readline(instance_str) + line = matchall(r"\S+", l) + end + + ## Mark where objective defintion begins + mark(instance_str) + + + ## Move forward to the monomial definition section + while line[1] != "MONO_DEF" && !eof(instance_str) + l = readline(instance_str) + line = matchall(r"\S+", l) + end + + while line[1] == "MONO_DEF" && !eof(instance_str) + exponame = line[2] + var = variables[line[3]] + if !haskey(exponents, exponame) + exponents[exponame] = Exponent() + end + exponents[exponame] = product(exponents[exponame], Exponent(Dict(var=>Degree(parse(line[5]), parse(line[6]))))) + l = readline(instance_str) + line = matchall(r"\S+", l) + end + + ## Reset stream to the objective defintion + reset(instance_str) + l = readline(instance_str) + line = matchall(r"\S+", l) + + ## Build polynomial objective + p = Polynomial() + while line[2] == "OBJ" + λ = parse(line[5]) + im*parse(line[6]) + var1, var2 = line[3:4] + if line[1] == "MONO" + p += λ * exponents[var1] + else + p += λ * (var1!="NONE" ? variables[var1] : 1) * (var2!="NONE" ? variables[var2] : 1) + end + l = readline(instance_str) + line = matchall(r"\S+", l) + end + set_objective!(pb, p) + + while line[1] != "MONO_DEF" && !eof(instance_str) + cstrname = line[2] + lb = -Inf + ub = +Inf + p = Polynomial() + while line[2] == cstrname + λ = parse(line[5]) + im*parse(line[6]) + var1, var2 = line[3:4] + if line[1] == "MONO" + p += λ * exponents[var1] + elseif line[1] ∈ Set(["CONST", "LIN", "QUAD"]) + p += λ * (var1!="NONE" ? variables[var1] : 1) * (var2!="NONE" ? variables[var2] : 1) + elseif line[1] == "UB" + ub = real(λ) + elseif line[1] == "LB" + lb = real(λ) + else + error("import_to_dat(): Unknown variable type $(line[1]) for constraint $(line[2]).") + end + l = readline(instance_str) + line = matchall(r"\S+", l) + end + add_constraint!(pb, String(cstrname), lb << p << ub) + l = readline(instance_str) + line = matchall(r"\S+", l) + end + + + precond_str = open(precondcstrspath) + + l = jump_comments!(precond_str) + line = matchall(r"\S+", l) + while !eof(precond_str) + if line[2] == "SQRT" + pb.constraints[line[1]].precond = :sqrt + else + error("import_from_dat(): Unknown preconditioning $(line[2]) for constraint $(line[1]).") + end + l = readline(precond_str) + line = matchall(r"\S+", l) + end + return pb, init_point +end + + +""" + l = jump_comments!(io::IOStream) + +Jump comments, i.e. lines starting by '#', in the `io` stream, and return the +first non commented line. +""" +function jump_comments!(io::IOStream) + l = "" + while !eof(io) + l = readline(io) + ismatch(r"\s*#", l) || break + end + return l +end diff --git a/src_PowSysMod/PowSysMod.jl b/src_PowSysMod/PowSysMod.jl new file mode 100644 index 0000000..670acf7 --- /dev/null +++ b/src_PowSysMod/PowSysMod.jl @@ -0,0 +1,12 @@ +module PowSysMod + +include("PowSysMod_body.jl") + + +export load_OPFproblems +export build_Problem! +export build_globalpb! +export MatpowerInput, MatpowerSimpleInput, IIDMInput, GOCInput + + +end diff --git a/src_PowSysMod/PowSysMod_accessors.jl b/src_PowSysMod/PowSysMod_accessors.jl new file mode 100644 index 0000000..1b55615 --- /dev/null +++ b/src_PowSysMod/PowSysMod_accessors.jl @@ -0,0 +1,49 @@ +## Gridstructure accessors +function get_buses(OPFpbs::OPFProblems, scenario::String) + return keys(OPFpbs[scenario].ds.bus) +end + +function get_links(OPFpbs::OPFProblems, scenario::String) + return keys(OPFpbs[scenario].ds.link) +end + +function get_node_elems(bus::String, OPFpbs::OPFProblems, scenario::String) + return keys(OPFpbs[scenario].ds.bus[bus]) +end + +function get_link_elems(link::Link, OPFpbs::OPFProblems, scenario::String) + return keys(OPFpbs[scenario].ds.link[link]) +end + +function get_elem_type(elem::String, bus::String, OPFpbs::OPFProblems, scenario::String) + return typeof(OPFpbs[scenario].ds.bus[bus][elem]) +end + +function get_elem_type(elem::String, link::Link, OPFpbs::OPFProblems, scenario::String) + return typeof(OPFpbs[scenario].ds.link[link][elem]) +end + +function get_links_in(bus::String, gs::GridStructure) + return haskey(gs.node_linksin, bus) ? gs.node_linksin[bus] : Set{Any}() +end + +function get_links_out(bus::String, gs::GridStructure) + return haskey(gs.node_linksout, bus) ? gs.node_linksout[bus] : Set{Any}() +end + +function get_Var(elem_type::String, bus::String, OPFpbs::OPFProblems, scenario::String) + return OPFpbs[scenario].gs.node_vars[bus][elem_type] +end + +function get_Var(elem_type::String, link::Link, OPFpbs::OPFProblems, scenario::String) + return OPFpbs[scenario].gs.link_vars[link][elem_type] +end + +## Modeling Choice accessors +function get_elem_formulation(elem_id::String, bus::String, OPFpbs::OPFProblems, scenario::String) + return OPFpbs[scenario].mc.node_formulations[bus][elem_id] +end + +function get_elem_formulation(elem_id::Type, link::Link, OPFpbs::OPFProblems, scenario::String) + return OPFpbs[scenario].mc.node_formulations[link][elem_id] +end diff --git a/src_PowSysMod/PowSysMod_body.jl b/src_PowSysMod/PowSysMod_body.jl new file mode 100644 index 0000000..25d9f42 --- /dev/null +++ b/src_PowSysMod/PowSysMod_body.jl @@ -0,0 +1,187 @@ +include(joinpath("..", "src_PolynomialOptim", "PolynomialOptim.jl")) +using LightXML, MAT + +import Base.copy + +""" + Link(orig, dest) + +Represents a link from a bus origin `orig` to a bus destination `dest` +Util for stock or treat data about transmission lines or transformers +### Examples +``` +julia > Link("BUS_1","BUS_4") #represents a link between BUS_1 and BUS_4 +``` +""" +struct Link + orig::String + dest::String +end + +""" + Abstract type AbstractNodeLabel + +Abstract type for any element about a node +### Examples +```julia +struct MatpowerLoad <: AbstractNodeLabel +struct GOCVolt <: AbstractNodeLabel +struct IIDMGenerator <: AbstractNodeLabel +struct GOCShunt <: AbstractNodeLabel + +``` +""" +abstract type AbstractNodeLabel end + +""" + abstract type AbstractLinkLabel + +Abstract type for any element about a link +### Examples +``` +struct MatpowerLine_π <: AbstractLinkLabel +struct GOCLineπ_withtransformer <: AbstractLinkLabel +struct GOCNullImpedance_notransformer <: AbstractLinkLabel +``` +""" +abstract type AbstractLinkLabel end + +""" + abstract type AbstractInput +Abstract type for any entry format +### Examples +``` +type MatpowerInput <: AbstractInput end +type GOCInput <:AbstractInput end +type IIDMInput <: AbstractInput end +``` +""" +abstract type AbstractInput end + + +""" + DataSource(bus::Dict{String, Dict{String, Any}}, link::Dict{Link, Dict{String, Any}}) + +Store network data either in an attribute `bus` or in an attribute `link` depending on data type. + +### Attributes +- `bus` : Dict{String, Dict{String, Any}} +- `link`: Dict{Link, Dict{String, Any}} + +### Example +``` +Instance Matpower WB2 with two nodes +julia > databus1 = Dict("Volt" => MatpowerVolt("BUS_1",1,0.95, 1.05), "Gen_1"=> MatpowerGenerator(-400 im, 600+400im, 3, [0 2 0], true)) +julia > databus2 = Dict("Volt" => MatpowerVolt("BUS_2",2,0.95, 1.05), "Load" => MatpowerLoad(350-350im)) +julia > busdata = Dict("BUS_1"=> databus1, "BUS_2" => databus2) +julia > link1to2 = Dict("LinkMP_1" => MatpowerLine_π(100,0.04,0.2,0.0,0.0,0.0)) +julia > linkdata = Dict( Link("BUS_1","BUS_2") => link1to2) +julia > ds = DataSource(busdata,linkdata) +``` +""" +struct DataSource + # baseMVA::Number + bus::Dict{String, Dict{String, Any}} + link::Dict{Link, Dict{String, Any}} +end + + + +""" + struct GridStructure(scenario::String, node_linksin::Dict{String, Set{Link}}, node_linksout::Dict{String, Set{Link}}) + +Store information about network to construct power balances. + +### Attributes +- `scenario::String` : scenario to study +- `generator_types::Set{Type}` : specify which types are generators +- `node_linksin::Dict{String, Set{Link}}` : for each node, set of links having this node for destination +- `node_linksout::Dict{String, Set{Link}}` : for each node, set of links having this node for origin + +### Example +``` +Instance Matpower WB2 with two nodes +julia > scenario = "BaseCase" +julia > node_linksin = Dict( "BUS_2" => Set{Link}([Link("BUS_1","BUS_2")]) ) +julia > node_linksout = Dict( "BUS_1" => Set{Link}([Link("BUS_1, "BUS_2")]) ) +julia > gs = GridStructure(scenario,generator_types, node_linksin, node_linksout) +``` +""" +mutable struct GridStructure + scenario::String + node_linksin::Dict{String, Set{Link}} + node_linksout::Dict{String, Set{Link}} +end + + +""" + struct MathematicalProgramming(node_formulations::Dict{String, Dict{String, Symbol}}, link_formulations::Dict{String, Dict{String, Symbol}}, node_vars::Dict{String, Dict{String, Variable}}, link_vars::Dict{Link, Dict{String, Variable}}) + +Store information about mathematical formulation. +A node element or a link element can be formulated with a minimal number of variables or with variables representing all the quantities linked to the element. +This formulation is represented by symbols in the dictonaries `node_formulations` or `link_formulations` depending on the type of element. +Store created variables in `node_vars` or `link_vars`. + +### Example +``` +Instance Matpower WB2 with two nodes +julia > node_formulations = +julia > link_formulations = Dict( Link("BUS_1","BUS_2") => Dict("LinkMP_1" => :NbMinVar)) +julia > node_vars = +julia > link_vars = + +``` +""" +mutable struct MathematicalProgramming + node_formulations::Dict{String, Dict{String, Symbol}} # :NbMinVar, :NewVar, :None + link_formulations::Dict{Link, Dict{String, Symbol}} + node_vars::Dict{String, Dict{String, Variable}} + link_vars::Dict{Link, Dict{String, Variable}} +end + + +""" + struct Scenario(ds::DataSource, gs::GridStructure, mp::MathematicalProgramming) + + +### Example +``` +Instance Matpower WB2 with two nodes +julia > basecase_scenario = Scenario(ds, gs, mp) +``` +""" +mutable struct Scenario + ds::DataSource + gs::GridStructure + mp::MathematicalProgramming +end + +""" + OPFproblems + +### Examples +``` + +``` +""" +const OPFProblems = Dict{String, Scenario} + +# files to include for each entry type +include(joinpath("..","src_GOC","GOC_files.jl")) +include(joinpath("..","src_Matpower","Matpower_files.jl")) +include(joinpath("..","src_IIDM","IIDM_files.jl")) + + +# common files +include("add_generator_variables.jl") +include("build_globalpb.jl") +include("build_Problem.jl") +include("check_feasibility.jl") +include("default_behaviour.jl") +include("load_OPFproblems.jl") +include("naming_conventions.jl") +include("PowSysMod_accessors.jl") +include("PowSysMod_operators.jl") +include("utils_plots.jl") + + \ No newline at end of file diff --git a/src_PowSysMod/PowSysMod_operators.jl b/src_PowSysMod/PowSysMod_operators.jl new file mode 100644 index 0000000..2a5f364 --- /dev/null +++ b/src_PowSysMod/PowSysMod_operators.jl @@ -0,0 +1,16 @@ +""" + Functions to copy structures (DataSource, GridStructure) +""" + +function copy(ds::DataSource) + bus = copy(ds.bus) + link = copy(ds.link) + return DataSource(bus, link) +end + +function copy(gs::GridStructure) + scenario = gs.scenario + node_linksin = copy(gs.node_linksin) + node_linksout = copy(gs.node_linksout) + return GridStructure(scenario, node_linksin, node_linksout) +end diff --git a/src_PowSysMod/add_generator_variables.jl b/src_PowSysMod/add_generator_variables.jl new file mode 100644 index 0000000..008e26f --- /dev/null +++ b/src_PowSysMod/add_generator_variables.jl @@ -0,0 +1,27 @@ +""" + introduce_Sgenvariables!(OPFpbs) + +Modify `mp` field of all `Scenario` in `OPFpbs` in order to create Sgen variables when the problem will be constructed. +""" +function introduce_Sgenvariables!(OPFpbs) + for (scenario, OPFpb) in OPFpbs + node_formulations = OPFpbs[scenario].mp.node_formulations + + for (bus, elems_form) in node_formulations + for elem in keys(elems_form) + if ismatch(r"Gen", elem) + if scenario != basecase_scenario_name() + elems_form[elem] = :GOCcoupling + else + elems_form[elem] = :NewVar + end + elseif ismatch(r"Load", elem) || ismatch(r"Shunt", elem) + elems_form[elem] = :NbMinVar + else + elems_form[elem] = :NewVar + end + end + end + end + return OPFpbs +end diff --git a/src_PowSysMod/build_Problem.jl b/src_PowSysMod/build_Problem.jl new file mode 100644 index 0000000..4a68306 --- /dev/null +++ b/src_PowSysMod/build_Problem.jl @@ -0,0 +1,193 @@ +""" + build_Problem!(OPFpbs, scenario::String) + +Return a polynomial problem in complex variables with polynomial constraints +Step 0 : verify that there is at most one infered generator variable for each node +Step 1 : create variables in `OPFpbs[scenario].mp` according to formulations in `OPFpbs[scenario].mp` and add them in optimization problem `pb_opt` +Step 2 : create power balance constraints for each node in optimization problem `pb_opt` according to elements associated to the node +Step 3 : create objective in optimization problem `pb_opt` iterating on all nodal and link elements + + +# Arguments +- `OPFpbs` : dictionary of scenario (string) associated to structure Scenario +- `scenario` : String which must be a key of OPFpbs + +# Output +- `pb_opt` : a polynomial optimization problem + +# Example +``` +julia > OPFpbs = build_OPFproblems(MatpowerInput, "instances\\matpower\\WB2.m") +julia > OPF = build_Problem!(OPFpbs, "BaseCase") +julia > print(OPF) +▶ variables: VOLT_1 VOLT_2 +▶ objective: +(192.3) * conj(VOLT_1) * VOLT_1 + (-96.15384615384615 - 480.7692307692308im) * VOLT_1 * conj(VOLT_2) + (-96.15384615384615 + 480.7692307692308im) * conj(VOLT_1) * VOLT_2 +▶ constraints: +LOAD_BUS_2: (-96.15384615384615 - 480.7692307692308im) * conj(VOLT_1) * VOLT_2 + (96.15384615384615 + 480.7692307692308im) * conj(VOLT_2) * VOLT_2 = -350.0f0 + 350.0f0im +UNIT_BUS_1: 0.0f0 - 400.0f0im < (96.15384615384615 + 480.7692307692308im) * conj(VOLT_1) * VOLT_1 + (-96.15384615384615 - 480.7692307692308im) * VOLT_1 * conj(VOLT_2) < 600.0f0 + 400.0f0im + VOLTM_1: 0.9025 < conj(VOLT_1) * VOLT_1 < 1.1025 + VOLTM_2: 0.9025 < conj(VOLT_2) * VOLT_2 < 1.056784 + +``` +""" + + +function build_Problem!(OPFpbs#=::OPFProblems=#, scenario::String) + + ds = OPFpbs[scenario].ds + gs = OPFpbs[scenario].gs + mp = OPFpbs[scenario].mp + + # 1. Creation des variables + for bus in get_buses(OPFpbs, scenario) + bus_elems = mp.node_formulations[bus] + bus_vars = mp.node_vars[bus] + for (elemid, elem) in ds.bus[bus] + create_vars!(elem, bus, elemid, bus_elems[elemid], bus_vars, scenario) + end + end + + for link in get_links(OPFpbs, scenario) + link_elems = mp.link_formulations[link] + link_vars = mp.link_vars[link] + for (elemid, elem) in ds.link[link] + create_vars!(elem, link, elemid, link_elems[elemid], link_vars, scenario) + end + end + + # 2. Création des bilans + pb_opt = Problem() + for (node, vars) in mp.node_vars + for (elem, var) in vars + add_variable!(pb_opt, var) + end + end + + for (link, vars) in mp.link_vars + for (elem, var) in vars + add_variable!(pb_opt, var) + end + end + + for (busid, bus_elems) in ds.bus + bus_elems_formulations = mp.node_formulations[busid] + bus_elems_var = mp.node_vars[busid] + + cstrnames, Snode, lb, ub = get_Snodal(busid, bus_elems, bus_elems_formulations, bus_elems_var) + S_balance = Snode + get_Slinks_in(busid, ds, gs, mp) + get_Slinks_out(busid, ds, gs, mp) + + add_constraint!(pb_opt, cstrname_nodal_balance(cstrnames, scenario, busid), lb << S_balance << ub) + end + + + ## 3. Création des contraintes + for bus in get_buses(OPFpbs, scenario) + bus_elems_formulations = mp.node_formulations[bus] + bus_vars = mp.node_vars[bus] + for (elemid, elem) in ds.bus[bus] + elem_formulation = bus_elems_formulations[elemid] + constraints = constraint(elem, bus, elemid, elem_formulation, bus_vars, scenario, OPFpbs) + for (cstrname, cstr) in constraints + add_constraint!(pb_opt, get_cstrname(scenario, bus, elemid, cstrname), cstr) + end + end + end + + for link in get_links(OPFpbs, scenario) + link_elems_formulations = mp.link_formulations[link] + link_vars = mp.link_vars[link] + for (elemid, elem) in ds.link[link] + elem_formulation = link_elems_formulations[elemid] + constraints = constraint(elem, link, elemid, elem_formulation, link_vars, scenario, OPFpbs) + for (cstrname, cstr) in constraints + add_constraint!(pb_opt, get_cstrname(scenario, link, elemid, cstrname), cstr) + end + end + end + + ## 4. Création de l'objectif + objective = Polynomial() + for bus in get_buses(OPFpbs, scenario) + bus_elems = ds.bus[bus] + bus_elems_formulations = mp.node_formulations[bus] + bus_elems_var = mp.node_vars[bus] + + _, Snode, lb, ub = get_Snodal(bus, bus_elems, bus_elems_formulations, bus_elems_var) + add!(Snode, get_Slinks_in(bus, ds, gs, mp) + get_Slinks_out(bus, ds, gs, mp)) + + for (elemid, elem) in bus_elems + gencost::Polynomial = cost(elem, bus, elemid, bus_elems_formulations[elemid], bus_elems_var, Snode, lb, ub) + add!(objective, gencost) + end + end + + set_objective!(pb_opt, objective) + return pb_opt +end + + +""" + get_Snodal(bus::String, bus_elems::Dict{String, Any}, bus_elems_formulations::Dict{String, Symbol}, bus_elems_var::Dict{String, Variable}) + +Return the total injected power at `bus` coming from nodal elements with bounds. The injected power for each specific element is computed using `Snodal`. + +# Arguments +- `bus::String` : +- `bus_elems::Dict{String,Any}`: +- `bus_elems_formulations` : +- `bus_elems_var` : + +# Output + + +#Examples +``` +Instance Matpower WB2 with two nodes +julia > get_Snodal("BUS_1", ) +``` +""" +function get_Snodal(bus::String, bus_elems::Dict{String, Any}, bus_elems_formulations::Dict{String, Symbol}, bus_elems_var::Dict{String, Variable}) + cstrnames, Sres, lbres, ubres = [Set{String}(), Polynomial(), 0, 0] + for (elemid, element) in bus_elems + elem_formulation = bus_elems_formulations[elemid] + cstrname_, Sres_, lbres_, ubres_ = Snodal(element, bus, elemid, elem_formulation, bus_elems_var) + Sres, lbres, ubres = [Sres, lbres, ubres] + [Sres_, lbres_, ubres_] + push!(cstrnames, cstrname_) + end + + # Remove all constraints names equal to "" + dense_cstrnames = Set{String}() + for cstr in cstrnames + if cstr != "" + push!(dense_cstrnames, cstr) + end + end + return [dense_cstrnames, Sres, lbres, ubres] +end + +function get_Slinks_in(bus, ds::DataSource, gs::GridStructure, mp::MathematicalProgramming) + Sres = Polynomial() + for link in get_links_in(bus, gs) + link_elems_formulation = mp.link_formulations[link] + link_vars = mp.link_vars[link] + for (elemid, elem) in ds.link[link] + elem_formulation = link_elems_formulation[elemid] + add!(Sres, Sdest(elem, link, elemid, elem_formulation, link_vars)) + end + end + return Sres +end + +function get_Slinks_out(bus, ds::DataSource, gs::GridStructure, mp::MathematicalProgramming) + Sres = Polynomial() + for link in get_links_out(bus, gs) + link_elems_formulations = mp.link_formulations[link] + link_vars = mp.link_vars[link] + for (elemid, elem) in ds.link[link] + elem_formulation = link_elems_formulations[elemid] + add!(Sres, Sorig(elem, link, elemid, elem_formulation, link_vars)) + end + end + return Sres +end diff --git a/src_PowSysMod/build_globalpb.jl b/src_PowSysMod/build_globalpb.jl new file mode 100644 index 0000000..af41a14 --- /dev/null +++ b/src_PowSysMod/build_globalpb.jl @@ -0,0 +1,40 @@ +""" + build_globalpb!(OPFpbs) + +Return a polynomial problem in complex variables with polynomial constraints combining all scenarios contained in `OPFpbs` + +# Arguments +- `OPFpbs` : dictionary of scenario (string) + +# Output +- `pb_opt` : a polynomial optimization problem + +# Example +``` +julia > OPFpbs = build_OPFproblems(MatpowerInput, "instances\\matpower\\WB2.m") +julia > pb = build_globalpb!(OPFpbs) +``` +""" + +function build_globalpb!(OPFpbs) + constraints = Dict{String,Dict{String,Constraint}}() + variables = Dict{String,Dict{String, Type}}() + pb_global = Problem() + pb_global = build_Problem!(OPFpbs, basecase_scenario_name()) + for scenario in setdiff(collect(keys(OPFpbs)), [basecase_scenario_name()]) + pb_scenario = build_Problem!(OPFpbs, scenario) + constraints[scenario] = pb_scenario.constraints + variables[scenario] = pb_scenario.variables + end + for (scenario, vars) in variables + for var in vars + add_variable!(pb_global, var) + end + end + for (scenario, constraints) in constraints + for (ctr_name, ctr) in constraints + add_constraint!(pb_global, ctr_name, ctr) + end + end + return pb_global +end diff --git a/src_PowSysMod/check_feasibility.jl b/src_PowSysMod/check_feasibility.jl new file mode 100644 index 0000000..7fd2514 --- /dev/null +++ b/src_PowSysMod/check_feasibility.jl @@ -0,0 +1,23 @@ +""" + check_feasibility(elem::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point) where T<:AbstractNodeLabel + +Evaluate the constraints defined by `elemid` of type `elem` at `bus` for `scenario` (depending on `elem_formulation`) on `point` and return a dictionary of infeasible constraints Dict(ctrname => message with information) +Return nothing if there is not violated constraints. + +""" +function check_feasibility(elem::T, bus::String, elemid::String, elem_formulation::Symbol, scenario::String, point::Point,epsilon::Float64) where T<:AbstractNodeLabel + return +end + + +""" + check_feasibility(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, point::Point, epsilon::Float64) where T<:AbstractLinkLabel + +Evaluate the constraints defined by `elemid` of type `elem` at `link` for `scenario` (depending on `elem_formulation`) on `point` and return a dictionary of infeasible constraints Dict(ctrname => message with information) +Return nothing if there is not violated constraints. + +""" + +function check_feasibility(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, point::Point, epsilon::Float64) where T<:AbstractLinkLabel + return +end diff --git a/src_PowSysMod/default_behaviour.jl b/src_PowSysMod/default_behaviour.jl new file mode 100644 index 0000000..72ff926 --- /dev/null +++ b/src_PowSysMod/default_behaviour.jl @@ -0,0 +1,41 @@ +# Default functions + +## 1. Variables creation +function create_vars!(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String) where T + return +end + +function create_vars!(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String) where T + return +end + +## 2. Power balance +function Snodal(element::T, busid::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}) where T + return ["", Polynomial(), 0, 0] +end + +function Sorig(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T + return Polynomial() +end + +function Sdest(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}) where T + return Polynomial() +end + +## 3. Constraints creation +function constraint(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T + return Dict{String, Constraint}() +end + +function constraint(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{String, Variable}, scenario::String, OPFpbs::OPFProblems) where T + return Dict{String, Constraint}() +end + +## 4. Element cost +function cost(element::T, bus::String, elemid::String, elem_formulation::Symbol, bus_vars::Dict{String, Variable}, Snode::Polynomial, lb, ub) where T + return 0 +end + +function cost(element::T, link::Link, elemid::String, elem_formulation::Symbol, link_vars::Dict{Link, Variable}, Snode::Polynomial, lb, ub) where T + return 0 +end diff --git a/src_PowSysMod/load_OPFproblems.jl b/src_PowSysMod/load_OPFproblems.jl new file mode 100644 index 0000000..12e4648 --- /dev/null +++ b/src_PowSysMod/load_OPFproblems.jl @@ -0,0 +1,66 @@ +""" + load_OPFproblems(input_type, instance_path::String) + +Read provided files in `instance_path` function of `input_type` and create a structure ds of type DataSource for each scenario. Also create empty structures gs and mp for each scenario +For each scenario, treat data in ds to complete gs : from bus and links in ds, generate node_linksin and node_linksout +For each scenario, complete mp structure +Return a dictonary Scenario => Scenario structure + + +# Arguments +- `input_type::T` : type of entry format for example MatpowerInput, GOCInput +- `instance_path::String` : path to the instance to treat, provided the instance path must be associated to an instance of format input_type + +# Output +- `OPFpbs` + + +# Examples +``` +julia > OPFpbs = load_OPFproblems(MatpowerInput, "instances\\matpower\\WB2.m") +julia > OPFpbs +Dict{String,Scenario} with 1 entry: + "BaseCase" => Scenario +``` +""" + +function load_OPFproblems(input_type, instance_path::String) + OPFpbs = read_input(input_type, instance_path) + # baseMVA, bus, link, node_elems, link_elems, elem_to_typekey = read_input(input_type, filenames) + # ds = DataSource(baseMVA, bus, link) + + for scenario in keys(OPFpbs) + node_linksin, node_linksout = Dict{String, Set{Link}}(), Dict{String, Set{Link}}() + + for (linkname, _) in OPFpbs[scenario].ds.link + if !haskey(node_linksin, linkname.dest) + node_linksin[linkname.dest] = Set{Link}() + end + union!(node_linksin[linkname.dest], [linkname]) + + if !haskey(node_linksout, linkname.orig) + node_linksout[linkname.orig] = Set{Link}() + end + union!(node_linksout[linkname.orig], [linkname]) + end + + OPFpbs[scenario].gs.node_linksin = node_linksin + OPFpbs[scenario].gs.node_linksout = node_linksout + + node_vars=Dict{String, Dict{String, Variable}}(busname => Dict{String, Variable}() for busname in keys(OPFpbs[scenario].ds.bus)) + link_vars=Dict{Link, Dict{String, Variable}}(link => Dict{String, Variable}() for link in keys(OPFpbs[scenario].ds.link)) + + node_formulations = Dict{String, Dict{String, Symbol}}() + link_formulations = Dict{Link, Dict{String, Symbol}}() + for bus in keys(OPFpbs[scenario].ds.bus) + node_formulations[bus] = Dict{String, Symbol}(elem => :NbMinVar for elem in keys(OPFpbs[scenario].ds.bus[bus])) + end + for link in keys(OPFpbs[scenario].ds.link) + link_formulations[link] = Dict{String, Symbol}(elem => :NbMinVar for elem in keys(OPFpbs[scenario].ds.link[link])) + end + + OPFpbs[scenario].mp = MathematicalProgramming(node_formulations, link_formulations, node_vars, link_vars) + end + + return OPFpbs +end diff --git a/src_PowSysMod/naming_conventions.jl b/src_PowSysMod/naming_conventions.jl new file mode 100644 index 0000000..622b6fa --- /dev/null +++ b/src_PowSysMod/naming_conventions.jl @@ -0,0 +1,94 @@ +""" + File containing all the conventions used to name objects. +""" +bus_name(id_bus::Int64) = "BUS_$id_bus" +get_busid(busname::String) = busname[5:end] +basecase_scenario_name() = "BaseCase" + +function variable_name(varname::String, bus::String, elemid::String, scenario::String) + if elemid == "" + return "$(scenario)_$(get_busid(bus))_$(varname)" + else + return "$(scenario)_$(get_busid(bus))_$(elemid)_$(varname)" + end +end + +function variable_name(varname::String, link::Link, elemid::String, scenario::String) + if elemid == "" + return "$(scenario)_$(get_busid(link.orig))-$(get_busid(link.dest))_$(varname)" + else + return "$(scenario)_$(get_busid(link.orig))-$(get_busid(link.dest))_$(elemid)_$(varname)" + end +end + +function cstrname_nodal_balance(cstrnames::Set{String}, scenario::String, bus::String) + cstr = "" + for cstrname in sort(collect(cstrnames)) + cstr = "$(cstr)_$cstrname" + end + + if cstr == "" + cstr = "_LOAD" + end + + return "$(scenario)_$(get_busid(bus))_BALANCE$(cstr)" +end + +function get_cstrname(scenario::String, bus::String, elemid::String, cstrname::String) + if elemid == "" + return "$(scenario)_$(get_busid(bus))_$(cstrname)" + else + return "$(scenario)_$(get_busid(bus))_$(elemid)_$(cstrname)" + end +end + +function get_cstrname(scenario::String, link::Link, elemid::String, cstrname::String) + if elemid == "" + return "$(scenario)_$(get_busid(link.orig))-$(get_busid(link.dest))_$(cstrname)" + else + return "$(scenario)_$(get_busid(link.orig))-$(get_busid(link.dest))_$(elemid)_$(cstrname)" + end +end + + +### GOC conventions +scenarioname(id_contingency::Int) = "Scen$id_contingency" + +# nodal element names +volt_name() = "Volt" +generator_name(gen_id) = "Gen$gen_id" +load_name(id_load) = "Load$id_load" +shunt_name(id_shunt) = "Shunt$id_shunt" + +get_delta_varname(scenario::String) = "$(scenario)_Delta" +get_binInf_varname(scenario1::String, scenario2::String, bus::String) = "BinVolt_$(get_busid(bus))_$(scenario1)_inf_$(scenario2)" + +# constraint names +get_VoltM_cstrname() = "VOLTM" +get_ShuntDef_cstrname() = "ShuntDef" +get_LoadDef_cstrname() = "LoadDef" +get_GenBounds_cstrname() = "GenBounds" + + +get_VoltBinDef_upper() = "BinDef_upper" +get_VoltBinDef_lower() = "BinDef_lower" +get_VoltBinDef_complement() = "BinDef_compl" + +get_CC_active_cstrname() = "CC_Pgen" +get_CC_reactiveupper_cstrname() = "CC_Qgen_upper" +get_CC_reactivelower_cstrname() = "CC_Qgen_lower" + +get_Smax_orig_cstrname() = "Smax_orig" +get_Smax_dest_cstrname() = "Smax_dest" + + +get_GOC_Volt_ϵ() = 1e-3 +get_GOC_BigM() = 1e1 + +##node elems + +##link elems +nullimpedance_notransformer_name(branch_id) = "NullImp_notransfo_$branch_id" +linepi_withtransformer_name(branch_id) = "Lineπ_transfo_$branch_id" +nullimpedance_withtransformer_name(branch_id) = "NullImp_transfo_$branch_id" +linepi_notransformer_name(branch_id) = "Lineπ_notransfo_$branch_id" diff --git a/src_PowSysMod/utils_plots.jl b/src_PowSysMod/utils_plots.jl new file mode 100644 index 0000000..a8f44ae --- /dev/null +++ b/src_PowSysMod/utils_plots.jl @@ -0,0 +1,120 @@ +function get_scen_vars(pt::Point, scenario::String) + a = Iterators.filter(x->contains(x.name, scenario), keys(pt)) + return Point(Dict([k=>pt[k] for k in a])) +end + +function get_volt_vars(pt::Point) + a = Iterators.filter(x->contains(x.name, "VOLT"), keys(pt)) + return Point(Dict([k=>pt[k] for k in a])) +end + +function get_bin_vars(pt::Point) + a = Iterators.filter(x->contains(x.name, "Bin"), keys(pt)) + return Point(Dict([k=>pt[k] for k in a])) +end + +function get_prod_vars(pt::Point) + a = Iterators.filter(x->contains(x.name, "Sgen"), keys(pt)) + return Point(Dict([k=>pt[k] for k in a])) +end + +function get_delta_var(pt::Point, scenario::String) + a = Iterators.filter(x->contains(x.name, get_delta_varname(scenario)), keys(pt)) + return Point(Dict([k=>pt[k] for k in a])) +end + +function get_splitted_Cpt(pt, scenario) + vars = get_scen_vars(pt, scenario) + volt_vars = real2cplx(get_volt_vars(vars)) + bin_vars = get_bin_vars(vars) + prod_vars = real2cplx(get_prod_vars(vars)) + + return volt_vars, bin_vars, prod_vars +end + + +################### +## Plot functions +################### + +function plot_Volt_vars(point, scenarios) + r1 = 0.9 + plot(r1*cos.(-π:0.01:π), r1*sin.(-π:0.01:π), label=:Vmin) + r2 = 1.1 + plot!(r2*cos.(-π:0.01:π), r2*sin.(-π:0.01:π), label=:Vmax) + for sc in scenarios + println(sc) + volt_vars, _, _ = get_splitted_Cpt(point, sc) + ptsV = Array{Complex}(collect(values(volt_vars))) + scatter!(real(ptsV), imag(ptsV), lab=sc) + end + gui() +end + + + +function plot_Sgen_vars(pt_global, scenarios) + fig = plot() + pt = real2cplx(get_prod_vars(pt_global)) + Sgens = Dict() + # Get prod by scenarios + for scenario in scenarios + _, _, bc_prod_vars = get_splitted_Cpt(pt_knitro, "BaseCase") + Sgens[scenario] = bc_prod_vars + end + + # One color per bus/generator + init_scen = sort(collect(keys(Sgens)))[1] + i=1 + cols = Dict() + for (varsc, valsc) in Sgens[init_scen] + scenario, busid, genname, varname = split(varsc.name, "_") + cols[(busid, genname)] = i + i+=1 + end + + # Loop on generators + for (varsc, valsc) in Sgens[init_scen] + base_scenario, busid, genname, varname = split(varsc.name, "_") + println("$base_scenario, $busid, $genname, $varname") + + gen = OPFpbs[base_scenario].ds.bus[bus_name(parse(busid))][genname] + Smin, Smax = gen.power_min, gen.power_max + domain = [ Smin, real(Smin)+im*imag(Smax), Smax, real(Smax)+im*imag(Smin), Smin] + plot!(real(domain), imag(domain), color=cols[(busid, genname)], label="Cstr_$(busid)_$genname") + + for scenario in setdiff(scenarios, init_scen) + gen_cur = OPFpbs[scenario].ds.bus[bus_name(parse(busid))][genname] + Smin_cur, Smax_cur = gen_cur.power_min, gen_cur.power_max + (Smin == Smin_cur) && (Smax == Smax_cur) || warn("bus $busid, gen $genname: between $base_scenario and $scenario, different bounds($Smin, $Smax vs. $Smin_cur, $Smax_cur)") + + var = Variable(variable_name("Sgen", bus_name(parse(busid)), String(genname), scenario), Complex) + scatter!([pt[var]], color=cols[(busid, genname)], label=var.name) + end + end + plot!(xlabel="P gen", ylabel = "Q gen") + return fig +end + + +function plot_ViVj_vars(pt, scenarios) + r1 = 0.9^2 + plt = plot(r1*cos.(-π:0.01:π), r1*sin.(-π:0.01:π), label=:Vmin2) + r2 = 1.1^2 + plot!(r2*cos.(-π:0.01:π), r2*sin.(-π:0.01:π), label=:Vmax2) + + cols = Dict() + i = 1 + for scen in scenarios + cols[scen] = i + i+=1 + end + + for scen in scenarios + volt_vars, _, _ = get_splitted_Cpt(pt_knitro, scen) + V = Vector{Complex}([val for (var, val) in volt_vars]) + ViVj = collect(Set(V*V')) + scatter!(ViVj, label=scen, color=cols[scen]) + end + return plt +end diff --git a/src_ampl/Knitro_sol.csv b/src_ampl/Knitro_sol.csv new file mode 100644 index 0000000..7ee5d2e --- /dev/null +++ b/src_ampl/Knitro_sol.csv @@ -0,0 +1,38 @@ +Scenario1_VOLT_BUS-1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-7;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-13;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-11;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-10;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-1_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-6_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-9;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-3_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-5;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-14;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-2_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-3;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-2;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-12;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-6;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-8;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-8_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-4;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-1_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-2;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-2_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-3;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-3_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-4;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-5;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-6_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-6;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-7;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-8;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-8_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-9;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-10;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-11;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-12;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-13;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-14;0.50000000000000000000;0.50000000000000000000 diff --git a/src_ampl/Knitro_sol.txt b/src_ampl/Knitro_sol.txt new file mode 100644 index 0000000..7ee5d2e --- /dev/null +++ b/src_ampl/Knitro_sol.txt @@ -0,0 +1,38 @@ +Scenario1_VOLT_BUS-1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-7;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-13;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-11;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-10;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-1_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-6_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-9;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-3_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-5;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-14;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-2_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-3;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-2;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-12;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-6;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-8;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-8_Generator_1;0.50000000000000000000;0.50000000000000000000 +Scenario1_VOLT_BUS-4;0.50000000000000000000;0.50000000000000000000 +Scenario1_Sgen_BUS-1_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-2;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-2_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-3;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-3_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-4;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-5;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-6_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-6;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-7;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-8;0.50000000000000000000;0.50000000000000000000 +BaseCase_Sgen_BUS-8_Generator_1;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-9;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-10;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-11;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-12;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-13;0.50000000000000000000;0.50000000000000000000 +BaseCase_VOLT_BUS-14;0.50000000000000000000;0.50000000000000000000 diff --git a/src_ampl/minlp.mod b/src_ampl/minlp.mod new file mode 100644 index 0000000..1749ceb --- /dev/null +++ b/src_ampl/minlp.mod @@ -0,0 +1,178 @@ + + +set KEYS dimen 4; + +param NONE symbolic := 'NONE'; +param VAR_TYPE symbolic := 'VAR_TYPE'; +param QUAD symbolic := 'QUAD'; +param LIN symbolic := 'LIN'; +param OBJ symbolic := 'OBJ'; +param LB symbolic := 'LB'; +param UB symbolic := 'UB'; +param CONST symbolic := 'CONST'; + +param RIGHT{KEYS} symbolic; +param LEFT {KEYS} symbolic; + +set SOL dimen 4; +param SOL_REAL{SOL}; +param SOL_IMAG{SOL}; + +set KEYS_NO_NONE_34 := {(key1, key2, key3, key4) in KEYS : key3 != NONE and key4 != NONE}; + +set VARIABLES := setof{(VAR_TYPE, kind, name, NONE) in KEYS}name; + +set VARIABLES_ORDERED ordered by ASCII := VARIABLES; +set TRIMMER := {v1 in VARIABLES_ORDERED, v2 in VARIABLES_ORDERED: ord(v1) <= ord(v2)}; + +set CONSTRAINTS := + setof{(LB, name, NONE, NONE) in KEYS}name + union + setof{(UB, name, NONE, NONE) in KEYS}name; + +param CTR_LB_LEFT {name in CONSTRAINTS} := 0 + sum{(LB, name, none1, none2) in KEYS}LEFT [LB, name, none1, none2]; +param CTR_LB_RIGHT{name in CONSTRAINTS} := 0 + sum{(LB, name, none1, none2) in KEYS}RIGHT[LB, name, none1, none2]; +param CTR_UB_LEFT {name in CONSTRAINTS} := 0 + sum{(UB, name, none1, none2) in KEYS}LEFT [UB, name, none1, none2]; +param CTR_UB_RIGHT{name in CONSTRAINTS} := 0 + sum{(UB, name, none1, none2) in KEYS}RIGHT[UB, name, none1, none2]; + +set CONSTRAINTS_LB := setof{(LB, name, NONE, NONE) in KEYS}name; +set CONSTRAINTS_UB := setof{(UB, name, NONE, NONE) in KEYS}name; + + +set VAR_REAL_IMAG := setof{name in VARIABLES }(name, name&'_real', name&'_imag'); +set OBJCTR_REAL_IMAG := setof{name in CONSTRAINTS union {OBJ}}(name, name&'_real', name&'_imag'); + +set REAL_VARIABLES := union{name in VARIABLES, (name, real, imag) in VAR_REAL_IMAG} {real, imag}; +set REAL_CONSTRAINTS := union{name in CONSTRAINTS, (name, real, imag) in OBJCTR_REAL_IMAG}{real}; +set IMAG_CONSTRAINTS := union{name in CONSTRAINTS, (name, real, imag) in OBJCTR_REAL_IMAG}{imag}; + +# (a_jk+i.b_jk)(x_j-i.y_j)(x_k+i.y_k) +# = (a_jk+i.b_jk)([x_j.x_k+y_j.y_k]+i[x_j.y_k-y_j.x_k]) +# = a_jk(x_j.x_k+y_j.y_k)-b_jk(x_j.y_k-y_j.x_k) + i [b_jk(x_j.x_k+y_j.y_k)+a_jk(x_j.y_k-y_j.x_k) ] +# = +# + a_jk.x_j.x_k +# + a_jk.y_j.y_k +# - b_jk.x_j.y_k +# + b_jk.y_j.x_k +# + i [ +# + b_jk.x_j.x_k +# + b_jk.y_j.y_k +# + a_jk.x_j.y_k +# - a_jk.y_j.x_k +# ] +set REAL_INDEXES := + # real part + # + a_jk.x_j.x_k + # + a_jk.y_j.y_k + # - b_jk.x_j.y_k + # + b_jk.y_j.x_k + union{ + (QUAD, objctr, var1, var2) in KEYS_NO_NONE_34, + (objctr, objctr_real, objctr_imag) in OBJCTR_REAL_IMAG, + (var1, var1_real, var1_imag) in VAR_REAL_IMAG, + (var2, var2_real, var2_imag) in VAR_REAL_IMAG + }( + {(objctr_real, var1_real, var2_real)} + union + {(objctr_real, var1_imag, var2_imag)} + union + {(objctr_real, var1_real, var2_imag)} + union + {(objctr_real, var1_imag, var2_real)} + ) + union + # imag part + # + b_jk.x_j.x_k + # + b_jk.y_j.y_k + # + a_jk.x_j.y_k + # - a_jk.y_j.x_k + union{ + (QUAD, objctr, var1, var2) in KEYS_NO_NONE_34, + (objctr, objctr_real, objctr_imag) in OBJCTR_REAL_IMAG, + (var1, var1_real, var1_imag) in VAR_REAL_IMAG, + (var2, var2_real, var2_imag) in VAR_REAL_IMAG + }( + {(objctr_real, var1_real, var2_real)} + union + {(objctr_real, var1_imag, var2_imag)} + union + {(objctr_real, var1_real, var2_imag)} + union + {(objctr_real, var1_imag, var2_real)} + ) +; + +set REAL_VARIABLES_ORDERED ordered by ASCII := REAL_VARIABLES; + +set REAL_TRIMMER := {v1 in REAL_VARIABLES_ORDERED, v2 in REAL_VARIABLES_ORDERED: ord(v1) <= ord(v2)}; + +var x{REAL_VARIABLES}; + +# real part +# + a_jk.x_j.x_k +# + a_jk.y_j.y_k +# - b_jk.x_j.y_k +# + b_jk.y_j.x_k +minimize CRITERION: ++sum{(QUAD, OBJ, var1, var2) in KEYS, (var1, var1_real, var1_imag) in VAR_REAL_IMAG, (var2, var2_real, var2_imag) in VAR_REAL_IMAG}( + +LEFT [QUAD, OBJ, var1, var2] * (if (var1_real, var2_real) in REAL_TRIMMER then x[var1_real] * x[var2_real] else x[var2_real]* x[var1_real]) + +LEFT [QUAD, OBJ, var1, var2] * (if (var1_imag, var2_imag) in REAL_TRIMMER then x[var1_imag] * x[var2_imag] else x[var2_imag]* x[var1_imag]) + -RIGHT[QUAD, OBJ, var1, var2] * (if (var1_real, var2_imag) in REAL_TRIMMER then x[var1_real] * x[var2_imag] else x[var2_imag]* x[var1_real]) + +RIGHT[QUAD, OBJ, var1, var2] * (if (var2_imag, var2_real) in REAL_TRIMMER then x[var1_imag] * x[var2_real] else x[var2_real]* x[var1_imag]) + ) + +(if (CONST, OBJ, NONE, NONE) in KEYS then LEFT[CONST, OBJ, NONE, NONE] else 0) + ; + +subject to constraint_real{ctr in CONSTRAINTS, (ctr, ctr_real, ctr_imag) in OBJCTR_REAL_IMAG}: + +(if ctr in CONSTRAINTS_LB then LEFT[LB, ctr, NONE, NONE] else -Infinity) + <= + +sum{(QUAD, ctr, var1, var2) in KEYS, (var1, var1_real, var1_imag) in VAR_REAL_IMAG, (var2, var2_real, var2_imag) in VAR_REAL_IMAG}( + +LEFT [QUAD, ctr, var1, var2] * (if (var1_real, var2_real) in REAL_TRIMMER then x[var1_real] * x[var2_real] else x[var2_real]* x[var1_real]) + +LEFT [QUAD, ctr, var1, var2] * (if (var1_imag, var2_imag) in REAL_TRIMMER then x[var1_imag] * x[var2_imag] else x[var2_imag]* x[var1_imag]) + -RIGHT[QUAD, ctr, var1, var2] * (if (var1_real, var2_imag) in REAL_TRIMMER then x[var1_real] * x[var2_imag] else x[var2_imag]* x[var1_real]) + +RIGHT[QUAD, ctr, var1, var2] * (if (var2_imag, var2_real) in REAL_TRIMMER then x[var1_imag] * x[var2_real] else x[var2_real]* x[var1_imag]) + ) + +(if (CONST, ctr, NONE, NONE) in KEYS then LEFT[CONST, ctr, NONE, NONE] else 0) + <= + +(if ctr in CONSTRAINTS_UB then LEFT[UB, ctr, NONE, NONE] else +Infinity) + ; +# imag part +# + b_jk.x_j.x_k +# + b_jk.y_j.y_k +# + a_jk.x_j.y_k +# - a_jk.y_j.x_k +subject to constraint_imag{ctr in CONSTRAINTS, (ctr, ctr_real, ctr_imag) in OBJCTR_REAL_IMAG}: + +(if ctr in CONSTRAINTS_LB then RIGHT[LB, ctr, NONE, NONE] else -Infinity) + <= + +sum{(QUAD, ctr, var1, var2) in KEYS, (var1, var1_real, var1_imag) in VAR_REAL_IMAG, (var2, var2_real, var2_imag) in VAR_REAL_IMAG}( + +RIGHT[QUAD, ctr, var1, var2] * (if (var1_real, var2_real) in REAL_TRIMMER then x[var1_real] * x[var2_real] else x[var2_real] * x[var1_real]) + +RIGHT[QUAD, ctr, var1, var2] * (if (var1_imag, var2_imag) in REAL_TRIMMER then x[var1_imag] * x[var2_imag] else x[var2_imag] * x[var1_imag]) + +LEFT [QUAD, ctr, var1, var2] * (if (var1_real, var2_imag) in REAL_TRIMMER then x[var1_real] * x[var2_imag] else x[var2_imag] * x[var1_real]) + -LEFT [QUAD, ctr, var1, var2] * (if (var1_imag, var2_real) in REAL_TRIMMER then x[var1_imag] * x[var2_real] else x[var2_real] * x[var1_imag]) + ) + +(if (CONST, ctr, NONE, NONE) in KEYS then RIGHT[CONST, ctr, NONE, NONE] else 0) + <= + +(if ctr in CONSTRAINTS_UB then RIGHT[UB, ctr, NONE, NONE] else +Infinity) + ; + +param DUAL_REAL{ctr in CONSTRAINTS} default 0; +param DUAL_IMAG{ctr in CONSTRAINTS} default 0; + + +set H_INDEX dimen 2 := union{(QUAD, objctr, var1, var2) in KEYS}{(var1, var2)}; + +param H_REAL{(var1, var2) in H_INDEX} := + +(if (QUAD, OBJ, var1, var2) in KEYS then LEFT [QUAD, OBJ, var1, var2] else 0) + +sum{(QUAD, ctr, var1, var2) in KEYS: ctr != OBJ}-( + DUAL_REAL[ctr]*LEFT[QUAD, ctr, var1, var2]-DUAL_IMAG[ctr]*RIGHT[QUAD, ctr, var1, var2] + ) + +; + +param H_IMAG{(var1, var2) in H_INDEX} := + +(if (QUAD, OBJ, var1, var2) in KEYS then RIGHT[QUAD, OBJ, var1, var2] else 0) + +sum{(QUAD, ctr, var1, var2) in KEYS: ctr != OBJ}-( + DUAL_REAL[ctr]*RIGHT[QUAD, ctr, var1, var2]+DUAL_IMAG[ctr]*LEFT[QUAD, ctr, var1, var2] + ) + +; \ No newline at end of file diff --git a/src_ampl/minlp.run b/src_ampl/minlp.run new file mode 100644 index 0000000..4ba2418 --- /dev/null +++ b/src_ampl/minlp.run @@ -0,0 +1,53 @@ +reset; +suffix xfeastol IN; +suffix cfeastol IN; +model minlp.mod; + +data minlp.dat; + +option solver knitroampl; +option knitro_options 'outlev=3 maxit=300 scale=0 feastol=1 opttol=1 feastolabs=1e-8 opttolabs=1e-6'; + +#let{(name, name_real, name_imag) in VAR_REAL_IMAG, (VAR_TYPE, key, name, NONE) in SOL} x[name_real] := SOL_REAL[VAR_TYPE, key, name, NONE]; +#let{(name, name_real, name_imag) in VAR_REAL_IMAG, (VAR_TYPE, key, name, NONE) in SOL} x[name_imag] := SOL_IMAG[VAR_TYPE, key, name, NONE]; +#display x; +#printf{id in 1.._ncons: _con[id].slack < -1e-6}"%-80s%15.6E%15.6E%15.6E%15.6E\n", _conname[id], _con[id].slack, _con[id].body, _con[id].lb, _con[id].ub; +#printf"Number of violated constraints : %10d\n", card({id in 1.._ncons: _con[id].slack < -1e-6}); + +let{name in REAL_VARIABLES} x[name] := 1.5; + +solve; + + +#solve; +#display _conname, _con, _con.lb, _con.body, _con.ub; + +let{ctr in CONSTRAINTS, (ctr, ctr_real, ctr_imag) in OBJCTR_REAL_IMAG} DUAL_REAL[ctr] := constraint_real[ctr, ctr_real, ctr_imag]; +let{ctr in CONSTRAINTS, (ctr, ctr_real, ctr_imag) in OBJCTR_REAL_IMAG} DUAL_IMAG[ctr] := constraint_imag[ctr, ctr_real, ctr_imag]; + +#close hessian.sol; +#for{(v1,v2) in TRIMMER}{ +# if (v1,v2) in H_INDEX then { +# printf "%20s%20s%15.6E%15.6E\n", v1, v2, H_REAL[v1, v2], H_IMAG[v1, v2] > hessian.sol; +# } +#}; +#close hessian.sol; + + +option solution_precision 0; +option display_precision 0; + +#printf{name in VARIABLES, (name, name_real, name_imag) in VAR_REAL_IMAG} "%15s %.20f %.20f\n", name, x[name_real], x[name_imag]; +#printf "\n"; +#printf{name in VARIABLES, (name, name_real, name_imag) in VAR_REAL_IMAG} "%15s;%.20f;%.20f\n", name, x[name_real], x[name_imag] > Knitro_sol.txt; +#printf{name in VARIABLES, (name, name_real, name_imag) in VAR_REAL_IMAG} "%15s;%.20f;%.20f\n", name, x[name_real], x[name_imag] > Knitro_sol.csv; +#display constraint_real.body, constraint_imag.body; + +printf{ctr in CONSTRAINTS, (ctr, ctr_real, ctr_imag) in OBJCTR_REAL_IMAG} + "%15s%15s%10.3f%15s%10.3f\n" + , ctr + , ctr_real + , constraint_real[ctr, ctr_real, ctr_imag].body + , ctr_imag + , constraint_imag[ctr, ctr_real, ctr_imag].body + ; diff --git a/src_ampl/real_minlp.mod b/src_ampl/real_minlp.mod new file mode 100644 index 0000000..aef26ac --- /dev/null +++ b/src_ampl/real_minlp.mod @@ -0,0 +1,118 @@ +param NONE symbolic := 'NONE'; +param VAR_TYPE symbolic := 'VAR_TYPE'; +param MONO_DEF symbolic := 'MONO_DEF'; +param MONO symbolic := 'MONO'; +param QUAD symbolic := 'QUAD'; +param LIN symbolic := 'LIN'; +param CONST symbolic := 'CONST'; +param OBJ symbolic := 'OBJ'; +param LB symbolic := 'LB'; +param UB symbolic := 'UB'; + +/* Variable types */ +param REAL symbolic := 'REAL'; +param BOOL symbolic := 'BOOL'; + +/* Preconditioning types */ +param SQRT symbolic := 'SQRT'; + + +/* Problem description */ +set KEYS dimen 4; +param RIGHT{KEYS} symbolic; +param LEFT {KEYS} symbolic; + +/* Preconditioning of some constraints */ +set PRECOND_CONSTRAINTS dimen 2; + + + +set REAL_VARIABLES := setof{(VAR_TYPE, REAL, name, NONE) in KEYS}name; +set BINARY_VARIABLES := setof{(VAR_TYPE, BOOL, name, NONE) in KEYS}name; + +var x{REAL_VARIABLES}; +var y{BINARY_VARIABLES} binary; + + + +set PRECOND_CSTRS := setof{(cstrname, any) in PRECOND_CONSTRAINTS}cstrname; +set SQRT_PRECOND_CSTR := setof{(cstrname, SQRT) in PRECOND_CONSTRAINTS}cstrname; + +set CONSTRAINTS := + setof{(LB, name, NONE, NONE) in KEYS}name + union + setof{(UB, name, NONE, NONE) in KEYS}name; + +set CONSTRAINTS_LB := setof{(LB, name, NONE, NONE) in KEYS}name; +set CONSTRAINTS_UB := setof{(UB, name, NONE, NONE) in KEYS}name; + + + +minimize CRITERION: + +sum{(MONO, OBJ, monname, NONE) in KEYS}( + +LEFT [MONO, OBJ, monname, NONE] * prod{(MONO_DEF, monname, var0, NONE) in KEYS}( + (if var0 in REAL_VARIABLES then x[var0] else y[var0])^LEFT [MONO_DEF, monname, var0, NONE] + ) + ) + +sum{(QUAD, OBJ, var1, var2) in KEYS}( + +LEFT [QUAD, OBJ, var1, var2] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) * (if var2 in REAL_VARIABLES then x[var2] else y[var2]) + ) + +sum{(LIN, OBJ, var1, NONE) in KEYS}( + +LEFT [LIN, OBJ, var1, NONE] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) + ) + +sum{(LIN, OBJ, NONE, var1) in KEYS}( + +LEFT [LIN, OBJ, NONE, var1] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) + ) + +(if (CONST, OBJ, NONE, NONE) in KEYS then LEFT[CONST, OBJ, NONE, NONE] else 0) + ; + + + +subject to constraint{ctr in CONSTRAINTS diff SQRT_PRECOND_CSTR}: + +(if ctr in CONSTRAINTS_LB then LEFT[LB, ctr, NONE, NONE] else -Infinity) + <= + +sum{(MONO, ctr, monname, NONE) in KEYS}( + +LEFT [MONO, ctr, monname, NONE] * prod{(MONO_DEF, monname, var0, NONE) in KEYS}( + (if var0 in REAL_VARIABLES then x[var0] else y[var0])^LEFT [MONO_DEF, monname, var0, NONE] + ) + ) + +sum{(QUAD, ctr, var1, var2) in KEYS}( + +LEFT [QUAD, ctr, var1, var2] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) * (if var2 in REAL_VARIABLES then x[var2] else y[var2]) + ) + +sum{(LIN, ctr, var1, NONE) in KEYS}( + +LEFT [LIN, ctr, var1, NONE] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) + ) + +sum{(LIN, ctr, NONE, var1) in KEYS}( + +LEFT [LIN, ctr, NONE, var1] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) + ) + +(if (CONST, ctr, NONE, NONE) in KEYS then LEFT[CONST, ctr, NONE, NONE] else 0) + <= + +(if ctr in CONSTRAINTS_UB then LEFT[UB, ctr, NONE, NONE] else +Infinity) + ; + + +/* SQRT precond constraints */ +subject to sqrt_constraint{ctr in SQRT_PRECOND_CSTR}: + +(if ctr in CONSTRAINTS_LB then sqrt(LEFT[LB, ctr, NONE, NONE]) else -Infinity) + <= + sqrt( + +1e-6 + +sum{(MONO, ctr, monname, NONE) in KEYS}( + +LEFT [MONO, ctr, monname, NONE] * prod{(MONO_DEF, monname, var0, NONE) in KEYS}( + (if var0 in REAL_VARIABLES then x[var0] else y[var0])^LEFT [MONO_DEF, monname, var0, NONE] + ) + ) + +sum{(QUAD, ctr, var1, var2) in KEYS}( + +LEFT [QUAD, ctr, var1, var2] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) * (if var2 in REAL_VARIABLES then x[var2] else y[var2]) + ) + +sum{(LIN, ctr, var1, NONE) in KEYS}( + +LEFT [LIN, ctr, var1, NONE] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) + ) + +sum{(LIN, ctr, NONE, var1) in KEYS}( + +LEFT [LIN, ctr, NONE, var1] * (if var1 in REAL_VARIABLES then x[var1] else y[var1]) + ) + +(if (CONST, ctr, NONE, NONE) in KEYS then LEFT[CONST, ctr, NONE, NONE] else 0) + ) + <= + +(if ctr in CONSTRAINTS_UB then sqrt(LEFT[UB, ctr, NONE, NONE]) else +Infinity) + ; diff --git a/src_ampl/real_minlp.run b/src_ampl/real_minlp.run new file mode 100644 index 0000000..f523e16 --- /dev/null +++ b/src_ampl/real_minlp.run @@ -0,0 +1,117 @@ +reset; +suffix xfeastol IN; +suffix cfeastol IN; +suffix intvarstrategy IN; + +param CONST_POINT symbolic := "CONST_POINT"; +param GOC_POINT symbolic := "GOC_POINT"; +param DO_NOTHING symbolic := "DO_NOTHING"; +param MPEC symbolic := "MPEC"; + +param OPTIMIZATION_CHOICE symbolic := MPEC; + +/* param STRATEGY_INITPOINT symbolic := CONST_POINT; */ +param STRATEGY_INITPOINT symbolic := GOC_POINT; + +model real_minlp.mod; + +data; + +param: KEYS: LEFT RIGHT := include "real_minlp_instance.dat"; +set PRECOND_CONSTRAINTS := include "real_minlp_precond_cstrs.dat"; + + +model; + +option solver knitroampl; +option knitro_options 'outlev=3 maxit=600 scale=0 feastol=1 opttol=1 feastolabs=1e-6 opttolabs=1e-3 bar_initpt=2 presolve=0 honorbnds=0'; + + +### +# initialization +### + +if STRATEGY_INITPOINT == GOC_POINT then{ + let{(VAR_TYPE, REAL, name, NONE) in KEYS} x[name] := LEFT[VAR_TYPE, REAL, name, NONE]*1.0; + let{(VAR_TYPE, BOOL, name, NONE) in KEYS} y[name] := LEFT[VAR_TYPE, BOOL, name, NONE]*1.0; +} else if STRATEGY_INITPOINT == CONST_POINT then{ + let{(VAR_TYPE, REAL, name, NONE) in KEYS} x[name] := 1.1; + let{(VAR_TYPE, BOOL, name, NONE) in KEYS} y[name] := 0.0; +}; + +option presolve 0; +if STRATEGY_INITPOINT == GOC_POINT and card({i in 1.._ncons: _con[i].slack<-1e-5})>1 then{ + printf "---\nListing the constraints violated by initial point\n---\n"; + printf "%-120s%15s%15s%15s%15s\n", "NAME", "LB", "BODY", "UB", "SLACK"; + printf{i in 1.._ncons: _con[i].slack<-1e-5}"%-120s%15.6E%15.6E%15.6E%15.6E\n", + _conname[i], + _con[i].lb, + _con[i].body, + _con[i].ub, + _con[i].slack; + printf "---\n"; +} +option presolve 0; + +#define KTR_PARAM_MIP_INTVAR_STRATEGY 2030 +# define KTR_MIP_INTVAR_STRATEGY_NONE 0 +# define KTR_MIP_INTVAR_STRATEGY_RELAX 1 +# define KTR_MIP_INTVAR_STRATEGY_MPEC 2 +if OPTIMIZATION_CHOICE == MPEC then { + # first phase relaxation binary constraints + let{varname in BINARY_VARIABLES} y[varname].intvarstrategy := 1; + + solve; + + if card({i in 1.._ncons: _con[i].slack<-1e-6})>1 then{ + printf "---\nListing the constraints violated by initial point\n---\n"; + printf "%60s%15s%15s%15s%15s\n", "NAME", "LB", "BODY", "UB", "SLACK"; + printf{i in 1.._ncons: _con[i].slack<-1e-6}"%60s%15.6E%15.6E%15.6E%15.6E\n", + _conname[i], + _con[i].lb, + _con[i].body, + _con[i].ub, + _con[i].slack; + printf "---\n"; + } + option presolve 0; + + option knitro_options 'outlev=3 maxit=600 scale=0 feastol=1 opttol=1 feastolabs=1e-6 opttolabs=1e-3 bar_initpt=2 presolve=0 honorbnds=0'; + + printf"Binary variables max dist to 1, phase 1\n"; + display max{varname in BINARY_VARIABLES}min( + y[varname], + 1-y[varname] + ); + + display card(BINARY_VARIABLES); + display BINARY_VARIABLES; + display y; + + # second phase reformulating binary constraints into MPEC and starting from the previous KKT solution + let{varname in BINARY_VARIABLES} y[varname].intvarstrategy := 2; + + solve; + + option solution_precision 0; + option display_precision 0; + + printf"Binary variables max dist to 1, phase 2\n"; + display max{varname in BINARY_VARIABLES}min( + y[varname], + 1-y[varname] + ); + + display card(BINARY_VARIABLES); + display BINARY_VARIABLES; + display y; + +}; + +printf"%-40s;%s;%s\n", "#Var name", "Knitro sol", "GOC sol" > ("solution_"&STRATEGY_INITPOINT&".csv"); +printf{var0 in REAL_VARIABLES} "%s;%.20e;%.20e\n", var0, x[var0], LEFT[VAR_TYPE, REAL, var0, NONE] > ("solution_"&STRATEGY_INITPOINT&".csv"); +printf{var0 in BINARY_VARIABLES} "%s;%.20e;%.20e\n", var0, y[var0], LEFT[VAR_TYPE, BOOL, var0, NONE] > ("solution_"&STRATEGY_INITPOINT&".csv"); + + +printf"||x-x_GOC||_2 : %f\n", sqrt(sum{var0 in REAL_VARIABLES} (x[var0] - LEFT[VAR_TYPE, REAL, var0, NONE])^2); +printf"||x-x_GOC||_inf : %f\n\n", max{var0 in REAL_VARIABLES} abs(x[var0] - LEFT[VAR_TYPE, REAL, var0, NONE]); diff --git a/src_test/export_dat.jl b/src_test/export_dat.jl new file mode 100644 index 0000000..4bab27c --- /dev/null +++ b/src_test/export_dat.jl @@ -0,0 +1,74 @@ +ROOT = pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) + +"""Test +Test to launch in complex-modeler/src_test folder for example D:\repo\complex-modeler\src_test>julia global_test.jl + +# Arguments +- type of instance (GOCInput, MatpowerInput, IIDMInput) +if GOCInput : julia global_test.jl GOCInput +if MatpowerInput or IIDMInput : julia global_test.jl MatpowerInput + + +""" +function read_arguments(ARGS) + if length(ARGS)==0 + error("Arguments must be : + Argument 1 is the type of instance (GOCInput, MatpowerInput or IIDMInput)\n + julia global_test.jl GOCInput + julia global_test.jl MatpowerInput + example : julia global_test.jl MatpowerInput ..\data_Matpower\matpower case9.m + julia global_test.jl IIDMInput + + ") + end + inputtype = ARGS[1] + if inputtype=="GOCInput" + inputtype = GOCInput + if length(ARGS)!=5 + error("Arguments must be : julia global_test.jl GOCInput ") + else + instance_path = joinpath(pwd(), ARGS[2], ARGS[3],ARGS[4]) + output_path = ARGS[3] + end + + elseif inputtype=="MatpowerInput" + inputtype = MatpowerInput + if length(ARGS)!=4 + error("Arguments must be : julia global_test.jl MatpowerInput ") + else + instance_path = joinpath(pwd(), ARGS[2], ARGS[3]) + output_path = ARGS[4] + end + + elseif inputtype=="IIDMInput" + inputtype = IIDMInput + if length(ARGS)!=4 + error("Arguments must be : julia global_test.jl IIDMInput ") + else + instance_path = joinpath(pwd(), ARGS[2], ARGS[3]) + output_path = ARGS[4] + end + else + error("Unexpected input type.") + end + return inputtype, instance_path, output_path +end + +function main(ARGS) + inputtype, instance_path, output_path = read_arguments(ARGS) + + ## Building network description + OPFpbs = load_OPFproblems(inputtype, instance_path) + + ## Setting Generators labels for coupling constraints if GOC instance + inputtype == GOCInput && introduce_Sgenvariables!(OPFpbs) + + ## Building polynomila optim problem + pb_global = build_globalpb!(OPFpbs) + + ## Exporting + export_to_dat(pb_global, output_path) +end + +main(ARGS) diff --git a/src_test/global_test.jl b/src_test/global_test.jl new file mode 100644 index 0000000..f35b3e2 --- /dev/null +++ b/src_test/global_test.jl @@ -0,0 +1,119 @@ +"""Test +Test to launch in complex-modeler/src_test folder for example D:\repo\complex-modeler\src_test>julia global_test.jl + +# Arguments +- type of instance (GOCInput, MatpowerInput, IIDMInput) +if GOCInput : julia global_test.jl GOCInput +if MatpowerInput or IIDMInput : julia global_test.jl MatpowerInput + + +""" +#TODO:: option sur les print +function read_arguments(ARGS) + if length(ARGS)==0 + error("Arguments must be : + Argument 1 is the type of instance (GOCInput, MatpowerInput or IIDMInput)\n + julia global_test.jl GOCInput + julia global_test.jl MatpowerInput + example : julia global_test.jl MatpowerInput ..\data_Matpower\matpower case9.m 1e-20 + julia global_test.jl IIDMInput + + ") + end + typeofinput = ARGS[1] + if typeofinput=="GOCInput" + typeofinput = GOCInput + if length(ARGS)!=5 + error("Arguments must be : julia global_test.jl GOCInput ") + else + instance_path = joinpath(pwd(), ARGS[2], ARGS[3],ARGS[4]) + eps = ARGS[5] + end + + elseif typeofinput=="MatpowerInput" + typeofinput = MatpowerInput + if length(ARGS)!=4 + error("Arguments must be : julia global_test.jl MatpowerInput ") + else + instance_path = joinpath(pwd(), ARGS[2], ARGS[3]) + eps = ARGS[4] + end + + elseif typeofinput=="IIDMInput" + typeofinput = IIDMInput + if length(ARGS)!=4 + error("Arguments must be : julia global_test.jl IIDMInput ") + else + instance_path = joinpath(pwd(), ARGS[2], ARGS[3]) + eps = ARGS[4] + end + + else + error() + end + return typeofinput, instance_path, eps +end + +ROOT = pwd() +include(joinpath(ROOT,"..","src_PowSysMod", "PowSysMod_body.jl")) +# include("para_fct.jl") +typeofinput, instance_path, eps = read_arguments(ARGS) + + +function build_and_solve_instance(typeofinput, instance_path) + nb_variables = nb_constraints = 0 + obj = feas = 0.0 + + tic() + OPFpbs = load_OPFproblems(typeofinput, instance_path) + + ## Introducing coupling constraints on generator output + (typeofinput != GOCInput) || introduce_Sgenvariables!(OPFpbs) + + ## Bulding optimization problem + pb_global = build_globalpb!(OPFpbs) + pb_global_real = pb_cplx2real(pb_global) + + ## Reading GOC initial point + init_point = Point() + (typeofinput != GOCInput) || (init_point = solution_point(instance_path)) + init_point_real = cplx2real(init_point) + + ## Exporting real problem + if typeofinput != GOCInput + instance_name = String(split(instance_path,'\\')[end]) + amplexportpath = joinpath("..","knitro_runs", "$(instance_name[1:end-2])") + else + folder,scenario = split(instance_path, '\\')[end-1:end] + folder = String(folder) + scenario = String(scenario) + amplexportpath = joinpath("..","knitro_runs", "$(folder[9:end])_$(scenario)") + end + export_to_dat(pb_global_real, amplexportpath, init_point_real) + t_buildexport = toq() + + + _, t_knitro, _ = @timed run_knitro(amplexportpath, joinpath(pwd(),"..","src_ampl")) + pt_knitro, ~ = read_Knitro_output(amplexportpath, pb_global_real) + feas = get_minslack(pb_global_real, pt_knitro) + obj = get_objective(pb_global_real, pt_knitro) + + nb_variables = length(pb_global_real.variables) + nb_constraints = length(pb_global_real.constraints) + + return String(split(instance_path,'\\')[end]) => (nb_variables, nb_constraints, obj, feas[1], t_buildexport, t_knitro) +end + +function main(ARGS) + typeofinput, instance_path, eps = read_arguments(ARGS) + + (instance,(nb_variables, nb_constraints, obj, feas, t_buildexport, t_knitro)) = build_and_solve_instance(typeofinput, instance_path) + println("NB variables : ",nb_variables) + println("NB_constraints : ",nb_constraints) + println("Objective : ",obj) + println("Feasibility : ",feas) + println("t_buildexport : ", t_buildexport) + println("t_knitro : ", t_knitro) +end + +main(ARGS) diff --git a/src_test/results_GOC.jl b/src_test/results_GOC.jl new file mode 100644 index 0000000..200c1fe --- /dev/null +++ b/src_test/results_GOC.jl @@ -0,0 +1,32 @@ +include(joinpath("..","src_PowSysMod", "PowSysMod_body.jl")) +include("global_test.jl") + +typeofinput = GOCInput +folders = ["Phase_0_IEEE14", "Phase_0_RTS96", "Phase_0_Modified_RTS96", "Phase_0_Modified_IEEE14"] + +folder = "Phase_0_RTS96" +path_folder = joinpath(pwd(),"..","data_GOC", folder) +nb_scenarios = length(readdir(path_folder))-1 +# scenarios = Set(readdir(joinpath("instances", "GOC", folder))) +# for scenario in scenarios +# if ismatch(r".csv", scenario) +# pop!(scenarios, scenario) +# end +# end +results = pmap(build_and_solve_instance, [typeofinput for i in 1:nb_scenarios], [joinpath(path_folder, scenario) for scenario in readdir(path_folder) if scenario!="scorepara.csv"]) + +filename = joinpath("resultsROADEF_$folder.csv") +touch(filename) + +f = open(filename, "w") + +write(f, "Folder;Scenario;Nb_variables;Nb_constraints;Objective;Feasibility\n") +for (scenario, data) in results + nb_variables = data[1] + nb_constraints = data[2] + obj = data[3] + feas = data[4] + + write(f, "$(folder);$(scenario);$(nb_variables);$(nb_constraints);$(obj);$(feas)\n") +end +close(f) diff --git a/test_export_pb.jl b/test_export_pb.jl new file mode 100644 index 0000000..7441056 --- /dev/null +++ b/test_export_pb.jl @@ -0,0 +1,29 @@ +ROOT = pwd() +include(joinpath(ROOT, "src_PowSysMod", "PowSysMod_body.jl")) + +folder = "Phase_0_IEEE14_1Scenario" +scenario = "scenario_1" +filenames = [folder, scenario] +filenames_path = joinpath("data_GOC", folder, scenario) +instance_path = filenames_path + +## Build global pb +OPFpbs = load_OPFproblems(GOCInput, instance_path) +introduce_Sgenvariables!(OPFpbs) +pb_global = build_globalpb!(OPFpbs) + +## Build solution +global_point = solution_point(instance_path) +println("minslack of GOC global solution: $(get_minslack(pb_global, global_point))") + +## Export problem +pb_global_real = pb_cplx2real(pb_global) +pt_global_real = cplx2real(global_point) + +# output_file = joinpath("toto", "real_minlp_instance.dat") +export_to_dat(pb_global_real, "src_ampl", pt_global_real) +# pb_global_real = pb_cplx2real(pb_global) +# pt_global_real = cplx2real(global_point) +# +# output_file = joinpath(amplexportpath, "real_minlp_instance.dat") +# export_to_dat(pb_global_real, output_file, pt_global_real)