#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DerivedPhysics {
pub signal_speed_um_tick: u32,
pub segment_length_um: u32,
pub v_seg: u32,
}
pub fn compute_derived_physics(
signal_speed_m_s: f32,
tick_duration_us: u32,
voxel_size_um: f32,
segment_length_vox: u32,
) -> Result<DerivedPhysics, String> {
let tick_ms = tick_duration_us as f32 / 1000.0;
let signal_speed_um_tick = signal_speed_m_s * 1000.0 * tick_ms;
let segment_length_um = voxel_size_um * segment_length_vox as f32;
if segment_length_um < 1e-6 {
return Err("segment_length_um must be > 0".to_string());
}
let v_seg_f32 = signal_speed_um_tick / segment_length_um;
let v_seg = v_seg_f32.round() as u32;
let diff = (v_seg_f32 - v_seg as f32).abs();
if diff > 1e-5 {
return Err(format!(
"1.6 violation: v_seg ({v_seg_f32:.4}) is not an integer. \
Physically impossible for Integer Physics engine. \
Check simulation.signal_speed_m_s ({} m/s) and simulation.voxel_size_um ({} um).",
signal_speed_m_s, voxel_size_um
));
}
Ok(DerivedPhysics {
signal_speed_um_tick: signal_speed_um_tick.round() as u32,
segment_length_um: segment_length_um.round() as u32,
v_seg,
})
}
#[inline(always)]
pub const fn compute_glif(
voltage: i32,
rest_potential: i32,
leak_rate: i32,
input_current: i32,
) -> i32 {
debug_assert!(leak_rate > 0, "Leak rate must be > 0");
let leak = (voltage - rest_potential) / leak_rate;
voltage - leak + input_current
}
#[inline(always)]
pub const fn update_homeostasis(
threshold_offset: i32,
decay: u16,
is_spiking: bool,
penalty: i32,
) -> i32 {
let decayed = threshold_offset.wrapping_sub(decay as i32);
let mask = decayed >> 31;
let clamped = decayed & !mask;
let spike_penalty = if is_spiking { penalty } else { 0 };
clamped + spike_penalty
}
#[inline(always)]
pub const fn inertia_rank(abs_weight: i32) -> usize {
let rank = (abs_weight >> 27) as usize; if rank > 15 {
15
} else {
rank
}
}
pub fn compute_gsop_weight(
current_weight: i32,
dopamine: i16,
d1_affinity: u8,
d2_affinity: u8,
gsop_potentiation: u16,
gsop_depression: u16,
inertia: u8,
dist_to_spike: Option<u32>,
burst_mult: u8,
) -> i32 {
let sign = if current_weight >= 0 { 1 } else { -1 };
let abs_w = current_weight.abs();
let pot_mod = ((dopamine as i32) * (d1_affinity as i32)) >> 7;
let dep_mod = ((dopamine as i32) * (d2_affinity as i32)) >> 7;
let raw_pot = (gsop_potentiation as i32) + pot_mod;
let raw_dep = (gsop_depression as i32) - dep_mod;
let final_dep = if raw_dep < 0 { 0 } else { raw_dep };
let delta_pot = (raw_pot * (inertia as i32) * (burst_mult as i32)) >> 7;
let delta_dep = (final_dep * (inertia as i32) * (burst_mult as i32)) >> 7;
let is_active = dist_to_spike.is_some();
let min_dist = if let Some(d) = dist_to_spike {
d
} else {
u32::MAX
};
let cooling_shift = if is_active { (min_dist >> 4) as u32 } else { 0 };
let delta = if is_active {
delta_pot >> cooling_shift
} else {
-delta_dep
};
let decay = 128i32; let delta = (delta * decay) >> 7;
let mut new_abs = abs_w + delta;
if new_abs < 0 {
new_abs = 0;
}
if new_abs > 2140000000 {
new_abs = 2140000000;
}
(sign * new_abs) as i32
}
#[cfg(test)]
#[path = "test_physics.rs"]
mod test_physics;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_glif_leak_positive_offset() {
assert_eq!(compute_glif(100, -70, 2, 0), 15);
}
#[test]
fn test_glif_leak_negative_offset() {
assert_eq!(compute_glif(-100, -70, 2, 0), -85);
}
#[test]
fn test_glif_at_rest_no_change() {
assert_eq!(compute_glif(-70, -70, 2, 0), -70);
}
#[test]
fn test_glif_with_input_current() {
assert_eq!(compute_glif(-70, -70, 2, 50), -20);
}
#[test]
fn test_homeostasis_branchless_clamp() {
assert_eq!(update_homeostasis(5, 10, false, 100), 0);
}
#[test]
fn test_homeostasis_normal_decay() {
assert_eq!(update_homeostasis(100, 3, false, 15), 97);
}
#[test]
fn test_homeostasis_spike_adds_penalty() {
assert_eq!(update_homeostasis(100, 3, true, 15), 112);
}
#[test]
fn test_homeostasis_zero_stays_zero() {
assert_eq!(update_homeostasis(0, 0, false, 15), 0);
}
#[test]
fn test_homeostasis_spike_from_zero() {
assert_eq!(update_homeostasis(0, 0, true, 15), 15);
}
#[test]
fn test_gsop_potentiation_basic() {
assert_eq!(
compute_gsop_weight(100, 0, 0, 0, 80, 40, 128, Some(0), 1),
180
);
}
#[test]
fn test_gsop_depression_basic() {
assert_eq!(
compute_gsop_weight(100, 0, 0, 0, 80, 40, 128, None, 1),
60
);
}
#[test]
fn test_gsop_i32_limits() {
let w = compute_gsop_weight(-1000000, 0, 0, 0, 80, 40, 128, Some(0), 1);
assert_eq!(w, -1000080);
let w2 = compute_gsop_weight(-1000000, 0, 0, 0, 80, 40, 128, None, 1);
assert_eq!(w2, -999960);
}
#[test]
fn test_gsop_sign_preservation_negative() {
let w = compute_gsop_weight(-500, 0, 0, 0, 80, 40, 128, None, 1);
assert!(w <= 0, "Dale's Law violated");
assert_eq!(w, -460);
}
#[test]
fn test_gsop_sign_preservation_depression_to_zero() {
let w = compute_gsop_weight(-5, 0, 0, 0, 80, 40, 128, None, 1);
assert_eq!(w, 0);
}
#[test]
fn test_gsop_clamp_max() {
let w = compute_gsop_weight(2140000000, 0, 0, 0, 80, 40, 128, Some(0), 1);
assert_eq!(w, 2140000000);
}
#[test]
fn test_gsop_inertia_rank_calculation() {
assert_eq!(inertia_rank(0), 0);
assert_eq!(inertia_rank(134_217_727), 0);
assert_eq!(inertia_rank(134_217_728), 1);
assert_eq!(inertia_rank(268_435_455), 1);
assert_eq!(inertia_rank(268_435_456), 2);
assert_eq!(inertia_rank(1_879_048_192), 14);
assert_eq!(inertia_rank(2_140_000_000), 15);
}
#[test]
fn test_gsop_cooling_effect() {
let w_close = compute_gsop_weight(1000, 0, 0, 0, 128, 40, 128, Some(0), 1);
let w_far = compute_gsop_weight(1000, 0, 0, 0, 128, 40, 128, Some(64), 1);
assert!(w_close > w_far);
assert_eq!(w_close, 1128); assert_eq!(w_far, 1008); }
}