///|
pub fn with_costs(p : Problem, costs : Array[Int32]) -> Result[Problem, String] {
if costs.length() != p.stocks.length() {
return Err("cost vector dimension mismatch")
}
let stocks = p.stocks.mapi((i, s) => Stock::{ ..s, cost: costs[i], })
problem(p.demands, stocks, kerf=p.kerf, min_remnant=p.min_remnant)
}
///|
pub fn with_inventory(
p : Problem,
quantities : Array[Int32],
) -> Result[Problem, String] {
if quantities.length() != p.stocks.length() {
return Err("inventory vector dimension mismatch")
}
let stocks = p.stocks.mapi((i, s) => Stock::{ ..s, quantity: quantities[i], })
problem(p.demands, stocks, kerf=p.kerf, min_remnant=p.min_remnant)
}
///|
pub fn with_kerf(p : Problem, kerf : Int32) -> Result[Problem, String] {
problem(p.demands, p.stocks, kerf~, min_remnant=p.min_remnant)
}
///|
pub(all) struct Comparison {
cost_delta : Int32
purchased_delta : Int32
bars_delta : Int32
ordering : Int32
} derive(Debug, Eq)
///|
pub extend Comparison with @debug.Debug::{to_repr}
///|
pub extend Comparison with Eq::{equal, not_equal}
///|
/// Negative deltas mean the candidate improves on the baseline, for the same problem.
pub fn compare_plans(
p : Problem,
baseline : Plan,
candidate : Plan,
) -> Result[Comparison, String] {
let a = match audit(p, baseline) {
Err(e) => return Err(e)
Ok(t) => t
}
let b = match audit(p, candidate) {
Err(e) => return Err(e)
Ok(t) => t
}
let ordering = if b.cost != a.cost {
b.cost.compare(a.cost)
} else if b.purchased != a.purchased {
b.purchased.compare(a.purchased)
} else {
b.bars.compare(a.bars)
}
Ok({
cost_delta: b.cost - a.cost,
purchased_delta: b.purchased - a.purchased,
bars_delta: b.bars - a.bars,
ordering,
})
}
///|
pub(all) struct KerfScenario {
kerf : Int32
status : Status
cost : Int32?
purchased : Int32?
search_work : Int32
} derive(Debug)
///|
pub extend KerfScenario with @debug.Debug::{to_repr}
///|
/// Each scenario uses an independent bounded solve; at most 16 scenarios per call.
pub fn kerf_scenarios(
p : Problem,
kerfs : Array[Int32],
limits~ : Limits = default_limits(),
) -> Result[Array[KerfScenario], String] {
if kerfs.length() > 16 {
return Err("at most 16 kerf scenarios")
}
match validate_limits(limits) {
Err(e) => return Err(e)
Ok(_) => ()
}
// Validate the complete batch before spending any solver budget.
for kerf in kerfs {
if kerf < 0 || kerf > 1000000 {
return Err("invalid kerf scenario")
}
}
let result : Array[KerfScenario] = []
for kerf in kerfs {
let q = with_kerf(p, kerf).unwrap()
let s = solve(q, limits~).unwrap()
let t = s.plan.map(plan => audit(q, plan).unwrap())
result.push({
kerf,
status: s.status,
cost: t.map(t => t.cost),
purchased: t.map(t => t.purchased),
search_work: s.work,
})
}
Ok(result)
}