Skip to content

Commit 370ae96

Browse files
authored
Merge pull request #51 from OptimalTransportNetworks/development
Development
2 parents c9066ec + 4b7cdd0 commit 370ae96

File tree

5 files changed

+31
-123
lines changed

5 files changed

+31
-123
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 0.2.0
2+
3+
* The reason for the less than ideal numerical problems of the exact dual solution for the Hessian with `cross_good_congestion = true` in v0.1.9 was that the sparse hessian had too few elements. This release fixes the problem by adding some additional off-diagonal elements to the sparse hesssian. The heuristic algorithm is now removed as the exact one always gives better solves (`duality = true` and `duality = 2` both call the exact algorithm now).
4+
15
# 0.1.9
26

37
* The exact dual solution for the Hessian with `cross_good_congestion = true` does not have good numerical properties in some cases. Therefore, by default now an approximate solution is used which works better for most problems. Users can set `duality = 2` to use the exact solution in the CGC case.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "OptimalTransportNetworks"
22
uuid = "e2b46e68-897f-4e4e-ba36-a93c9789fd96"
33
authors = ["Sebastian Krantz <sebastian.krantz@graduateinstitute.ch>"]
4-
version = "0.1.9"
4+
version = "0.2.0"
55

66
[deps]
77
Dierckx = "39dd38d3-220a-591b-8e3c-4c3a8c710a94"

