Skip to content

Commit e3498a1

Browse files
committed
Add dual solution experimental files.
1 parent 2078abb commit e3498a1

1 file changed

Lines changed: 359 additions & 0 deletions

File tree

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
2+
using OptimalTransportNetworks
3+
import OptimalTransportNetworks as otn
4+
using LinearAlgebra
5+
using SparseArrays
6+
using ForwardDiff # Numeric Solutions
7+
8+
# Model Parameters
9+
param = init_parameters(labor_mobility = false, K = 10, gamma = 1, beta = 1, verbose = true,
10+
N = 3, cross_good_congestion = true, nu = 2, rho = 0, duality = true) # , tol = 1e-4)
11+
# Init network
12+
map_size = 4
13+
if map_size == 11
14+
graph = create_graph(param, 11, 11, type = "map") # create a map network of 11x11 nodes located in [0,10]x[0,10]
15+
# Customize graph
16+
graph[:Zjn] = fill(0.1, graph[:J], param[:N]) # set most places to low productivity
17+
Ni = find_node(graph, 6, 6) # Find index of the central node at (6,6)
18+
graph[:Zjn][Ni, 1] = 2 # central node more productive
19+
Ni = find_node(graph, 8, 2)
20+
graph[:Zjn][Ni, 2] = 1
21+
Ni = find_node(graph, 1, 10)
22+
graph[:Zjn][Ni, 3] = 1
23+
end
24+
if map_size == 4
25+
graph = create_graph(param, 4, 4, type = "map")
26+
# Customize graph
27+
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
34+
end
35+
if map_size == 3
36+
graph = create_graph(param, 3, 3, type = "map")
37+
# Customize graph
38+
graph[:Zjn] = fill(0.1, graph[:J], param[:N])
39+
Ni = find_node(graph, 2, 3)
40+
graph[:Zjn][Ni, 1] = 2
41+
Ni = find_node(graph, 2, 1)
42+
graph[:Zjn][Ni, 2] = 1
43+
Ni = find_node(graph, 3, 3)
44+
graph[:Zjn][Ni, 3] = 1
45+
end
46+
47+
# Get Model
48+
# param[:optimizer_attr] = Dict(:hsllib => "/usr/local/lib/libhsl.dylib", :linear_solver => "ma57")
49+
param[:duality] = true # Change to see dual/non-dual models
50+
51+
RN = param[:N] > 1 ? range(1, 2, length=param[:N])' : 1
52+
x0 = vec(range(1, 2, length=graph[:J]) * RN)
53+
54+
edges = otn.represent_edges(otn.dict_to_namedtuple(graph))
55+
I0 = Float64.(graph[:adjacency])
56+
I0 *= param[:K] / sum(graph[:delta_i] .* I0) #
57+
# I0 = rescale_network!(param, graph, I0, Il, Iu)
58+
59+
# --------------
60+
# INITIALIZATION
61+
62+
auxdata = otn.create_auxdata(otn.dict_to_namedtuple(param), otn.dict_to_namedtuple(graph), edges, I0)
63+
64+
# Recover allocation
65+
# Function to recover allocation from optimization variables
66+
function recover_allocation_duality_cgc(x, auxdata)
67+
param = auxdata.param
68+
graph = auxdata.graph
69+
omegaj = graph.omegaj
70+
kappa = auxdata.kappa
71+
Lj = graph.Lj
72+
m = param.m
73+
nu = param.nu
74+
beta = param.beta
75+
hj1malpha = (graph.hj / (1-param.alpha)) .^ (1-param.alpha)
76+
nadj = .!graph.adjacency
77+
78+
# Extract price vectors
79+
Pjn = reshape(x, (graph.J, param.N))
80+
PCj = sum(Pjn .^ (1 - param.sigma), dims=2) .^ (1 / (1 - param.sigma))
81+
82+
# Calculate labor allocation
83+
if param.a < 1
84+
temp = (Pjn .* graph.Zjn) .^ (1 / (1 - param.a))
85+
Ljn = temp .* (Lj ./ sum(temp, dims=2))
86+
Ljn[graph.Zjn .== 0] .= 0
87+
else
88+
_, max_id = findmax(Pjn .* graph.Zjn, dims=2)
89+
Ljn = zeros(graph.J, param.N)
90+
Ljn[CartesianIndex.(1:graph.J, getindex.(max_id, 2))] .= Lj
91+
end
92+
Yjn = graph.Zjn .* (Ljn .^ param.a)
93+
94+
# Calculate aggregate consumption
95+
cj = param.alpha * (PCj ./ omegaj) .^
96+
(-1 / (1 + param.alpha * (param.rho - 1))) .*
97+
hj1malpha .^ (-(param.rho - 1)/(1 + param.alpha * (param.rho - 1)))
98+
Cj = cj .* Lj
99+
100+
# Calculate the aggregate flows Qjk
101+
temp = kappa ./ ((1 + beta) * PCj)
102+
temp[nadj] .= 0
103+
Qjk = fill(zero(eltype(temp)), graph.J, graph.J)
104+
for n in 1:param.N
105+
Lambda = repeat(Pjn[:, n], 1, graph.J)
106+
LL = max.(Lambda' - Lambda, 0) # P^n_k - P^n_j (non-negative flows)
107+
Qjk += m[n] * (LL / m[n]) .^ (nu/(nu-1))
108+
end
109+
Qjk .^= ((nu-1)/nu)
110+
Qjk .*= temp
111+
Qjk .^= (1/beta)
112+
113+
# Calculate the flows Qjkn
114+
temp = kappa ./ ((1 + beta) * PCj .* Qjk .^ (1+beta-nu))
115+
Qjkn = fill(zero(eltype(temp)), graph.J, graph.J, param.N)
116+
temp[nadj .| .!isfinite.(temp)] .= 0 # Because of the max() clause, Qjk may be zero
117+
for n in 1:param.N
118+
Lambda = repeat(Pjn[:, n], 1, graph.J)
119+
LL = max.(Lambda' - Lambda, 0) # P^n_k - P^n_j (non-negative flows)
120+
Qjkn[:, :, n] = ((LL .* temp) / m[n]) .^ (1/(nu-1))
121+
end
122+
123+
# Now calculating consumption bundle pre-transport cost
124+
temp = Qjk .^ (1+beta) ./ kappa
125+
temp[nadj] .= 0
126+
Dj = Cj + sum(temp, dims = 2)
127+
Djn = Dj .* (Pjn ./ PCj) .^ (-param.sigma)
128+
129+
return (Pjn=Pjn, PCj=PCj, Ljn=Ljn, Yjn=Yjn, cj=cj, Cj=Cj, Dj=Dj, Djn=Djn, Qjk=Qjk, Qjkn=Qjkn)
130+
end
131+
132+
# Objective function
133+
function objective_duality_cgc(x, auxdata)
134+
graph = auxdata.graph
135+
res = recover_allocation_duality_cgc(x, auxdata)
136+
137+
# Compute constraint
138+
cons = res.Djn + dropdims(sum(res.Qjkn - permutedims(res.Qjkn, (2, 1, 3)), dims=2), dims = 2) - res.Yjn
139+
cons = sum(res.Pjn .* cons, dims=2)
140+
141+
# Compute objective value
142+
f = sum(graph.omegaj .* graph.Lj .* auxdata.param.u.(res.cj, graph.hj)) - sum(cons)
143+
144+
return f # Negative objective because Ipopt minimizes?
145+
end
146+
147+
148+
function objective(x)
149+
return objective_duality_cgc(x, auxdata)
150+
end
151+
152+
objective(x0)
153+
154+
# Gradient function = negative constraint
155+
function gradient_duality_cgc(x::Vector{Float64}, auxdata)
156+
157+
res = recover_allocation_duality_cgc(x, auxdata)
158+
159+
# Compute constraint
160+
cons = res.Djn + dropdims(sum(res.Qjkn - permutedims(res.Qjkn, (2, 1, 3)), dims=2), dims = 2) - res.Yjn
161+
162+
return -cons[:] # Flatten the array and store in grad_f
163+
end
164+
165+
gradient_duality_cgc(x0, auxdata)
166+
167+
# Numeric Solution
168+
ForwardDiff.gradient(objective, x0)
169+
170+
# Ratio
171+
sum(abs.(gradient_duality_cgc(x0, auxdata) ./ ForwardDiff.gradient(objective, x0))) / length(x0)
172+
173+
174+
# Now the Hessian
175+
function hessian_structure_duality(auxdata)
176+
graph = auxdata.graph
177+
param = auxdata.param
178+
179+
# 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)))
181+
182+
# Get the row and column indices of non-zero elements
183+
rows, cols, _ = findnz(H_structure)
184+
return rows, cols
185+
end
186+
187+
function hessian_duality_cgc(
188+
x::Vector{Float64},
189+
rows::Vector{Int64},
190+
cols::Vector{Int64},
191+
values::Vector{Float64},
192+
auxdata;
193+
exact_algo = false
194+
)
195+
# function hessian(x, auxdata, obj_factor, lambda)
196+
param = auxdata.param
197+
graph = auxdata.graph
198+
nodes = graph.nodes
199+
kappa = auxdata.kappa
200+
beta = param.beta
201+
m1dbeta = -1 / beta
202+
sigma = param.sigma
203+
nu = param.nu
204+
m = param.m
205+
n1dnum1 = 1 / (nu - 1)
206+
numbetam1 = nu - beta - 1
207+
a = param.a
208+
adam1 = a / (a - 1)
209+
J = graph.J
210+
Zjn = graph.Zjn
211+
Lj = graph.Lj
212+
omegaj = graph.omegaj
213+
214+
# Constant term: first part of Bprime
215+
cons = m1dbeta * n1dnum1 * numbetam1
216+
217+
# # Constant in T'
218+
if !exact_algo
219+
cons2 = numbetam1 / (numbetam1+nu*beta)
220+
else
221+
# New: constant in T' -> more accurate
222+
cons3 = m1dbeta * n1dnum1 * (numbetam1+nu*beta) / (1+beta)
223+
end
224+
225+
# Precompute elements
226+
res = recover_allocation_duality_cgc(x, auxdata)
227+
Pjn = res.Pjn
228+
PCj = res.PCj
229+
Qjk = res.Qjk
230+
Qjkn = res.Qjkn
231+
232+
# Prepare computation of production function
233+
if a != 1
234+
psi = (Pjn .* Zjn) .^ (1 / (1 - a))
235+
Psi = sum(psi, dims=2)
236+
end
237+
238+
# Now the big loop over the hessian terms to compute
239+
ind = 0
240+
241+
# https://stackoverflow.com/questions/38901275/inbounds-propagation-rules-in-julia
242+
@inbounds for (jdnd, jn) in zip(rows, cols)
243+
ind += 1
244+
# First get the indices of the element and the respective derivative
245+
j = (jn-1) % J + 1
246+
n = Int(ceil(jn / J))
247+
jd = (jdnd-1) % J + 1
248+
nd = Int(ceil(jdnd / J))
249+
# Get neighbors k
250+
neighbors = nodes[j]
251+
252+
# Stores the derivative term
253+
term = 0.0
254+
255+
# Starting with D^n_j = (C+T)G, derivative: (C'+T')G + (C+T)G' = C'G + CG' + T'G + TG'
256+
# Terms involving T are computed below. Here forcus on C'G + CG'
257+
G = (Pjn[j, n] / PCj[j])^(-sigma)
258+
if jd == j # 0 for P^n_k
259+
Gprime = sigma * (Pjn[j, n] * Pjn[j, nd])^(-sigma) * PCj[j]^(2*sigma-1)
260+
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])
261+
if nd == n
262+
Gprime -= sigma / PCj[j] * G^((sigma+1)/sigma)
263+
end
264+
term += Cprime * G + res.Cj[j] * Gprime
265+
end
266+
267+
# Now terms sum_k(Q^n_{jk} - Q^n_{kj}) as well as T'G + TG'
268+
for k in neighbors
269+
# diff = Pjn[k, nd] - Pjn[j, nd]
270+
if Qjkn[j, k, n] > 0 # Flows in the direction of k
271+
T = Qjk[j, k]^(1 + beta) / kappa[j, k]
272+
PK0 = PCj[j] * (1 + beta) / kappa[j, k]
273+
KPABprime1 = cons * ((Pjn[k, nd] - Pjn[j, nd])/m[nd])^n1dnum1 * (PK0 * Qjk[j, k]^beta)^(-nu*n1dnum1)
274+
KPAprimeB1 = nd == n ? n1dnum1 / (Pjn[k, n] - Pjn[j, n]) : 0.0
275+
if jd == j
276+
KPprimeAB1 = m1dbeta * Pjn[j, nd]^(-sigma) * PCj[j]^(sigma-1)
277+
term += Qjkn[j, k, n] * (KPprimeAB1 - KPAprimeB1 + KPABprime1) # Derivative of Qjkn
278+
if !exact_algo
279+
term += T * (((1+beta) * KPprimeAB1 + cons2 * KPABprime1) * G + Gprime) # T'G + TG'
280+
else
281+
term += T * ((1+beta) * KPprimeAB1 * G + Gprime) # T'G (first part) + TG'
282+
term += cons3 * Qjkn[j, k, nd] / PCj[j] * G # Second part of T'G
283+
end
284+
elseif jd == k
285+
term += Qjkn[j, k, n] * (KPAprimeB1 - KPABprime1) # Derivative of Qjkn
286+
if !exact_algo
287+
term -= T * cons2 * KPABprime1 * G # T'G: second part [B'(k) has opposite sign]
288+
else
289+
term -= cons3 * Qjkn[j, k, nd] / PCj[j] * G # Second part of T'G
290+
end
291+
end
292+
end
293+
if Qjkn[k, j, n] > 0 # Flows in the direction of j
294+
PK0 = PCj[k] * (1 + beta) / kappa[k, j]
295+
KPABprime1 = cons * ((Pjn[j, nd] - Pjn[k, nd])/m[nd])^n1dnum1 * (PK0 * Qjk[k, j]^beta)^(-nu*n1dnum1)
296+
KPAprimeB1 = nd == n ? n1dnum1 / (Pjn[j, n] - Pjn[k, n]) : 0.0
297+
if jd == k
298+
KPprimeAB1 = m1dbeta * Pjn[k, nd]^(-sigma) * PCj[k]^(sigma-1)
299+
term -= Qjkn[k, j, n] * (KPprimeAB1 - KPAprimeB1 + KPABprime1) # Derivative of Qkjn
300+
elseif jd == j
301+
term -= Qjkn[k, j, n] * (KPAprimeB1 - KPABprime1) # Derivative of Qkjn
302+
end
303+
end
304+
end # End of k loop
305+
306+
# Finally: need to compute production function (X)
307+
if a != 1 && jd == j
308+
X = adam1 * Zjn[j, n] * Zjn[j, nd] * (psi[j, n] * psi[j, nd])^a / Psi[j]^(a+1) * Lj[j]^a
309+
if nd == n
310+
X *= 1 - Psi[j] / psi[j, n]
311+
end
312+
term -= X
313+
end
314+
315+
# Assign result
316+
values[ind] = -term # + 1e-6
317+
end
318+
return values
319+
end
320+
321+
rows, cols = hessian_structure_duality(auxdata)
322+
cind = CartesianIndex.(rows, cols)
323+
values = zeros(length(cind))
324+
325+
hessian_duality_cgc(x0, rows, cols, values, auxdata, exact_algo = true)
326+
ForwardDiff.hessian(objective, x0)[cind]
327+
328+
# findnz(sparse(ForwardDiff.hessian(objective, x0)))[1]
329+
# extrema(abs.(findnz(sparse(ForwardDiff.hessian(objective, x0)))[3]))
330+
331+
ForwardDiff.hessian(objective, x0)
332+
H = zeros(length(x0), length(x0))
333+
H[cind] = hessian_duality_cgc(x0, rows, cols, values, auxdata, exact_algo = true)
334+
H
335+
336+
# Ratios
337+
hessian_duality_cgc(x0, rows, cols, values, auxdata, exact_algo = true) ./ ForwardDiff.hessian(objective, x0)[cind]
338+
extrema(hessian_duality_cgc(x0, rows, cols, values, auxdata, exact_algo = true) ./ ForwardDiff.hessian(objective, x0)[cind])
339+
340+
# Differences
341+
ad = abs.(hessian_duality_cgc(x0, rows, cols, values, auxdata, exact_algo = true) - ForwardDiff.hessian(objective, x0)[cind])
342+
sum(ad)/length(ad)
343+
sum(ad .> 0.01)
344+
sort(ad, rev = true)[1:20]
345+
cr = rows[ad .> 0.01]
346+
cc = cols[ad .> 0.01]
347+
348+
349+
350+
351+
352+
353+
354+
355+
356+
357+
358+
359+

0 commit comments

Comments
 (0)