use super::funejson::Value; use super::math_round_div::round_div; use super::math_round_float::round_float; fn scaled(name: &str, value: f64, decimals: i64, low: i64, high: i64) -> i64 { if !value.is_finite() || value < low as f64 || value > high as f64 { panic!("{} must be a finite number from {} to {}, received {}", name, low, high, value); } (round_float(value, decimals) * 10f64.powi(decimals as i32)).round() as i64 } /// Tank mix for a sprayer. /// /// Area (0.0001 ha), water volume (0.1 L/ha), dose (0.001/ha) and tank /// (0.1 L) are held as integers, so the job's spray volume is exact and the /// count of full tanks cannot be thrown by a float: 129.2 ha at 250 L/ha is /// exactly 19 tanks of 1700 L, where 129.2 x 250 in floating point is /// 32299.999999999996 and makes it 18 and a sliver. /// /// # Panics /// Panics on an argument out of range, an unknown dose unit, or a liquid dose /// that is not less than the water volume. pub fn spray_mix( area_hectares: f64, water_volume_litres_per_hectare: f64, product_dose_per_hectare: f64, dose_unit: &str, tank_litres: f64, ) -> SprayMix { let area = scaled("areaHectares", area_hectares, 4, 0, 100000); let rate = scaled("waterVolumeLitresPerHectare", water_volume_litres_per_hectare, 1, 0, 10000); let dose = scaled("productDosePerHectare", product_dose_per_hectare, 3, 0, 100); let tank = scaled("tankLitres", tank_litres, 1, 0, 100000); if area < 1 { panic!("areaHectares must be at least 0.0001 hectares"); } if rate < 1 { panic!("waterVolumeLitresPerHectare must be at least 0.1 litres"); } if tank < 1 { panic!("tankLitres must be at least 0.1 litres"); } if dose_unit != "litres" && dose_unit != "kilograms" { panic!("unknown doseUnit \"{}\": use litres or kilograms", dose_unit); } // A litre of product per litre of spray would leave no room for water. if dose_unit == "litres" && dose >= rate * 100 { panic!("productDosePerHectare must be less than waterVolumeLitresPerHectare for a liquid product"); } let spray = area * rate; // litres x 10^5 let tank_spray = tank * 10000; // litres x 10^5 let full_tanks = spray / tank_spray; let remainder = spray - full_tanks * tank_spray; // A load from its spray volume in litres x 10^5: area = spray / rate, // product = spray x dose / (rate x 10^4) in thousandths. let load = |s: i64| -> TankLoad { let spray_ml = round_div(s, 100, "half-up"); let product = round_div(s * dose, rate * 10000, "half-up"); TankLoad { area_hectares: round_div(s, rate, "half-up") as f64 / 10000.0, spray_litres: spray_ml as f64 / 1000.0, product: product as f64 / 1000.0, water_litres: (if dose_unit == "litres" { spray_ml - product } else { spray_ml }) as f64 / 1000.0, } }; SprayMix { tank_fills: full_tanks + if remainder > 0 { 1 } else { 0 }, full_tanks, full_tank: load(tank_spray), partial_tank: if remainder > 0 { Some(load(remainder)) } else { None }, total_spray_litres: round_div(spray, 100, "half-up") as f64 / 1000.0, total_product: round_div(area * dose, 10000, "half-up") as f64 / 1000.0, } } pub fn tank_load_to_value(t: &TankLoad) -> Value { Value::obj(vec![ ("areaHectares", Value::Float(t.area_hectares)), ("sprayLitres", Value::Float(t.spray_litres)), ("product", Value::Float(t.product)), ("waterLitres", Value::Float(t.water_litres)), ]) } pub fn spray_mix_to_value(m: &SprayMix) -> Value { Value::obj(vec![ ("tankFills", Value::Int(m.tank_fills)), ("fullTanks", Value::Int(m.full_tanks)), ("fullTank", tank_load_to_value(&m.full_tank)), ("partialTank", m.partial_tank.as_ref().map_or(Value::Null, tank_load_to_value)), ("totalSprayLitres", Value::Float(m.total_spray_litres)), ("totalProduct", Value::Float(m.total_product)), ]) } pub fn fune_vector(args: &[Value]) -> Value { spray_mix_to_value(&spray_mix( args[0].as_f64(), args[1].as_f64(), args[2].as_f64(), args[3].as_str(), args[4].as_f64(), )) }