misc/experimental/dual_solution_manual_cgc.jl

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,20 @@ if map_size == 4
2525
graph = create_graph(param, 4, 4, type = "map")
2626
# Customize graph
2727
graph[:Zjn] = fill(0.1, graph[:J], param[:N])
28-
Ni = find_node(graph, 2, 3)
29-
graph[:Zjn][Ni, 1] = 2
30-
Ni = find_node(graph, 2, 1)
31-
graph[:Zjn][Ni, 2] = 1
32-
Ni = find_node(graph, 4, 4)
33-
graph[:Zjn][Ni, 3] = 1
28+
if param[:N] == 3
29+
Ni = find_node(graph, 2, 3)
30+
graph[:Zjn][Ni, 1] = 2
31+
Ni = find_node(graph, 2, 1)
32+
graph[:Zjn][Ni, 2] = 1
33+
Ni = find_node(graph, 4, 4)
34+
graph[:Zjn][Ni, 3] = 1
35+
else
36+
using Random: rand
37+
for i in 1:param[:N]
38+
Ni = find_node(graph, rand((1, 2, 3, 4)), rand((1, 2, 3, 4)))
39+
graph[:Zjn][Ni, i] = rand() * 2
40+
end
41+
end
3442
end
3543
if map_size == 3
3644
graph = create_graph(param, 3, 3, type = "map")
@@ -172,12 +180,12 @@ sum(abs.(gradient_duality_cgc(x0, auxdata) ./ ForwardDiff.gradient(objective, x0
172180

173181

174182
# Now the Hessian
175-
function hessian_structure_duality(auxdata)
183+
function hessian_structure_duality_cgc(auxdata)
176184
graph = auxdata.graph
177185
param = auxdata.param
178186

179187
# Create the Hessian structure
180-
H_structure = tril(repeat(sparse(I(graph.J)), param.N, param.N) + kron(sparse(I(param.N)), sparse(graph.adjacency)))
188+
H_structure = tril(repeat(sparse(I(graph.J)), param.N, param.N) + kron(sparse(ones(Int, param.N, param.N)), sparse(graph.adjacency))) # tril(repeat(sparse(I(graph.J)), param.N, param.N) + kron(sparse(I(param.N)), sparse(graph.adjacency)))
181189

182190
# Get the row and column indices of non-zero elements
183191
rows, cols, _ = findnz(H_structure)
@@ -318,7 +326,7 @@ function hessian_duality_cgc(
318326
return values
319327
end
320328

321-
rows, cols = hessian_structure_duality(auxdata)
329+
rows, cols = hessian_structure_duality_cgc(auxdata)
322330
cind = CartesianIndex.(rows, cols)
323331
values = zeros(length(cind))
324332

src/main/init_parameters.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Returns a `param` dict with the model parameters. These are independent of the g
2020
- `m::Vector{Float64}=ones(N)`: Vector of weights Nx1 in the cross congestion cost function
2121
- `annealing::Bool=true`: Switch for the use of annealing at the end of iterations (only if gamma > beta)
2222
- `verbose::Bool=true`: Switch to turn on/off text output (from Ipopt or other optimizers)
23-
- `duality::Bool=true`: Switch to turn on/off duality whenever available (fixed labor and beta <= 1). Note that if `cross_good_congestion == true`, setting `duality = 2` uses an exact algorithm to compute the hessian, which has however not shown good numerical properties in some cases.
23+
- `duality::Bool=true`: Switch to turn on/off duality whenever available (fixed labor and beta <= 1).
2424
- `warm_start::Bool=true`: Use the previous solution as a warm start for the next iteration
2525
- `kappa_min::Float64=1e-5`: Minimum value for road capacities K
2626
- `min_iter::Int64=20`: Minimum number of iterations

src/models/solve_allocation_by_duality_cgc.jl

Lines changed: 8 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,6 @@ function hessian_duality_cgc(
141141
graph = auxdata.graph
142142
nodes = graph.nodes
143143
kappa = auxdata.kappa
144-
inexact_algo = param.duality == true # param.duality == 2 for exact algorithm
145144
beta = param.beta
146145
m1dbeta = -1 / beta
147146
sigma = param.sigma
@@ -160,12 +159,9 @@ function hessian_duality_cgc(
160159
# Constant term: first part of Bprime
161160
cons = m1dbeta * n1dnum1 * numbetam1
162161
## Constant in T'
163-
if inexact_algo
164-
cons2 = numbetam1 / (numbetam1+nu*beta)
165-
else
166-
# New: constant in T' -> More accurate!
167-
cons3 = m1dbeta * n1dnum1 * (numbetam1+nu*beta) / (1+beta)
168-
end
162+
# cons2 = numbetam1 / (numbetam1+nu*beta)
163+
# New: constant in T' -> More accurate!
164+
cons3 = m1dbeta * n1dnum1 * (numbetam1+nu*beta) / (1+beta)
169165

170166
# Precompute elements
171167
res = recover_allocation_duality_cgc(x, auxdata)
@@ -219,19 +215,13 @@ function hessian_duality_cgc(
219215
if jd == j
220216
KPprimeAB1 = m1dbeta * Pjn[j, nd]^(-sigma) * PCj[j]^(sigma-1)
221217
term += Qjkn[j, k, n] * (KPprimeAB1 - KPAprimeB1 + KPABprime1) # Derivative of Qjkn
222-
if inexact_algo
223-
term += T * (((1+beta) * KPprimeAB1 + cons2 * KPABprime1) * G + Gprime) # T'G + TG'
224-
else
225-
term += T * ((1+beta) * KPprimeAB1 * G + Gprime) # T'G (first part) + TG'
226-
term += cons3 * Qjkn[j, k, nd] / PCj[j] * G # Second part of T'G
227-
end
218+
# term += T * (((1+beta) * KPprimeAB1 + cons2 * KPABprime1) * G + Gprime) # T'G + TG'
219+
term += T * ((1+beta) * KPprimeAB1 * G + Gprime) # T'G (first part) + TG'
220+
term += cons3 * Qjkn[j, k, nd] / PCj[j] * G # Second part of T'G
228221
elseif jd == k
229222
term += Qjkn[j, k, n] * (KPAprimeB1 - KPABprime1) # Derivative of Qjkn
230-
if inexact_algo
231-
term -= T * cons2 * KPABprime1 * G # T'G: second part [B'(k) has opposite sign]
232-
else
233-
term -= cons3 * Qjkn[j, k, nd] / PCj[j] * G # Second part of T'G
234-
end
223+
# term -= T * cons2 * KPABprime1 * G # T'G: second part [B'(k) has opposite sign]
224+
term -= cons3 * Qjkn[j, k, nd] / PCj[j] * G # Second part of T'G
235225
end
236226
end
237227
if Qjkn[k, j, n] > 0 # Flows in the direction of j
@@ -330,97 +320,3 @@ function recover_allocation_duality_cgc(x, auxdata)
330320
return (Pjn=Pjn, PCj=PCj, Ljn=Ljn, Yjn=Yjn, cj=cj, Cj=Cj, Dj=Dj, Djn=Djn, Qjk=Qjk, Qjkn=Qjkn)
331321
end
332322

333-
334-
335-
# # Hessian Experimental:
336-
# # These are the lagrange multipliers = prices
337-
# Lambda = repeat(x, 1, graph.J * param.N) # P^n_j: each column is a price, the rows should be the derivatives (P^n'_k)
338-
# lambda = reshape(x, (graph.J, param.N))
339-
340-
# # Compute price index
341-
# P = sum(lambda .^ (1 - param.sigma), dims=2) .^ (1 / (1 - param.sigma))
342-
# mat_P = repeat(P, param.N, param.N * graph.J)
343-
344-
# # Create masks
345-
# # This lets different products in the same location relate
346-
# Iij = kron(ones(param.N, param.N), I(graph.J))
347-
# # This is adjacency, but only for the same product (n == n')
348-
# Inm = kron(I(param.N), graph.adjacency)
349-
350-
# # Compute Qjkn terms for Hessian
351-
# Qjknprime = zeros(graph.J, graph.J, param.N)
352-
353-
354-
355-
356-
357-
# diff = Lambda' - Lambda # P^n'_k - P^n_j
358-
# mat_kappa = repeat(kappa, param.N, param.N)
359-
# mN = repeat(param.m, inner = graph.J)
360-
# # Rows are the derivatives, columns are P^n_j
361-
# PCjN = repeat(res.PCj, param.N)
362-
# A = mat_kappa ./ ((1+param.beta) * mN .* PCjN)'
363-
# # Adding term for (block-digonal) elements
364-
# temp = diff .* (x .^ (-param.sigma) .* PCjN .^ (param.sigma - 1))' .- 1
365-
# temp[Iij .== 0] .= 1
366-
# Aprime = A .* temp
367-
# temp = diff .* Inm
368-
# temp[Inm .== 0] .= 1
369-
# A .*= temp
370-
371-
# BA = A .^ (1/(nu-1))
372-
# BprimeAAprime = (1/(nu-1)) * A .^ (nu/(1-nu)) .* Aprime
373-
# BprimeAAprime[isnan.(BprimeAAprime)] .= 0
374-
375-
# C = repeat(res.Qjk .^ ((nu-param.beta-1)/(nu-1)), param.N, param.N)
376-
# Cprime = repeat(((nu-param.beta-1)/(nu-1)) * res.Qjk .^ (param.beta/(1-nu)), param.N, param.N)
377-
378-
# Qjknprime = BA .* Cprime .* BprimeAAprime .* mN # Off-diagonal (n') part
379-
# Qjknprime[Inm] += BprimeAAprime[Inm] .* C[Inm] # Adding diagonal
380-
# # Compute Qjkn Sums
381-
382-
# # Derivatives of Qjk
383-
# # Result should be nedg * N
384-
# Qjkprime = zeros(graph.J, graph.J, param.N)
385-
# temp = kappa ./ ((1 + param.beta) * res.PCj)
386-
# for n in 1:param.N
387-
# Lambda = repeat(Pjn[:, n], 1, graph.J)
388-
# Qjkprime[:,:,n] = m[n] / param.beta * res.Qjk .^ ((nu-nu*param.beta-1)/(nu-1))
389-
# LL = Lambda' - Lambda # P^n_k - P^n_j
390-
# LL[.!graph.adjacency] .= 0
391-
# Qjkprime[:,:,n] .*= ((LL .* temp) / m[n]) .^ (1/(nu-1)) # A^(1/(nu-1))
392-
# Qjkprime[:,:,n] .*= temp / m[n] # A'(P^n_k)
393-
# tril()
394-
# end
395-
# Qjk = (Qjk .* temp) .^ (nu-1)/(nu*param.beta)
396-
397-
# Qjkprime = res.Qjk[graph.adjacency]
398-
399-
400-
# # This is for Djn, the standard trade part (excluding trade costs)
401-
# termA = -param.sigma * (repeat(P, param.N) .^ param.sigma .* x .^ (-(param.sigma + 1)) .* repeat(res.Cj, param.N))
402-
# part1 = Iij .* Lambda .^ (-param.sigma) .* Lambda' .^ (-param.sigma) .* mat_P .^ (2 * param.sigma)
403-
# termB = param.sigma * part1 ./ mat_P .* repeat(res.Cj, param.N, graph.J * param.N)
404-
# termC = part1 .* repeat(graph.Lj ./ (graph.omegaj .* param.usecond.(res.cj, graph.hj)), param.N, graph.J * param.N)
405-
# Cjn = Diagonal(termA[:]) + termB + termC
406-
407-
# # Now comes the part of Djn relating to trade costs
408-
# Djn_costs =
409-
410-
411-
# Djn = Cjn + Djn_costs
412-
413-
414-
# # This is related to the flows terms in the standard case
415-
416-
# # Off-diagonal P^n_k terms
417-
# termD = 1 / (param.beta * (1 + param.beta)^(1 / param.beta)) * Inm .* mat_kappa .^ (1 / param.beta) .*
418-
# abs.(diff) .^ (1 / param.beta - 1) .*
419-
# ((diff .> 0) .* Lambda' ./ Lambda .^ (1 + 1 / param.beta) +
420-
# (diff .< 0) .* Lambda ./ Lambda' .^ (1 + 1 / param.beta))
421-
# # Diagonal P^n_j terms: sum across kappa
422-
# termE = -1 / (param.beta * (1 + param.beta)^(1 / param.beta)) * Inm .* mat_kappa .^ (1 / param.beta) .*
423-
# abs.(diff) .^ (1 / param.beta - 1) .*
424-
# ((diff .> 0) .* Lambda' .^ 2 ./ Lambda .^ (2 + 1 / param.beta) +
425-
# (diff .< 0) ./ Lambda' .^ (1 / param.beta))
426-
# termE = sum(termE, dims=2)

0 commit comments

Comments
 (0)