feat(env): extend static Fick gradient geometry to the predator-sulfolipid field - #228
Conversation
…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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR extends the predator-sulfolipid chemical gradient field to support Fick (Gaussian) diffusion kernel geometry alongside the existing exponential decay. A shared mode-aware ChangesPredator Fick Gradient Extension
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (10)
openspec/changes/archive/2026-06-13-extend-fick-chemical-fields/.openspec.yamlopenspec/changes/archive/2026-06-13-extend-fick-chemical-fields/design.mdopenspec/changes/archive/2026-06-13-extend-fick-chemical-fields/proposal.mdopenspec/changes/archive/2026-06-13-extend-fick-chemical-fields/specs/chemical-gradient-fidelity/spec.mdopenspec/changes/archive/2026-06-13-extend-fick-chemical-fields/tasks.mdopenspec/changes/phase6-tracking/tasks.mdopenspec/specs/chemical-gradient-fidelity/spec.mdpackages/quantum-nematode/quantumnematode/env/env.pypackages/quantum-nematode/quantumnematode/utils/config_loader.pypackages/quantum-nematode/tests/quantumnematode_tests/env/test_env.py
| 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 | ||
|
|
There was a problem hiding this comment.
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 Report❌ Patch coverage is
📢 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>
Review addressed (commit 574fd49)Fixed (2 valid findings):
+2 regression tests ( Skipped (1, with reason): the test |
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
field_magnitude(distance, *, mode, decay, strength, fick_length)encodes the exp-vs-Fick dispatch once._food_field_magnitudenow delegates to it (numerically identical — IEEE-754-verified); new_predator_field_magnitudecalls it with the predator params.PredatorParams/PredatorConfiggaingradient_field_mode(exponentialdefault |fick),diffusion_coefficient,assay_time,fick_length()— predatorDis independent of foodD.get_predator_concentration/get_predator_sulfolipid_concentration(alias) /_compute_predator_gradient_vectorroute through the mode-aware magnitude.Dliterature calibration are noted follow-ups (the latter is phase-7 depth — this ships the mechanism + sensible defaults, not calibratedDvalues).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==0special-case removal is byte-identical (field_magnitude(0) == strength); the repulsive sign + guard are preserved.Validation
Dindependent of food; exponential-default byte-stability;fick_length).pre-commit -a: 3885 passed, ruff + pyright clean.tmp/verify_predator_fick.yml): predators withgradient_field_mode: fick,D=3.0,predator_chemosensation_klinotaxissensing — runs clean, exercising the config→env→Fick-predator-field→sensing path.Notes
chemical-gradient-fidelityspec (predator-sulfolipid in; the exclusion clause retired).fick_length==0div-by-zero is unreachable (pydantic enforcesD>0/assay_time>0; decay validated>0).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests