winds_engine/lib.rs
1//! Deterministic wind fields and radiosonde validation for trajectory
2//! simulation, consumed by rockets-engine.
3//!
4//! Everything here is stateless and reproducible from inputs: no network, no
5//! wall clock, no unseeded randomness. The [`WindField`] trait, the profile
6//! types and the analytic profiles are this crate's own. They were the
7//! staging ground for the modeling core planned for ballistics-engine's
8//! `wind` module (see `docs/winds-engine-plan.md`), but ballistics-engine's
9//! wind fields (unreleased) differ: its `WindField::wind` takes and returns
10//! `nalgebra::Vector3<f64>`, with no counterpart to [`Wind`]; its
11//! `ConstantField` is a tuple struct over that vector; its `LogProfile` and
12//! `EkmanProfile` keep their fields private behind getters; and its
13//! constructors return `WindFieldError`. Moving this crate onto those types
14//! changes its public API (every [`WindField::wind`] call,
15//! [`validate::validate`]'s signature), so it is a deliberate breaking
16//! release, not a drop-in re-export.
17//!
18//! - [`field`]: the [`WindField`] trait, [`ConstantField`], [`ProfileField`].
19//! - [`analytic`]: [`LogProfile`](analytic::LogProfile) and
20//! [`EkmanProfile`](analytic::EkmanProfile) boundary-layer models.
21//! - [`sounding`]: radiosonde interchange-format parsing.
22//! - [`validate`]: profile-vs-sounding comparison metrics.
23
24pub mod analytic;
25#[cfg(feature = "download")]
26pub mod download;
27pub mod field;
28#[cfg(feature = "grib")]
29pub mod grib;
30pub mod sounding;
31pub mod terrain;
32pub mod validate;
33
34pub use field::{ConstantField, ProfileField, ProfileLevel, Wind, WindField};
35pub use sounding::{Sounding, SoundingLevel};
36pub use terrain::{local_en_m, monitors_from_geo_csv, HeightGrid, Monitor, TerrainFlowField};
37pub use validate::{LayerError, ValidationReport};
38
39/// Crate-wide error: invalid input with a field name and reason.
40#[derive(Debug, Clone, PartialEq)]
41pub struct Error {
42 /// The offending input or field.
43 pub field: &'static str,
44 /// Why it was rejected.
45 pub reason: String,
46}
47
48impl Error {
49 pub(crate) fn invalid(field: &'static str, reason: impl Into<String>) -> Self {
50 Self {
51 field,
52 reason: reason.into(),
53 }
54 }
55}
56
57impl std::fmt::Display for Error {
58 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
59 write!(f, "{}: {}", self.field, self.reason)
60 }
61}
62
63impl std::error::Error for Error {}
64
65/// Crate-wide result.
66pub type Result<T> = std::result::Result<T, Error>;