Skip to content

Latest commit

 

History

History
2518 lines (2194 loc) · 114 KB

kalman-folding-09-008-in-C.org

File metadata and controls

2518 lines (2194 loc) · 114 KB

Kalman Folding 9: in C (WORKING DRAFT)

1 Abstract

In this literate program, we show one way to implement the basic Kalman fold in C. The background for Kalman folding appears in Kalman Folding, Part 1,klf1 where we highlight the the unique advantages of functional folding for deploying test-hardened code verbatim in harsh, mission-critical environments. In that and in other papers in this series, the emphasis is on mathematical clarity and conciseness. Here, the emphasis is on embedded systems and on the primary language for implementing such systems, C.

2 This Document is a Literate Program

2.1 Literate Code

A /literate program/litp is a single text document that contains both

  • \LaTeX{} typesetting instructions and
  • source code and scripts for an application.

A literate program can be edited in any text editor and controlled with text-based versioning tools like git, but requires other tools to extract the typeset document and to extract the source code. My chosen tools are emacs and org-babel.babl In addition to extraction, called tangling, these tools allow interactive evaluation of code in the original text document. A reader may reproduce the results here by interacting with the source document.rprs To follow along interactively, you will need emacs and the text source for this document. The text source for this document is found here /TODO/. A distribution of emacs for the Mac with adequate org-babel support is maintained by Vincent Goulet at the University of Laval.lavl Emacs for Linux and Windows is easy to find, but I do not discuss it further. Spacemacs/spcm offers high-fidelity /vim emulation with the tools needed for literate programming.

2.2 Get C to Work

First, make sure the following worksobc1 in org-babel and org-babel tangle. If it does work, you have a correctly installed C compiler.

int a=7;
int b=6;
printf("%d\n", a*b);
// ~~> produces
42

2.3 Recurrence for the Mean

2.3.1 Business Code

C code is usually much longer than the corresponding Wolfram code (or Lisp, or Haskell, or OCaml, etc.). The extra length, overall, is due mostly to manual memory management. We also don’t have pattern matching for unpacking inputs and we don’t have literal list / array notation for expressing some inputs and for packing outputs.

The goal of our C code is to make the final result as clean and as close to the original design in Wolfram as possible. For instance, the original source for Recurrence for the Mean in paper 1klf1 is

cume[{x_, n_}, z_] := (* pattern matching for unpacking inputs *)
  With[{K = 1/(n + 1)},
   {x + K (z - x), n + 1}]; (* literal notation for packing outputs *)
Fold[cume, {0, 0}, {55, 89, 144}] (* literal notation for some inputs *)
~~> {96, 3}

The equivalent “business” code in C is still longer, but can see that the code for computing the gain and for the resulting Accumulation is virtually identical (we must use an explicit multiplication operator *, whereas a space suffices in Wolfram for scalar multiplication). At least the memory management is hidden in the types Accumulation and Observation, articulated further below.

{   Accumulator cume = ^(Accumulation a, Observation z)
        {   /* unpack inputs */
            T x = a.elements[0];
            T n = a.elements[1];
            /* compute gain */
            T K = 1.0 / (1.0 + n);
            /* busines logic, and packing results */
            Accumulation r;
            r.elements[0] = x + K * (z - x);
            r.elements[1] = 1.0 + n;

            return r;   };

    Accumulation x0 = zeroAccumulation ();

    Observation tmp[3] = {55, 89, 144};
    Observations zs = createObservations(3, tmp);

    Accumulation result = fold (cume, x0, zs);

    printAccumulation (result);
    return 0;   }

2.3.2 Primary Numerical Type

We abstract out the primary numerical type into the traditional T to make it easier to change.

typedef double T;

2.3.3 Accumulation Type

The code for the Accumulation type is a bit elaborate, but the extra abstractions will serve us well when we get to the Kalman filter.

The Accumulation structure presumes that all values are copied around on every use, and that’s safe, and also means that we don’t need alloc & free routines for this type. These accumulation types are usually small, so the time needed to copy them around may be acceptable. More sophisticated memory management for them entails more code, so we opt for keeping the code small at the cost of some copying that could be optimized away.

Also, in the interest of saving space, specifically, staircases of closing curly braces on lines by themselves, we adopt the /Pico/pico style for bracing.

const size_t Accumulation_size = 3;
typedef struct s_Accumulation
{   T elements[Accumulation_size];   } Accumulation, * pAccumulation;

Accumulation zeroAccumulation (void)
{   Accumulation r;
    memset ((void *)r.elements, 0, Accumulation_size * sizeof (T));
    return r;   }

void printAccumulation (Accumulation a)
{   printf ("{");
    for (size_t i = 0; i < Accumulation_size; ++i)
    {   printf ("%lf", a.elements[i]);
        if (i < Accumulation_size - 1)
        {   printf (", ");   }   }
    printf ("}\n");   }

We have harmlessly used $3$ for the accumulation size because we want to reuse this code later. We could make it variable at the cost of more unilluminating code.

2.3.4 Observation Types

