Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::math_round_div::round_div; ← from math.round-div ^1.0.0 · built alongside by fune
use super::math_round_float::round_float; ← from math.round-float ^1.0.0 · built alongside by fune
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(),
))
}