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
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
|
+
}
|