const HEIGHT_MIN_CM: f32 = 150.0;
const HEIGHT_MAX_CM: f32 = 200.0;
const BMI_MIN: f32 = 15.0;
const BMI_MAX: f32 = 40.0;
const AGE_MIN_YR: f32 = 2.0;
const AGE_MAX_YR: f32 = 90.0;
pub fn height_cm_to_param(cm: f32) -> f32 {
((cm - HEIGHT_MIN_CM) / (HEIGHT_MAX_CM - HEIGHT_MIN_CM)).clamp(0.0, 1.0)
}
pub fn param_to_height_cm(param: f32) -> f32 {
HEIGHT_MIN_CM + param.clamp(0.0, 1.0) * (HEIGHT_MAX_CM - HEIGHT_MIN_CM)
}
pub fn compute_bmi(weight_kg: f32, height_cm: f32) -> f32 {
let h_m = height_cm / 100.0;
if h_m < 0.01 {
return 0.0;
}
weight_kg / (h_m * h_m)
}
pub fn bmi_to_weight_param(bmi: f32) -> f32 {
((bmi - BMI_MIN) / (BMI_MAX - BMI_MIN)).clamp(0.0, 1.0)
}
pub fn weight_kg_to_param(weight_kg: f32, height_cm: f32) -> f32 {
bmi_to_weight_param(compute_bmi(weight_kg, height_cm))
}
pub fn age_yr_to_param(years: f32) -> f32 {
((years - AGE_MIN_YR) / (AGE_MAX_YR - AGE_MIN_YR)).clamp(0.0, 1.0)
}
pub fn param_to_age_yr(param: f32) -> f32 {
AGE_MIN_YR + param.clamp(0.0, 1.0) * (AGE_MAX_YR - AGE_MIN_YR)
}
pub fn params_from_measurements(
height_cm: f32,
weight_kg: f32,
age_years: f32,
muscle_param: f32, ) -> crate::params::ParamState {
crate::params::ParamState::new(
height_cm_to_param(height_cm),
weight_kg_to_param(weight_kg, height_cm),
muscle_param.clamp(0.0, 1.0),
age_yr_to_param(age_years),
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn average_height_maps_to_midpoint() {
let p = height_cm_to_param(175.0);
assert!((p - 0.5).abs() < 1e-5);
}
#[test]
fn height_roundtrip() {
for cm in [150.0f32, 165.0, 175.0, 185.0, 200.0] {
let p = height_cm_to_param(cm);
let back = param_to_height_cm(p);
assert!(
(back - cm).abs() < 0.1,
"height roundtrip failed for {}cm: got {}cm",
cm,
back
);
}
}
#[test]
fn bmi_calculation() {
let bmi = compute_bmi(70.0, 175.0);
assert!((bmi - 22.86).abs() < 0.1);
}
#[test]
fn normal_bmi_maps_to_midrange() {
let p = bmi_to_weight_param(22.86);
assert!(p > 0.2 && p < 0.5);
}
#[test]
fn age_30_maps_to_midrange() {
let p = age_yr_to_param(30.0);
assert!(p > 0.2 && p < 0.5);
}
#[test]
fn age_roundtrip() {
for yr in [10.0f32, 25.0, 40.0, 60.0, 80.0] {
let p = age_yr_to_param(yr);
let back = param_to_age_yr(p);
assert!(
(back - yr).abs() < 0.1,
"age roundtrip failed for {}yr: got {}yr",
yr,
back
);
}
}
#[test]
fn params_from_real_measurements() {
let p = params_from_measurements(175.0, 70.0, 30.0, 0.6);
assert!((0.0..=1.0).contains(&p.height));
assert!((0.0..=1.0).contains(&p.weight));
assert!((0.0..=1.0).contains(&p.muscle));
assert!((0.0..=1.0).contains(&p.age));
assert!((p.height - 0.5).abs() < 1e-5);
}
#[test]
fn extreme_values_clamped() {
assert!((height_cm_to_param(50.0) - 0.0).abs() < 1e-5);
assert!((height_cm_to_param(300.0) - 1.0).abs() < 1e-5);
assert!((age_yr_to_param(0.0) - 0.0).abs() < 1e-5);
assert!((age_yr_to_param(200.0) - 1.0).abs() < 1e-5);
}
}