Layered Control Architecture for Multi-Quadrotor Tracking (Scenario 5)

This example demonstrates a layered control architecture designed for multi-agent target tracking where high-level spatial coordination is handled by a static cellular sheaf and low-level tracking is managed by independent LQR controllers running on distributed Julia worker processes.

Architecture Overview

The control pipeline is structured as follows:

  1. 2D Coordination Sheaf: Operates on spatial positions ($y, z$), resolving the conflict between target tracking and agent consensus by computing a harmonic extension $\mathbf{q}^*$.
  2. Distributed Tree Solve: Solves for $\mathbf{q}^*$ across parallel worker processes using tree-parallel message passing on the clique tree of the restricted Laplacian.
  3. Local Stalk Embedding & Tikhonov Filter: Each worker embeds its 2D spatial reference into its full 6D state space ($[y^*, z^*, 0, 0, 0, 0]^\top$) and smooths it using a Tikhonov reference filter.
  4. LQR Control: Drives each agent to the filtered reference on its dedicated worker process using an optimal state-feedback gain computed via the Discrete Algebraic Riccati Equation (DARE).

This separation of concerns ensures that the coordination Laplacian $H$ remains strictly positive-definite (full rank) and allows high-level spatial coordination to run efficiently across worker processes.

using CellularSheaves
using CellularSheaves.ControlSheaves.AgentControllers
using CellularSheaves.ControlSheaves.DistributedLayeredControl
using LinearAlgebra
using Plots
using Printf
using Distributed

A single house style for every figure below — matches the escort.jl style

default(framestyle = :box, grid = true, gridalpha = 0.18, gridstyle = :dot,
    titlefontsize = 10, guidefontsize = 9, legendfontsize = 8, tickfontsize = 8,
    markerstrokewidth = 0, size = (1200, 380))

Setup Dynamics & Constants

h = 0.05
T_end = 2.0
steps = Int(T_end / h) + 1
41

We use the PlanarQuadrotorDynamics built into AgentControllers.jl

dyn = PlanarQuadrotorDynamics()
Ac, Bc = AgentControllers.continuous_matrices(dyn)

nx = size(Ac, 1)
nu = size(Bc, 2)
2

Compute Optimal LQR Gain (Discrete-time)

Q_diag = zeros(nx)
Q_diag[1:2] .= 10000.0
Q_diag[3] = 50.0
Q_diag[4] = 500.0
Q_diag[5:6] .= 10.0
Q_lqr = Matrix{Float64}(Diagonal(Q_diag))
R_lqr = Matrix{Float64}(I, nu, nu) * 0.0001

ctrl = LQRController(dyn, h, Q_lqr, R_lqr)
CellularSheaves.ControlSheaves.AgentControllers.LQRController([-17.14452492749712 88.26817645626129 12.129118116954233 -7.555914057276707 7.20592528430904 1.07321442932949; 17.144524927497006 88.26817645626127 -12.12911811695422 7.555914057276625 7.20592528430904 -1.0732144293294927])

2D Coordination Sheaf Construction

Scenario 5 Configuration:

  • Agent 1 tracks Target 1 in z
  • Agent 2 tracks Target 2 in yz
  • Agents agree in y (Consensus)

We set D = 2 for spatial positions [y, z].

D = 2
NA = 2
NT = 2
TotalV = NA + NT

sheaf = EuclideanSheaf{Float64}(fill(D, TotalV))
A network sheaf with 4 vertex stalks and 0 edge stalks.

Coordinate projection matrices

R_y  = [1.0 0.0]
R_z  = [0.0 1.0]
R_yz = Matrix{Float64}(I, 2, 2)
2×2 Matrix{Float64}:
 1.0  0.0
 0.0  1.0

Setup coordination edges

add_sheaf_edge!(sheaf, 1, 2, R_y, R_y)
add_sheaf_edge!(sheaf, 1, 3, R_z, R_z)
add_sheaf_edge!(sheaf, 2, 4, R_yz, R_yz)
3

Simulation Framework

Ensure we have enough worker processes. Workers start with stderr detached: a worker inherits the parent's stderr, which while these docs are built is the pipe capturing the page's output, and holding it open stalls the build. Each worker still reports through its stdout, which the parent relays.

workers_pids = workers()
if length(workers_pids) < NA
    redirect_stderr(devnull) do
        addprocs(NA - length(workers_pids);
            exeflags = ["--project=$(Base.active_project())",
                "--sysimage=$(unsafe_string(Base.JLOptions().image_file))"])
    end
    workers_pids = workers()
    @eval @everywhere using CellularSheaves
    @eval @everywhere using CellularSheaves.ControlSheaves.AgentControllers
    @eval @everywhere using CellularSheaves.ControlSheaves.DistributedLayeredControl
end

function run_scenario(target_type=:bobbing, mode=:distributed)
    local t1_pos, t2_pos
    if target_type == :bobbing
        omega = 2π * 2 / (40 * h)
        t1_pos = t -> [0.0, 1.0 + 0.3sin(omega*t)]
        t2_pos = t -> [1.5, 2.0 + 0.3sin(omega*t)]
    else
        t1_pos = t -> [0.0, 1.0]
        t2_pos = t -> [1.5, 2.0]
    end

    target_trajectory_func = (v, t) -> v == 3 ? t1_pos(t) : t2_pos(t)

    init_states = [[-2.0, 0.5, 0.0, 0.0, 0.0, 0.0],
                   [2.0, 1.0, 0.0, 0.0, 0.0, 0.0]]

    agent_configs = [ (init_states[i], dyn, ctrl.K) for i in 1:NA ] # Initialize agent configs for the workers

    init_distributed_agents!(workers_pids, agent_configs, h, 0.02) # Initialize workers

    prob = LayeredControlProblem(
        sheaf=sheaf,
        target_nodes=collect((NA+1):(NA+NT)),
        target_trajectory_func=target_trajectory_func,
        agent_configs=agent_configs,
        dt=h,
        steps=steps,
        pos_dim=D  # pass both y and z to the physical controller (no homogeneous coordinate)
    )

    result = run_layered_simulation(prob, workers_pids; mode=mode, nx=nx)
    return result
end
run_scenario (generic function with 3 methods)

Execution and Visualization

Run distributed simulation scenarios

