Code covered by the BSD License  

Highlights from
Kepler State Transition Matrix (MEX)

image thumbnail
from Kepler State Transition Matrix (MEX) by Rody Oldenhuis
Compute Kepler state transition matrix for an arbitrary number of time steps. M and C++ version.

progress_orbitM(dts, varargin)
% PROGRESS_ORBIT   progress orbit/trajectory through Kepler state 
%                  transition matrix 
%
% USAGE: 
%   new_state = progress_orbit(time_step, [x y z xdot ydot zdot], GM_central)
%   [x y z xdot ydot zdot] = ...
%              progress_orbit(time_step, x, y, z, xdot, ydot, zdot, GM_central, 'days' or 'seconds')
%
% INPUT:
% ======================
%     x,y,z,     - Initial Cartesian statevector; 3 position coordinates, and     
% xdot,ydot,zdot   3 velocity components. 
%
%      time_step - the time step(s) to take. May be scalar or vector. In
%                  case of a vector, the output arument(s) will have a
%                  number of rows equal to the number of elements in
%                  [time_step].
%
%     GM_central - std. grav. parameter of the central body [km2/s2]
%
%          units - either 'days' (default) or 'seconds'; the units in
%                  which the time step is expressed.
%
% The Kepler State transition matrix provides a way to progress any given
% state vector for a given time step, without having to perform a lengthy
% triple-coordinate conversion (from Cartesian coordinates to Kepler
% elements, progressing, and back). This procedure has been generalized and
% greatly optimized for computational efficiency by Shepperd (1985)[1], who
% used continued fractions to calculate the corresponding solution to
% Kepler's equation. The resulting algorithm is an order of magnitude
% faster that mentioned triple coordinate conversion, which makes it very
% well suited as a basis for a number of other algorithms that require
% frequent progressing of state vectors. 
%
% This procedure implements a robust version of this algorithm. A small
% correction to the original version was made: instead of Newton's method
% to update the Kepler-loop, a Halley-step is used. This change makes the
% algorithm much more robust while increasing the rate of convergence even
% further. If the algorithm fails however, the slower triple-coordinate 
% conversion is automatically started. 
%
%
% References: 
% [1] S.W. Shepperd, "Universal Keplerian State Transition Matrix".
% Celestial Mechanics 35(1985) pp. 129--144, DOI: 0008-8714/85.15
%
% See also kep2cart, cart2kep.

