Skip to content

File syn_kalman.c

File List > dsp > syn_kalman.c

Go to the documentation of this file

#if __has_include("syn_config.h")
#include "syn_config.h"
#endif

#if !defined(SYN_USE_KALMAN) || SYN_USE_KALMAN

#include "../util/syn_assert.h"
#include "syn_kalman.h"

#include <string.h>

SYN_Status syn_kalman_init(SYN_Kalman *kf, const SYN_Kalman_Config *cfg)
{
    SYN_ASSERT(kf != NULL);
    SYN_ASSERT(cfg != NULL);

    uint8_t ns = cfg->n_state;
    uint8_t nm = cfg->n_meas;

    /* Validate dimensions */
    if (cfg->x->rows != ns || cfg->x->cols != 1)
        return SYN_INVALID_PARAM;
    if (cfg->P->rows != ns || cfg->P->cols != ns)
        return SYN_INVALID_PARAM;
    if (cfg->F->rows != ns || cfg->F->cols != ns)
        return SYN_INVALID_PARAM;
    if (cfg->Q->rows != ns || cfg->Q->cols != ns)
        return SYN_INVALID_PARAM;
    if (cfg->H->rows != nm || cfg->H->cols != ns)
        return SYN_INVALID_PARAM;
    if (cfg->R->rows != nm || cfg->R->cols != nm)
        return SYN_INVALID_PARAM;

    kf->cfg = cfg;

    return SYN_OK;
}

void syn_kalman_predict(SYN_Kalman *kf)
{
    SYN_ASSERT(kf != NULL && kf->cfg != NULL);

    const SYN_Kalman_Config *c = kf->cfg;

    /* x̂⁻ = F · x̂ */
    syn_matrix_mul(c->F, c->x, kf->temp_n1);
    syn_matrix_copy(c->x, kf->temp_n1);

    /* P⁻ = F · P · Fᵀ + Q */
    syn_matrix_mul(c->F, c->P, kf->temp_nn_1);          /* F·P */
    syn_matrix_transpose(c->F, kf->temp_nn_2);          /* Fᵀ */
    syn_matrix_mul(kf->temp_nn_1, kf->temp_nn_2, c->P); /* F·P·Fᵀ → P */
    syn_matrix_add(c->P, c->Q, kf->temp_nn_1);          /* P + Q */
    syn_matrix_copy(c->P, kf->temp_nn_1);
}

SYN_Status syn_kalman_update(SYN_Kalman *kf, const SYN_Matrix *z)
{
    SYN_ASSERT(kf != NULL && z != NULL);

    const SYN_Kalman_Config *c = kf->cfg;
    uint8_t ns = c->n_state;
    uint8_t nm = c->n_meas;

    /* ── Innovation: y = z − H·x̂⁻ ────────────────────────────────────── */
    syn_matrix_mul(c->H, c->x, kf->temp_m1);     /* H·x → temp_m1 */
    syn_matrix_sub(z, kf->temp_m1, kf->temp_m1); /* y = z - H·x   */

    /* ── Innovation covariance: S = H·P⁻·Hᵀ + R ──────────────────────── */
    syn_matrix_mul(c->H, c->P, kf->temp_mn);               /* H·P  (nm×ns)   */
    syn_matrix_transpose(c->H, kf->temp_nm);               /* Hᵀ   (ns×nm)   */
    syn_matrix_mul(kf->temp_mn, kf->temp_nm, kf->temp_mm); /* H·P·Hᵀ (nm×nm) */
    syn_matrix_add(kf->temp_mm, c->R, kf->temp_mm_2);      /* S = HPHᵀ + R    */

    /* ── S⁻¹ ──────────────────────────────────────────────────────────── */
    SYN_Status inv_st = syn_matrix_inv(kf->temp_mm_2, kf->temp_mm);
    if (inv_st != SYN_OK)
        return SYN_ERROR;
    /* temp_mm = S⁻¹ (nm × nm) */

    /* ── Kalman gain: K = P·Hᵀ·S⁻¹ ────────────────────────────────────
     *
     * temp_nm already holds Hᵀ (ns × nm).
     * We compute K in two steps to avoid aliasing:
     *   1. Hᵀ·S⁻¹ → (ns×nm)(nm×nm) = (ns×nm), store on stack as K
     *   2. K_final = P · K_partial → (ns×ns)(ns×nm) = (ns×nm)
     *
     * K is at most SYN_KALMAN_MAX_STATE × SYN_KALMAN_MAX_MEAS
     * elements. For a 6-state 4-measurement filter this is 24 × 4 = 96 bytes.
     */
    q16_t k_buf[SYN_KALMAN_MAX_STATE * SYN_KALMAN_MAX_MEAS];
    SYN_Matrix K = {k_buf, ns, nm};

    syn_matrix_mul(kf->temp_nm, kf->temp_mm, &K); /* Hᵀ·S⁻¹ */
    syn_matrix_mul(c->P, &K, kf->temp_nm);        /* P·Hᵀ·S⁻¹ */
    syn_matrix_copy(&K, kf->temp_nm);             /* K final  */

    /* ── State update: x̂ = x̂⁻ + K·y ──────────────────────────────────── */
    syn_matrix_mul(&K, kf->temp_m1, kf->temp_n1); /* K·y */
    syn_matrix_add(c->x, kf->temp_n1, c->x);      /* x += K·y */

    /* ── Covariance update: P = (I − K·H)·P⁻ ─────────────────────────── */
    syn_matrix_mul(&K, c->H, kf->temp_nn_1);                     /* K·H (ns×ns) */
    syn_matrix_identity(kf->temp_nn_2);                          /* I */
    syn_matrix_sub(kf->temp_nn_2, kf->temp_nn_1, kf->temp_nn_1); /* I - K·H */

    syn_matrix_copy(kf->temp_nn_2, c->P);               /* save P⁻  */
    syn_matrix_mul(kf->temp_nn_1, kf->temp_nn_2, c->P); /* (I-K·H)·P⁻ → P */

    return SYN_OK;
}

#endif /* SYN_USE_KALMAN */