///|
fn same_problem(a : Problem, b : Problem) -> Bool {
a.demands == b.demands &&
a.stocks == b.stocks &&
a.kerf == b.kerf &&
a.min_remnant == b.min_remnant
}
///|
fn cut_json(c : Cut) -> Json {
Json::object({
"demand": Json(c.demand_id),
"ordinal": Json(c.ordinal),
"start": Json(c.start),
"end": Json(c.end),
"kerf_end": Json(c.kerf_end),
})
}
///|
fn map_json(m : BarMap) -> Json {
Json::object({
"bar": Json(m.bar),
"stock": Json(m.stock_id),
"usable_start": Json(m.usable_start),
"usable_end": Json(m.usable_end),
"cuts": Json::array(m.cuts.map(cut_json)),
"remnant_start": Json(m.remnant_start),
"remnant_end": Json(m.remnant_end),
"reusable": Json(m.reusable),
})
}
///|
/// Bind optimality claims to the exact problem that was solved.
pub fn encode_solution(p : Problem, s : Solution) -> Result[String, String] {
if !same_problem(p, s.source) {
return Err("solution belongs to a different problem")
}
let fields : Map[String, Json] = {
"schema": Json("mooncutstock.solution/1"),
"status": Json(status_name(s.status)),
"pattern_complete": Json(s.pattern_complete),
"search_complete": Json(s.search_complete),
"pattern_visits": Json(s.pattern_visits),
"patterns": Json(s.pattern_count),
"search_work": Json(s.work),
"dominated_states": Json(s.pruned),
"cost_lower_bound": Json(lower_bound(p).cost),
}
match s.plan {
None => {
fields["plan"] = Json::null()
fields["totals"] = Json::null()
fields["cut_maps"] = Json::array([])
}
Some(plan) => {
let t = match audit(p, plan) {
Err(e) => return Err(e)
Ok(t) => t
}
fields["plan"] = Json::array(plan.bars.map(bar_json))
fields["totals"] = totals_json(t)
fields["cut_maps"] = Json::array(
cutting_map(p, plan).unwrap().map(map_json),
)
}
}
Ok(Json::object(fields).stringify())
}
///|
pub fn text_report(p : Problem, s : Solution) -> Result[String, String] {
if !same_problem(p, s.source) {
return Err("solution belongs to a different problem")
}
let out = StringBuilder()
out.write_string("status: " + status_name(s.status) + "\n")
out.write_string(
"patterns: " +
s.pattern_count.to_string() +
"; enumeration visits: " +
s.pattern_visits.to_string() +
"\n",
)
out.write_string(
"search work: " +
s.work.to_string() +
"; dominated states: " +
s.pruned.to_string() +
"\n",
)
match s.plan {
None => out.write_string("no complete feasible plan found\n")
Some(plan) => {
let t = match audit(p, plan) {
Err(e) => return Err(e)
Ok(t) => t
}
out.write_string(
"cost: " +
t.cost.to_string() +
"; bars: " +
t.bars.to_string() +
"; pieces: " +
t.pieces.to_string() +
"\n",
)
out.write_string(
"length: " +
t.purchased.to_string() +
" = useful " +
t.useful.to_string() +
" + kerf " +
t.kerf_loss.to_string() +
" + trims " +
t.trim_loss.to_string() +
" + reusable " +
t.reusable.to_string() +
" + scrap " +
t.scrap.to_string() +
"\n",
)
for g in group_patterns(p, plan).unwrap() {
out.write_string(g.stock_id + " x " + g.repetitions.to_string() + ":")
for i, n in g.counts {
if n > 0 {
out.write_string(" " + p.demands[i].id + "=" + n.to_string())
}
}
out.write_string("; remainder=" + g.remaining_each.to_string() + "\n")
}
}
}
Ok(out.to_string())
}