#![allow(unused)]
use std::{collections::HashMap, time::Instant};
use good_lp::{
constraint, default_solver, variable, variables, Expression, Solution, SolverModel
};
fn main() {
let start_time = Instant::now();
engine::init();
println!("Balance Engine - Multi-Period Production Planning Demo");
let products = vec!["A", "B"];
let periods = vec!["January", "February", "March"];
let mut initial_inventory = HashMap::new();
initial_inventory.insert("A", 100.0);
initial_inventory.insert("B", 120.0);
let mut safety_stock = HashMap::new();
safety_stock.insert("A", 130.0);
safety_stock.insert("B", 110.0);
let mut production_cost = HashMap::new();
production_cost.insert("A", 20.0);
production_cost.insert("B", 25.0);
let holding_cost_rate = 0.02;
let mut holding_cost = HashMap::new();
for &p in &products {
holding_cost.insert(p, production_cost[&p] * holding_cost_rate);
}
let mut demand = HashMap::new();
demand.insert(("A", "January"), 700.0);
demand.insert(("A", "February"), 900.0);
demand.insert(("A", "March"), 1000.0);
demand.insert(("B", "January"), 800.0);
demand.insert(("B", "February"), 600.0);
demand.insert(("B", "March"), 900.0);
let mut machine_capacity = HashMap::new();
machine_capacity.insert("January", 3000.0);
machine_capacity.insert("February", 2800.0);
machine_capacity.insert("March", 3600.0);
let mut labor_capacity = HashMap::new();
labor_capacity.insert("January", 2500.0);
labor_capacity.insert("February", 2300.0);
labor_capacity.insert("March", 2400.0);
let mut machine_hours = HashMap::new();
machine_hours.insert("A", 1.5);
machine_hours.insert("B", 1.6);
let mut labor_hours = HashMap::new();
labor_hours.insert("A", 1.1);
labor_hours.insert("B", 1.2);
let mut vars = variables!();
let mut x = HashMap::new();
for &p in &products {
for &t in &periods {
x.insert((p, t), vars.add(variable().min(0.0).integer().name(format!("x_{}_{}", p, t))));
}
}
let mut inv = HashMap::new();
for &p in &products {
for &t in &periods {
inv.insert((p, t), vars.add(variable().min(0.0).integer().name(format!("inv_{}_{}", p, t))));
}
}
let mut objective = Expression::from(0.0);
for &p in &products {
for &t in &periods {
objective += production_cost[&p] * *x.get(&(p, t)).unwrap();
}
}
for &p in &products {
for &t in &periods {
objective += holding_cost[&p] * *inv.get(&(p, t)).unwrap();
}
}
let mut model = vars.minimise(objective.clone()).using(default_solver);
for &p in &products {
for (i, &t) in periods.iter().enumerate() {
if i == 0 { model = model.with(constraint!(
*inv.get(&(p, t)).unwrap() == initial_inventory[&p] + *x.get(&(p, t)).unwrap() - demand[&(p, t)]
));
} else {
let prev_t = periods[i - 1];
model = model.with(constraint!(
*inv.get(&(p, t)).unwrap() == *inv.get(&(p, prev_t)).unwrap() + *x.get(&(p, t)).unwrap() - demand[&(p, t)]
));
}
}
}
for &t in &periods {
let mut machine_usage = Expression::from(0.0);
for &p in &products {
machine_usage += machine_hours[&p] * *x.get(&(p, t)).unwrap();
}
model = model.with(constraint!(machine_usage <= machine_capacity[&t]));
let mut labor_usage = Expression::from(0.0);
for &p in &products {
labor_usage += labor_hours[&p] * *x.get(&(p, t)).unwrap();
}
model = model.with(constraint!(labor_usage <= labor_capacity[&t]));
}
for &p in &products {
let last_period = periods.last().unwrap();
model = model.with(constraint!(*inv.get(&(p, last_period)).unwrap() >= safety_stock[&p]));
}
match model.solve() {
Ok(solution) => {
println!("\nOptimal Solution Found:");
println!("Total Cost: €{:.2}", solution.eval(&objective));
println!("\nProduction Plan:");
println!("{:<10} {:<10} {:<10}", "Period", "Product A", "Product B");
println!("{}", "-".repeat(30));
for &t in &periods {
println!("{:<10} {:<10} {:<10}",
t,
solution.value(*x.get(&("A", t)).unwrap()) as i32,
solution.value(*x.get(&("B", t)).unwrap()) as i32);
}
println!("\nEnding Inventory:");
println!("{:<10} {:<10} {:<10}", "Period", "Product A", "Product B");
println!("{}", "-".repeat(30));
for &t in &periods {
println!("{:<10} {:<10} {:<10}",
t,
solution.value(*inv.get(&("A", t)).unwrap()) as i32,
solution.value(*inv.get(&("B", t)).unwrap()) as i32);
}
println!("\nResource Utilization:");
println!("{:<10} {:<30} {:<30}",
"Period",
"Machine Hours (Used/Available)",
"Labor Hours (Used/Available)");
println!("{}", "-".repeat(70));
for &t in &periods {
let mut machine_used = 0.0;
let mut labor_used = 0.0;
for &p in &products {
machine_used += machine_hours[&p] * solution.value(*x.get(&(p, t)).unwrap());
labor_used += labor_hours[&p] * solution.value(*x.get(&(p, t)).unwrap());
}
let machine_util = machine_used / machine_capacity[&t] * 100.0;
let labor_util = labor_used / labor_capacity[&t] * 100.0;
println!("{:<10} {:.1}/{} ({:.1}%) {:<5} {:.1}/{} ({:.1}%)",
t,
machine_used,
machine_capacity[&t],
machine_util,
"",
labor_used,
labor_capacity[&t],
labor_util);
}
},
Err(e) => {
println!("No optimal solution found. Error: {:?}", e);
}
}
let elapsed = start_time.elapsed();
println!("\nTime elapsed: {:?}", elapsed);
}