eml-stdlib 0.4.0__py3-none-any.whl
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.
- eml_stdlib/__init__.py +81 -0
- eml_stdlib/ballistics/air_density.eml +73 -0
- eml_stdlib/ballistics/ballistic_solver.eml +102 -0
- eml_stdlib/ballistics/coriolis.eml +60 -0
- eml_stdlib/ballistics/drag.eml +44 -0
- eml_stdlib/ballistics/gravity.eml +52 -0
- eml_stdlib/ballistics/muzzle_velocity.eml +42 -0
- eml_stdlib/ballistics/spin_drift.eml +60 -0
- eml_stdlib/ballistics/time_of_flight.eml +79 -0
- eml_stdlib/ballistics/wind.eml +58 -0
- eml_stdlib/biology/abilities/ballistic_armor.eml +61 -0
- eml_stdlib/biology/abilities/bite_force.eml +48 -0
- eml_stdlib/biology/abilities/chromatophore.eml +50 -0
- eml_stdlib/biology/abilities/discharge.eml +45 -0
- eml_stdlib/biology/abilities/elastic_energy.eml +42 -0
- eml_stdlib/biology/abilities/locomotion.eml +50 -0
- eml_stdlib/biology/abilities/muscle_power.eml +47 -0
- eml_stdlib/biology/abilities/sprint.eml +36 -0
- eml_stdlib/biology/abilities/texture_match.eml +49 -0
- eml_stdlib/biology/abilities/toxin.eml +44 -0
- eml_stdlib/biology/cone_l.eml +36 -0
- eml_stdlib/biology/cone_s.eml +32 -0
- eml_stdlib/biology/cryptochrome.eml +44 -0
- eml_stdlib/biology/geomagnetic_field.eml +32 -0
- eml_stdlib/biology/goldman_equation.eml +41 -0
- eml_stdlib/biology/hill_equation.eml +65 -0
- eml_stdlib/biology/hodgkin_huxley.eml +108 -0
- eml_stdlib/biology/michaelis_menten.eml +65 -0
- eml_stdlib/biology/plume_diffusion.eml +47 -0
- eml_stdlib/biology/receptor_binding.eml +35 -0
- eml_stdlib/biology/rod_sensitivity.eml +38 -0
- eml_stdlib/biology/tapetum.eml +70 -0
- eml_stdlib/carriers/acoustic_cloak.eml +56 -0
- eml_stdlib/carriers/ferron_emission.eml +55 -0
- eml_stdlib/carriers/ferron_logic.eml +45 -0
- eml_stdlib/carriers/ferron_propagation.eml +69 -0
- eml_stdlib/carriers/mach_zehnder.eml +52 -0
- eml_stdlib/carriers/magnon_dispersion.eml +57 -0
- eml_stdlib/carriers/magnon_logic.eml +45 -0
- eml_stdlib/carriers/optical_neuron.eml +55 -0
- eml_stdlib/carriers/phonon_bandgap.eml +54 -0
- eml_stdlib/carriers/ring_resonator.eml +48 -0
- eml_stdlib/carriers/spin_torque.eml +61 -0
- eml_stdlib/carriers/thermal_rectifier.eml +63 -0
- eml_stdlib/catalog.json +4209 -0
- eml_stdlib/circuits/buck_converter.eml +74 -0
- eml_stdlib/circuits/cmos_inverter.eml +75 -0
- eml_stdlib/circuits/h_bridge.eml +69 -0
- eml_stdlib/circuits/ldo.eml +66 -0
- eml_stdlib/circuits/mosfet_iv.eml +59 -0
- eml_stdlib/circuits/pll_loop.eml +63 -0
- eml_stdlib/circuits/rc_filter.eml +98 -0
- eml_stdlib/circuits/voltage_divider.eml +68 -0
- eml_stdlib/control/bangbang.eml +34 -0
- eml_stdlib/control/deadband.eml +28 -0
- eml_stdlib/control/hysteresis.eml +28 -0
- eml_stdlib/control/kalman_1d.eml +53 -0
- eml_stdlib/control/lqr_1d.eml +47 -0
- eml_stdlib/control/pid.eml +38 -0
- eml_stdlib/gaming/animation/bounce.eml +39 -0
- eml_stdlib/gaming/animation/ease_in.eml +27 -0
- eml_stdlib/gaming/animation/ease_in_out.eml +29 -0
- eml_stdlib/gaming/animation/ease_out.eml +27 -0
- eml_stdlib/gaming/animation/ik_2bone.eml +57 -0
- eml_stdlib/gaming/animation/spring.eml +39 -0
- eml_stdlib/gaming/audio/distance_attenuation.eml +50 -0
- eml_stdlib/gaming/audio/doppler.eml +39 -0
- eml_stdlib/gaming/audio/lowpass_audio.eml +32 -0
- eml_stdlib/gaming/audio/reverb_delay.eml +41 -0
- eml_stdlib/gaming/camera/dof_blur.eml +30 -0
- eml_stdlib/gaming/camera/fov_projection.eml +47 -0
- eml_stdlib/gaming/camera/orbit.eml +48 -0
- eml_stdlib/gaming/camera/shake.eml +20 -0
- eml_stdlib/gaming/lighting/ambient_occlusion.eml +40 -0
- eml_stdlib/gaming/lighting/area_light.eml +50 -0
- eml_stdlib/gaming/lighting/fog_exp.eml +28 -0
- eml_stdlib/gaming/lighting/fog_exp2.eml +30 -0
- eml_stdlib/gaming/lighting/point_light.eml +33 -0
- eml_stdlib/gaming/lighting/spot_light.eml +43 -0
- eml_stdlib/gaming/noise/fbm.eml +65 -0
- eml_stdlib/gaming/noise/perlin_2d.eml +65 -0
- eml_stdlib/gaming/noise/perlin_3d.eml +63 -0
- eml_stdlib/gaming/noise/simplex_2d.eml +67 -0
- eml_stdlib/gaming/noise/turbulence.eml +46 -0
- eml_stdlib/gaming/noise/value_noise.eml +45 -0
- eml_stdlib/gaming/noise/voronoi_2d.eml +83 -0
- eml_stdlib/gaming/noise/white_noise.eml +47 -0
- eml_stdlib/gaming/noise/worley.eml +79 -0
- eml_stdlib/gaming/particles/drag.eml +30 -0
- eml_stdlib/gaming/particles/emitter_radial.eml +35 -0
- eml_stdlib/gaming/particles/fade.eml +36 -0
- eml_stdlib/gaming/particles/gravity_particle.eml +32 -0
- eml_stdlib/gaming/particles/size_over_life.eml +28 -0
- eml_stdlib/gaming/physics/aabb_overlap.eml +38 -0
- eml_stdlib/gaming/physics/buoyancy.eml +43 -0
- eml_stdlib/gaming/physics/circle_collision.eml +46 -0
- eml_stdlib/gaming/physics/impulse_resolve.eml +39 -0
- eml_stdlib/gaming/physics/rigid_body_2d.eml +61 -0
- eml_stdlib/gaming/physics/spring_damper.eml +30 -0
- eml_stdlib/gaming/physics/verlet.eml +29 -0
- eml_stdlib/gaming/shading/fresnel.eml +57 -0
- eml_stdlib/gaming/shading/matcap.eml +34 -0
- eml_stdlib/gaming/shading/pbr_diffuse.eml +41 -0
- eml_stdlib/gaming/shading/pbr_specular.eml +75 -0
- eml_stdlib/gaming/shading/subsurface.eml +52 -0
- eml_stdlib/gaming/shading/toon.eml +30 -0
- eml_stdlib/gaming/terrain/biome_select.eml +41 -0
- eml_stdlib/gaming/terrain/cliff_detect.eml +35 -0
- eml_stdlib/gaming/terrain/erosion_hydraulic.eml +57 -0
- eml_stdlib/gaming/terrain/erosion_thermal.eml +44 -0
- eml_stdlib/gaming/terrain/heightmap.eml +33 -0
- eml_stdlib/gaming/textures/brick.eml +27 -0
- eml_stdlib/gaming/textures/caustics.eml +25 -0
- eml_stdlib/gaming/textures/checkerboard.eml +27 -0
- eml_stdlib/gaming/textures/fire.eml +26 -0
- eml_stdlib/gaming/textures/marble.eml +24 -0
- eml_stdlib/gaming/textures/rust.eml +23 -0
- eml_stdlib/gaming/textures/water_surface.eml +49 -0
- eml_stdlib/gaming/textures/wood.eml +41 -0
- eml_stdlib/math/abs.eml +45 -0
- eml_stdlib/math/atan.eml +32 -0
- eml_stdlib/math/atan2.eml +61 -0
- eml_stdlib/math/bessel_j0.eml +65 -0
- eml_stdlib/math/cos.eml +42 -0
- eml_stdlib/math/cosh.eml +24 -0
- eml_stdlib/math/erf.eml +66 -0
- eml_stdlib/math/exp.eml +49 -0
- eml_stdlib/math/gamma.eml +55 -0
- eml_stdlib/math/gaussian.eml +18 -0
- eml_stdlib/math/log.eml +59 -0
- eml_stdlib/math/pow.eml +51 -0
- eml_stdlib/math/relu.eml +63 -0
- eml_stdlib/math/sigmoid.eml +21 -0
- eml_stdlib/math/sin.eml +37 -0
- eml_stdlib/math/sinh.eml +24 -0
- eml_stdlib/math/softmax.eml +87 -0
- eml_stdlib/math/sqrt.eml +46 -0
- eml_stdlib/math/tan.eml +36 -0
- eml_stdlib/math/tanh.eml +54 -0
- eml_stdlib/ml/attention.eml +70 -0
- eml_stdlib/ml/cross_entropy.eml +85 -0
- eml_stdlib/ml/gelu.eml +51 -0
- eml_stdlib/ml/layernorm.eml +49 -0
- eml_stdlib/ml/leaky_relu.eml +51 -0
- eml_stdlib/ml/relu.eml +63 -0
- eml_stdlib/ml/rotary_embed.eml +64 -0
- eml_stdlib/ml/silu.eml +40 -0
- eml_stdlib/ml/softmax.eml +87 -0
- eml_stdlib/physics/coulomb.eml +48 -0
- eml_stdlib/physics/damped_oscillator.eml +20 -0
- eml_stdlib/physics/diffusion.eml +45 -0
- eml_stdlib/physics/gravity.eml +60 -0
- eml_stdlib/physics/hooke.eml +56 -0
- eml_stdlib/physics/navier_stokes_1d.eml +57 -0
- eml_stdlib/physics/planck_radiation.eml +77 -0
- eml_stdlib/physics/stefan_boltzmann.eml +75 -0
- eml_stdlib/physics/wave_propagation.eml +52 -0
- eml_stdlib/quantum/cnot.eml +72 -0
- eml_stdlib/quantum/grover_oracle.eml +69 -0
- eml_stdlib/quantum/hadamard.eml +73 -0
- eml_stdlib/quantum/pauli_x.eml +29 -0
- eml_stdlib/quantum/pauli_z.eml +29 -0
- eml_stdlib/quantum/phase_gate.eml +76 -0
- eml_stdlib/quantum/rotation_rz.eml +92 -0
- eml_stdlib/sensors/accelerometer.eml +59 -0
- eml_stdlib/sensors/hall_effect.eml +42 -0
- eml_stdlib/sensors/photodetector.eml +58 -0
- eml_stdlib/sensors/rtd.eml +57 -0
- eml_stdlib/sensors/strain_gauge.eml +63 -0
- eml_stdlib/sensors/thermistor.eml +71 -0
- eml_stdlib/signal/bandpass.eml +39 -0
- eml_stdlib/signal/chirp.eml +42 -0
- eml_stdlib/signal/dft_single.eml +30 -0
- eml_stdlib/signal/fft_butterfly.eml +36 -0
- eml_stdlib/signal/highpass.eml +45 -0
- eml_stdlib/signal/lowpass.eml +49 -0
- eml_stdlib/signal/matched_filter.eml +48 -0
- eml_stdlib/signal/notch_filter.eml +47 -0
- eml_stdlib/signal/window_hamming.eml +47 -0
- eml_stdlib/signal/window_hann.eml +50 -0
- eml_stdlib-0.4.0.dist-info/METADATA +453 -0
- eml_stdlib-0.4.0.dist-info/RECORD +185 -0
- eml_stdlib-0.4.0.dist-info/WHEEL +5 -0
- eml_stdlib-0.4.0.dist-info/licenses/LICENSE +21 -0
- eml_stdlib-0.4.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// ballistic_armor.eml -- armadillo plate / ballistic vest absorption.
|
|
2
|
+
//
|
|
3
|
+
// Kinetic energy delivered by a bullet:
|
|
4
|
+
// E_k = ½ * m * v²
|
|
5
|
+
//
|
|
6
|
+
// Armour absorbs a fraction of that energy; remaining energy
|
|
7
|
+
// causes wound damage. Chain order 0.
|
|
8
|
+
|
|
9
|
+
module ballistic_armor;
|
|
10
|
+
|
|
11
|
+
@verify(lean, theorem = "kinetic_energy_nonneg")
|
|
12
|
+
fn kinetic_energy(mass_kg: Real, velocity_mps: Real) -> Real
|
|
13
|
+
where chain_order <= 0,
|
|
14
|
+
domain: mass_kg >= 0.0
|
|
15
|
+
ensures (result >= 0.0)
|
|
16
|
+
{
|
|
17
|
+
0.5 * mass_kg * velocity_mps * velocity_mps
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Energy absorbed by armor with given absorption coefficient
|
|
21
|
+
// (dimensionless, [0,1]).
|
|
22
|
+
@verify(lean, theorem = "armor_absorbed_in_band")
|
|
23
|
+
fn energy_absorbed(mass_kg: Real, velocity_mps: Real,
|
|
24
|
+
absorption_coeff: Real) -> Real
|
|
25
|
+
where chain_order <= 0,
|
|
26
|
+
domain: mass_kg >= 0.0
|
|
27
|
+
requires (absorption_coeff >= 0.0)
|
|
28
|
+
requires (absorption_coeff <= 1.0)
|
|
29
|
+
ensures (result >= 0.0)
|
|
30
|
+
{
|
|
31
|
+
let e_k: Real = 0.5 * mass_kg * velocity_mps * velocity_mps;
|
|
32
|
+
e_k * absorption_coeff
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Energy that penetrates (delivered to flesh).
|
|
36
|
+
@verify(lean, theorem = "armor_penetrating_energy_nonneg")
|
|
37
|
+
fn energy_penetrating(mass_kg: Real, velocity_mps: Real,
|
|
38
|
+
absorption_coeff: Real) -> Real
|
|
39
|
+
where chain_order <= 0,
|
|
40
|
+
domain: mass_kg >= 0.0
|
|
41
|
+
requires (absorption_coeff >= 0.0)
|
|
42
|
+
requires (absorption_coeff <= 1.0)
|
|
43
|
+
ensures (result >= 0.0)
|
|
44
|
+
{
|
|
45
|
+
let e_k: Real = 0.5 * mass_kg * velocity_mps * velocity_mps;
|
|
46
|
+
e_k * (1.0 - absorption_coeff)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Ricochet probability rises sharply for shallow impact angles.
|
|
50
|
+
// θ_impact = angle between bullet velocity and surface normal.
|
|
51
|
+
// p_ricochet = 1 - cos(θ_impact)^2 for θ in (0, π/2)
|
|
52
|
+
@verify(lean, theorem = "ricochet_probability_in_unit_interval")
|
|
53
|
+
fn ricochet_probability(angle_rad: Real) -> Real
|
|
54
|
+
where chain_order <= 1,
|
|
55
|
+
domain: angle_rad >= 0.0
|
|
56
|
+
ensures (result >= 0.0)
|
|
57
|
+
ensures (result <= 1.0)
|
|
58
|
+
{
|
|
59
|
+
let c: Real = clamp(cos(angle_rad), 0.0, 1.0);
|
|
60
|
+
1.0 - c * c
|
|
61
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// bite_force.eml -- jaw-power scaling for the beast's stage attacks.
|
|
2
|
+
//
|
|
3
|
+
// Bite force at the canines:
|
|
4
|
+
//
|
|
5
|
+
// F_bite = k * BSL² * cos(θ_jaw)
|
|
6
|
+
//
|
|
7
|
+
// where BSL is the body's bite-strength linear factor (proxy for
|
|
8
|
+
// mass-and-jaw-leverage compound) and θ_jaw the jaw opening angle.
|
|
9
|
+
// Chain order 1 (cos). Used to compute how many points of damage
|
|
10
|
+
// the beast deals per stage.
|
|
11
|
+
|
|
12
|
+
module bite_force;
|
|
13
|
+
|
|
14
|
+
@verify(lean, theorem = "bite_force_nonneg")
|
|
15
|
+
fn bite_force_n(k: Real, bsl: Real, jaw_angle_rad: Real) -> Real
|
|
16
|
+
where chain_order <= 1,
|
|
17
|
+
domain: k >= 0.0 && bsl >= 0.0
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
{
|
|
20
|
+
let c: Real = clamp(cos(jaw_angle_rad), 0.0, 1.0);
|
|
21
|
+
k * bsl * bsl * c
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Damage delivered per bite, scaling with bite force and a wound
|
|
25
|
+
// constant (HP per Newton, calibrated to game balance).
|
|
26
|
+
@verify(lean, theorem = "bite_damage_nonneg")
|
|
27
|
+
fn damage_per_bite(bite_force_n_in: Real, wound_constant: Real) -> Real
|
|
28
|
+
where chain_order <= 0,
|
|
29
|
+
domain: bite_force_n_in >= 0.0 && wound_constant >= 0.0
|
|
30
|
+
ensures (result >= 0.0)
|
|
31
|
+
{
|
|
32
|
+
bite_force_n_in * wound_constant
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Stage-multiplier for the beast's bite, callable from beast_combat.
|
|
36
|
+
// stage 0 (hands): 0.10
|
|
37
|
+
// stage 1 (claws): 0.30
|
|
38
|
+
// stage 2 (bite): 0.60
|
|
39
|
+
// stage 3 (lunge): 0.80
|
|
40
|
+
@verify(lean, theorem = "bite_stage_multiplier_nondecreasing")
|
|
41
|
+
fn stage_multiplier(stage: Real) -> Real
|
|
42
|
+
where chain_order <= 0,
|
|
43
|
+
domain: stage >= 0.0
|
|
44
|
+
ensures (result >= 0.0)
|
|
45
|
+
{
|
|
46
|
+
let s_clamped: Real = clamp(stage, 0.0, 3.0);
|
|
47
|
+
0.10 + 0.235 * s_clamped + 0.0166 * s_clamped * s_clamped
|
|
48
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// chromatophore.eml -- gecko / cephalopod camouflage via Bragg reflection.
|
|
2
|
+
//
|
|
3
|
+
// Iridophore stacks reflect a band of wavelengths centred on:
|
|
4
|
+
//
|
|
5
|
+
// λ = 2 * n * d * cos(θ)
|
|
6
|
+
//
|
|
7
|
+
// where n is refractive index, d the lamellar spacing, θ the
|
|
8
|
+
// viewing angle. The chromatophore can dilate/contract d so the
|
|
9
|
+
// reflected band shifts. Chain order 1 (cos).
|
|
10
|
+
|
|
11
|
+
module chromatophore;
|
|
12
|
+
|
|
13
|
+
@verify(lean, theorem = "bragg_wavelength_nonneg")
|
|
14
|
+
fn bragg_wavelength(refractive_index: Real, spacing_nm: Real,
|
|
15
|
+
angle_rad: Real) -> Real
|
|
16
|
+
where chain_order <= 1,
|
|
17
|
+
domain: refractive_index > 0.0 && spacing_nm > 0.0
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
{
|
|
20
|
+
let cos_t: Real = clamp(cos(angle_rad), 0.0, 1.0);
|
|
21
|
+
2.0 * refractive_index * spacing_nm * cos_t
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Camouflage transparency [0, 1] as a function of motion.
|
|
25
|
+
// Stationary subject: full Bragg-tuned camouflage.
|
|
26
|
+
// Moving subject: temporal mismatch leaks the silhouette.
|
|
27
|
+
// transparency = max_t * exp(-velocity / tau)
|
|
28
|
+
@verify(lean, theorem = "camo_transparency_in_unit_interval")
|
|
29
|
+
fn transparency(max_t: Real, velocity_mps: Real, tau_mps: Real) -> Real
|
|
30
|
+
where chain_order <= 1,
|
|
31
|
+
domain: max_t >= 0.0 && max_t <= 1.0 && tau_mps > 0.0
|
|
32
|
+
requires (velocity_mps >= 0.0)
|
|
33
|
+
ensures (result >= 0.0)
|
|
34
|
+
ensures (result <= 1.0)
|
|
35
|
+
{
|
|
36
|
+
max_t * exp(-velocity_mps / tau_mps)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Detection contrast against the background at distance r.
|
|
40
|
+
// contrast(r, transparency) = (1 - transparency) * base_contrast / r²
|
|
41
|
+
@verify(lean, theorem = "camo_contrast_drops_with_distance")
|
|
42
|
+
fn detection_contrast(transparency: Real, base_contrast: Real, r_m: Real) -> Real
|
|
43
|
+
where chain_order <= 0,
|
|
44
|
+
domain: r_m > 0.0 && base_contrast >= 0.0
|
|
45
|
+
requires (transparency >= 0.0)
|
|
46
|
+
requires (transparency <= 1.0)
|
|
47
|
+
ensures (result >= 0.0)
|
|
48
|
+
{
|
|
49
|
+
(1.0 - transparency) * base_contrast / (r_m * r_m)
|
|
50
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// discharge.eml -- electric eel discharge waveform.
|
|
2
|
+
//
|
|
3
|
+
// Eel pulse voltage:
|
|
4
|
+
//
|
|
5
|
+
// V(t) = V_peak * exp(-t / τ)
|
|
6
|
+
//
|
|
7
|
+
// where V_peak ≈ 860 V and τ ≈ 2 ms. Chain order 1.
|
|
8
|
+
//
|
|
9
|
+
// Used by the eel ability to compute EMP duration on hunter
|
|
10
|
+
// instruments and stun damage on the beast.
|
|
11
|
+
|
|
12
|
+
module discharge;
|
|
13
|
+
|
|
14
|
+
const V_PEAK_DEFAULT: Real = 860.0 // Volts
|
|
15
|
+
const TAU_DEFAULT: Real = 0.002 // seconds
|
|
16
|
+
|
|
17
|
+
@verify(lean, theorem = "discharge_voltage_nonneg")
|
|
18
|
+
fn voltage(v_peak: Real, tau_s: Real, t_s: Real) -> Real
|
|
19
|
+
where chain_order <= 1,
|
|
20
|
+
domain: v_peak >= 0.0 && tau_s > 0.0 && t_s >= 0.0
|
|
21
|
+
ensures (result >= 0.0)
|
|
22
|
+
{
|
|
23
|
+
v_peak * exp(-t_s / tau_s)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Default-parameters convenience.
|
|
27
|
+
@verify(lean, theorem = "discharge_default_voltage_nonneg")
|
|
28
|
+
fn voltage_default(t_s: Real) -> Real
|
|
29
|
+
where chain_order <= 1,
|
|
30
|
+
domain: t_s >= 0.0
|
|
31
|
+
ensures (result >= 0.0)
|
|
32
|
+
{
|
|
33
|
+
V_PEAK_DEFAULT * exp(-t_s / TAU_DEFAULT)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Energy delivered per pulse (J), assuming constant body resistance.
|
|
37
|
+
// E = V_peak² * τ / (2 * R)
|
|
38
|
+
@verify(lean, theorem = "discharge_energy_nonneg")
|
|
39
|
+
fn pulse_energy(v_peak: Real, tau_s: Real, body_ohm: Real) -> Real
|
|
40
|
+
where chain_order <= 0,
|
|
41
|
+
domain: v_peak >= 0.0 && tau_s >= 0.0 && body_ohm > 0.0
|
|
42
|
+
ensures (result >= 0.0)
|
|
43
|
+
{
|
|
44
|
+
v_peak * v_peak * tau_s / (2.0 * body_ohm)
|
|
45
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// elastic_energy.eml -- flea-style elastic-jump physics.
|
|
2
|
+
//
|
|
3
|
+
// A flea stores energy in its resilin pad, then releases it
|
|
4
|
+
// instantaneously. Take-off velocity is governed by:
|
|
5
|
+
//
|
|
6
|
+
// v = sqrt(2 * E / m)
|
|
7
|
+
//
|
|
8
|
+
// where E is stored elastic energy (J) and m the body mass (kg).
|
|
9
|
+
// Chain order 1 (sqrt). Used by the beast jump ability.
|
|
10
|
+
|
|
11
|
+
module elastic_energy;
|
|
12
|
+
|
|
13
|
+
@verify(lean, theorem = "elastic_takeoff_velocity_nonneg")
|
|
14
|
+
fn takeoff_velocity(elastic_energy_j: Real, mass_kg: Real) -> Real
|
|
15
|
+
where chain_order <= 1,
|
|
16
|
+
domain: elastic_energy_j >= 0.0 && mass_kg > 0.0
|
|
17
|
+
ensures (result >= 0.0)
|
|
18
|
+
{
|
|
19
|
+
sqrt(2.0 * elastic_energy_j / mass_kg)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Maximum jump height assuming straight-up release, no drag.
|
|
23
|
+
// h = v² / (2g)
|
|
24
|
+
@verify(lean, theorem = "elastic_jump_height_nonneg")
|
|
25
|
+
fn jump_height(elastic_energy_j: Real, mass_kg: Real, g: Real) -> Real
|
|
26
|
+
where chain_order <= 0,
|
|
27
|
+
domain: elastic_energy_j >= 0.0 && mass_kg > 0.0 && g > 0.0
|
|
28
|
+
ensures (result >= 0.0)
|
|
29
|
+
{
|
|
30
|
+
elastic_energy_j / (mass_kg * g)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Range of a 45° elastic launch (no drag, level ground).
|
|
34
|
+
// R = v² / g
|
|
35
|
+
@verify(lean, theorem = "elastic_jump_range_nonneg")
|
|
36
|
+
fn jump_range(elastic_energy_j: Real, mass_kg: Real, g: Real) -> Real
|
|
37
|
+
where chain_order <= 0,
|
|
38
|
+
domain: elastic_energy_j >= 0.0 && mass_kg > 0.0 && g > 0.0
|
|
39
|
+
ensures (result >= 0.0)
|
|
40
|
+
{
|
|
41
|
+
2.0 * elastic_energy_j / (mass_kg * g)
|
|
42
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// locomotion.eml -- many-legged speed bonus model (centipede).
|
|
2
|
+
//
|
|
3
|
+
// Stride frequency f * stride length L = ground-speed v.
|
|
4
|
+
// More legs in phase → higher achievable f at the same metabolic
|
|
5
|
+
// cost. Linear surrogate:
|
|
6
|
+
//
|
|
7
|
+
// v(n_legs) = v_base * (1 + α * (n_legs - 2))
|
|
8
|
+
//
|
|
9
|
+
// with α = 0.10 per pair beyond the bipedal baseline; chain 0.
|
|
10
|
+
|
|
11
|
+
module locomotion;
|
|
12
|
+
|
|
13
|
+
const ALPHA_PER_LEG_PAIR: Real = 0.10
|
|
14
|
+
const N_LEGS_BASELINE: Real = 2.0 // bipedal human reference
|
|
15
|
+
|
|
16
|
+
@verify(lean, theorem = "locomotion_speed_grows_with_legs")
|
|
17
|
+
fn ground_speed(v_base: Real, n_legs: Real) -> Real
|
|
18
|
+
where chain_order <= 0,
|
|
19
|
+
domain: v_base >= 0.0 && n_legs >= 0.0
|
|
20
|
+
ensures (result >= 0.0)
|
|
21
|
+
{
|
|
22
|
+
let extra_pairs: Real = clamp((n_legs - N_LEGS_BASELINE) * 0.5, 0.0, 100.0);
|
|
23
|
+
v_base * (1.0 + ALPHA_PER_LEG_PAIR * extra_pairs)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Same expressed as a multiplier on the base speed.
|
|
27
|
+
@verify(lean, theorem = "locomotion_multiplier_geq_one")
|
|
28
|
+
fn speed_multiplier(n_legs: Real) -> Real
|
|
29
|
+
where chain_order <= 0,
|
|
30
|
+
domain: n_legs >= 0.0
|
|
31
|
+
ensures (result >= 1.0)
|
|
32
|
+
{
|
|
33
|
+
let extra_pairs: Real = clamp((n_legs - N_LEGS_BASELINE) * 0.5, 0.0, 100.0);
|
|
34
|
+
1.0 + ALPHA_PER_LEG_PAIR * extra_pairs
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Wall-climbing toggle threshold: enough legs and enough adhesion to
|
|
38
|
+
// support body weight against gravity. Returns 1.0 if climbing is
|
|
39
|
+
// possible, 0.0 otherwise (for clamp-style boolean dispatch).
|
|
40
|
+
@verify(lean, theorem = "wall_climb_threshold_in_zero_one")
|
|
41
|
+
fn wall_climb_capable(n_legs: Real, adhesion_n_per_leg: Real,
|
|
42
|
+
body_weight_n: Real) -> Real
|
|
43
|
+
where chain_order <= 0,
|
|
44
|
+
domain: n_legs >= 0.0 && adhesion_n_per_leg >= 0.0
|
|
45
|
+
ensures (result >= 0.0)
|
|
46
|
+
ensures (result <= 1.0)
|
|
47
|
+
{
|
|
48
|
+
let total_grip: Real = n_legs * adhesion_n_per_leg;
|
|
49
|
+
clamp((total_grip - body_weight_n) * 1.0e6, 0.0, 1.0)
|
|
50
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// muscle_power.eml -- striated-muscle power-to-mass scaling.
|
|
2
|
+
//
|
|
3
|
+
// Peak power per kg of muscle ~ 250 W/kg in mammals. Power-to-weight
|
|
4
|
+
// scales linearly with mass-fraction of muscle:
|
|
5
|
+
//
|
|
6
|
+
// P_total = P_per_kg * mass_kg * muscle_fraction
|
|
7
|
+
//
|
|
8
|
+
// Chain order 0. Used by beast stage thresholds (each consumed
|
|
9
|
+
// animal adds a small muscle-mass contribution).
|
|
10
|
+
|
|
11
|
+
module muscle_power;
|
|
12
|
+
|
|
13
|
+
const P_PER_KG: Real = 250.0 // W/kg, peak skeletal muscle
|
|
14
|
+
|
|
15
|
+
@verify(lean, theorem = "muscle_power_nonneg")
|
|
16
|
+
fn total_power(mass_kg: Real, muscle_fraction: Real) -> Real
|
|
17
|
+
where chain_order <= 0,
|
|
18
|
+
domain: mass_kg >= 0.0
|
|
19
|
+
requires (muscle_fraction >= 0.0)
|
|
20
|
+
requires (muscle_fraction <= 1.0)
|
|
21
|
+
ensures (result >= 0.0)
|
|
22
|
+
{
|
|
23
|
+
P_PER_KG * mass_kg * muscle_fraction
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Power-to-weight ratio (W/kg total body).
|
|
27
|
+
@verify(lean, theorem = "muscle_power_to_weight_nonneg")
|
|
28
|
+
fn power_to_weight(muscle_fraction: Real) -> Real
|
|
29
|
+
where chain_order <= 0
|
|
30
|
+
requires (muscle_fraction >= 0.0)
|
|
31
|
+
requires (muscle_fraction <= 1.0)
|
|
32
|
+
ensures (result >= 0.0)
|
|
33
|
+
{
|
|
34
|
+
P_PER_KG * muscle_fraction
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Top-speed proxy: P-to-W against drag = constant gives v ∝ ∛P.
|
|
38
|
+
@verify(lean, theorem = "muscle_topspeed_proxy_nonneg")
|
|
39
|
+
fn top_speed_proxy(muscle_fraction: Real, k_drag_inv_cube: Real) -> Real
|
|
40
|
+
where chain_order <= 1,
|
|
41
|
+
domain: k_drag_inv_cube >= 0.0
|
|
42
|
+
requires (muscle_fraction >= 0.0)
|
|
43
|
+
requires (muscle_fraction <= 1.0)
|
|
44
|
+
ensures (result >= 0.0)
|
|
45
|
+
{
|
|
46
|
+
pow(P_PER_KG * muscle_fraction * k_drag_inv_cube, 0.333333)
|
|
47
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// sprint.eml -- cheetah-style burst-acceleration model.
|
|
2
|
+
//
|
|
3
|
+
// v(t) = v_max * (1 - exp(-t / τ))
|
|
4
|
+
//
|
|
5
|
+
// Asymptotic approach to peak speed; cheetahs hit ~95% of peak in
|
|
6
|
+
// ~3 s. Chain order 1.
|
|
7
|
+
|
|
8
|
+
module sprint;
|
|
9
|
+
|
|
10
|
+
const V_MAX_CHEETAH: Real = 33.3 // m/s (~120 km/h)
|
|
11
|
+
const TAU_CHEETAH: Real = 1.2 // seconds, time constant
|
|
12
|
+
|
|
13
|
+
@verify(lean, theorem = "sprint_velocity_in_band")
|
|
14
|
+
fn velocity(v_max: Real, tau_s: Real, t_s: Real) -> Real
|
|
15
|
+
where chain_order <= 1,
|
|
16
|
+
domain: v_max >= 0.0 && tau_s > 0.0 && t_s >= 0.0
|
|
17
|
+
ensures (result >= 0.0)
|
|
18
|
+
ensures (result <= v_max)
|
|
19
|
+
{
|
|
20
|
+
v_max * (1.0 - exp(-t_s / tau_s))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Distance covered from cold start at time t.
|
|
24
|
+
// x(t) = v_max * (t - τ * (1 - exp(-t/τ)))
|
|
25
|
+
@verify(lean, theorem = "sprint_distance_nonneg")
|
|
26
|
+
fn distance_covered(v_max: Real, tau_s: Real, t_s: Real) -> Real
|
|
27
|
+
where chain_order <= 1,
|
|
28
|
+
domain: v_max >= 0.0 && tau_s > 0.0 && t_s >= 0.0
|
|
29
|
+
ensures (result >= 0.0)
|
|
30
|
+
{
|
|
31
|
+
v_max * (t_s - tau_s * (1.0 - exp(-t_s / tau_s)))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Burst duration before fatigue kicks in (cheetah ≈ 30 s).
|
|
35
|
+
const BURST_LIMIT_S: Real = 30.0
|
|
36
|
+
const COOLDOWN_S: Real = 30.0
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// texture_match.eml -- octopus chromatic + textural skin matching.
|
|
2
|
+
//
|
|
3
|
+
// Per-pixel match score between the cephalopod's skin
|
|
4
|
+
// and the surrounding background, integrated over a small patch:
|
|
5
|
+
//
|
|
6
|
+
// match(p) = 1 - sqrt( ( ΔL² + Δa² + Δb² ) / S_max² )
|
|
7
|
+
//
|
|
8
|
+
// where (L, a, b) are perceptual colour coordinates and S_max
|
|
9
|
+
// the worst-case distance. Output ∈ [0, 1].
|
|
10
|
+
|
|
11
|
+
module texture_match;
|
|
12
|
+
|
|
13
|
+
const S_MAX: Real = 256.0 // perceptual distance for 8-bit Lab range
|
|
14
|
+
|
|
15
|
+
@verify(lean, theorem = "texture_match_in_unit_interval")
|
|
16
|
+
fn match_score(d_l: Real, d_a: Real, d_b: Real) -> Real
|
|
17
|
+
where chain_order <= 1
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
ensures (result <= 1.0)
|
|
20
|
+
{
|
|
21
|
+
let dist: Real = sqrt(d_l * d_l + d_a * d_a + d_b * d_b);
|
|
22
|
+
clamp(1.0 - dist / S_MAX, 0.0, 1.0)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Detection probability at distance r given a match score.
|
|
26
|
+
// p_detect = (1 - match) * (1 / r²) * detector_sensitivity
|
|
27
|
+
@verify(lean, theorem = "texture_detect_in_unit_interval")
|
|
28
|
+
fn detection_probability(match_score: Real, r_m: Real,
|
|
29
|
+
detector_sensitivity: Real) -> Real
|
|
30
|
+
where chain_order <= 0,
|
|
31
|
+
domain: r_m > 0.0 && detector_sensitivity >= 0.0
|
|
32
|
+
requires (match_score >= 0.0)
|
|
33
|
+
requires (match_score <= 1.0)
|
|
34
|
+
ensures (result >= 0.0)
|
|
35
|
+
{
|
|
36
|
+
(1.0 - match_score) * detector_sensitivity / (r_m * r_m)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Ink-cloud blinding decay: visibility recovers exponentially.
|
|
40
|
+
// visibility(t) = 1 - exp(-t / τ)
|
|
41
|
+
@verify(lean, theorem = "ink_recovery_in_unit_interval")
|
|
42
|
+
fn ink_visibility(t_s: Real, tau_s: Real) -> Real
|
|
43
|
+
where chain_order <= 1,
|
|
44
|
+
domain: t_s >= 0.0 && tau_s > 0.0
|
|
45
|
+
ensures (result >= 0.0)
|
|
46
|
+
ensures (result <= 1.0)
|
|
47
|
+
{
|
|
48
|
+
1.0 - exp(-t_s / tau_s)
|
|
49
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// toxin.eml -- viper-bite damage-over-time model.
|
|
2
|
+
//
|
|
3
|
+
// Toxin concentration in the bloodstream follows first-order
|
|
4
|
+
// exponential clearance:
|
|
5
|
+
//
|
|
6
|
+
// C(t) = C_0 * exp(-k * t)
|
|
7
|
+
//
|
|
8
|
+
// Damage rate is proportional to concentration above the LD50
|
|
9
|
+
// threshold; integral over the wound's active window yields total
|
|
10
|
+
// damage. Chain order 1.
|
|
11
|
+
|
|
12
|
+
module toxin;
|
|
13
|
+
|
|
14
|
+
@verify(lean, theorem = "toxin_concentration_nonneg")
|
|
15
|
+
fn concentration(dose: Real, k_clearance: Real, t_seconds: Real) -> Real
|
|
16
|
+
where chain_order <= 1,
|
|
17
|
+
domain: dose >= 0.0 && k_clearance >= 0.0 && t_seconds >= 0.0
|
|
18
|
+
ensures (result >= 0.0)
|
|
19
|
+
{
|
|
20
|
+
dose * exp(-k_clearance * t_seconds)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Instantaneous damage rate (HP per second).
|
|
24
|
+
@verify(lean, theorem = "toxin_damage_rate_nonneg")
|
|
25
|
+
fn damage_rate(dose: Real, k_clearance: Real, t_seconds: Real,
|
|
26
|
+
toxicity: Real) -> Real
|
|
27
|
+
where chain_order <= 1,
|
|
28
|
+
domain: dose >= 0.0 && k_clearance >= 0.0 && t_seconds >= 0.0 && toxicity >= 0.0
|
|
29
|
+
ensures (result >= 0.0)
|
|
30
|
+
{
|
|
31
|
+
toxicity * dose * exp(-k_clearance * t_seconds)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Total damage integrated from t=0 to t=T.
|
|
35
|
+
// D = (toxicity * dose / k) * (1 - exp(-k*T))
|
|
36
|
+
@verify(lean, theorem = "toxin_total_damage_nonneg")
|
|
37
|
+
fn total_damage(dose: Real, k_clearance: Real, duration_s: Real,
|
|
38
|
+
toxicity: Real) -> Real
|
|
39
|
+
where chain_order <= 1,
|
|
40
|
+
domain: dose >= 0.0 && k_clearance > 0.0 && duration_s >= 0.0 && toxicity >= 0.0
|
|
41
|
+
ensures (result >= 0.0)
|
|
42
|
+
{
|
|
43
|
+
(toxicity * dose / k_clearance) * (1.0 - exp(-k_clearance * duration_s))
|
|
44
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// cone_l.eml
|
|
2
|
+
//
|
|
3
|
+
// Feline long-wavelength (L, "green-yellow") cone spectral
|
|
4
|
+
// sensitivity. This is the cone that *almost* sees red — its
|
|
5
|
+
// peak at 554 nm is shifted ~10 nm shorter than the human L
|
|
6
|
+
// cone's 564 nm peak, which is why a red ball looks olive-yellow
|
|
7
|
+
// to a cat: the response in the deep-red region (>600 nm) tails
|
|
8
|
+
// off much faster.
|
|
9
|
+
//
|
|
10
|
+
// Source: Loop et al. (1987) and the Govardovskii et al. (2000)
|
|
11
|
+
// visual-pigment template. λ_peak = 554 nm. σ ≈ 50 nm (FWHM
|
|
12
|
+
// ≈ 118 nm), reflecting the wider long-wavelength tail of L
|
|
13
|
+
// cones — without this the model would render deep red as
|
|
14
|
+
// absolute black instead of the dark olive cats actually see.
|
|
15
|
+
//
|
|
16
|
+
// L(λ) = exp(-((λ - 554)² / (2 · 50²)))
|
|
17
|
+
//
|
|
18
|
+
// This file does NOT define a "red" / M-wavelength cone — cats
|
|
19
|
+
// don't have one, and adding a third cone here would silently
|
|
20
|
+
// turn the model into a trichromat. Postcondition keeps the
|
|
21
|
+
// output bounded.
|
|
22
|
+
|
|
23
|
+
module cone_l;
|
|
24
|
+
|
|
25
|
+
const CONE_L_PEAK_NM: Real = 554.0
|
|
26
|
+
const CONE_L_SIGMA_NM: Real = 50.0
|
|
27
|
+
|
|
28
|
+
@verify(lean, theorem = "cone_l_sensitivity_bounded_unit")
|
|
29
|
+
fn cone_l_sensitivity(wavelength_nm: Real) -> Real
|
|
30
|
+
where chain_order <= 1
|
|
31
|
+
ensures (result >= 0.0)
|
|
32
|
+
ensures (result <= 1.0)
|
|
33
|
+
{
|
|
34
|
+
exp(-((wavelength_nm - CONE_L_PEAK_NM) * (wavelength_nm - CONE_L_PEAK_NM))
|
|
35
|
+
/ (2.0 * CONE_L_SIGMA_NM * CONE_L_SIGMA_NM))
|
|
36
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// cone_s.eml
|
|
2
|
+
//
|
|
3
|
+
// Feline short-wavelength (S, "blue") cone spectral sensitivity.
|
|
4
|
+
// Cats are dichromats — only S and L cones, no medium-wavelength
|
|
5
|
+
// "red" cone. The S cone is the cleaner of the two; its peak at
|
|
6
|
+
// 450 nm is well-isolated from the L cone at 554 nm.
|
|
7
|
+
//
|
|
8
|
+
// Source: Loop et al. (1987), "Cone receptor population in the
|
|
9
|
+
// adult cat retina." Peak λ ≈ 450 nm, FWHM ≈ 70 nm → σ ≈ 30 nm
|
|
10
|
+
// (the cone curve is narrower than the rod's because cone opsins
|
|
11
|
+
// have tighter absorption profiles).
|
|
12
|
+
//
|
|
13
|
+
// S(λ) = exp(-((λ - 450)² / (2 · 30²)))
|
|
14
|
+
//
|
|
15
|
+
// Postcondition pins the sensitivity to [0, 1]; values outside
|
|
16
|
+
// would corrupt every downstream channel of the cat-vision
|
|
17
|
+
// transform.
|
|
18
|
+
|
|
19
|
+
module cone_s;
|
|
20
|
+
|
|
21
|
+
const CONE_S_PEAK_NM: Real = 450.0
|
|
22
|
+
const CONE_S_SIGMA_NM: Real = 30.0
|
|
23
|
+
|
|
24
|
+
@verify(lean, theorem = "cone_s_sensitivity_bounded_unit")
|
|
25
|
+
fn cone_s_sensitivity(wavelength_nm: Real) -> Real
|
|
26
|
+
where chain_order <= 1
|
|
27
|
+
ensures (result >= 0.0)
|
|
28
|
+
ensures (result <= 1.0)
|
|
29
|
+
{
|
|
30
|
+
exp(-((wavelength_nm - CONE_S_PEAK_NM) * (wavelength_nm - CONE_S_PEAK_NM))
|
|
31
|
+
/ (2.0 * CONE_S_SIGMA_NM * CONE_S_SIGMA_NM))
|
|
32
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// cryptochrome.eml
|
|
2
|
+
//
|
|
3
|
+
// Cryptochrome radical-pair magnetoreception. Cryptochrome (CRY)
|
|
4
|
+
// is a flavoprotein in the bird retina; absorbing a blue-light
|
|
5
|
+
// photon excites a flavin/tryptophan radical pair whose singlet
|
|
6
|
+
// vs. triplet recombination yield depends on the orientation of
|
|
7
|
+
// the geomagnetic field relative to the molecular axis:
|
|
8
|
+
//
|
|
9
|
+
// Φ(B, θ) = 1/3 + (2/15) · (γ · B · τ)² · (3·cos²(θ) - 1)
|
|
10
|
+
//
|
|
11
|
+
// θ is the angle between the field vector and the radical-pair
|
|
12
|
+
// axis; γ is the gyromagnetic ratio (~28 GHz/T); τ is the
|
|
13
|
+
// recombination lifetime (~µs). The term `3·cos²(θ) - 1` is the
|
|
14
|
+
// signature P₂ Legendre polynomial — the same anisotropy
|
|
15
|
+
// signature seen in NMR and EPR spectra. This is "quantum
|
|
16
|
+
// biology" in the strict sense: the spin-coherence of the
|
|
17
|
+
// radical pair has to outlast environmental decoherence to
|
|
18
|
+
// transmit a usable signal (Hore & Mouritsen 2016).
|
|
19
|
+
//
|
|
20
|
+
// Postcondition: Φ ∈ [0, 1] — it's a quantum yield, a probability.
|
|
21
|
+
|
|
22
|
+
module cryptochrome;
|
|
23
|
+
|
|
24
|
+
const ONE_THIRD: Real = 0.3333333333333333
|
|
25
|
+
|
|
26
|
+
@verify(lean, theorem = "cryptochrome_yield_in_unit_interval")
|
|
27
|
+
fn cryptochrome_yield(
|
|
28
|
+
field_strength_T: Real{b | b >= 0.0},
|
|
29
|
+
angle_rad: Real,
|
|
30
|
+
coupling: Real{c | c >= 0.0},
|
|
31
|
+
) -> Real{r | 0.0 <= r && r <= 1.0}
|
|
32
|
+
where chain_order <= 1
|
|
33
|
+
{
|
|
34
|
+
// (γ·B·τ)² is bundled into `coupling` for clean units; the
|
|
35
|
+
// model is qualitative, not quantitative, in this file (the
|
|
36
|
+
// exact pre-factor is a tuning parameter for a given CRY
|
|
37
|
+
// isoform). The clamp pins the postcondition.
|
|
38
|
+
clamp(
|
|
39
|
+
ONE_THIRD + (2.0 / 15.0) * coupling
|
|
40
|
+
* (3.0 * cos(angle_rad) * cos(angle_rad) - 1.0),
|
|
41
|
+
0.0,
|
|
42
|
+
1.0
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// geomagnetic_field.eml
|
|
2
|
+
//
|
|
3
|
+
// Earth's magnetic field magnitude as a function of geomagnetic
|
|
4
|
+
// latitude. Modeled as a centered axial dipole — the leading-order
|
|
5
|
+
// approximation that captures ~95 % of the surface field:
|
|
6
|
+
//
|
|
7
|
+
// B(λ) = B_eq · √(1 + 3 · sin²(λ))
|
|
8
|
+
//
|
|
9
|
+
// B_eq ≈ 30 µT at the equator; the field is twice as strong
|
|
10
|
+
// (~60 µT) at the poles where field lines converge. Pigeons
|
|
11
|
+
// navigate using the *magnitude* component of this map for the
|
|
12
|
+
// "map" sense and the inclination angle (separate file) for
|
|
13
|
+
// "compass" — the famous Wiltschko (1972) bicomponent model.
|
|
14
|
+
//
|
|
15
|
+
// Postcondition: B > 0 everywhere on Earth's surface (the dipole
|
|
16
|
+
// has no zeros). The Cryptochrome model downstream takes B as
|
|
17
|
+
// an input and would divide-by-zero on a vanishing field.
|
|
18
|
+
|
|
19
|
+
module geomagnetic_field;
|
|
20
|
+
|
|
21
|
+
const B_EQUATORIAL_T: Real = 3.0e-5 // 30 µT — IGRF equatorial baseline
|
|
22
|
+
|
|
23
|
+
@verify(lean, theorem = "geomagnetic_field_strictly_positive")
|
|
24
|
+
fn geomagnetic_field(latitude_rad: Real)
|
|
25
|
+
-> Real{r | r >= B_EQUATORIAL_T}
|
|
26
|
+
where chain_order <= 1
|
|
27
|
+
{
|
|
28
|
+
// sqrt(1 + 3·sin²) ≥ 1 ⇒ result ≥ B_eq. We use max() as the
|
|
29
|
+
// outer guard so the postcondition is one-line provable.
|
|
30
|
+
max(B_EQUATORIAL_T,
|
|
31
|
+
B_EQUATORIAL_T * sqrt(1.0 + 3.0 * sin(latitude_rad) * sin(latitude_rad)))
|
|
32
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// goldman_equation.eml -- Goldman-Hodgkin-Katz voltage equation.
|
|
2
|
+
//
|
|
3
|
+
// Resting membrane potential when multiple ions cross a permeable
|
|
4
|
+
// membrane:
|
|
5
|
+
//
|
|
6
|
+
// V_m = (RT / F) * ln( (P_K[K+]_o + P_Na[Na+]_o + P_Cl[Cl-]_i)
|
|
7
|
+
// / (P_K[K+]_i + P_Na[Na+]_i + P_Cl[Cl-]_o) )
|
|
8
|
+
//
|
|
9
|
+
// The single-ion limit collapses to the Nernst equation. Chain
|
|
10
|
+
// order 1.
|
|
11
|
+
|
|
12
|
+
module goldman_equation;
|
|
13
|
+
|
|
14
|
+
const R_GAS: Real = 8.314462618 // J / (mol K)
|
|
15
|
+
const F_FAR: Real = 96485.33212 // C / mol
|
|
16
|
+
|
|
17
|
+
// Three-ion form (K+, Na+, Cl-) at body temperature.
|
|
18
|
+
@verify(lean, theorem = "goldman_voltage_sign_matches_log_argument")
|
|
19
|
+
fn membrane_potential(t_kelvin: Real,
|
|
20
|
+
p_k: Real, k_out: Real, k_in: Real,
|
|
21
|
+
p_na: Real, na_out: Real, na_in: Real,
|
|
22
|
+
p_cl: Real, cl_out: Real, cl_in: Real) -> Real
|
|
23
|
+
where chain_order <= 1,
|
|
24
|
+
domain: t_kelvin > 0.0
|
|
25
|
+
{
|
|
26
|
+
let num = p_k * k_out + p_na * na_out + p_cl * cl_in;
|
|
27
|
+
let den = p_k * k_in + p_na * na_in + p_cl * cl_out;
|
|
28
|
+
(R_GAS * t_kelvin / F_FAR) * ln(num / den)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Nernst equation: single-ion limit.
|
|
32
|
+
@verify(lean, theorem = "nernst_voltage_sign_matches_concentration_ratio")
|
|
33
|
+
fn nernst(t_kelvin: Real, valence: Real, c_out: Real, c_in: Real) -> Real
|
|
34
|
+
where chain_order <= 1,
|
|
35
|
+
domain: t_kelvin > 0.0 && valence != 0.0 && c_in > 0.0
|
|
36
|
+
{
|
|
37
|
+
(R_GAS * t_kelvin / (valence * F_FAR)) * ln(c_out / c_in)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Body-temperature pre-factor RT/F at 37 deg C, in mV.
|
|
41
|
+
const RT_OVER_F_BODY_MV: Real = 26.7148
|