product_mix/
product-mix.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 - Production Mix Optimization Demo");
16    
17    // Define the problem data
18    let raw_materials = vec!["A", "B", "C"];
19    let products = vec!["Super", "Unleaded", "Super_Unleaded"];
20    
21    // Raw material properties
22    let mut octane_number = HashMap::new();
23    octane_number.insert("A", 120.0);
24    octane_number.insert("B", 90.0);
25    octane_number.insert("C", 130.0);
26    
27    let mut material_cost = HashMap::new();
28    material_cost.insert("A", 38.0);
29    material_cost.insert("B", 42.0);
30    material_cost.insert("C", 105.0);
31    
32    let mut max_available = HashMap::new();
33    max_available.insert("A", 1000.0);
34    max_available.insert("B", 1200.0);
35    max_available.insert("C", 700.0);
36    
37    // Product properties
38    let mut octane_requirement = HashMap::new();
39    octane_requirement.insert("Super", 94.0);
40    octane_requirement.insert("Unleaded", 92.0);
41    octane_requirement.insert("Super_Unleaded", 96.0);
42    
43    let mut selling_price = HashMap::new();
44    selling_price.insert("Super", 85.0);
45    selling_price.insert("Unleaded", 80.0);
46    selling_price.insert("Super_Unleaded", 88.0);
47    
48    let mut demand = HashMap::new();
49    demand.insert("Super", 800.0);
50    demand.insert("Unleaded", 1100.0);
51    demand.insert("Super_Unleaded", 500.0);
52    
53    // Create variables for the problem
54    let mut vars = variables!();
55    
56    // z[i,j] = amount of raw material i used in product j (tons)
57    let mut z = HashMap::new();
58    for &i in &raw_materials {
59        for &j in &products {
60            z.insert((i, j), vars.add(variable().min(0.0).name(format!("z_{}_{}", i, j))));
61        }
62    }
63    
64    // y[j] = total amount of product j produced (tons)
65    let mut y = HashMap::new();
66    for &j in &products {
67        y.insert(j, vars.add(variable().min(0.0).max(*demand.get(j).unwrap()).name(format!("y_{}", j))));
68    }
69    
70    // Objective function: maximize total profit
71    let mut objective = Expression::from(0.0);
72    
73    // Revenue part
74    for &j in &products {
75        objective += *selling_price.get(j).unwrap() * *y.get(j).unwrap();
76    }
77    
78    // Material costs part
79    for &i in &raw_materials {
80        for &j in &products {
81            objective -= *material_cost.get(i).unwrap() * *z.get(&(i, j)).unwrap();
82        }
83    }
84    
85    // Create model with maximization objective
86    let mut model = vars.maximise(objective.clone()).using(default_solver);
87    
88    // Add constraints
89    
90    // 1. Raw material availability constraints
91    for &i in &raw_materials {
92        let mut usage = Expression::from(0.0);
93        for &j in &products {
94            usage += z.get(&(i, j)).unwrap();
95        }
96        model = model.with(constraint!(usage <= *max_available.get(i).unwrap()));
97    }
98    
99    // 2. Mass balance constraints (sum of raw materials = product quantity)
100    for &j in &products {
101        let mut sum_materials = Expression::from(0.0);
102        for &i in &raw_materials {
103            sum_materials += z.get(&(i, j)).unwrap();
104        }
105        model = model.with(constraint!(sum_materials == y.get(j).unwrap()));
106    }
107    
108    // 3. Octane requirements
109    for &j in &products {
110        let mut weighted_octane = Expression::from(0.0);
111        for &i in &raw_materials {
112            weighted_octane += *octane_number.get(i).unwrap() * *z.get(&(i, j)).unwrap();
113        }
114        let octane_req = *octane_requirement.get(j).unwrap() * *y.get(j).unwrap();
115        model = model.with(constraint!(weighted_octane >= octane_req));
116    }
117    
118    // Solve the model
119    match model.solve() {
120        Ok(solution) => {
121            println!("\nOptimal Solution Found:");
122            println!("Total Profit: €{:.2}", solution.eval(&objective));
123            
124            // Print production plan
125            println!("\nProduction Quantities:");
126            for &j in &products {
127                let amount = solution.value(*y.get(j).unwrap());
128                println!("{}: {:.2} tons (Max demand: {} tons)", j, amount, demand.get(j).unwrap());
129            }
130            
131            // Print raw material usage
132            println!("\nRaw Material Usage:");
133            for &i in &raw_materials {
134                let mut total_used = 0.0;
135                for &j in &products {
136                    total_used += solution.value(*z.get(&(i, j)).unwrap());
137                }
138                let utilization = total_used / max_available.get(i).unwrap() * 100.0;
139                println!("{}: {:.2} tons ({:.1}% of available {} tons)", 
140                         i, total_used, utilization, max_available.get(i).unwrap());
141            }
142            
143            // Print product composition for each product
144            println!("\nProduct Composition:");
145            for &j in &products {
146                println!("\n{} Mix:", j);
147                let total_product = solution.value(*y.get(j).unwrap());
148                if total_product > 0.001 {  // Only print if product is produced
149                    for &i in &raw_materials {
150                        let tons_used = solution.value(*z.get(&(i, j)).unwrap());
151                        let percentage = (tons_used / total_product) * 100.0;
152                        println!("  {}: {:.1}% ({:.2} tons)", i, percentage, tons_used);
153                    }
154                } else {
155                    println!("  Not produced");
156                }
157            }
158            
159            // Calculate weighted average octane for each product
160            println!("\nOctane Verification:");
161            for &j in &products {
162                let total_product = solution.value(*y.get(j).unwrap());
163                if total_product > 0.001 {  // Only print if product is produced
164                    let mut weighted_octane_sum = 0.0;
165                    for &i in &raw_materials {
166                        weighted_octane_sum += octane_number.get(i).unwrap() * solution.value(*z.get(&(i, j)).unwrap());
167                    }
168                    let achieved_octane = weighted_octane_sum / total_product;
169                    println!("{}: Requirement = {}, Achieved = {:.2}", 
170                             j, octane_requirement.get(j).unwrap(), achieved_octane);
171                }
172            }
173            
174        },
175        Err(e) => {
176            println!("No optimal solution found. Error: {:?}", e);
177        }
178    }
179    
180    // Print elapsed time
181    let elapsed = start_time.elapsed();
182    println!("\nTime elapsed: {:?}", elapsed);
183}