Skip to content

Commit 877dd24

Browse files
committed
Some fixes to (random) rebranching algorithm.
1 parent 54f3f69 commit 877dd24

1 file changed

Lines changed: 121 additions & 118 deletions

File tree

src/main/annealing.jl

Lines changed: 121 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -15,23 +15,23 @@ Runs the simulated annealing method starting from network `I0`. Only sensible if
1515
("random" is purely random, works horribly; "shake" applies a gaussian blur
1616
along a random direction, works alright; "rebranching" (deterministic) and "random rebranching" (default) is the algorithm
1717
described in Appendix A.4 in the paper, works nicely)
18-
- `preserve_central_symmetry::Bool=false`: Only applies to shake method
19-
- `preserve_vertical_symmetry::Bool=false`: Only applies to shake method
20-
- `preserve_horizontal_symmetry::Bool=false`: Only applies to shake method
21-
- `smoothing_radius::Float64=0.25`: Parameters of the Gaussian blur
22-
- `mu_perturbation::Float64=log(0.3)`: Parameters of the Gaussian blur
23-
- `sigma_perturbation::Float64=0.05`: Parameters of the Gaussian blur
18+
- `preserve_central_symmetry::Bool=false`: Only applies to "shake" method
19+
- `preserve_vertical_symmetry::Bool=false`: Only applies to "shake" method
20+
- `preserve_horizontal_symmetry::Bool=false`: Only applies to "shake" method
21+
- `smooth_network::Bool=true`: Whether to smooth the network after each perturbation if `perturbation_method` is "random"
22+
- `smoothing_radius::Float64=0.25`: Parameters of the Gaussian blur if `perturbation_method` is "random" or "shake"
23+
- `mu_perturbation::Float64=log(0.3)`: Parameters of the Gaussian blur if `perturbation_method` is "shake"
24+
- `sigma_perturbation::Float64=0.05`: Parameters of the Gaussian blur if `perturbation_method` is "shake"
25+
- `num_random_perturbations::Int64=1`: Number of links to be randomly affected ("random" and "random rebranching" only)
2426
- `display::Bool`: Display the graph in each iteration as we go
2527
- `t_start::Float64=100`: Initial temperature
2628
- `t_end::Float64=1`: Final temperature
2729
- `t_step::Float64=0.9`: Speed of cooling
2830
- `num_deepening::Int64=4`: Number of FOC iterations between candidate draws
29-
- `num_random_perturbations::Int64=1`: Number of links to be randomly affected ('random' and 'random rebranching' only)
3031
- `Iu::Matrix{Float64}=Inf * ones(J, J)`: J x J matrix of upper bounds on network infrastructure Ijk
3132
- `Il::Matrix{Float64}=zeros(J, J)`: J x J matrix of lower bounds on network infrastructure Ijk
32-
- `model::Function`: For custom models => a function that taks an optimizer and an 'auxdata' structure as created by create_auxdata() as input and returns a fully parameterized JuMP model
33-
- `final_model::JuMPModel`: Alternatively: a readily parameterized JuMP model to be used (from `optimal_network()`)
34-
- `recover_allocation::Function`: The `recover_allocation()` function corresponding to either `model` or `final_model`
33+
- `final_model::JuMPModel`: (Optionally) a readily parameterized JuMP model to be used (from `optimal_network()`)
34+
- `recover_allocation::Function`: The `recover_allocation()` function corresponding to `final_model`
3535
- `allocation::Dict`: The result from `recover_allocation()` from a previous solution of the model: to skip an initial resolve without perturbations.
3636
3737
# Examples
@@ -85,7 +85,7 @@ function annealing(param, graph, I0; kwargs...)
8585
perturbate = rebranch_network
8686
elseif options.perturbation_method == "random rebranching"
8787
perturbate = random_rebranch_network
88-
# elseif options.perturbation_method == "hybrid alder"
88+
# elseif options.perturbation_method == "hybrid alder" # Not provided because need model as input (and takes long)
8989
# perturbate = hybrid
9090
else
9191
error("unknown perturbation method %s\n", options.perturbation_method)
@@ -326,6 +326,7 @@ function retrieve_options_annealing(graph; kwargs...)
326326
:preserve_central_symmetry => false,
327327
:preserve_vertical_symmetry => false,
328328
:preserve_horizontal_symmetry => false,
329+
:smooth_network => true,
329330
:smoothing_radius => 0.25,
330331
:mu_perturbation => log(0.3),
331332
:sigma_perturbation => 0.05,
@@ -391,11 +392,13 @@ function random_perturbation(param, graph, I0, results, options)
391392

