Logistic regression
First, we import DynamicHMC and related libraries,
using TransformVariables, LogDensityProblems, LogDensityProblemsAD,
DynamicHMC, TransformedLogDensitiesthen some packages that help code the log posterior,
using Parameters, Statistics, Random, Distributions, LinearAlgebra, StatsFuns, LogExpFunctionsthen diagnostic and benchmark tools,
using MCMCDiagnosticTools, BenchmarkToolsand use ForwardDiff for AD since the dimensions is small.
import ForwardDiff
"""
Logistic regression.
For each draw, ``logit(Pr(yᵢ == 1)) ∼ Xᵢ β``. Uses a `β ∼ Normal(0, σ)` prior.
`X` is supposed to include the `1`s for the intercept.
"""
struct LogisticRegression{Ty, TX, Tσ}
y::Ty
X::TX
σ::Tσ
end
function (problem::LogisticRegression)(θ)
@unpack y, X, σ = problem
@unpack β = θ
ℓ_y = mapreduce((y, x) -> logpdf(Bernoulli(logistic(dot(x, β))), y), +, y, eachrow(X))
ℓ_β = loglikelihood(Normal(0, σ), β)
ℓ_y + ℓ_β
endMake up parameters, generate data using random draws.
N = 1000
β = [1.0, 2.0]
X = hcat(ones(N), randn(N))
y = rand.(Bernoulli.(logistic.(X*β)));Create a problem, apply a transformation, then use automatic differentiation.
p = LogisticRegression(y, X, 10.0) # data and (vague) priors
t = as((β = as(Array, length(β)), )) # identity transformation, just to get the dimension
P = TransformedLogDensity(t, p) # transformed
∇P = ADgradient(:ForwardDiff, P)ForwardDiff AD wrapper for TransformedLogDensity of dimension 2, w/ chunk size 2Benchmark
@btime p((β = $β,))-442.0958140546533Sample using NUTS, random starting point.
results = map(_ -> mcmc_with_warmup(Random.default_rng(), ∇P, 1000), 1:5)5-element Vector{NamedTuple{(:posterior_matrix, :tree_statistics, :κ, :ϵ), Tuple{Matrix{Float64}, Vector{DynamicHMC.TreeStatisticsNUTS}, DynamicHMC.GaussianKineticEnergy{LinearAlgebra.Diagonal{Float64, Vector{Float64}}, LinearAlgebra.Diagonal{Float64, Vector{Float64}}}, Float64}}}:
(posterior_matrix = [0.9057322257501476 0.8752679781282163 … 1.081989392236934 0.9659275918008315; 2.0698856647660255 2.087665605543101 … 2.340439330491711 2.2381915172343634], tree_statistics = [DynamicHMC.TreeStatisticsNUTS(-443.3528567183514, 2, turning at positions 0:3, 0.9445557575988827, 3, DynamicHMC.Directions(0x5182015f)), DynamicHMC.TreeStatisticsNUTS(-441.5725186159271, 3, turning at positions -5:2, 0.9480620033673984, 7, DynamicHMC.Directions(0xc85f9732)), DynamicHMC.TreeStatisticsNUTS(-441.7320447795951, 2, turning at positions -2:-5, 0.988118190522935, 7, DynamicHMC.Directions(0x2ed3903a)), DynamicHMC.TreeStatisticsNUTS(-444.2064327233359, 2, turning at positions 1:4, 0.8767335115252227, 7, DynamicHMC.Directions(0x44a947dc)), DynamicHMC.TreeStatisticsNUTS(-448.2071145757616, 2, turning at positions 0:3, 0.7488347970349228, 3, DynamicHMC.Directions(0x74f24abb)), DynamicHMC.TreeStatisticsNUTS(-447.3634116562838, 1, turning at positions 0:1, 1.0, 1, DynamicHMC.Directions(0xec0b0775)), DynamicHMC.TreeStatisticsNUTS(-446.5274667130504, 2, turning at positions -1:2, 0.9419992194563586, 3, DynamicHMC.Directions(0xf526c21e)), DynamicHMC.TreeStatisticsNUTS(-446.37789410143046, 2, turning at positions -3:0, 0.9395219018589276, 3, DynamicHMC.Directions(0x8803cfc8)), DynamicHMC.TreeStatisticsNUTS(-444.4717075689284, 3, turning at positions -3:4, 0.9999999999999999, 7, DynamicHMC.Directions(0xec59361c)), DynamicHMC.TreeStatisticsNUTS(-442.5212534151549, 2, turning at positions -1:2, 0.9999999999999999, 3, DynamicHMC.Directions(0xf331e566)) … DynamicHMC.TreeStatisticsNUTS(-444.6569715953046, 1, turning at positions 0:1, 1.0, 1, DynamicHMC.Directions(0x97f9e5c7)), DynamicHMC.TreeStatisticsNUTS(-444.5780982336841, 2, turning at positions -4:-5, 0.984122274725701, 5, DynamicHMC.Directions(0x4753e3c8)), DynamicHMC.TreeStatisticsNUTS(-442.02399370147555, 2, turning at positions -1:2, 0.9922188287564989, 3, DynamicHMC.Directions(0xcc748372)), DynamicHMC.TreeStatisticsNUTS(-441.8239880917822, 3, turning at positions -1:6, 0.9937090530455414, 7, DynamicHMC.Directions(0x724cb7f6)), DynamicHMC.TreeStatisticsNUTS(-441.2463884970516, 2, turning at positions -2:1, 0.9950741052725235, 3, DynamicHMC.Directions(0xd371a7c1)), DynamicHMC.TreeStatisticsNUTS(-442.086152405855, 2, turning at positions -3:0, 0.9533984620216637, 3, DynamicHMC.Directions(0xcf2b1bac)), DynamicHMC.TreeStatisticsNUTS(-442.0752523452796, 2, turning at positions -1:2, 0.9995437988520789, 3, DynamicHMC.Directions(0xf959955a)), DynamicHMC.TreeStatisticsNUTS(-444.51767779384454, 2, turning at positions -3:0, 0.8103095026269881, 3, DynamicHMC.Directions(0x69a19204)), DynamicHMC.TreeStatisticsNUTS(-444.88667014785193, 2, turning at positions -3:0, 0.9999999999999999, 3, DynamicHMC.Directions(0xab99eb64)), DynamicHMC.TreeStatisticsNUTS(-445.68844264411405, 3, turning at positions 0:7, 0.9491656736376227, 7, DynamicHMC.Directions(0x35595f77))], κ = Gaussian kinetic energy (Diagonal), √diag(M⁻¹): [0.09859876161419211, 0.1364409718719911], ϵ = 0.6649562272646156)
(posterior_matrix = [0.8680389315238533 0.9664918910126306 … 0.9655759747225012 0.854318350943319; 2.0745742741377855 2.0795199583839286 … 2.0080975391058846 2.036809331962731], tree_statistics = [DynamicHMC.TreeStatisticsNUTS(-441.37236695288294, 2, turning at positions 5:6, 0.9995407148165915, 7, DynamicHMC.Directions(0x70dd045e)), DynamicHMC.TreeStatisticsNUTS(-441.42375252578125, 2, turning at positions -3:0, 0.9919669305076559, 3, DynamicHMC.Directions(0xe769bca4)), DynamicHMC.TreeStatisticsNUTS(-443.12215863771576, 2, turning at positions -2:-5, 0.9190122858612002, 7, DynamicHMC.Directions(0x76c5f462)), DynamicHMC.TreeStatisticsNUTS(-443.68193144993614, 3, turning at positions -2:5, 0.9035353238187245, 7, DynamicHMC.Directions(0xbb3cf07d)), DynamicHMC.TreeStatisticsNUTS(-444.4932652737158, 2, turning at positions -2:1, 0.9552951515905713, 3, DynamicHMC.Directions(0x3b227481)), DynamicHMC.TreeStatisticsNUTS(-445.11994644002203, 3, turning at positions -4:3, 0.9828357024991398, 7, DynamicHMC.Directions(0x7049a4fb)), DynamicHMC.TreeStatisticsNUTS(-442.9902592024522, 2, turning at positions -3:0, 0.9875734026513524, 3, DynamicHMC.Directions(0xb1eec538)), DynamicHMC.TreeStatisticsNUTS(-446.1693414503824, 2, turning at positions 2:5, 0.754882384097319, 7, DynamicHMC.Directions(0x5546dc3d)), DynamicHMC.TreeStatisticsNUTS(-445.6267111968051, 2, turning at positions -3:0, 0.9214545148566867, 3, DynamicHMC.Directions(0xcf8cdf54)), DynamicHMC.TreeStatisticsNUTS(-446.7134979526038, 3, turning at positions -4:3, 0.8299499167138817, 7, DynamicHMC.Directions(0x0defcd03)) … DynamicHMC.TreeStatisticsNUTS(-442.4555606214261, 2, turning at positions -2:1, 0.9544693876495488, 3, DynamicHMC.Directions(0x31b43939)), DynamicHMC.TreeStatisticsNUTS(-442.3740804977069, 2, turning at positions -3:-6, 0.9364269567361699, 7, DynamicHMC.Directions(0x07339c09)), DynamicHMC.TreeStatisticsNUTS(-443.34401365782986, 2, turning at positions 0:3, 0.8096203205897968, 3, DynamicHMC.Directions(0xe33a272f)), DynamicHMC.TreeStatisticsNUTS(-443.48497342081873, 2, turning at positions -1:2, 0.9999999999999999, 3, DynamicHMC.Directions(0xdb785be2)), DynamicHMC.TreeStatisticsNUTS(-442.0381069465855, 3, turning at positions -7:0, 0.9921880883844194, 7, DynamicHMC.Directions(0x69a09c00)), DynamicHMC.TreeStatisticsNUTS(-443.52789837960785, 1, turning at positions 2:3, 0.8912095963359657, 3, DynamicHMC.Directions(0xf9fa9d23)), DynamicHMC.TreeStatisticsNUTS(-442.21751069467683, 1, turning at positions 0:1, 1.0, 1, DynamicHMC.Directions(0xc6f0c1bd)), DynamicHMC.TreeStatisticsNUTS(-442.1512115483032, 3, turning at positions -4:3, 0.9974897177515113, 7, DynamicHMC.Directions(0x35a6d55b)), DynamicHMC.TreeStatisticsNUTS(-442.84191981254264, 1, turning at positions 1:2, 0.8929360575709236, 3, DynamicHMC.Directions(0xe7cd4a92)), DynamicHMC.TreeStatisticsNUTS(-441.7398168025174, 2, turning at positions 4:7, 0.9955648068416462, 7, DynamicHMC.Directions(0x3d1c676f))], κ = Gaussian kinetic energy (Diagonal), √diag(M⁻¹): [0.09444138639632087, 0.14191598872483382], ϵ = 0.7259540380709076)
(posterior_matrix = [0.9300005743967242 1.0469861723794402 … 0.9995717115022427 0.9166429882059818; 2.1332074966789634 2.2760111600973976 … 1.979633480318152 2.121782736485274], tree_statistics = [DynamicHMC.TreeStatisticsNUTS(-444.32129990269414, 2, turning at positions -2:1, 0.9999999999999999, 3, DynamicHMC.Directions(0x570c5c59)), DynamicHMC.TreeStatisticsNUTS(-442.4998709888647, 2, turning at positions -3:0, 0.9024303396394538, 3, DynamicHMC.Directions(0x71d85400)), DynamicHMC.TreeStatisticsNUTS(-444.10107319654065, 3, turning at positions -7:0, 0.8934132870263864, 7, DynamicHMC.Directions(0xcad3ed38)), DynamicHMC.TreeStatisticsNUTS(-444.53460602675847, 2, turning at positions -1:2, 0.7334697982860136, 3, DynamicHMC.Directions(0x6473c8d2)), DynamicHMC.TreeStatisticsNUTS(-442.4375388439867, 2, turning at positions -1:2, 0.9999999999999999, 3, DynamicHMC.Directions(0xb579a316)), DynamicHMC.TreeStatisticsNUTS(-442.7923386684332, 2, turning at positions -3:0, 0.9264636405869505, 3, DynamicHMC.Directions(0x1b583428)), DynamicHMC.TreeStatisticsNUTS(-442.41047741992253, 2, turning at positions -2:1, 0.9895275525369986, 3, DynamicHMC.Directions(0xd0492d9d)), DynamicHMC.TreeStatisticsNUTS(-441.8367253317915, 1, turning at positions 0:1, 0.9418813860719479, 1, DynamicHMC.Directions(0x92b46f6f)), DynamicHMC.TreeStatisticsNUTS(-442.1375189371335, 3, turning at positions 0:7, 0.9962231840938022, 7, DynamicHMC.Directions(0x8c689a4f)), DynamicHMC.TreeStatisticsNUTS(-442.4126192711258, 2, turning at positions -2:1, 0.9109099202109029, 3, DynamicHMC.Directions(0x92e1e645)) … DynamicHMC.TreeStatisticsNUTS(-443.23208072729636, 2, turning at positions -2:-5, 0.9722059577990623, 7, DynamicHMC.Directions(0x082e8fea)), DynamicHMC.TreeStatisticsNUTS(-443.182341631735, 2, turning at positions -3:0, 0.9604741821091735, 3, DynamicHMC.Directions(0x877d17d4)), DynamicHMC.TreeStatisticsNUTS(-444.8212267639885, 2, turning at positions -3:0, 0.6447940135481102, 3, DynamicHMC.Directions(0x7d7e6548)), DynamicHMC.TreeStatisticsNUTS(-441.7767212313912, 1, turning at positions 2:3, 0.9999999999999999, 3, DynamicHMC.Directions(0xd0d6e89b)), DynamicHMC.TreeStatisticsNUTS(-441.46752458324903, 2, turning at positions 0:3, 0.9841060996753638, 3, DynamicHMC.Directions(0x162d6a8b)), DynamicHMC.TreeStatisticsNUTS(-442.14902419701457, 2, turning at positions 0:3, 0.8630806885044894, 3, DynamicHMC.Directions(0x157d274f)), DynamicHMC.TreeStatisticsNUTS(-441.93622244085407, 2, turning at positions 0:3, 0.87930819743465, 3, DynamicHMC.Directions(0x774fbda7)), DynamicHMC.TreeStatisticsNUTS(-441.60217301382494, 2, turning at positions -2:1, 0.9863206574623171, 3, DynamicHMC.Directions(0xa77294d1)), DynamicHMC.TreeStatisticsNUTS(-442.9271414999545, 2, turning at positions -3:0, 0.7640950549142134, 3, DynamicHMC.Directions(0x98612aa4)), DynamicHMC.TreeStatisticsNUTS(-442.41517078821255, 1, turning at positions 2:3, 0.9651911949218475, 3, DynamicHMC.Directions(0x8514eb17))], κ = Gaussian kinetic energy (Diagonal), √diag(M⁻¹): [0.08925736349698302, 0.13477170039858566], ϵ = 0.8094384027458571)
(posterior_matrix = [1.0030030352565988 1.0030030352565988 … 0.9867499302097263 1.0359124883679902; 2.371792974245721 2.371792974245721 … 2.188550450962671 2.2447280798094913], tree_statistics = [DynamicHMC.TreeStatisticsNUTS(-443.64048447238906, 2, turning at positions -2:1, 0.9698900620100616, 3, DynamicHMC.Directions(0xb89e94e1)), DynamicHMC.TreeStatisticsNUTS(-444.35224563456603, 1, turning at positions 0:1, 0.8431875642366928, 1, DynamicHMC.Directions(0x96f20d6b)), DynamicHMC.TreeStatisticsNUTS(-443.2853078951459, 2, turning at positions 0:3, 0.9995984901112641, 3, DynamicHMC.Directions(0xe13e0b4f)), DynamicHMC.TreeStatisticsNUTS(-442.9288443544148, 3, turning at positions -1:6, 0.9634019309335874, 7, DynamicHMC.Directions(0x2834c10e)), DynamicHMC.TreeStatisticsNUTS(-442.6770945903042, 1, turning at positions 0:1, 0.9631774962153405, 1, DynamicHMC.Directions(0x0a1314ff)), DynamicHMC.TreeStatisticsNUTS(-443.842099703705, 2, turning at positions -4:-5, 0.8812748397684533, 7, DynamicHMC.Directions(0xa65c85fa)), DynamicHMC.TreeStatisticsNUTS(-442.56401682059885, 2, turning at positions -1:2, 0.9999999999999999, 3, DynamicHMC.Directions(0x78dfbdb2)), DynamicHMC.TreeStatisticsNUTS(-445.4041369321881, 3, turning at positions -2:5, 0.6920656479222848, 7, DynamicHMC.Directions(0x8fef85b5)), DynamicHMC.TreeStatisticsNUTS(-444.2727911605053, 2, turning at positions -4:-7, 0.9562963351738432, 7, DynamicHMC.Directions(0x77af2020)), DynamicHMC.TreeStatisticsNUTS(-444.98983146025563, 2, turning at positions 0:3, 0.8333393850847409, 3, DynamicHMC.Directions(0xe8bc130f)) … DynamicHMC.TreeStatisticsNUTS(-444.7089587665511, 2, turning at positions -3:0, 0.9114398461845945, 3, DynamicHMC.Directions(0x82abb6c4)), DynamicHMC.TreeStatisticsNUTS(-443.7272173515674, 2, turning at positions -1:2, 0.7768076043796553, 3, DynamicHMC.Directions(0x2bf00376)), DynamicHMC.TreeStatisticsNUTS(-443.33658914353066, 1, turning at positions -1:0, 1.0, 1, DynamicHMC.Directions(0xb180046c)), DynamicHMC.TreeStatisticsNUTS(-444.09102563333516, 3, turning at positions -2:5, 0.9832285972598562, 7, DynamicHMC.Directions(0xac4785ad)), DynamicHMC.TreeStatisticsNUTS(-441.42512738719324, 2, turning at positions -1:2, 0.9913589631483116, 3, DynamicHMC.Directions(0xcd0ec4e6)), DynamicHMC.TreeStatisticsNUTS(-442.9231512829026, 2, turning at positions -1:2, 0.8380180535019042, 3, DynamicHMC.Directions(0x414a2d5a)), DynamicHMC.TreeStatisticsNUTS(-442.84532714068513, 2, turning at positions -3:0, 0.9999999999999999, 3, DynamicHMC.Directions(0xcacab790)), DynamicHMC.TreeStatisticsNUTS(-443.83774902198036, 2, turning at positions 1:4, 0.8935284985176232, 7, DynamicHMC.Directions(0x54be3c44)), DynamicHMC.TreeStatisticsNUTS(-442.4057760022732, 2, turning at positions -2:1, 0.9780363787853594, 3, DynamicHMC.Directions(0x52fb0eb1)), DynamicHMC.TreeStatisticsNUTS(-442.56917751473884, 2, turning at positions -3:0, 0.9409434307900381, 3, DynamicHMC.Directions(0x352209fc))], κ = Gaussian kinetic energy (Diagonal), √diag(M⁻¹): [0.09490765609315233, 0.1317832735244582], ϵ = 0.688749434496165)
(posterior_matrix = [1.023965195505918 0.9612844060676489 … 1.038612798013524 0.8372371094184493; 2.096776974658813 2.2347616882219263 … 2.0979135839585576 2.1676600553195975], tree_statistics = [DynamicHMC.TreeStatisticsNUTS(-441.93408846099106, 2, turning at positions -2:1, 0.9229687873646237, 3, DynamicHMC.Directions(0x89d5fd69)), DynamicHMC.TreeStatisticsNUTS(-442.67537180773945, 1, turning at positions -2:-3, 0.9237945296409088, 3, DynamicHMC.Directions(0xea894d9c)), DynamicHMC.TreeStatisticsNUTS(-441.87359697226066, 3, turning at positions -3:4, 0.994454049694762, 7, DynamicHMC.Directions(0x0d7ca824)), DynamicHMC.TreeStatisticsNUTS(-442.4125465647762, 1, turning at positions -1:-2, 0.8243943721897288, 3, DynamicHMC.Directions(0x78bf2cc5)), DynamicHMC.TreeStatisticsNUTS(-444.45523627044963, 2, turning at positions -2:-5, 0.9166369557331793, 7, DynamicHMC.Directions(0x3a5ea212)), DynamicHMC.TreeStatisticsNUTS(-442.59804154443725, 3, turning at positions -3:4, 0.9999999999999999, 7, DynamicHMC.Directions(0x3ba86dd4)), DynamicHMC.TreeStatisticsNUTS(-442.24555406456886, 3, turning at positions -2:5, 0.9713780592304351, 7, DynamicHMC.Directions(0x01ea3bcd)), DynamicHMC.TreeStatisticsNUTS(-441.7901645880536, 2, turning at positions -2:1, 0.9477135200482004, 3, DynamicHMC.Directions(0xfdd24bb1)), DynamicHMC.TreeStatisticsNUTS(-442.77563602452506, 2, turning at positions -2:-5, 0.9551041493409107, 7, DynamicHMC.Directions(0x967d6f22)), DynamicHMC.TreeStatisticsNUTS(-443.1330212355244, 2, turning at positions -1:2, 0.9922072970486213, 3, DynamicHMC.Directions(0xa76c6a02)) … DynamicHMC.TreeStatisticsNUTS(-442.7721119293837, 2, turning at positions -3:0, 0.8927040123809284, 3, DynamicHMC.Directions(0x3671e674)), DynamicHMC.TreeStatisticsNUTS(-443.9719495032516, 2, turning at positions -3:0, 0.9468481945660057, 3, DynamicHMC.Directions(0x126e9710)), DynamicHMC.TreeStatisticsNUTS(-444.0624305160022, 2, turning at positions -3:0, 0.8937550830777706, 3, DynamicHMC.Directions(0x39c5a8c0)), DynamicHMC.TreeStatisticsNUTS(-443.08932499266155, 2, turning at positions 2:3, 0.9021751069999207, 5, DynamicHMC.Directions(0x0d1ef27d)), DynamicHMC.TreeStatisticsNUTS(-443.7040080919868, 3, turning at positions -6:1, 0.8912249850997995, 7, DynamicHMC.Directions(0x0977abd9)), DynamicHMC.TreeStatisticsNUTS(-442.00014884707815, 2, turning at positions -1:2, 0.9859256161677346, 3, DynamicHMC.Directions(0x8baa2062)), DynamicHMC.TreeStatisticsNUTS(-442.9864407716316, 3, turning at positions -1:6, 0.9339052447501205, 7, DynamicHMC.Directions(0xc0bd397e)), DynamicHMC.TreeStatisticsNUTS(-442.75422933100975, 2, turning at positions -3:0, 0.9767157931063353, 3, DynamicHMC.Directions(0x2e57a7a8)), DynamicHMC.TreeStatisticsNUTS(-442.5302427710723, 2, turning at positions -2:1, 0.9999999999999999, 3, DynamicHMC.Directions(0x499be545)), DynamicHMC.TreeStatisticsNUTS(-442.4837120262183, 2, turning at positions -3:0, 0.9782734102479482, 3, DynamicHMC.Directions(0x91caeb94))], κ = Gaussian kinetic energy (Diagonal), √diag(M⁻¹): [0.09806430050584929, 0.1236349114171122], ϵ = 0.7578842425880905)Extract the posterior. (Here the transformation was not really necessary).
β_posterior = first.(transform.(t, eachcol(pool_posterior_matrices(results))))5000-element Vector{Vector{Float64}}:
[0.9057322257501476, 2.0698856647660255]
[0.8752679781282163, 2.087665605543101]
[0.8402597039008076, 2.029435420448242]
[1.1131455345514611, 2.3509021824182565]
[1.2731405154201603, 2.3360131755631364]
[1.2007524256286601, 2.346046862036971]
[1.1095775456852723, 2.4554393527487015]
[1.1725024345443675, 2.21938389276195]
[1.0241001157755028, 2.2948145423489406]
[0.984523536186026, 2.2728866805870336]
⋮
[0.9363047422995505, 1.8691745924506784]
[0.9924990026586501, 2.1172847228672955]
[0.935119163629553, 1.9586176676293778]
[0.9649240686619207, 1.9922454819049382]
[0.9726998146069341, 1.9834644171619502]
[1.0584331502362228, 2.138866889724439]
[0.8935991288162878, 2.258989695960221]
[1.038612798013524, 2.0979135839585576]
[0.8372371094184493, 2.1676600553195975]Check that we recover the parameters.
mean(β_posterior)2-element Vector{Float64}:
0.9279946260356703
2.0975251777947475Quantiles
qs = [0.05, 0.25, 0.5, 0.75, 0.95]
quantile(first.(β_posterior), qs)
quantile(last.(β_posterior), qs)5-element Vector{Float64}:
1.8797198000821063
2.0028218185172713
2.092802233488305
2.189963324353938
2.331852696914637Check that mixing is good.
ess, R̂ = ess_rhat(stack_posterior_matrices(results))(ess = [2179.132637809816, 2062.1423194196213], rhat = [1.0011882272307435, 1.0011149334356764])This page was generated using Literate.jl.