Skip to content

feat(env): extend static Fick gradient geometry to the predator-sulfolipid field - #228

Merged
chrisjz merged 6 commits into
mainfrom
openspec/extend-fick-chemical-fields
Jun 13, 2026
Merged

feat(env): extend static Fick gradient geometry to the predator-sulfolipid field#228
chrisjz merged 6 commits into
mainfrom
openspec/extend-fick-chemical-fields

Conversation

@chrisjz

@chrisjz chrisjz commented Jun 13, 2026

Copy link
Copy Markdown
Member

Summary

Implements + archives extend-fick-chemical-fields (T7.prep.fick_chemical_fields, Tier 2 fidelity). T6 wired the static Fick (Gaussian) gradient kernel on the food field only and explicitly excluded the predator field "(predators-on-continuous not yet exercised)." That premise no longer holds — add-continuous-predator-kinematics (#226) made predators continuous. The predator sulfolipid distal-chemo field is a genuinely diffusing chemical (Liu et al. 2018), so it now shares the same physically-faithful Fick geometry as food, with its own diffusion coefficient.

This closes the last fidelity-coherence gap on the diffusing chemical fields: every diffusing chemical the worm senses can now use the diffusion-faithful kernel, strengthening the continuous-physics platform claim and the T7 real-worm-validation defensibility.

What changed

  • Shared kernel: new module-level field_magnitude(distance, *, mode, decay, strength, fick_length) encodes the exp-vs-Fick dispatch once. _food_field_magnitude now delegates to it (numerically identical — IEEE-754-verified); new _predator_field_magnitude calls it with the predator params.
  • Per-signal D: PredatorParams/PredatorConfig gain gradient_field_mode (exponential default | fick), diffusion_coefficient, assay_time, fick_length() — predator D is independent of food D.
  • Routing: get_predator_concentration / get_predator_sulfolipid_concentration (alias) / _compute_predator_gradient_vector route through the mode-aware magnitude.
  • Scope: temperature + the oxygen base gradient stay out (imposed/boundary gradients, not point-source diffusion); pheromone Fick + per-signal-D literature calibration are noted follow-ups (the latter is phase-7 depth — this ships the mechanism + sensible defaults, not calibrated D values).

Byte-stability (the gate)

Exponential stays the default for every field → existing grid + continuous configs are byte-identical. The branch review confirmed at hex level: food + predator both modes match the prior inline kernels; the distance==0 special-case removal is byte-identical (field_magnitude(0) == strength); the repulsive sign + guard are preserved.

Validation

  • +7 predator Fick unit tests (Gaussian; differs from exp; source = strength; per-signal D independent of food; exponential-default byte-stability; fick_length).
  • Food Fick + predator byte-equivalence suites green; full pre-commit -a: 3885 passed, ruff + pyright clean.
  • End-to-end headless smoke (tmp/verify_predator_fick.yml): predators with gradient_field_mode: fick, D=3.0, predator_chemosensation_klinotaxis sensing — runs clean, exercising the config→env→Fick-predator-field→sensing path.

Notes

  • Archived: the MODIFIED Fick requirement is applied to the chemical-gradient-fidelity spec (predator-sulfolipid in; the exclusion clause retired).
  • Non-blocking: a fick-mode fick_length==0 div-by-zero is unreachable (pydantic enforces D>0/assay_time>0; decay validated >0).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Predator chemical signal can use a Fick-shaped diffusion mode (selectable per signal); exponential remains the default.
    • Per-signal diffusion parameters and assay-time controls added to configure Fick spread.
  • Documentation

    • Specs and design docs updated to describe Fick-mode applicability, defaults, and migration notes.
  • Tests

    • Added tests validating predator Fick vs exponential behavior, parameter handling, and kernel dispatch.

chrisjz and others added 5 commits June 13, 2026 14:19
…idelity)

