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.
Files changed (185) hide show
  1. eml_stdlib/__init__.py +81 -0
  2. eml_stdlib/ballistics/air_density.eml +73 -0
  3. eml_stdlib/ballistics/ballistic_solver.eml +102 -0
  4. eml_stdlib/ballistics/coriolis.eml +60 -0
  5. eml_stdlib/ballistics/drag.eml +44 -0
  6. eml_stdlib/ballistics/gravity.eml +52 -0
  7. eml_stdlib/ballistics/muzzle_velocity.eml +42 -0
  8. eml_stdlib/ballistics/spin_drift.eml +60 -0
  9. eml_stdlib/ballistics/time_of_flight.eml +79 -0
  10. eml_stdlib/ballistics/wind.eml +58 -0
  11. eml_stdlib/biology/abilities/ballistic_armor.eml +61 -0
  12. eml_stdlib/biology/abilities/bite_force.eml +48 -0
  13. eml_stdlib/biology/abilities/chromatophore.eml +50 -0
  14. eml_stdlib/biology/abilities/discharge.eml +45 -0
  15. eml_stdlib/biology/abilities/elastic_energy.eml +42 -0
  16. eml_stdlib/biology/abilities/locomotion.eml +50 -0
  17. eml_stdlib/biology/abilities/muscle_power.eml +47 -0
  18. eml_stdlib/biology/abilities/sprint.eml +36 -0
  19. eml_stdlib/biology/abilities/texture_match.eml +49 -0
  20. eml_stdlib/biology/abilities/toxin.eml +44 -0
  21. eml_stdlib/biology/cone_l.eml +36 -0
  22. eml_stdlib/biology/cone_s.eml +32 -0
  23. eml_stdlib/biology/cryptochrome.eml +44 -0
  24. eml_stdlib/biology/geomagnetic_field.eml +32 -0
  25. eml_stdlib/biology/goldman_equation.eml +41 -0
  26. eml_stdlib/biology/hill_equation.eml +65 -0
  27. eml_stdlib/biology/hodgkin_huxley.eml +108 -0
  28. eml_stdlib/biology/michaelis_menten.eml +65 -0
  29. eml_stdlib/biology/plume_diffusion.eml +47 -0
  30. eml_stdlib/biology/receptor_binding.eml +35 -0
  31. eml_stdlib/biology/rod_sensitivity.eml +38 -0
  32. eml_stdlib/biology/tapetum.eml +70 -0
  33. eml_stdlib/carriers/acoustic_cloak.eml +56 -0
  34. eml_stdlib/carriers/ferron_emission.eml +55 -0
  35. eml_stdlib/carriers/ferron_logic.eml +45 -0
  36. eml_stdlib/carriers/ferron_propagation.eml +69 -0
  37. eml_stdlib/carriers/mach_zehnder.eml +52 -0
  38. eml_stdlib/carriers/magnon_dispersion.eml +57 -0
  39. eml_stdlib/carriers/magnon_logic.eml +45 -0
  40. eml_stdlib/carriers/optical_neuron.eml +55 -0
  41. eml_stdlib/carriers/phonon_bandgap.eml +54 -0
  42. eml_stdlib/carriers/ring_resonator.eml +48 -0
  43. eml_stdlib/carriers/spin_torque.eml +61 -0
  44. eml_stdlib/carriers/thermal_rectifier.eml +63 -0
  45. eml_stdlib/catalog.json +4209 -0
  46. eml_stdlib/circuits/buck_converter.eml +74 -0
  47. eml_stdlib/circuits/cmos_inverter.eml +75 -0
  48. eml_stdlib/circuits/h_bridge.eml +69 -0
  49. eml_stdlib/circuits/ldo.eml +66 -0
  50. eml_stdlib/circuits/mosfet_iv.eml +59 -0
  51. eml_stdlib/circuits/pll_loop.eml +63 -0
  52. eml_stdlib/circuits/rc_filter.eml +98 -0
  53. eml_stdlib/circuits/voltage_divider.eml +68 -0
  54. eml_stdlib/control/bangbang.eml +34 -0
  55. eml_stdlib/control/deadband.eml +28 -0
  56. eml_stdlib/control/hysteresis.eml +28 -0
  57. eml_stdlib/control/kalman_1d.eml +53 -0
  58. eml_stdlib/control/lqr_1d.eml +47 -0
  59. eml_stdlib/control/pid.eml +38 -0
  60. eml_stdlib/gaming/animation/bounce.eml +39 -0
  61. eml_stdlib/gaming/animation/ease_in.eml +27 -0
  62. eml_stdlib/gaming/animation/ease_in_out.eml +29 -0
  63. eml_stdlib/gaming/animation/ease_out.eml +27 -0
  64. eml_stdlib/gaming/animation/ik_2bone.eml +57 -0
  65. eml_stdlib/gaming/animation/spring.eml +39 -0
  66. eml_stdlib/gaming/audio/distance_attenuation.eml +50 -0
  67. eml_stdlib/gaming/audio/doppler.eml +39 -0
  68. eml_stdlib/gaming/audio/lowpass_audio.eml +32 -0
  69. eml_stdlib/gaming/audio/reverb_delay.eml +41 -0
  70. eml_stdlib/gaming/camera/dof_blur.eml +30 -0
  71. eml_stdlib/gaming/camera/fov_projection.eml +47 -0
  72. eml_stdlib/gaming/camera/orbit.eml +48 -0
  73. eml_stdlib/gaming/camera/shake.eml +20 -0
  74. eml_stdlib/gaming/lighting/ambient_occlusion.eml +40 -0
  75. eml_stdlib/gaming/lighting/area_light.eml +50 -0
  76. eml_stdlib/gaming/lighting/fog_exp.eml +28 -0
  77. eml_stdlib/gaming/lighting/fog_exp2.eml +30 -0
  78. eml_stdlib/gaming/lighting/point_light.eml +33 -0
  79. eml_stdlib/gaming/lighting/spot_light.eml +43 -0
  80. eml_stdlib/gaming/noise/fbm.eml +65 -0
  81. eml_stdlib/gaming/noise/perlin_2d.eml +65 -0
  82. eml_stdlib/gaming/noise/perlin_3d.eml +63 -0
  83. eml_stdlib/gaming/noise/simplex_2d.eml +67 -0
  84. eml_stdlib/gaming/noise/turbulence.eml +46 -0
  85. eml_stdlib/gaming/noise/value_noise.eml +45 -0
  86. eml_stdlib/gaming/noise/voronoi_2d.eml +83 -0
  87. eml_stdlib/gaming/noise/white_noise.eml +47 -0
  88. eml_stdlib/gaming/noise/worley.eml +79 -0
  89. eml_stdlib/gaming/particles/drag.eml +30 -0
  90. eml_stdlib/gaming/particles/emitter_radial.eml +35 -0
  91. eml_stdlib/gaming/particles/fade.eml +36 -0
  92. eml_stdlib/gaming/particles/gravity_particle.eml +32 -0
  93. eml_stdlib/gaming/particles/size_over_life.eml +28 -0
  94. eml_stdlib/gaming/physics/aabb_overlap.eml +38 -0
  95. eml_stdlib/gaming/physics/buoyancy.eml +43 -0
  96. eml_stdlib/gaming/physics/circle_collision.eml +46 -0
  97. eml_stdlib/gaming/physics/impulse_resolve.eml +39 -0
  98. eml_stdlib/gaming/physics/rigid_body_2d.eml +61 -0
  99. eml_stdlib/gaming/physics/spring_damper.eml +30 -0
  100. eml_stdlib/gaming/physics/verlet.eml +29 -0
  101. eml_stdlib/gaming/shading/fresnel.eml +57 -0
  102. eml_stdlib/gaming/shading/matcap.eml +34 -0
  103. eml_stdlib/gaming/shading/pbr_diffuse.eml +41 -0
  104. eml_stdlib/gaming/shading/pbr_specular.eml +75 -0
  105. eml_stdlib/gaming/shading/subsurface.eml +52 -0
  106. eml_stdlib/gaming/shading/toon.eml +30 -0
  107. eml_stdlib/gaming/terrain/biome_select.eml +41 -0
  108. eml_stdlib/gaming/terrain/cliff_detect.eml +35 -0
  109. eml_stdlib/gaming/terrain/erosion_hydraulic.eml +57 -0
  110. eml_stdlib/gaming/terrain/erosion_thermal.eml +44 -0
  111. eml_stdlib/gaming/terrain/heightmap.eml +33 -0
  112. eml_stdlib/gaming/textures/brick.eml +27 -0
  113. eml_stdlib/gaming/textures/caustics.eml +25 -0
  114. eml_stdlib/gaming/textures/checkerboard.eml +27 -0
  115. eml_stdlib/gaming/textures/fire.eml +26 -0
  116. eml_stdlib/gaming/textures/marble.eml +24 -0
  117. eml_stdlib/gaming/textures/rust.eml +23 -0
  118. eml_stdlib/gaming/textures/water_surface.eml +49 -0
  119. eml_stdlib/gaming/textures/wood.eml +41 -0
  120. eml_stdlib/math/abs.eml +45 -0
  121. eml_stdlib/math/atan.eml +32 -0
  122. eml_stdlib/math/atan2.eml +61 -0
  123. eml_stdlib/math/bessel_j0.eml +65 -0
  124. eml_stdlib/math/cos.eml +42 -0
  125. eml_stdlib/math/cosh.eml +24 -0
  126. eml_stdlib/math/erf.eml +66 -0
  127. eml_stdlib/math/exp.eml +49 -0
  128. eml_stdlib/math/gamma.eml +55 -0
  129. eml_stdlib/math/gaussian.eml +18 -0
  130. eml_stdlib/math/log.eml +59 -0
  131. eml_stdlib/math/pow.eml +51 -0
  132. eml_stdlib/math/relu.eml +63 -0
  133. eml_stdlib/math/sigmoid.eml +21 -0
  134. eml_stdlib/math/sin.eml +37 -0
  135. eml_stdlib/math/sinh.eml +24 -0
  136. eml_stdlib/math/softmax.eml +87 -0
  137. eml_stdlib/math/sqrt.eml +46 -0
  138. eml_stdlib/math/tan.eml +36 -0
  139. eml_stdlib/math/tanh.eml +54 -0
  140. eml_stdlib/ml/attention.eml +70 -0
  141. eml_stdlib/ml/cross_entropy.eml +85 -0
  142. eml_stdlib/ml/gelu.eml +51 -0
  143. eml_stdlib/ml/layernorm.eml +49 -0
  144. eml_stdlib/ml/leaky_relu.eml +51 -0
  145. eml_stdlib/ml/relu.eml +63 -0
  146. eml_stdlib/ml/rotary_embed.eml +64 -0
  147. eml_stdlib/ml/silu.eml +40 -0
  148. eml_stdlib/ml/softmax.eml +87 -0
  149. eml_stdlib/physics/coulomb.eml +48 -0
  150. eml_stdlib/physics/damped_oscillator.eml +20 -0
  151. eml_stdlib/physics/diffusion.eml +45 -0
  152. eml_stdlib/physics/gravity.eml +60 -0
  153. eml_stdlib/physics/hooke.eml +56 -0
  154. eml_stdlib/physics/navier_stokes_1d.eml +57 -0
  155. eml_stdlib/physics/planck_radiation.eml +77 -0
  156. eml_stdlib/physics/stefan_boltzmann.eml +75 -0
  157. eml_stdlib/physics/wave_propagation.eml +52 -0
  158. eml_stdlib/quantum/cnot.eml +72 -0
  159. eml_stdlib/quantum/grover_oracle.eml +69 -0
  160. eml_stdlib/quantum/hadamard.eml +73 -0
  161. eml_stdlib/quantum/pauli_x.eml +29 -0
  162. eml_stdlib/quantum/pauli_z.eml +29 -0
  163. eml_stdlib/quantum/phase_gate.eml +76 -0
  164. eml_stdlib/quantum/rotation_rz.eml +92 -0
  165. eml_stdlib/sensors/accelerometer.eml +59 -0
  166. eml_stdlib/sensors/hall_effect.eml +42 -0
  167. eml_stdlib/sensors/photodetector.eml +58 -0
  168. eml_stdlib/sensors/rtd.eml +57 -0
  169. eml_stdlib/sensors/strain_gauge.eml +63 -0
  170. eml_stdlib/sensors/thermistor.eml +71 -0
  171. eml_stdlib/signal/bandpass.eml +39 -0
  172. eml_stdlib/signal/chirp.eml +42 -0
  173. eml_stdlib/signal/dft_single.eml +30 -0
  174. eml_stdlib/signal/fft_butterfly.eml +36 -0
  175. eml_stdlib/signal/highpass.eml +45 -0
  176. eml_stdlib/signal/lowpass.eml +49 -0
  177. eml_stdlib/signal/matched_filter.eml +48 -0
  178. eml_stdlib/signal/notch_filter.eml +47 -0
  179. eml_stdlib/signal/window_hamming.eml +47 -0
  180. eml_stdlib/signal/window_hann.eml +50 -0
  181. eml_stdlib-0.4.0.dist-info/METADATA +453 -0
  182. eml_stdlib-0.4.0.dist-info/RECORD +185 -0
  183. eml_stdlib-0.4.0.dist-info/WHEEL +5 -0
  184. eml_stdlib-0.4.0.dist-info/licenses/LICENSE +21 -0
  185. eml_stdlib-0.4.0.dist-info/top_level.txt +1 -0
