Phase 87: Expanded Numba JIT¶
Block: 9 (Performance at Scale) Status: Complete Tests: 40 (18 detection + 11 engagement + 11 morale)
Overview¶
Extracts pure-math inner kernels from detection, combat, and morale hot paths into module-level functions decorated with @optional_jit. When Numba is installed, these compile to native machine code; without Numba, they run as plain Python with zero behavioral change. All kernels produce identical results to the original methods.
What Was Built¶
87a: Detection SNR Kernels (detection/detection.py)¶
5 JIT-compilable kernel functions extracted from DetectionEngine static methods:
_snr_visual_kernel: Beer-Lambert atmospheric extinction, pure scalar math_snr_thermal_kernel: IR atmospheric loss model, pure scalar math_snr_radar_kernel: Full radar range equation (Pt, Gt², λ², σ, R⁴), constants inlined_snr_acoustic_kernel: Signal excess (SE = SL - TL - NL + DI), sentinel-1.0for no TL override_detection_probability_kernel: Usesmath.erfc(Numba-compatible) instead ofscipy.special.erfc
Static methods now delegate to kernels: extract primitive params from sensor objects, call kernel, return result.
87b: Engagement Math Kernels¶
-
_hit_probability_kernel(combat/hit_probability.py): 20-param pure-scalar function combining dispersion, crew skill, motion penalties, visibility, posture, uncertainty, terrain cover, elevation, condition floor, and clamping.compute_phitextracts primitives from weapon/config objects, calls kernel, then builds diagnosticmodifiersdict separately. -
_penetration_kernel(combat/damage.py): DeMarre penetration with obliquity, ricochet check, range-dependent velocity decay, armor effectiveness. Returns(penetrated, pen_mm, armor_eff_mm, margin_mm)tuple.compute_penetrationhandles armor type enum lookup, then delegates.
87c: Morale Transition Kernels (morale/state.py)¶
-
_transition_matrix_kernel: Discrete Markov transition matrix (5×5). AllMoraleConfigfields passed as primitive floats.leadership_present_fas1.0/0.0(Numba cannot handle bool in all contexts). Uses manualmin/maxclamps instead ofnp.clipfor Numba scalar compatibility. -
_continuous_transition_kernel: Continuous-time variant usingmath.exp(-λ·dt). Same parameter pattern.
Both wired into compute_transition_matrix and compute_continuous_transition_probs — existing cache logic preserved.
Key Design Decisions¶
-
Extract-and-delegate pattern: Kernels are pure-math module-level functions; existing methods remain as the public API, handling object access and result construction. Same pattern as
ballistics.py. -
math.erfcoverscipy.special.erfc: Standard librarymath.erfcis supported by Numba nopython mode and produces identical results. Scipy import retained forfalse_alarm_probabilityand other consumers. -
Sentinel for None params:
_snr_acoustic_kerneluses-1.0for "no transmission loss override" since Numba cannot handleNone. Wrapper convertsNone→-1.0. -
Manual clamps in kernels:
max(lo, min(hi, v))pattern instead ofnp.clipfor scalar values — guaranteed float return in Numba. -
No new
enable_*flags: JIT is transparent — identical results, just faster when Numba is installed. -
Modifiers dict outside JIT path:
compute_phitbuilds the diagnosticmodifiersdict in Python after the kernel call — dicts are not JIT-compatible. -
Batch morale deferred: The batch morale kernel (processing all units on a side at once) requires SoA data layout from Phase 88. Scalar kernels still provide meaningful speedup per-call.
Files Changed¶
Modified Source (5)¶
stochastic_warfare/detection/detection.py— 5 JIT kernels + delegation wiringstochastic_warfare/combat/hit_probability.py— 1 JIT kernel + delegation wiringstochastic_warfare/combat/damage.py— 1 JIT kernel + delegation wiringstochastic_warfare/morale/state.py— 2 JIT kernels + delegation wiring +import math
New Test Files (3)¶
tests/unit/test_phase87_detection_jit.py— 18 teststests/unit/test_phase87_engagement_jit.py— 11 teststests/unit/test_phase87_morale_jit.py— 11 tests
Accepted Limitations¶
- Batch detection sweep deferred: Per-target loop in
fog_of_war.pyaccesses Python objects; vectorization requires SoA data layer (Phase 88). - Batch morale update deferred: Same dependency on SoA for array-based processing.
@guvectorize/prangeparallelism deferred: Phase 89 (per-side threading).- First-call JIT warmup: ~100-500ms per function on first invocation with Numba;
cache=Trueavoids this on subsequent process starts. - Constants inlined in radar kernel: Module-level constants (
_FOUR_PI_CUBED, etc.) not accessible in Numba nopython mode; values inlined in kernel body.