Because we don’t statically know the number of observations, we must use dynamic memory allocation. In an embedded application, we would use arena memory (fixed-length circular buffer pools of fixed-length structs) or stack allocation (calloc). Here, for brevity and because this is a testing deployment, we use heap memory (stdlib’s malloc and free). These are unacceptable in embedded applications because of fragmentation and unbounded execution times.

When we get to lazy streams, we won’t need these at all. They’re only for arrays of observations all in memory at one time.

The primary helper type is a bounded array of Observations type that includes the length and a handy iterator-like current index. Most of the code for this type concerns explicit memory management for this helper type.

We also include an Observation type, for asbstraction hygiene.

typedef T Observation, * pObservation;
typedef struct s_BoundedArray_Observations
{   int count;
    int current;
    pObservation observations;   } Observations;

/*private*/pObservation allocObservationArray (int count_)
{   /* Don't use malloc & free in embedded apps. Use arena or stack memory. */
    pObservation po = (pObservation) malloc (count_ * sizeof (Observation));
    if (NULL == po)
    {   printf ("Failed to alloc %d observations\n", count_);
        exit (-1);   }
    return po;   }

Observations createObservations (int count_, pObservation pObservations)
{   pObservation po = allocObservationArray (count_);
    memcpy ((void *)po, (void *)pObservations, sizeof (Observation) * count_);
    Observations result;
    result.count   = count_;
    result.current = 0;
    result.observations    = po;
    return result;   }

void freeObservations (Observations o)
{   /* Don't use malloc & free in embedded apps. Use arena or stack memory. */
    free ((void *)o.observations);   }

2.3.5 Accumulator Type

Our last type definition is for the Accumulator function. With the blocks extension, the Accumulator type, defined with the hat syntax ^, behaves just like a function pointer, which would be defined with the ordinary pointer syntax, * without blocks.

typedef Accumulation (^Accumulator) (Accumulation a, Observation b);

2.3.6 The Fold Over Observations

The final piece is the fold operator. This particular one knows details of the Observations type, so is specific to it. We have another fold over lazy streams, articulated below, just as with Wolfram.

Accumulation fold (Accumulator f, Accumulation x0, Observations zs)
{   for (zs.current = 0; zs.current < zs.count; ++zs.current)
    {   x0 = f (x0, zs.observations[zs.current]);   }
    return x0;   }

2.3.7 Pulling it All Together

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Block.h>
<<c-numerical-type>>
<<c-accumulation-type>>
<<c-observation-types>>
<<c-accumulator-type>>
<<c-fold-over-observations>>
int main (int argc, char ** argv)
<<c-business-logic>>