function varargout = progress_orbitM(dts, varargin)
% Author:
% Name       : Rody P.S. Oldenhuis
% E-mail     : oldenhuis@dds.nl / oldenhuis@gmail.com
% Affiliation: Delft University of Technology
%
% please report any bugs or suggestions to oldnhuis@dds.nl.    
    
    % standard error
    error(nargchk(3, 9, nargin));%#ok
            
    % parse input
    narg = nargin;
    time_unit = 'days';
    if (narg >= 8)       
        % otherwise
        r1 = [varargin{1:3}];
        v1 = [varargin{4:6}];   
        muC = varargin{7};  
    end
    if (narg == 9), time_unit = varargin{8}; end
    if (narg <= 4)    
        % otherwise
        r1 = varargin{1}(1:3);
        v1 = varargin{1}(4:6);
        muC = varargin{2};        
    end
    if (narg == 4), time_unit = varargin{3}; end    
    
    % force everyting to be row-matrices
    r1 = r1(:).';   v1 = v1(:).';
    
    % initialize output
    zero                        = zeros(numel(dts), 1);
    exitflag                    = zero;
    final_states                = NaN(numel(dts), 6);
    output.Kepler_iterations    = zero;
    output.cont_frac_iterations = zero;
    output.time_error           = zero;
    
    % progress only one orbit (to command window, debugging puruposes only)
    if numel(r1) ~= 3
        error('progress_orbit:only_one_body_allowed',...
              'I can only progress one orbit at a time. Please use ARRAYFUN().');
    end
      
    % times are given in days, unless specified otherwise
    if strcmpi(time_unit, 'days')
        dts = dts * 86400;   
    elseif ~strcmpi(time_unit, 'seconds')
        error('progress_orbit:invalid_timeunit',...
            'Only ''seconds'' and ''days'' are valid timeunits.');
    end
    
    % intitialize
    nu0  = r1*v1.';
    r1m  = sqrt(r1*r1.');
    beta = 2*muC/r1m - v1*v1.';
    
    % period effects
    DeltaU = zero;
    if (beta > 0)
        P = 2*pi*muC*beta^(-3/2);
        n = floor((dts + P/2 - 2*nu0/beta)/P);
        DeltaU = 2*pi*n*beta^(-5/2);
    end
    
    % loop through all requested time steps
    for i = 1:numel(dts)
        
        % extract current time step
        dt = dts(i);
        
        % quick exit for trivial case
        if (dt == 0)
            final_states(i, :) = [r1,v1];
            exitflag(i) = 1;
            output.Kepler_iterations(i)    = 0;
            output.cont_frac_iterations(i) = 0;
            output.time_error(i)           = 0;
            continue;
        end 
        
        % loop until convergence of the time step
        u = 0; t = 0; qisbad = false; iter = 0; cont_frac = 0; deltaT = t-dt;
        while abs(deltaT) > 1 % one second accuracy seems fine
            
            % increase iterations
            iter = iter + 1;
            
            % compute q
            % NOTE: [q] may not exceed 1/2. In principle, this will never 
            % occur, but the iterative nature of the procedure can bring 
            % it above 1/2 for some iterations.
            bu = beta*u*u;
            q  = bu/(1 + bu);
            
            % escape clause;
            % The value for [q] will almost always stabilize to a value less 
            % than 1/2 after a few iterations, but NOT always. In those
            % cases, just use repeated coordinate transformations
            if (iter > 25) || (q >= 1), qisbad = true; break; end
            
            % evaluate continued fraction (when q < 1, always converges)
            A =  1;   B = 1;   G = 1;   n = 0;
            k = -9;   d = 15;  l = 3;   Gprev = inf;
            while abs(G-Gprev) > 1e-14
                k = -k;                 l = l + 2;
                d = d + 4*l;            n = n + (1+k)*l;
                A = d/(d - n*A*q);      B = (A-1)*B;
                Gprev = G;              G = G + B;
                cont_frac = cont_frac + 1;
            end % continued fraction evaluation
            
            % continue kepler loop
            U0w2   = 1 - 2*q;
            U1w2   = 2*(1-q)*u;
            U      = 16/15*U1w2^5*G + DeltaU(i);
            U0     = 2*U0w2^2-1;
            U1     = 2*U0w2*U1w2;
            U2     = 2*U1w2^2;
            U3     = beta*U + U1*U2/3;
            r      = r1m*U0 + nu0*U1 + muC*U2;
            t      = r1m*U1 + nu0*U2 + muC*U3;
            deltaT = t - dt;
            % Newton-Raphson method works most of the time, but is 
            % not too stable; the method fails far too often for my
            % liking...
            % u = u - deltaT/4/(1-q)/r; 
            % Halley's method is much better in that respect. Working 
            % out all substitutions and collecting terms gives the 
            % following simplification:
            u = u - deltaT/((1-q)*(4*r + deltaT*beta*u));

        end % time loop
                
        % do it the slow way if state transition matrix fails for some q
        if qisbad
            % repeated coordinate transformations
            [aa, ee, ii, OO, oo, M] = cart2kep([r1, v1], muC, 'M');
            M = M + sqrt(muC/abs(aa)^3)*dt;
            [x, y, z, xd, yd, zd] = kep2cart(aa, ee, ii, OO, oo, M, muC, 'M');
            final_states(i, :) = [x, y, z, xd, yd, zd];
            % this means failure
            exitflag(i) = -1;            
            
        % use state transition matrix if all went well
        else
            % Kepler solution
            f = 1 - muC/r1m*U2;     F = -muC*U1/r/r1m;
            g = r1m*U1 + nu0*U2;    G = 1 - muC/r*U2;
            % create new position and velocity matrices
            final_states(i, :) = [r1*f+v1*g, r1*F+v1*G]; 
            % all went fine
            exitflag(i) = 1;
        end % "q is bad" clause
        
        % process output
        output.Kepler_iterations(i)    = iter;
        output.cont_frac_iterations(i) = cont_frac;
        output.time_error(i)           = abs(t-dt);
        
    end % loop through [dt]
    
    % generate properly formatted output
    narg = nargout;
    if (narg <= 3)
        varargout{1} = final_states;     % and output array
        varargout{2} = exitflag;         % exitflag
        varargout{3} = output;           % output
    elseif (narg > 3)
        varargout{1} = final_states(:, 1);  varargout{4} = final_states(:, 4);
        varargout{2} = final_states(:, 2);  varargout{5} = final_states(:, 5);
        varargout{3} = final_states(:, 3);  varargout{6} = final_states(:, 6);
        varargout{7} = exitflag;         % exitflag
        varargout{8} = output;           % output
    end % process output
    
end % progress orbit

Contact us