Closed-loop MPC for multi-agent tracking sheaf problems#65
Conversation
61d075f to
8990b4d
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #65 +/- ##
==========================================
+ Coverage 73.60% 75.06% +1.45%
==========================================
Files 36 36
Lines 2550 2707 +157
==========================================
+ Hits 1877 2032 +155
- Misses 673 675 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
| for i in 1:prob.n_agents | ||
| va = agent_vertex(prob, i, 0) | ||
| for c in 1:size(prob.Ad[i], 1) | ||
| pinned[offsets[va] + c] = x_agents[i][c] | ||
| end | ||
| end |
| for j in 1:prob.n_targets, ts in 0:prob.k | ||
| vt = target_vertex(prob, j, ts) | ||
| val = target_window[j][ts + 1] | ||
| for c in 1:length(val) | ||
| pinned[offsets[vt] + c] = val[c] | ||
| end | ||
| end |
| nx = [size(prob.Ad[i], 1) for i in 1:prob.n_agents] | ||
| x_now = copy.(x0_agents) | ||
| agent_trajs = [Matrix{Float64}(undef, k+1, nx[i]) for i in 1:prob.n_agents] | ||
| for i in 1:prob.n_agents; agent_trajs[i][1, :] = x_now[i]; end |
| x_B = Vector{Float64}(undef, length(op.boundary)) | ||
| p = 1 | ||
| for i in 1:prob.n_agents, c in 1:nx[i]; x_B[p] = x_now[i][c]; p += 1; end | ||
| for ts in 0:op.window, j in 1:prob.n_targets | ||
| for c in eachindex(local_targets[j][ts+1]); x_B[p] = local_targets[j][ts+1][c]; p += 1; end | ||
| end | ||
| z = op(x_B) |
| α_opt = pinv(QN) * rhs | ||
| QN = Matrix(N' * Q * N) | ||
| rhs = -(N' * Q * point) | ||
| α_opt = svd(QN) \ rhs |
There was a problem hiding this comment.
Why is this using svd instead of ldl_nullspace?
| # Overload that pins individual stalk components rather than whole vertices. | ||
| # Works directly on the flat Laplacian since the vertex-level helpers above | ||
| # cannot express partial pinning. | ||
| function harmonic_extension(s::EuclideanSheaf, boundary::Dict{Int,Float64}) |
There was a problem hiding this comment.
Is this dead code? I thought we discussed a way to avoid the need for partial pinning that breaks the abstraction of harmonic extension.
| _default_ldlt_nullity_threshold(maximum(i -> abs(D[i, i]), 1:n; init=0.0)) : tol | ||
| c = F.P' \ b | ||
| z = F.L \ c | ||
| w = ldiv_diag_pinv(D, z, threshold) |
There was a problem hiding this comment.
this should use the same ldiv_diag_pinv! as above. If we are reusing the F factorization object, then we might as well bundle a w vector buffer to reuse as well.
| n = size(D, 1) | ||
| threshold = isnothing(tol) ? | ||
| _default_ldlt_nullity_threshold(maximum(i -> abs(D[i, i]), 1:n; init=0.0)) : tol | ||
| c = F.P' \ b |
There was a problem hiding this comment.
you can do an in place ldiv and reuse all the memory. 0 allocations per step would be ideal
| c = F.P' \ b | ||
| z = F.L \ c | ||
| w = ldiv_diag_pinv(D, z, threshold) | ||
| return F.P \ (F.L' \ w) |
| L_II = sparse(L[interior, interior]) | ||
| L_IB = sparse(L[interior, boundary]) | ||
| F = ldlt!(ChordalLDLt(L_II), RowMaximum(); check=false) | ||
| tol = sqrt(eps(Float64)) * max(1.0, maximum(i -> abs(F.D[i,i]), 1:size(F.D,1); init=0.0)) |
There was a problem hiding this comment.
I think this logic is repeated at least one other place and so should be a function.
| Fq = ldlt!(DenseLDLtPivoted(NtQ_II * N), RowMaximum(); check=false) | ||
| rhs = NtQ_II * H | ||
| coeff = similar(rhs) | ||
| for col in axes(rhs, 2); coeff[:, col] = ldlt_pinv_solve(Fq, rhs[:, col]); end |
There was a problem hiding this comment.
Is this computing a nullspace with multiple right hand sides? Maybe this should be its own method?
| return TrackingExtension(M, boundary, interior, stalks, sparse(L), size(N, 2), W) | ||
| end | ||
|
|
||
| function (op::TrackingExtension)(x_B::AbstractVector) |
There was a problem hiding this comment.
nice use of function call overloading.
|
|
||
| function (op::TrackingExtension)(x_B::AbstractVector) | ||
| @argcheck length(x_B) == length(op.boundary) | ||
| z = zeros(sum(op.stalks)) |
There was a problem hiding this comment.
This could be cached in the cache and returned by reference.
There was a problem hiding this comment.
or use a mul!(z::AbstractVector, op::TrackingExtension, x_B::AbstractVector)
| L = sheaf_laplacian_matrix_direct(sheaf) | ||
| t == 0 && (nd = size(N, 2)) | ||
| residual = sqrt(max(0.0, dot(z_arr, L * z_arr))) | ||
| x_now = _apply_first_control(local_prob, z_arr, sheaf.vertex_stalks) |
There was a problem hiding this comment.
try to minimize the repeated logic across the use_cached switch
This PR implements a receding-horizon MPC algorithm for multi-agent tracking sheaf problems.
Summary
window_problemto take the entire horizon and slice it up into sub problems of sizek.run_mpc_scenariowith:cached(reuses the factorization + orthogonal projection) &:naive(recomputes the open loop problem for each MPC step)ldlt_pinv_solvehelpers toEuclideanSheavesand switchedsolve_quadratic_on_basisto usesvd\Tests
test/network_sheaves/MultiAgentTrackingMPC.jl.Docs
docs/literature/control/mpc_target_tracking.jlshowcasing the results of this PR and the application of the algorithm in different scenarios.Closes #62