Skip to content

Commit 3a84b94

Browse files
committed
Significant performance increase through iterative (sparse) method of computing the hessian.
1 parent 45ccb94 commit 3a84b94

1 file changed

Lines changed: 86 additions & 67 deletions

File tree

src/models/solve_allocation_by_duality.jl

Lines changed: 86 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,8 @@ function solve_allocation_by_duality(x0, auxdata, verbose=true)
3232
n = graph.J * param.N
3333

3434
# Get the Hessian structure
35-
hess_str = hessian_structure_duality(auxdata)
36-
auxdata = (auxdata..., hess = hess_str, hess_ind = CartesianIndex.(hess_str[1], hess_str[2]))
37-
nnz_hess = length(hess_str[1])
38-
35+
auxdata = (auxdata..., hess = hessian_structure_duality(auxdata))
36+
nnz_hess = length(auxdata.hess[1])
3937

4038
prob = Ipopt.CreateIpoptProblem(
4139
n,
@@ -149,81 +147,101 @@ function hessian_duality(
149147
auxdata
150148
)
151149
if values === nothing
152-
# # Return the sparsity structure
153-
# nz = length(findnz(tril(h))[1])
154-
# resize!(rows, nz)
155-
# resize!(cols, nz)
156-
# r, c, _ = findnz(tril(h))
157150
r, c = auxdata.hess
158151
rows .= r
159152
cols .= c
160153
else
161154
# function hessian(x, auxdata, obj_factor, lambda)
162155
param = auxdata.param
163156
graph = auxdata.graph
157+
nodes = graph.nodes
164158
kappa = auxdata.kappa
165-
159+
beta = param.beta
160+
m1dbeta = -1 / beta
161+
sigma = param.sigma
162+
a = param.a
163+
adam1 = a / (a - 1)
164+
J = graph.J
165+
Zjn = graph.Zjn
166+
Lj = graph.Lj
167+
omegaj = graph.omegaj
168+
# Constant term for Q'^n_{jk}
169+
cons = m1dbeta / (1 + beta)
170+
hess_str = auxdata.hess
171+
172+
# Precompute elements
166173
res = recover_allocation_duality(x, auxdata)
167-
Lambda = repeat(x, 1, graph.J * param.N)
168-
lambda = reshape(x, (graph.J, param.N))
169-
170-
# Compute price index
171-
P = sum(lambda .^ (1 - param.sigma), dims=2) .^ (1 / (1 - param.sigma))
172-
mat_P = repeat(P, param.N, param.N * graph.J)
173-
174-
# Create masks
175-
# This lets different products in the same location relate
176-
Iij = kron(ones(param.N, param.N), I(graph.J))
177-
# This is adjacency, but only for the same product (n == n')
178-
Inm = kron(I(param.N), graph.adjacency)
174+
Pjn = res.Pjn
175+
PCj = res.PCj
176+
Qjkn = res.Qjkn
177+
178+
# Prepare computation of production function
179+
if a != 1
180+
psi = (Pjn .* Zjn) .^ (1 / (1 - a))
181+
Psi = sum(psi, dims=2)
182+
end
179183

180-
# Compute terms for Hessian
181-
termA = -param.sigma * (repeat(P, param.N) .^ param.sigma .* x .^ (-(param.sigma + 1)) .* repeat(res.Cj, param.N))
184+
# Now the big loop over the hessian terms to compute
185+
ind = 0
186+
187+
# https://stackoverflow.com/questions/38901275/inbounds-propagation-rules-in-julia
188+
@inbounds for (jdnd, jn) in zip(hess_str[1], hess_str[2])
189+
ind += 1
190+
# First get the indices of the element and the respective derivative
191+
j = (jn-1) % J + 1
192+
n = Int(ceil(jn / J))
193+
jd = (jdnd-1) % J + 1
194+
nd = Int(ceil(jdnd / J))
195+
# Get neighbors k
196+
neighbors = nodes[j]
197+
198+
# Stores the derivative term
199+
term = 0.0
200+
201+
# Starting with C^n_j = CG, derivative: C'G + CG'
202+
if jd == j # 0 for P^n_k
203+
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])
204+
G = (Pjn[j, n] / PCj[j])^(-sigma)
205+
Gprime = sigma * (Pjn[j, n] * Pjn[j, nd])^(-sigma) * PCj[j]^(2*sigma-1)
206+
if nd == n
207+
Gprime -= sigma / PCj[j] * G^((sigma+1)/sigma)
208+
end
209+
term += Cprime * G + res.Cj[j] * Gprime
210+
end
182211

