Skip to content

Commit 20f5f44

Browse files
authored
Merge pull request #15 from SebKrantz/main
Update devel
2 parents f65b8b6 + 272c89b commit 20f5f44

34 files changed

+598
-492
lines changed

NEWS.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# 0.1.6
2+
## Breaking Changes
3+
* All spatial parameters, including `Lj`, `Lr`, `Hj`, `hj`, `Zjn`, `omegaj`, `omegar`, and `region` are now stored in the `graph` structure created by `create_graph()`. `create_graph()` therefore only returns the `graph` structure, instead of both the (updated) parameters and the graph. Converesely, `init_parameters()` only contains parameters that are independent of the particular geography defined by `create_graph()`.
4+
5+
## Improvements
6+
* Minor improvements to Simulated Annealing.
7+
* Better spline options for plotting frictions surface (geography).
8+
* More faithful translation of `apply_geography()`.
9+
10+
11+
# 0.1.5
12+
13+
* Removed the MATLAB toolbox and corresponding documentation (PDF files) from the repo to decrease size. A new repo was created for the MATLAB toolbox at [SebKrantz/OptimalTransportNetworkToolbox](https://github.com/SebKrantz/OptimalTransportNetworkToolbox). This repo and especially the `docs` folder continue to be very useful for Julia users, but are no longer part of the Julia library.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,15 @@ import MathOptInterface as MOI
4242
import MathOptSymbolicAD
4343

4444
param[:model_attr] = Dict(:backend => (MOI.AutomaticDifferentiationBackend(),
45-
MathOptSymbolicAD.DefaultBackend()))
45+
MathOptSymbolicAD.DefaultBackend()))
4646
# Or: MathOptSymbolicAD.ThreadedBackend()
4747
```
4848

4949
* It is recommended to use Coin-HSL linear solvers for [Ipopt](https://github.com/jump-dev/Ipopt.jl) to speed up computations. In my opinion the simplest way to use them is to get a (free for academics) license and download the binaries [here](https://licences.stfc.ac.uk/product/coin-hsl), extract them somewhere, and then set the `hsllib` (place here the path to where you extracted `libhsl.dylib`, it may also be called `libcoinhsl.dylib`, in which case you may have to rename it to `libhsl.dylib`) and `linear_solver` options as follows:
5050

5151
```julia
5252
param[:optimizer_attr] = Dict(:hsllib => "/usr/local/lib/libhsl.dylib", # Adjust path
53-
:linear_solver => "ma57") # Use ma57, ma86 or ma97
53+
:linear_solver => "ma57") # Use ma57, ma86 or ma97
5454
```
5555

5656
The [Ipopt.jl README](https://github.com/jump-dev/Ipopt.jl?tab=readme-ov-file#linear-solvers) suggests to use the larger LibHSL package for which there exists a Julia module and proceed similarly. In addition, users may try an [optimized BLAS](https://github.com/jump-dev/Ipopt.jl?tab=readme-ov-file#blas-and-lapack) and see if it yields significant performance gains (and let me know if it does).

docs/src/api.md

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,28 @@ Order = [:function]
1616

1717
# Exported Functions
1818

19-
```@autodocs
20-
Modules = [OptimalTransportNetworks]
21-
Private = false
22-
Order = [:function]
19+
```@docs
20+
init_parameters
21+
create_graph
22+
apply_geography
23+
optimal_network
24+
annealing
25+
plot_graph
26+
find_node
27+
add_node
28+
remove_node
2329
```
2430

2531
# Documented Internal Functions
2632

27-
```@autodocs
28-
Modules = [OptimalTransportNetworks]
29-
Public = false
30-
Private = true
31-
Order = [:function]
32-
```
33+
```@docs
34+
OptimalTransportNetworks.dict_to_namedtuple
35+
OptimalTransportNetworks.namedtuple_to_dict
36+
OptimalTransportNetworks.create_map
37+
OptimalTransportNetworks.create_square
38+
OptimalTransportNetworks.create_triangle
39+
OptimalTransportNetworks.create_custom
40+
OptimalTransportNetworks.represent_edges
41+
OptimalTransportNetworks.create_auxdata
42+
OptimalTransportNetworks.get_model
43+
```

docs/src/index.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@ import MathOptInterface as MOI
3737
import MathOptSymbolicAD
3838