Extend the static Fick gradient geometry (food-only at T6) to the predator-
sulfolipid distal-chemo field with its own per-signal diffusion coefficient,
now that predators are continuous (#226). Closes the last fidelity-coherence
gap on the diffusing chemical fields.

Authored proposal/design/specs/tasks; modifies the chemical-gradient-fidelity
capability (retires its "predator field is out of scope, retains exponential"
clause). Exponential stays the default -> byte-stable. A shared mode-aware
kernel helper backs both food + predator (food numerically identical).
Temperature + the oxygen base gradient stay out (imposed/boundary, not
point-source diffusion); pheromone is a documented follow-up. Validated --strict.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lipid field

Implements extend-fick-chemical-fields. Predators are continuous (#226), so the
predator distal-chemo (sulfolipid) field — a diffusing chemical — gains the same
Fick mode + its own diffusion coefficient as the food field.

- Shared module-level `field_magnitude(distance, *, mode, decay, strength,
  fick_length)` kernel; `_food_field_magnitude` delegates (numerically identical),
  new `_predator_field_magnitude` for the predator field.
- PredatorParams/PredatorConfig gain `gradient_field_mode` (exponential default |
  fick), `diffusion_coefficient`, `assay_time`, `fick_length()` — predator D is
  independent of food D.
- get_predator_concentration / get_predator_sulfolipid_concentration (alias) /
  _compute_predator_gradient_vector route through the mode-aware magnitude.
- Exponential stays the default -> byte-stable (verified). Temperature + oxygen
  base gradient stay out (imposed, not diffusion); pheromone is a follow-up.
- +7 predator Fick tests (Gaussian; differs from exp; source=strength; per-signal
  D independent of food; exponential-default byte-stability; fick_length).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…_chemical_fields

Tick §1-§4 + 5.1/5.3/5.4 (implementation + tests + validation done; full suite
3885 passed; end-to-end predator-Fick smoke clean via tmp/verify_predator_fick.yml).
Tick phase6-tracking T7.prep.fick_chemical_fields (predator-sulfolipid done;
pheromone + per-signal-D calibration noted as follow-ups). 5.2 (full pre-commit
-a) left for the pre-push gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drift fix: task 3.2 as-built note (distance==0 special-case removed; boundary
preserved implicitly by the kernel). ruff-format PEP8 blank line after the
field_magnitude helper. Tick 5.2 (full pre-commit -a green: ruff/pyright + full
suite pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Applies the MODIFIED Fick requirement to the chemical-gradient-fidelity spec
(predator-sulfolipid in; the "predator field out of scope, retains exponential"
exclusion retired; temperature + oxygen base gradient explicitly excluded as
imposed/boundary). --specs 41 passed; no duplicate requirement headers.

Branch review clean: food + predator field byte-stability IEEE-754-identical at
the exponential default for both modes; distance==0 removal byte-identical;
repulsive sign + guard preserved; callers unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f9efb8f3-2a82-44ed-8ab1-b0c2ac2b34f3

📥 Commits

Reviewing files that changed from the base of the PR and between 34ff739 and 574fd49.

📒 Files selected for processing (2)
  • packages/quantum-nematode/quantumnematode/env/env.py
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
  • packages/quantum-nematode/quantumnematode/env/env.py

📝 Walkthrough

Walkthrough

This PR extends the predator-sulfolipid chemical gradient field to support Fick (Gaussian) diffusion kernel geometry alongside the existing exponential decay. A shared mode-aware field_magnitude() helper dispatches between exponential and Fick formulations, both food and predator parameters gain field-mode configuration and diffusion settings, the config schema is extended, environment routing is refactored to use the shared helper, and comprehensive tests verify mode switching and byte-stable defaults.

Changes

Predator Fick Gradient Extension

Layer / File(s) Summary
Specification and design documentation
openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/.openspec.yaml, openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/design.md, openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/proposal.md, openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/specs/chemical-gradient-fidelity/spec.md, openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/tasks.md, openspec/changes/phase6-tracking/tasks.md, openspec/specs/chemical-gradient-fidelity/spec.md
Design and proposal documents define the Fick mode contract, per-signal diffusion parameters (diffusion_coefficient, assay_time, fick_length()), shared kernel approach, scope constraints (diffusing chemicals only; boundary fields excluded), and implementation checklist. Phase6 tracking marked predator Fick task complete.
Shared kernel helper and parameter types
packages/quantum-nematode/quantumnematode/env/env.py
New field_magnitude() helper computes per-source field strength for exponential and Fick/Gaussian modes (returns strength at distance == 0); ForagingParams/PredatorParams gain gradient_field_mode, diffusion_coefficient, assay_time, and fick_length() with validation and fallback to gradient_decay_constant.
Configuration schema extension
packages/quantum-nematode/quantumnematode/utils/config_loader.py
PredatorConfig YAML schema extended with gradient_field_mode (default "exponential"), diffusion_coefficient (optional, positive), and assay_time (positive); to_params() forwards these into PredatorParams.
Environment implementation and routing
packages/quantum-nematode/quantumnematode/env/env.py
_food_field_magnitude() and _predator_field_magnitude() refactored to delegate to field_magnitude(); predator gradient superposition and get_predator_concentration() updated to use mode-aware magnitudes and the explicit distance == 0 special-case removed.
Test suite and validation
packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
New TestPredatorFickGradientGeometry verifies exponential (byte-stable) and Fick gradient modes, asserts they differ at equal distances, confirms on-source saturation, validates per-signal diffusion independence and fick_length() behavior, and checks unknown-mode error handling.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • SyntheticBrains/nematode#226: Touches the same predator concentration/gradient computation paths and continuous-coordinate routing that this change refactors.
  • SyntheticBrains/nematode#219: Prior work on ForagingParams.gradient_field_mode / Fick kernel for food that this PR generalizes and reuses for predator fields.

Poem

A rabbit hops through Fick and Gaussian bloom,
Predators plume where kernels now resume,
Diffusion, assay, and modes align,
Shared helpers route each gradient line,
Exponential keeps the bytes in tune. 🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: extending static Fick gradient geometry to the predator-sulfolipid field, which is the core feature implemented across the codebase changes.
Docstring Coverage ✅ Passed Docstring coverage is 95.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch openspec/extend-fick-chemical-fields

Comment @coderabbitai help to get the list of available commands and usage tips.

@chrisjz
chrisjz marked this pull request as ready for review June 13, 2026 04:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/quantum-nematode/quantumnematode/env/env.py`:
- Around line 73-95: The function field_magnitude currently treats any
non-"fick" mode as exponential; change field_magnitude to validate mode
explicitly: if mode == "fick" compute the Fick kernel, elif mode ==
"exponential" compute the exponential kernel, else raise a ValueError listing
the allowed modes (e.g. "exponential" and "fick") and include the provided mode
in the message; update only the body of field_magnitude to perform this explicit
dispatch/validation.
- Around line 305-318: The fick_length method must validate inputs to avoid
zero/NaN diffusion lengths: inside PredatorForagingParams.fick_length, if
diffusion_coefficient is not None check that diffusion_coefficient > 0 and
assay_time > 0 and raise a ValueError with a clear message if not; otherwise
(when falling back to gradient_decay_constant) validate gradient_decay_constant
> 0 and raise a ValueError if it is non-positive; keep the existing sqrt
computation unchanged when values are valid.

In `@packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py`:
- Around line 288-368: The new test functions lack explicit return annotations;
update each test definition—test_exponential_default_byte_stable,
test_fick_mode_uses_gaussian_kernel, test_fick_differs_from_exponential,
test_fick_at_source_returns_strength,
test_per_signal_diffusion_coefficient_independent_of_food, and
test_predator_fick_length—to include an explicit -> None return type (e.g., def
test_name(self) -> None:) so they comply with the repository typing rule for
Python files.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3cc5c6ef-a5fd-400e-aef0-21f6341dac22

📥 Commits

Reviewing files that changed from the base of the PR and between d01d053 and 34ff739.

📒 Files selected for processing (10)
  • openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/.openspec.yaml
  • openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/design.md
  • openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/proposal.md
  • openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/specs/chemical-gradient-fidelity/spec.md
  • openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/tasks.md
  • openspec/changes/phase6-tracking/tasks.md
  • openspec/specs/chemical-gradient-fidelity/spec.md
  • packages/quantum-nematode/quantumnematode/env/env.py
  • packages/quantum-nematode/quantumnematode/utils/config_loader.py
  • packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py

Comment thread packages/quantum-nematode/quantumnematode/env/env.py Outdated
Comment thread packages/quantum-nematode/quantumnematode/env/env.py
Comment on lines +288 to +368
def test_exponential_default_byte_stable(self):
"""Default (exponential) predator field matches the prior inline kernel exactly."""
env = self._env(
PredatorParams(
enabled=True,
count=1,
gradient_decay_constant=8.0,
gradient_strength=1.0,
),
)
self._place_one(env, (30, 20)) # distance 10 east of (20, 20)
expected = float(np.tanh(1.0 * np.exp(-10.0 / 8.0) * 1.0))
assert env.get_predator_concentration((20, 20)) == expected

def test_fick_mode_uses_gaussian_kernel(self):
"""Fick mode evaluates strength * exp(-(r/L)**2) on the predator field."""
env = self._env(
PredatorParams(
enabled=True,
count=1,
gradient_decay_constant=8.0,
gradient_strength=1.0,
gradient_field_mode="fick",
),
)
self._place_one(env, (30, 20))
expected = float(np.tanh(1.0 * np.exp(-((10.0 / 8.0) ** 2)) * 1.0))
assert env.get_predator_concentration((20, 20)) == expected

def test_fick_differs_from_exponential(self):
"""Fick and exponential predator fields differ at the same distance."""
common = {
"enabled": True,
"count": 1,
"gradient_decay_constant": 8.0,
"gradient_strength": 1.0,
}
exp_env = self._env(PredatorParams(**common))
fick_env = self._env(PredatorParams(**common, gradient_field_mode="fick"))
self._place_one(exp_env, (30, 20))
self._place_one(fick_env, (30, 20))
assert exp_env.get_predator_concentration(
(20, 20),
) != fick_env.get_predator_concentration((20, 20))

def test_fick_at_source_returns_strength(self):
"""At distance 0 the Fick predator kernel returns the configured strength."""
env = self._env(
PredatorParams(
enabled=True,
count=1,
gradient_strength=1.0,
gradient_field_mode="fick",
),
)
self._place_one(env, (20, 20)) # on the query point
assert env.get_predator_concentration((20, 20)) == float(np.tanh(1.0))

def test_per_signal_diffusion_coefficient_independent_of_food(self):
"""The predator field uses its OWN D (sqrt(4 D t)), not the food field's scale."""
env = self._env(
PredatorParams(
enabled=True,
count=1,
gradient_decay_constant=8.0,
gradient_strength=1.0,
gradient_field_mode="fick",
diffusion_coefficient=2.0,
),
)
self._place_one(env, (30, 20))
length = (4.0 * 2.0 * 1.0) ** 0.5 # predator's own L = 2.828, not the decay 8.0
expected = float(np.tanh(np.exp(-((10.0 / length) ** 2))))
assert env.get_predator_concentration((20, 20)) == expected

def test_predator_fick_length(self):
"""PredatorParams.fick_length(): sqrt(4 D t) when D set; decay fallback otherwise."""
with_d = PredatorParams(diffusion_coefficient=9.0, assay_time=1.0)
assert with_d.fick_length() == pytest.approx((4.0 * 9.0 * 1.0) ** 0.5)
assert PredatorParams(gradient_decay_constant=8.0).fick_length() == 8.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit -> None return annotations to the new test methods.

On Line 288, Line 302, Line 317, Line 333, Line 346, and Line 363, the new test_* methods
omit explicit return types. Please annotate them with -> None to match the repository typing
rule.

Suggested patch
-    def test_exponential_default_byte_stable(self):
+    def test_exponential_default_byte_stable(self) -> None:
@@
-    def test_fick_mode_uses_gaussian_kernel(self):
+    def test_fick_mode_uses_gaussian_kernel(self) -> None:
@@
-    def test_fick_differs_from_exponential(self):
+    def test_fick_differs_from_exponential(self) -> None:
@@
-    def test_fick_at_source_returns_strength(self):
+    def test_fick_at_source_returns_strength(self) -> None:
@@
-    def test_per_signal_diffusion_coefficient_independent_of_food(self):
+    def test_per_signal_diffusion_coefficient_independent_of_food(self) -> None:
@@
-    def test_predator_fick_length(self):
+    def test_predator_fick_length(self) -> None:

As per coding guidelines, **/*.py requires “comprehensive type annotations in Python code”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py` around
lines 288 - 368, The new test functions lack explicit return annotations; update
each test definition—test_exponential_default_byte_stable,
test_fick_mode_uses_gaussian_kernel, test_fick_differs_from_exponential,
test_fick_at_source_returns_strength,
test_per_signal_diffusion_coefficient_independent_of_food, and
test_predator_fick_length—to include an explicit -> None return type (e.g., def
test_name(self) -> None:) so they comply with the repository typing rule for
Python files.

Source: Coding guidelines

@codecov

codecov Bot commented Jun 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.33333% with 6 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ckages/quantum-nematode/quantumnematode/env/env.py 81.81% 4 Missing and 2 partials ⚠️

📢 Thoughts on this report? Let us know!

…k_length validation

Two valid review findings (defensive robustness):

- field_magnitude no longer silently treats any non-"fick" mode as exponential:
  explicit fick/exponential dispatch, else ValueError listing the allowed modes
  and the provided mode.
- fick_length (food + predator) validates its inputs: raises on
  diffusion_coefficient<=0 / assay_time<=0 (when D set), else gradient_decay_
  constant<=0. Belt-and-suspenders at the data layer — decay>0 is already
  enforced at env init (env.py:1486/1492) and D/assay by the pydantic config
  (gt=0); the real gap is direct dataclass construction bypassing pydantic.

+2 regression tests (fick_length non-positive inputs; field_magnitude unknown
mode). Skipped the test '-> None' nitpick: the repo's ruff config exempts tests
from ANN rules (pyproject.toml) and the sibling Fick tests carry none.

Full suite: 3887 passed. ruff + pyright clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@chrisjz

chrisjz commented Jun 13, 2026

Copy link
Copy Markdown
Member Author

Review addressed (commit 574fd49)

Fixed (2 valid findings):

  1. field_magnitude silent exp-fallback → explicit fick/exponential dispatch; an unrecognised mode now raises ValueError naming the allowed modes and the provided mode.
  2. fick_length input validation → both the food and predator fick_length raise on diffusion_coefficient ≤ 0 / assay_time ≤ 0 (when D is set), else on gradient_decay_constant ≤ 0. This is data-layer defense-in-depth: decay > 0 is already enforced at env-init (env.py:1486/1492) and D/assay_time by the pydantic config (Field(gt=0.0)), so the genuine gap it closes is direct dataclass construction that bypasses pydantic.

+2 regression tests (fick_length non-positive inputs; field_magnitude unknown mode). Full suite 3887 passed; ruff + pyright clean.

Skipped (1, with reason): the test -> None return annotations. The repo's ruff config exempts test files from the annotation rules (pyproject.toml"/**/tests/**/*.py" = ["ANN", …]), and the sibling food-Fick tests in the same file carry no -> None. Adding them only to the new tests would be inconsistent with the file + the repo convention, and ruff/pyright already pass.

@chrisjz
chrisjz merged commit 565d544 into main Jun 13, 2026
3 checks passed
@chrisjz
chrisjz deleted the openspec/extend-fick-chemical-fields branch June 13, 2026 05:56
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.

1 participant