Distributed Decentralized EKF for Satellite Mega-Constellations

Updated:

Tags: , , , , ,

Application of a novel distributed decentralized EKF framework to the navigation of very large-scale constellations of satellites, illustrated in particular for a shell of the Starlink mega-constellation.


A novel distributed decentralized EKF framework for very large-scale networks was proposed in [1]. In this example, this solution is applied to the satellite mega-constellation navigation problem. An illustrative mega-constellation of a single shell inspired in the first shell of the Starlink constellation to be deployed is considered. The constellation is a Walker \(53.0º:1584/72/17\). A snapshot of ground track and inter-satellite links (ISL) of the simulated constellation at 0 TDB seconds since J2000 is depicted below.

This example is organized into two parts: i) simulation of the constellations making use of an high-fidelity propagator; and ii) implementation of the EKF to obtain a distributed navigation solution in a decentralized framework.


High fidelity simulation

The realistic nonlinear numeric simulation is computed making use of the high fidelity open-source TU Delft’s Astrodynamic Toolbox (TUDAT). The documentation is available at https://docs.tudat.space/ and source code at https://github.com/tudat-team/tudat-bundle/. The orbit propagation of the satellites of the constellation accounts for several perturbations. The parameters that fully characterize the constellation, as well as the perturbations considered in the simulation, are detailed in [1].

The TUDAT application source-code can be found at

Examples/DistributedDecentralizedEKFStarlinkConstellation/tudatSimulation.

The .mat output of a simulation of roughly 1 full orbital period can be downloaded (419 MB) here.

The TUDAT application source-code consists of a C++ script that simulates the orbital dynamics of a constellation of satellites. It also establishes a UDP connection with a server running on a MATLAB instance to obtain thruster actuation feedback. For more details on how to setup the simulation and on the intricacies of the thruster actuation feedback, see the dedicated GitHub repository.


Implementation of distributed decentralized EKF

The main steps of the simulation are described below. Jump to Results to see the implementation results.

To open this example execute the following command in the MATLAB command window

open DDEKFConstellationCart

First, initialize variables and import simulation data

%% Define constellation
numberOfPlanes = 72;
numberOfSatellitesPerPlane = 22;
% Maximum communication distance normalized by the arc length between
% staellites on the same orbit
ISLRange = 750e3;
minInNeighbourhood = 1;
maxInNeighbourhood = 3;
semiMajorAxis = 6921000;

% Number of satellites
N = numberOfPlanes*numberOfSatellitesPerPlane;

% Init dimensions of the dynamics of each satellite
o_single = 3;
n_single = 6;
fprintf("Constellation defined.\n");

%% Simulation
Ts = 1; % Sampling time (s)
Tsim = 5730;
ItSim = Tsim/Ts+1; % One week
% Load true data
load('./data/output_orb_2023_01_15.mat','x');

% Reduce dimension of imported data arrays
parfor i = 1:N
    x{i,1} = x{i,1}(:,1:ItSim);
end
fprintf("Constellation simulation uploaded.\n");

%% Filter simulation - variable definition
%%%% DEK simulation variables
% Estimate vector time series
x_hat = cell(N,1);
for i = 1:N
    x_hat{i,1} = zeros(n_single,ItSim);
end
x_hat_pred = cell(N,1);
% Output vector
y = cell(N,1);
% In neighbourhood
Fim = cell(N,1);
FimNew = cell(N,1);
FimHist = cell(ItSim-1,1);
% Access to inertial measurements (skip = 1)
Fii = [];
% count = 0;
% for i = 0:numberOfPlanes-1
%     j = i*numberOfSatellitesPerPlane+1+count;
%     count = count + 1;
%     count = rem(count,numberOfSatellitesPerPlane);
%     Fii = [Fii j];
% end
Fii = (1:N)';

% Data structures of the dynamics to emulate communication
A = cell(N,1);
Q = cell(N,1);
C = cell(N,N);
o = zeros(N,1);
R = cell(N,N);
% Covariance between nodes in F_i^-
P_kl = cell(N,1);
P_kl_pred = cell(N,1);
% Matrices S_ii
S = cell(N,1);
% Gains K_i
K = cell(N,1);

%% Filter simulation - covariance initializaton
%%%% Covariance initialization
% Initial estimation error covariance
P0_single = blkdiag(10^2*eye(3),0.1^2*eye(3));
%%%% Estimate initialization
for i = 1:N
    x_hat{i,1}(:,1) = x{i,1}(1:n_single,1)+ mvnrnd(zeros(n_single,1),P0_single)';
end

%% Evolution of feedback variables
trace_log = zeros(N,ItSim-1);
P_pos_log = zeros(N,ItSim-1,3);

Second, compute the estimate for each discrete-time instant

%% Filter simulation - filter iterations
fprintf("Simulating DEKF.\n");
fprintf("Iteration: %08d/%08d",1,ItSim);

for t = 1:ItSim-1
    % Update counter
    fprintf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%08d/%08d",t,ItSim-1);
    % To distribute less memory across cores define temporary variables
    parfor i = 1:N
        x_t1(:,i) = x{i,1}(1:n_single,t+1);
    end
    %% DEKF - Steps for each time-step for each satellite
    FimHist{t,1} = Fim;
    %% Step 1 - Predict the estimate, topology and compute linearized dynamics
%     % Before UKF
%     parfor i = 1:N
%         % x_hat_i(t+1|t)
%         % A_ii(t)
%         % Q_ii(t)
%         [x_hat_pred{i,1},A{i,1},Q{i,1}] = predictionCart(x_hat{i,1}(:,t),zeros(3,1),Ts);
%     end
    parfor i = 1:N
        Q{i,1} = QCartUT(x_hat{i,1}(:,t),zeros(3,1),Ts);        
    end

    %% Step 2 - Predict the topology
    % Here we are using the real distance between satellites
    % We could, instead use an estimate, and if the satellite does not
    % communicate then we disregard it in function
    % LEOConstellationMeasurementGraphSynch
    aux = zeros(n_single,N);
    parfor i = 1:N
        aux(:,i) = x_t1(:,i);
    end
    parfor i = 1:N
        % Before limited number of neighbors
        % FimNew{i,1} = LEOConstellationMeasurementGraph(i,aux,ISLRange);
        % After limited number of neighbors
        FimNew{i,1} = LEOConstellationMeasurementGraph_limited(i,aux,ISLRange);
    end

    %% Step 3,4 - Communication
    %% Step 5 - Topology synch
    % Before limited number of neighbors
    FimNew = LEOConstellationMeasurementGraphSynch(FimNew);    
    % After limited number of neighbors
    %FimNew = LEOConstellationMeasurementGraphSynch_limited(FimNew,minInNeighbourhood,maxInNeighbourhood);

    %% Step 6 - Update filtered covariance
    % P(t|t)
    if t > 1
        parfor i = 1:N
            P_kl{i,1} = newCovarianceStorage(FimNew{i,1});
            for j = 1:size(P_kl{i,1},1)         
                p = P_kl{i,1}{j,1}(1);
                q = P_kl{i,1}{j,1}(2);
                Cp = zeros(n_single,length(FimNew{p,1})*n_single);
                Cq = zeros(n_single,length(FimNew{q,1})*n_single);
                Prs = zeros(length(FimNew{p,1})*n_single,length(FimNew{q,1})*n_single);
                lossPrs = 0;
                count_r = 0;
                % Sum over indices r ans s
                % Build matrix P_rs, C_p, and Cq for each (p,q)
                for r = Fim{p,1}'    
                    if p == q
                        count_s = count_r;
                        for s = Fim{q,1}(count_r+1:end)'
                            % Get the P_rs computation available to i
                            [aux,loss] = searchP(i,p,q,r,s,P_kl_pred,Fim);
                            Prs(count_r*n_single+1:(count_r+1)*n_single,count_s*n_single+1:(count_s+1)*n_single)=...
                                aux;  
                            if r ~= s
                                Prs(count_s*n_single+1:(count_s+1)*n_single,count_r*n_single+1:(count_r+1)*n_single)=...
                                    Prs(count_r*n_single+1:(count_r+1)*n_single,count_s*n_single+1:(count_s+1)*n_single)';
                            end
                            lossPrs = lossPrs + loss;
                            count_s = count_s + 1;    
                        end   
                    else
                        count_s = 0;
                        for s = Fim{q,1}'
                            % Get the P_rs computation available to i
                            [aux,loss] = searchP(i,p,q,r,s,P_kl_pred,Fim);
                            Prs(count_r*n_single+1:(count_r+1)*n_single,count_s*n_single+1:(count_s+1)*n_single)=...
                                aux;             
                            if count_r == 0
                                Cq(:,count_s*n_single+1:(count_s+1)*n_single) = eye(n_single)*(q==s)-K{q,1}*C{q,s};     
                            end
                            lossPrs = lossPrs + loss;
                            count_s = count_s + 1;    
                        end
                    end                 
                    Cp(:,count_r*n_single+1:(count_r+1)*n_single) = eye(n_single)*(p==r)-K{p,1}*C{p,r};     
                    count_r = count_r + 1;
                end

                if p == q
                    % If p == q then, P_rs should be positive definite
                    Prs = forcePositiveDefiniteness(Prs,0);
                    P_kl{i,1}{j,2} = K{p,1}*R{p,p}*K{p,1}'+Cp*Prs*Cp';
                    P_kl{i,1}{j,2} = (1/2)*(P_kl{i,1}{j,2}+P_kl{i,1}{j,2}');                   
                elseif sum(q == Fim{p,1}) % if p and q have a relative mesurment between them
                    P_kl{i,1}{j,2} = K{p,1}*R{p,q}*K{q,1}' + Cp*Prs*Cq';
                else
                    P_kl{i,1}{j,2} = Cp*Prs*Cq';
                end  
                % Update loss
                P_kl{i,1}{j,3} = lossPrs;
            end
        end

    else
        % Covariance initialization
        for i = 1:N
            P_kl{i,1} = newCovarianceStorage(FimNew{i,1});
            % Repeated combinations
            for j = 1:size(P_kl{i,1},1)
                p = P_kl{i,1}{j,1}(1);
                q = P_kl{i,1}{j,1}(2);
                if p == q
                    P_kl{i,1}{j,2} = P0_single;
                else
                    P_kl{i,1}{j,2} = zeros(n_single,n_single);
                end  
            end
        end
    end

    %% Step 7 - Update C and R
    % C_ij(t+1)
    % R_ij(t+1)
    parfor i = 1:N
        %[Caux,R{i,1},o(i)] = LEOConstellationOutputDynamics(i,FimNew{i,1},sum(Fii==i));
        [Caux,Raux,o(i)] = LEOConstellationOutputDynamics(i,FimNew,Fii);
        aux = 1;
        % Temporary variable to allow paralelization of cell C
        C_tmp = cell(1,N);
        for j = FimNew{i,1}'
            C_tmp{1,j} = Caux{aux,1};
            aux = aux + 1;
        end
        C(i,:) = C_tmp;
        % Temporary variable to allow paralelization of cell R
        aux = 1;
        for j = FimNew{i,1}'
            C_tmp{1,j} = Raux{aux,1};
            aux = aux + 1;
        end
        R(i,:) = C_tmp;
    end

%     %% Step 8 - Predict covariance (before UKF)
%     % P_i(pq)(t+1|t)
%     % S_ii(t+1)
%     % Ki(t+1)
%     P_kl_pred = P_kl;
%     parfor i = 1:N
%         for j = 1:size(P_kl{i,1},1)
%             p = P_kl_pred{i,1}{j,1}(1);
%             q = P_kl_pred{i,1}{j,1}(2);
%             % Local covariance prediction
%             P_kl_pred{i,1}{j,2} = A{p,1}*P_kl{i,1}{j,2}*A{q,1}';
%             if p == q
%                 % if p == q P_i(pq)(t+1|t) must be positive definite
%                 P_kl_pred{i,1}{j,2} = P_kl_pred{i,1}{j,2} + Q{p,1};
%                 P_kl_pred{i,1}{j,2} = (1/2)*(P_kl_pred{i,1}{j,2}+P_kl_pred{i,1}{j,2}');
%             end
%         end
%         
%         % Compute innovation covarinace
%         Pi = zeros(length(FimNew{i,1})*n_single);
%         Ci = zeros(o_single*o(i),length(FimNew{i,1})*n_single);
%         idxi = 0;
%         countk = 0;
%         for k = FimNew{i,1}'
%             if k == i
%                 idxi = countk;
%             end
%             Ci(:,countk*n_single+1:(countk+1)*n_single) = C{i,k};
%             countl = countk;
%             for l = FimNew{i,1}(countk+1:end)'
%                 Pi(countk*n_single+1:(countk+1)*n_single,countl*n_single+1:(countl+1)*n_single)=...
%                     getP(P_kl_pred{i,1},k,l);
%                 if l ~= k
%                     Pi(countl*n_single+1:(countl+1)*n_single,countk*n_single+1:(countk+1)*n_single)=...
%                         Pi(countk*n_single+1:(countk+1)*n_single,countl*n_single+1:(countl+1)*n_single)';
%                 end
%                 countl = countl + 1;
%             end
%             countk = countk + 1;         
%         end
%         % Pi must be positive definite
%         Pi = forcePositiveDefiniteness(Pi,0);
%         % Local computation of innovation covariance matrix
%         S{i,1} = R{i,i}+Ci*Pi*Ci';  
%         S{i,1} = (1/2)*(S{i,1}+S{i,1}');
%         aux = Pi(idxi*n_single+1:(idxi+1)*n_single,:)*Ci';
%          
%         % Compute the gain locally
%         K{i,1} = aux/S{i,1};
%         
%     end

    %% Step 8 - Predict covariance (UKF)
    % P_i(pq)(t+1|t)
    % S_ii(t+1)
    % Ki(t+1)
    P_kl_pred = P_kl;
    parfor i = 1:N
        % Define local concatenated covariance matrices and local concatenated estimates
        % Take avatage of the loop to compute the concatenation of C
        x_hat_i = zeros(length(FimNew{i,1})*n_single,1);
        Pi = zeros(length(FimNew{i,1})*n_single);
        Ci = zeros(o_single*o(i),length(FimNew{i,1})*n_single);
        idxi = 0;
        countk = 0;
        for k = FimNew{i,1}'
            if k == i
                idxi = countk;
            end
            x_hat_i(countk*n_single+1:(countk+1)*n_single,1) = x_hat{i,1}(:,t);
            Ci(:,countk*n_single+1:(countk+1)*n_single) = C{i,k};
            countl = countk;
            for l = FimNew{i,1}(countk+1:end)'
                Pi(countk*n_single+1:(countk+1)*n_single,countl*n_single+1:(countl+1)*n_single)=...
                    getP(P_kl{i,1},k,l);
                if l ~= k
                    Pi(countl*n_single+1:(countl+1)*n_single,countk*n_single+1:(countk+1)*n_single)=...
                        Pi(countk*n_single+1:(countk+1)*n_single,countl*n_single+1:(countl+1)*n_single)';
                end
                countl = countl + 1;
            end
            countk = countk + 1;         
        end    
        % Pi must be positive definite
        Pi = forcePositiveDefiniteness(Pi,0);
        % Compute sigma points and weights
        % Wan, E.A. and Van Der Merwe, R., 2000, October. The unscented Kalman
        % filter for nonlinear estimation. In Proceedings of the IEEE 2000 Adaptive
        % Systems for Signal Processing, Communications, and Control Symposium
        % (Cat. No. 00EX373) (pp. 153-158). Ieee.
        % Julier, S.J. and Uhlmann, J.K., 2004. Unscented filtering and
        % nonlinear estimation. Proceedings of the IEEE, 92(3), pp.401-422.
        % Unscented transformation parameters
        L = length(x_hat_i);
        UTpar_alpha = 1e-3;
        UTpar_k = 0;
        UTpar_beta = 2;
        % Sigma points
        UTpar_lambda = (UTpar_alpha^2)*(L+UTpar_k)-L;
        P_sqrt = chol((L+UTpar_lambda)*Pi)';    
        Ksi = [x_hat_i x_hat_i+P_sqrt x_hat_i-P_sqrt];
        % Weights
        W_m = [UTpar_lambda/(L+UTpar_lambda); ones(2*L,1)*1/(2*(L+UTpar_lambda))];
        W_c = W_m;
        W_c(1) = W_m(1) + 1-UTpar_alpha^2+UTpar_beta;
        % Propagate sigma points
        for l = 1:2*L+1
            for s = 1:length(FimNew{i,1})
                Ksi((s-1)*n_single+1:s*n_single,l) = predictionCartUT(Ksi((s-1)*n_single+1:s*n_single,l),zeros(3,1),Ts);
            end
        end
        % Compute mean
        x_hat_i = zeros(L,1);
        for l = 1:2*L+1
            x_hat_i = x_hat_i + W_m(l)*Ksi(:,l);
        end
        % Compute covariance
        Ksi = Ksi - x_hat_i;
        Pi = zeros(L,L);
        for l = 1:2*L+1
            Pi = Pi + W_c(l)*(Ksi(:,l)*Ksi(:,l)');
        end      
        % Fill predicted estimate of the satellite
        x_hat_pred{i,1} = x_hat_i(idxi*n_single+1:(idxi+1)*n_single,1);
        % Add additive process noise and fill data structure with covariances
        countk = 0;
        for k = FimNew{i,1}'  
            Pi(countk*n_single+1:(countk+1)*n_single,countk*n_single+1:(countk+1)*n_single) = ...
                Pi(countk*n_single+1:(countk+1)*n_single,countk*n_single+1:(countk+1)*n_single) + Q{k,1};
            countl = countk;
            for l = FimNew{i,1}(countk+1:end)'
                % Find the position on the data structure
                for s = 1:size(P_kl_pred{i,1})
                    if(sum(P_kl_pred{i,1}{s,1} == [k;l]) == 2)
                        P_kl_pred{i,1}{s,2} = ...
                            Pi(countk*n_single+1:(countk+1)*n_single,countl*n_single+1:(countl+1)*n_single);
                        break;
                    end
                end              
                countl = countl + 1;
            end
            countk = countk + 1;         
        end          
        % Local computation of innovation covariance matrix
        S{i,1} = R{i,i}+Ci*Pi*Ci';  
        S{i,1} = (1/2)*(S{i,1}+S{i,1}');
        aux = Pi(idxi*n_single+1:(idxi+1)*n_single,:)*Ci';
        % Compute the gain locally
        K{i,1} = aux/S{i,1};      
    end

    %% Step 7 - Take the measurement
    % Correlated measurement noise generated locally
    for i = 1:N
        % Add error to each measurement relative to satellites in Fim of i
        y{i,1} = zeros(o(i)*o_single,1);
        % Counter for the number of the local output
        aux = 1;         
        for j = 1:size(FimNew{i,1},1)
            % temporary variable to allow paralelization
            y_tmp = zeros(o(i)*o_single,1);
            % Number of satellite
            satj = FimNew{i,1}(j);
            if satj == i
                continue;
            end
            if satj > i
                y{i,1}((aux-1)*o_single+1:aux*o_single,1) = mvnrnd(zeros(o_single,1),R{i,i}((aux-1)*o_single+1:aux*o_single,(aux-1)*o_single+1:aux*o_single))';
            else
                % Find the number of the local output in j
                auxj = 1;
                for l = 1:size(FimNew{satj,1},1)
                    if FimNew{satj,1}(l) == i
                        break;
                    elseif FimNew{satj,1}(l) ~= satj
                        auxj = auxj + 1;
                    end             
                end
                y{i,1}((aux-1)*o_single+1:aux*o_single,1) = -y{satj,1}((auxj-1)*o_single+1:auxj*o_single);
            end
            aux = aux + 1;           
        end
        % Add inertial error
        if o(i)==aux % if has inertial measurment
            y{i,1}((aux-1)*o_single+1:aux*o_single,1) = mvnrnd(zeros(o_single,1),R{i,i}((aux-1)*o_single+1:aux*o_single,(aux-1)*o_single+1:aux*o_single))';
        end
    end  
    % Take measurments
    parfor i = 1:N
        for j = FimNew{i,1}'
            y{i,1} = y{i,1} + C{i,j}*x_t1(:,j);
        end
    end

    %% Step 8 - Update the estimate
    parfor i = 1:N
        % Compute predicted output (it is linear in cartesian coodinates)
        y_hat = zeros(size(C{i,i},1),1);
        for j = FimNew{i,1}'
            y_hat = y_hat + C{i,j}*x_hat_pred{j,1};
        end
        % Update step
        x_hat{i,1}(:,t+1) = x_hat_pred{i,1}+K{i,1}*(y{i,1}-y_hat);
        Fim{i,1} = FimNew{i,1};
    end

    %% Log estimated trace
    parfor i = 1:N
        aux = getP(P_kl{i,1},i,i);
        trace_log(i,t) = trace(aux);
        for m = 1:3
            P_pos_log(i,t,m) = aux(m,m);
        end
    end
end        

The auxiliary functions that are employed in the script above are defined in the main simulation script, made available in the DECENTER toolbox.


Results

The evolution of the position estimation error with the use of relative measurements, making use of the novel algorithm proposed in [1], for satellite 1, is shown below.

It is possible to note that, near the poles, there are more relative measurement links. Thus, near the poles, the estimation performance increases. This effect is also visible in the evolution of the trace of the estimation error covariance matrix and number of satellites in ISL range, for satellite 1.

References

[1] Not published yet