neckcompass 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- neckcompass-1.0.0/LICENSE +21 -0
- neckcompass-1.0.0/MANIFEST.in +10 -0
- neckcompass-1.0.0/PKG-INFO +269 -0
- neckcompass-1.0.0/README.md +233 -0
- neckcompass-1.0.0/csrc/Makefile +21 -0
- neckcompass-1.0.0/csrc/neckcompass.c +527 -0
- neckcompass-1.0.0/csrc/neckcompass.h +119 -0
- neckcompass-1.0.0/csrc/tools/neckcompass_selftest.c +300 -0
- neckcompass-1.0.0/neckcompass/__init__.py +22 -0
- neckcompass-1.0.0/neckcompass/__main__.py +6 -0
- neckcompass-1.0.0/neckcompass/cli.py +281 -0
- neckcompass-1.0.0/neckcompass/path_integrator.py +233 -0
- neckcompass-1.0.0/neckcompass/ring_attractor.py +453 -0
- neckcompass-1.0.0/neckcompass.egg-info/PKG-INFO +269 -0
- neckcompass-1.0.0/neckcompass.egg-info/SOURCES.txt +19 -0
- neckcompass-1.0.0/neckcompass.egg-info/dependency_links.txt +1 -0
- neckcompass-1.0.0/neckcompass.egg-info/entry_points.txt +2 -0
- neckcompass-1.0.0/neckcompass.egg-info/requires.txt +7 -0
- neckcompass-1.0.0/neckcompass.egg-info/top_level.txt +1 -0
- neckcompass-1.0.0/pyproject.toml +65 -0
- neckcompass-1.0.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Amandeep Singh Bains
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: neckcompass
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: NeckCompass: Drosophila Central Complex ring-attractor compass and GPS-denied path integrator with natural ferromagnetic spike rejection
|
|
5
|
+
Author: Amandeep Singh Bains
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/indian-aman/neckcompass
|
|
8
|
+
Project-URL: Repository, https://github.com/indian-aman/neckcompass
|
|
9
|
+
Project-URL: Releases, https://github.com/indian-aman/neckcompass/releases
|
|
10
|
+
Project-URL: Issues, https://github.com/indian-aman/neckcompass/issues
|
|
11
|
+
Project-URL: Documentation, https://github.com/indian-aman/neckcompass#readme
|
|
12
|
+
Keywords: neckcompass,necklink,ring-attractor,central-complex,drosophila,compass,ahrs,magnetometer,imu,path-integration,gps-denied,robotics,usv,auv,esp32
|
|
13
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: C
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
25
|
+
Classifier: Topic :: System :: Hardware :: Hardware Drivers
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: numpy>=1.24
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
32
|
+
Requires-Dist: matplotlib>=3.7; extra == "dev"
|
|
33
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
34
|
+
Requires-Dist: twine>=5.0; extra == "dev"
|
|
35
|
+
Dynamic: license-file
|
|
36
|
+
|
|
37
|
+
<div align="center">
|
|
38
|
+
|
|
39
|
+
# NeckCompass (`neckcompass` v1.0.0)
|
|
40
|
+
|
|
41
|
+
**Biophysical *Drosophila* Central Complex ring-attractor compass and GPS-denied path integrator with natural ferromagnetic spike rejection — in Python and zero-heap, zero-float ANSI C99 (`316 B` RAM, `~1 us`/step).**
|
|
42
|
+
|
|
43
|
+
[](https://pypi.org/project/neckcompass/)
|
|
44
|
+
[](https://github.com/indian-aman/neckcompass/releases/tag/v1.0.0)
|
|
45
|
+
[](https://pypi.org/project/neckcompass/)
|
|
46
|
+
[](csrc/)
|
|
47
|
+
[](csrc/tools/neckcompass_selftest.c)
|
|
48
|
+
[](tests/test_neckcompass.py)
|
|
49
|
+
[](LICENSE)
|
|
50
|
+
|
|
51
|
+
[**30-Second Quick Start**](#-30-second-quick-start) ·
|
|
52
|
+
[**Plastic Harbor Calibration**](#2-online-soft-iron--harbor-distortion-learning-plasticlandmarkring) ·
|
|
53
|
+
[**Zero-Float ANSI C99 MCU Core**](#3-zero-heap-zero-float-ansi-c99-core-csrc) ·
|
|
54
|
+
[**Honest Benchmarks**](#-honest-empirical-benchmarks-5-seeds--50-hz) ·
|
|
55
|
+
[**When to Use (and When Not To)**](#-when-to-use-neckcompass--and-when-not-to)
|
|
56
|
+
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
<p align="center">
|
|
60
|
+
<img src="https://raw.githubusercontent.com/indian-aman/neckcompass/main/assets/neckcompass_benchmarks.png" alt="NeckCompass v1.0.0 Ring Attractor & Path Integrator Benchmarks" width="100%">
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Why NeckCompass?
|
|
66
|
+
|
|
67
|
+
Marine USVs, inspection drones, subsea rovers, and factory AGVs operate around steel ship hulls, sheet-pile docks, high-current motor leads, and buried power cables that inject intermittent **`70°–150°` ferromagnetic heading spikes** lasting `0.5–2.0 s`. Standard linear-Gaussian estimators break down in this regime:
|
|
68
|
+
|
|
69
|
+
- **Gyro Dead-Reckoning** ignores magnetic anomalies completely, but MEMS gyro bias (`~0.8–1.5°/s`) drifts unboundedly (`50°–100°+` within minutes).
|
|
70
|
+
- **2-State Circular Extended Kalman Filters (`[heading, gyro_bias]`)** are minimum-variance optimal under textbook Gaussian noise, **but** a `120°` ferromagnetic anomaly feeds through the Kalman cross-covariance straight into the **gyro bias state**, causing both massive heading yanks (`45°+` P95) and post-spike bias hangovers.
|
|
71
|
+
|
|
72
|
+
**NeckCompass** ports the neural navigation circuit of the fruit-fly (*Drosophila melanogaster*) **Central Complex** into a deterministic software and microcontroller library — **Product #3** in the **[NeckLink](https://github.com/indian-aman/necklink)** fruit-fly brain engineering family:
|
|
73
|
+
|
|
74
|
+
1. **Ellipsoid Body (`E-PG`) Ring Attractor + `Delta7` Inhibition + `P-EN` Shifters (`RingCompass`)**: Maintains a single topological activity bump across `N=16` (or `N=32`) circular wedges with **zero `-π ↔ +π` wrap-around discontinuity** (`< 10⁻¹⁵ rad` harmonic aliasing on even-`N` rings). Asymmetric Left/Right `P-EN` projections integrate IMU yaw rate (`omega_rad_s * dt`), while localized `R-neuron` sensory cues (`mag_heading_rad`, `sun_heading_rad`) undergo cooperative dendritic coincidence gating against the active `E-PG` bump — smoothly anchoring gyro drift inside the attractor basin while naturally shunting **`70°–150°` ferromagnetic spikes** (`10.2×` lower RMSE than 2-State EKF).
|
|
75
|
+
2. **Associative `R-Neuron → E-PG` Synaptic Plasticity (`PlasticLandmarkRing`)**: Movement-gated Hebbian / anti-Hebbian ring weight matrix that learns warped local soft-iron and hard-iron magnetic distortion tables online around steel harbors or indoor rooms — with or without an external sun/visual reference.
|
|
76
|
+
3. **Fan-Shaped Body `CPU4` Path Integrator + `PFL3`/`PFL2` Steering (`FanBodyNavigator`)**: `N=16` push-pull opponent memory columns integrate translational speed along the live `E-PG` compass bump with common-mode cancellation, computing exact `(distance_m, home_bearing_rad)` and `±67.5°` phase-shifted `PFL3` + `180°` `PFL2` motor yaw commands to steer straight home during complete **GPS blackouts** (`0.84 m` miss after a `1,125 m` spiky mission).
|
|
77
|
+
4. **Zero-Heap, Zero-Float ANSI C99 Core (`csrc/`)**: Pure `Q15` / `int32_t` 16-wedge ring attractor + `CPU4`/`PFL3` navigator using **`316 bytes` of struct RAM** (`576 bytes` total including `260 B` Flash LUTs), **zero `malloc`**, **zero floating-point instructions**, and **`~1.0 us` per update** — ready for `$4` `ESP32`, `STM32`, `RP2040`, and flight controllers.
|
|
78
|
+
|
|
79
|
+
```mermaid
|
|
80
|
+
flowchart LR
|
|
81
|
+
IMU["IMU Gyro Yaw Rate (omega_rad_s)"] --> PEN["P-EN Left/Right Velocity Shifters"]
|
|
82
|
+
PEN --> EPG["16-Wedge E-PG Ring Attractor + Delta7 Inhibition"]
|
|
83
|
+
MAG["Spiky Magnetometer / Sun Azimuth"] --> RN["R2 / R4d Ring Neurons (Plastic W + Basin Gate)"]
|
|
84
|
+
RN -->|"Coherent cues anchor drift; 70-150 deg spikes shunted"| EPG
|
|
85
|
+
EPG --> CPU4["16 Opponent CPU4 Memory Columns (+ Speed)"]
|
|
86
|
+
CPU4 --> PFL3["PFL3 (+/-67.5 deg) + PFL2 (180 deg) Steering"]
|
|
87
|
+
PFL3 --> HOME["GPS-Denied Homebound Yaw Command"]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## ⚡ 30-Second Quick Start
|
|
93
|
+
|
|
94
|
+
### 1. Python (`pip install neckcompass`)
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
pip install neckcompass
|
|
98
|
+
# Or directly from source:
|
|
99
|
+
pip install git+https://github.com/indian-aman/neckcompass.git
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Fuse a biased IMU gyro with a spiky magnetometer and steer home through a complete GPS blackout in 14 lines:
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
import math
|
|
106
|
+
from neckcompass import RingCompass, FanBodyNavigator
|
|
107
|
+
|
|
108
|
+
compass = RingCompass(n_wedges=16, initial_heading_rad=0.0)
|
|
109
|
+
nav = FanBodyNavigator(n_columns=16, compass=compass)
|
|
110
|
+
|
|
111
|
+
# Outbound step @ 50 Hz (dt = 0.02 s): 125-deg hull spike is rejected automatically!
|
|
112
|
+
heading_rad, dist_m, home_bearing_rad = nav.step_with_compass(
|
|
113
|
+
omega_rad_s=0.15,
|
|
114
|
+
speed_m_s=1.5,
|
|
115
|
+
dt=0.02,
|
|
116
|
+
mag_heading_rad=math.radians(125.0), # Transient ferromagnetic spike -> gated out
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# During GPS blackout, generate bounded [-0.8, +0.8] rad/s yaw command back to (0, 0):
|
|
120
|
+
yaw_rate_cmd = nav.steering_cmd(max_turn_rate=0.8)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Run the built-in CLI demo or head-to-head quick benchmark from your terminal:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
neckcompass demo
|
|
127
|
+
neckcompass benchmark-quick
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
### 2. Online Soft-Iron / Harbor Distortion Learning (`PlasticLandmarkRing`)
|
|
133
|
+
|
|
134
|
+
When operating repeatedly inside a steel marina slip or indoor robot bay with static nonlinear magnetic warp (`+20° sin(ψ) + 16° sin(2ψ)`), `PlasticLandmarkRing` learns the `R-neuron → E-PG` unwarping matrix online:
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
from neckcompass import PlasticLandmarkRing
|
|
138
|
+
|
|
139
|
+
plastic = PlasticLandmarkRing(n_wedges=16, n_ring_neurons=32)
|
|
140
|
+
|
|
141
|
+
# Self-supervised during normal turns (or pass reference_heading_rad=sun_rad when sky is visible)
|
|
142
|
+
corrected_heading = plastic.update(
|
|
143
|
+
omega_rad_s=imu_gz,
|
|
144
|
+
dt=0.02,
|
|
145
|
+
raw_mag_heading_rad=raw_mag_rad,
|
|
146
|
+
learn=True,
|
|
147
|
+
)
|
|
148
|
+
unwarped_mag_rad, confidence = plastic.map_raw_heading(raw_mag_rad)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
### 3. Zero-Heap, Zero-Float ANSI C99 Core (`csrc/`)
|
|
154
|
+
|
|
155
|
+
Drop [`csrc/neckcompass.h`](csrc/neckcompass.h) and [`csrc/neckcompass.c`](csrc/neckcompass.c) into any `ESP32`, `STM32`, `RP2040`, ArduPilot, or bare-metal firmware project (`-std=c99 -Wall -Wextra -pedantic -Werror`, zero FPU, zero `malloc`):
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
make -C csrc clean all test
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
```c
|
|
162
|
+
#include "neckcompass.h"
|
|
163
|
+
|
|
164
|
+
nc_compass_t comp;
|
|
165
|
+
nc_navigator_t nav;
|
|
166
|
+
|
|
167
|
+
void autopilot_init(void) {
|
|
168
|
+
nc_compass_init(&comp);
|
|
169
|
+
nc_compass_reset(&comp, nc_cdeg_to_bam(0));
|
|
170
|
+
nc_navigator_init(&nav, 1500); /* 1.5 m smooth arrival taper */
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* 50 Hz deterministic fixed-point loop (~1.0 us on desktop, < 8 us on ESP32) */
|
|
174
|
+
int32_t autopilot_step_50hz(int32_t gyro_cdeg_s, int32_t mag_cdeg, int32_t speed_mm_s) {
|
|
175
|
+
nc_bam_t heading = nc_compass_update(
|
|
176
|
+
&comp,
|
|
177
|
+
nc_cdeg_to_bam(gyro_cdeg_s),
|
|
178
|
+
20u, /* dt = 20 ms */
|
|
179
|
+
nc_cdeg_to_bam(mag_cdeg), 1u, NC_Q15_ONE,
|
|
180
|
+
0, 0u, 0 /* optional sun/sky sensor */
|
|
181
|
+
);
|
|
182
|
+
nc_navigator_update(&nav, speed_mm_s, 20u, heading);
|
|
183
|
+
return nc_navigator_steering_cmd(&nav, heading, nc_cdeg_to_bam(4500));
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 📊 Honest Empirical Benchmarks (5 Seeds @ `50 Hz`)
|
|
190
|
+
|
|
191
|
+
Reproduce all numbers and regenerate [`assets/neckcompass_benchmarks.png`](assets/neckcompass_benchmarks.png) + [`results/benchmark_summary.json`](results/benchmark_summary.json) locally:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
python scripts/benchmark_neckcompass.py
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
All four estimators receive the **exact same** `50 Hz` sensor streams across 5 seeds (`101–105`): realistic MEMS gyro bias (`~0.9°/s` initial offset + `0.08°/s/√s` random walk + `1.5°/s` white noise) and `3.0°` Gaussian magnetometer noise. The **2-State Circular EKF** is given the exact true clean discrete-time process and measurement noise covariances `(Q, R)` with zero handicap.
|
|
198
|
+
|
|
199
|
+
### 1. Heading Accuracy: Clean Gaussian Noise vs. `70°–150°` Ferromagnetic Spikes
|
|
200
|
+
|
|
201
|
+
Mean ± std across 5 seeds (`180 s` trajectories @ `50 Hz`):
|
|
202
|
+
|
|
203
|
+
| Estimator | **Scenario A: Clean Gaussian RMSE** | **Scenario A: P95** | **Scenario B: Spiky Mag RMSE** | **Scenario B: Spiky Mag P95** | **Scenario B: Max Error** |
|
|
204
|
+
| :--- | :---: | :---: | :---: | :---: | :---: |
|
|
205
|
+
| **`NeckCompass` (`E-PG` 16w)** | `0.43° ± 0.02°` | `0.86° ± 0.03°` | **`1.70° ± 0.18°`** | **`4.27° ± 0.37°`** | **`10.55° ± 1.88°`** |
|
|
206
|
+
| **`2-State Circular EKF`** | **`0.39° ± 0.02°`** *(wins)* | **`0.77° ± 0.03°`** | `17.42° ± 0.93°` | `45.28° ± 2.63°` | `82.53° ± 7.50°` |
|
|
207
|
+
| **`Complementary IMU+Mag`** | `3.64° ± 1.54°` | `5.34° ± 1.80°` | `11.80° ± 0.54°` | `27.77° ± 1.30°` | `47.45° ± 4.87°` |
|
|
208
|
+
| **`Gyro Dead-Reckoning`** | `90.22° ± 32.41°` | `141.70° ± 46.28°` | `84.80° ± 30.74°` | `139.91° ± 53.31°` | `145.98° ± 55.90°` |
|
|
209
|
+
|
|
210
|
+
### 2. Scenario C: `600 s` Outbound Survey + Blind GPS-Blackout Return (`~1,125 m` Total Path)
|
|
211
|
+
|
|
212
|
+
A USV / rover executes a `10-minute` (`900 m`) multi-leg survey in complete GPS blackout (`2%` wheel/water-speed noise + biased MEMS gyro + intermittent `70°–150°` ferromagnetic spikes), then autonomously steers back to `(0, 0)`:
|
|
213
|
+
|
|
214
|
+
| Navigation Stack | **Return-to-Home Miss (`m`)** | **Miss `%` of `1.12 km` Path** | **RAM / State Footprint** | **Step Latency** |
|
|
215
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
216
|
+
| **`NeckCompass` (C99 `Q15` Core)** | **`0.32 m`** *(158 m selftest)* | **`0.20%`** | **`316 B` RAM (`576 B` w/ LUT)** | **`~1.04 us / step`** |
|
|
217
|
+
| **`NeckCompass` (Python `E-PG + CPU4`)** | **`0.84 ± 0.23 m`** | **`0.07% ± 0.02%`** | `3,143 B` | `171.26 us / step` |
|
|
218
|
+
| **`Complementary IMU+Mag + DR`** | `13.31 ± 3.08 m` | `1.19% ± 0.28%` | `~256 B` | `~6.5 us / step` |
|
|
219
|
+
| **`2-State Circular EKF + DR`** | `14.29 ± 6.95 m` | `1.28% ± 0.63%` | `5,792 B` *(Py)* | `13.58 us / step` *(Py)* |
|
|
220
|
+
| **`Gyro Dead-Reckoning`** | `256.74 ± 52.38 m` | `24.16% ± 5.69%` | `~128 B` | `~2.0 us / step` |
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## 🎯 When to Use NeckCompass — and When Not To
|
|
225
|
+
|
|
226
|
+
> [!TIP]
|
|
227
|
+
> **Reach for NeckCompass when:**
|
|
228
|
+
> - **Operating near steel, motors, docks, ship hulls, or power cables**: Intermittent `60°–160°` ferromagnetic spikes yank standard complementary filters and corrupt EKF gyro-bias states; `RingCompass` shunts off-basin anomalies naturally (`1.70°` vs `17.42°` RMSE).
|
|
229
|
+
> - **Surviving GPS blackouts, jamming, or subsea/indoor dropouts**: `FanBodyNavigator` (`CPU4` + `PFL3`/`PFL2`) maintains a bounded, common-mode-cancelled home vector and smooth return controller over `10+ minute` (`> 1 km`) dead-reckoning missions.
|
|
230
|
+
> - **Deploying on tiny FPU-less or ultra-low-power microcontrollers (`$1–$4` MCUs)**: `csrc/neckcompass.c` uses `316 bytes` of RAM, `0` heap allocations, `0` floats, and `~1 us` per step.
|
|
231
|
+
> - **Learning warped harbor or indoor magnetic scenes online**: `PlasticLandmarkRing` unwarps repeatable soft-iron / hard-iron distortions continuously without offline ellipsoid-fitting batch scripts.
|
|
232
|
+
|
|
233
|
+
> [!NOTE]
|
|
234
|
+
> **Stick with a classical EKF / Factor Graph when:**
|
|
235
|
+
> - **Sensors are clean, pre-calibrated, and strictly Gaussian (e.g. open-air UAV far from iron/high-current leads)**: As shown in Scenario A, a properly tuned 2-State Circular EKF achieves slightly lower steady-state RMSE (`0.39°` vs `NeckCompass` `0.43°`) because linear Kalman filtering is minimum-variance optimal under pure Gaussian noise.
|
|
236
|
+
> - **Pure Python ultra-high-rate (`> 10 kHz`) loops without C bindings**: Scalar 2-State Python EKF runs in `~14 us/step` vs `~171 us/step` for 16-wedge NumPy vector ops (`~5.8 kHz`). For `> 5 kHz` embedded or C/C++ loops, compile [`csrc/neckcompass.c`](csrc/neckcompass.c) (`~1.0 us/step`).
|
|
237
|
+
> - **Full 6-DOF visual-inertial SLAM (`SE(3)` pose graphs + loop closure)**: `NeckCompass` is a lightweight 2D/yaw heading & home-vector module (`SO(2)` + `R²`), not a replacement for full 3D LiDAR/camera SLAM when rich 3D visual features and GPU/CPU compute are available.
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## 🗂️ Repository Layout
|
|
242
|
+
|
|
243
|
+
| Path | Contents |
|
|
244
|
+
| :--- | :--- |
|
|
245
|
+
| [`neckcompass/ring_attractor.py`](neckcompass/ring_attractor.py) | `RingCompass` (`N=16`/`32` `E-PG` ring attractor + `P-EN` shifters + multi-cue basin gating) & `PlasticLandmarkRing` (`R-neuron → E-PG` Hebbian/anti-Hebbian distortion learner) |
|
|
246
|
+
| [`neckcompass/path_integrator.py`](neckcompass/path_integrator.py) | `FanBodyNavigator` (`N=16` opponent `CPU4` vector memory + `PFL3`/`PFL2` GPS-denied homebound steering) |
|
|
247
|
+
| [`neckcompass/cli.py`](neckcompass/cli.py) | `neckcompass` CLI (`neckcompass demo`, `neckcompass benchmark-quick`) |
|
|
248
|
+
| [`csrc/`](csrc/) | Zero-heap, zero-float ANSI C99 fixed-point `Q15` core (`neckcompass.h`, `neckcompass.c`, `Makefile`, and `6/6` `neckcompass_selftest.c`) |
|
|
249
|
+
| [`scripts/benchmark_neckcompass.py`](scripts/benchmark_neckcompass.py) | Reproducible 5-seed empirical benchmark & 3-panel hero chart generator |
|
|
250
|
+
| [`results/benchmark_summary.json`](results/benchmark_summary.json) | Exact 5-seed benchmark metrics and C99/Python latency & memory measurements |
|
|
251
|
+
| [`tests/test_neckcompass.py`](tests/test_neckcompass.py) | `19`-test `pytest` suite covering wrap-around continuity, spike rejection, soft-iron plasticity, GPS-denied return, and CLI |
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
## 📚 Scientific Foundations
|
|
256
|
+
|
|
257
|
+
1. **Seelig, J. D. & Jayaraman, V.** (2015). Neural dynamics for landmark orientation and angular path integration. *Nature*, 521, 186–191.
|
|
258
|
+
2. **Stone, T. et al.** (2017). An anatomically constrained model for path integration in the bee brain. *Current Biology*, 27(20), 3069–3085.
|
|
259
|
+
3. **Turner-Evans, D. et al.** (2017). Angular velocity integration in a fly heading circuit. *eLife*, 6, e23496.
|
|
260
|
+
4. **Fisher, Y. E., Lu, J., D'Alessandro, I. & Wilson, R. I.** (2019). Sensorimotor experience remaps visual input to a heading network. *Nature*, 576, 121–125.
|
|
261
|
+
5. **Kim, S. S., Hermundstad, A. M., Romani, S., Abbott, L. F. & Jayaraman, V.** (2019). Generation of stable heading representations in diverse visual scenes. *Nature*, 576, 126–131.
|
|
262
|
+
6. **Lyu, C., Abbott, L. F. & Maimon, G.** (2022). Building an allocentric travelling direction signal via vector computation. *Nature*, 601, 92–97.
|
|
263
|
+
7. **Westeinde, E. A. et al.** (2024). Transforming a head direction signal into a goal-oriented steering command. *Nature*, 626, 819–826.
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## License
|
|
268
|
+
|
|
269
|
+
Released under the [MIT License](LICENSE). Copyright (c) 2026 Amandeep Singh Bains.
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
# NeckCompass (`neckcompass` v1.0.0)
|
|
4
|
+
|
|
5
|
+
**Biophysical *Drosophila* Central Complex ring-attractor compass and GPS-denied path integrator with natural ferromagnetic spike rejection — in Python and zero-heap, zero-float ANSI C99 (`316 B` RAM, `~1 us`/step).**
|
|
6
|
+
|
|
7
|
+
[](https://pypi.org/project/neckcompass/)
|
|
8
|
+
[](https://github.com/indian-aman/neckcompass/releases/tag/v1.0.0)
|
|
9
|
+
[](https://pypi.org/project/neckcompass/)
|
|
10
|
+
[](csrc/)
|
|
11
|
+
[](csrc/tools/neckcompass_selftest.c)
|
|
12
|
+
[](tests/test_neckcompass.py)
|
|
13
|
+
[](LICENSE)
|
|
14
|
+
|
|
15
|
+
[**30-Second Quick Start**](#-30-second-quick-start) ·
|
|
16
|
+
[**Plastic Harbor Calibration**](#2-online-soft-iron--harbor-distortion-learning-plasticlandmarkring) ·
|
|
17
|
+
[**Zero-Float ANSI C99 MCU Core**](#3-zero-heap-zero-float-ansi-c99-core-csrc) ·
|
|
18
|
+
[**Honest Benchmarks**](#-honest-empirical-benchmarks-5-seeds--50-hz) ·
|
|
19
|
+
[**When to Use (and When Not To)**](#-when-to-use-neckcompass--and-when-not-to)
|
|
20
|
+
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<p align="center">
|
|
24
|
+
<img src="https://raw.githubusercontent.com/indian-aman/neckcompass/main/assets/neckcompass_benchmarks.png" alt="NeckCompass v1.0.0 Ring Attractor & Path Integrator Benchmarks" width="100%">
|
|
25
|
+
</p>
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Why NeckCompass?
|
|
30
|
+
|
|
31
|
+
Marine USVs, inspection drones, subsea rovers, and factory AGVs operate around steel ship hulls, sheet-pile docks, high-current motor leads, and buried power cables that inject intermittent **`70°–150°` ferromagnetic heading spikes** lasting `0.5–2.0 s`. Standard linear-Gaussian estimators break down in this regime:
|
|
32
|
+
|
|
33
|
+
- **Gyro Dead-Reckoning** ignores magnetic anomalies completely, but MEMS gyro bias (`~0.8–1.5°/s`) drifts unboundedly (`50°–100°+` within minutes).
|
|
34
|
+
- **2-State Circular Extended Kalman Filters (`[heading, gyro_bias]`)** are minimum-variance optimal under textbook Gaussian noise, **but** a `120°` ferromagnetic anomaly feeds through the Kalman cross-covariance straight into the **gyro bias state**, causing both massive heading yanks (`45°+` P95) and post-spike bias hangovers.
|
|
35
|
+
|
|
36
|
+
**NeckCompass** ports the neural navigation circuit of the fruit-fly (*Drosophila melanogaster*) **Central Complex** into a deterministic software and microcontroller library — **Product #3** in the **[NeckLink](https://github.com/indian-aman/necklink)** fruit-fly brain engineering family:
|
|
37
|
+
|
|
38
|
+
1. **Ellipsoid Body (`E-PG`) Ring Attractor + `Delta7` Inhibition + `P-EN` Shifters (`RingCompass`)**: Maintains a single topological activity bump across `N=16` (or `N=32`) circular wedges with **zero `-π ↔ +π` wrap-around discontinuity** (`< 10⁻¹⁵ rad` harmonic aliasing on even-`N` rings). Asymmetric Left/Right `P-EN` projections integrate IMU yaw rate (`omega_rad_s * dt`), while localized `R-neuron` sensory cues (`mag_heading_rad`, `sun_heading_rad`) undergo cooperative dendritic coincidence gating against the active `E-PG` bump — smoothly anchoring gyro drift inside the attractor basin while naturally shunting **`70°–150°` ferromagnetic spikes** (`10.2×` lower RMSE than 2-State EKF).
|
|
39
|
+
2. **Associative `R-Neuron → E-PG` Synaptic Plasticity (`PlasticLandmarkRing`)**: Movement-gated Hebbian / anti-Hebbian ring weight matrix that learns warped local soft-iron and hard-iron magnetic distortion tables online around steel harbors or indoor rooms — with or without an external sun/visual reference.
|
|
40
|
+
3. **Fan-Shaped Body `CPU4` Path Integrator + `PFL3`/`PFL2` Steering (`FanBodyNavigator`)**: `N=16` push-pull opponent memory columns integrate translational speed along the live `E-PG` compass bump with common-mode cancellation, computing exact `(distance_m, home_bearing_rad)` and `±67.5°` phase-shifted `PFL3` + `180°` `PFL2` motor yaw commands to steer straight home during complete **GPS blackouts** (`0.84 m` miss after a `1,125 m` spiky mission).
|
|
41
|
+
4. **Zero-Heap, Zero-Float ANSI C99 Core (`csrc/`)**: Pure `Q15` / `int32_t` 16-wedge ring attractor + `CPU4`/`PFL3` navigator using **`316 bytes` of struct RAM** (`576 bytes` total including `260 B` Flash LUTs), **zero `malloc`**, **zero floating-point instructions**, and **`~1.0 us` per update** — ready for `$4` `ESP32`, `STM32`, `RP2040`, and flight controllers.
|
|
42
|
+
|
|
43
|
+
```mermaid
|
|
44
|
+
flowchart LR
|
|
45
|
+
IMU["IMU Gyro Yaw Rate (omega_rad_s)"] --> PEN["P-EN Left/Right Velocity Shifters"]
|
|
46
|
+
PEN --> EPG["16-Wedge E-PG Ring Attractor + Delta7 Inhibition"]
|
|
47
|
+
MAG["Spiky Magnetometer / Sun Azimuth"] --> RN["R2 / R4d Ring Neurons (Plastic W + Basin Gate)"]
|
|
48
|
+
RN -->|"Coherent cues anchor drift; 70-150 deg spikes shunted"| EPG
|
|
49
|
+
EPG --> CPU4["16 Opponent CPU4 Memory Columns (+ Speed)"]
|
|
50
|
+
CPU4 --> PFL3["PFL3 (+/-67.5 deg) + PFL2 (180 deg) Steering"]
|
|
51
|
+
PFL3 --> HOME["GPS-Denied Homebound Yaw Command"]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## ⚡ 30-Second Quick Start
|
|
57
|
+
|
|
58
|
+
### 1. Python (`pip install neckcompass`)
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
pip install neckcompass
|
|
62
|
+
# Or directly from source:
|
|
63
|
+
pip install git+https://github.com/indian-aman/neckcompass.git
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Fuse a biased IMU gyro with a spiky magnetometer and steer home through a complete GPS blackout in 14 lines:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
import math
|
|
70
|
+
from neckcompass import RingCompass, FanBodyNavigator
|
|
71
|
+
|
|
72
|
+
compass = RingCompass(n_wedges=16, initial_heading_rad=0.0)
|
|
73
|
+
nav = FanBodyNavigator(n_columns=16, compass=compass)
|
|
74
|
+
|
|
75
|
+
# Outbound step @ 50 Hz (dt = 0.02 s): 125-deg hull spike is rejected automatically!
|
|
76
|
+
heading_rad, dist_m, home_bearing_rad = nav.step_with_compass(
|
|
77
|
+
omega_rad_s=0.15,
|
|
78
|
+
speed_m_s=1.5,
|
|
79
|
+
dt=0.02,
|
|
80
|
+
mag_heading_rad=math.radians(125.0), # Transient ferromagnetic spike -> gated out
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# During GPS blackout, generate bounded [-0.8, +0.8] rad/s yaw command back to (0, 0):
|
|
84
|
+
yaw_rate_cmd = nav.steering_cmd(max_turn_rate=0.8)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Run the built-in CLI demo or head-to-head quick benchmark from your terminal:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
neckcompass demo
|
|
91
|
+
neckcompass benchmark-quick
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
### 2. Online Soft-Iron / Harbor Distortion Learning (`PlasticLandmarkRing`)
|
|
97
|
+
|
|
98
|
+
When operating repeatedly inside a steel marina slip or indoor robot bay with static nonlinear magnetic warp (`+20° sin(ψ) + 16° sin(2ψ)`), `PlasticLandmarkRing` learns the `R-neuron → E-PG` unwarping matrix online:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from neckcompass import PlasticLandmarkRing
|
|
102
|
+
|
|
103
|
+
plastic = PlasticLandmarkRing(n_wedges=16, n_ring_neurons=32)
|
|
104
|
+
|
|
105
|
+
# Self-supervised during normal turns (or pass reference_heading_rad=sun_rad when sky is visible)
|
|
106
|
+
corrected_heading = plastic.update(
|
|
107
|
+
omega_rad_s=imu_gz,
|
|
108
|
+
dt=0.02,
|
|
109
|
+
raw_mag_heading_rad=raw_mag_rad,
|
|
110
|
+
learn=True,
|
|
111
|
+
)
|
|
112
|
+
unwarped_mag_rad, confidence = plastic.map_raw_heading(raw_mag_rad)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### 3. Zero-Heap, Zero-Float ANSI C99 Core (`csrc/`)
|
|
118
|
+
|
|
119
|
+
Drop [`csrc/neckcompass.h`](csrc/neckcompass.h) and [`csrc/neckcompass.c`](csrc/neckcompass.c) into any `ESP32`, `STM32`, `RP2040`, ArduPilot, or bare-metal firmware project (`-std=c99 -Wall -Wextra -pedantic -Werror`, zero FPU, zero `malloc`):
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
make -C csrc clean all test
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
```c
|
|
126
|
+
#include "neckcompass.h"
|
|
127
|
+
|
|
128
|
+
nc_compass_t comp;
|
|
129
|
+
nc_navigator_t nav;
|
|
130
|
+
|
|
131
|
+
void autopilot_init(void) {
|
|
132
|
+
nc_compass_init(&comp);
|
|
133
|
+
nc_compass_reset(&comp, nc_cdeg_to_bam(0));
|
|
134
|
+
nc_navigator_init(&nav, 1500); /* 1.5 m smooth arrival taper */
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/* 50 Hz deterministic fixed-point loop (~1.0 us on desktop, < 8 us on ESP32) */
|
|
138
|
+
int32_t autopilot_step_50hz(int32_t gyro_cdeg_s, int32_t mag_cdeg, int32_t speed_mm_s) {
|
|
139
|
+
nc_bam_t heading = nc_compass_update(
|
|
140
|
+
&comp,
|
|
141
|
+
nc_cdeg_to_bam(gyro_cdeg_s),
|
|
142
|
+
20u, /* dt = 20 ms */
|
|
143
|
+
nc_cdeg_to_bam(mag_cdeg), 1u, NC_Q15_ONE,
|
|
144
|
+
0, 0u, 0 /* optional sun/sky sensor */
|
|
145
|
+
);
|
|
146
|
+
nc_navigator_update(&nav, speed_mm_s, 20u, heading);
|
|
147
|
+
return nc_navigator_steering_cmd(&nav, heading, nc_cdeg_to_bam(4500));
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 📊 Honest Empirical Benchmarks (5 Seeds @ `50 Hz`)
|
|
154
|
+
|
|
155
|
+
Reproduce all numbers and regenerate [`assets/neckcompass_benchmarks.png`](assets/neckcompass_benchmarks.png) + [`results/benchmark_summary.json`](results/benchmark_summary.json) locally:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
python scripts/benchmark_neckcompass.py
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
All four estimators receive the **exact same** `50 Hz` sensor streams across 5 seeds (`101–105`): realistic MEMS gyro bias (`~0.9°/s` initial offset + `0.08°/s/√s` random walk + `1.5°/s` white noise) and `3.0°` Gaussian magnetometer noise. The **2-State Circular EKF** is given the exact true clean discrete-time process and measurement noise covariances `(Q, R)` with zero handicap.
|
|
162
|
+
|
|
163
|
+
### 1. Heading Accuracy: Clean Gaussian Noise vs. `70°–150°` Ferromagnetic Spikes
|
|
164
|
+
|
|
165
|
+
Mean ± std across 5 seeds (`180 s` trajectories @ `50 Hz`):
|
|
166
|
+
|
|
167
|
+
| Estimator | **Scenario A: Clean Gaussian RMSE** | **Scenario A: P95** | **Scenario B: Spiky Mag RMSE** | **Scenario B: Spiky Mag P95** | **Scenario B: Max Error** |
|
|
168
|
+
| :--- | :---: | :---: | :---: | :---: | :---: |
|
|
169
|
+
| **`NeckCompass` (`E-PG` 16w)** | `0.43° ± 0.02°` | `0.86° ± 0.03°` | **`1.70° ± 0.18°`** | **`4.27° ± 0.37°`** | **`10.55° ± 1.88°`** |
|
|
170
|
+
| **`2-State Circular EKF`** | **`0.39° ± 0.02°`** *(wins)* | **`0.77° ± 0.03°`** | `17.42° ± 0.93°` | `45.28° ± 2.63°` | `82.53° ± 7.50°` |
|
|
171
|
+
| **`Complementary IMU+Mag`** | `3.64° ± 1.54°` | `5.34° ± 1.80°` | `11.80° ± 0.54°` | `27.77° ± 1.30°` | `47.45° ± 4.87°` |
|
|
172
|
+
| **`Gyro Dead-Reckoning`** | `90.22° ± 32.41°` | `141.70° ± 46.28°` | `84.80° ± 30.74°` | `139.91° ± 53.31°` | `145.98° ± 55.90°` |
|
|
173
|
+
|
|
174
|
+
### 2. Scenario C: `600 s` Outbound Survey + Blind GPS-Blackout Return (`~1,125 m` Total Path)
|
|
175
|
+
|
|
176
|
+
A USV / rover executes a `10-minute` (`900 m`) multi-leg survey in complete GPS blackout (`2%` wheel/water-speed noise + biased MEMS gyro + intermittent `70°–150°` ferromagnetic spikes), then autonomously steers back to `(0, 0)`:
|
|
177
|
+
|
|
178
|
+
| Navigation Stack | **Return-to-Home Miss (`m`)** | **Miss `%` of `1.12 km` Path** | **RAM / State Footprint** | **Step Latency** |
|
|
179
|
+
| :--- | :---: | :---: | :---: | :---: |
|
|
180
|
+
| **`NeckCompass` (C99 `Q15` Core)** | **`0.32 m`** *(158 m selftest)* | **`0.20%`** | **`316 B` RAM (`576 B` w/ LUT)** | **`~1.04 us / step`** |
|
|
181
|
+
| **`NeckCompass` (Python `E-PG + CPU4`)** | **`0.84 ± 0.23 m`** | **`0.07% ± 0.02%`** | `3,143 B` | `171.26 us / step` |
|
|
182
|
+
| **`Complementary IMU+Mag + DR`** | `13.31 ± 3.08 m` | `1.19% ± 0.28%` | `~256 B` | `~6.5 us / step` |
|
|
183
|
+
| **`2-State Circular EKF + DR`** | `14.29 ± 6.95 m` | `1.28% ± 0.63%` | `5,792 B` *(Py)* | `13.58 us / step` *(Py)* |
|
|
184
|
+
| **`Gyro Dead-Reckoning`** | `256.74 ± 52.38 m` | `24.16% ± 5.69%` | `~128 B` | `~2.0 us / step` |
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 🎯 When to Use NeckCompass — and When Not To
|
|
189
|
+
|
|
190
|
+
> [!TIP]
|
|
191
|
+
> **Reach for NeckCompass when:**
|
|
192
|
+
> - **Operating near steel, motors, docks, ship hulls, or power cables**: Intermittent `60°–160°` ferromagnetic spikes yank standard complementary filters and corrupt EKF gyro-bias states; `RingCompass` shunts off-basin anomalies naturally (`1.70°` vs `17.42°` RMSE).
|
|
193
|
+
> - **Surviving GPS blackouts, jamming, or subsea/indoor dropouts**: `FanBodyNavigator` (`CPU4` + `PFL3`/`PFL2`) maintains a bounded, common-mode-cancelled home vector and smooth return controller over `10+ minute` (`> 1 km`) dead-reckoning missions.
|
|
194
|
+
> - **Deploying on tiny FPU-less or ultra-low-power microcontrollers (`$1–$4` MCUs)**: `csrc/neckcompass.c` uses `316 bytes` of RAM, `0` heap allocations, `0` floats, and `~1 us` per step.
|
|
195
|
+
> - **Learning warped harbor or indoor magnetic scenes online**: `PlasticLandmarkRing` unwarps repeatable soft-iron / hard-iron distortions continuously without offline ellipsoid-fitting batch scripts.
|
|
196
|
+
|
|
197
|
+
> [!NOTE]
|
|
198
|
+
> **Stick with a classical EKF / Factor Graph when:**
|
|
199
|
+
> - **Sensors are clean, pre-calibrated, and strictly Gaussian (e.g. open-air UAV far from iron/high-current leads)**: As shown in Scenario A, a properly tuned 2-State Circular EKF achieves slightly lower steady-state RMSE (`0.39°` vs `NeckCompass` `0.43°`) because linear Kalman filtering is minimum-variance optimal under pure Gaussian noise.
|
|
200
|
+
> - **Pure Python ultra-high-rate (`> 10 kHz`) loops without C bindings**: Scalar 2-State Python EKF runs in `~14 us/step` vs `~171 us/step` for 16-wedge NumPy vector ops (`~5.8 kHz`). For `> 5 kHz` embedded or C/C++ loops, compile [`csrc/neckcompass.c`](csrc/neckcompass.c) (`~1.0 us/step`).
|
|
201
|
+
> - **Full 6-DOF visual-inertial SLAM (`SE(3)` pose graphs + loop closure)**: `NeckCompass` is a lightweight 2D/yaw heading & home-vector module (`SO(2)` + `R²`), not a replacement for full 3D LiDAR/camera SLAM when rich 3D visual features and GPU/CPU compute are available.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## 🗂️ Repository Layout
|
|
206
|
+
|
|
207
|
+
| Path | Contents |
|
|
208
|
+
| :--- | :--- |
|
|
209
|
+
| [`neckcompass/ring_attractor.py`](neckcompass/ring_attractor.py) | `RingCompass` (`N=16`/`32` `E-PG` ring attractor + `P-EN` shifters + multi-cue basin gating) & `PlasticLandmarkRing` (`R-neuron → E-PG` Hebbian/anti-Hebbian distortion learner) |
|
|
210
|
+
| [`neckcompass/path_integrator.py`](neckcompass/path_integrator.py) | `FanBodyNavigator` (`N=16` opponent `CPU4` vector memory + `PFL3`/`PFL2` GPS-denied homebound steering) |
|
|
211
|
+
| [`neckcompass/cli.py`](neckcompass/cli.py) | `neckcompass` CLI (`neckcompass demo`, `neckcompass benchmark-quick`) |
|
|
212
|
+
| [`csrc/`](csrc/) | Zero-heap, zero-float ANSI C99 fixed-point `Q15` core (`neckcompass.h`, `neckcompass.c`, `Makefile`, and `6/6` `neckcompass_selftest.c`) |
|
|
213
|
+
| [`scripts/benchmark_neckcompass.py`](scripts/benchmark_neckcompass.py) | Reproducible 5-seed empirical benchmark & 3-panel hero chart generator |
|
|
214
|
+
| [`results/benchmark_summary.json`](results/benchmark_summary.json) | Exact 5-seed benchmark metrics and C99/Python latency & memory measurements |
|
|
215
|
+
| [`tests/test_neckcompass.py`](tests/test_neckcompass.py) | `19`-test `pytest` suite covering wrap-around continuity, spike rejection, soft-iron plasticity, GPS-denied return, and CLI |
|
|
216
|
+
|
|
217
|
+
---
|
|
218
|
+
|
|
219
|
+
## 📚 Scientific Foundations
|
|
220
|
+
|
|
221
|
+
1. **Seelig, J. D. & Jayaraman, V.** (2015). Neural dynamics for landmark orientation and angular path integration. *Nature*, 521, 186–191.
|
|
222
|
+
2. **Stone, T. et al.** (2017). An anatomically constrained model for path integration in the bee brain. *Current Biology*, 27(20), 3069–3085.
|
|
223
|
+
3. **Turner-Evans, D. et al.** (2017). Angular velocity integration in a fly heading circuit. *eLife*, 6, e23496.
|
|
224
|
+
4. **Fisher, Y. E., Lu, J., D'Alessandro, I. & Wilson, R. I.** (2019). Sensorimotor experience remaps visual input to a heading network. *Nature*, 576, 121–125.
|
|
225
|
+
5. **Kim, S. S., Hermundstad, A. M., Romani, S., Abbott, L. F. & Jayaraman, V.** (2019). Generation of stable heading representations in diverse visual scenes. *Nature*, 576, 126–131.
|
|
226
|
+
6. **Lyu, C., Abbott, L. F. & Maimon, G.** (2022). Building an allocentric travelling direction signal via vector computation. *Nature*, 601, 92–97.
|
|
227
|
+
7. **Westeinde, E. A. et al.** (2024). Transforming a head direction signal into a goal-oriented steering command. *Nature*, 626, 819–826.
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## License
|
|
232
|
+
|
|
233
|
+
Released under the [MIT License](LICENSE). Copyright (c) 2026 Amandeep Singh Bains.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
CC ?= gcc
|
|
2
|
+
CFLAGS ?= -O2 -std=c99 -Wall -Wextra -pedantic -Werror -I.
|
|
3
|
+
BUILD ?= build
|
|
4
|
+
|
|
5
|
+
CORE_SRCS := neckcompass.c
|
|
6
|
+
|
|
7
|
+
.PHONY: all test clean
|
|
8
|
+
|
|
9
|
+
all: $(BUILD)/neckcompass_selftest
|
|
10
|
+
|
|
11
|
+
$(BUILD):
|
|
12
|
+
mkdir -p $(BUILD)
|
|
13
|
+
|
|
14
|
+
$(BUILD)/neckcompass_selftest: tools/neckcompass_selftest.c $(CORE_SRCS) neckcompass.h | $(BUILD)
|
|
15
|
+
$(CC) $(CFLAGS) -o $@ tools/neckcompass_selftest.c $(CORE_SRCS)
|
|
16
|
+
|
|
17
|
+
test: $(BUILD)/neckcompass_selftest
|
|
18
|
+
./$(BUILD)/neckcompass_selftest
|
|
19
|
+
|
|
20
|
+
clean:
|
|
21
|
+
rm -rf $(BUILD)
|