392393
# Make sure graph satisfies symmetry and capacity constraint
393394
I1 += I1' # make sure kappa is symmetric
394-
I1 ./= 2
395+
I1 /= 2
395396
I1 *= param.K / sum(graph.delta_i .* I1) # rescale
396397

397398
# Smooth network (optional)
398-
I1 = smooth_network(param, graph, I1)
399+
if options.smooth_network
400+
I1 = smooth_network(param, graph, I1, options)
401+
end
399402

400403
return I1
401404
end
@@ -405,9 +408,9 @@ end
405408
# This function produces a smoothed version of the network by applying a
406409
# Gaussian smoother (i.e., a Gaussian kernel estimator). The main benefit is
407410
# that it makes the network less sparse and the variance of investments gets reduced.
408-
function smooth_network(param, graph, I0)
411+
function smooth_network(param, graph, I0, options)
409412
J = graph.J
410-
smoothing_radius = 0.3 # This could also be an option in `param` if variable
413+
smoothing_radius = options.smoothing_radius # 0.3 # This could also be an option in `param` if variable
411414

412415
I1 = zeros(J, J)
413416
feasible_edge = zeros(Bool, J, J)
@@ -441,7 +444,7 @@ function smooth_network(param, graph, I0)
441444

442445
# Make sure graph satisfies symmetry and capacity constraint
443446
I1 += I1' # make sure kappa is symmetric
444-
I1 ./= 2
447+
I1 /= 2
445448
I1 *= param.K / sum(graph.delta_i .* I1) # rescale
446449

447450
return I1
@@ -504,7 +507,7 @@ function shake_network(param, graph, I0, results, options)
504507

505508
# Make sure graph satisfies symmetry and capacity constraint
506509
I1 += I1' # make sure kappa is symmetric
507-
I1 ./= 2
510+
I1 /= 2
508511
I1 *= param.K / sum(graph.delta_i .* I1) # rescale
509512

510513
return I1
@@ -541,14 +544,14 @@ function rebranch_network(param, graph, I0, results, options)
541544
# swap roads
542545
I1[i, lowest_price_parent] = I0[i, best_connected_parent]
543546
I1[i, best_connected_parent] = I0[i, lowest_price_parent]
544-
I1[lowest_price_parent, i] = I0[best_connected_parent, i]
545-
I1[best_connected_parent, i] = I0[lowest_price_parent, i]
547+
I1[lowest_price_parent, i] = I0[i, best_connected_parent]
548+
I1[best_connected_parent, i] = I0[i, lowest_price_parent]
546549
end
547550
end
548551

549552
# Make sure graph satisfies symmetry and capacity constraint
550553
I1 += I1' # make sure kappa is symmetric
551-
I1 ./= 2
554+
I1 /= 2
552555
I1 *= param.K / sum(graph.delta_i .* I1) # rescale
553556

554557
return I1
@@ -587,116 +590,116 @@ function random_rebranch_network(param, graph, I0, results, options)
587590
# Swap roads
588591
I1[i, lowest_price_parent] = I0[i, best_connected_parent]
589592
I1[i, best_connected_parent] = I0[i, lowest_price_parent]
590-
I1[lowest_price_parent, i] = I0[best_connected_parent, i]
591-
I1[best_connected_parent, i] = I0[lowest_price_parent, i]
593+
I1[lowest_price_parent, i] = I0[i, best_connected_parent]
594+
I1[best_connected_parent, i] = I0[i, lowest_price_parent]
592595
end
593596
end
594597

595598
# Make sure graph satisfies symmetry and capacity constraint
596599
I1 += I1' # make sure kappa is symmetric
597-
I1 ./= 2
600+
I1 /= 2
598601
I1 *= param.K / sum(graph.delta_i .* I1) # rescale
599602

600603
return I1
601604
end
602605

603606

604-
"""
605-
hybrid(param, graph, I0, results, options)
606-
607-
This function attempts to adapt the spirit of Alder (2018)'s algorithm to
608-
delete/add links to the network in a way that blends with our model.
609-
"""
610-
function hybrid(param, graph, I0, results, options)
611-
# ========================
612-
# COMPUTE GRADIENT WRT Ijk
613-
# ========================
614-
J = graph.J
615-
616-
if !param.cong # no cross-good congestion
617-
PQ = permutedims(repeat(results[:Pjn], 1, 1, J), [1, 3, 2]) .* results[:Qjkn] .^ (1 + param.beta)
618-
PQ = dropdims(sum(PQ + permutedims(PQ, [2, 1, 3]), dims=3), dims = 3)
619-
else # cross-good congestion
620-
PQ = repeat(results[:PCj], 1, J)
621-
matm = permutedims(repeat(param.m, 1, J, J), [3, 2, 1])
622-
cost = dropdims(sum(matm .* results[:Qjkn] .^ param.nu, dims=3), dims = 3) .^ ((param.beta + 1) / param.nu)
623-
PQ .*= cost
624-
PQ += PQ'
625-
end
607+
# """
608+
# hybrid(param, graph, I0, results, options)
609+
610+
# This function attempts to adapt the spirit of Alder (2018)'s algorithm to
611+
# delete/add links to the network in a way that blends with our model.
612+
# """
613+
# function hybrid(param, graph, I0, results, options)
614+
# # ========================
615+
# # COMPUTE GRADIENT WRT Ijk
616+
# # ========================
617+
# J = graph.J
618+
619+
# if !param.cong # no cross-good congestion
620+
# PQ = permutedims(repeat(results[:Pjn], 1, 1, J), [1, 3, 2]) .* results[:Qjkn] .^ (1 + param.beta)
621+
# PQ = dropdims(sum(PQ + permutedims(PQ, [2, 1, 3]), dims=3), dims = 3)
622+
# else # cross-good congestion
623+
# PQ = repeat(results[:PCj], 1, J)
624+
# matm = permutedims(repeat(param.m, 1, J, J), [3, 2, 1])
625+
# cost = dropdims(sum(matm .* results[:Qjkn] .^ param.nu, dims=3), dims = 3) .^ ((param.beta + 1) / param.nu)
626+
# PQ .*= cost
627+
# PQ += PQ'
628+
# end
626629

