Skip to content

Commit dcbfc04

Browse files
Preview for 1.8 release (#208)
* Update to julia by examples to make 1.7 oriented and fix plotting * Added 1.7 material for the essentials * Updates on style guides * Added additional kwarg trick * Cleanup on geometric series Co-authored-by: James Yu <[email protected]>
1 parent 4314b8a commit dcbfc04

File tree

9 files changed

+282
-199
lines changed

9 files changed

+282
-199
lines changed

lectures/dynamic_programming/mccall_model.md

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,8 @@ generates a sequence that converges to the fixed point.
291291
tags: [hide-output]
292292
---
293293
using LinearAlgebra, Statistics
294-
using Distributions, Expectations, LaTeXStrings, NLsolve, Roots, Random, Plots, Parameters
294+
using Distributions, Expectations, LaTeXStrings
295+
using NLsolve, Roots, Random, Plots, Parameters
295296
```
296297

297298
```{code-cell} julia
@@ -314,7 +315,8 @@ dist = BetaBinomial(n, 200, 100) # probability distribution
314315
w = range(10.0, 60.0, length = n+1) # linearly space wages
315316
316317
using StatsPlots
317-
plt = plot(w, pdf.(dist, support(dist)), xlabel = "wages", ylabel = "probabilities", legend = false)
318+
plt = plot(w, pdf.(dist, support(dist)), xlabel = "wages",
319+
ylabel = "probabilities", legend = false)
318320
```
319321

320322
We can explore taking expectations over this distribution
@@ -367,22 +369,22 @@ One approach to solving the model is to directly implement this sort of iteratio
367369
between successive iterates is below tol
368370

369371
```{code-cell} julia
370-
function compute_reservation_wage_direct(params; v_iv = collect(w ./(1-β)), max_iter = 500,
371-
tol = 1e-6)
372+
function compute_reservation_wage_direct(params; v_iv = collect(w ./(1-β)),
373+
max_iter = 500, tol = 1e-6)
372374
(;c, β, w) = params
373375
374376
# create a closure for the T operator
375377
T(v) = max.(w/(1 - β), c + β * E*v) # (5) fixing the parameter values
376378
377-
v = copy(v_iv) # start at initial value. copy to prevent v_iv modification
379+
v = copy(v_iv) # copy to prevent v_iv modification
378380
v_next = similar(v)
379381
i = 0
380382
error = Inf
381383
while i < max_iter && error > tol
382384
v_next .= T(v) # (4)
383385
error = norm(v_next - v)
384386
i += 1
385-
v .= v_next # copy contents into v. Also could have used v[:] = v_next
387+
v .= v_next # copy contents into v
386388
end
387389
# now compute the reservation wage
388390
return (1 - β) * (c + β * E*v) # (2)
@@ -406,17 +408,22 @@ As usual, we are better off using a package, which may give a better algorithm a
406408
In this case, we can use the `fixedpoint` algorithm discussed in {doc}`our Julia by Example lecture <../getting_started_julia/julia_by_example>` to find the fixed point of the $T$ operator. Note that below we set the parameter `m=1` for Anderson iteration rather than leaving as the default value - which fails to converge in this case. This is still almost 10x faster than the `m=0` case, which corresponds to naive fixed-point iteration.
407409

408410
```{code-cell} julia
409-
function compute_reservation_wage(params; v_iv = collect(w ./(1-β)), iterations = 500,
410-
ftol = 1e-6, m = 6)
411+
function compute_reservation_wage(params; v_iv = collect(w ./(1-β)),
412+
iterations = 500, ftol = 1e-6, m = 1)
411413
(;c, β, w) = params
412414
T(v) = max.(w/(1 - β), c + β * E*v) # (5) fixing the parameter values
413415
414-
v_star = fixedpoint(T, v_iv, iterations = iterations, ftol = ftol,
415-
m = 1).zero # (5)
416+
sol = fixedpoint(T, v_iv; iterations, ftol, m) # (5)
417+
sol.f_converged || error("Failed to converge")
418+
v_star = sol.zero
416419
return (1 - β) * (c + β * E*v_star) # (3)
417420
end
418421
```
419422

423+
Note that this checks the convergence (i.e, the `sol.f_converged`) from the fixedpoint iteration and throws an error if it fails.
424+
425+
This coding pattern, where `expression || error("failure)` first checks the expression is true and then moves to the right hand side of the `||` or operator if it is false, is a common pattern in Julia.
426+
420427
Let's compute the reservation wage at the default parameters
421428

422429
```{code-cell} julia
@@ -430,8 +437,8 @@ compute_reservation_wage(mcm()) # call with default parameters
430437
tags: [remove-cell]
431438
---
432439
@testset "Reservation Wage Tests" begin
433-
#test compute_reservation_wage(mcm()) ≈ 47.316499766546215
434-
#test compute_reservation_wage_direct(mcm()) ≈ 47.31649975736077
440+
@test compute_reservation_wage(mcm()) ≈ 47.316499766546215
441+
@test compute_reservation_wage_direct(mcm()) ≈ 47.31649975736077
435442
end
436443
```
437444

@@ -445,24 +452,32 @@ $c$.
445452

446453
```{code-cell} julia
447454
grid_size = 25
448-
R = rand(grid_size, grid_size)
455+
R = zeros(grid_size, grid_size)
449456
450457
c_vals = range(10.0, 30.0, length = grid_size)
451458
β_vals = range(0.9, 0.99, length = grid_size)
452459
453460
for (i, c) in enumerate(c_vals)
454461
for (j, β) in enumerate(β_vals)
455-
R[i, j] = compute_reservation_wage(mcm(c=c, β=β)) # change from defaults
462+
R[i, j] = compute_reservation_wage(mcm(;c, β);m=0)
456463
end
457464
end
458465
```
459466

467+
Note the above is setting the `m` parameter to `0` to use naive fixed-point iteration. This is because the Anderson iteration fails to converge in a 2 of the 25^2 cases.
468+
469+
This demonstrates care must be used with advanced algorithms, and checking the return type (i.e., the `sol.f_converged` field) is important.
470+
471+
472+
460473
```{code-cell} julia
461474
---
462475
tags: [remove-cell]
463476
---
464477
@testset "Comparative Statics Tests" begin
465-
#test R[4, 4] ≈ 41.15851842606614 # arbitrary reservation wage.
478+
@test minimum(R) ≈ 40.39579058732559
479+
@test maximum(R) ≈ 47.69960582438523
480+
@test R[4, 4] ≈ 41.15851842606614 # arbitrary reservation wage.
466481
@test grid_size == 25 # grid invariance.
467482
@test length(c_vals) == grid_size && c_vals[1] ≈ 10.0 && c_vals[end] ≈ 30.0
468483
@test length(β_vals) == grid_size && β_vals[1] ≈ 0.9 && β_vals[end] ≈ 0.99
@@ -559,11 +574,13 @@ The big difference here, however, is that we're iterating on a single number, ra
559574
Here's an implementation:
560575

561576
```{code-cell} julia
562-
function compute_reservation_wage_ψ(c, β; ψ_iv = E * w ./ (1 - β), max_iter = 500,
563-
tol = 1e-5)
577+
function compute_reservation_wage_ψ(c, β; ψ_iv = E * w ./ (1 - β),
578+
iterations = 500, ftol = 1e-5, m = 1)
564579
T_ψ(ψ) = [c + β * E*max.((w ./ (1 - β)), ψ[1])] # (7)
565580
# using vectors since fixedpoint doesn't support scalar
566-
ψ_star = fixedpoint(T_ψ, [ψ_iv]).zero[1]
581+
sol = fixedpoint(T_ψ, [ψ_iv]; iterations, ftol, m)
582+
sol.f_converged || error("Failed to converge")
583+
ψ_star = sol.zero[1]
567584
return (1 - β) * ψ_star # (2)
568585
end
569586
compute_reservation_wage_ψ(c, β)
@@ -574,10 +591,10 @@ You can use this code to solve the exercise below.
574591
Another option is to solve for the root of the $T_{\psi}(\psi) - \psi$ equation
575592

576593
```{code-cell} julia
577-
function compute_reservation_wage_ψ2(c, β; ψ_iv = E * w ./ (1 - β), max_iter = 500,
578-
tol = 1e-5)
594+
function compute_reservation_wage_ψ2(c, β; ψ_iv = E * w ./ (1 - β),
595+
maxiters = 500, rtol = 1e-5)
579596
root_ψ(ψ) = c + β * E*max.((w ./ (1 - β)), ψ) - ψ # (7)
580-
ψ_star = find_zero(root_ψ, ψ_iv)
597+
ψ_star = find_zero(root_ψ, ψ_iv;maxiters, rtol)
581598
return (1 - β) * ψ_star # (2)
582599
end
583600
compute_reservation_wage_ψ2(c, β)
@@ -589,9 +606,9 @@ tags: [remove-cell]
589606
---
590607
@testset begin
591608
mcmp = mcm()
592-
#test compute_reservation_wage(mcmp) ≈ 47.316499766546215
593-
#test compute_reservation_wage_ψ(mcmp.c, mcmp.β) ≈ 47.31649976654623
594-
#test compute_reservation_wage_ψ2(mcmp.c, mcmp.β) ≈ 47.31649976654623
609+
@test compute_reservation_wage(mcmp) ≈ 47.316499766546215
610+
@test compute_reservation_wage_ψ(mcmp.c, mcmp.β) ≈ 47.31649976654623
611+
@test compute_reservation_wage_ψ2(mcmp.c, mcmp.β) ≈ 47.31649976654623
595612
end
596613
```
597614

lectures/getting_started_julia/fundamental_types.md

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ x = [1, 2, 3]
234234
y = similar(x, 2, 2) # make a 2x2 matrix
235235
```
236236

237-
#### Manual Array Definitions
237+
#### Array Definitions from Literals
238238

239239
As we've seen, you can create one dimensional arrays from manually specified data like so
240240

@@ -276,6 +276,39 @@ a = [10 20 30 40]'
276276
ndims(a)
277277
```
278278

279+
Note, however, that the transformed array is not a matrix but rather a special type (e.g., `adjoint(::Matrix{Int64}) with eltype Int64`).
280+
281+
Keeping the special structure of a transposed array can let Julia use more efficient algorithms in some cases.
282+
283+
Finally, we can see how to create multidimensional arrays from literals with the `;` separator.
284+
285+
First, we can create a vector
286+
287+
```{code-cell} julia
288+
[1; 2;]
289+
```
290+
291+
Next, by appending an additional `;` we can tell it to use a 2 dimensional array
292+
293+
```{code-cell} julia
294+
[1; 2;;]
295+
```
296+
297+
Or 3 dimensions
298+
```{code-cell} julia
299+
[1; 2;;;]
300+
```
301+
302+
Or a 1x1 matrix with a single value
303+
```{code-cell} julia
304+
[1;;]
305+
```
306+
307+
Or a 2x2x1 array
308+
```{code-cell} julia
309+
[1 2; 3 4;;;]
310+
```
311+
279312
### Array Indexing
280313

281314
We've already seen the basics of array indexing
@@ -522,7 +555,7 @@ val = [1, 2]
522555
y = similar(val)
523556
524557
function f!(out, x)
525-
out = [1 2; 3 4] * x # MISTAKE! Should be .= or [:]
558+
out = [1 2; 3 4] * x # MISTAKE! Should be .=
526559
end
527560
f!(y, val)
528561
y
@@ -950,28 +983,29 @@ println("a = $a and b = $b")
950983
As well as **named tuples**, which extend tuples with names for each argument.
951984

952985
```{code-cell} julia
953-
t = (val1 = 1.0, val2 = "test")
986+
t = (;val1 = 1.0, val2 = "test") # ; is optional but good form
954987
t.val1 # access by index
955-
# a, b = t # bad style, better to unpack by name
956-
println("val1 = $(t.val1) and val1 = $(t.val1)") # access by name
988+
println("val1 = $(t.val1) and val2 = $(t.val2)") # access by name
989+
(;val1, val2) = t # unpacking notation (note the ;)
990+
println("val1 = $val1 and val2 = $val2")
957991
```
958992

959993
While immutable, it is possible to manipulate tuples and generate new ones
960994

961995
```{code-cell} julia
962-
t2 = (val3 = 4, val4 = "test!!")
996+
t2 = (;val3 = 4, val4 = "test!!")
963997
t3 = merge(t, t2) # new tuple
964998
```
965999

9661000
Named tuples are a convenient and high-performance way to manage and unpack sets of parameters
9671001

9681002
```{code-cell} julia
9691003
function f(parameters)
970-
α, β = parameters.α, parameters.β # poor style, error prone if adding parameters
1004+
α, β = parameters.α, parameters.β # poor style, error prone
9711005
return α + β
9721006
end
9731007
974-
parameters = (α = 0.1, β = 0.2)
1008+
parameters = (;α = 0.1, β = 0.2)
9751009
f(parameters)
9761010
```
9771011

@@ -983,7 +1017,7 @@ function f(parameters)
9831017
return α + β
9841018
end
9851019
986-
parameters = (α = 0.1, β = 0.2)
1020+
parameters = (;α = 0.1, β = 0.2)
9871021
f(parameters)
9881022
```
9891023

@@ -999,6 +1033,15 @@ paramgen = @with_kw (α = 0.1, β = 0.2) # create named tuples with defaults
9991033
@show paramgen(α = 0.2, β = 0.5);
10001034
```
10011035

1036+
Or create a function which returns the named tuple with defaults, which can also do intermediate calculations
1037+
```{code-cell} julia
1038+
function paramgen2(;α = 0.1, β = 0.2)
1039+
return (;α, β)
1040+
end
1041+
@show paramgen2()
1042+
@show paramgen2(;α = 0.2)
1043+
```
1044+
10021045
An alternative approach, defining a new type using `struct` tends to be more prone to accidental misuse, and leads to a great deal of boilerplate code.
10031046

10041047
For that, and other reasons of generality, we will use named tuples for collections of parameters where possible.

lectures/getting_started_julia/introduction_to_types.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -669,10 +669,9 @@ This is just one of many decisions and patterns to ensure that your code is cons
669669

670670
The best resource is to carefully read other peoples code, but a few sources to review are
671671

672-
* [Julia Style Guide](https://docs.julialang.org/en/v1/manual/style-guide/).
673-
* [Invenia Blue Style Guide](https://github.com/invenia/BlueStyle).
674-
* [Julia Praxis Naming Guides](https://github.com/JuliaPraxis/Naming/tree/master/guides).
675-
* [QuantEcon Style Guide](https://github.com/QuantEcon/lecture-source-jl/blob/master/style.md) used in these lectures.
672+
* [Julia Style Guide](https://docs.julialang.org/en/v1/manual/style-guide/)
673+
* [SciML Style Guide](https://github.com/SciML/SciMLStyle)
674+
* [Blue Style Guide](https://github.com/invenia/BlueStyle)
676675

677676
Now why would we emphasize naming and style as a crucial part of the lectures?
678677

@@ -693,6 +692,9 @@ Some helpful ways to think about this are
693692
For example, if you change notation in your model, then immediately update
694693
all variables in the code to reflect it.
695694

695+
696+
While variable naming takes discipline, code formatting for a specific style guide can be automated using [JuliaFormatter.jl](https://github.com/domluna/JuliaFormatter.jl) which is built into the Julia VS Code extension.
697+
696698
#### Commenting Code
697699

698700
One common mistake people make when trying to apply these goals is to add in a large number of comments.

0 commit comments

Comments
 (0)