183-
part1 = Iij .* Lambda .^ (-param.sigma) .* Lambda' .^ (-param.sigma) .* mat_P .^ (2 * param.sigma)
212+
# Now terms sum_k((Q^n_{jk} + K_{jk}(Q^n_{jk})^(1+beta)) - Q^n_{kj})
213+
if nd == n
214+
for k in neighbors
215+
if jd == j # P^x_j
216+
diff = Pjn[k, n] - Pjn[j, n]
217+
if diff >= 0
218+
term += m1dbeta * (Pjn[k, n] / Pjn[j, n])^2 * Qjkn[j, k, n] / diff
219+
else
220+
term += m1dbeta * Qjkn[k, j, n] / abs(diff)
221+
end
222+
elseif jd == k # P^x_k
223+
diff = Pjn[k, n] - Pjn[j, n]
224+
if diff >= 0
225+
term -= m1dbeta * Pjn[k, n] / Pjn[j, n] * Qjkn[j, k, n] / diff
226+
else
227+
term -= m1dbeta * Pjn[j, n] / Pjn[k, n] * Qjkn[k, j, n] / abs(diff)
228+
end
229+
end
230+
end # End of k loop
231+
end
184232

185-
termB = param.sigma * part1 ./ mat_P .* repeat(res.Cj, param.N, graph.J * param.N)
186-
187-
termC = part1 .* repeat(graph.Lj ./ (graph.omegaj .* param.usecond.(res.cj, graph.hj)), param.N, graph.J * param.N)
188-
189-
diff = Lambda' - Lambda # P^n_k - P^n_j
190-
mat_kappa = repeat(kappa, param.N, param.N)
191-
part1 = 1 / (param.beta * (1 + param.beta)^(1 / param.beta)) * Inm .* mat_kappa .^ (1 / param.beta)
192-
abs_diff_1betam1 = abs.(diff) .^ (1 / param.beta - 1)
193-
diffpos = diff .> 0
194-
diffneg = diff .< 0
195-
196-
termD = part1 .* abs_diff_1betam1 .*
197-
(diffpos .* Lambda' ./ Lambda .^ (1 + 1 / param.beta) +
198-
diffneg .* Lambda ./ Lambda' .^ (1 + 1 / param.beta))
199-
200-
termE = -part1 .* abs_diff_1betam1 .*
201-
(diffpos .* Lambda' .^ 2 ./ Lambda .^ (2 + 1 / param.beta) +
202-
diffneg ./ Lambda' .^ (1 / param.beta))
203-
termE = sum(termE, dims=2)
204-
205-
# Compute labor term
206-
if param.a == 1
207-
X = 0
208-
else
209-
# This is Psi in your notes
210-
denom = sum((lambda .* graph.Zjn) .^ (1 / (1 - param.a)), dims=2)
211-
num = (x .* graph.Zjn[:]) .^ (1 / (1 - param.a))
212-
# Non-diagonal elements: using 1-a in denominator because output is subtracted
213-
X = param.a / (1 - param.a) * Iij .* (graph.Zjn[:] * graph.Zjn[:]') .* (num * num') .^ param.a .*
214-
repeat(graph.Lj .^ param.a ./ denom .^ (1 + param.a), param.N, graph.J * param.N)
215-
# The term that is multiplied to get the diagonal
216-
X_diag = (num - repeat(denom, param.N)) ./ num
217-
# Adding the diagonal
218-
@inbounds for i in 1:length(X_diag)
219-
X[i, i] *= X_diag[i]
233+
# Finally: need to compute production function (X)
234+
if a != 1 && jd == j
235+
X = adam1 * Zjn[j, n] * Zjn[j, nd] * (psi[j, n] * psi[j, nd])^a / Psi[j]^(a+1) * Lj[j]^a
236+
if nd == n
237+
X *= 1 - Psi[j] / psi[j, n]
238+
end
239+
term -= X
220240
end
241+
242+
# Assign result
243+
values[ind] = -obj_factor * term + 1e-5 # Somehow need this increment to make it work
221244
end
222-
# Compute Hessian
223-
h = -obj_factor * (Diagonal(termA[:]) + termB + termC + termD + Diagonal(termE[:]) + X)
224-
225-
# Return lower triangular part
226-
values .= h[auxdata.hess_ind]
227245
end
228246
return
229247
end
@@ -268,11 +286,12 @@ function recover_allocation_duality(x, auxdata)
268286

269287
# Calculate the flows Qjkn
270288
Qjkn = zeros(graph.J, graph.J, param.N)
271-
for n in 1:param.N
289+
nadj = findall(.!graph.adjacency)
290+
@inbounds for n in 1:param.N
272291
Lambda = repeat(Pjn[:, n], 1, graph.J)
273292
# Note: max(P^n_k/P^n_j-1, 0) = max(P^n_k-P^n_j, 0)/P^n_j
274293
LL = max.(Lambda' - Lambda, 0) # P^n_k - P^n_j
275-
LL[.!graph.adjacency] .= 0
294+
LL[nadj] .= 0
276295
Qjkn[:, :, n] = (1 / (1 + param.beta) * kappa .* LL ./ Lambda) .^ (1 / param.beta)
277296
end
278297

0 commit comments

Comments
 (0)