Tangle this code out to a C file by executing `org-babel-tangle’ while visiting this literate source code in emacs.

Compile and run the code as follows:

gcc -Wall -Werror recurrenceForTheMean.c -o recurrenceForTheMean
./recurrenceForTheMean
{96.0000003.00.000000}

\noindent producing results all-but-identical to those from the Wolfram language.

2.4 FoldList and Recurrence for the Variance

The original paper introduced Wolfram’s FoldList along with the recurrence for the variance. We do likewise here, implementing our own foldList in C.

2.4.1 Bounded Array for Accumulations

FoldList produces a list of accumulations, one for the initial accumulation and another for each observation. With lists of observations all in memory, we could calculate the length of the output and preallocate a list of accumlations of the correct size, but we are not able to do that with lazy streams of observations or asynchronous observables of observations. We opt, then, for on-demand, dynamic memory management for the output accumulations. “On-demand,” here, means growing the output array as new accumulations arrive. We use the common trick of doubling the capacity of the output array every time the capacity is exceeded. This trick is a reasonable compromise of space and time efficiency.

We emulate the bounded-array interface created for observations, and add three more functions to the usual create, free, and print.

lastAccumulations
returns the last accumulation in a bounded array; needed for foldList
appendAccumulations
appends a new accumulation to a bounded array of accumulations, growing the capacity if needed
foldList
takes an accumulator $f$, an initial accumulation $a_0$, a bounded array of observations $zs$, and produces a bounded array of accumulations.
typedef struct s_BoundedArray_Accumulations
{   int count;
    int max;
    pAccumulation accumulations ;   } Accumulations;

Accumulation lastAccumulations (Accumulations as)
{   if (0 == as.count)
    {   printf ("Attempt to pull non-existent element\n");
        exit (-4);   }
    return as.accumulations[as.count - 1];   }

Accumulations appendAccumulations (Accumulations as, Accumulation a)
{   Accumulations result = as;
    if (result.count + 1 > result.max)
    {   /* Double the storage. */
        int new_max = 2 * result.max;
        /* Don't use malloc & free in embdded apps. Use arena or stack memory. */
        pAccumulation new = (pAccumulation)
          malloc (sizeof (Accumulation) * new_max);
        if (NULL == new)
        {   printf ("Failed to alloc %d Accumulations\n", new_max);
            exit (-2);   }
        if (result.count != result.max)
        {   printf ("Internal bugcheck\n");
            exit (-3);   }
        memset ((void *)new, 0, new_max * sizeof (Accumulation));
        memcpy ((void *)new, (void *)result.accumulations,
          (sizeof (Accumulation) * result.max));
        free ((void *) result.accumulations);
        result.accumulations = new;
        result.max = new_max;   }
    result.accumulations[result.count] = a;
    ++ result.count;
    return result;   }

Accumulations createAccumulations (void)
{   Accumulations result;
    const int init_size = 4;
    result.max = init_size;
    result.count = 0;
    result.accumulations = (pAccumulation)
      malloc (sizeof (Accumulation) * init_size);
    memset ((void *)result.accumulations, 0,
      sizeof (Accumulation) * init_size);
    return result;   }

void freeAccumulations (Accumulations as)
{   memset ((void *) as.accumulations, 0,
      (sizeof (Accumulation) * as.count));
    free ((void *) as.accumulations);   }

void printAccumulations (Accumulations as)
{   for (int j = 0; j < as.count; ++j )
    {   printAccumulation (as.accumulations[j]);   }   }

Accumulations foldList (Accumulator f, Accumulation a0, Observations zs)
{   Accumulations result = createAccumulations ();
    result = appendAccumulations (result, a0);
    for (zs.current = 0; zs.current < zs.count; ++zs.current)
    {   result = appendAccumulations (
          result,
          f(lastAccumulations(result),
          zs.observations[zs.current]));   }
        return result;   }

2.4.2 Pulling Together Recurrence for the Variance

  #include <stdio.h>
  #include <string.h>
  #include <stdlib.h>
  #include <Block.h>
  <<c-numerical-type>>
  <<c-accumulation-type>>
  <<c-observation-types>>
  <<c-accumulator-type>>
  <<c-fold-over-observations>>
  <<c-bounded-array-for-accumulations>>
  int main (int argc, char ** argv)
{   Observation tmp[3] = {55, 89, 144};
    Observations zs = createObservations(3, tmp);
    Accumulation x0 = zeroAccumulation ();
    Accumulator cume = ^(Accumulation a, Observation z)
        {   T var = a.elements[0];
            T x   = a.elements[1];
            T n   = a.elements[2];

            T K = 1.0 / (1.0 + n);
            T x2 = x + K * (z - x);
            T ssr2 = (n - 1.0) * var + K * n * (z - x) * (z - x);

            Accumulation r;
            r.elements[0] = ssr2 / (n > 1.0 ? n : 1.0);
            r.elements[1] = x2;
            r.elements[2] = n + 1.0;
            return r;   };

    Accumulations results = foldList (cume, x0, zs);
    printAccumulations (results);

    freeAccumulations (results);
    freeObservations (zs);
    return 0;   }
gcc -Wall -Werror recurrenceForTheVariance.c -o recurrenceForTheVariance
./recurrenceForTheVariance
{0.0000000.00.000000}
{0.00000055.01.000000}
{578.00000072.02.000000}
{2017.00000096.03.000000}

This result is semantically identical to that produced by the following Wolfram code:

cume[{var_, x_, n_}, z_] :=
  With[{K = 1/(n + 1)},
   With[{x2 = x + K (z - x),
     ssr2 = (n - 1) var + K n (z - x)^2},
    {ssr2/Max[1, n], x2, n + 1}]];
Fold[cume, {0, 0, 0}, zs]
~~> {2017, 96, 3}

3 Basic Kalman Folding

3.1 Avoiding the Inverse

In the first paper in this series, we wrote one version of the static Kalman filter, when there are no system dynamics,klf2 as follows.

\noindent where

\noindent and all quantities are matrices, and

  • \(\mathbold{Z}\) = \({b}×{b}\) covariance of observation noise
  • \(\mathbold{x}\) = \({n}×{1}\) model states
  • \(\mathbold{P}\) = \({n}×{n}\) theoretical covariance of \(\mathbold{x}\)
  • \(\mathbold{A}\) = \({b}×{n}\) \emph{observation partials}
  • \(\mathbold{z}\) = \({b}×{1}\) multidimensional, decorrelated observations
  • \(\mathbold{K}\) = \({n}×{b}\) \emph{Kalman gain}
  • \(\mathbold{D}\) = \({b}×{b}\) the Kalman denominator

Adding physical dimensions, if the physical and matrix dimensions of \(\mathbold{x}\) are \(\left[\left[\mathbold{x}\right]\right] \stackrel{\text{\tiny def}}{=} (\mathcal{X}, n×{1})\) and of \(\mathbold{z}\) are \(\left[\left[\mathbold{z}\right]\right] \stackrel{\text{\tiny def}}{=} (\mathcal{Z}, b×{1})\), then

While an expression with an explicit inverse is mathematically acceptable, inverses are numerically risky, expensive in storage, slow to compute, and usually not necessary.ditm LAPACK can solve linear systems very efficiently, much more efficiently than it can invert matrices. Therefore, we rewrite the basic filter to avoid computing $\mathbold{D}-1$.

If \(\textrm{DiRes}=\mathbold{D}-1\,(\mathbold{z}-\mathbold{A}\,\mathbold{x})\) is the solution of the linear equation \(\mathbold{D}×\textrm{\textrm{DiRes}}=(\mathbold{z}-\mathbold{A}\,\mathbold{x})\), and if $\mathbold{K}=\mathbold{P}\,\mathbold{A}^\intercal\,\mathbold{D}-1$, then $\mathbold{K}\,(\mathbold{z}-\mathbold{A}\,\mathbold{x})=\mathbold{P}\,\mathbold{A}^\intercal\,\textrm{DiRes}$ and the Kalman state-update is $\mathbold{x}←\mathbold{x}+\mathbold{P}\,\mathbold{A}^\intercal\,\textrm{DiRes}$. Likewise, if $\textrm{DiAP}=\mathbold{D}-1\,\mathbold{A}\,\mathbold{P}$ is the solution of the linear equation $\mathbold{D}×\textrm{DiAP}=\mathbold{A}\,\mathbold{P}$, then $\mathbold{K}\,\mathbold{A}\,\mathbold{P}=\mathbold{P}\,\mathbold{A}^\intercal\,\textrm{DiAP}$ and the Kalman covariance update is $\mathbold{P}←\mathbold{P}-\mathbold{P}\,\mathbold{A}^\intercal\,\textrm{DiAP}$.

In Wolfram, our original, foldable Kalman filter was

\noindent and our new minimal, foldable filter is

This reads almost as easily as the original if one reads LinearSolve as invert-first-argument-and-matrix-multiply.

Notice we do not compute the Kalman gain explicitly, but only use it in combination with other matrices. This produces results indistinguishable from the original, up to floating-point issues, when folded over any source of data.

LAPACK offers a function, dposv,dposndoc that solves this linear system when $\mathbold{D}$ is symmetric and positive definite. Because $\mathbold{D}$ is the sum of a diagonal matrix $\mathbold{Z}$ and a symmetric, positive-definite matrix $\mathbold{A}\,\mathbold{P}\,\mathbold{A}^\intercal$, it should also be symmetric and positive definite. Therefore, we transcribe the code above into C as follows

3.2 Fortran and C

We need matrix operations, and we choose CBLAScbls and LAPACKE.lpke

3.3 LAPACK and LAPACKE

3.3.1 Full Least-Squares Without Fold

const int    m = 5;
const int    n = 4;

double A[m * n] = { 1,  0.,  0.,  0.,
                    1,  1.,  1.,  1.,
                    1, -1.,  1., -1.,
                    1, -2.,  4., -8.,
                    1,  2.,  4.,  8. };

// Compute Transpose[A].A; A is not disturbed.

double AtA[n * n];
memset (AtA, 0, n * n * sizeof (double));

cblas_dgemm (CblasRowMajor, CblasTrans, CblasNoTrans,
             n, n, m, 1,
             A, n, // LDA is pre-transpose
             A, n,
             0,
             AtA, n);

for (int r = 0; r < n; ++r)
{   for (int c = 0; c < n; ++c)
    {   printf ("%g ", AtA[c + r * n]);   }
    printf ("\n");   }
printf ("\n");

// Compute Transpose[A].z; neither A nor z is disturbed. Results are deposited
// into Atz.

double z[m] = {-2.28442, -4.83168, -10.4601, 1.40488, -40.8079};

double Atz[n];

cblas_dgemv (CblasRowMajor, CblasTrans,
             m, n, 1,
             A, n,
             z, 1, 0,
             Atz, 1);

for (int i = 0; i < n; ++i)
{   printf ("%g ", Atz[i]);   }
printf ("\n");

// Solve At.A.x = At.z = Atz. Unlike the CBLAS routines, the input storage
// locations are modified. The Cholesky decomposition of AtA is deposited into
// AtA, in-place, and the solution is deposited into Atz. To preserve these
// matrices, it's necessary to copy them first.

// The documentation for LAPACKE_dposv has an apparent error (see
// http://tinyurl.com/htvod3e). It states that the leading dimension of B must
// be >= max(1, N), but we suspect it should say >= max(1, NRHS). The results
// are definitely wrong if N is used as LDB.

// The results of this computation are identical to those from Mathematica. This
// is not surprising because Mathematica probably uses LAPACK internally.

lapack_int LAPACKE_dposv( int matrix_layout, char uplo, lapack_int n,
                          lapack_int nrhs, double* a, lapack_int lda, double* b,
                          lapack_int ldb );

lapack_int result = LAPACKE_dposv (LAPACK_ROW_MAJOR, 'U', n, 1, AtA, n, Atz, 1);

printf ("%d\n\n", result);

for (int i = 0; i < n; ++i)
{   printf ("%g ", Atz[i]);   }
printf ("\n");
50100
010034
100340
0340130
-56.9792-78.7971-172.904-332.074
0
-2.975077.27001-4.21039-4.4558

3.3.2 Foldable Kalman Without Inverse

/*
  Copyright 2016 Brian C. Beckman

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
/* This is an educational example only, not suitable for real applications.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cblas.h>
#include <lapacke.h>

void printm (char * nym, double * m, int rows, int cols)
{   printf ("%s\n", nym);
    for (int r = 0; r < rows; ++r)
    {   for (int c = 0; c < cols; ++c)
        {   printf ("%g ", m[c + r * cols]);   }
        printf ("\n");   }
    printf ("\n");   }

void kalman (int b,        /* # rows, cols, in Z; # rows in z */
             int n,        /* # rows, cols, in P; # rows in x */
             double * IdN, /* n x n identity matrix */
             double * Z,   /* b x b observation covariance */
             double * x,   /* n x 1, current state */
             double * P,   /* n x n, current covariance */
             double * A,   /* b x n, current observation partials */
             double * z    /* b x 1, current observation vector */
             ) {

    /* Transcribe the following Wolfram code (the intermediate matrices are not
     * necessary in Wolfram, but we need them in C).
     *
     * noInverseKalman[Z_][{x_, P_}, {A_, z_}] :=
     *   Module[{PAT, D, DiRes, DiAP, KRes, KAP},
     *    PAT = P.Transpose[A];               (* n x b *)
     *    D = Z + A.PAT;                      (* b x b *)
     *    DiRes = LinearSolve[D, z - A.x];    (* b x 1 *)
     *    KRes = PAT.DiRes;                   (* n x 1 *)
     *    DiAP = LinearSolve[D, A.P];         (* b x n *)
     *    KAP = PAT.DiAP;                     (* n x n *)
     *    {x + KRes, P - KAP}];
     */


    /* Use dgemm for P.A^T because dsymm doesn't offer a way to transpose the
       right-hand multiplicand. */

    /*
     *      PAT              P           AT
     *       b               n           b
     *  n / * * \     n / * * * * \ n / * * \
     *    | * * |  <--  | * * * * |   | * * |
     *    | * * |       | * * * * |   | * * |
     *    \ * * /       \ * * * * /   \ * * /
     *
     */

    double PAT[n * b];
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasTrans,
                 n,          /* m (n),    # rows of A (P) */
                 b,          /* n (b),    # cols of B (AT) (post-transpose) */
                 n,          /* k (n),    # cols of A (P) == rows of B (AT post-tranpose) */
                 1, P, n,    /* alpha, A, # cols A (P,  pre-transpose)*/
                 A, n,       /*        B, # cols B (AT, pre-transpose)*/
                 0, PAT, b); /* beta,  C, # cols C */
    printm ("P.AT", PAT, n, b);

    /*
     *       D      =         A     .    PAT    +     Z
     *       b                n           b           b
     *  b / * * \      b / * * * * \ n / * * \ + b / * * \
     *    \ * * /  <--   \ * * * * /   | * * |     \ * * /
     *                                 | * * |
     *                                 \ * * /
     *
     */

    double D[b * b];
    /* D <- A.PAT + Z (copy Z to D first) */
    cblas_dcopy (b * b, Z, 1, D, 1);
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans,
                 b,          /* m (b),          # rows of A (A) */
                 b,          /* n (b),          # cols of B (PAT) */
                 n,          /* k (n),          # cols of A (A) == rows of B (PAT) */
                 1, A, n,    /* alpha, A (A),   # cols A (A) */
                 PAT, b,     /*        B (PAT), # cols B (PAT)*/
                 1, D, b);   /* beta,  C (Z),   # cols C (D) */
    printm ("D", D, b, b);

    /*
     *     Res  =  alpha * A    .     x + beta * z
     *      1              n          1          1
     *  b / * \     b / * * * * \ n / * \ +  b / * \
     *    \ * /  <--  \ * * * * /   | * |      \ * /
     *                              | * |
     *                              \ * /
     *
     */
    double Res[b * 1];
    /* Res <- (-A.x) + z (copy z to Res first)  */
    cblas_dcopy (b * 1, z, 1, Res, 1);
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans,
                 b,          /* m (b),        # rows of A (A) */
                 1,          /* n (1),        # cols of B (x) */
                 n,          /* k (n),        # cols of A (A) == rows of B (x) */
                 -1, A, n,   /* alpha, A (A), # cols A (A) */
                 x, 1,       /*        B (x), # cols B (x) */
                 1, Res, 1); /* beta,  C (z), # cols C (Res) */
    printm ("Res", Res, b, 1);

    /*
     *    DiRes  = (Di = D^-1) . Res
     *      1            b        1
     *  b / * \     b / * * \ b / * \
     *    \ * /  <--  \ * * /   \ * /
     *
     */

    double DiRes[b * 1];
    double DCholesky[b * b];
    /* DiRes = LinearSolve[D, z - A.x];    (* b x 1 *) */
    /* copy Res to DiRes, first. */
    /* copy D to DCholesky first. */
    /* dposv: http://goo.gl/O7gUH8 */
    cblas_dcopy (b * 1, Res, 1, DiRes,     1);
    cblas_dcopy (b * b, D,   1, DCholesky, 1);
    int result = LAPACKE_dposv (LAPACK_ROW_MAJOR, 'U',
                                b,         /* NEQS: # rows of D */
                                1,         /* NRHS: # columns of z - A.x == Res */
                                DCholesky, /* DCholesky starts as D */
                                b,         /* PDA D */
                                DiRes,     /* output buffer */
                                b);        /* PDA DiRes */
    printf ("DPOSV DiRes result %d\n\n", result);
    printm ("DiRes",     DiRes,     b, 1);
    printm ("DCholesky", DCholesky, b, b);

    /*
     *     KRes  =      PAT  .  DiRes
     *      1            b        1
     *  n / * \     n / * * \ b / * \
     *    | * |  <--  | * * |   \ * /
     *    | * |       | * * |
     *    \ * /       \ * * /
     *
     */

    double KRes[n * 1];
    /* KRes <-- PAT.DiRes */
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans,
                 n,           /* m (n),            # rows of A (PAT) */
                 1,           /* n (1),            # cols of B (DiRes) */
                 b,           /* k (b),            # cols of A (PAT) == # rows of B (DiRes) */
                 1, PAT, b,   /* alpha, A (PAT),   # cols A (PAT) */
                 DiRes, 1,    /*        B (DiRes), # cols B (DiRes) */
                 0, KRes, 1); /* beta,  C (KRes),  # cols C (KRes) */
    printm ("KRes", KRes, n, 1);

    /*
     *         AP      =         A      .      P
     *         n                 n             n
     *  b / * * * * \     b / * * * * \ n / * * * * \
     *    \ * * * * /  <--  \ * * * * /   | * * * * |
     *                                    | * * * * |
     *                                    \ * * * * /
     *
     */

    double AP[b * n];
    /* AP <-- A.P */
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans,
                 b,           /* m (b),          # rows of A (A) */
                 n,           /* n (n),          # cols of B (P) */
                 n,           /* k (n),          # cols of A (A) == # rows of B (P) */
                 1, A, n,     /* alpha, A (A),   # cols A (PAT) */
                 P, n,        /*        B (P),   # cols B (DiRes) */
                 0, AP, n);   /* beta,  C (AP),  # cols C (KRes) */
    printm ("AP", AP, b, n);

    /*
     *        DiAP    =    (Di = D^-1)  .  A      .      P
     *         n               b           n             n
     *  b / * * * * \     b / * * \ b / * * * * \ n / * * * * \
     *    \ * * * * /  <--  \ * * /   \ * * * * /   | * * * * |
     *                                              | * * * * |
     *                                              \ * * * * /
     *
     */

    double DiAP[b * n];
    /* DiAP = LinearSolve[D, AP];    (* b x n *) */
    /* copy AP to DiAP, first. */
    /* copy D to DCholesky first. */
    /* dposv: http://goo.gl/O7gUH8 */
    cblas_dcopy (b * n, AP, 1, DiAP,      1);
    cblas_dcopy (b * b, D,  1, DCholesky, 1);
    result = LAPACKE_dposv (LAPACK_ROW_MAJOR, 'U',
                            b,         /* NEQS: # rows of D */
                            n,         /* NRHS: # columns of z - A.x == Res */
                            DCholesky, /* DCholesky starts as D */
                            b,         /* PDA D */
                            DiAP,      /* output buffer */
                            n);        /* PDA DiRes */
    printf ("DPOSV DiAP result %d\n\n", result);
    printm ("DiAP",      DiAP,      b, n);
    printm ("DCholesky", DCholesky, b, b);

    /*
     *        KAP      =      PAT    .    DiAP
     *         n               b           n
     *  n / * * * * \     n / * * \ b / * * * * \
     *    | * * * * |  <--  | * * |   \ * * * * /
     *    | * * * * |       | * * |
     *    \ * * * * /       \ * * /
     *
     */

    double KAP[n * n];
    /* KAP <-- PAT.DiAP */
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans,
                 n,             /* m (n),           # rows of A (PAT) */
                 n,             /* n (n),           # cols of B (DiAP) */
                 b,             /* k (b),           # cols of A (PAT) == # rows of B (DiAP) */
                 1, PAT, b,     /* alpha, A (PAT),  # cols A (PAT) */
                 DiAP, n,       /*        B (Diap), # cols B (DiRes) */
                 0, KAP, n);    /* beta,  C (KAP),  # cols C (KAP) */
    printm ("KAP", KAP, n, n);

    /*
     *      x  =  alpha * Id     .    x   +    KRes
     *      1              n          1         1
     *  n / * \     n / * * * * \ n / * \ + n / * \
     *    | * |  <--  | * * * * |   | * |     | * |
     *    | * |       | * * * * |   | * |     | * |
     *    \ * /       \ * * * * /   \ * /     \ * /
     *
     */

    /* x <-- alpha * IdN[n] * KRes + beta * x */
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans,
                 n,           /* m (n),           # rows of A (Id) */
                 1,           /* n (1),           # cols of B (x) */
                 n,           /* k (n),           # cols of A (Id) == rows of B (x) */
                 1, IdN, n,   /* alpha, A (Id),   # cols A */
                 x, 1,        /*        B (x),    # cols B */
                 1, KRes, 1); /* beta,  C (Kres), # cols C (new x) */
    cblas_dcopy (n * 1, KRes, 1, x, 1);
    printm ("x", x, n, 1);

    /*
     *         P    =   alpha * Id      .     KAP    +  beta * P
     *         n                 n             n               n
     *  n / * * * * \     n / * * * * \ n / * * * * \ + n / * * * * \
     *    | * * * * |  <--  | * * * * |   | * * * * |     | * * * * |
     *    | * * * * |       | * * * * |   | * * * * |     | * * * * |
     *    \ * * * * /       \ * * * * /   \ * * * * /     \ * * * * /
     *
     */

    /* P <-- P - KAP == - IdN[n] * KAP  + P */
    /* dgemm: http://tinyurl.com/j24npm4 */
    /* C <-- alpha * A * B + beta * C */
    cblas_dgemm (CblasRowMajor, CblasNoTrans, CblasNoTrans,
                 n,           /* m (n),         # rows of A (Id) */
                 n,           /* n (n),         # cols of B (KAP) */
                 n,           /* k (n),         # cols of A (Id) == rows of B (KAP) */
                 -1, IdN, n,  /* alpha, A (Id), # cols A */
                 KAP, n,      /*        B (x),  # cols B */
                 1, P, n);    /* beta,  C (P),  # cols C (new P) */
    printm ("P", P, n, n); }

int main (int argc, char ** argv)
{   const int    b = 1;
    const int    n = 4;

    double IdN[n * n] = { 1., 0., 0., 0.,
                          0., 1., 0., 0.,
                          0., 0., 1., 0.,
                          0., 0., 0., 1. };


    double Z[b * b] = {1.};

    double x[n * 1] = {0., 0., 0., 0};
    double P[n * n] = {1000.,    0.,    0.,    0.,
                       0., 1000.,    0.,    0.,
                       0.,    0., 1000.,    0.,
                       0.,    0.,    0., 1000. };

    double A[b * n] = {1., 0., 0., 0};
    double z[b] = {-2.28442};

    kalman (b, n, IdN, Z, x, P, A, z);

    A[0] = 1;
    A[1] = 1;
    A[2] = 1;
    A[3] = 1;

    z[0] = -4.83168;

    kalman (b, n, IdN, Z, x, P, A, z);

    A[0] = 1;
    A[1] = -1;
    A[2] = 1;
    A[3] = -1;

    z[0] = -10.4601;

    kalman (b, n, IdN, Z, x, P, A, z);

    A[0] = 1;
    A[1] = -2;
    A[2] = 4;
    A[3] = -8;

    z[0] = 1.40488;

    kalman (b, n, IdN, Z, x, P, A, z);

    A[0] = 1;
    A[1] = 2;
    A[2] = 4;
    A[3] = 8;

    z[0] = -40.8079;

    kalman (b, n, IdN, Z, x, P, A, z);

    return 0;   }
gcc qux.c -lcblas -llapacke -llapack
./a.out

4 Footnotes

affn https://en.wikipedia.org/wiki/Affine_transformation babl http://orgmode.org/worg/org-contrib/babel/ bars Bar-Shalom, Yaakov, et al. Estimation with applications to tracking and navigation. New York: Wiley, 2001. bier http://tinyurl.com/h3jh4kt blas http://www.netlib.org/blas/ blck http://tinyurl.com/bgwfkyc bssl https://en.wikipedia.org/wiki/Bessel’s_correction busi https://en.wikipedia.org/wiki/Business_logic cbls http://www.netlib.org/blas/ cdot We sometimes use the center dot or the $×$ symbols to clarify matrix multiplication. They have no other significance and we can always write matrix multiplication just by juxtaposing the matrices. clos https://en.wikipedia.org/wiki/Closure_(computer_programming) cold This convention only models so-called cold observables, but it’s enough to demonstrate Kalman’s working over them. cons This is quite similar to the standard — not Wolfram’s — definition of a list as a pair of a value and of another list. cova We use the terms covariance for matrices and variance for scalars. csoc https://en.wikipedia.org/wiki/Separation_of_concerns ctsc https://en.wikipedia.org/wiki/Catastrophic_cancellation ditm Cook, John D. Don’t invert that matrix http://tinyurl.com/ya4q2kv dpos http://www.nag.com/numeric/FL/manual/pdf/F07/f07faf.pdf doxy http://www.stack.nl/~dimitri/doxygen/ dstr http://tinyurl.com/ze6qfb3 dtaa http://tinyurl.com/cwcdwq8 eclx https://gitlab.com/embeddable-common-lisp/ecl/wikis/home elib Brookner, Eli. Tracking and Kalman Filtering Made Easy, New York: Wiley, 1998. http://tinyurl.com/h8see8k fldl http://tinyurl.com/jmxsevr fncc http://blog.madhukaraphatak.com/functional-programming-in-c++/ fwik https://en.wikipedia.org/wiki/Fold_%28higher-order_function%29 gama https://en.wikipedia.org/wiki/Gauss%E2%80%93Markov_theorem gibb Bruce P. Gibbs, Advanced Kalman Filtering, Least-Squares and Modeling, New York: Wiley, 2011. gslb http://www.gnu.org/software/gsl/ gtst https://code.google.com/p/googletest/ intr http://introtorx.com/ ipyt http://ipython.org/ jass http://www.jstatsoft.org/v46/i03 javd http://tinyurl.com/o424429 jplg JPL Geodynamics Program http://www.jpl.nasa.gov/report/1981.pdf just justified by the fact that $\mathbold{D}$ is a diagonal matrix that commutes with all other products, therefore its left and right inverses are equal and can be written as a reciprocal; in fact, $\mathbold{D}$ is a $1×{1}$ matrix — effectively a scalar — in all examples in this paper klde B. Beckman, Kalman Folding 3: Derivations, to appear. klf1 B. Beckman, Kalman Folding, Part 1, to appear. klf2 B. Beckman, Kalman Folding 2: Tracking and System Dynamics, to appear. klf3 B. Beckman, Kalman Folding 3: Derivations, to appear. klf4 B. Beckman, Kalman Folding 4: Streams and Observables, to appear. klf5 B. Beckman, Kalman Folding 5: Non-Linear Models and the EKF, to appear. klf7 B. Beckman, Kalman Folding 7: A Small Streams Library, to appear. klf9 B. Beckman, Kalman Folding 9: in C, to appear. klfl B. Beckman, Kalman Folding, Part 1, to appear. kohl https://chriskohlhepp.wordpress.com/embedding-lisp-in-cplusplus-a-recipe/ lavl http://vgoulet.act.ulaval.ca/en/emacs/ layi https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering litp https://en.wikipedia.org/wiki/Literate_programming lmbd Many languages use the keyword lambda for such expressions; Wolfram uses the name Function. lmlf https://en.wikipedia.org/wiki/Lambda_lifting lpck http://www.netlib.org/lapack/ lpke http://www.netlib.org/lapack/lapacke.html lsqo LINQ’s Standard Query Operators lssq https://en.wikipedia.org/wiki/Least_squares ltis http://tinyurl.com/hhhcgca matt https://www.cs.kent.ac.uk/people/staff/dat/miranda/whyfp90.pdf mcmc https://en.wikipedia.org/wiki/Particle_filter mond https://en.wikipedia.org/wiki/Monad mrck https://gist.github.com/mrocklin/5144149 musc http://www1.cs.dartmouth.edu/~doug/music.ps.gz ndim https://en.wikipedia.org/wiki/Nondimensionalization ndoc http://www.nag.com/numeric/CL/CLdocumentation.asp obc1 Make sure the first example from http://tinyurl.com/kz2lz7m works orgm https://en.wikipedia.org/wiki/Org-mode patt http://tinyurl.com/j5jzy69 pico http://tinyurl.com/gku2k74 plnt http://plantuml.com/emacs.html pseu http://tinyurl.com/j8gvlug rasp http://www.wolfram.com/raspberry-pi/ rcrn https://en.wikipedia.org/wiki/Recurrence_relation root https://root.cern.ch/root-user-guides-and-manuals rprs https://www.coursera.org/learn/reproducible-research rsfr http://rosettacode.org/wiki/Loops/Foreach rxbk http://www.introtorx.com/content/v1.0.10621.0/07_Aggregation.html sage http://www.sagemath.org/ scan and of Haskell’s scans and folds, and Rx’s scans and folds, etc. scla http://tinyurl.com/hhdot36 scnd A state-space form containing a position and derivative is commonplace in second-order dynamics like Newton’s Second Law. We usually employ state-space form to reduce \(n\)-th-order differential equations to first-order differential equations by stacking the dependent variable on $n-1$ of its derivatives in the state vector. scnl http://learnyouahaskell.com/higher-order-functions scp1 https://en.wikipedia.org/wiki/Scripting_language scp2 http://tinyurl.com/mj3n9aq scp3 http://tinyurl.com/3tunry3 siod http://tinyurl.com/o5jx6xr spac https://github.com/syl20bnr/spacemacs spcm http://www.spacemacs.org stsp https://en.wikipedia.org/wiki/State-space_representation tikz http://tinyurl.com/juw7524 trak B. Beckman, Kalman Folding 2: Tracking and System Dynamics, To appear. uncl The initial uncial (lower-case) letter signifies that we wrote this function; it wasn’t supplied by Wolfram. wfld http://reference.wolfram.com/language/ref/FoldList.html?q=FoldList wlf1 http://tinyurl.com/nfz9fyo wlf2 http://rebcabin.github.io/blog/2013/02/04/welfords-better-formula/ wolf http://reference.wolfram.com/language/ zarc Zarchan and Musoff, Fundamentals of Kalman Filtering, A Practical Approach, Fourth Edition, Ch. 4