multi_period/
multi-period.rs

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    // Start timing
10    let start_time = Instant::now();
11    
12    // Initialize engine (as per template)
13    engine::init();
14    
15    println!("Balance Engine - Multi-Period Production Planning Demo");
16    
17    // Define the problem data
18    let products = vec!["A", "B"];
19    let periods = vec!["January", "February", "March"];
20    
21    // Initial conditions
22    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;  // 2% of production cost
35    
36    // Calculate holding cost per unit per period
37    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    // Demand forecasts
43    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    // Resource capacities
52    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    // Resource requirements per unit
63    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    // Create variables for the problem
72    let mut vars = variables!();
73    
74    // x[p,t] = production of product p in period t
75    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    // inv[p,t] = inventory of product p at the end of period t
83    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    // Objective function: minimize total cost (production + inventory holding)
91    let mut objective = Expression::from(0.0);
92    
93    // Production costs
94    for &p in &products {
95        for &t in &periods {
96            objective += production_cost[&p] * *x.get(&(p, t)).unwrap();
97        }
98    }
99    
100    // Holding costs
101    for &p in &products {
102        for &t in &periods {
103            objective += holding_cost[&p] * *inv.get(&(p, t)).unwrap();
104        }
105    }
106    
107    // Create model with minimization objective
108    let mut model = vars.minimise(objective.clone()).using(default_solver);
109    
110    // Add constraints
111    
112    // 1. Inventory balance constraints
113    for &p in &products {
114        for (i, &t) in periods.iter().enumerate() {
115            if i == 0 {  // first period
116                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    // 2. Capacity constraints
129    for &t in &periods {
130        // Machine capacity
131        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        // Labor capacity
138        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    // 3. Safety stock requirements (end of planning horizon)
146    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    // Solve the model
152    match model.solve() {
153        Ok(solution) => {
154            println!("\nOptimal Solution Found:");
155            println!("Total Cost: €{:.2}", solution.eval(&objective));
156            
157            // Print production plan
158            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            // Print ending inventory
169            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            // Calculate resource utilization
180            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    // Print elapsed time
216    let elapsed = start_time.elapsed();
217    println!("\nTime elapsed: {:?}", elapsed);
218}