diff --git a/class08/background_materials/admm_vs_central_cost.png b/class08/background_materials/admm_vs_central_cost.png new file mode 100644 index 0000000..ca94fc1 Binary files /dev/null and b/class08/background_materials/admm_vs_central_cost.png differ diff --git a/class08/background_materials/admm_vs_central_trajectory.png b/class08/background_materials/admm_vs_central_trajectory.png new file mode 100644 index 0000000..8070b6c Binary files /dev/null and b/class08/background_materials/admm_vs_central_trajectory.png differ diff --git a/class08/background_materials/platooning.jpg b/class08/background_materials/platooning.jpg new file mode 100644 index 0000000..d587ca1 Binary files /dev/null and b/class08/background_materials/platooning.jpg differ diff --git a/class08/class08.html b/class08/class08.html new file mode 100644 index 0000000..08cd5d2 --- /dev/null +++ b/class08/class08.html @@ -0,0 +1,19 @@ + + + + + + + +
\ No newline at end of file diff --git a/class08/class08.jl b/class08/class08.jl new file mode 100644 index 0000000..2537455 --- /dev/null +++ b/class08/class08.jl @@ -0,0 +1,1240 @@ +### A Pluto.jl notebook ### +# v0.20.19 + +using Markdown +using InteractiveUtils + +# This Pluto notebook uses @bind for interactivity. When running this notebook outside of Pluto, the following 'mock version' of @bind gives bound variables a default value (instead of an error). +macro bind(def, element) + #! format: off + return quote + local iv = try Base.loaded_modules[Base.PkgId(Base.UUID("6e696c72-6542-2067-7265-42206c756150"), "AbstractPlutoDingetjes")].Bonds.initial_value catch; b -> missing; end + local el = $(esc(element)) + global $(esc(def)) = Core.applicable(Base.get, el) ? Base.get(el) : iv(el) + el + end + #! format: on +end + +# ╔═╡ 462afff0-2ae3-4730-b48e-cf475fc9e14f +begin + using Pkg + Pkg.activate("class08/pluto_env") + Pkg.instantiate() # Installs packages from Manifest.toml if needed + + # Then import each package you want to use + using Graphs + using NetworkLayout + using Plots + using LinearAlgebra + using PlutoUI + using Statistics + using GraphRecipes + using Colors + using PlutoTeachingTools + using Random +end + +# ╔═╡ 195683a4-b093-46af-9fb2-0a8a67d996e8 +md""" +References: + +* Olfati-Saber, Reza, J. Alex Fax, and Richard M. Murray. "Consensus and cooperation in networked multi-agent systems." Proceedings of the IEEE 95.1 (2007): 215-233. + +* Boyd, Stephen, et al. "Distributed optimization and statistical learning via the alternating direction method of multipliers." Foundations and Trends® in Machine learning 3.1 (2011): 1-122. + +* Summers, Tyler H., and John Lygeros. "Distributed model predictive consensus via the alternating direction method of multipliers." 2012 50th annual Allerton conference on communication, control, and computing (Allerton). IEEE, 2012. + +* Piansky, R., Stinchfield, G., Kody, A., Molzahn, D. K., & Watson, J. P. (2024). Long duration battery sizing, siting, and operation under wildfire risk using progressive hedging. Electric Power Systems Research, 235, 110785. +""" + +# ╔═╡ 75bdf059-c8ac-4f9c-b023-3c010b4389cb +md""" +# Consensus, ADMM, and Distributed Optimal Control + +## Learning Objectives + +* Understand the intuition behind consensus and why it arises in distributed settings +* Learn ADMM and how it enables distributed optimization +* Explore applications to distributed control and model predictive control (MPC) + +## Table of Contents + +1. Centralized vs. Distributed Control +2. Consensus Algorithms +3. ADMM for Consensus Optimization +4. Distributed Control Application +5. Connections with Other Lectures +6. Summary and Discussion + +""" + +# ╔═╡ fe6b8381-edeb-4d0c-87f5-4ecd2b7b9183 +md""" +## 1. Centralized vs. Distributed Control + +Consider a cooperative control setting involving a network of autonomous vehicles. +Each agent $i$ is described by a simple double-integrator model: + +```math +\dot{x}_i = v_i, \quad \dot{v}_i = u_i. +``` + +Suppose a centralized controller aims to maintain a desired formation or platoon. +__Since each agent’s behavior depends on its neighbors’ states__, the overall coordination problem can be expressed as: + +```math +\min_{u} \sum_{i\in V} \int_{0}^{T} \left( ||x_i - x_i^{\text{ref}}||^2 + ||u_i||^2 + \alpha\sum_{j \in \mathcal{N}_i}||x_i - x_j||^2 \right) dt +``` + +where $\mathcal{N}_i$ denotes the neighbors of agent $i$ within the communication graph $G = (V,E)$. + +While centralized control offers optimal coordination, it is often __impractical__ for large-scale systems due to limitations in scalability, privacy, and robustness. + +A natural alternative is distributed control, where each agent solves a local optimization problem using only information from its immediate neighbors. This distributed formulation retains cooperative behavior while reducing computational and communication burdens: +```math +\min_{u_i} \int_{0}^{T} \left(||x_i - x_i^{\text{ref}}||^2 + ||u_i||^2 + \alpha\sum_{j \in \mathcal{N}_i}||x_i - x_j||^2 \right) dt +``` +Here, each agent independently computes its control input $u_i$ and exchanges limited information with neighbors, and collectively the network reaches a coordinated outcome. +""" + +# ╔═╡ 44154c9f-8e5e-49a7-b6ec-f465e0769c88 +md""" +## 2. Consensus Algorithms + +So what exactly is __consensus__? +It refers to _reaching an agreement on a certain quantity of interest that depends on the states of all agents in a network._ + +A __consensus algorithm__ is a protocol that enables this agreement through _local communication_, by exchanging information only with neighboring agents. + +### 2A. Applications of Consensus +In the earlier example of distributed control for autonomous vehicles, each agent must achieve consensus over shared variables such as _relative positions_ and _velocities_. +This coordination behavior is commonly known as the __flocking problem__. +""" + +# ╔═╡ 40d12761-6ba5-4f92-aada-a26c9ddf5120 +begin + imgpath = joinpath(@__DIR__, "background_materials", "platooning.jpg") + + md""" + $(PlutoUI.LocalResource(imgpath, :width => 300)) + + *Figure: Vehicle platooning illustration* + """ +end + +# ╔═╡ 242f3a4c-ad0b-4619-80e6-1f1ee244b6f4 +question_box(md"""This is just one example where consensus appears. Other examples include: + 1. space rendezvous + 2. power grid stability + 3. federated learning + 4. blockchain (distributed ledgers) + What shared variables require consensus in each of these settings?""") + +# ╔═╡ a6c12abf-4303-45bc-99a7-7b55061013d6 +Foldable(md"Answer...", md""" + + 1. space rendezvous - shared position + 2. power grid stability - shared voltage + 3. federated learning - shared global model + 4. blockchain - shared transaction history + """) + +# ╔═╡ cc94ceb2-e01a-4f10-9ee0-d0c906e5f46f +md""" +### 2B. Consensus Notation + +We consider a network of decision-making agents with dynamics +$\dot{x}_i = u_i$, +where each agent communicates locally with its neighbors on a graph $G = (V, E)$, and with $|E| = N$ agents total. +The objective is to achieve asymptotic convergence to an agreement space such that +$x_1 = x_2 = \dots = x_n$. +Equivalently, this can be written as +$x = \alpha \mathbf{1}$, +where $\alpha \in \mathbb{R}$ represents the collective decision. + +Let the adjacency matrix be $A = [a_{ij}]$. +The set of neighbors of agent $i$ is denoted by +$\mathcal{N}i = {, j \in V : a{ij} \neq 0 ,}$, +meaning that agent $i$ can communicate with agent $j$ whenever $a_{ij} \neq 0$. + +In more general settings—such as mobile sensor networks or flocking—the graph can vary with time. +In this case, we write +$G(t) = (V, E(t))$, $A(t)$, and $\mathcal{N}_i(t)$ +to denote the time-varying topology, adjacency matrix, and neighbor set, respectively. +""" + +# ╔═╡ 453fb681-6f7b-42f2-9d10-0da7ad5b811c +md""" +### 2C. Distributed Consensus Algorithm +The distributed consensus algorithm takes the general form: +```math +\dot x_i(t) = \sum_{j \in \mathcal{N_i}} \left( x_j(t) - x_i(t) \right) + b_i(t) +``` +where $b$ is an input bias, typically set to zero. A nonzero bias may represent, for instance, a desired inter-vehicle relative-position vector. + +#### Average-Consensus +A common and instructive special case is the __average-consensus__ algorithm: +```math +\dot x_i(t) = \sum_{j \in \mathcal{N_i}} a_{ij} \left( x_j(t) - x_i(t) \right) +``` +For graphs that are __connected__ and __undirected__ ($a_{ij} = a_{ji}$), the system satisfies a key invariance property: $\sum_i \dot x_i = 0$. This implies that the final consensus value must equal the average of the initial conditions: +```math +\alpha = \frac{1}{n}\sum_i x_i(0) +``` +Hence, the algorithm asymptotically drives all agents to this average value for any initial state. +Average consensus finds wide application in sensor fusion, distributed estimation, and cooperative control problems within multi-agent systems. +""" + +# ╔═╡ ee0fbf98-db44-4188-b623-3868a08c02b2 +md""" +#### Graph Laplacian +The __Laplacian__ is a matrix defined as +```math +L = [l_{ij}], \qquad +l_{ij} = +\begin{cases} +-1, & j \in \mathcal{N}_i, \\ +|\mathcal{N}_i|, & j = i. +\end{cases} +``` +The dynamics of the average-consensus algorithm can be compactly expressed as +```math +\dot x = -Lx +``` +In an undirected graph, the matrix satisfies the sum-of-squares (SOS) property: +```math +x^\top Lx = \frac{1}{2}\sum_{(i,j)\in E}a_{ij}(x_j - x_i)^2 +``` +Defining the quadratic disagreement function $\varphi(x) = \frac{1}{2} x^T L x$, this yields the __gradient-descent__ algorithm: +```math +\dot{x} = -\nabla \varphi(x) +``` +""" + +# ╔═╡ 46a8121f-f1aa-4d22-8ef9-1d02f957767d +question_box(md"""What motivates the division by 2 in the quadratic disagreement function?""") + +# ╔═╡ 85b74a70-a8df-4741-aa1b-d551d0e9bea2 +Foldable(md"Answer...", md"It accounts for the double-counting of undirected edges in the graph.") + +# ╔═╡ 524365a7-e799-4c55-acf4-88fcd5a716e2 +md""" +### 2D. Demo: Connectivity and Consensus Performance +Spectral properties of $L$ affect the convergence of consensus algorithms. Try the demo below to see how __algebraic connectivity__ affects the performance in consensus algorithms. +""" + +# ╔═╡ 3c5d6e70-6f43-11ef-3456-0123456789ab +md""" +##### Parameters + +Adjust the parameters below to explore different graph topologies and consensus dynamics: + +**Graph Structure:** + +Number of nodes: $(@bind n_nodes Slider(5:50, default=15, show_value=true)) + +Topology: $(@bind topology Select(["random" => "Random (Erdős-Rényi)", "ring" => "Ring", "star" => "Star", "complete" => "Complete", "path" => "Path", "wheel" => "Wheel"])) + +Connectivity (for random graphs): $(@bind connectivity Slider(0.1:0.05:0.9, default=0.3, show_value=true)) + +--- + +**Consensus Dynamics:** + +Step size (α): $(@bind step_size Slider(0.01:0.01:0.5, default=0.1, show_value=true)) + +Number of iterations: $(@bind n_iterations Slider(10:10:200, default=100, show_value=true)) +""" + +# ╔═╡ 4d6e7f80-6f43-11ef-4567-0123456789ab +md""" +--- +##### Graph Generation +""" + +# ╔═╡ 5e7f8090-6f43-11ef-5678-0123456789ab +function create_graph(n::Int, topology_type::String, p::Float64=0.3) + if topology_type == "complete" + return complete_graph(n) + elseif topology_type == "ring" + return cycle_graph(n) + elseif topology_type == "star" + return star_graph(n) + elseif topology_type == "grid" + k = Int(ceil(sqrt(n))) + # Create grid and take first n nodes + g = grid([k, k]) + if nv(g) > n + # Remove extra nodes + for _ in 1:(nv(g) - n) + rem_vertex!(g, nv(g)) + end + end + return g + elseif topology_type == "path" + return path_graph(n) + elseif topology_type == "wheel" + return wheel_graph(n) + else # random (Erdős-Rényi) + return erdos_renyi(n, p) + end +end + +# ╔═╡ 6f809fa0-6f43-11ef-6789-0123456789ab +G = create_graph(n_nodes, topology, connectivity) + +# ╔═╡ 7091a0b0-6f43-11ef-789a-0123456789ab +md""" +**Graph properties:** +- Nodes: $(nv(G)) +- Edges: $(ne(G)) +- Average degree: $(round(2 * ne(G) / nv(G), digits=2)) +- Is connected: $(is_connected(G)) +""" + +# ╔═╡ 81a2b1c0-6f43-11ef-89ab-0123456789ab +md""" +--- +##### Laplacian Matrix +""" + +# ╔═╡ 92b3c2d0-6f43-11ef-9abc-0123456789ab +L = Matrix(laplacian_matrix(G)) + +# ╔═╡ a3c4d3e0-6f43-11ef-abcd-0123456789ab +md""" +Show Laplacian matrix: $(@bind show_laplacian CheckBox()) +""" + +# ╔═╡ b4d5e4f0-6f43-11ef-bcde-0123456789ab +if show_laplacian + L +end + +# ╔═╡ c5e6f500-6f43-11ef-cdef-0123456789ab +md""" +**Laplacian eigenvalues:** + +The second smallest eigenvalue (algebraic connectivity) indicates how well-connected the graph is. +""" + +# ╔═╡ d6f70610-6f43-11ef-def0-0123456789ab +begin + λ = sort(eigvals(L)) + md""" + - Smallest eigenvalue: $(round(λ[1], digits=6)) (should be ≈ 0) + - Second smallest (algebraic connectivity): $(round(λ[2], digits=4)) + - Largest eigenvalue: $(round(λ[end], digits=4)) + """ +end + +# ╔═╡ e7081720-6f43-11ef-ef01-0123456789ab +md""" +--- +##### Initial Conditions +""" + +# ╔═╡ f8192830-6f43-11ef-f012-0123456789ab +# Random initial values between 0 and 100 +x₀ = rand(nv(G)) .* 100 + +# ╔═╡ 09293940-6f43-11ef-0123-0123456789ab +consensus_value = mean(x₀) + +# ╔═╡ 1a3a4a50-6f43-11ef-1234-0123456789ab +md""" +**Initial statistics:** +- Mean (consensus value): $(round(consensus_value, digits=2)) +- Std deviation: $(round(std(x₀), digits=2)) +- Min: $(round(minimum(x₀), digits=2)) +- Max: $(round(maximum(x₀), digits=2)) +""" + +# ╔═╡ 2b4b5b60-6f43-11ef-2345-0123456789ab +md""" +--- +##### Consensus Dynamics +""" + +# ╔═╡ 3c5c6c70-6f43-11ef-3456-0123456789ab +function consensus_evolution(L::Matrix, x₀::Vector, α::Float64, steps::Int) + n = length(x₀) + X = zeros(n, steps + 1) + X[:, 1] = x₀ + + for t in 1:steps + # Discrete-time consensus update: x(t+1) = x(t) - α * L * x(t) + X[:, t + 1] = X[:, t] - α * L * X[:, t] + end + + return X +end + +# ╔═╡ 4d6d7d80-6f43-11ef-4567-0123456789ab +X = consensus_evolution(L, x₀, step_size, n_iterations) + +# ╔═╡ 6f8f9fa0-6f43-11ef-6789-0123456789ab +md""" +--- +##### Visualization +""" + +# ╔═╡ 92b2c2d0-6f43-11ef-9abc-0123456789ab +md""" +**Controls:** + +Time step: $(@bind time_step Slider(0:n_iterations, default=0, show_value=true)) + +Node size: $(@bind node_size Slider(0.5:0.5:3.0, default=1.5, show_value=true)) + +Method: $(@bind method Select(["spring" => "Spring", "sfdp" => "SFDP", "stress" => "Stress"])) +""" + +# ╔═╡ 5e7e8e90-6f43-11ef-5678-0123456789ab +md""" +**Statistics at time step $(time_step):** +- Mean: $(round(mean(X[:, time_step + 1]), digits=2)) +- Variance: $(round(var(X[:, time_step + 1]), digits=4)) +- Std deviation: $(round(std(X[:, time_step + 1]), digits=4)) +- Distance to consensus: $(round(std(X[:, time_step + 1]), digits=4)) +""" + +# ╔═╡ a3c3d3e0-6f43-11ef-abcd-0123456789ab +begin + # Get node colors based on current values + node_values = X[:, time_step + 1] + + # Use the initial current min/max + min_val_current = minimum(X[:, 1]) + max_val_current = maximum(X[:, 1]) + val_range = max_val_current - min_val_current + + # Create color palette from blue to red + if val_range < 1e-6 # All values converged + node_colors = [:purple for _ in 1:length(node_values)] + else + # Normalize to [0, 1] + normalized_values = (node_values .- min_val_current) ./ val_range + # Create colormap from blue (low) to red (high) + node_colors = [RGB(v, 0.3*(1-v), 1-v) for v in normalized_values] + end + + # Get layout based on method + if method == "spring" + layout_fn = spring + elseif method == "sfdp" + layout_fn = sfdp + else + layout_fn = stress + end + + # Compute layout + adj_mat = adjacency_matrix(G) + pos = layout_fn(adj_mat) + + # Extract x and y coordinates + xs = [p[1] for p in pos] + ys = [p[2] for p in pos] + + # Create the plot + p1 = plot(size=(700, 600), + legend=false, + framestyle=:none, + aspect_ratio=:equal, + title="Consensus State at t = $(time_step)\nConsensus: $(round(consensus_value, digits=2)) | Std: $(round(std(node_values), digits=3))") + + # Draw edges + for e in edges(G) + i, j = src(e), dst(e) + plot!(p1, [xs[i], xs[j]], [ys[i], ys[j]], + color=:gray, alpha=0.4, linewidth=1.5) + end + + # Draw nodes + scatter!(p1, xs, ys, + markercolor=node_colors, + markersize=10*node_size, + markerstrokewidth=2, + markerstrokecolor=:black) + + p1 +end + +# ╔═╡ b4d4e4f0-6f43-11ef-bcde-0123456789ab +md""" +--- +##### Node Value Evolution Over Time +""" + +# ╔═╡ c5e5f500-6f43-11ef-cdef-0123456789ab +begin + p2 = plot(0:n_iterations, X', + alpha = 0.6, + legend = false, + xlabel = "Time step", + ylabel = "Node value", + title = "Consensus Convergence", + linewidth = 2, + size = (700, 400), + margin = 5Plots.mm + ) + + # Add consensus line + hline!(p2, [consensus_value], + color = :red, + linewidth = 3, + linestyle = :dash, + label = "Consensus value", + legend = :topright + ) + + # Add current time marker + vline!(p2, [time_step], + color = :green, + linewidth = 2, + linestyle = :dot, + alpha = 0.5 + ) +end + +# ╔═╡ d6f60610-6f43-11ef-def0-0123456789ab +md""" +--- +##### Variance Over Time + +""" + +# ╔═╡ e7071720-6f43-11ef-ef01-0123456789ab +begin + variances = [var(X[:, t]) for t in 1:(n_iterations+1)] + + p3 = plot(0:n_iterations, variances, + xlabel = "Time step", + ylabel = "Variance", + title = "Convergence to Consensus", + linewidth = 2, + color = :purple, + legend = false, + size = (700, 400), + margin = 5Plots.mm, + yscale = :log10 + ) + + vline!(p3, [time_step], + color = :green, + linewidth = 2, + linestyle = :dot, + alpha = 0.5 + ) +end + +# ╔═╡ f8182830-6f43-11ef-f012-0123456789ab +md""" +--- +##### Analysis +A larger algebraic connectivity means faster convergence. This depends largely on the chosen topology. __Step size__ also affects stability. +- Too small: Slow convergence +- Too large: May cause oscillations or instability +- Optimal: Related to the largest eigenvalue of L (α < 2/λ_max for stability) + +**Maximum safe step size for current graph:** α < $(round(2/maximum(λ), digits=3)) +""" + +# ╔═╡ 7e2909e5-0667-4589-b3df-24f48ae67fd8 +md""" +## 3. ADMM for Consensus +The __Alternating Direction Method of Multipliers (ADMM)__, first introduced in the 1970s, is a powerful decomposition–coordination algorithm designed to solve large optimization problems by breaking them into smaller subproblems that can be solved in parallel and then coordinated toward a global solution. + +To understand how ADMM applies to __consensus__, we first review several precursor methods that motivate its structure and intuition. + +### 3A. Dual Ascent Method +We begin with the **convex optimization problem**: + +```math +\begin{aligned} +&\text{minimize} && f(x) \quad \quad \quad \quad (1) \\ +&\text{subject to} && Ax = b +\end{aligned} +``` +The __Lagrangian__ is defined as: +```math +L(x,y) = f(x) + y^\top (Ax - b) +``` +The __dual function__ is obtained by minimizing the Lagrangian over $x$: +```math +g(y) = \inf_{x} L(x,y) = -f^*(-A^\top y) - b^\top y +``` +and the __dual problem__ seeks to maximize $g(y)$, where $f^*$ represents the convex conjugate of $f$. + +The dual ascent iterations alternate between minimizing the Lagrangian over $x$ and updating the dual variable $y$: +```math +\begin{aligned} +x^{k+1} &:= \arg\min_x L(x, y^k) \\ +y^{k+1} &:= y^k + \alpha^k (A x^{k+1} - b) +\end{aligned} +``` +where __residual__ $(A x^{k+1} - b)$ acts as a gradient (or subgradient) direction. + +However, this method comes with restrictive convergence assumptions: $f$ must be strictly convex, and $L$ must remain bounded in $x$ for every $y$. As a result, it is not widely applicable in practice. +""" + +# ╔═╡ 8bcd8d66-fc1c-419f-8e36-374c2c22965a +md""" +### 3B. Dual Decomposition +The next precursor method is __dual decomposition__. Suppose now that the objective is separable: +```math +f(x) = \sum_{i = 1}^N f_i(x_i) +``` +and the matrix $A$ is partitioned so that $Ax = \sum_{i=1}^N A_ix_i$. This allows the Lagrangian to be expressed as: +```math +L(x, y) = \sum_{i=1}^{N} L_i(x_i, y) += \sum_{i=1}^{N} \left( f_i(x_i) + y^{T} A_i x_i - \frac{1}{N} y^{T} b \right) +``` + +With this formulation, the $x$-minimization step decomposes into $N$ independent subproblems which can now be solved in parallel. This parallelization significantly reduces computation time and enhances scalability. +```math +\begin{aligned} + x_i^{k+1} &:= \arg\min_{x_i} L_i(x_i, y^{k}) \\ + y^{k+1} &:= y^{k} + \alpha^{k} (A x^{k+1} - b) +\end{aligned} +``` +""" + +# ╔═╡ f75ec743-d448-432f-9511-b2c7a382873c +md""" +### 3C. Augmented Lagrangians and Method of Multipliers +The final precursor method is the __Augmented Lagrangian__ and __Method of Multipliers__. + +The __Augmented Lagrangian__ introduces an additional quadratic penalty term: +```math +L_{\rho}(x, y) = f(x) + y^{T}(A x - b) + \frac{\rho}{2} \|A x - b\|_{2}^{2} +``` +This corresponds to the optimization problem: +```math +\begin{aligned} + &\text{minimize} \quad f(x) + \frac{\rho}{2}\|A x - b\|_{2}^{2} \\ + &\text{subject to} \quad A x = b + \end{aligned} +``` +This formulation is equivalent to Model (1), since the penalty term vanishes for any feasible $x$. + +Applying the dual ascent framework to this Lagrangian yields the algorithm known as the __Method of Multipliers__: +```math +\begin{aligned} + x^{k+1} &:= \arg\min_{x} L_{\rho}(x, y^{k})\\ + y^{k+1} &:= y^{k} + \rho (A x^{k+1} - b) +\end{aligned} +``` +This method enhances the robustness of the dual ascent algorithm, removing the need for strict convexity or boundedness assumptions on $f$. However, $f$ must still be convex and closed, and Slater's condition remains necessary to ensure strong duality. + +__Note:__ the penalty parameter $\rho$ now serves as the algorithm’s step size. The quadratic penalty smooths the dual function but also scales its gradient, and the step size $\rho$ compensates for this effect. + +Finally, even when $f$ is separable, the agumented Lagrangian $L_\rho$ is not, a key limitation that motivates the next method: __ADMM__ +""" + +# ╔═╡ b9146a5c-2898-4814-935a-6b1bfe7844c4 +md""" +### 3D. ADMM: Alternating Direction Method of Multipliers +The ADMM algorithm blends the best of both worlds. It benefits from the separability and parallelization of dual ascent, while also carrying the superior convergence properties provided by the method of multipliers. + +Given the problem: +```math +\begin{aligned} +&\text{minimize} && f(x) + g(z) \\ +&\text{subject to} && A x + B z = c +\end{aligned} +``` +Its augmented Lagrangian is formed: +```math +L_{\rho}(x, z, y) = f(x) + g(z) + y^{T}(A x + B z - c) + \frac{\rho}{2}\|A x + B z - c\|_{2}^{2} +``` +Finally, the algorithm iterates: +```math +\begin{aligned} +x^{k+1} &:= \arg\min_{x}\; L_{\rho}(x, z^{k}, y^{k}) \\ +z^{k+1} &:= \arg\min_{z}\; L_{\rho}(x^{k+1}, z, y^{k}) \\ +y^{k+1} &:= y^{k} + \rho\,\big(A x^{k+1} + B z^{k+1} - c\big) +\end{aligned} +``` +Note that in the method of multipliers, the $x,z$-update occurs simultaneously. Here, they are updated in sequential or __alternating__ fashion, hence the name. +""" + +# ╔═╡ aec156de-2fce-47bd-b5ad-11e7c62e2a74 +md""" +#### Assumptions for Convergence +__1.$f$ must be closed, proper, and convex__ + +This condition implies that the $x$-update is solvable. + +__2. The un-augmented Lagrangian ($\rho=0$) has a saddle point $(x^*, z^*, y^*)$__ + +```math +L_0(x^*,z^*,y) \leq L_0(x^*,z^*,y^*) \leq L_0(x,z,y^*) +``` +The first inequality implies that $y^*$ is dual optimal, and the second implies that $x^*,z^*$ is primal optimal. Together, they imply that strong duality holds. + +With these two assumptions, the ADMM algorithm guarantees +* __residual convergence__: iterates approach feasibility +* __objective convergence:__ objective approaches optimal value +* __dual variable convergence__: iterates approach a dual optimal point + +We refer the reader to the references for the proof of convergence. The important takeaway is that the augmented Lagrangian must be convex, so that the dual function is well-behaved and that the ADMM steps consistently decrease $L_\rho$. +""" + +# ╔═╡ af74a473-4f23-45cc-883f-ed6ebe7167cf +question_box(md"""What happens when ADMM is used in the case when $f$ is not convex?""") + +# ╔═╡ 3691eca5-604c-4947-8b44-6f0b16798187 +Foldable(md"Answer...", md"Without convexity, the theoretical convergence guarantee is lost. Iterates could oscillate, diverge, or get trapped at local optima. However, the method can still be used heuristically.") + +# ╔═╡ 6b93441f-e476-4c81-ad56-4df9222ade40 +question_box(md"""What happens when ADMM is used in the case when the feasible region itself is nonconvex?""") + +# ╔═╡ 58e61280-9add-40e1-b189-a4750591c804 +Foldable(md"Answer...", md"ADMM loses its convexity-based guarantees: the subproblems may become ill-posed, and feasibility restoration through dual variables may fail. However, __in practice__, ADMM can still serve as a robust heuristic, often converging to feasible, locally optimal points when the problem is well-structured (e.g., low-rank constraints, sparsity, or separable nonlinearities).") + +# ╔═╡ 01f2ceeb-9498-4c91-bd19-7f506752c005 +md""" +#### Optimality Conditions +The necessary and sufficient optimality conditions for ADMM are primal feasibility: +```math +Ax^* + Bz^* - c = 0 +``` +and dual feasibility: +```math +\begin{aligned} +0 &\in \partial f(x^*) + A^\top y^* \\ +0 &\in \partial g(z^*) + B^\top y^* +\end{aligned} +``` +where $\partial$ denotes the subdifferential operator. + +The stopping criteria for ADMM are typically based on __primal and dual residual tolerances__, which serve as practical bounds on the suboptimality of the current iterate. + +#### Convergence in Practice +While ADMM can be relatively slow to achieve high-precision solutions, it typically converges to __moderate accuracy within a few tens of iterations__. In practice, this is often sufficient for large-scale problems where approximate solutions are acceptable or even preferable. + +ADMM is particularly effective in __statistical and machine learning applications__, such as parameter estimation or regularized optimization, where extremely high accuracy offers diminishing returns. This contrasts with algorithms like __Newton’s method__ or __interior-point methods__, which are designed to achieve high precision efficiently but at significantly greater computational cost per iteration. + +""" + +# ╔═╡ 7fa5a486-0801-4977-8163-5a1fb02d59c9 +md""" +### 3E. Consensus via ADMM +Consider the __global consensus problem__, where shared variables $x_i$ must reach a collective decision indicated by $z$: +```math +\begin{aligned} +& \text{minimize} \quad \sum_{i=1}^N f_i(x_i) \\ +& \text{subject to} \quad x_i - z=0, \quad i=1,\dots,N +\end{aligned} +``` +This yields the augmented Lagrangian: +```math +L_{\rho}(x_1, \ldots, x_N, z, y) = \sum_{i=1}^{N} \left( f_i(x_i) + y_i^{T}(x_i - z) + \frac{\rho}{2}\|x_i - z\|_{2}^{2} \right) +``` +The ADMM then iterates: +```math +\begin{aligned} +x_i^{k+1} &:= \arg\min_{x_i} \left( f_i(x_i) + (y_i^{k})^{T}(x_i - z^{k}) + \frac{\rho}{2}\|x_i - z^{k}\|_{2}^{2} \right) \\ +z^{k+1} &:= \frac{1}{N} \sum_{i=1}^{N} \left( x_i^{k+1} + \frac{1}{\rho} y_i^{k} \right) \\ +y_i^{k+1} &:= y_i^{k} + \rho \left( x_i^{k+1} - z^{k+1} \right) +\end{aligned} +``` +""" + +# ╔═╡ 1de20b3e-5d93-4180-84d8-a2cbf2ab2966 +md""" +#### ADMM in Expansion Planning: Progressive Hedging +A common application of ADMM for solving global consensus problems arises in the domain of __capacity expansion planning__ for power systems. This is typically formulated as a __two-stage optimization problem__, where the first stage determines investment decisions for generation, transmission, and/or storage, and the second stage evaluates system operation under various scenarios representing different operating conditions (e.g., demand levels, renewable outputs, or contingency events). + +Let $s$ index the scenarios. For each scenario $s$, let $x_s$ denote the first-stage investment decisions and $w_s$ the corresponding second-stage operational decisions. The problem can be written as: +```math +\begin{aligned} +\min_{x_s,w_s,z} \quad & \sum_s p_s[c^\top x_s + f_s(w_s)] & \\ +\text{s.t.} \quad & Tx_s + U_sw_s = d_s, \quad & \forall s \\ +& x_s = z, \quad & \forall s +\end{aligned} +``` +Here, $p_s$ denotes the probability (or weight) of scenario $s$. The __consensus constraint__ $x_s = z$, also called the __non-anticipativity constraint__, ensures that all scenario-specific investment decisions agree on a common global expansion plan $z$. + +In this domain, the ADMM algorithm is given the name __Progressive Hedging__. + +First, the scenario-specific two-stage problems are solved, relaxing anticipativity: +```math +(x_s^{k+1}, w_s^{k+1}) = \arg\min_{x_s, w_s} \left[ c^\top x_s + f_s(w_s) + (y_s^{k})^\top (x_s - z^{k}) + \frac{\rho}{2}\|x_s - z^{k}\|^{2} \right] +``` + +Next, the consensus investment decision is updated. +```math +z^{k+1} = \sum_{s} p_s \left( x_s^{k+1} + \frac{1}{\rho} y_s^{k} \right) +``` + +Finally, the multipliers are updated: +```math +y_s^{k+1} = y_s^{k} + \rho \left( x_s^{k+1} - z^{k+1} \right) +``` +""" + +# ╔═╡ c9484783-f791-4c68-a2e0-89f2a71fe851 +md""" +### 3F. General Form Consensus +We now consider a __more general form of consensus__, beyond the global consensus setting. Suppose there are local variables $x_i \in \mathbb{R}^{n_i}$, and the objective function is separable across them: +$\sum_i f_i(x_i)$. + +Each local variable $x_i$ contains a subset of components that correspond to certain components of a global variable $z$. We define a mapping $\mathcal{G}(i, j)$ from a local index $(i, j)$ to the corresponding global index $g$, such that +$(x_i)_j = z_{\mathcal{G}(i, j)}$. + +As a motivating example, consider a __model fitting__ problem. Here, the global variable $z$ represents the full feature vector, while each processor (or node) $i$ holds a subset of the data. The corresponding local variable $x_i$ represents the subset of features in $z$ that appear in block $i$ of the data. This structure is typical in large-scale, high-dimensional datasets that are __sparse__ and __distributed__ across multiple computing nodes. + +The general form consensus problem is: +```math +\begin{aligned} +& \text{minimize} \quad \sum_{i=1}^N f_i(x_i) \\ +& \text{subject to} \quad x_i - \tilde z_{i}=0, \quad \forall i +\end{aligned} +``` +where $(\tilde z_i)_j = z_{\mathcal{G}(i,j)}$. +""" + +# ╔═╡ 373b57ee-68b2-4f2c-a94c-f06173c9ea2b +question_box(md"""As an exercise: write the augmented Lagrangian and the ADMM iterate updates for this problem. How does this differ from the algorithm for global consensus?""") + +# ╔═╡ 4161b697-441e-4821-ba52-7f244886bd31 +md""" +## 4. Application of ADMM to Distributed Control +Let's tie it all together! How can we conduct __distributed model predictive consensus__ via ADMM? + +### 4A. Problem Setup + +Consider the flocking problem in a network of double integrators. We assume dynamics in a discrete-time linear state: +```math +x_i(t+1) = A_ix_i(t) + B_iu_i(t), \quad \forall i +``` + +The objective is the infinite-horizon cost function: +```math +J = \sum_{t=0}^\infty \sum_{i=1}^N l_i(x_{\mathcal{N}_i}(t), u_{\mathcal{N}_i}(t)) +``` +where $x_{\mathcal{N}_i}(t), u_{\mathcal{N}_i}(t)$ are the concatenations of states and inputs of the neighbors of $i$. + +The goal here is to find a __distributed__ optimal policy $\pi: \mathcal{X} \rightarrow \mathcal{U}$. Each agent $i$ should only depend on information from its neighbors in $G$. The finite-horizon optimization problem is: + +```math +\begin{aligned} +\text{minimize} \quad +& J = \sum_{t=0}^{T-1} \sum_{i=1}^{N} \ell_i(x_{\mathcal{N}_i}(t), u_{\mathcal{N}_i}(t)) ++ \sum_{i=1}^{N} \ell_{if}(x_{\mathcal{N}_i}(T), u_{\mathcal{N}_i}(T)) \\ +\text{subject to} \quad +& x_i(t+1) = A_i x_i(t) + B_i u_i(t), \\ +& x_{\mathcal{N}_i}(t) \in \mathcal{X}_i, \quad + u_{\mathcal{N}_i}(t) \in \mathcal{U}_i, \\ +& i = 1, \ldots, N, \quad t = 0, 1, \ldots, T-1, \\ +& x_{\mathcal{N}_i}(T) \in \mathcal{X}_{if}, \quad i = 1, \ldots, N. +\end{aligned} +``` +""" + +# ╔═╡ df6c20bd-fcb9-458f-a592-39f5a3781aa1 +md""" +### 4B. Reformulation into General Form Consensus + +We can formulate this into general form consensus. Let $\mathbf{x}_i$ be the local variable vector for agent $i$ that includes a copy of the state and input vectors of itself and all neighbors for the finite-horizon. Let $\mathbf{x}$ be the concatenation of all $\mathbf{x}_i$. The problem takes the form: +```math +\begin{aligned} +&\min_{\mathbf{x}_i \in \mathcal{X}_i} \quad \sum_{i=1}^{N} f_i(\mathbf{x}_i) \\ +&\text{subject to} \quad \mathbf{x}_i - \bar{E}_i z = 0, \quad i = 1, \ldots, N +\end{aligned} +``` +where $\bar{E}_i$ is a matrix that picks out components of $\mathbf{x}$ to match those of the local variable $\mathbf{x}_i$. + +The Lagrangian takes the form: +```math +L_{\rho}(x, z, \lambda) += \sum_{i=1}^{N} \left[ +f_i(x_i) ++ \lambda_i^{T}(x_i - \bar{E}_i z) ++ \frac{\rho}{2}\|x_i - \bar{E}_i z\|_2^2 +\right] += \sum_{i=1}^{N} L_{\rho i}(x_i, z, \lambda) +``` +and the ADMM iterates: +```math +\begin{aligned} +\mathbf{x}_i^{k+1} &= \arg\min_{\mathbf{x}_i \in \mathcal{X}_i} L_{\rho i}(\mathbf{x}_i, z^k, \lambda^k) \\ +z^{k+1} &= \arg\min_{z} L_{\rho}(\mathbf{x}^{k+1}, z, \lambda^k) \\ +\lambda_i^{k+1} &= \lambda_i^k + \rho \left( \mathbf{x}_i^{k+1} - \bar{E}_i z^{k+1} \right) +\end{aligned} +``` +""" + +# ╔═╡ e0141ff8-e681-45cc-b9a8-6908ed332fef +md""" +### 4C. Distributed MPC for Flocking Demo +A simple 2D multi-agent flocking problem solved using distributed model predictive control (MPC) with consensus ADMM. +""" + +# ╔═╡ 326a7a64-73b0-4698-b82d-6ee5f4e9bbc9 +begin + # Problem parameters + N_agents = 6 # Number of agents + N_horizon = 10 # MPC horizon + dt = 0.1 # Time step + + # ADMM parameters + ρ = 10.0 # Penalty parameter + max_iter = 20 # ADMM iterations + + # Cost weights + Q_pos = 1.0 # Position tracking + Q_vel = 0.5 # Velocity matching (flocking) + R_control = 0.1 # Control effort + + # Communication radius + comm_radius = 3.0 +end + +# ╔═╡ b37792db-7786-41b6-9014-58b24636e5e5 +function get_neighbors(positions, comm_radius) + """Find neighbors within communication radius""" + N = size(positions, 2) + neighbors = [Int[] for _ in 1:N] + + for i in 1:N + for j in (i+1):N + dist = norm(positions[:, i] - positions[:, j]) + if dist < comm_radius + push!(neighbors[i], j) + push!(neighbors[j], i) + end + end + end + return neighbors +end + +# ╔═╡ 7c3aa41b-b0b6-42f7-bcea-a92a0f53e3f5 +function agent_mpc_step(state_i, z_neighbors, λ_neighbors, neighbors, center, ρ, N_horizon, dt) + """Solve local MPC for one agent using ADMM consensus""" + # State: [x, y, vx, vy] + # Control: [ax, ay] + + n_state = 4 + + # Initialize trajectory + x = zeros(n_state, N_horizon + 1) + x[:, 1] = state_i + + # Compute desired velocity (average of neighbors + cohesion) + target_vel = zeros(2) + if !isempty(neighbors) + for (j_idx, _) in enumerate(neighbors) + target_vel += z_neighbors[j_idx][3:4, 1] + end + target_vel /= length(neighbors) + else + target_vel = state_i[3:4] + end + + # Add cohesion component (move toward center) + pos_to_center = center - state_i[1:2] + cohesion_vel = 0.5 * pos_to_center + target_vel = 0.7 * target_vel + 0.3 * cohesion_vel + + # Add consensus terms from dual variables + if !isempty(neighbors) + consensus_correction = zeros(2) + for (j_idx, _) in enumerate(neighbors) + consensus_correction -= λ_neighbors[j_idx][3:4, 1] / ρ + end + target_vel += consensus_correction / length(neighbors) + end + + # Plan trajectory with smooth velocity transition + for k in 1:N_horizon + # Gradually transition to target velocity + α_blend = min(1.0, k / 5.0) + desired_vel = (1 - α_blend) * x[3:4, k] + α_blend * target_vel + + x[3:4, k+1] = desired_vel + x[1:2, k+1] = x[1:2, k] + x[3:4, k+1] * dt + end + + return x +end + +# ╔═╡ 94207f26-34e1-418e-9e7b-e7fdfbb6ca8a +function consensus_admm_mpc(states, N_horizon, dt, ρ, max_iter) + """Run consensus ADMM for distributed MPC""" + N = size(states, 2) + n_state = 4 + + # Get communication graph + neighbors = get_neighbors(states[1:2, :], comm_radius) + + # Compute flock center + center = mean(states[1:2, :], dims=2)[:] + + # Initialize ADMM variables + x_local = [zeros(n_state, N_horizon + 1) for _ in 1:N] + z = [zeros(n_state, N_horizon + 1) for _ in 1:N] + λ = [[zeros(n_state, N_horizon + 1) for _ in neighbors[i]] for i in 1:N] + + # Initialize with current state + for i in 1:N + x_local[i][:, 1] = states[:, i] + z[i] = copy(x_local[i]) + end + + # ADMM iterations + for iter in 1:max_iter + # Update x (local MPC solutions) + for i in 1:N + z_neigh = [z[j] for j in neighbors[i]] + x_local[i] = agent_mpc_step(states[:, i], z_neigh, λ[i], + neighbors[i], center, ρ, N_horizon, dt) + end + + # Update z (consensus - average with neighbors) + z_old = deepcopy(z) + for i in 1:N + if !isempty(neighbors[i]) + # Average velocity with neighbors for consensus + z[i] = copy(x_local[i]) + for j in neighbors[i] + z[i][3:4, :] += x_local[j][3:4, :] + end + z[i][3:4, :] /= (1 + length(neighbors[i])) + else + z[i] = copy(x_local[i]) + end + end + + # Update λ (dual variables) + for i in 1:N + for (j_idx, j) in enumerate(neighbors[i]) + λ[i][j_idx][3:4, :] += ρ * (x_local[i][3:4, :] - z[i][3:4, :]) + end + end + end + + # Apply velocity from consensus + new_velocities = zeros(2, N) + for i in 1:N + new_velocities[:, i] = z[i][3:4, 2] # Use next step velocity + end + + return new_velocities +end + +# ╔═╡ d475dc80-319b-409d-96d5-3706ac5c68c9 +begin + # Initialize agents in random positions + Random.seed!(400) + states = zeros(4, N_agents) # [x, y, vx, vy] + states[1:2, :] = randn(2, N_agents) * 3.0 + states[3:4, :] = randn(2, N_agents) * 0.5 + + # Simulation + T_sim = 100 + history = zeros(4, N_agents, T_sim) + + for t in 1:T_sim + history[:, :, t] = states + + # Run distributed MPC with ADMM + new_velocities = consensus_admm_mpc(states, N_horizon, dt, ρ, max_iter) + + # Update states + states[3:4, :] = new_velocities # Set consensus velocities + states[1:2, :] += states[3:4, :] * dt # Update positions + end +end + +# ╔═╡ 65b219d0-62fa-47cb-97d3-9f1285d956f5 +begin + # Visualization + anim = @animate for t in 1:T_sim + plot(size=(600, 600), xlim=(-8, 8), ylim=(-8, 8), + aspect_ratio=:equal, legend=false, + title="Distributed MPC Flocking (ADMM)\nTime: $(round(t*dt, digits=1))s") + + # Plot trajectories + for i in 1:N_agents + plot!(history[1, i, max(1,t-20):t], history[2, i, max(1,t-20):t], + alpha=0.3, color=i) + end + + # Plot current positions and velocities + for i in 1:N_agents + scatter!([history[1, i, t]], [history[2, i, t]], + markersize=10, color=i, markerstrokewidth=2) + + # Velocity arrows + quiver!([history[1, i, t]], [history[2, i, t]], + quiver=([history[3, i, t]], [history[4, i, t]]), + color=i, arrow=true, linewidth=2) + end + + # Draw communication links + neighbors = get_neighbors(history[1:2, :, t], comm_radius) + for i in 1:N_agents + for j in neighbors[i] + if i < j + plot!([history[1, i, t], history[1, j, t]], + [history[2, i, t], history[2, j, t]], + color=:gray, alpha=0.2, linestyle=:dash) + end + end + end + end + + gif(anim, "flocking_admm.gif", fps=10) +end + +# ╔═╡ a1dcc909-2116-4d53-825b-b5f686360bcf +md""" +### 4D. Benchmarking Distributed MPC against Central MPC +This section presents results from Summers et al. (2012), who apply __Distributed Model Predictive Consensus__ using the ADMM framework. The authors consider a flocking problem involving five agents, each with a six-dimensional state space representing position and velocity in 3D ($\mathbb{R}^6$). The objective penalizes both __neighbor disagreement__ and __control effort__. (Refer to the paper for detailed formulations of the system dynamics and noise modeling.) + +Using a finite-horizon MPC with a horizon length of 10 and a 250-step closed-loop simulation, each local subproblem is solved in under 2 ms per ADMM iteration, demonstrating the __computational scalability__ of the distributed approach. + +The first figure compares the agent trajectories from the __centralized solver__ (blue) and __ADMM__ (red), showing near-identical paths. The second figure shows the __percentage difference in total cost__ between ADMM and the centralized solver. Remarkably, ADMM achieves performance within __1.5%__ of the centralized solution after only __two iterations__. +""" + +# ╔═╡ 1a351fa9-7dda-40a5-9bdd-788747344d94 +begin + imgpath2 = joinpath(@__DIR__, "background_materials", "admm_vs_central_trajectory.png") + + md""" + $(PlutoUI.LocalResource(imgpath2, :width => 300)) + + *Figure: Agent Trajectories Spatial Plot* + """ +end + +# ╔═╡ 8dc43ea8-6edb-4e8c-9f3a-bbdaf72860fa +begin + imgpath3 = joinpath(@__DIR__, "background_materials", "admm_vs_central_cost.png") + + md""" + $(PlutoUI.LocalResource(imgpath3, :width => 300)) + + *Figure: Percent difference of ADMM cost with centralized solver* + """ +end + +# ╔═╡ eb7fc856-7182-43d3-9d0c-cb7b74260ef8 +md""" +## 5. Connections with Other Lectures + +### 5A. Dual Decomposition & SDDP +Recall the lecture on __SDDP (Stochastic Dual Dynamic Programming)__. This methodology is built upon __Dual Decomposition__ (Section 3B) principles, applied to multistage stochastic programs. + +In this chapter, dual decomposition was used to decouple the problem __across agents__. In contrast, SDDP performs decomposition __across both scenarios and stages (time)__. The __forward propagation__ step, which simulates policies over sampled scenarios, is analogous to the __primal update__, while the __backward propagation__ step, which generates cuts to approximate the future cost-to-go function, is analogous to the __dual update__. + +### 5B. ADMM & Kalman Filters +The previous lecture introduced the __Kalman Filter__, where each agent/sensor estimates its own system state from local measurements. However, __consensus was not enforced__: each local estimator opearted independently, and no mechanism ensured that neighboring agents' state estimates agreed. + +This lecture introduced __consensus optimization (via ADMM)__ that introduces a coordination layer: agents exchange information and iteratively enforce __agreement on shared variables__. + +Conceptually, both frameworks involve __iterative information fusion__. However, one does not enforce consensus (only context sharing), while the other does. +""" + +# ╔═╡ dd094928-b10a-4269-a5b0-90ec935254fb +md""" +## 6. Summary and Discussion +In this chapter, we introduced __consensus__: + +* a mechanism for __agreement__ among agents through local communication +* convergence guaranteed under __connectivity__ and __convexity__ +* enables coordination without centralized control (scalable, robust) + +We also introduced __ADMM__, a powerful optimization framework: + +* an Augmented Lagrangian method that combines __local optimization__ and __consensus enforcement__ +* alternates between solving local subproblems and updating shared variables (dual updates) +* provides robust convergence and parallelizability for distributed optimization + +Finally, we saw an application for __distributed MPC__: + +* enables agents to cooperatively solve MPC problems without centralized control, enabling scalability, privacy, and robustness +""" + +# ╔═╡ 58ae03d0-104b-4fc3-8983-b54548852c11 +question_box(md"""Consensus and ADMM rely on cooperation and information exchange. What real-world systems or domains _fail_ to satisfy these assumptions (e.g., competitive markets, social media), and what modifications would be needed for such settings?""") + +# ╔═╡ 1f24c378-3e4a-42d1-9a07-f3f226bb27b2 +Foldable(md"Hint...", md"In such _adversarial_ or _strategic_ environments, the assumptions of standard consensus no longer hold. The optimization must be reformulated. It becomes __equilibrium-seeking__ rather than __agreement-seeking__.") + +# ╔═╡ Cell order: +# ╟─462afff0-2ae3-4730-b48e-cf475fc9e14f +# ╟─195683a4-b093-46af-9fb2-0a8a67d996e8 +# ╟─75bdf059-c8ac-4f9c-b023-3c010b4389cb +# ╟─fe6b8381-edeb-4d0c-87f5-4ecd2b7b9183 +# ╟─44154c9f-8e5e-49a7-b6ec-f465e0769c88 +# ╟─40d12761-6ba5-4f92-aada-a26c9ddf5120 +# ╟─242f3a4c-ad0b-4619-80e6-1f1ee244b6f4 +# ╟─a6c12abf-4303-45bc-99a7-7b55061013d6 +# ╟─cc94ceb2-e01a-4f10-9ee0-d0c906e5f46f +# ╟─453fb681-6f7b-42f2-9d10-0da7ad5b811c +# ╟─ee0fbf98-db44-4188-b623-3868a08c02b2 +# ╟─46a8121f-f1aa-4d22-8ef9-1d02f957767d +# ╟─85b74a70-a8df-4741-aa1b-d551d0e9bea2 +# ╟─524365a7-e799-4c55-acf4-88fcd5a716e2 +# ╟─3c5d6e70-6f43-11ef-3456-0123456789ab +# ╟─4d6e7f80-6f43-11ef-4567-0123456789ab +# ╟─5e7f8090-6f43-11ef-5678-0123456789ab +# ╟─6f809fa0-6f43-11ef-6789-0123456789ab +# ╟─7091a0b0-6f43-11ef-789a-0123456789ab +# ╟─81a2b1c0-6f43-11ef-89ab-0123456789ab +# ╟─92b3c2d0-6f43-11ef-9abc-0123456789ab +# ╟─a3c4d3e0-6f43-11ef-abcd-0123456789ab +# ╟─b4d5e4f0-6f43-11ef-bcde-0123456789ab +# ╟─c5e6f500-6f43-11ef-cdef-0123456789ab +# ╟─d6f70610-6f43-11ef-def0-0123456789ab +# ╟─e7081720-6f43-11ef-ef01-0123456789ab +# ╟─f8192830-6f43-11ef-f012-0123456789ab +# ╟─09293940-6f43-11ef-0123-0123456789ab +# ╟─1a3a4a50-6f43-11ef-1234-0123456789ab +# ╟─2b4b5b60-6f43-11ef-2345-0123456789ab +# ╟─3c5c6c70-6f43-11ef-3456-0123456789ab +# ╟─4d6d7d80-6f43-11ef-4567-0123456789ab +# ╟─5e7e8e90-6f43-11ef-5678-0123456789ab +# ╟─6f8f9fa0-6f43-11ef-6789-0123456789ab +# ╟─92b2c2d0-6f43-11ef-9abc-0123456789ab +# ╟─a3c3d3e0-6f43-11ef-abcd-0123456789ab +# ╟─b4d4e4f0-6f43-11ef-bcde-0123456789ab +# ╟─c5e5f500-6f43-11ef-cdef-0123456789ab +# ╟─d6f60610-6f43-11ef-def0-0123456789ab +# ╟─e7071720-6f43-11ef-ef01-0123456789ab +# ╟─f8182830-6f43-11ef-f012-0123456789ab +# ╟─7e2909e5-0667-4589-b3df-24f48ae67fd8 +# ╟─8bcd8d66-fc1c-419f-8e36-374c2c22965a +# ╟─f75ec743-d448-432f-9511-b2c7a382873c +# ╟─b9146a5c-2898-4814-935a-6b1bfe7844c4 +# ╟─aec156de-2fce-47bd-b5ad-11e7c62e2a74 +# ╟─af74a473-4f23-45cc-883f-ed6ebe7167cf +# ╟─3691eca5-604c-4947-8b44-6f0b16798187 +# ╟─6b93441f-e476-4c81-ad56-4df9222ade40 +# ╟─58e61280-9add-40e1-b189-a4750591c804 +# ╟─01f2ceeb-9498-4c91-bd19-7f506752c005 +# ╟─7fa5a486-0801-4977-8163-5a1fb02d59c9 +# ╟─1de20b3e-5d93-4180-84d8-a2cbf2ab2966 +# ╟─c9484783-f791-4c68-a2e0-89f2a71fe851 +# ╟─373b57ee-68b2-4f2c-a94c-f06173c9ea2b +# ╟─4161b697-441e-4821-ba52-7f244886bd31 +# ╟─df6c20bd-fcb9-458f-a592-39f5a3781aa1 +# ╟─e0141ff8-e681-45cc-b9a8-6908ed332fef +# ╟─326a7a64-73b0-4698-b82d-6ee5f4e9bbc9 +# ╟─b37792db-7786-41b6-9014-58b24636e5e5 +# ╟─7c3aa41b-b0b6-42f7-bcea-a92a0f53e3f5 +# ╟─94207f26-34e1-418e-9e7b-e7fdfbb6ca8a +# ╟─d475dc80-319b-409d-96d5-3706ac5c68c9 +# ╟─65b219d0-62fa-47cb-97d3-9f1285d956f5 +# ╟─a1dcc909-2116-4d53-825b-b5f686360bcf +# ╟─1a351fa9-7dda-40a5-9bdd-788747344d94 +# ╟─8dc43ea8-6edb-4e8c-9f3a-bbdaf72860fa +# ╟─eb7fc856-7182-43d3-9d0c-cb7b74260ef8 +# ╟─dd094928-b10a-4269-a5b0-90ec935254fb +# ╟─58ae03d0-104b-4fc3-8983-b54548852c11 +# ╟─1f24c378-3e4a-42d1-9a07-f3f226bb27b2 diff --git a/class08/class08.md b/class08/class08.md index 1b228a5..2ad04e6 100644 --- a/class08/class08.md +++ b/class08/class08.md @@ -2,9 +2,13 @@ **Presenter:** Kevin Wu -**Topic:** Distributed optimal control & multi-agent coordination; Consensus, distributed MPC, and optimization over graphs (ADMM) +**Topic:** Consensus, ADMM, and Distributed Optimal Control --- -Add notes, links, and resources below. +In this lecture, we will cover the concept of *consensus*, *ADMM* (alternating direction method of multipliers), and how these methods can be applied for *distributed optimal control*. +The lecture note is contained in a Pluto notebook (class08/class08.jl). To run it locally, please refer to the [Class 01 documentation](https://learningtooptimize.github.io/LearningToControlClass/dev/class01/class01/#Background-Material). +(In *Step 3: Install Pluto and other dependencies*, you should `activate` the environment in the class08 folder.) + +The notebook can also be accessed [online](https://learningtooptimize.github.io/LearningToControlClass/dev/class08/class08.html) \ No newline at end of file diff --git a/class08/distributed_mpc.jl b/class08/distributed_mpc.jl new file mode 100644 index 0000000..5214a32 --- /dev/null +++ b/class08/distributed_mpc.jl @@ -0,0 +1,1334 @@ +### A Pluto.jl notebook ### +# v0.20.19 + +using Markdown +using InteractiveUtils + +# ╔═╡ 991c318e-a5f7-11f0-863c-43de9de771b5 +begin + using Plots + using LinearAlgebra + using Random + using Statistics +end + +# ╔═╡ 991c38b4-a5f7-11f0-9a05-41e54e0f74d7 +md""" +# Distributed MPC Flocking with Consensus ADMM +A simple 2D multi-agent flocking problem solved using distributed model predictive control (MPC) with consensus ADMM. +""" + +# ╔═╡ 991c3aa8-a5f7-11f0-83c6-05f3e36fe828 +begin + # Problem parameters + N_agents = 6 # Number of agents + N_horizon = 10 # MPC horizon + dt = 0.1 # Time step + + # ADMM parameters + ρ = 10.0 # Penalty parameter + max_iter = 20 # ADMM iterations + + # Cost weights + Q_pos = 1.0 # Position tracking + Q_vel = 0.5 # Velocity matching (flocking) + R_control = 0.1 # Control effort + + # Communication radius + comm_radius = 3.0 +end + +# ╔═╡ 991c416a-a5f7-11f0-80f0-575e5b1b47bd +function get_neighbors(positions, comm_radius) + """Find neighbors within communication radius""" + N = size(positions, 2) + neighbors = [Int[] for _ in 1:N] + + for i in 1:N + for j in (i+1):N + dist = norm(positions[:, i] - positions[:, j]) + if dist < comm_radius + push!(neighbors[i], j) + push!(neighbors[j], i) + end + end + end + return neighbors +end + +# ╔═╡ 991c42dc-a5f7-11f0-b04f-db65b34a6f41 +function agent_mpc_step(state_i, z_neighbors, λ_neighbors, neighbors, center, ρ, N_horizon, dt) + """Solve local MPC for one agent using ADMM consensus""" + # State: [x, y, vx, vy] + # Control: [ax, ay] + + n_state = 4 + + # Initialize trajectory + x = zeros(n_state, N_horizon + 1) + x[:, 1] = state_i + + # Compute desired velocity (average of neighbors + cohesion) + target_vel = zeros(2) + if !isempty(neighbors) + for (j_idx, _) in enumerate(neighbors) + target_vel += z_neighbors[j_idx][3:4, 1] + end + target_vel /= length(neighbors) + else + target_vel = state_i[3:4] + end + + # Add cohesion component (move toward center) + pos_to_center = center - state_i[1:2] + cohesion_vel = 0.5 * pos_to_center + target_vel = 0.7 * target_vel + 0.3 * cohesion_vel + + # Add consensus terms from dual variables + if !isempty(neighbors) + consensus_correction = zeros(2) + for (j_idx, _) in enumerate(neighbors) + consensus_correction -= λ_neighbors[j_idx][3:4, 1] / ρ + end + target_vel += consensus_correction / length(neighbors) + end + + # Plan trajectory with smooth velocity transition + for k in 1:N_horizon + # Gradually transition to target velocity + α_blend = min(1.0, k / 5.0) + desired_vel = (1 - α_blend) * x[3:4, k] + α_blend * target_vel + + x[3:4, k+1] = desired_vel + x[1:2, k+1] = x[1:2, k] + x[3:4, k+1] * dt + end + + return x +end + +# ╔═╡ 991c48ea-a5f7-11f0-9593-b1fa84c5079f +function consensus_admm_mpc(states, N_horizon, dt, ρ, max_iter) + """Run consensus ADMM for distributed MPC""" + N = size(states, 2) + n_state = 4 + + # Get communication graph + neighbors = get_neighbors(states[1:2, :], comm_radius) + + # Compute flock center + center = mean(states[1:2, :], dims=2)[:] + + # Initialize ADMM variables + x_local = [zeros(n_state, N_horizon + 1) for _ in 1:N] + z = [zeros(n_state, N_horizon + 1) for _ in 1:N] + λ = [[zeros(n_state, N_horizon + 1) for _ in neighbors[i]] for i in 1:N] + + # Initialize with current state + for i in 1:N + x_local[i][:, 1] = states[:, i] + z[i] = copy(x_local[i]) + end + + # ADMM iterations + for iter in 1:max_iter + # Update x (local MPC solutions) + for i in 1:N + z_neigh = [z[j] for j in neighbors[i]] + x_local[i] = agent_mpc_step(states[:, i], z_neigh, λ[i], + neighbors[i], center, ρ, N_horizon, dt) + end + + # Update z (consensus - average with neighbors) + z_old = deepcopy(z) + for i in 1:N + if !isempty(neighbors[i]) + # Average velocity with neighbors for consensus + z[i] = copy(x_local[i]) + for j in neighbors[i] + z[i][3:4, :] += x_local[j][3:4, :] + end + z[i][3:4, :] /= (1 + length(neighbors[i])) + else + z[i] = copy(x_local[i]) + end + end + + # Update λ (dual variables) + for i in 1:N + for (j_idx, j) in enumerate(neighbors[i]) + λ[i][j_idx][3:4, :] += ρ * (x_local[i][3:4, :] - z[i][3:4, :]) + end + end + end + + # Apply velocity from consensus + new_velocities = zeros(2, N) + for i in 1:N + new_velocities[:, i] = z[i][3:4, 2] # Use next step velocity + end + + return new_velocities +end + +# ╔═╡ 991c4fd4-a5f7-11f0-b856-1dc751df9856 +begin + # Initialize agents in random positions + Random.seed!(400) + states = zeros(4, N_agents) # [x, y, vx, vy] + states[1:2, :] = randn(2, N_agents) * 3.0 + states[3:4, :] = randn(2, N_agents) * 0.5 + + # Simulation + T_sim = 100 + history = zeros(4, N_agents, T_sim) + + for t in 1:T_sim + history[:, :, t] = states + + # Run distributed MPC with ADMM + new_velocities = consensus_admm_mpc(states, N_horizon, dt, ρ, max_iter) + + # Update states + states[3:4, :] = new_velocities # Set consensus velocities + states[1:2, :] += states[3:4, :] * dt # Update positions + end +end + +# ╔═╡ 991c529a-a5f7-11f0-af23-b9049ff1b267 +begin + # Visualization + anim = @animate for t in 1:T_sim + plot(size=(600, 600), xlim=(-8, 8), ylim=(-8, 8), + aspect_ratio=:equal, legend=false, + title="Distributed MPC Flocking (ADMM)\nTime: $(round(t*dt, digits=1))s") + + # Plot trajectories + for i in 1:N_agents + plot!(history[1, i, max(1,t-20):t], history[2, i, max(1,t-20):t], + alpha=0.3, color=i) + end + + # Plot current positions and velocities + for i in 1:N_agents + scatter!([history[1, i, t]], [history[2, i, t]], + markersize=10, color=i, markerstrokewidth=2) + + # Velocity arrows + quiver!([history[1, i, t]], [history[2, i, t]], + quiver=([history[3, i, t]], [history[4, i, t]]), + color=i, arrow=true, linewidth=2) + end + + # Draw communication links + neighbors = get_neighbors(history[1:2, :, t], comm_radius) + for i in 1:N_agents + for j in neighbors[i] + if i < j + plot!([history[1, i, t], history[1, j, t]], + [history[2, i, t], history[2, j, t]], + color=:gray, alpha=0.2, linestyle=:dash) + end + end + end + end + + gif(anim, "flocking_admm.gif", fps=10) +end + +# ╔═╡ 00000000-0000-0000-0000-000000000001 +PLUTO_PROJECT_TOML_CONTENTS = """ +[deps] +LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" + +[compat] +Plots = "~1.41.1" +""" + +# ╔═╡ 00000000-0000-0000-0000-000000000002 +PLUTO_MANIFEST_TOML_CONTENTS = """ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.12.0" +manifest_format = "2.0" +project_hash = "37249e6c236a66571ca77babc33f3688051565c1" + +[[deps.AliasTables]] +deps = ["PtrArrays", "Random"] +git-tree-sha1 = "9876e1e164b144ca45e9e3198d0b689cadfed9ff" +uuid = "66dad0bd-aa9a-41b7-9441-69ab47430ed8" +version = "1.1.3" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.BitFlags]] +git-tree-sha1 = "0691e34b3bb8be9307330f88d1a3c3f25466c24d" +uuid = "d1d4a3ce-64b1-5f1a-9ba4-7e7e69966f35" +version = "0.1.9" + +[[deps.Bzip2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1b96ea4a01afe0ea4090c5c8039690672dd13f2e" +uuid = "6e34b625-4abd-537c-b88f-471c36dfa7a0" +version = "1.0.9+0" + +[[deps.Cairo_jll]] +deps = ["Artifacts", "Bzip2_jll", "CompilerSupportLibraries_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "JLLWrappers", "LZO_jll", "Libdl", "Pixman_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "fde3bf89aead2e723284a8ff9cdf5b551ed700e8" +uuid = "83423d85-b0ee-5818-9007-b63ccbeb887a" +version = "1.18.5+0" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.8" + +[[deps.ColorSchemes]] +deps = ["ColorTypes", "ColorVectorSpace", "Colors", "FixedPointNumbers", "PrecompileTools", "Random"] +git-tree-sha1 = "b0fd3f56fa442f81e0a47815c92245acfaaa4e34" +uuid = "35d6a980-a343-548e-a6ea-1d62b119f2f4" +version = "3.31.0" + +[[deps.ColorTypes]] +deps = ["FixedPointNumbers", "Random"] +git-tree-sha1 = "67e11ee83a43eb71ddc950302c53bf33f0690dfe" +uuid = "3da002f7-5984-5a60-b8a6-cbb66c0b333f" +version = "0.12.1" +weakdeps = ["StyledStrings"] + + [deps.ColorTypes.extensions] + StyledStringsExt = "StyledStrings" + +[[deps.ColorVectorSpace]] +deps = ["ColorTypes", "FixedPointNumbers", "LinearAlgebra", "Requires", "Statistics", "TensorCore"] +git-tree-sha1 = "8b3b6f87ce8f65a2b4f857528fd8d70086cd72b1" +uuid = "c3611d14-8923-5661-9e6a-0046d554d3a4" +version = "0.11.0" + + [deps.ColorVectorSpace.extensions] + SpecialFunctionsExt = "SpecialFunctions" + + [deps.ColorVectorSpace.weakdeps] + SpecialFunctions = "276daf66-3868-5448-9aa4-cd146d93841b" + +[[deps.Colors]] +deps = ["ColorTypes", "FixedPointNumbers", "Reexport"] +git-tree-sha1 = "37ea44092930b1811e666c3bc38065d7d87fcc74" +uuid = "5ae59095-9a9b-59fe-a467-6f913c188581" +version = "0.13.1" + +[[deps.CompilerSupportLibraries_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "e66e0078-7015-5450-92f7-15fbd957f2ae" +version = "1.3.0+1" + +[[deps.ConcurrentUtilities]] +deps = ["Serialization", "Sockets"] +git-tree-sha1 = "d9d26935a0bcffc87d2613ce14c527c99fc543fd" +uuid = "f0e56b4a-5159-44fe-b623-3e5288b988bb" +version = "2.5.0" + +[[deps.Contour]] +git-tree-sha1 = "439e35b0b36e2e5881738abc8857bd92ad6ff9a8" +uuid = "d38c429a-6771-53c6-b99e-75d170b6e991" +version = "0.6.3" + +[[deps.DataAPI]] +git-tree-sha1 = "abe83f3a2f1b857aac70ef8b269080af17764bbe" +uuid = "9a962f9c-6df0-11e9-0e5d-c546b8b5ee8a" +version = "1.16.0" + +[[deps.DataStructures]] +deps = ["OrderedCollections"] +git-tree-sha1 = "6c72198e6a101cccdd4c9731d3985e904ba26037" +uuid = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" +version = "0.19.1" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.Dbus_jll]] +deps = ["Artifacts", "Expat_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "473e9afc9cf30814eb67ffa5f2db7df82c3ad9fd" +uuid = "ee1fde0b-3d02-5ea6-8484-8dfef6360eab" +version = "1.16.2+0" + +[[deps.DelimitedFiles]] +deps = ["Mmap"] +git-tree-sha1 = "9e2f36d3c96a820c678f2f1f1782582fcf685bae" +uuid = "8bb1440f-4735-579b-a4ab-409b98df4dab" +version = "1.9.1" + +[[deps.DocStringExtensions]] +git-tree-sha1 = "7442a5dfe1ebb773c29cc2962a8980f47221d76c" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.5" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.EpollShim_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8a4be429317c42cfae6a7fc03c31bad1970c310d" +uuid = "2702e6a9-849d-5ed8-8c21-79e8b8f9ee43" +version = "0.0.20230411+1" + +[[deps.ExceptionUnwrapping]] +deps = ["Test"] +git-tree-sha1 = "d36f682e590a83d63d1c7dbd287573764682d12a" +uuid = "460bff9d-24e4-43bc-9d9f-a8973cb893f4" +version = "0.1.11" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7bb1361afdb33c7f2b085aa49ea8fe1b0fb14e58" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.7.1+0" + +[[deps.FFMPEG]] +deps = ["FFMPEG_jll"] +git-tree-sha1 = "83dc665d0312b41367b7263e8a4d172eac1897f4" +uuid = "c87230d0-a227-11e9-1b43-d7ebe4e7570a" +version = "0.4.4" + +[[deps.FFMPEG_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "JLLWrappers", "LAME_jll", "Libdl", "Ogg_jll", "OpenSSL_jll", "Opus_jll", "PCRE2_jll", "Zlib_jll", "libaom_jll", "libass_jll", "libfdk_aac_jll", "libvorbis_jll", "x264_jll", "x265_jll"] +git-tree-sha1 = "3a948313e7a41eb1db7a1e733e6335f17b4ab3c4" +uuid = "b22a6f82-2f65-5046-a5b2-351ab43fb4e5" +version = "7.1.1+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.FixedPointNumbers]] +deps = ["Statistics"] +git-tree-sha1 = "05882d6995ae5c12bb5f36dd2ed3f61c98cbb172" +uuid = "53c48c17-4a7d-5ca2-90c5-79b7896eea93" +version = "0.8.5" + +[[deps.Fontconfig_jll]] +deps = ["Artifacts", "Bzip2_jll", "Expat_jll", "FreeType2_jll", "JLLWrappers", "Libdl", "Libuuid_jll", "Zlib_jll"] +git-tree-sha1 = "f85dac9a96a01087df6e3a749840015a0ca3817d" +uuid = "a3f928ae-7b40-5064-980b-68af3947d34b" +version = "2.17.1+0" + +[[deps.Format]] +git-tree-sha1 = "9c68794ef81b08086aeb32eeaf33531668d5f5fc" +uuid = "1fa38f19-a742-5d3f-a2b9-30dd87b9d5f8" +version = "1.3.7" + +[[deps.FreeType2_jll]] +deps = ["Artifacts", "Bzip2_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "2c5512e11c791d1baed2049c5652441b28fc6a31" +uuid = "d7e528f0-a631-5988-bf34-fe36492bcfd7" +version = "2.13.4+0" + +[[deps.FriBidi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "7a214fdac5ed5f59a22c2d9a885a16da1c74bbc7" +uuid = "559328eb-81f9-559d-9380-de523a88c83c" +version = "1.0.17+0" + +[[deps.GLFW_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Libglvnd_jll", "Xorg_libXcursor_jll", "Xorg_libXi_jll", "Xorg_libXinerama_jll", "Xorg_libXrandr_jll", "libdecor_jll", "xkbcommon_jll"] +git-tree-sha1 = "fcb0584ff34e25155876418979d4c8971243bb89" +uuid = "0656b61e-2033-5cc2-a64a-77c0f6c09b89" +version = "3.4.0+2" + +[[deps.GR]] +deps = ["Artifacts", "Base64", "DelimitedFiles", "Downloads", "GR_jll", "HTTP", "JSON", "Libdl", "LinearAlgebra", "Preferences", "Printf", "Qt6Wayland_jll", "Random", "Serialization", "Sockets", "TOML", "Tar", "Test", "p7zip_jll"] +git-tree-sha1 = "1828eb7275491981fa5f1752a5e126e8f26f8741" +uuid = "28b8d3ca-fb5f-59d9-8090-bfdbd6d07a71" +version = "0.73.17" + +[[deps.GR_jll]] +deps = ["Artifacts", "Bzip2_jll", "Cairo_jll", "FFMPEG_jll", "Fontconfig_jll", "FreeType2_jll", "GLFW_jll", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Libtiff_jll", "Pixman_jll", "Qt6Base_jll", "Zlib_jll", "libpng_jll"] +git-tree-sha1 = "27299071cc29e409488ada41ec7643e0ab19091f" +uuid = "d2c73de3-f751-5644-a686-071e5b155ba9" +version = "0.73.17+0" + +[[deps.GettextRuntime_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "Libdl", "Libiconv_jll"] +git-tree-sha1 = "45288942190db7c5f760f59c04495064eedf9340" +uuid = "b0724c58-0f36-5564-988d-3bb0596ebc4a" +version = "0.22.4+0" + +[[deps.Ghostscript_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "Libdl", "Zlib_jll"] +git-tree-sha1 = "38044a04637976140074d0b0621c1edf0eb531fd" +uuid = "61579ee1-b43e-5ca0-a5da-69d92c66a64b" +version = "9.55.1+0" + +[[deps.Glib_jll]] +deps = ["Artifacts", "GettextRuntime_jll", "JLLWrappers", "Libdl", "Libffi_jll", "Libiconv_jll", "Libmount_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "50c11ffab2a3d50192a228c313f05b5b5dc5acb2" +uuid = "7746bdde-850d-59dc-9ae8-88ece973131d" +version = "2.86.0+0" + +[[deps.Graphite2_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "8a6dbda1fd736d60cc477d99f2e7a042acfa46e8" +uuid = "3b182d85-2403-5c21-9c21-1e1f0cc25472" +version = "1.3.15+0" + +[[deps.Grisu]] +git-tree-sha1 = "53bb909d1151e57e2484c3d1b53e19552b887fb2" +uuid = "42e2da0e-8278-4e71-bc24-59509adca0fe" +version = "1.0.2" + +[[deps.HTTP]] +deps = ["Base64", "CodecZlib", "ConcurrentUtilities", "Dates", "ExceptionUnwrapping", "Logging", "LoggingExtras", "MbedTLS", "NetworkOptions", "OpenSSL", "PrecompileTools", "Random", "SimpleBufferStream", "Sockets", "URIs", "UUIDs"] +git-tree-sha1 = "5e6fe50ae7f23d171f44e311c2960294aaa0beb5" +uuid = "cd3eb016-35fb-5094-929b-558a96fad6f3" +version = "1.10.19" + +[[deps.HarfBuzz_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "Glib_jll", "Graphite2_jll", "JLLWrappers", "Libdl", "Libffi_jll"] +git-tree-sha1 = "f923f9a774fcf3f5cb761bfa43aeadd689714813" +uuid = "2e76f6c2-a576-52d4-95c1-20adfe4de566" +version = "8.5.1+0" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.IrrationalConstants]] +git-tree-sha1 = "e2222959fbc6c19554dc15174c81bf7bf3aa691c" +uuid = "92d709cd-6900-40b7-9082-c6be49f344b6" +version = "0.2.4" + +[[deps.JLFzf]] +deps = ["REPL", "Random", "fzf_jll"] +git-tree-sha1 = "82f7acdc599b65e0f8ccd270ffa1467c21cb647b" +uuid = "1019f520-868f-41f5-a6de-eb00f4b6a39c" +version = "0.1.11" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "0533e564aae234aff59ab625543145446d8b6ec2" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.7.1" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JpegTurbo_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "4255f0032eafd6451d707a51d5f0248b8a165e4d" +uuid = "aacddb02-875f-59d6-b918-886e6ef4fbf8" +version = "3.1.3+0" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" + +[[deps.LAME_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "059aabebaa7c82ccb853dd4a0ee9d17796f7e1bc" +uuid = "c1c5ebd0-6772-5130-a774-d5fcae4a789d" +version = "3.100.3+0" + +[[deps.LERC_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "aaafe88dccbd957a8d82f7d05be9b69172e0cee3" +uuid = "88015f11-f218-50d7-93a8-a6af411a945d" +version = "4.0.1+0" + +[[deps.LLVMOpenMP_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "eb62a3deb62fc6d8822c0c4bef73e4412419c5d8" +uuid = "1d63c593-3942-5779-bab2-d838dc0a180e" +version = "18.1.8+0" + +[[deps.LZO_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1c602b1127f4751facb671441ca72715cc95938a" +uuid = "dd4b983a-f0e5-5f8d-a1b7-129d4a5fb1ac" +version = "2.10.3+0" + +[[deps.LaTeXStrings]] +git-tree-sha1 = "dda21b8cbd6a6c40d9d02a73230f9d70fed6918c" +uuid = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +version = "1.4.0" + +[[deps.Latexify]] +deps = ["Format", "Ghostscript_jll", "InteractiveUtils", "LaTeXStrings", "MacroTools", "Markdown", "OrderedCollections", "Requires"] +git-tree-sha1 = "44f93c47f9cd6c7e431f2f2091fcba8f01cd7e8f" +uuid = "23fbe1c1-3f47-55db-b15f-69d7ec21a316" +version = "0.16.10" + + [deps.Latexify.extensions] + DataFramesExt = "DataFrames" + SparseArraysExt = "SparseArrays" + SymEngineExt = "SymEngine" + TectonicExt = "tectonic_jll" + + [deps.Latexify.weakdeps] + DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" + SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" + SymEngine = "123dc426-2d89-5057-bbad-38513e3affd8" + tectonic_jll = "d7dd28d6-a5e6-559c-9131-7eb760cdacc5" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.11.1+1" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.9.0+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "OpenSSL_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.3+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Libffi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c8da7e6a91781c41a863611c7e966098d783c57a" +uuid = "e9f186c6-92d2-5b65-8a66-fee21dc1b490" +version = "3.4.7+0" + +[[deps.Libglvnd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll", "Xorg_libXext_jll"] +git-tree-sha1 = "d36c21b9e7c172a44a10484125024495e2625ac0" +uuid = "7e76a0d4-f3c7-5321-8279-8d96eeed0f29" +version = "1.7.1+1" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.18.0+0" + +[[deps.Libmount_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "3acf07f130a76f87c041cfb2ff7d7284ca67b072" +uuid = "4b2f31a3-9ecc-558c-b454-b3730dcb73e9" +version = "2.41.2+0" + +[[deps.Libtiff_jll]] +deps = ["Artifacts", "JLLWrappers", "JpegTurbo_jll", "LERC_jll", "Libdl", "XZ_jll", "Zlib_jll", "Zstd_jll"] +git-tree-sha1 = "f04133fe05eff1667d2054c53d59f9122383fe05" +uuid = "89763e89-9b03-5906-acba-b20f662cd828" +version = "4.7.2+0" + +[[deps.Libuuid_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "2a7a12fc0a4e7fb773450d17975322aa77142106" +uuid = "38a345b3-de98-5d2b-a5d3-14cd9215e700" +version = "2.41.2+0" + +[[deps.LinearAlgebra]] +deps = ["Libdl", "OpenBLAS_jll", "libblastrampoline_jll"] +uuid = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +version = "1.12.0" + +[[deps.LogExpFunctions]] +deps = ["DocStringExtensions", "IrrationalConstants", "LinearAlgebra"] +git-tree-sha1 = "13ca9e2586b89836fd20cccf56e57e2b9ae7f38f" +uuid = "2ab3a3ac-af41-5b50-aa03-7779005ae688" +version = "0.3.29" + + [deps.LogExpFunctions.extensions] + LogExpFunctionsChainRulesCoreExt = "ChainRulesCore" + LogExpFunctionsChangesOfVariablesExt = "ChangesOfVariables" + LogExpFunctionsInverseFunctionsExt = "InverseFunctions" + + [deps.LogExpFunctions.weakdeps] + ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" + ChangesOfVariables = "9e997f8a-9a97-42d5-a9f1-ce6bfc15e2c0" + InverseFunctions = "3587e190-3f89-42d0-90ee-14403ec27112" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.LoggingExtras]] +deps = ["Dates", "Logging"] +git-tree-sha1 = "f00544d95982ea270145636c181ceda21c4e2575" +uuid = "e6f89c97-d47a-5376-807f-9c37f3926c36" +version = "1.2.0" + +[[deps.MacroTools]] +git-tree-sha1 = "1e0228a030642014fe5cfe68c2c0a818f9e3f522" +uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" +version = "0.5.16" + +[[deps.Markdown]] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MbedTLS]] +deps = ["Dates", "MbedTLS_jll", "MozillaCACerts_jll", "NetworkOptions", "Random", "Sockets"] +git-tree-sha1 = "c067a280ddc25f196b5e7df3877c6b226d390aaf" +uuid = "739be429-bea8-5141-9913-cc70e7f3736d" +version = "1.1.9" + +[[deps.MbedTLS_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "45ad1a08605aabad6f1e7711daa84370655b4e86" +uuid = "c8ffd9c3-330d-5841-b78e-0817d7145fa1" +version = "2.28.6+2" + +[[deps.Measures]] +git-tree-sha1 = "c13304c81eec1ed3af7fc20e75fb6b26092a1102" +uuid = "442fdcdd-2543-5da2-b0f3-8c86c306513e" +version = "0.3.2" + +[[deps.Missings]] +deps = ["DataAPI"] +git-tree-sha1 = "ec4f7fbeab05d7747bdf98eb74d130a2a2ed298d" +uuid = "e1d29d7a-bbdc-5cf2-9ac0-f12de2c33e28" +version = "1.2.0" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2025.5.20" + +[[deps.NaNMath]] +deps = ["OpenLibm_jll"] +git-tree-sha1 = "9b8215b1ee9e78a293f99797cd31375471b2bcae" +uuid = "77ba4419-2d1f-58cd-9bb1-8ffee604a2e3" +version = "1.1.3" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.3.0" + +[[deps.Ogg_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "b6aa4566bb7ae78498a5e68943863fa8b5231b59" +uuid = "e7412a2a-1a6e-54c0-be00-318e2571c051" +version = "1.3.6+0" + +[[deps.OpenBLAS_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl"] +uuid = "4536629a-c528-5b80-bd46-f80d51c5b363" +version = "0.3.29+0" + +[[deps.OpenLibm_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "05823500-19ac-5b8b-9628-191a04bc5112" +version = "0.8.7+0" + +[[deps.OpenSSL]] +deps = ["BitFlags", "Dates", "MozillaCACerts_jll", "OpenSSL_jll", "Sockets"] +git-tree-sha1 = "f1a7e086c677df53e064e0fdd2c9d0b0833e3f6e" +uuid = "4d8831e6-92b7-49fb-bdf8-b643e874388c" +version = "1.5.0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.5.1+0" + +[[deps.Opus_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c392fc5dd032381919e3b22dd32d6443760ce7ea" +uuid = "91d4177d-7536-5919-b921-800302f37372" +version = "1.5.2+0" + +[[deps.OrderedCollections]] +git-tree-sha1 = "05868e21324cede2207c6f0f466b4bfef6d5e7ee" +uuid = "bac558e1-5e72-5ebc-8fee-abe8a469f55d" +version = "1.8.1" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.44.0+1" + +[[deps.Pango_jll]] +deps = ["Artifacts", "Cairo_jll", "Fontconfig_jll", "FreeType2_jll", "FriBidi_jll", "Glib_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl"] +git-tree-sha1 = "1f7f9bbd5f7a2e5a9f7d96e51c9754454ea7f60b" +uuid = "36c8627f-9965-5494-a995-c6b170f724f3" +version = "1.56.4+0" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "7d2f8f21da5db6a806faf7b9b292296da42b2810" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.3" + +[[deps.Pixman_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "JLLWrappers", "LLVMOpenMP_jll", "Libdl"] +git-tree-sha1 = "db76b1ecd5e9715f3d043cec13b2ec93ce015d53" +uuid = "30392449-352a-5448-841d-b1acce4e97dc" +version = "0.44.2+0" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.12.0" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.PlotThemes]] +deps = ["PlotUtils", "Statistics"] +git-tree-sha1 = "41031ef3a1be6f5bbbf3e8073f210556daeae5ca" +uuid = "ccf2f8ad-2431-5c83-bf29-c5338b663b6a" +version = "3.3.0" + +[[deps.PlotUtils]] +deps = ["ColorSchemes", "Colors", "Dates", "PrecompileTools", "Printf", "Random", "Reexport", "StableRNGs", "Statistics"] +git-tree-sha1 = "3ca9a356cd2e113c420f2c13bea19f8d3fb1cb18" +uuid = "995b91a9-d308-5afd-9ec6-746e21dbc043" +version = "1.4.3" + +[[deps.Plots]] +deps = ["Base64", "Contour", "Dates", "Downloads", "FFMPEG", "FixedPointNumbers", "GR", "JLFzf", "JSON", "LaTeXStrings", "Latexify", "LinearAlgebra", "Measures", "NaNMath", "Pkg", "PlotThemes", "PlotUtils", "PrecompileTools", "Printf", "REPL", "Random", "RecipesBase", "RecipesPipeline", "Reexport", "RelocatableFolders", "Requires", "Scratch", "Showoff", "SparseArrays", "Statistics", "StatsBase", "TOML", "UUIDs", "UnicodeFun", "Unzip"] +git-tree-sha1 = "12ce661880f8e309569074a61d3767e5756a199f" +uuid = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" +version = "1.41.1" + + [deps.Plots.extensions] + FileIOExt = "FileIO" + GeometryBasicsExt = "GeometryBasics" + IJuliaExt = "IJulia" + ImageInTerminalExt = "ImageInTerminal" + UnitfulExt = "Unitful" + + [deps.Plots.weakdeps] + FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549" + GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" + IJulia = "7073ff75-c697-5162-941a-fcdaad2a7d2a" + ImageInTerminal = "d8c32880-2388-543b-8c61-d9f865259254" + Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "07a921781cab75691315adc645096ed5e370cb77" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.3.3" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "0f27480397253da18fe2c12a4ba4eb9eb208bf3d" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.5.0" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.PtrArrays]] +git-tree-sha1 = "1d36ef11a9aaf1e8b74dacc6a731dd1de8fd493d" +uuid = "43287f4e-b6f4-7ad1-bb20-aadabca52c3d" +version = "1.3.0" + +[[deps.Qt6Base_jll]] +deps = ["Artifacts", "CompilerSupportLibraries_jll", "Fontconfig_jll", "Glib_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "OpenSSL_jll", "Vulkan_Loader_jll", "Xorg_libSM_jll", "Xorg_libXext_jll", "Xorg_libXrender_jll", "Xorg_libxcb_jll", "Xorg_xcb_util_cursor_jll", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_keysyms_jll", "Xorg_xcb_util_renderutil_jll", "Xorg_xcb_util_wm_jll", "Zlib_jll", "libinput_jll", "xkbcommon_jll"] +git-tree-sha1 = "34f7e5d2861083ec7596af8b8c092531facf2192" +uuid = "c0090381-4147-56d7-9ebc-da0b1113ec56" +version = "6.8.2+2" + +[[deps.Qt6Declarative_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6ShaderTools_jll"] +git-tree-sha1 = "da7adf145cce0d44e892626e647f9dcbe9cb3e10" +uuid = "629bc702-f1f5-5709-abd5-49b8460ea067" +version = "6.8.2+1" + +[[deps.Qt6ShaderTools_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll"] +git-tree-sha1 = "9eca9fc3fe515d619ce004c83c31ffd3f85c7ccf" +uuid = "ce943373-25bb-56aa-8eca-768745ed7b5a" +version = "6.8.2+1" + +[[deps.Qt6Wayland_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Qt6Base_jll", "Qt6Declarative_jll"] +git-tree-sha1 = "8f528b0851b5b7025032818eb5abbeb8a736f853" +uuid = "e99dba38-086e-5de3-a5b1-6e4c66e897c3" +version = "6.8.2+2" + +[[deps.REPL]] +deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.RecipesBase]] +deps = ["PrecompileTools"] +git-tree-sha1 = "5c3d09cc4f31f5fc6af001c250bf1278733100ff" +uuid = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" +version = "1.3.4" + +[[deps.RecipesPipeline]] +deps = ["Dates", "NaNMath", "PlotUtils", "PrecompileTools", "RecipesBase"] +git-tree-sha1 = "45cf9fd0ca5839d06ef333c8201714e888486342" +uuid = "01d81517-befc-4cb6-b9ec-a95719d0359c" +version = "0.6.12" + +[[deps.Reexport]] +git-tree-sha1 = "45e428421666073eab6f2da5c9d310d99bb12f9b" +uuid = "189a3867-3050-52da-a836-e630ba90ab69" +version = "1.2.2" + +[[deps.RelocatableFolders]] +deps = ["SHA", "Scratch"] +git-tree-sha1 = "ffdaf70d81cf6ff22c2b6e733c900c3321cab864" +uuid = "05181044-ff0b-4ac5-8273-598c1e38db00" +version = "1.0.1" + +[[deps.Requires]] +deps = ["UUIDs"] +git-tree-sha1 = "62389eeff14780bfe55195b7204c0d8738436d64" +uuid = "ae029012-a4dd-5104-9daa-d747884805df" +version = "1.3.1" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Scratch]] +deps = ["Dates"] +git-tree-sha1 = "9b81b8393e50b7d4e6d0a9f14e192294d3b7c109" +uuid = "6c6a2e73-6563-6170-7368-637461726353" +version = "1.3.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.Showoff]] +deps = ["Dates", "Grisu"] +git-tree-sha1 = "91eddf657aca81df9ae6ceb20b959ae5653ad1de" +uuid = "992d4aef-0814-514b-bc4d-f2e9a6c4116f" +version = "1.0.3" + +[[deps.SimpleBufferStream]] +git-tree-sha1 = "f305871d2f381d21527c770d4788c06c097c9bc1" +uuid = "777ac1f9-54b0-4bf8-805c-2214025038e7" +version = "1.2.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.SortingAlgorithms]] +deps = ["DataStructures"] +git-tree-sha1 = "64d974c2e6fdf07f8155b5b2ca2ffa9069b608d9" +uuid = "a2af1166-a08f-5f64-846c-94a0d3cef48c" +version = "1.2.2" + +[[deps.SparseArrays]] +deps = ["Libdl", "LinearAlgebra", "Random", "Serialization", "SuiteSparse_jll"] +uuid = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +version = "1.12.0" + +[[deps.StableRNGs]] +deps = ["Random"] +git-tree-sha1 = "95af145932c2ed859b63329952ce8d633719f091" +uuid = "860ef19b-820b-49d6-a774-d7a799459cd3" +version = "1.0.3" + +[[deps.Statistics]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "ae3bb1eb3bba077cd276bc5cfc337cc65c3075c0" +uuid = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" +version = "1.11.1" +weakdeps = ["SparseArrays"] + + [deps.Statistics.extensions] + SparseArraysExt = ["SparseArrays"] + +[[deps.StatsAPI]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "9d72a13a3f4dd3795a195ac5a44d7d6ff5f552ff" +uuid = "82ae8749-77ed-4fe6-ae5f-f523153014b0" +version = "1.7.1" + +[[deps.StatsBase]] +deps = ["AliasTables", "DataAPI", "DataStructures", "LinearAlgebra", "LogExpFunctions", "Missings", "Printf", "Random", "SortingAlgorithms", "SparseArrays", "Statistics", "StatsAPI"] +git-tree-sha1 = "2c962245732371acd51700dbb268af311bddd719" +uuid = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91" +version = "0.34.6" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.SuiteSparse_jll]] +deps = ["Artifacts", "Libdl", "libblastrampoline_jll"] +uuid = "bea87d4a-7f5b-5778-9afe-8cc45184846c" +version = "7.8.3+2" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.TensorCore]] +deps = ["LinearAlgebra"] +git-tree-sha1 = "1feb45f88d133a655e001435632f019a9a1bcdb6" +uuid = "62fd8b95-f654-4bbd-a8a5-9c27f68ccd50" +version = "0.1.1" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.URIs]] +git-tree-sha1 = "bef26fb046d031353ef97a82e3fdb6afe7f21b1a" +uuid = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" +version = "1.6.1" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.UnicodeFun]] +deps = ["REPL"] +git-tree-sha1 = "53915e50200959667e78a92a418594b428dffddf" +uuid = "1cfade01-22cf-5700-b092-accc4b62d6e1" +version = "0.4.1" + +[[deps.Unzip]] +git-tree-sha1 = "ca0969166a028236229f63514992fc073799bb78" +uuid = "41fe7b60-77ed-43a1-b4f0-825fd5a5650d" +version = "0.2.0" + +[[deps.Vulkan_Loader_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Wayland_jll", "Xorg_libX11_jll", "Xorg_libXrandr_jll", "xkbcommon_jll"] +git-tree-sha1 = "2f0486047a07670caad3a81a075d2e518acc5c59" +uuid = "a44049a8-05dd-5a78-86c9-5fde0876e88c" +version = "1.3.243+0" + +[[deps.Wayland_jll]] +deps = ["Artifacts", "EpollShim_jll", "Expat_jll", "JLLWrappers", "Libdl", "Libffi_jll"] +git-tree-sha1 = "96478df35bbc2f3e1e791bc7a3d0eeee559e60e9" +uuid = "a2964d1f-97da-50d4-b82a-358c7fce9d89" +version = "1.24.0+0" + +[[deps.XZ_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "fee71455b0aaa3440dfdd54a9a36ccef829be7d4" +uuid = "ffd25f8a-64ca-5728-b0f7-c24cf3aae800" +version = "5.8.1+0" + +[[deps.Xorg_libICE_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a3ea76ee3f4facd7a64684f9af25310825ee3668" +uuid = "f67eecfb-183a-506d-b269-f58e52b52d7c" +version = "1.1.2+0" + +[[deps.Xorg_libSM_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libICE_jll"] +git-tree-sha1 = "9c7ad99c629a44f81e7799eb05ec2746abb5d588" +uuid = "c834827a-8449-5923-a945-d239c165b7dd" +version = "1.2.6+0" + +[[deps.Xorg_libX11_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xtrans_jll"] +git-tree-sha1 = "b5899b25d17bf1889d25906fb9deed5da0c15b3b" +uuid = "4f6342f7-b3d2-589e-9d20-edeb45f2b2bc" +version = "1.8.12+0" + +[[deps.Xorg_libXau_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "aa1261ebbac3ccc8d16558ae6799524c450ed16b" +uuid = "0c0b7dd1-d40b-584c-a123-a41640f87eec" +version = "1.0.13+0" + +[[deps.Xorg_libXcursor_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXfixes_jll", "Xorg_libXrender_jll"] +git-tree-sha1 = "6c74ca84bbabc18c4547014765d194ff0b4dc9da" +uuid = "935fb764-8cf2-53bf-bb30-45bb1f8bf724" +version = "1.2.4+0" + +[[deps.Xorg_libXdmcp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "52858d64353db33a56e13c341d7bf44cd0d7b309" +uuid = "a3789734-cfe1-5b06-b2d0-1dd0d9d62d05" +version = "1.1.6+0" + +[[deps.Xorg_libXext_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "a4c0ee07ad36bf8bbce1c3bb52d21fb1e0b987fb" +uuid = "1082639a-0dae-5f34-9b06-72781eeb8cb3" +version = "1.3.7+0" + +[[deps.Xorg_libXfixes_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "75e00946e43621e09d431d9b95818ee751e6b2ef" +uuid = "d091e8ba-531a-589c-9de9-94069b037ed8" +version = "6.0.2+0" + +[[deps.Xorg_libXi_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXfixes_jll"] +git-tree-sha1 = "a376af5c7ae60d29825164db40787f15c80c7c54" +uuid = "a51aa0fd-4e3c-5386-b890-e753decda492" +version = "1.8.3+0" + +[[deps.Xorg_libXinerama_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll"] +git-tree-sha1 = "a5bc75478d323358a90dc36766f3c99ba7feb024" +uuid = "d1454406-59df-5ea1-beac-c340f2130bc3" +version = "1.1.6+0" + +[[deps.Xorg_libXrandr_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXext_jll", "Xorg_libXrender_jll"] +git-tree-sha1 = "aff463c82a773cb86061bce8d53a0d976854923e" +uuid = "ec84b674-ba8e-5d96-8ba1-2a689ba10484" +version = "1.5.5+0" + +[[deps.Xorg_libXrender_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "7ed9347888fac59a618302ee38216dd0379c480d" +uuid = "ea2f1a96-1ddc-540d-b46f-429655e07cfa" +version = "0.9.12+0" + +[[deps.Xorg_libxcb_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libXau_jll", "Xorg_libXdmcp_jll"] +git-tree-sha1 = "bfcaf7ec088eaba362093393fe11aa141fa15422" +uuid = "c7cfdc94-dc32-55de-ac96-5a1b8d977c5b" +version = "1.17.1+0" + +[[deps.Xorg_libxkbfile_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libX11_jll"] +git-tree-sha1 = "e3150c7400c41e207012b41659591f083f3ef795" +uuid = "cc61e674-0454-545c-8b26-ed2c68acab7a" +version = "1.1.3+0" + +[[deps.Xorg_xcb_util_cursor_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_image_jll", "Xorg_xcb_util_jll", "Xorg_xcb_util_renderutil_jll"] +git-tree-sha1 = "9750dc53819eba4e9a20be42349a6d3b86c7cdf8" +uuid = "e920d4aa-a673-5f3a-b3d7-f755a4d47c43" +version = "0.1.6+0" + +[[deps.Xorg_xcb_util_image_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] +git-tree-sha1 = "f4fc02e384b74418679983a97385644b67e1263b" +uuid = "12413925-8142-5f55-bb0e-6d7ca50bb09b" +version = "0.4.1+0" + +[[deps.Xorg_xcb_util_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll"] +git-tree-sha1 = "68da27247e7d8d8dafd1fcf0c3654ad6506f5f97" +uuid = "2def613f-5ad1-5310-b15b-b15d46f528f5" +version = "0.4.1+0" + +[[deps.Xorg_xcb_util_keysyms_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] +git-tree-sha1 = "44ec54b0e2acd408b0fb361e1e9244c60c9c3dd4" +uuid = "975044d2-76e6-5fbe-bf08-97ce7c6574c7" +version = "0.4.1+0" + +[[deps.Xorg_xcb_util_renderutil_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] +git-tree-sha1 = "5b0263b6d080716a02544c55fdff2c8d7f9a16a0" +uuid = "0d47668e-0667-5a69-a72c-f761630bfb7e" +version = "0.3.10+0" + +[[deps.Xorg_xcb_util_wm_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xcb_util_jll"] +git-tree-sha1 = "f233c83cad1fa0e70b7771e0e21b061a116f2763" +uuid = "c22f9ab0-d5fe-5066-847c-f4bb1cd4e361" +version = "0.4.2+0" + +[[deps.Xorg_xkbcomp_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxkbfile_jll"] +git-tree-sha1 = "801a858fc9fb90c11ffddee1801bb06a738bda9b" +uuid = "35661453-b289-5fab-8a00-3d9160c6a3a4" +version = "1.4.7+0" + +[[deps.Xorg_xkeyboard_config_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_xkbcomp_jll"] +git-tree-sha1 = "00af7ebdc563c9217ecc67776d1bbf037dbcebf4" +uuid = "33bec58e-1273-512f-9401-5d533626f822" +version = "2.44.0+0" + +[[deps.Xorg_xtrans_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "a63799ff68005991f9d9491b6e95bd3478d783cb" +uuid = "c5fb5394-a638-5e4d-96e5-b29de1b5cf10" +version = "1.6.0+0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.1+2" + +[[deps.Zstd_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "446b23e73536f84e8037f5dce465e92275f6a308" +uuid = "3161d3a3-bdf6-5164-811a-617609db77b4" +version = "1.5.7+1" + +[[deps.eudev_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "c3b0e6196d50eab0c5ed34021aaa0bb463489510" +uuid = "35ca27e7-8b34-5b7f-bca9-bdc33f59eb06" +version = "3.2.14+0" + +[[deps.fzf_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "b6a34e0e0960190ac2a4363a1bd003504772d631" +uuid = "214eeab7-80f7-51ab-84ad-2988db7cef09" +version = "0.61.1+0" + +[[deps.libaom_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "371cc681c00a3ccc3fbc5c0fb91f58ba9bec1ecf" +uuid = "a4ae2306-e953-59d6-aa16-d00cac43593b" +version = "3.13.1+0" + +[[deps.libass_jll]] +deps = ["Artifacts", "Bzip2_jll", "FreeType2_jll", "FriBidi_jll", "HarfBuzz_jll", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "125eedcb0a4a0bba65b657251ce1d27c8714e9d6" +uuid = "0ac62f75-1d6f-5e53-bd7c-93b484bb37c0" +version = "0.17.4+0" + +[[deps.libblastrampoline_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850b90-86db-534c-a0d3-1478176c7d93" +version = "5.13.1+1" + +[[deps.libdecor_jll]] +deps = ["Artifacts", "Dbus_jll", "JLLWrappers", "Libdl", "Libglvnd_jll", "Pango_jll", "Wayland_jll", "xkbcommon_jll"] +git-tree-sha1 = "9bf7903af251d2050b467f76bdbe57ce541f7f4f" +uuid = "1183f4f0-6f2a-5f1a-908b-139f9cdfea6f" +version = "0.2.2+0" + +[[deps.libevdev_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "56d643b57b188d30cccc25e331d416d3d358e557" +uuid = "2db6ffa8-e38f-5e21-84af-90c45d0032cc" +version = "1.13.4+0" + +[[deps.libfdk_aac_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "646634dd19587a56ee2f1199563ec056c5f228df" +uuid = "f638f0a6-7fb0-5443-88ba-1cc74229b280" +version = "2.0.4+0" + +[[deps.libinput_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "eudev_jll", "libevdev_jll", "mtdev_jll"] +git-tree-sha1 = "91d05d7f4a9f67205bd6cf395e488009fe85b499" +uuid = "36db933b-70db-51c0-b978-0f229ee0e533" +version = "1.28.1+0" + +[[deps.libpng_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Zlib_jll"] +git-tree-sha1 = "07b6a107d926093898e82b3b1db657ebe33134ec" +uuid = "b53b4c65-9356-5827-b1ea-8c7a1a84506f" +version = "1.6.50+0" + +[[deps.libvorbis_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Ogg_jll"] +git-tree-sha1 = "11e1772e7f3cc987e9d3de991dd4f6b2602663a5" +uuid = "f27f6e37-5d2b-51aa-960f-b287f2bc3b7a" +version = "1.3.8+0" + +[[deps.mtdev_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "b4d631fd51f2e9cdd93724ae25b2efc198b059b1" +uuid = "009596ad-96f7-51b1-9f1b-5ce2d5e8a71e" +version = "1.1.7+0" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.64.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.5.0+2" + +[[deps.x264_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "14cc7083fc6dff3cc44f2bc435ee96d06ed79aa7" +uuid = "1270edf5-f2f9-52d2-97e9-ab00b5d0237a" +version = "10164.0.1+0" + +[[deps.x265_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "e7b67590c14d487e734dcb925924c5dc43ec85f3" +uuid = "dfaa095f-4041-5dcd-9319-2fabd8486b76" +version = "4.1.0+0" + +[[deps.xkbcommon_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl", "Xorg_libxcb_jll", "Xorg_xkeyboard_config_jll"] +git-tree-sha1 = "fbf139bce07a534df0e699dbb5f5cc9346f95cc1" +uuid = "d8fb68d0-12a3-5cfd-a85a-d49703b185fd" +version = "1.9.2+0" +""" + +# ╔═╡ Cell order: +# ╠═991c318e-a5f7-11f0-863c-43de9de771b5 +# ╠═991c38b4-a5f7-11f0-9a05-41e54e0f74d7 +# ╠═991c3aa8-a5f7-11f0-83c6-05f3e36fe828 +# ╠═991c416a-a5f7-11f0-80f0-575e5b1b47bd +# ╠═991c42dc-a5f7-11f0-b04f-db65b34a6f41 +# ╠═991c48ea-a5f7-11f0-9593-b1fa84c5079f +# ╠═991c4fd4-a5f7-11f0-b856-1dc751df9856 +# ╠═991c529a-a5f7-11f0-af23-b9049ff1b267 +# ╟─00000000-0000-0000-0000-000000000001 +# ╟─00000000-0000-0000-0000-000000000002