1#![allow(unused)]
2
3use std::{collections::HashMap, time::Instant};
4use good_lp::{
5 constraint, default_solver, variable, variables, Expression, Solution, SolverModel
6};
7
8fn main() {
9 let start_time = Instant::now();
11
12 engine::init();
14
15 println!("Balance Engine - Multi-Period Production Planning Demo");
16
17 let products = vec!["A", "B"];
19 let periods = vec!["January", "February", "March"];
20
21 let mut initial_inventory = HashMap::new();
23 initial_inventory.insert("A", 100.0);
24 initial_inventory.insert("B", 120.0);
25
26 let mut safety_stock = HashMap::new();
27 safety_stock.insert("A", 130.0);
28 safety_stock.insert("B", 110.0);
29
30 let mut production_cost = HashMap::new();
31 production_cost.insert("A", 20.0);
32 production_cost.insert("B", 25.0);
33
34 let holding_cost_rate = 0.02; let mut holding_cost = HashMap::new();
38 for &p in &products {
39 holding_cost.insert(p, production_cost[&p] * holding_cost_rate);
40 }
41
42 let mut demand = HashMap::new();
44 demand.insert(("A", "January"), 700.0);
45 demand.insert(("A", "February"), 900.0);
46 demand.insert(("A", "March"), 1000.0);
47 demand.insert(("B", "January"), 800.0);
48 demand.insert(("B", "February"), 600.0);
49 demand.insert(("B", "March"), 900.0);
50
51 let mut machine_capacity = HashMap::new();
53 machine_capacity.insert("January", 3000.0);
54 machine_capacity.insert("February", 2800.0);
55 machine_capacity.insert("March", 3600.0);
56
57 let mut labor_capacity = HashMap::new();
58 labor_capacity.insert("January", 2500.0);
59 labor_capacity.insert("February", 2300.0);
60 labor_capacity.insert("March", 2400.0);
61
62 let mut machine_hours = HashMap::new();
64 machine_hours.insert("A", 1.5);
65 machine_hours.insert("B", 1.6);
66
67 let mut labor_hours = HashMap::new();
68 labor_hours.insert("A", 1.1);
69 labor_hours.insert("B", 1.2);
70
71 let mut vars = variables!();
73
74 let mut x = HashMap::new();
76 for &p in &products {
77 for &t in &periods {
78 x.insert((p, t), vars.add(variable().min(0.0).integer().name(format!("x_{}_{}", p, t))));
79 }
80 }
81
82 let mut inv = HashMap::new();
84 for &p in &products {
85 for &t in &periods {
86 inv.insert((p, t), vars.add(variable().min(0.0).integer().name(format!("inv_{}_{}", p, t))));
87 }
88 }
89
90 let mut objective = Expression::from(0.0);
92
93 for &p in &products {
95 for &t in &periods {
96 objective += production_cost[&p] * *x.get(&(p, t)).unwrap();
97 }
98 }
99
100 for &p in &products {
102 for &t in &periods {
103 objective += holding_cost[&p] * *inv.get(&(p, t)).unwrap();
104 }
105 }
106
107 let mut model = vars.minimise(objective.clone()).using(default_solver);
109
110 for &p in &products {
114 for (i, &t) in periods.iter().enumerate() {
115 if i == 0 { model = model.with(constraint!(
117 *inv.get(&(p, t)).unwrap() == initial_inventory[&p] + *x.get(&(p, t)).unwrap() - demand[&(p, t)]
118 ));
119 } else {
120 let prev_t = periods[i - 1];
121 model = model.with(constraint!(
122 *inv.get(&(p, t)).unwrap() == *inv.get(&(p, prev_t)).unwrap() + *x.get(&(p, t)).unwrap() - demand[&(p, t)]
123 ));
124 }
125 }
126 }
127
128 for &t in &periods {
130 let mut machine_usage = Expression::from(0.0);
132 for &p in &products {
133 machine_usage += machine_hours[&p] * *x.get(&(p, t)).unwrap();
134 }
135 model = model.with(constraint!(machine_usage <= machine_capacity[&t]));
136
137 let mut labor_usage = Expression::from(0.0);
139 for &p in &products {
140 labor_usage += labor_hours[&p] * *x.get(&(p, t)).unwrap();
141 }
142 model = model.with(constraint!(labor_usage <= labor_capacity[&t]));
143 }
144
145 for &p in &products {
147 let last_period = periods.last().unwrap();
148 model = model.with(constraint!(*inv.get(&(p, last_period)).unwrap() >= safety_stock[&p]));
149 }
150
151 match model.solve() {
153 Ok(solution) => {
154 println!("\nOptimal Solution Found:");
155 println!("Total Cost: €{:.2}", solution.eval(&objective));
156
157 println!("\nProduction Plan:");
159 println!("{:<10} {:<10} {:<10}", "Period", "Product A", "Product B");
160 println!("{}", "-".repeat(30));
161 for &t in &periods {
162 println!("{:<10} {:<10} {:<10}",
163 t,
164 solution.value(*x.get(&("A", t)).unwrap()) as i32,
165 solution.value(*x.get(&("B", t)).unwrap()) as i32);
166 }
167
168 println!("\nEnding Inventory:");
170 println!("{:<10} {:<10} {:<10}", "Period", "Product A", "Product B");
171 println!("{}", "-".repeat(30));
172 for &t in &periods {
173 println!("{:<10} {:<10} {:<10}",
174 t,
175 solution.value(*inv.get(&("A", t)).unwrap()) as i32,
176 solution.value(*inv.get(&("B", t)).unwrap()) as i32);
177 }
178
179 println!("\nResource Utilization:");
181 println!("{:<10} {:<30} {:<30}",
182 "Period",
183 "Machine Hours (Used/Available)",
184 "Labor Hours (Used/Available)");
185 println!("{}", "-".repeat(70));
186 for &t in &periods {
187 let mut machine_used = 0.0;
188 let mut labor_used = 0.0;
189
190 for &p in &products {
191 machine_used += machine_hours[&p] * solution.value(*x.get(&(p, t)).unwrap());
192 labor_used += labor_hours[&p] * solution.value(*x.get(&(p, t)).unwrap());
193 }
194
195 let machine_util = machine_used / machine_capacity[&t] * 100.0;
196 let labor_util = labor_used / labor_capacity[&t] * 100.0;
197
198 println!("{:<10} {:.1}/{} ({:.1}%) {:<5} {:.1}/{} ({:.1}%)",
199 t,
200 machine_used,
201 machine_capacity[&t],
202 machine_util,
203 "",
204 labor_used,
205 labor_capacity[&t],
206 labor_util);
207 }
208
209 },
210 Err(e) => {
211 println!("No optimal solution found. Error: {:?}", e);
212 }
213 }
214
215 let elapsed = start_time.elapsed();
217 println!("\nTime elapsed: {:?}", elapsed);
218}