res_fixed = run_scenario(:fixed, :distributed)
res_bob   = run_scenario(:bobbing, :distributed)
LayeredSimulationResult(LayeredControlProblem(A network sheaf with 4 vertex stalks and 3 edge stalks.
, [3, 4], Main.var"##2261".var"#run_scenario##8#run_scenario##9"(Core.Box(Main.var"##2261".var"#run_scenario##2#run_scenario##3"{Float64}(6.283185307179586)), Core.Box(Main.var"##2261".var"#run_scenario##0#run_scenario##1"{Float64}(6.283185307179586))), nothing, nothing, Tuple{Vector{Float64}, PlanarQuadrotorDynamics, Matrix{Float64}}[([-2.0, 0.5, 0.0, 0.0, 0.0, 0.0], PlanarQuadrotorDynamics(9.81, 0.5, 0.01, 0.25), [-17.14452492749712 88.26817645626129 12.129118116954233 -7.555914057276707 7.20592528430904 1.07321442932949; 17.144524927497006 88.26817645626127 -12.12911811695422 7.555914057276625 7.20592528430904 -1.0732144293294927]), ([2.0, 1.0, 0.0, 0.0, 0.0, 0.0], PlanarQuadrotorDynamics(9.81, 0.5, 0.01, 0.25), [-17.14452492749712 88.26817645626129 12.129118116954233 -7.555914057276707 7.20592528430904 1.07321442932949; 17.144524927497006 88.26817645626127 -12.12911811695422 7.555914057276625 7.20592528430904 -1.0732144293294927])], 0.05, 41, 0.0, 2), [-1.9963024171765864 1.9993176924708422; -1.9472236051570877 1.990407868490155; -1.801630306125528 1.9654946846389558; -1.5532977012565852 1.9255373511985092; -1.225403134248643 1.8751619955149559; -0.852457406702256 1.819834940733259; -0.46873341112944694 1.7644684687947072; -0.10209188105530297 1.7127762697506381; 0.22834886921535535 1.6671122753210175; 0.5121028216517901 1.6285964323008972; 0.7458820789200127 1.597380108960741; 0.931642468559826 1.5729499149110866; 1.0746286336989785 1.554408192202188; 1.181708230320708 1.5406989990587387; 1.2601161492575632 1.5307698378473136; 1.316618358349855 1.5236727612779253; 1.357041076653062 1.5186155176789171; 1.3860825534929688 1.514975879846205; 1.407320168451939 1.5122918809608257; 1.4233351250018806 1.5102386756057107; 1.4358932400000082 1.5086001162230385; 1.4461381150078783 1.507240515020394; 1.4547692017583107 1.5060798042998327; 1.4621903901675515 1.5050735670341913; 1.4686242946490542 1.5041982007631458; 1.4741936181401616 1.503440738523179; 1.4789744160041596 1.5027924808938384; 1.483027471099466 1.5022454863121788; 1.486414009153775 1.5017910258495373; 1.4892012059310746 1.5014192558809727; 1.4914618017236578 1.5011195410451545; 1.4932709434998128 1.5008810339156675; 1.494702300427491 1.500693266454803; 1.4958246319422728 1.5005466232075342; 1.4966993518947158 1.5004326469425338; 1.4973792103901975 1.500344178327274; 1.4979079688472414 1.5002753585956434; 1.498320828860151 1.5002215347431154; 1.4986453489927465 1.5001791065931913; 1.4989026097888678 1.5001453489561718; 1.4991084388267542 1.5001182335645453;;; 0.7219990545364317 1.4064420299623546; 1.0402935928777268 1.9641005432907734; 1.1805175984470286 2.168398233091473; 1.2495343601047038 2.2478583862663397; 1.285148274151202 2.284931597634253; 1.2915808735314336 2.291553901088821; 1.269305536954337 2.269302256066413; 1.220647897396589 2.220647504188614; 1.1503891633777668 2.1503891167129305; 1.0654089848572978 2.06540897935586; 0.9740260828605944 1.9740260822149556; 0.885185684558683 1.8851856844831485; 0.8075841109071417 1.807584110898323; 0.7488175450614125 1.7488175450603838; 0.7146384679892617 1.7146384679891413; 0.7083925659002237 1.7083925659002093; 0.7306912312092415 1.7306912312092397; 0.779351715192014 1.7793517151920135; 0.8496107906387891 1.849610790638789; 0.9345910097210746 1.9345910097210746; 1.025973916503094 2.0259739165030934; 1.114814315366873 2.114814315366873; 1.192415889084169 2.192415889084169; 1.2511824549375756 2.251182454937575; 1.285361532010621 2.2853615320106204; 1.291607434099763 2.2916074340997628; 1.2693087687907572 2.269308768790757; 1.220648284807986 2.220648284807986; 1.1503892093612107 2.1503892093612107; 1.065408990278925 2.065408990278925; 0.9740260834969058 1.974026083496906; 0.885185684633127 1.885185684633127; 0.8075841109158314 1.807584110915831; 0.7488175450624247 1.7488175450624244; 0.714638467989379 1.714638467989379; 0.7083925659002371 1.7083925659002372; 0.7306912312092428 1.730691231209243; 0.779351715192014 1.779351715192014; 0.8496107906387893 1.8496107906387893; 0.9345910097210747 1.9345910097210746; 1.0259739165030943 2.025973916503094;;; -1.8092148371443195 0.3338507787928138; -4.112734415632083 0.6871883351444119; -4.287654835066025 0.6246560204721929; -3.2807139737117708 0.4273032469214091; -1.8346058628007198 0.19981474853107206; -0.41660870149549023 -0.0026010206022928292; 0.7269225895979411 -0.15451786025480907; 1.5057757100616773 -0.25002974418084223; 1.9282400913073312 -0.2947649374220972; 2.0555165067603567 -0.29988732049750777; 1.9690450763000067 -0.2779922820500279; 1.7495031294611951 -0.24062009288698824; 1.4652077884328425 -0.1970160077983308; 1.1673821751634348 -0.15376028928858518; 0.8899259020731934 -0.11493800795130411; 0.6517489421173253 -0.08258911965683041; 0.46022542982252285 -0.05725420606394798; 0.3147999221284302 -0.038498552447992204; 0.21017871571235028 -0.025351191698494976; 0.13884211171016206 -0.016634682989977743; 0.09282110996966728 -0.011186857884413058; 0.06480733428474413 -0.00799003726201997; 0.04872671290173139 -0.006229213328128171; 0.03992472504857431 -0.005301171313491184; 0.035100741316132916 -0.004793810953988996; 0.03210443201413699 -0.004450764934208393; 0.029677875354423844 -0.0041319883284673875; 0.02719890309805538 -0.003777020371231266; 0.02445763971889201 -0.003374441117833971; 0.02148037042079449 -0.0029387490385460405; 0.018402746454462597 -0.002494402562392776; 0.015387096620135803 -0.0020659501477059334; 0.01257514286796044 -0.0016728451335750946; 0.010066569695536208 -0.0013275378905739043; 0.007914664684857728 -0.0010356204957600408; 0.006131838849880817 -0.0007970643644475402; 0.004699687336368105 -0.0006078685224697634; 0.003580006628875641 -0.000461683610759611; 0.0027246526447154617 -0.00035117431979421725; 0.0020832340061410753 -0.0002690259887623529; 0.0016083935861964905 -0.00020859411938503735;;; 0.29580662587309625 -0.05458460233262505; 1.8556585804482308 -0.3306937021266116; 3.9823731251028707 -0.6607216351968991; 5.868613654239133 -0.9214381131831876; 7.128949688011582 -1.0749789294155343; 7.67295814590719 -1.121555772725684; 7.5825179939582865 -1.080683903174804; 7.019471966409565 -0.9791959620769963; 6.163621581249938 -0.8437067130603589; 5.176131669244168 -0.6965082529280346; 4.182107660924865 -0.5539346000712965; 3.26625547882174 -0.42632833837895134; 2.476432270873423 -0.3189052039329995; 1.8310988378805342 -0.23299867679313668; 1.3278999699187994 -0.16734149316319943; 0.9516593602492611 -0.11918609123040833; 0.6809064190091191 -0.0851747819161386; 0.4926411898411502 -0.06194400607544783; 0.3654161921421739 -0.04649074608099227; 0.28101383723266327 -0.03635004271052403; 0.22507297958471809 -0.029637692298741358; 0.18701214689233372 -0.02500769589291626; 0.15954591392302175 -0.021564680286129803; 0.1380211849536038 -0.018760677774224358; 0.1197293549766333 -0.01629544977698041; 0.10328853295309956 -0.014031083833816675; 0.08814175261375166 -0.011925281327326504; 0.0741831071804629 -0.00998352056955962; 0.0615025132731363 -0.008227808790070984; 0.05022874958397557 -0.006678607779997613; 0.04044667787859321 -0.005346310977144799; 0.03216552254156492 -0.004229000187240539; 0.025318631784808227 -0.0033138345822543713; 0.019779704663306415 -0.0025801241756236728; 0.015385011669041774 -0.0020027906714227424; 0.011955074162233775 -0.0015554559027005995; 0.009312342505749294 -0.0012128001225934781; 0.00729359190847235 -0.0009521045950590054; 0.005757138583557462 -0.0007540555364465317; 0.004585729235003206 -0.0006029655703917461; 0.003686250484787095 -0.0004865903999901453;;; 8.879962181457264 16.25768119849419; 3.8518193521945356 6.048659334642554; 1.7571408705775369 2.123248257385433; 1.0035295957294728 1.0551578696092379; 0.42102696613044976 0.42777058510730115; -0.1637229909211836 -0.1628784469245933; -0.7272904721626839 -0.7271873539717243; -1.2190151101472273 -1.2190027211402406; -1.5913342506056658 -1.5913327778870936; -1.8078728902130927 -1.8078727163957313; -1.847443189655046 -1.8474431692404512; -1.7061727424214146 -1.7061727400318334; -1.397890203640236 -1.3978902033611822; -0.952772430188933 -0.9527724301563886; -0.41439065269709974 -0.4143906526933062; 0.1645545691355822 0.16455456913602862; 0.7273920432251325 0.7273920432251841; 1.2190273160857668 1.2190273160857708; 1.5913357017852403 1.5913357017852465; 1.8078730615061773 1.8078730615061733; 1.8474432097746036 1.8474432097745965; 1.7061727447765556 1.7061727447765775; 1.397890203915275 1.3978902039152643; 0.9527724302209906 0.9527724302209866; 0.41439065270082454 0.4143906527008293; -0.16455456913515132 -0.16455456913514632; -0.7273920432250811 -0.7273920432250839; -1.2190273160857652 -1.2190273160857643; -1.5913357017852485 -1.5913357017852419; -1.8078730615061724 -1.8078730615061716; -1.8474432097745948 -1.8474432097746016; -1.7061727447765582 -1.7061727447765556; -1.397890203915269 -1.397890203915274; -0.952772430221 -0.95277243022099; -0.4143906527008273 -0.414390652700824; 0.16455456913514943 0.16455456913515165; 0.7273920432250787 0.7273920432250776; 1.2190273160857648 1.2190273160857594; 1.5913357017852454 1.5913357017852516; 1.8078730615061698 1.8078730615061633; 1.847443209774612 1.847443209774609;;; -72.36859348577278 13.354031151712547; -19.772189653737797 0.7794711023513798; 12.77537287638011 -3.2807636892401355; 27.50226157779007 -4.613347252791218; 30.342062858651975 -4.486192682822264; 26.377823593557217 -3.610438082512333; 19.363428050180037 -2.466235503588318; 11.790696768369417 -1.3542398534530096; 5.107878481456748 -0.43516787619718855; -0.016821863335738207 0.23027255318076578; -3.4420353550782576 0.6455289847184291; -5.339642518474198 0.8493585818031575; -6.032171122659909 0.8948048217431395; -5.880853408116395 0.8354239186466863; -5.2173975154932615 0.7174673348445566; -4.309680882741469 0.5764881969343914; -3.351259609050628 0.43690834678090584; -2.465760698713077 0.31331779785732516; -1.7190875579301212 0.21257663212256417; -1.1343766021574078 0.13608371621812532; -0.7064634674623842 0.08182928800446204; -0.41408755993454205 0.046043536891261505; -0.2291372953859675 0.024389420464410394; -0.12294221874031563 0.012732260121069094; -0.07001713055734038 0.0075621542590185165; -0.049835241522496854 0.006159686532205555; -0.04722702486602903 0.006591377697434675; -0.05193186538870942 0.007607340592010292; -0.05771866977782525 0.008495829543881506; -0.06137210214607558 0.00893185362763571; -0.06173285650720027 0.008842005418494864; -0.058893136865871484 0.008296091168978864; -0.05358501322114305 0.007428109396254684; -0.046757913675826254 0.006384180323792926; -0.03931828675131287 0.0052925154687617215; -0.03199474664776368 0.004249729783738249; -0.025291313892744624 0.003318103895372654; -0.01949591440695396 0.0025292925730333315; -0.014718244959453383 0.0018910790655823628; -0.010938500583522068 0.0013948541756922672; -0.008055116214261318 0.001022420599400345], [1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5; 1.5 1.5;;; 1.0927050983124842 2.0927050983124844; 1.1763355756877418 2.176335575687742; 1.2427050983124843 2.2427050983124843; 1.285316954888546 2.285316954888546; 1.3 2.3; 1.285316954888546 2.285316954888546; 1.2427050983124843 2.2427050983124843; 1.176335575687742 2.176335575687742; 1.0927050983124842 2.0927050983124844; 1.0 2.0; 0.9072949016875157 1.9072949016875156; 0.823664424312258 1.823664424312258; 0.7572949016875158 1.757294901687516; 0.714683045111454 1.714683045111454; 0.7 1.7; 0.7146830451114539 1.7146830451114539; 0.7572949016875159 1.757294901687516; 0.823664424312258 1.823664424312258; 0.9072949016875157 1.9072949016875158; 0.9999999999999999 2.0; 1.0927050983124842 2.092705098312484; 1.176335575687742 2.1763355756877423; 1.2427050983124843 2.2427050983124843; 1.2853169548885461 2.285316954888546; 1.3 2.3; 1.2853169548885461 2.285316954888546; 1.2427050983124843 2.2427050983124843; 1.176335575687742 2.176335575687742; 1.0927050983124837 2.092705098312484; 1.0 2.0; 0.9072949016875159 1.9072949016875158; 0.8236644243122582 1.8236644243122582; 0.7572949016875159 1.757294901687516; 0.7146830451114539 1.7146830451114539; 0.7 1.7; 0.7146830451114539 1.7146830451114539; 0.7572949016875157 1.7572949016875157; 0.823664424312258 1.823664424312258; 0.9072949016875161 1.9072949016875163; 0.9999999999999999 1.9999999999999998; 1.0927050983124846 2.0927050983124844])