eml_stdlib/__init__.py ADDED
@@ -0,0 +1,81 @@
1
+ """eml-stdlib -- EML standard-library prototype.
2
+
3
+ This package ships *.eml source files (not Python modules). Use
4
+ the helpers below to locate them so a Forge project can wire them
5
+ into its `use stdlib::...` resolver.
6
+
7
+ Example:
8
+
9
+ from eml_stdlib import path_to, list_modules, load_catalog
10
+
11
+ sigmoid = path_to('math/sigmoid.eml') # absolute Path
12
+ math_dir = path_to('math') # category directory
13
+ print(load_catalog()[0]) # introspection metadata
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import json
19
+ from pathlib import Path
20
+ from typing import Any
21
+
22
+ __version__ = "0.4.0"
23
+
24
+ _PACKAGE_ROOT = Path(__file__).resolve().parent
25
+ _CATALOG_PATH = _PACKAGE_ROOT / "catalog.json"
26
+
27
+
28
+ def root() -> Path:
29
+ """Return the directory holding all category subfolders."""
30
+ return _PACKAGE_ROOT
31
+
32
+
33
+ def path_to(relative: str) -> Path:
34
+ """Resolve a path inside the stdlib (file or directory).
35
+
36
+ Raises FileNotFoundError if the resolved path doesn't exist.
37
+ """
38
+ target = (_PACKAGE_ROOT / relative).resolve()
39
+ if not str(target).startswith(str(_PACKAGE_ROOT)):
40
+ raise ValueError(f"path escapes stdlib: {relative}")
41
+ if not target.exists():
42
+ raise FileNotFoundError(target)
43
+ return target
44
+
45
+
46
+ def list_modules(category: str | None = None) -> list[Path]:
47
+ """Return paths to every .eml file in the stdlib (or one category).
48
+
49
+ `category` accepts both flat ("math") and nested ("gaming/noise")
50
+ paths. Nested subcategories under gaming/ are picked up via rglob
51
+ so `list_modules("gaming")` returns all 60 gaming kernels.
52
+ """
53
+ if category is None:
54
+ return sorted(_PACKAGE_ROOT.rglob("*.eml"))
55
+ cat_dir = _PACKAGE_ROOT / category
56
+ if not cat_dir.is_dir():
57
+ raise FileNotFoundError(cat_dir)
58
+ return sorted(cat_dir.rglob("*.eml"))
59
+
60
+
61
+ def categories() -> list[str]:
62
+ """Return the names of every shipped category subdir."""
63
+ return sorted(
64
+ p.name for p in _PACKAGE_ROOT.iterdir()
65
+ if p.is_dir() and not p.name.startswith("_") and not p.name.startswith(".")
66
+ )
67
+
68
+
69
+ def load_catalog() -> list[dict[str, Any]]:
70
+ """Return the parsed catalog (function-level introspection)."""
71
+ return json.loads(_CATALOG_PATH.read_text(encoding="utf-8"))
72
+
73
+
74
+ __all__ = [
75
+ "__version__",
76
+ "root",
77
+ "path_to",
78
+ "list_modules",
79
+ "categories",
80
+ "load_catalog",
81
+ ]
@@ -0,0 +1,73 @@
1
+ // air_density.eml -- atmospheric density model.
2
+ //
3
+ // Density falls off roughly exponentially with altitude:
4
+ //
5
+ // ρ(alt) = ρ₀ * exp(-alt / scale_height)
6
+ //
7
+ // Plus temperature and humidity corrections (ideal gas + saturation
8
+ // vapour pressure).
9
+ //
10
+ // Chain order 1 (one exp). Reference values: ρ₀ = 1.225 kg/m³ at
11
+ // sea level, 15 °C, dry air; scale height ~8434 m.
12
+
13
+ module air_density;
14
+
15
+ const RHO_0_SEA_LEVEL: Real = 1.225 // kg/m³, dry air, 15°C
16
+ const SCALE_HEIGHT: Real = 8434.0 // m
17
+ const T_REF_K: Real = 288.15 // 15°C in kelvin
18
+ const R_DRY_AIR: Real = 287.058 // J / (kg K)
19
+ const R_WATER: Real = 461.495 // J / (kg K)
20
+
21
+ // Pure-altitude isothermal model.
22
+ @verify(lean, theorem = "air_density_decreases_with_altitude")
23
+ fn density_isothermal(alt_m: Real) -> Real
24
+ where chain_order <= 1,
25
+ domain: alt_m >= 0.0
26
+ ensures (result > 0.0)
27
+ ensures (result <= RHO_0_SEA_LEVEL)
28
+ {
29
+ RHO_0_SEA_LEVEL * exp(-alt_m / SCALE_HEIGHT)
30
+ }
31
+
32
+ // Temperature correction: ρ scales inversely with absolute T at
33
+ // fixed pressure.
34
+ @verify(lean, theorem = "air_density_inverse_temperature")
35
+ fn density_with_temperature(alt_m: Real, t_kelvin: Real) -> Real
36
+ where chain_order <= 1,
37
+ domain: alt_m >= 0.0 && t_kelvin > 0.0
38
+ ensures (result > 0.0)
39
+ {
40
+ let rho_iso: Real = RHO_0_SEA_LEVEL * exp(-alt_m / SCALE_HEIGHT);
41
+ rho_iso * T_REF_K / t_kelvin
42
+ }
43
+
44
+ // Humid-air density: dry-air density minus the water-vapour offset.
45
+ // p_water is saturation vapour pressure (Pa) × relative humidity in [0,1].
46
+ @verify(lean, theorem = "air_density_humid_lower_than_dry")
47
+ fn density_humid(alt_m: Real, t_kelvin: Real,
48
+ pressure_pa: Real, humidity_fraction: Real) -> Real
49
+ where chain_order <= 1,
50
+ domain: alt_m >= 0.0 && t_kelvin > 0.0 && pressure_pa > 0.0
51
+ requires (humidity_fraction >= 0.0)
52
+ requires (humidity_fraction <= 1.0)
53
+ ensures (result > 0.0)
54
+ {
55
+ // Saturation vapour pressure (Magnus formula): in Pa, given T in °C.
56
+ let t_c: Real = t_kelvin - 273.15;
57
+ let p_sat: Real = 610.78 * exp(17.27 * t_c / (t_c + 237.3));
58
+ let p_water: Real = humidity_fraction * p_sat;
59
+ let p_dry: Real = pressure_pa - p_water;
60
+ p_dry / (R_DRY_AIR * t_kelvin) + p_water / (R_WATER * t_kelvin)
61
+ }
62
+
63
+ // Convert metres of altitude to a multiplier on sea-level density —
64
+ // useful for the "thinner air = less drag" UI bar.
65
+ @verify(lean, theorem = "density_ratio_in_unit_interval")
66
+ fn density_ratio(alt_m: Real) -> Real
67
+ where chain_order <= 1,
68
+ domain: alt_m >= 0.0
69
+ ensures (result > 0.0)
70
+ ensures (result <= 1.0)
71
+ {
72
+ exp(-alt_m / SCALE_HEIGHT)
73
+ }
@@ -0,0 +1,102 @@
1
+ // ballistic_solver.eml -- composes drag/gravity/wind/coriolis/spin
2
+ // into a single trajectory prediction.
3
+ //
4
+ // This is the kernel the longshot calculator UI calls per shot.
5
+ // Inputs: shooter state (velocity, BC, twist), environmental state
6
+ // (rho, wind vector, latitude, heading), target state (range).
7
+ // Outputs: drop (m), drift (m), eastward Coriolis component (m),
8
+ // spin drift (m), time of flight (s), impact velocity (m/s).
9
+ //
10
+ // Each output is reported separately so the post-shot analysis
11
+ // screen can show how much each force contributed.
12
+ //
13
+ // Chain order 1 across the composition (Pejsa-style log + density
14
+ // exp dominate the cost).
15
+
16
+ module ballistic_solver;
17
+
18
+ use stdlib::ballistics::drag;
19
+ use stdlib::ballistics::gravity;
20
+ use stdlib::ballistics::wind;
21
+ use stdlib::ballistics::coriolis;
22
+ use stdlib::ballistics::spin_drift;
23
+ use stdlib::ballistics::time_of_flight;
24
+
25
+ // Time of flight via the Pejsa form (single-step accurate).
26
+ @verify(lean, theorem = "solver_tof_nonneg")
27
+ fn solve_tof(range_m: Real, v_0: Real, k_retard: Real) -> Real
28
+ where chain_order <= 1,
29
+ domain: v_0 > 0.0 && k_retard > 0.0 && range_m >= 0.0
30
+ ensures (result >= 0.0)
31
+ {
32
+ time_of_flight::tof_pejsa(range_m, v_0, k_retard)
33
+ }
34
+
35
+ // Gravity drop given the time of flight.
36
+ @verify(lean, theorem = "solver_drop_nonneg")
37
+ fn solve_drop(g: Real, tof: Real) -> Real
38
+ where chain_order <= 0,
39
+ domain: g >= 0.0 && tof >= 0.0
40
+ ensures (result >= 0.0)
41
+ {
42
+ gravity::drop(g, tof)
43
+ }
44
+
45
+ // Crosswind drift.
46
+ @verify(lean, theorem = "solver_wind_drift_signed")
47
+ fn solve_wind_drift(v_wind_perp: Real, tof: Real) -> Real
48
+ where chain_order <= 0,
49
+ domain: tof >= 0.0
50
+ {
51
+ wind::drift(v_wind_perp, tof)
52
+ }
53
+
54
+ // Coriolis horizontal component.
55
+ @verify(lean, theorem = "solver_coriolis_signed")
56
+ fn solve_coriolis_horiz(v_0: Real, latitude_rad: Real, tof: Real) -> Real
57
+ where chain_order <= 1,
58
+ domain: tof >= 0.0
59
+ {
60
+ coriolis::horizontal_drift(v_0, latitude_rad, tof)
61
+ }
62
+
63
+ // Spin drift via Litz curve.
64
+ @verify(lean, theorem = "solver_spin_drift_nonneg")
65
+ fn solve_spin_drift(stability_factor: Real, tof: Real) -> Real
66
+ where chain_order <= 1,
67
+ domain: stability_factor > 0.0 && tof >= 0.0
68
+ ensures (result >= 0.0)
69
+ {
70
+ spin_drift::drift_metres_litz(stability_factor, tof)
71
+ }
72
+
73
+ // Impact velocity at the target via Pejsa retardation.
74
+ @verify(lean, theorem = "solver_impact_velocity_le_muzzle")
75
+ fn solve_impact_velocity(v_0: Real, k_retard: Real, range_m: Real) -> Real
76
+ where chain_order <= 1,
77
+ domain: v_0 > 0.0 && k_retard > 0.0 && range_m >= 0.0
78
+ ensures (result >= 0.0)
79
+ ensures (result <= v_0)
80
+ {
81
+ v_0 * exp(-range_m / k_retard)
82
+ }
83
+
84
+ // Total predicted hold-up in milliradians.
85
+ @verify(lean, theorem = "solver_hold_up_mil_nonneg")
86
+ fn solve_hold_up_mil(drop_m: Real, range_m: Real) -> Real
87
+ where chain_order <= 0,
88
+ domain: range_m > 0.0 && drop_m >= 0.0
89
+ ensures (result >= 0.0)
90
+ {
91
+ drop_m / range_m * 1000.0
92
+ }
93
+
94
+ // Total predicted hold-off in milliradians (wind + coriolis + spin).
95
+ @verify(lean, theorem = "solver_hold_off_mil_signed")
96
+ fn solve_hold_off_mil(wind_drift_m: Real, coriolis_m: Real, spin_drift_m: Real,
97
+ range_m: Real) -> Real
98
+ where chain_order <= 0,
99
+ domain: range_m > 0.0
100
+ {
101
+ (wind_drift_m + coriolis_m + spin_drift_m) / range_m * 1000.0
102
+ }
@@ -0,0 +1,60 @@
1
+ // coriolis.eml -- Coriolis deflection from Earth's rotation.
2
+ //
3
+ // At the latitudes long-range shooters operate, the Coriolis
4
+ // acceleration is small but non-negligible for shots beyond 1000 m.
5
+ // Two components:
6
+ //
7
+ // horizontal: 2 * Ω * sin(lat) * v (always rightward in
8
+ // the N hemisphere,
9
+ // leftward in S)
10
+ // vertical: 2 * Ω * cos(lat) * v * sin(heading)
11
+ // (Eötvös effect: more
12
+ // apparent drop east-
13
+ // firing, less west)
14
+ //
15
+ // where Ω is Earth's angular velocity and v is muzzle velocity.
16
+
17
+ module coriolis;
18
+
19
+ const OMEGA_EARTH: Real = 7.2921159e-5 // rad/s
20
+
21
+ // Horizontal Coriolis acceleration (m/s²). Sign convention: positive
22
+ // = rightward when facing the line-of-sight in the northern hemisphere.
23
+ @verify(lean, theorem = "coriolis_horizontal_signed")
24
+ fn horizontal_accel(v: Real, latitude_rad: Real) -> Real
25
+ where chain_order <= 1
26
+ {
27
+ 2.0 * OMEGA_EARTH * sin(latitude_rad) * v
28
+ }
29
+
30
+ // Vertical (Eötvös) Coriolis acceleration. heading_rad is azimuth
31
+ // of the line of sight, measured east of true north.
32
+ // positive result = bullet rises (apparent drop reduces)
33
+ // negative result = bullet drops faster (apparent drop increases)
34
+ @verify(lean, theorem = "coriolis_vertical_signed")
35
+ fn vertical_accel(v: Real, latitude_rad: Real, heading_rad: Real) -> Real
36
+ where chain_order <= 1
37
+ {
38
+ 2.0 * OMEGA_EARTH * cos(latitude_rad) * v * sin(heading_rad)
39
+ }
40
+
41
+ // Total horizontal drift over the time of flight. Linearised:
42
+ // x = ½ * a * t²
43
+ @verify(lean, theorem = "coriolis_drift_signed")
44
+ fn horizontal_drift(v: Real, latitude_rad: Real, time_of_flight: Real) -> Real
45
+ where chain_order <= 1,
46
+ domain: time_of_flight >= 0.0
47
+ {
48
+ 0.5 * 2.0 * OMEGA_EARTH * sin(latitude_rad) * v * time_of_flight * time_of_flight
49
+ }
50
+
51
+ // Vertical drift contribution.
52
+ @verify(lean, theorem = "coriolis_eotvos_signed")
53
+ fn vertical_drift(v: Real, latitude_rad: Real, heading_rad: Real,
54
+ time_of_flight: Real) -> Real
55
+ where chain_order <= 1,
56
+ domain: time_of_flight >= 0.0
57
+ {
58
+ 0.5 * 2.0 * OMEGA_EARTH * cos(latitude_rad) * v * sin(heading_rad)
59
+ * time_of_flight * time_of_flight
60
+ }
@@ -0,0 +1,44 @@
1
+ // drag.eml -- quadratic aerodynamic drag.
2
+ //
3
+ // F_drag = ½ * rho * v² * C_d * A
4
+ //
5
+ // where rho is air density [kg/m³], v is the airspeed magnitude
6
+ // [m/s], C_d the drag coefficient (dimensionless), and A the
7
+ // reference area [m²].
8
+ //
9
+ // Chain order 0 — pure polynomial in v. The exponential-tail
10
+ // regimes (transonic + supersonic) are folded into C_d, kept as
11
+ // a piecewise table outside this kernel.
12
+
13
+ module drag;
14
+
15
+ @verify(lean, theorem = "drag_force_nonneg")
16
+ fn drag_force(rho: Real, v: Real, c_d: Real, area: Real) -> Real
17
+ where chain_order <= 0,
18
+ domain: rho >= 0.0 && c_d >= 0.0 && area >= 0.0
19
+ ensures (result >= 0.0)
20
+ {
21
+ 0.5 * rho * v * v * c_d * area
22
+ }
23
+
24
+ // Drag deceleration: a_drag = F_drag / m.
25
+ @verify(lean, theorem = "drag_decel_nonneg")
26
+ fn drag_deceleration(rho: Real, v: Real, c_d: Real, area: Real, mass: Real) -> Real
27
+ where chain_order <= 0,
28
+ domain: rho >= 0.0 && c_d >= 0.0 && area >= 0.0 && mass > 0.0
29
+ ensures (result >= 0.0)
30
+ {
31
+ 0.5 * rho * v * v * c_d * area / mass
32
+ }
33
+
34
+ // Ballistic coefficient form: BC = m / (C_d * A). BC encodes the
35
+ // projectile's resistance to drag in a single number; ammunition
36
+ // manufacturers publish BC instead of C_d/A separately.
37
+ @verify(lean, theorem = "drag_decel_from_bc_nonneg")
38
+ fn drag_decel_from_bc(rho: Real, v: Real, ballistic_coeff: Real) -> Real
39
+ where chain_order <= 0,
40
+ domain: rho >= 0.0 && ballistic_coeff > 0.0
41
+ ensures (result >= 0.0)
42
+ {
43
+ 0.5 * rho * v * v / ballistic_coeff
44
+ }
@@ -0,0 +1,52 @@
1
+ // gravity.eml -- ballistic drop from gravity, no drag coupling.
2
+ //
3
+ // Δy = ½ * g * t²
4
+ //
5
+ // Chain order 0 (pure polynomial). g is positive downward by
6
+ // convention, so the returned drop is a positive distance below
7
+ // the line-of-sight. The full coupled-with-drag drop comes from
8
+ // time_of_flight + this kernel: the ballistic_solver evaluates
9
+ // `drop(g, time_of_flight(...))`.
10
+
11
+ module gravity;
12
+
13
+ const G_EARTH: Real = 9.80665 // standard gravity (m/s²)
14
+
15
+ @verify(lean, theorem = "gravity_drop_monotone_increasing")
16
+ fn drop(g: Real, t: Real) -> Real
17
+ where chain_order <= 0,
18
+ domain: g >= 0.0 && t >= 0.0
19
+ ensures (result >= 0.0)
20
+ {
21
+ 0.5 * g * t * t
22
+ }
23
+
24
+ @verify(lean, theorem = "gravity_drop_at_zero_is_zero")
25
+ fn drop_at_zero(g: Real) -> Real
26
+ where chain_order <= 0,
27
+ domain: g >= 0.0
28
+ ensures (result == 0.0)
29
+ {
30
+ 0.5 * g * 0.0 * 0.0
31
+ }
32
+
33
+ // Earth-gravity convenience wrapper.
34
+ fn drop_earth(t: Real) -> Real
35
+ where chain_order <= 0,
36
+ domain: t >= 0.0
37
+ ensures (result >= 0.0)
38
+ {
39
+ 0.5 * G_EARTH * t * t
40
+ }
41
+
42
+ // Required scope hold-up in *milliradians* given a drop distance
43
+ // at a given range. Used by the ballistic-calculator UI.
44
+ // hold_up_mil = drop_meters / range_meters * 1000
45
+ @verify(lean, theorem = "hold_up_mil_nonneg")
46
+ fn hold_up_mil(drop_m: Real, range_m: Real) -> Real
47
+ where chain_order <= 0,
48
+ domain: drop_m >= 0.0 && range_m > 0.0
49
+ ensures (result >= 0.0)
50
+ {
51
+ drop_m / range_m * 1000.0
52
+ }
@@ -0,0 +1,42 @@
1
+ // muzzle_velocity.eml -- temperature compensation for muzzle velocity.
2
+ //
3
+ // Powder burn rate increases with temperature, raising muzzle
4
+ // velocity. A common rule of thumb:
5
+ //
6
+ // v_m(T) = v_m(T_ref) + α * (T - T_ref)
7
+ //
8
+ // where α ≈ 1 m/s per °C (roughly 1.5 fps/°F) for typical
9
+ // smokeless powders. The exact slope depends on the powder.
10
+
11
+ module muzzle_velocity;
12
+
13
+ const T_REF_C: Real = 21.0 // 70°F reference, °C
14
+ const ALPHA: Real = 1.0 // m/s per °C, typical
15
+
16
+ // Linear correction.
17
+ @verify(lean, theorem = "muzzle_velocity_linear_in_temp")
18
+ fn corrected_velocity(v_ref: Real, temp_c: Real, alpha: Real) -> Real
19
+ where chain_order <= 0,
20
+ domain: v_ref > 0.0
21
+ {
22
+ v_ref + alpha * (temp_c - T_REF_C)
23
+ }
24
+
25
+ // Default-α convenience.
26
+ @verify(lean, theorem = "muzzle_velocity_default_alpha")
27
+ fn corrected_default(v_ref: Real, temp_c: Real) -> Real
28
+ where chain_order <= 0,
29
+ domain: v_ref > 0.0
30
+ {
31
+ v_ref + ALPHA * (temp_c - T_REF_C)
32
+ }
33
+
34
+ // Powder-temperature sensitivity expressed as percentage change
35
+ // per degree. Used by the calculator UI.
36
+ @verify(lean, theorem = "powder_sensitivity_signed")
37
+ fn percent_change(temp_c: Real, alpha: Real, v_ref: Real) -> Real
38
+ where chain_order <= 0,
39
+ domain: v_ref > 0.0
40
+ {
41
+ alpha * (temp_c - T_REF_C) / v_ref * 100.0
42
+ }
@@ -0,0 +1,60 @@
1
+ // spin_drift.eml -- horizontal drift from gyroscopic Magnus effect.
2
+ //
3
+ // A spinning projectile precesses slightly off its line-of-flight,
4
+ // causing a horizontal drift in the direction of the spin. For a
5
+ // right-hand twist barrel (the U.S. standard) the drift is to the
6
+ // right.
7
+ //
8
+ // Bryan Litz's curve-fit (Applied Ballistics):
9
+ //
10
+ // SD_inches = 1.25 * (Sg + 1.2) * tof^1.83
11
+ //
12
+ // where Sg is the gyroscopic stability factor (dimensionless,
13
+ // computed from the Miller stability formula) and tof is time of
14
+ // flight in seconds.
15
+ //
16
+ // This module ships the Litz approximation plus a chain-order-0
17
+ // linear surrogate for FPGA / shader use.
18
+
19
+ module spin_drift;
20
+
21
+ // Litz curve-fit. Returns drift in inches.
22
+ @verify(lean, theorem = "spin_drift_litz_nonneg")
23
+ fn drift_inches_litz(stability_factor: Real, time_of_flight: Real) -> Real
24
+ where chain_order <= 1,
25
+ domain: stability_factor > 0.0 && time_of_flight >= 0.0
26
+ ensures (result >= 0.0)
27
+ {
28
+ 1.25 * (stability_factor + 1.2) * pow(time_of_flight, 1.83)
29
+ }
30
+
31
+ // Same in metres, for the SI side of the calculator.
32
+ @verify(lean, theorem = "spin_drift_litz_metres_nonneg")
33
+ fn drift_metres_litz(stability_factor: Real, time_of_flight: Real) -> Real
34
+ where chain_order <= 1,
35
+ domain: stability_factor > 0.0 && time_of_flight >= 0.0
36
+ ensures (result >= 0.0)
37
+ {
38
+ 0.0254 * 1.25 * (stability_factor + 1.2) * pow(time_of_flight, 1.83)
39
+ }
40
+
41
+ // Linear surrogate for the budget pipeline:
42
+ // SD_m = k * tof
43
+ // k tuned so the surrogate matches Litz at tof = 1.0 s.
44
+ @verify(lean, theorem = "spin_drift_surrogate_nonneg")
45
+ fn drift_metres_surrogate(stability_factor: Real, time_of_flight: Real) -> Real
46
+ where chain_order <= 0,
47
+ domain: stability_factor > 0.0 && time_of_flight >= 0.0
48
+ ensures (result >= 0.0)
49
+ {
50
+ 0.0254 * 1.25 * (stability_factor + 1.2) * time_of_flight
51
+ }
52
+
53
+ // Hold-off in milliradians.
54
+ @verify(lean, theorem = "spin_drift_hold_off_signed")
55
+ fn hold_off_mil(drift_m: Real, range_m: Real) -> Real
56
+ where chain_order <= 0,
57
+ domain: range_m > 0.0
58
+ {
59
+ drift_m / range_m * 1000.0
60
+ }
@@ -0,0 +1,79 @@
1
+ // time_of_flight.eml -- iterative time-of-flight under quadratic drag.
2
+ //
3
+ // In vacuum, t = R / v_0. Drag complicates this: the bullet
4
+ // decelerates, so the average velocity is less than v_0 and the
5
+ // true time-of-flight is longer.
6
+ //
7
+ // Two solutions ship here:
8
+ //
9
+ // tof_vacuum : R / v_0 (chain 0, baseline only)
10
+ // tof_constant_decel : closed-form quadratic, treats drag
11
+ // deceleration as constant (chain 0)
12
+ // tof_pejsa : Pejsa retardation-coefficient form
13
+ // (chain 1, log) — most accurate single-
14
+ // step approximation ballistic calculators
15
+ // use in the field.
16
+ //
17
+ // For the iterative-solver path, host code calls tof_step with the
18
+ // current (range_remaining, v) state until convergence.
19
+
20
+ module time_of_flight;
21
+
22
+ @verify(lean, theorem = "tof_vacuum_nonneg")
23
+ fn tof_vacuum(range_m: Real, v_0: Real) -> Real
24
+ where chain_order <= 0,
25
+ domain: v_0 > 0.0 && range_m >= 0.0
26
+ ensures (result >= 0.0)
27
+ {
28
+ range_m / v_0
29
+ }
30
+
31
+ // Constant-deceleration model: v(t) = v_0 - a*t, range = ∫v dt.
32
+ // R = v_0 * t - ½ * a * t²
33
+ // t = (v_0 - sqrt(v_0² - 2*a*R)) / a
34
+ @verify(lean, theorem = "tof_constant_decel_nonneg")
35
+ fn tof_constant_decel(range_m: Real, v_0: Real, drag_decel: Real) -> Real
36
+ where chain_order <= 1,
37
+ domain: v_0 > 0.0 && range_m >= 0.0 && drag_decel >= 0.0
38
+ ensures (result >= 0.0)
39
+ {
40
+ let disc: Real = v_0 * v_0 - 2.0 * drag_decel * range_m;
41
+ let safe_disc: Real = clamp(disc, 0.0, v_0 * v_0);
42
+ (v_0 - sqrt(safe_disc)) / clamp(drag_decel, 1.0e-6, 1.0e6)
43
+ }
44
+
45
+ // Pejsa form: t = R / V_avg, V_avg = (V_0 + V_R) / 2 with
46
+ // V_R = V_0 * exp(-R / k), where k is the retardation coefficient
47
+ // derived from the ballistic coefficient.
48
+ @verify(lean, theorem = "tof_pejsa_nonneg")
49
+ fn tof_pejsa(range_m: Real, v_0: Real, k_retard: Real) -> Real
50
+ where chain_order <= 1,
51
+ domain: v_0 > 0.0 && k_retard > 0.0 && range_m >= 0.0
52
+ ensures (result >= 0.0)
53
+ {
54
+ let v_remaining: Real = v_0 * exp(-range_m / k_retard);
55
+ let v_avg: Real = 0.5 * (v_0 + v_remaining);
56
+ range_m / v_avg
57
+ }
58
+
59
+ // Single integration step for an iterative solver. dt is the time
60
+ // step; returns the new (range_remaining, v) tuple-ish via two
61
+ // helper functions.
62
+ @verify(lean, theorem = "tof_step_range_decreases")
63
+ fn step_range(range_remaining: Real, v: Real, dt: Real) -> Real
64
+ where chain_order <= 0,
65
+ domain: dt >= 0.0 && range_remaining >= 0.0 && v >= 0.0
66
+ ensures (result <= range_remaining)
67
+ {
68
+ clamp(range_remaining - v * dt, 0.0, range_remaining)
69
+ }
70
+
71
+ @verify(lean, theorem = "tof_step_velocity_nonneg")
72
+ fn step_velocity(v: Real, drag_decel: Real, dt: Real) -> Real
73
+ where chain_order <= 0,
74
+ domain: drag_decel >= 0.0 && dt >= 0.0
75
+ requires (v >= 0.0)
76
+ ensures (result >= 0.0)
77
+ {
78
+ clamp(v - drag_decel * dt, 0.0, v)
79
+ }
@@ -0,0 +1,58 @@
1
+ // wind.eml -- crosswind deflection.
2
+ //
3
+ // drift = v_wind_perp * time_of_flight
4
+ //
5
+ // where v_wind_perp is the wind component perpendicular to the
6
+ // line-of-sight in m/s, and time_of_flight in seconds.
7
+ //
8
+ // First-order approximation; ignores the bullet "lag" effect at
9
+ // transonic speeds. For target distances <= 1500 m at typical
10
+ // rifle velocities this is within 5% of the full integral.
11
+
12
+ module wind;
13
+
14
+ @verify(lean, theorem = "wind_drift_signed")
15
+ fn drift(v_wind_perp: Real, time_of_flight: Real) -> Real
16
+ where chain_order <= 0,
17
+ domain: time_of_flight >= 0.0
18
+ {
19
+ v_wind_perp * time_of_flight
20
+ }
21
+
22
+ // Headwind / tailwind component. Positive = headwind = adds to
23
+ // drag = bullet arrives later. v_wind_along is the wind component
24
+ // parallel to the LOS, sign convention as above.
25
+ @verify(lean, theorem = "wind_extra_drag_signed")
26
+ fn relative_airspeed(v_bullet: Real, v_wind_along: Real) -> Real
27
+ where chain_order <= 0
28
+ {
29
+ v_bullet + v_wind_along
30
+ }
31
+
32
+ // Convert a wind speed + bearing to (perpendicular, along) components
33
+ // relative to the shooter's heading. Heading and bearing both in
34
+ // radians, measured from the shooter's forward direction.
35
+ // v_perp = v_wind * sin(bearing - heading)
36
+ // v_along = -v_wind * cos(bearing - heading)
37
+ @verify(lean, theorem = "wind_components_pythagorean")
38
+ fn perp_component(v_wind: Real, bearing: Real, heading: Real) -> Real
39
+ where chain_order <= 1
40
+ {
41
+ v_wind * sin(bearing - heading)
42
+ }
43
+
44
+ @verify(lean, theorem = "wind_components_pythagorean")
45
+ fn along_component(v_wind: Real, bearing: Real, heading: Real) -> Real
46
+ where chain_order <= 1
47
+ {
48
+ -v_wind * cos(bearing - heading)
49
+ }
50
+
51
+ // Hold-off in milliradians for a crosswind drift at a given range.
52
+ @verify(lean, theorem = "wind_hold_off_signed")
53
+ fn hold_off_mil(drift_m: Real, range_m: Real) -> Real
54
+ where chain_order <= 0,
55
+ domain: range_m > 0.0
56
+ {
57
+ drift_m / range_m * 1000.0
58
+ }