Thanks for your willing to help. I’ve figured out a way.
This is the code to generate bft
describing the graph.
import JuMP, Gurobi
import LinearAlgebra.dot as dot
import SparseArrays.sparse as sparse
import Random
function adjnode(n)
nv = Int[]
for ci in findall(x -> x == n, bft)
b, c = ci.I
on = bft[b, 3 - c] # the other node on branch b
if on in nv
error("in Function adjnode(), shouldn't happen")
else
push!(nv, on)
end
end
return sort(nv)
end
function search_extreme_cases_code() # 📘 invoke this code on demand
sd_vec, obj_vec = Int[], Float64[]
while true
sd = rand(Int) # seed
Random.seed!(sd)
JuMP.delete(ø, c)
LD = rand(0.03:7e-15:1) * lrand(L) .* R_lnv; # randomly generate a load vector at an instance
c = JuMP.@constraint(ø, [n in 1:N], ı_at(n) + dot( 𝐟 , view(A, :, n)) == w_at(n)); # 📘 KCL
JuMP.optimize!(ø)
if JuMP.termination_status(ø) == JuMP.OPTIMAL
push!(sd_vec, sd)
push!(obj_vec, JuMP.objective_value(ø))
l = length(sd_vec)
print("\rl = $l")
l == 1 && break
end
end
while true
sd = rand(Int) # seed
Random.seed!(sd)
JuMP.delete(ø, c)
LD = rand(0.03:7e-15:1) * lrand(L) .* R_lnv; # randomly generate a load vector at an instance
c = JuMP.@constraint(ø, [n in 1:N], ı_at(n) + dot( 𝐟 , view(A, :, n)) == w_at(n)); # 📘 KCL
JuMP.optimize!(ø)
if JuMP.termination_status(ø) == JuMP.OPTIMAL
o = JuMP.objective_value(ø)
if o < obj_vec[1]
pushfirst!(sd_vec, sd)
pushfirst!(obj_vec, o)
elseif o > obj_vec[end]
push!(sd_vec, sd)
push!(obj_vec, o)
else
continue
end
l = length(sd_vec)
print("\rl = $l")
l == 20 && break
end
end
end
function lrand() return sin(rand(0.1:7e-15:1.57)) end;
function lrand(N) return sin.(rand(0.1:7e-15:1.57, N)) end;
function bft_2_A(bft)
B, N = size(bft, 1), maximum(bft)
return sparse([Vector(1:B); Vector(1:B)], vec(bft), [-ones(Int, B); ones(Int, B)], B, N)
end;
function load_111_data()
R = [10.76, 25.35, 137.5, 10.15, 20.32, 52.75, 42.08, 15.63, 15.79, 54.85, 17.45, 6.72, 32.03, 14.7, 15.21, 4.4, 5.51, 13.06, 24.61, 6.05, 21.62, 22.23, 9.39, 27.28, 12.94, 11.32, 6.91, 22.02, 13.79, 29.41, 6.77, 12.82, 11.56, 28.96, 22.21, 13.01, 6.88, 32.27, 9.4, 10.92, 14.24, 8.64, 4.35, 107.59, 22.07, 7.59, 39.87, 115.33, 29.96, 10.15, 6.31, 20.73, 17.77, 22.11, 5.88, 7.96, 4.44, 6.49, 12.1, 7.95, 8.48, 5.67, 17.19, 6.79, 5.67, 20.97, 14.08, 7.73, 18.06, 6.67, 9.0, 7.47, 15.46, 113.06, 70.8, 10.96, 7.91, 10.96, 14.73, 4.79, 8.71, 5.09, 7.57, 7.32, 81.68, 19.56, 29.19, 41.93, 11.35, 37.06, 24.0, 5.03, 9.38, 30.37, 10.81, 69.97, 3.87, 3.32, 30.37, 8.61, 2.73, 30.72, 5.56, 6.06, 24.31, 8.13, 7.63, 8.74, 26.49, 7.27, 10.64, 5.38, 86.71, 44.94, 32.17, 15.58, 12.44, 29.32, 7.69, 7.29, 15.86, 8.79, 10.81, 6.43, 15.49, 16.72, 12.86, 28.73, 8.45, 12.68, 6.8, 14.68, 24.77, 6.06, 20.27, 12.35, 11.81, 10.16, 5.33, 3.72, 18.52, 19.61, 12.46, 6.13, 13.49, 8.7, 19.63, 9.8, 20.47, 5.38, 6.81, 6.57, 4.74, 28.75, 19.9, 5.9, 14.98, 5.9, 36.65, 6.06, 13.85, 16.38, 35.72, 5.3, 17.93, 14.81, 105.49, 276.46, 22.37, 19.78, 29.11, 30.37]
gnv = [1, 4, 6, 8, 12, 15, 18, 19, 24, 25, 26, 27, 31, 32, 34, 36, 40, 42, 46, 49, 54, 55, 56, 59, 61, 62, 63, 65, 66, 69, 70, 72, 73, 74, 76, 77, 80, 81, 85, 86, 87, 89, 90, 91, 92, 99, 100, 103, 104, 105, 107, 110]
lnv = [1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 27, 28, 29, 31, 32, 33, 34, 35, 36, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 62, 63, 66, 67, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111]
bft = [1 2; 1 3; 4 5; 3 5; 5 6; 6 7; 5 8; 4 11; 5 11; 11 12; 2 12; 3 12; 7 12; 11 13; 12 14; 13 15; 14 15; 12 16; 15 17; 16 17; 17 18; 18 19; 19 20; 15 19; 20 21; 21 22; 22 23; 23 24; 23 25; 25 26; 25 27; 27 28; 28 29; 17 30; 8 30; 26 30; 17 31; 29 31; 23 32; 31 32; 27 32; 15 33; 19 34; 35 36; 35 37; 33 37; 34 36; 34 37; 37 38; 37 39; 37 40; 30 38; 39 40; 40 41; 40 42; 41 42; 43 44; 34 43; 44 45; 45 46; 46 47; 46 48; 47 49; 42 49; 45 49; 48 49; 49 50; 49 51; 51 52; 52 53; 53 54; 49 54; 54 55; 54 56; 55 56; 56 57; 50 57; 56 58; 51 58; 54 59; 56 59; 55 59; 59 60; 59 61; 60 61; 60 62; 61 62; 61 64; 38 65; 64 65; 49 66; 62 66; 62 67; 65 66; 66 67; 65 68; 47 69; 49 69; 68 69; 69 70; 24 70; 70 71; 24 72; 71 72; 71 73; 70 74; 70 75; 69 75; 74 75; 76 77; 69 77; 75 77; 77 78; 78 79; 77 80; 79 80; 77 82; 82 83; 83 84; 83 85; 84 85; 85 86; 85 88; 85 89; 88 89; 89 90; 90 91; 89 92; 91 92; 92 93; 92 94; 93 94; 94 95; 80 96; 82 96; 94 96; 80 97; 80 98; 80 99; 92 100; 94 100; 95 96; 96 97; 98 100; 99 100; 100 101; 92 102; 101 102; 100 103; 100 104; 103 104; 103 105; 100 106; 104 105; 105 106; 105 107; 105 108; 106 107; 108 109; 103 110; 109 110; 63 110; 17 81; 32 81; 9 32; 10 27; 9 10; 68 87; 75 111; 76 111; 59 64; 68 80]
return R, gnv, lnv, bft # RateA, generator_node_vector, load_node_vector, branch_from_to
end;
function ı_at(n) # return the power injection Given a node index
i = findfirst(x -> x == n, gnv) # injection index, NOT a generator index
b = isnothing(i)
if b
return !b # no injection
else
return ı[i] # a VariableRef
end
end;
function w_at(n) # return the power withdrawal Given a node index
l = findfirst(x -> x == n, lnv) # load index
b = isnothing(l)
if b
return !b # no injection
else
return LD[l] # load data
end
end;
R, gnv, lnv, bft = load_111_data(); A = bft_2_A(bft);
And this is the code that generate a graph that can be understood by human.
using Graphs, GraphPlot, CairoMakie
function clockwise_rotate(A, t)
c, s = cos(t), sin(t)
[c s; -s c] * A
end
function relocate_as_mean!(xv, yv, n, n1, n2)
xv[n] = (xv[n1] + xv[n2])/2
yv[n] = (yv[n1] + yv[n2])/2
end
g = SimpleGraph(maximum(bft)); for (n, m) in eachrow(bft) # graph construction
add_edge!(g, n, m)
end
function custom_modification!(xv, yv)
relocate_as_mean!(xv, yv, 48, 66, 68)
relocate_as_mean!(xv, yv, 47, 46, 69)
relocate_as_mean!(xv, yv, 50, 49, 57)
relocate_as_mean!(xv, yv, 92, 91, 94)
relocate_as_mean!(xv, yv, 25, 23, 27)
relocate_as_mean!(xv, yv, 9, 29, 32)
relocate_as_mean!(xv, yv, 86, 83, 89)
relocate_as_mean!(xv, yv, 79, 77, 97)
relocate_as_mean!(xv, yv, 87, 69, 80)
relocate_as_mean!(xv, yv, 51, 42, 50)
relocate_as_mean!(xv, yv, 73, 71, 74)
relocate_as_mean!(xv, yv, 43, 15, 44)
relocate_as_mean!(xv, yv, 1, 2, 14)
relocate_as_mean!(xv, yv, 108, 55, 107)
relocate_as_mean!(xv, yv, 109, 108, 110)
relocate_as_mean!(xv, yv, 63, 91, 110)
relocate_as_mean!(xv, yv, 88, 86, 89)
relocate_as_mean!(xv, yv, 90, 89, 91)
relocate_as_mean!(xv, yv, 72, 23, 69)
end; Random.seed!(2527829590547944688);
xv, yv = spring_layout(g); # supported by the layout algorithm, crude
angle = 39; # choose an apt angle
temp = clockwise_rotate([xv'; yv'], π/180 * angle);
xv, yv = temp[1, :], temp[2, :];
custom_modification!(xv, yv);
line_colors = [
:chocolate, # Yellow
:gold1, # Yellow
:blue, # blue
:cyan, # blue
:lime, # green lemon
:darkgreen, # green
:olive, # green
:deeppink, # Red
:red4 # Red
];
label_size = 8
f = Figure(); ax = Axis(f[1, 1]); # 🖼️
Random.seed!(2)
for (n, m) in eachrow(bft) # Draw lines
lines!(ax, [xv[n], xv[m]], [yv[n], yv[m]]; linewidth = 1, color = rand(line_colors))
end
for n in 1:maximum(bft) # Draw Points
if n in gnv
if n in lnv
text!(ax, [xv[n]], [yv[n]]; color = :purple, text = "$n", fontsize = label_size)
else
text!(ax, [xv[n]], [yv[n]]; color = :red, text = "$n", fontsize = label_size)
end
elseif n in lnv
text!(ax, [xv[n]], [yv[n]]; color = :skyblue, text = "$n", fontsize = label_size)
else
text!(ax, [xv[n]], [yv[n]]; color = :black, text = "$n", fontsize = label_size)
end
end
hidedecorations!(ax)
f # After typing this in REPL, it will generate the picture
The picture derived is
The good ideas include
- use diverse line colors (I use 2 Yellow + 2 Blue + 2 Red + 3 Green)
- as a user, you need to do some custom relocations inevitably. First, Givens rotation can be employed to make the layout look upright. Second, adjust specific point locations, as I had done in
custom_modification!
.