Compare distributed against centralized simulation to verify precision

res_fixed_c = run_scenario(:fixed, :centralised)
divergence = maximum(abs.(res_fixed.sim_data .- res_fixed_c.sim_data))
@printf("Max divergence between centralized and distributed simulation: %.3e\n", divergence)
Max divergence between centralized and distributed simulation: 3.553e-14

Animated Multi-panel Visualization

We animate each scenario so the bobbing motion is clearly visible. For each frame k the three panels show:

  1. y-z position plane with agent trails, target positions, and harmonic extensions
  2. Tilt angle θ for both agents vs time (up to current frame)
  3. Per-agent tracking error ||xi[1:2] - q*i|| vs time

The visual style matches the escort example: steelblue agents, purple harmonic extension squares (alpha 0.3), grey star targets.

animate_scenario5(res_fixed;
    frame_step = 2,
    filename   = "scenario5_fixed.gif",
    fps        = 15,
    label_suffix = "fixed targets")
nothing # hide

animate_scenario5(res_bob;
    frame_step = 2,
    filename   = "scenario5_bobbing.gif",
    fps        = 15,
    label_suffix = "bobbing targets")
nothing # hide
[ Info: Saved animation to /home/runner/work/CellularSheaves.jl/CellularSheaves.jl/docs/src/generated/layered/scenario5_fixed.gif
[ Info: Saved animation to /home/runner/work/CellularSheaves.jl/CellularSheaves.jl/docs/src/generated/layered/scenario5_bobbing.gif

Scenario 5 [fixed targets]

Scenario 5 [bobbing targets]

Clean up worker processes

rmprocs(workers_pids)
Task (done) @0x00007fe2a1aa11e0