3939
param[:model_attr] = Dict(:backend => (MOI.AutomaticDifferentiationBackend(),
40-
MathOptSymbolicAD.DefaultBackend()))
40+
MathOptSymbolicAD.DefaultBackend()))
4141
# Or: MathOptSymbolicAD.ThreadedBackend()
4242
```
4343

4444
* It is recommended to use Coin-HSL linear solvers for [Ipopt](https://github.com/jump-dev/Ipopt.jl) to speed up computations. In my opinion the simplest way to use them is to get a (free for academics) license and download the binaries [here](https://licences.stfc.ac.uk/product/coin-hsl), extract them somewhere, and then set the `hsllib` (place here the path to where you extracted `libhsl.dylib`, it may also be called `libcoinhsl.dylib`, in which case you may have to rename it to `libhsl.dylib`) and `linear_solver` options as follows:
4545

4646
```julia
4747
param[:optimizer_attr] = Dict(:hsllib => "/usr/local/lib/libhsl.dylib", # Adjust path
48-
:linear_solver => "ma57") # Use ma57, ma86 or ma97
48+
:linear_solver => "ma57") # Use ma57, ma86 or ma97
4949
```
5050

5151
The [Ipopt.jl README](https://github.com/jump-dev/Ipopt.jl?tab=readme-ov-file#linear-solvers) suggests to use the larger LibHSL package for which there exists a Julia module and proceed similarly. In addition, users may try an [optimized BLAS](https://github.com/jump-dev/Ipopt.jl?tab=readme-ov-file#blas-and-lapack) and see if it yields significant performance gains (and let me know if it does).

examples/example01.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ param = init_parameters(labor_mobility = true, K = 10, gamma = 1, beta = 1, verb
1717
# ------------
1818
# Init network
1919

20-
param, graph = create_graph(param, 11, 11, type = "map") # create a map network of 11x11 nodes located in [0,10]x[0,10]
20+
graph = create_graph(param, 11, 11, type = "map") # create a map network of 11x11 nodes located in [0,10]x[0,10]
2121
# note: by default, productivity and population are equalized everywhere
2222

2323
# Customize graph
24-
param[:Zjn] = fill(0.1, param[:J], param[:N]) # set most places to low productivity
24+
graph[:Zjn] = fill(0.1, graph[:J], param[:N]) # set most places to low productivity
2525
Ni = find_node(graph, 6, 6) # Find index of the central node at (6,6)
26-
param[:Zjn][Ni, :] .= 1 # central node more productive
26+
graph[:Zjn][Ni, :] .= 1 # central node more productive
2727

2828
# ==========
2929
# RESOLUTION

examples/example02.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,31 @@ param = init_parameters(labor_mobility = false, K = 100, gamma = 1, beta = 1, N
1313
# Init network
1414

1515
w, h = 13, 13
16-
param, graph = create_graph(param, w, h, type = "triangle") # create a triangular network of 21x21
16+
graph = create_graph(param, w, h, type = "triangle") # create a triangular network of 21x21
1717

1818
ncities = 20 # number of random cities
19-
param[:Lj] .= 0 # set population to minimum everywhere
20-
param[:Zjn] .= 0.01 # set low productivity everywhere
19+
graph[:Lj] .= 0 # set population to minimum everywhere
20+
graph[:Zjn] .= 0.01 # set low productivity everywhere
2121

2222
Ni = find_node(graph, ceil(Int, w/2), ceil(Int, h/2)) # Find the central node
23-
param[:Zjn][Ni] = 1 # central node is more productive
24-
param[:Lj][Ni] = 1 # cities are equally populated
23+
graph[:Zjn][Ni] = 1 # central node is more productive
24+
graph[:Lj][Ni] = 1 # cities are equally populated
2525

2626
# Draw the rest of the cities randomly
2727
Random.seed!(5) # reinit random number generator
2828
for i in 1:ncities
2929
newdraw = false
3030
while !newdraw
3131
j = round(Int, 1 + rand() * (graph[:J] - 1))
32-
if param[:Lj][j] != 1 / (ncities + 1) # make sure node j is unpopulated
32+
if graph[:Lj][j] != 1 / (ncities + 1) # make sure node j is unpopulated
3333
newdraw = true
34-
param[:Lj][j] = 1
34+
graph[:Lj][j] = 1
3535
end
3636
end
3737
end
3838

3939
# For Ipopt: population cannot be zero!
40-
param[:Lj][param[:Lj] .== 0] .= 1e-6
40+
graph[:Lj][graph[:Lj] .== 0] .= 1e-6
4141

4242
# ==========
4343
# RESOLUTION

examples/example03.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,21 @@ param = init_parameters(labor_mobility = true, K = 100, gamma = 2, beta = 1, N =
1515
# ------------
1616
# Init network
1717

18-
param, graph = create_graph(param, 7, 7, type = "triangle") # create a triangular network of 21x21
18+
graph = create_graph(param, 7, 7, type = "triangle") # create a triangular network of 21x21
1919

20-
param[:Zjn][:, 1:param[:N]-1] .= 0 # default locations cannot produce goods 1-10
21-
param[:Zjn][:, param[:N]] .= 1 # but they can all produce good 11 (agricultural)
20+
graph[:Zjn][:, 1:param[:N]-1] .= 0 # default locations cannot produce goods 1-10
21+
graph[:Zjn][:, param[:N]] .= 1 # but they can all produce good 11 (agricultural)
2222

2323
# Draw the cities randomly
2424
Random.seed!(5) # reinit random number generator
2525
for i in 1:param[:N]-1
2626
newdraw = false
2727
while newdraw == false
2828
j = round(Int, 1 + rand() * (graph[:J] - 1))
29-
if any(param[:Zjn][j, 1:param[:N]-1] .> 0) == false # make sure node j does not produce any differentiated good
29+
if any(graph[:Zjn][j, 1:param[:N]-1] .> 0) == false # make sure node j does not produce any differentiated good
3030
newdraw = true
31-
param[:Zjn][j, 1:param[:N]] .= 0
32-
param[:Zjn][j, i] = 1
31+
graph[:Zjn][j, 1:param[:N]] .= 0
32+
graph[:Zjn][j, i] = 1
3333
end
3434
end
3535
end

examples/example04.jl

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,33 @@ import Random
1111
param = init_parameters(K = 100, labor_mobility = false,
1212
N = 1, gamma = 1, beta = 1, duality = false)
1313
w = 13; h = 13
14-
param, graph = create_graph(param, w, h, type = "map")
14+
graph = create_graph(param, w, h, type = "map")
1515

1616
# ----------------
1717
# Draw populations
1818

19-
param[:Zjn] = ones(param[:J], 1) .* 1e-6 # matrix of productivity (not 0 to avoid numerical glitches)
20-
param[:Lj] = ones(param[:J]) .* 1e-6 # matrix of population
19+
graph[:Zjn] = ones(graph[:J], 1) .* 1e-6 # matrix of productivity (not 0 to avoid numerical glitches)
20+
graph[:Lj] = ones(graph[:J]) .* 1e-6 # matrix of population
2121

2222
Ni = find_node(graph, ceil(w/2), ceil(h/2)) # center
23-
param[:Zjn][Ni] = 1 # more productive node
24-
param[:Lj][Ni] = 1 # more productive node
23+
graph[:Zjn][Ni] = 1 # more productive node
24+
graph[:Lj][Ni] = 1 # more productive node
2525

2626
Random.seed!(5)
2727
ncities = 20 # draw a number of random cities in space
2828
for i in 1:ncities-1
2929
newdraw = false
3030
while newdraw == false
31-
j = round(Int, 1 + rand() * (param[:J] - 1))
32-
if param[:Lj][j] <= 1e-6
31+
j = round(Int, 1 + rand() * (graph[:J] - 1))
32+
if graph[:Lj][j] <= 1e-6
3333
newdraw = true
34-
param[:Lj][j] = 1
34+
graph[:Lj][j] = 1
3535
end
3636
end
3737
end
3838

39-
param[:hj] = param[:Hj] ./ param[:Lj]
40-
param[:hj][param[:Lj] .== 1e-6] .= 1 # catch errors in places with infinite housing per capita
39+
graph[:hj] = graph[:Hj] ./ graph[:Lj]
40+
graph[:hj][graph[:Lj] .== 1e-6] .= 1 # catch errors in places with infinite housing per capita
4141

4242

4343
# --------------
@@ -72,7 +72,7 @@ graph = apply_geography(graph, geography, alpha_up_i = 10, alpha_down_i = 10)
7272
plot_graph(graph, aspect_ratio = 3/4,
7373
geography = geography, obstacles = true,
7474
mesh = true, mesh_transparency = 0.2,
75-
node_sizes = param[:Lj], node_shades = param[:Zjn],
75+
node_sizes = graph[:Lj], node_shades = graph[:Zjn],
7676
node_color = :seismic,
7777
edge_min_thickness = 1, edge_max_thickness = 4)
7878

@@ -86,8 +86,8 @@ plot_graph(graph, aspect_ratio = 3/4,
8686
# PLOT RESULTS
8787
# ============
8888

89-
sizes = 2 .* results[:cj] .* (param[:Lj] .> 1e-6) / maximum(results[:cj])
90-
shades = results[:cj] .* (param[:Lj] .> 1e-6) / maximum(results[:cj])
89+
sizes = 2 .* results[:cj] .* (graph[:Lj] .> 1e-6) / maximum(results[:cj])
90+
shades = results[:cj] .* (graph[:Lj] .> 1e-6) / maximum(results[:cj])
9191

9292
plot_graph(graph, results[:Ijk], aspect_ratio = 3/4,
9393
geography = geography, obstacles = true,

examples/paper_example01.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,41 @@ using Plots
1010

1111
K = [1, 100]
1212
param = init_parameters()
13-
param, g = create_graph(param, 9, 9, type = "map")
13+
graph = create_graph(param, 9, 9, type = "map")
1414

1515
# Define fundamentals
16-
param[:Zjn] *= 0.1; # matrix of productivity
17-
Ni = find_node(g, 5, 5); # center
18-
param[:Zjn][Ni, :] .= 1; # more productive node
16+
graph[:Zjn] *= 0.1; # matrix of productivity
17+
Ni = find_node(graph, 5, 5); # center
18+
graph[:Zjn][Ni, :] .= 1; # more productive node
1919

2020
# Plot the mesh with population
21-
plot_graph(g, edges = false, mesh = true, node_sizes = param[:Lj])
21+
plot_graph(graph, edges = false, mesh = true, node_sizes = graph[:Lj])
2222

2323
# Plot the mesh with productivity
24-
plot_graph(g, edges = false, mesh = true, node_sizes = param[:Zjn])
24+
plot_graph(graph, edges = false, mesh = true, node_sizes = graph[:Zjn])
2525

2626
# Compute networks
2727
results = []
2828
for i in 1:length(K)
2929
param[:K] = K[i]
30-
push!(results, optimal_network(param, g))
30+
push!(results, optimal_network(param, graph))
3131
end
3232

3333
# Plot networks
3434

3535
plots = [] # Initialize an empty array to hold the subplots
3636
for i in 1:length(K)
3737
shades = sizes = results[i][:Cj] / maximum(results[i][:Cj])
38-
p = plot_graph(g, results[i][:Ijk], node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
38+
p = plot_graph(graph, results[i][:Ijk], node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
3939
title!(p, "(a) Transport Network (I_{jk})")
4040
push!(plots, p)
41-
p = plot_graph(g, results[1][:Qjkn][:, :, 1], edge_color = :brown, arrows = true, node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
41+
p = plot_graph(graph, results[1][:Qjkn][:, :, 1], edge_color = :brown, arrows = true, node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
4242
title!(p, "(b) Shipping (Q_{jk})")
4343
push!(plots, p)
44-
p = plot_graph(g, results[i][:Ijk], map = results[i][:Pjn] / maximum(results[i][:Pjn]), node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
44+
p = plot_graph(graph, results[i][:Ijk], map = results[i][:Pjn] / maximum(results[i][:Pjn]), node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
4545
title!(p, "(c) Prices (P_{j})")
4646
push!(plots, p)
47-
p = plot_graph(g, results[i][:Ijk], map = results[i][:cj] / maximum(results[i][:cj]), node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
47+
p = plot_graph(graph, results[i][:Ijk], map = results[i][:cj] / maximum(results[i][:cj]), node_sizes = sizes, node_shades = shades, node_sizes_scale = 40)
4848
title!(p, "(d) Consumption (c_{j})")
4949
push!(plots, p)
5050
end

examples/paper_example02.jl

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,42 +11,42 @@ height = 9
1111
nb_cities = 20
1212

1313
param = init_parameters(K = 100, annealing = false)
14-
param, g = create_graph(param, width, height, type = "triangle") # case with random cities, one good
14+
graph = create_graph(param, width, height, type = "triangle") # case with random cities, one good
1515

1616
# set fundamentals
1717

1818
Random.seed!(5)
19-
minpop = minimum(param[:Lj])
20-
param[:Zjn] = fill(minpop, g[:J], 1) # matrix of productivity
19+
minpop = minimum(graph[:Lj])
20+
graph[:Zjn] = fill(minpop, graph[:J], 1) # matrix of productivity
2121

22-
Ni = find_node(g, ceil(width/2), ceil(height/2)) # find center
23-
param[:Zjn][Ni] = 1 # more productive node
24-
param[:Lj][Ni] = 1 # more productive node
22+
Ni = find_node(graph, ceil(width/2), ceil(height/2)) # find center
23+
graph[:Zjn][Ni] = 1 # more productive node
24+
graph[:Lj][Ni] = 1 # more productive node
2525

2626
for i in 1:nb_cities-1
2727
newdraw = false
2828
while newdraw == false
29-
j = round(Int, 1 + rand() * (g[:J] - 1))
30-
if param[:Lj][j] <= minpop
29+
j = round(Int, 1 + rand() * (graph[:J] - 1))
30+
if graph[:Lj][j] <= minpop
3131
newdraw = true
32-
param[:Lj][j] = 1
32+
graph[:Lj][j] = 1
3333
end
3434
end
3535
end
3636

37-
param[:hj] = param[:Hj] ./ param[:Lj]
38-
param[:hj][param[:Lj] .== minpop] .= 1
37+
graph[:hj] = graph[:Hj] ./ graph[:Lj]
38+
graph[:hj][graph[:Lj] .== minpop] .= 1
3939

4040
# Convex case
4141
results = Vector{Any}(undef, 3)
42-
results[1] = optimal_network(param, g)
42+
results[1] = optimal_network(param, graph)
4343

4444
# Nonconvex - no annealing
4545
param[:gamma] = 2
46-
results[2] = optimal_network(param, g)
46+
results[2] = optimal_network(param, graph)
4747

4848
# Nonconvex - annealing
49-
results[3] = annealing(param, g, results[2][:Ijk], perturbation_method = "rebranching")
49+
results[3] = annealing(param, graph, results[2][:Ijk], perturbation_method = "rebranching")
5050

5151
welfare_increase = (results[3][:welfare] / results[2][:welfare]) ^ (1 / (param[:alpha] * (1 - param[:rho]))) # compute welfare increase in consumption equivalent
5252

@@ -55,18 +55,18 @@ welfare_increase = (results[3][:welfare] / results[2][:welfare]) ^ (1 / (param[:
5555

5656
plots = Vector{Any}(undef, 6) # Initialize an empty array to hold the subplots
5757

58-
plots[1] = plot_graph(g, results[1][:Ijk], node_sizes = results[1][:Lj], node_shades = results[1][:Cj], node_sizes_scale = 40)
58+
plots[1] = plot_graph(graph, results[1][:Ijk], node_sizes = results[1][:Lj], node_shades = results[1][:Cj], node_sizes_scale = 40)
5959
title!(plots[1], "Convex Network (I_{jk})")
60-
plots[2] = plot_graph(g, results[1][:Qjkn][:, :, 1], edge_color = :brown, arrows = true, node_sizes = results[1][:Lj], node_shades = results[1][:Cj], node_sizes_scale = 40)
60+
plots[2] = plot_graph(graph, results[1][:Qjkn][:, :, 1], edge_color = :brown, arrows = true, node_sizes = results[1][:Lj], node_shades = results[1][:Cj], node_sizes_scale = 40)
6161
title!(plots[2], "Convex Shipping (Q_{jk})")
6262

63-
plots[3] = plot_graph(g, results[2][:Ijk], node_sizes = results[2][:Lj], node_shades = results[2][:Cj], node_sizes_scale = 40)
63+
plots[3] = plot_graph(graph, results[2][:Ijk], node_sizes = results[2][:Lj], node_shades = results[2][:Cj], node_sizes_scale = 40)
6464
title!(plots[3], "Nonconvex Network (I_{jk})")
65-
plots[4] = plot_graph(g, results[2][:Qjkn][:, :, 1], edge_color = :brown, arrows = true, node_sizes = results[2][:Lj], node_shades = results[2][:Cj], node_sizes_scale = 40)
65+
plots[4] = plot_graph(graph, results[2][:Qjkn][:, :, 1], edge_color = :brown, arrows = true, node_sizes = results[2][:Lj], node_shades = results[2][:Cj], node_sizes_scale = 40)
6666
title!(plots[4], "Nonconvex Shipping (Q_{jk})")
6767

6868
plots[5] = plots[3]
69-
plots[6] = plot_graph(g, results[3][:Ijk], node_sizes = results[3][:Lj], node_shades = results[3][:Cj], node_sizes_scale = 40)
69+
plots[6] = plot_graph(graph, results[3][:Ijk], node_sizes = results[3][:Lj], node_shades = results[3][:Cj], node_sizes_scale = 40)
7070
title!(plots[6], "Nonconvex Network (I_{jk})")
7171
annotate!(plots[6], [(0.5, 1.04, text("With Annealing. Welfare increase: $(round((welfare_increase-1)*100, digits = 2))%", :black, :center, 10))])
7272

0 commit comments

Comments
 (0)