Skip to content

Commit fc14b0b

Browse files
authored
Merge pull request #43 from OptimalTransportNetworks/development
Development
2 parents 2081d82 + ec61605 commit fc14b0b

File tree

2 files changed

+665
-0
lines changed

2 files changed

+665
-0
lines changed
Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
using OptimalTransportNetworks
2+
import OptimalTransportNetworks as otn
3+
using LinearAlgebra
4+
using SparseArrays
5+
using ForwardDiff # Numeric Solutions
6+
7+
# Model Parameters
8+
param = init_parameters(labor_mobility = false, K = 10, gamma = 1, beta = 1, verbose = true,
9+
N = 3, cross_good_congestion = true, nu = 2, rho = 0, duality = true) # , tol = 1e-4)
10+
# Init network
11+
map_size = 4
12+
if map_size == 11
13+
graph = create_graph(param, 11, 11, type = "map") # create a map network of 11x11 nodes located in [0,10]x[0,10]
14+
# Customize graph
15+
graph[:Zjn] = fill(0.1, graph[:J], param[:N]) # set most places to low productivity
16+
Ni = find_node(graph, 6, 6) # Find index of the central node at (6,6)
17+
graph[:Zjn][Ni, 1] = 2 # central node more productive
18+
Ni = find_node(graph, 8, 2)
19+
graph[:Zjn][Ni, 2] = 1
20+
Ni = find_node(graph, 1, 10)
21+
graph[:Zjn][Ni, 3] = 1
22+
end
23+
if map_size == 4
24+
graph = create_graph(param, 4, 4, type = "map")
25+
# Customize graph
26+
graph[:Zjn] = fill(0.1, graph[:J], param[:N])
27+
Ni = find_node(graph, 2, 3)
28+
graph[:Zjn][Ni, 1] = 2
29+
Ni = find_node(graph, 2, 1)
30+
graph[:Zjn][Ni, 2] = 1
31+
Ni = find_node(graph, 4, 4)
32+
graph[:Zjn][Ni, 3] = 1
33+
end
34+
if map_size == 3
35+
graph = create_graph(param, 3, 3, type = "map")
36+
# Customize graph
37+
graph[:Zjn] = fill(0.1, graph[:J], param[:N])
38+
Ni = find_node(graph, 2, 3)
39+
graph[:Zjn][Ni, 1] = 2
40+
Ni = find_node(graph, 2, 1)
41+
graph[:Zjn][Ni, 2] = 1
42+
Ni = find_node(graph, 3, 3)
43+
graph[:Zjn][Ni, 3] = 1
44+
end
45+
46+
# Get Model
47+
# param[:optimizer_attr] = Dict(:hsllib => "/usr/local/lib/libhsl.dylib", :linear_solver => "ma57")
48+
param[:duality] = true # Change to see dual/non-dual models
49+
50+
RN = param[:N] > 1 ? range(1, 2, length=param[:N])' : 1
51+
x0 = vec(range(1, 2, length=graph[:J]) * RN)
52+
53+
edges = otn.represent_edges(otn.dict_to_namedtuple(graph))
54+
I0 = Float64.(graph[:adjacency])
55+
I0 *= param[:K] / sum(graph[:delta_i] .* I0) #
56+
# I0 = rescale_network!(param, graph, I0, Il, Iu)
57+
58+
# --------------
59+
# INITIALIZATION
60+
61+
auxdata = otn.create_auxdata(otn.dict_to_namedtuple(param), otn.dict_to_namedtuple(graph), edges, I0)
62+
63+
# Recover allocation
64+
function recover_allocation_duality(x, auxdata)
65+
param = auxdata.param
66+
graph = auxdata.graph
67+
omegaj = graph.omegaj
68+
kappa = auxdata.kappa
69+
Lj = graph.Lj
70+
hj1malpha = (graph.hj / (1-param.alpha)) .^ (1-param.alpha)
71+
72+
# Extract price vectors
73+
Pjn = reshape(x, (graph.J, param.N))
74+
PCj = sum(Pjn .^ (1 - param.sigma), dims=2) .^ (1 / (1 - param.sigma))
75+
76+
# Calculate labor allocation
77+
if param.a < 1
78+
temp = (Pjn .* graph.Zjn) .^ (1 / (1 - param.a))
79+
Ljn = temp .* (Lj ./ sum(temp, dims=2))
80+
Ljn[graph.Zjn .== 0] .= 0
81+
else
82+
_, max_id = findmax(Pjn .* graph.Zjn, dims=2)
83+
Ljn = zeros(graph.J, param.N)
84+
Ljn[CartesianIndex.(1:graph.J, getindex.(max_id, 2))] .= Lj
85+
end
86+
Yjn = graph.Zjn .* (Ljn .^ param.a)
87+
88+
# Calculate consumption
89+
cj = param.alpha * (PCj ./ omegaj) .^
90+
(-1 / (1 + param.alpha * (param.rho - 1))) .*
91+
hj1malpha .^ (-(param.rho - 1)/(1 + param.alpha * (param.rho - 1)))
92+
93+
# This is = PCj
94+
zeta = omegaj .* ((cj / param.alpha) .^ param.alpha .* hj1malpha) .^ (-param.rho) .*
95+
((cj / param.alpha) .^ (param.alpha - 1) .* hj1malpha)
96+
97+
cjn = (Pjn ./ zeta) .^ (-param.sigma) .* cj
98+
Cj = cj .* Lj
99+
Cjn = cjn .* Lj
100+
101+
# Calculate the flows Qjkn
102+
Qjkn = fill(zero(eltype(Pjn)), graph.J, graph.J, param.N)
103+
nadj = findall(.!graph.adjacency)
104+
@inbounds for n in 1:param.N
105+
Lambda = repeat(Pjn[:, n], 1, graph.J)
106+
# Note: max(P^n_k/P^n_j-1, 0) = max(P^n_k-P^n_j, 0)/P^n_j
107+
LL = max.(Lambda' - Lambda, 0) # P^n_k - P^n_j
108+
LL[nadj] .= 0
109+
Qjkn[:, :, n] = (1 / (1 + param.beta) * kappa .* LL ./ Lambda) .^ (1 / param.beta)
110+
end
111+
112+
return (Pjn=Pjn, PCj=PCj, Ljn=Ljn, Yjn=Yjn, cj=cj, cjn=cjn, Cj=Cj, Cjn=Cjn, Qjkn=Qjkn)
113+
end
114+
115+
116+
# Objective function
117+
function objective_duality(x, auxdata)
118+
param = auxdata.param
119+
graph = auxdata.graph
120+
121+
res = recover_allocation_duality(x, auxdata)
122+
Pjn = reshape(x, (graph.J, param.N))
123+
124+
# Compute transportation cost
125+
cost = res.Qjkn .^ (1 + param.beta) ./ repeat(auxdata.kappa, 1, 1, param.N)
126+
cost[res.Qjkn .== 0] .= 0 # Deal with cases Qjkn=kappa=0
127+
128+
# Compute constraint
129+
cons = res.cjn .* graph.Lj + dropdims(sum(res.Qjkn + cost - permutedims(res.Qjkn, (2, 1, 3)), dims=2), dims = 2) - res.Yjn
130+
cons = sum(Pjn .* cons, dims=2)
131+
132+
# Compute objective value
133+
f = sum(graph.omegaj .* graph.Lj .* param.u.(res.cj, graph.hj)) - sum(cons)
134+
return f # Negative objective because Ipopt minimizes?
135+
end
136+
137+
138+
function objective(x)
139+
return objective_duality(x, auxdata)
140+
end
141+
142+
objective(x0)
143+
144+
# Gradient function = negative constraint
145+
function gradient_duality(x::Vector{Float64}, auxdata)
146+
param = auxdata.param
147+
graph = auxdata.graph
148+
kappa = auxdata.kappa
149+
150+
res = recover_allocation_duality(x, auxdata)
151+
152+
# Compute transportation cost
153+
cost = res.Qjkn .^ (1 + param.beta) ./ repeat(kappa, 1, 1, param.N)
154+
cost[res.Qjkn .== 0] .= 0 # Deal with cases Qjkn=kappa=0
155+
156+
# Compute constraint
157+
cons = res.cjn .* graph.Lj + dropdims(sum(res.Qjkn + cost - permutedims(res.Qjkn, (2, 1, 3)), dims=2), dims = 2) - res.Yjn
158+
159+
return -cons[:] # Flatten the array and store in grad_f
160+
end
161+
162+
163+
gradient_duality(x0, auxdata)
164+
165+
# Numeric Solutions
166+
ForwardDiff.gradient(objective, x0)
167+
168+
# Difference
169+
gradient_duality(x0, auxdata) ./ ForwardDiff.gradient(objective, x0)
170+
171+
172+
# Now the Hessian
173+
function hessian_structure_duality(auxdata)
174+
graph = auxdata.graph
175+
param = auxdata.param
176+
177+
# Create the Hessian structure
178+
H_structure = tril(repeat(sparse(I(graph.J)), param.N, param.N) + kron(sparse(I(param.N)), sparse(graph.adjacency)))
179+
180+
# Get the row and column indices of non-zero elements
181+
rows, cols, _ = findnz(H_structure)
182+
return rows, cols
183+
end
184+
185+
function hessian_duality(
186+
x::Vector{Float64},
187+
rows::Vector{Int64},
188+
cols::Vector{Int64},
189+
values::Vector{Float64},
190+
auxdata
191+
)
192+
# function hessian(x, auxdata, obj_factor, lambda)
193+
param = auxdata.param
194+
graph = auxdata.graph
195+
nodes = graph.nodes
196+
beta = param.beta
197+
m1dbeta = -1 / beta
198+
sigma = param.sigma
199+
a = param.a
200+
adam1 = a / (a - 1)
201+
J = graph.J
202+
Zjn = graph.Zjn
203+
Lj = graph.Lj
204+
omegaj = graph.omegaj
205+
206+
# Precompute elements
207+
res = recover_allocation_duality(x, auxdata)
208+
Pjn = res.Pjn
209+
PCj = res.PCj
210+
Qjkn = res.Qjkn
211+
212+
# Prepare computation of production function
213+
if a != 1
214+
psi = (Pjn .* Zjn) .^ (1 / (1 - a))
215+
Psi = sum(psi, dims=2)
216+
end
217+
218+
# Now the big loop over the hessian terms to compute
219+
ind = 0
220+
221+
# https://stackoverflow.com/questions/38901275/inbounds-propagation-rules-in-julia
222+
@inbounds for (jdnd, jn) in zip(rows, cols)
223+
ind += 1
224+
# First get the indices of the element and the respective derivative
225+
j = (jn-1) % J + 1
226+
n = Int(ceil(jn / J))
227+
jd = (jdnd-1) % J + 1
228+
nd = Int(ceil(jdnd / J))
229+
# Get neighbors k
230+
neighbors = nodes[j]
231+
232+
# Stores the derivative term
233+
term = 0.0
234+
235+
# Starting with C^n_j = CG, derivative: C'G + CG'
236+
if jd == j # 0 for P^n_k
237+
Cprime = Lj[j]/omegaj[j] * (PCj[j] / Pjn[j, nd])^sigma / param.usecond(res.cj[j], graph.hj[j]) # param.uprimeinvprime(PCj[j]/omegaj[j], graph.hj[j])
238+
G = (Pjn[j, n] / PCj[j])^(-sigma)
239+
Gprime = sigma * (Pjn[j, n] * Pjn[j, nd])^(-sigma) * PCj[j]^(2*sigma-1)
240+
if nd == n
241+
Gprime -= sigma / PCj[j] * G^((sigma+1)/sigma)
242+
end
243+
term += Cprime * G + res.Cj[j] * Gprime
244+
end
245+
246+
# Now terms sum_k((Q^n_{jk} + K_{jk}(Q^n_{jk})^(1+beta)) - Q^n_{kj})
247+
if nd == n
248+
for k in neighbors
249+
if jd == j # P^x_j
250+
diff = Pjn[k, n] - Pjn[j, n]
251+
if diff >= 0 # Flows in the direction of k
252+
term += m1dbeta * (Pjn[k, n] / Pjn[j, n])^2 * Qjkn[j, k, n] / diff
253+
else # Flows in the direction of j
254+
term += m1dbeta * Qjkn[k, j, n] / abs(diff)
255+
end
256+
elseif jd == k # P^x_k
257+
diff = Pjn[k, n] - Pjn[j, n]
258+
if diff >= 0 # Flows in the direction of k
259+
term -= m1dbeta * Pjn[k, n] / Pjn[j, n] * Qjkn[j, k, n] / diff
260+
else # Flows in the direction of j
261+
term -= m1dbeta * Pjn[j, n] / Pjn[k, n] * Qjkn[k, j, n] / abs(diff)
262+
end
263+
end
264+
end # End of k loop
265+
end
266+
267+
# Finally: need to compute production function (X)
268+
if a != 1 && jd == j
269+
X = adam1 * Zjn[j, n] * Zjn[j, nd] * (psi[j, n] * psi[j, nd])^a / Psi[j]^(a+1) * Lj[j]^a
270+
if nd == n
271+
X *= 1 - Psi[j] / psi[j, n]
272+
end
273+
term -= X
274+
end
275+
276+
# Assign result
277+
values[ind] = -term
278+
end
279+
return values
280+
end
281+
282+
rows, cols = hessian_structure_duality(auxdata)
283+
cind = CartesianIndex.(rows, cols)
284+
values = zeros(length(cind))
285+
286+
hessian_duality(x0, rows, cols, values, auxdata)
287+
ForwardDiff.hessian(objective, x0)[cind]
288+
289+
# findnz(sparse(ForwardDiff.hessian(objective, x0)))[1]
290+
# extrema(abs.(findnz(sparse(ForwardDiff.hessian(objective, x0)))[3]))
291+
# sum(abs.(ForwardDiff.hessian(objective, x0)) .> 0.01)
292+
293+
ForwardDiff.hessian(objective, x0)
294+
H = zeros(length(x0), length(x0))
295+
H[cind] = hessian_duality(x0, rows, cols, values, auxdata)
296+
H
297+
298+
# Ratios
299+
hessian_duality(x0, rows, cols, values, auxdata) ./ ForwardDiff.hessian(objective, x0)[cind]
300+
extrema(hessian_duality(x0, rows, cols, values, auxdata) ./ ForwardDiff.hessian(objective, x0)[cind])
301+
302+
303+
304+
305+
306+

0 commit comments

Comments
 (0)