Skip to content

Port AxialKalmanFilter from libDirectional to PyRecEst#1701

Open
Copilot wants to merge 2 commits intomainfrom
copilot/port-axial-kalman-filter-to-pyrecest
Open

Port AxialKalmanFilter from libDirectional to PyRecEst#1701
Copilot wants to merge 2 commits intomainfrom
copilot/port-axial-kalman-filter-to-pyrecest

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 15, 2026

Ports the MATLAB AxialKalmanFilter — a Kalman filter for antipodally symmetric distributions on the unit hypersphere — to Python, supporting both unit complex numbers (2D) and quaternions (4D).

Changes

  • pyrecest/filters/axial_kalman_filter.py — new AxialKalmanFilter(AbstractAxialFilter):

    • dim property returns filter_state.dim - 1 (manifold dim) so the inherited _set_composition_operator selects complex vs. quaternion multiplication correctly
    • filter_state setter validates unit-norm mean and 2D/4D shape, then re-arms the composition operator
    • predict_identity(gauss_w): mu_new = mu ⊕ gauss_w.mu, C_new = C + C_w
    • update_identity(gauss_v, z): conjugates noise mean out of measurement, mirrors z for antipodal consistency, runs standard Kalman update (K = C·(C+V)⁻¹), re-normalises mean to unit vector
  • pyrecest/filters/__init__.py — exports AxialKalmanFilter

  • pyrecest/tests/filters/test_axial_kalman_filter.py — 12 tests (2D + 4D) mirroring AxialKalmanFilterTest.m: state round-trip, predict with identity/non-identity noise, update at mode, antipodal symmetry, and unit-norm enforcement after update

from pyrecest.filters import AxialKalmanFilter
from pyrecest.distributions import GaussianDistribution
import numpy as np

f = AxialKalmanFilter()
mu = np.array([1., 2., 3., 4.]); mu /= np.linalg.norm(mu)
f.filter_state = GaussianDistribution(mu, 0.3 * np.eye(4))

# predict: x_{k+1} = x_k ⊕ w_k  (quaternion multiply)
f.predict_identity(GaussianDistribution(np.array([1.,0.,0.,0.]), 0.01*np.eye(4)))

# update: z = x_k ⊕ v_k
f.update_identity(GaussianDistribution(np.array([1.,0.,0.,0.]), 0.1*np.eye(4)), z)

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 15, 2026

MegaLinter analysis: Success

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ COPYPASTE jscpd yes no no 11.37s
✅ JSON prettier 2 0 0 0 0.46s
✅ JSON v8r 2 0 0 2.48s
✅ MARKDOWN markdownlint 1 0 0 0 0.65s
✅ MARKDOWN markdown-table-formatter 1 0 0 0 0.23s
✅ PYTHON bandit 296 0 0 3.76s
✅ PYTHON black 296 20 0 0 7.48s
✅ PYTHON flake8 296 0 0 2.11s
✅ PYTHON isort 296 26 0 0 0.57s
✅ PYTHON mypy 296 0 0 4.33s
✅ PYTHON pylint 296 0 0 85.17s
✅ PYTHON ruff 296 26 0 0 0.06s
✅ REPOSITORY checkov yes no no 22.78s
✅ REPOSITORY gitleaks yes no no 4.48s
✅ REPOSITORY git_diff yes no no 0.05s
✅ REPOSITORY secretlint yes no no 7.03s
✅ REPOSITORY syft yes no no 4.42s
✅ REPOSITORY trivy-sbom yes no no 1.82s
✅ REPOSITORY trufflehog yes no no 18.21s
✅ YAML prettier 4 1 0 0 0.48s
✅ YAML v8r 4 0 0 5.5s
✅ YAML yamllint 4 0 0 0.49s

See detailed reports in MegaLinter artifacts

Your project could benefit from a custom flavor, which would allow you to run only the linters you need, and thus improve runtime performances. (Skip this info by defining FLAVOR_SUGGESTIONS: false)

  • Documentation: Custom Flavors
  • Command: npx mega-linter-runner@9.4.0 --custom-flavor-setup --custom-flavor-linters PYTHON_PYLINT,PYTHON_BLACK,PYTHON_FLAKE8,PYTHON_ISORT,PYTHON_BANDIT,PYTHON_MYPY,PYTHON_RUFF,COPYPASTE_JSCPD,JSON_V8R,JSON_PRETTIER,MARKDOWN_MARKDOWNLINT,MARKDOWN_MARKDOWN_TABLE_FORMATTER,REPOSITORY_CHECKOV,REPOSITORY_GIT_DIFF,REPOSITORY_GITLEAKS,REPOSITORY_SECRETLINT,REPOSITORY_SYFT,REPOSITORY_TRIVY_SBOM,REPOSITORY_TRUFFLEHOG,YAML_PRETTIER,YAML_YAMLLINT,YAML_V8R

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

@github-actions
Copy link
Copy Markdown

Test Results

    3 files      3 suites   31m 55s ⏱️
  777 tests   777 ✅ 0 💤 0 ❌
2 463 runs  2 463 ✅ 0 💤 0 ❌

Results for commit e48046c.

@FlorianPfaff
Copy link
Copy Markdown
Owner

@copilot Iincrease tolerance of the test case so that it also succeeds for the jax backend

=========================== short test summary info ============================
FAILED pyrecest/tests/filters/test_axial_kalman_filter.py::TestAxialKalmanFilter4D::test_update_identity_at_mode - AssertionError:
Not equal to tolerance rtol=1e-10, atol=0

Mismatched elements: 4 / 4 (100%)
Mismatch at indices:
[0]: 0.18257418274879456 (ACTUAL), 0.18257419764995575 (DESIRED)
[1]: 0.3651483654975891 (ACTUAL), 0.3651483952999115 (DESIRED)
[2]: 0.5477225184440613 (ACTUAL), 0.547722578048706 (DESIRED)
[3]: 0.7302967309951782 (ACTUAL), 0.730296790599823 (DESIRED)
Max absolute difference among violations: 5.9604645e-08
Max relative difference among violations: 1.0882269e-07
ACTUAL: array([0.182574, 0.365148, 0.547723, 0.730297], dtype=float32)
DESIRED: array([0.182574, 0.365148, 0.547723, 0.730297], dtype=float32)
FAILED pyrecest/tests/filters/test_axial_kalman_filter.py::TestAxialKalmanFilter2D::test_update_identity_at_mode - AssertionError:
Not equal to tolerance rtol=1e-10, atol=0

Mismatched elements: 2 / 2 (100%)
Mismatch at indices:
[0]: 0.4472135901451111 (ACTUAL), 0.44721361994743347 (DESIRED)
[1]: 0.8944271802902222 (ACTUAL), 0.8944272398948669 (DESIRED)
Max absolute difference among violations: 5.9604645e-08
Max relative difference among violations: 6.664001e-08
ACTUAL: array([0.447214, 0.894427], dtype=float32)
DESIRED: array([0.447214, 0.894427], dtype=float32)
= 2 failed, 472 passed, 303 skipped, 48 warnings, 9 subtests passed in 996.60s (0:16:36) =

auto-merge was automatically disabled April 16, 2026 07:44

Head branch was pushed to by a user without write access

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 16, 2026

@copilot Iincrease tolerance of the test case so that it also succeeds for the jax backend

=========================== short test summary info ============================
...

Fixed in 1361b0f. Changed the three rtol=1e-10 assertions in test_update_identity_at_mode (2D & 4D) and test_update_identity_unit_norm (4D) to atol=1e-6, which comfortably covers the ~6e-8 float32 error seen on the JAX backend.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants