Functional Weave
Code in TypeScript

construction.brick-count@1.0.0

impl/rust.rs

3,842 bytes · the Rust implementation · view raw

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

// A 215 x 65 mm brick face plus a 10 mm joint is 225 x 75 mm of wall.
const FACE_MM2: i64 = 225 * 75;
// Mortar per mm2 of wall face, in mm3: each skin fills the joints around a
// brick, (16875 - 215 x 65) / 16875 of the face, 102.5 mm deep, which is
// 2378/135 mm; each collar joint between skins is 10 mm = 1350/135 mm.
const SKIN_MORTAR: i64 = 2378;
const COLLAR_MORTAR: i64 = 1350;
const MORTAR_DIVISOR: i64 = 135;
const MM3_PER_LITRE: i64 = 1_000_000;

fn millimetres(value: f64, message: &str, max: i64) -> i64 {
    if !value.is_finite() || value <= 0.0 || value > max as f64 {
        panic!(
            "{} must be a finite number greater than 0 and at most {} metres, received {}",
            message, max, value
        );
    }
    (round_float(value, 3) * 1000.0).round() as i64
}

/// Bricks and mortar for a wall of UK standard bricks, openings subtracted.
///
/// The count is the net face area over one brick's 225 x 75 mm share of it,
/// 59.26 per m2 per skin (the trade's "60 a metre"), times the skins, plus
/// wastage, rounded up. Mortar is the joint volume that geometry implies.
/// Everything is whole millimetres and integer arithmetic.
///
/// # Panics
/// Panics on a dimension, skin count or wastage out of range, or openings
/// larger than the wall.
pub fn brick_count(
    wall_length: f64,
    wall_height: f64,
    openings: &[Opening],
    skins: i64,
    wastage_basis_points: i64,
) -> BrickCount {
    let length = millimetres(wall_length, "wallLength", 1000);
    let height = millimetres(wall_height, "wallHeight", 100);
    if !(1..=4).contains(&skins) {
        panic!("skins must be a whole number from 1 to 4, received {}", skins);
    }
    if !(0..=10000).contains(&wastage_basis_points) {
        panic!("wastageBasisPoints must be a whole number from 0 to 10000, received {}", wastage_basis_points);
    }
    let mut net = length * height;
    for (i, opening) in openings.iter().enumerate() {
        let w = millimetres(opening.width, &format!("opening {}: width", i + 1), 1000);
        let h = millimetres(opening.height, &format!("opening {}: height", i + 1), 1000);
        net -= w * h;
    }
    if net < 0 {
        panic!("openings are larger than the wall");
    }

    let bricks = round_div(net * skins * (10000 + wastage_basis_points), FACE_MM2 * 10000, "up");
    let per_mm2 = skins * SKIN_MORTAR + (skins - 1) * COLLAR_MORTAR;
    // Up to the whole litre first, then the wastage, so no product passes 2^53.
    let litres = round_div(net * per_mm2, MORTAR_DIVISOR * MM3_PER_LITRE, "up");
    let mortar = round_div(litres * (10000 + wastage_basis_points), 10000, "up");
    BrickCount {
        net_area_square_metres: net as f64 / 1_000_000.0,
        bricks,
        mortar_cubic_metres: mortar as f64 / 1000.0,
    }
}

pub fn brick_count_to_value(b: &BrickCount) -> Value {
    Value::obj(vec![
        ("netAreaSquareMetres", Value::Float(b.net_area_square_metres)),
        ("bricks", Value::Int(b.bricks)),
        ("mortarCubicMetres", Value::Float(b.mortar_cubic_metres)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    let openings: Vec<Opening> = args[2]
        .as_arr()
        .iter()
        .map(|v| Opening { width: v.get("width").as_f64(), height: v.get("height").as_f64() })
        .collect();
    if let Value::Float(f) = &args[3] {
        panic!("skins must be a whole number from 1 to 4, received {}", f);
    }
    if let Value::Float(f) = &args[4] {
        panic!("wastageBasisPoints must be a whole number from 0 to 10000, received {}", f);
    }
    brick_count_to_value(&brick_count(
        args[0].as_f64(),
        args[1].as_f64(),
        &openings,
        args[3].as_i64(),
        args[4].as_i64(),
    ))
}