627-
grad = param.gamma * graph.delta_tau ./ graph.delta_i .* PQ .* I0.^-(1 + param.gamma)
628-
grad[graph.adjacency .== 0] .= 0
629-
# I1[PQ .== 0] .= 0
630-
# I1[graph.delta_i .== 0] .= 0
631-
632-
# ============
633-
# REMOVE LINKS: remove 5% worst links
634-
# ============
635-
I1 = copy(I0)
636-
nremove = ceil(Int, 0.05 * graph.ndeg) # remove 5% of the links
637-
remove_list = sortperm(vec(grad[tril(graph.adjacency)]), alg=QuickSort)[1:nremove]
638-
639-
id = 1
640-
for j in 1:J
641-
for k in graph.nodes[j]
642-
if id in remove_list # if link is in the list to remove
643-
I1[j, k] = 1e-6
644-
I1[k, j] = 1e-6
645-
end
646-
id += 1
647-
end
648-
end
649-
650-
# TODO: Finish revision this function from here onwards!
651-
# Problem: need model as input to solve again!!
652-
# ====================
653-
# COMPUTE NEW GRADIENT
654-
# ====================
655-
results = solve_allocation(param, graph, I1) # Assuming this function is defined elsewhere
656-
657-
if !param.cong # no cross-good congestion
658-
Pjkn = repeat(permute(results[:Pjn], [1, 3, 2]), outer=[1, J, 1])
659-
PQ = Pjkn .* results[:Qjkn].^(1 + param.beta)
660-
grad = param.gamma * graph.delta_tau .* sum(PQ + permute(PQ, [2, 1, 3]), dims=3) .* I0.^-(1 + param.gamma) ./ graph.delta_i
661-
grad[.!graph.adjacency] .= 0
662-
else # cross-good congestion
663-
PCj = repeat(results[:PCj], outer=[1, J])
664-
matm = permutedims(repeat(param.m, outer=[1, J, J]), [2, 1, 3])
665-
cost = sum(matm .* results[:Qjkn].^param.nu, dims=3).^((param.beta + 1) / param.nu)
666-
PQ = PCj .* cost
667-
grad = param.gamma * graph.delta_tau .* (PQ + PQ') .* I0.^-(1 + param.gamma) ./ graph.delta_i
668-
grad[.!graph.adjacency] .= 0
669-
end
670-
671-
# ========
672-
# ADD LINK: add the most beneficial link
673-
# ========
674-
675-
I2 = copy(I1)
676-
add_list = sortperm(vec(grad[tril(graph.adjacency)]), rev=true, alg=QuickSort)
677-
add_link = add_list[1]
678-
679-
id = 1
680-
for j in 1:J
681-
for k in graph.nodes[j]
682-
if id == add_link # if link is the one to add
683-
I2[j, k] = I2[j, k] = param.K / (2 * graph.ndeg)
684-
end
685-
id += 1
686-
end
687-
end
688-
689-
# =======
690-
# RESCALE
691-
# =======
692-
693-
# Make sure graph satisfies symmetry and capacity constraint
694-
I2 = (I2 + I2') / 2 # make sure kappa is symmetric
695-
total_delta_i = sum(graph.delta_i .* I2)
696-
I2 *= param.K / total_delta_i # rescale
697-
698-
return I2
699-
end
630+
# grad = param.gamma * graph.delta_tau ./ graph.delta_i .* PQ .* I0.^-(1 + param.gamma)
631+
# grad[graph.adjacency .== 0] .= 0
632+
# # I1[PQ .== 0] .= 0
633+
# # I1[graph.delta_i .== 0] .= 0
634+
635+
# # ============
636+
# # REMOVE LINKS: remove 5% worst links
637+
# # ============
638+
# I1 = copy(I0)
639+
# nremove = ceil(Int, 0.05 * graph.ndeg) # remove 5% of the links
640+
# remove_list = sortperm(vec(grad[tril(graph.adjacency)]), alg=QuickSort)[1:nremove]
641+
642+
# id = 1
643+
# for j in 1:J
644+
# for k in graph.nodes[j]
645+
# if id in remove_list # if link is in the list to remove
646+
# I1[j, k] = 1e-6
647+
# I1[k, j] = 1e-6
648+
# end
649+
# id += 1
650+
# end
651+
# end
652+
653+
# # TODO: Finish revision this function from here onwards!
654+
# # Problem: need model as input to solve again!!
655+
# # ====================
656+
# # COMPUTE NEW GRADIENT
657+
# # ====================
658+
# results = solve_allocation(param, graph, I1) # Assuming this function is defined elsewhere
659+
660+
# if !param.cong # no cross-good congestion
661+
# Pjkn = repeat(permute(results[:Pjn], [1, 3, 2]), outer=[1, J, 1])
662+
# PQ = Pjkn .* results[:Qjkn].^(1 + param.beta)
663+
# grad = param.gamma * graph.delta_tau .* sum(PQ + permute(PQ, [2, 1, 3]), dims=3) .* I0.^-(1 + param.gamma) ./ graph.delta_i
664+
# grad[.!graph.adjacency] .= 0
665+
# else # cross-good congestion
666+
# PCj = repeat(results[:PCj], outer=[1, J])
667+
# matm = permutedims(repeat(param.m, outer=[1, J, J]), [2, 1, 3])
668+
# cost = sum(matm .* results[:Qjkn].^param.nu, dims=3).^((param.beta + 1) / param.nu)
669+
# PQ = PCj .* cost
670+
# grad = param.gamma * graph.delta_tau .* (PQ + PQ') .* I0.^-(1 + param.gamma) ./ graph.delta_i
671+
# grad[.!graph.adjacency] .= 0
672+
# end
673+
674+
# # ========
675+
# # ADD LINK: add the most beneficial link
676+
# # ========
677+
678+
# I2 = copy(I1)
679+
# add_list = sortperm(vec(grad[tril(graph.adjacency)]), rev=true, alg=QuickSort)
680+
# add_link = add_list[1]
681+
682+
# id = 1
683+
# for j in 1:J
684+
# for k in graph.nodes[j]
685+
# if id == add_link # if link is the one to add
686+
# I2[j, k] = I2[j, k] = param.K / (2 * graph.ndeg)
687+
# end
688+
# id += 1
689+
# end
690+
# end
691+
692+
# # =======
693+
# # RESCALE
694+
# # =======
695+
696+
# # Make sure graph satisfies symmetry and capacity constraint
697+
# I2 = (I2 + I2') / 2 # make sure kappa is symmetric
698+
# total_delta_i = sum(graph.delta_i .* I2)
699+
# I2 *= param.K / total_delta_i # rescale
700+
701+
# return I2
702+
# end
700703

701704

702705

0 commit comments

Comments
 (0)