Functional Weave
Code in Python

manufacturing.batch-size@1.0.0

impl/rust.rs

3,232 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_integer_sqrt::integer_sqrt;  ← from math.integer-sqrt ^1.0.0 · built alongside by fune
use super::money_amount::{assert_same_currency, money_from_value, Money};  ← from money.amount ^1.0.0 · built alongside by fune

const MAX_SAFE: i64 = (1i64 << 53) - 1;

/// Economic batch quantity, sqrt(2DSP / (H(P - D))), in whole units.
///
/// The root is never taken in floating point: the floor is an exact integer
/// square root and the rounding decision compares whole numbers.
///
/// # Panics
/// Panics on negative demand or set-up cost, a production rate not above
/// demand, a holding cost that is not positive, mixed currencies, an unknown
/// mode, or 8DSP beyond 2^63 - 1.
pub fn economic_batch_quantity(
    annual_demand: i64,
    annual_production_rate: i64,
    setup_cost: &Money,
    holding_cost: &Money,
    mode: &str,
) -> i64 {
    if !(0..=MAX_SAFE).contains(&annual_demand) {
        panic!("annualDemand must be a whole number of units, not negative, received {}", annual_demand);
    }
    if annual_production_rate <= annual_demand || annual_production_rate > MAX_SAFE {
        panic!(
            "annualProductionRate must be a whole number greater than annualDemand, received {}",
            annual_production_rate
        );
    }
    assert_same_currency(setup_cost, holding_cost);
    if setup_cost.minor < 0 {
        panic!("setupCost must not be negative, received {}", setup_cost.minor);
    }
    if holding_cost.minor <= 0 {
        panic!("holdingCost must be greater than zero, received {}", holding_cost.minor);
    }
    let d = annual_demand as i128;
    let p = annual_production_rate as i128;
    let s = setup_cost.minor as i128;
    let eight = (8 * d)
        .checked_mul(s)
        .and_then(|x| x.checked_mul(p))
        .filter(|x| *x <= i64::MAX as i128)
        .unwrap_or_else(|| {
            panic!("annualDemand, setupCost and annualProductionRate are too large: 8 x D x S x P must stay within 2^63 - 1")
        });
    let two = eight / 4;
    let m = holding_cost.minor as i128 * (p - d);
    let n = integer_sqrt((two / m) as i64) as i128;
    let odd = 2 * n + 1;
    let result = match mode {
        "down" => n,
        "up" => {
            if n * n * m == two {
                n
            } else {
                n + 1
            }
        }
        "half-up" => {
            if odd * odd * m <= eight {
                n + 1
            } else {
                n
            }
        }
        "half-even" => {
            let half = odd * odd * m;
            if half < eight || (half == eight && n % 2 == 1) {
                n + 1
            } else {
                n
            }
        }
        other => panic!("unknown rounding mode \"{}\"", other),
    };
    result as i64
}

pub fn fune_vector(args: &[Value]) -> Value {
    if let Value::Float(f) = &args[0] {
        panic!("annualDemand must be a whole number of units, not negative, received {}", f);
    }
    if let Value::Float(f) = &args[1] {
        panic!("annualProductionRate must be a whole number greater than annualDemand, received {}", f);
    }
    Value::Int(economic_batch_quantity(
        args[0].as_i64(),
        args[1].as_i64(),
        &money_from_value(&args[2]),
        &money_from_value(&args[3]),
        args[4].as_str(),
    ))
}