1use super::{
8 ApplicationError, ApplicationResult, IndustryConstraint, IndustryObjective, IndustrySolution,
9 OptimizationProblem,
10};
11use crate::ising::IsingModel;
12use crate::qubo::{QuboBuilder, QuboFormulation};
13use crate::simulator::{AnnealingParams, ClassicalAnnealingSimulator};
14use std::collections::HashMap;
15
16use std::fmt::Write;
17#[derive(Debug, Clone)]
19pub struct ProductionScheduling {
20 pub num_jobs: usize,
22 pub num_machines: usize,
24 pub processing_times: Vec<Vec<f64>>,
26 pub setup_times: Vec<Vec<Vec<f64>>>,
28 pub job_priorities: Vec<f64>,
30 pub due_dates: Vec<f64>,
32 pub machine_capabilities: Vec<Vec<bool>>,
34 pub resource_requirements: Vec<HashMap<String, f64>>,
36 pub available_resources: HashMap<String, f64>,
38 pub quality_constraints: Vec<IndustryConstraint>,
40}
41
42impl ProductionScheduling {
43 pub fn new(
45 num_jobs: usize,
46 num_machines: usize,
47 processing_times: Vec<Vec<f64>>,
48 due_dates: Vec<f64>,
49 ) -> ApplicationResult<Self> {
50 if processing_times.len() != num_jobs {
51 return Err(ApplicationError::InvalidConfiguration(
52 "Processing times must match number of jobs".to_string(),
53 ));
54 }
55
56 for (i, times) in processing_times.iter().enumerate() {
57 if times.len() != num_machines {
58 return Err(ApplicationError::InvalidConfiguration(format!(
59 "Processing times for job {i} must match number of machines"
60 )));
61 }
62 }
63
64 if due_dates.len() != num_jobs {
65 return Err(ApplicationError::InvalidConfiguration(
66 "Due dates must match number of jobs".to_string(),
67 ));
68 }
69
70 Ok(Self {
71 num_jobs,
72 num_machines,
73 processing_times,
74 setup_times: vec![vec![vec![0.0; num_jobs]; num_jobs]; num_machines],
75 job_priorities: vec![1.0; num_jobs],
76 due_dates,
77 machine_capabilities: vec![vec![true; num_machines]; num_jobs],
78 resource_requirements: vec![HashMap::new(); num_jobs],
79 available_resources: HashMap::new(),
80 quality_constraints: Vec::new(),
81 })
82 }
83
84 pub fn set_setup_times(&mut self, setup_times: Vec<Vec<Vec<f64>>>) -> ApplicationResult<()> {
86 if setup_times.len() != self.num_machines {
87 return Err(ApplicationError::InvalidConfiguration(
88 "Setup times must match number of machines".to_string(),
89 ));
90 }
91
92 self.setup_times = setup_times;
93 Ok(())
94 }
95
96 pub fn set_machine_capabilities(
98 &mut self,
99 capabilities: Vec<Vec<bool>>,
100 ) -> ApplicationResult<()> {
101 if capabilities.len() != self.num_jobs {
102 return Err(ApplicationError::InvalidConfiguration(
103 "Machine capabilities must match number of jobs".to_string(),
104 ));
105 }
106
107 self.machine_capabilities = capabilities;
108 Ok(())
109 }
110
111 pub fn add_resource_requirement(
113 &mut self,
114 job: usize,
115 resource: String,
116 amount: f64,
117 ) -> ApplicationResult<()> {
118 if job >= self.num_jobs {
119 return Err(ApplicationError::InvalidConfiguration(
120 "Job index out of bounds".to_string(),
121 ));
122 }
123
124 self.resource_requirements[job].insert(resource, amount);
125 Ok(())
126 }
127
128 pub fn set_resource_capacity(&mut self, resource: String, capacity: f64) {
130 self.available_resources.insert(resource, capacity);
131 }
132
133 #[must_use]
135 pub fn calculate_makespan(&self, schedule: &ProductionSchedule) -> f64 {
136 let mut machine_finish_times = vec![0.0f64; self.num_machines];
137
138 for assignment in &schedule.job_assignments {
139 let job = assignment.job_id;
140 let machine = assignment.machine_id;
141 let start_time = assignment.start_time;
142
143 let processing_time = self.processing_times[job][machine];
144 let finish_time = start_time + processing_time;
145
146 machine_finish_times[machine] = machine_finish_times[machine].max(finish_time);
147 }
148
149 machine_finish_times.iter().fold(0.0f64, |a, &b| a.max(b))
150 }
151
152 #[must_use]
154 pub fn calculate_tardiness(&self, schedule: &ProductionSchedule) -> f64 {
155 let mut total_tardiness = 0.0;
156
157 for assignment in &schedule.job_assignments {
158 let job = assignment.job_id;
159 let completion_time =
160 assignment.start_time + self.processing_times[job][assignment.machine_id];
161 let tardiness = (completion_time - self.due_dates[job]).max(0.0);
162 total_tardiness += tardiness * self.job_priorities[job];
163 }
164
165 total_tardiness
166 }
167
168 #[must_use]
170 pub fn calculate_resource_utilization(
171 &self,
172 schedule: &ProductionSchedule,
173 ) -> HashMap<String, f64> {
174 let mut utilization = HashMap::new();
175
176 for (resource, &capacity) in &self.available_resources {
177 let mut total_usage = 0.0;
178
179 for assignment in &schedule.job_assignments {
180 let job = assignment.job_id;
181 if let Some(&usage) = self.resource_requirements[job].get(resource) {
182 total_usage += usage;
183 }
184 }
185
186 utilization.insert(resource.clone(), total_usage / capacity);
187 }
188
189 utilization
190 }
191}
192
193impl OptimizationProblem for ProductionScheduling {
194 type Solution = ProductionSchedule;
195 type ObjectiveValue = f64;
196
197 fn description(&self) -> String {
198 format!(
199 "Production scheduling with {} jobs and {} machines",
200 self.num_jobs, self.num_machines
201 )
202 }
203
204 fn size_metrics(&self) -> HashMap<String, usize> {
205 let mut metrics = HashMap::new();
206 metrics.insert("num_jobs".to_string(), self.num_jobs);
207 metrics.insert("num_machines".to_string(), self.num_machines);
208 metrics.insert("num_resources".to_string(), self.available_resources.len());
209 metrics.insert(
210 "num_constraints".to_string(),
211 self.quality_constraints.len(),
212 );
213 metrics
214 }
215
216 fn validate(&self) -> ApplicationResult<()> {
217 if self.num_jobs == 0 {
218 return Err(ApplicationError::DataValidationError(
219 "At least one job required".to_string(),
220 ));
221 }
222
223 if self.num_machines == 0 {
224 return Err(ApplicationError::DataValidationError(
225 "At least one machine required".to_string(),
226 ));
227 }
228
229 for (job, capabilities) in self.machine_capabilities.iter().enumerate() {
231 if !capabilities.iter().any(|&capable| capable) {
232 return Err(ApplicationError::DataValidationError(format!(
233 "Job {job} cannot be processed on any machine"
234 )));
235 }
236 }
237
238 for (job, times) in self.processing_times.iter().enumerate() {
240 for (machine, &time) in times.iter().enumerate() {
241 if time < 0.0 {
242 return Err(ApplicationError::DataValidationError(format!(
243 "Negative processing time for job {job} on machine {machine}"
244 )));
245 }
246 }
247 }
248
249 Ok(())
250 }
251
252 fn to_qubo(&self) -> ApplicationResult<(crate::ising::QuboModel, HashMap<String, usize>)> {
253 let mut builder = QuboBuilder::new();
254 let time_horizon = 100; let mut var_counter = 0;
258 let mut var_map = HashMap::new();
259 let mut string_var_map = HashMap::new();
260
261 for job in 0..self.num_jobs {
262 for machine in 0..self.num_machines {
263 if self.machine_capabilities[job][machine] {
264 for time in 0..time_horizon {
265 let var_name = format!("x_{job}_{machine}_{time}");
266 var_map.insert((job, machine, time), var_counter);
267 string_var_map.insert(var_name, var_counter);
268 var_counter += 1;
269 }
270 }
271 }
272 }
273
274 for job in 0..self.num_jobs {
276 for machine in 0..self.num_machines {
277 if self.machine_capabilities[job][machine] {
278 for time in 0..time_horizon {
279 let var_idx = var_map[&(job, machine, time)];
280 let processing_time = self.processing_times[job][machine];
281 let completion_time = time as f64 + processing_time;
282
283 let makespan_penalty = completion_time * 0.1;
285 builder.add_bias(var_idx, makespan_penalty);
286
287 let tardiness = (completion_time - self.due_dates[job]).max(0.0);
289 let tardiness_penalty = tardiness * self.job_priorities[job];
290 builder.add_bias(var_idx, tardiness_penalty);
291 }
292 }
293 }
294 }
295
296 let constraint_penalty = 10_000.0;
298 for job in 0..self.num_jobs {
299 let mut job_vars = Vec::new();
300
301 for machine in 0..self.num_machines {
302 if self.machine_capabilities[job][machine] {
303 for time in 0..time_horizon {
304 job_vars.push(var_map[&(job, machine, time)]);
305 }
306 }
307 }
308
309 for &var1 in &job_vars {
311 builder.add_bias(var1, -constraint_penalty);
312 for &var2 in &job_vars {
313 if var1 != var2 {
314 builder.add_coupling(var1, var2, constraint_penalty);
315 }
316 }
317 }
318 }
319
320 for machine in 0..self.num_machines {
322 for time in 0..time_horizon {
323 let mut overlapping_vars = Vec::new();
324
325 for job in 0..self.num_jobs {
326 if self.machine_capabilities[job][machine] {
327 let processing_time = self.processing_times[job][machine] as usize;
328
329 for start_time in 0..=time {
331 if start_time + processing_time > time {
332 if let Some(&var_idx) = var_map.get(&(job, machine, start_time)) {
333 overlapping_vars.push(var_idx);
334 }
335 }
336 }
337 }
338 }
339
340 if overlapping_vars.len() > 1 {
342 for &var1 in &overlapping_vars {
343 for &var2 in &overlapping_vars {
344 if var1 != var2 {
345 builder.add_coupling(var1, var2, constraint_penalty);
346 }
347 }
348 }
349 }
350 }
351 }
352
353 Ok((builder.build(), string_var_map))
354 }
355
356 fn evaluate_solution(
357 &self,
358 solution: &Self::Solution,
359 ) -> ApplicationResult<Self::ObjectiveValue> {
360 let makespan = self.calculate_makespan(solution);
361 let tardiness = self.calculate_tardiness(solution);
362
363 Ok(makespan + tardiness)
365 }
366
367 fn is_feasible(&self, solution: &Self::Solution) -> bool {
368 if solution.job_assignments.len() != self.num_jobs {
370 return false;
371 }
372
373 for assignment in &solution.job_assignments {
375 if !self.machine_capabilities[assignment.job_id][assignment.machine_id] {
376 return false;
377 }
378 }
379
380 let mut machine_schedules: Vec<Vec<(f64, f64)>> = vec![Vec::new(); self.num_machines];
382
383 for assignment in &solution.job_assignments {
384 let start = assignment.start_time;
385 let end = start + self.processing_times[assignment.job_id][assignment.machine_id];
386 machine_schedules[assignment.machine_id].push((start, end));
387 }
388
389 for schedule in &machine_schedules {
390 let mut sorted_schedule = schedule.clone();
391 sorted_schedule
392 .sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
393
394 for i in 1..sorted_schedule.len() {
395 if sorted_schedule[i].0 < sorted_schedule[i - 1].1 {
396 return false; }
398 }
399 }
400
401 let utilization = self.calculate_resource_utilization(solution);
403 for (_, &util) in &utilization {
404 if util > 1.0 {
405 return false;
406 }
407 }
408
409 true
410 }
411}
412
413#[derive(Debug, Clone)]
415pub struct ProductionSchedule {
416 pub job_assignments: Vec<JobAssignment>,
418 pub makespan: f64,
420 pub total_tardiness: f64,
422 pub resource_utilization: HashMap<String, f64>,
424 pub metrics: SchedulingMetrics,
426}
427
428#[derive(Debug, Clone)]
430pub struct JobAssignment {
431 pub job_id: usize,
433 pub machine_id: usize,
435 pub start_time: f64,
437 pub priority: f64,
439}
440
441#[derive(Debug, Clone)]
443pub struct SchedulingMetrics {
444 pub avg_flow_time: f64,
446 pub machine_utilization: Vec<f64>,
448 pub on_time_rate: f64,
450 pub total_setup_time: f64,
452 pub efficiency_score: f64,
454}
455
456impl IndustrySolution for ProductionSchedule {
457 type Problem = ProductionScheduling;
458
459 fn from_binary(problem: &Self::Problem, binary_solution: &[i8]) -> ApplicationResult<Self> {
460 let time_horizon = 100;
461 let mut job_assignments = Vec::new();
462 let mut var_idx = 0;
463
464 for job in 0..problem.num_jobs {
466 for machine in 0..problem.num_machines {
467 if problem.machine_capabilities[job][machine] {
468 for time in 0..time_horizon {
469 if var_idx < binary_solution.len() && binary_solution[var_idx] == 1 {
470 job_assignments.push(JobAssignment {
471 job_id: job,
472 machine_id: machine,
473 start_time: f64::from(time),
474 priority: problem.job_priorities[job],
475 });
476 }
477 var_idx += 1;
478 }
479 }
480 }
481 }
482
483 let makespan = problem.calculate_makespan(&Self {
485 job_assignments: job_assignments.clone(),
486 makespan: 0.0,
487 total_tardiness: 0.0,
488 resource_utilization: HashMap::new(),
489 metrics: SchedulingMetrics {
490 avg_flow_time: 0.0,
491 machine_utilization: Vec::new(),
492 on_time_rate: 0.0,
493 total_setup_time: 0.0,
494 efficiency_score: 0.0,
495 },
496 });
497
498 let total_tardiness = problem.calculate_tardiness(&Self {
499 job_assignments: job_assignments.clone(),
500 makespan,
501 total_tardiness: 0.0,
502 resource_utilization: HashMap::new(),
503 metrics: SchedulingMetrics {
504 avg_flow_time: 0.0,
505 machine_utilization: Vec::new(),
506 on_time_rate: 0.0,
507 total_setup_time: 0.0,
508 efficiency_score: 0.0,
509 },
510 });
511
512 let mut machine_utilization = vec![0.0; problem.num_machines];
514 for assignment in &job_assignments {
515 let processing_time =
516 problem.processing_times[assignment.job_id][assignment.machine_id];
517 machine_utilization[assignment.machine_id] += processing_time;
518 }
519
520 for util in &mut machine_utilization {
521 *util /= makespan.max(1.0);
522 }
523
524 let mut on_time_count = 0;
526 for assignment in &job_assignments {
527 let completion_time = assignment.start_time
528 + problem.processing_times[assignment.job_id][assignment.machine_id];
529 if completion_time <= problem.due_dates[assignment.job_id] {
530 on_time_count += 1;
531 }
532 }
533 let on_time_rate = f64::from(on_time_count) / problem.num_jobs as f64;
534
535 let resource_utilization = problem.calculate_resource_utilization(&Self {
536 job_assignments: job_assignments.clone(),
537 makespan,
538 total_tardiness,
539 resource_utilization: HashMap::new(),
540 metrics: SchedulingMetrics {
541 avg_flow_time: 0.0,
542 machine_utilization: machine_utilization.clone(),
543 on_time_rate,
544 total_setup_time: 0.0,
545 efficiency_score: 0.0,
546 },
547 });
548
549 let metrics = SchedulingMetrics {
550 avg_flow_time: makespan / problem.num_jobs as f64,
551 machine_utilization,
552 on_time_rate,
553 total_setup_time: 0.0, efficiency_score: on_time_rate
555 * (1.0 - total_tardiness / (makespan * problem.num_jobs as f64)),
556 };
557
558 Ok(Self {
559 job_assignments,
560 makespan,
561 total_tardiness,
562 resource_utilization,
563 metrics,
564 })
565 }
566
567 fn summary(&self) -> HashMap<String, String> {
568 let mut summary = HashMap::new();
569 summary.insert("type".to_string(), "Production Scheduling".to_string());
570 summary.insert(
571 "num_jobs".to_string(),
572 self.job_assignments.len().to_string(),
573 );
574 summary.insert(
575 "makespan".to_string(),
576 format!("{:.2} hours", self.makespan),
577 );
578 summary.insert(
579 "total_tardiness".to_string(),
580 format!("{:.2} hours", self.total_tardiness),
581 );
582 summary.insert(
583 "on_time_rate".to_string(),
584 format!("{:.1}%", self.metrics.on_time_rate * 100.0),
585 );
586 summary.insert(
587 "efficiency_score".to_string(),
588 format!("{:.3}", self.metrics.efficiency_score),
589 );
590
591 let avg_machine_util = self.metrics.machine_utilization.iter().sum::<f64>()
592 / self.metrics.machine_utilization.len() as f64;
593 summary.insert(
594 "avg_machine_utilization".to_string(),
595 format!("{:.1}%", avg_machine_util * 100.0),
596 );
597
598 summary
599 }
600
601 fn metrics(&self) -> HashMap<String, f64> {
602 let mut metrics = HashMap::new();
603 metrics.insert("makespan".to_string(), self.makespan);
604 metrics.insert("total_tardiness".to_string(), self.total_tardiness);
605 metrics.insert("avg_flow_time".to_string(), self.metrics.avg_flow_time);
606 metrics.insert("on_time_rate".to_string(), self.metrics.on_time_rate);
607 metrics.insert(
608 "efficiency_score".to_string(),
609 self.metrics.efficiency_score,
610 );
611
612 for (i, &util) in self.metrics.machine_utilization.iter().enumerate() {
613 metrics.insert(format!("machine_{i}_utilization"), util);
614 }
615
616 for (resource, &util) in &self.resource_utilization {
617 metrics.insert(format!("resource_{resource}_utilization"), util);
618 }
619
620 metrics
621 }
622
623 fn export_format(&self) -> ApplicationResult<String> {
624 let mut output = String::new();
625 output.push_str("# Production Schedule Report\n\n");
626
627 output.push_str("## Schedule Summary\n");
628 let _ = writeln!(output, "Makespan: {:.2} hours", self.makespan);
629 let _ = write!(
630 output,
631 "Total Tardiness: {:.2} hours\n",
632 self.total_tardiness
633 );
634 let _ = write!(
635 output,
636 "On-time Delivery Rate: {:.1}%\n",
637 self.metrics.on_time_rate * 100.0
638 );
639 let _ = write!(
640 output,
641 "Efficiency Score: {:.3}\n",
642 self.metrics.efficiency_score
643 );
644
645 output.push_str("\n## Job Assignments\n");
646 let mut sorted_assignments = self.job_assignments.clone();
647 sorted_assignments.sort_by(|a, b| {
648 a.start_time
649 .partial_cmp(&b.start_time)
650 .unwrap_or(std::cmp::Ordering::Equal)
651 });
652
653 for assignment in &sorted_assignments {
654 let _ = write!(
655 output,
656 "Job {}: Machine {} at {:.1}h (Priority: {:.1})\n",
657 assignment.job_id,
658 assignment.machine_id,
659 assignment.start_time,
660 assignment.priority
661 );
662 }
663
664 output.push_str("\n## Machine Utilization\n");
665 for (i, &util) in self.metrics.machine_utilization.iter().enumerate() {
666 let _ = writeln!(output, "Machine {}: {:.1}%", i, util * 100.0);
667 }
668
669 output.push_str("\n## Resource Utilization\n");
670 for (resource, &util) in &self.resource_utilization {
671 let _ = writeln!(output, "{}: {:.1}%", resource, util * 100.0);
672 }
673
674 Ok(output)
675 }
676}
677
678#[derive(Debug, Clone)]
680pub struct QualityControlOptimization {
681 pub num_stations: usize,
683 pub num_parameters: usize,
685 pub inspection_costs: Vec<f64>,
687 pub detection_probabilities: Vec<Vec<f64>>,
689 pub defect_costs: Vec<f64>,
691 pub station_capacities: Vec<f64>,
693 pub quality_targets: Vec<f64>,
695 pub flow_constraints: Vec<IndustryConstraint>,
697}
698
699impl QualityControlOptimization {
700 pub fn new(
702 num_stations: usize,
703 num_parameters: usize,
704 inspection_costs: Vec<f64>,
705 detection_probabilities: Vec<Vec<f64>>,
706 defect_costs: Vec<f64>,
707 ) -> ApplicationResult<Self> {
708 if detection_probabilities.len() != num_stations {
709 return Err(ApplicationError::InvalidConfiguration(
710 "Detection probabilities must match number of stations".to_string(),
711 ));
712 }
713
714 Ok(Self {
715 num_stations,
716 num_parameters,
717 inspection_costs,
718 detection_probabilities,
719 defect_costs,
720 station_capacities: vec![100.0; num_stations],
721 quality_targets: vec![0.95; num_parameters],
722 flow_constraints: Vec::new(),
723 })
724 }
725
726 #[must_use]
728 pub fn calculate_quality_score(&self, allocation: &[bool]) -> f64 {
729 let mut total_score = 0.0;
730
731 for param in 0..self.num_parameters {
732 let mut detection_prob = 0.0;
733
734 for (station, &allocated) in allocation.iter().enumerate() {
735 if allocated {
736 detection_prob += self.detection_probabilities[station][param];
737 }
738 }
739
740 detection_prob = detection_prob.min(1.0);
741 total_score += detection_prob * self.quality_targets[param];
742 }
743
744 total_score / self.num_parameters as f64
745 }
746}
747
748#[derive(Debug, Clone)]
750pub struct ManufacturingResourcePlanning {
751 pub bill_of_materials: HashMap<String, Vec<(String, f64)>>,
753 pub lead_times: HashMap<String, f64>,
755 pub inventory_levels: HashMap<String, f64>,
757 pub demand_forecast: HashMap<String, Vec<f64>>,
759 pub supplier_capabilities: HashMap<String, Vec<String>>,
761 pub cost_structure: HashMap<String, f64>,
763}
764
765#[derive(Debug, Clone)]
767pub struct BinaryProductionScheduling {
768 inner: ProductionScheduling,
769}
770
771impl BinaryProductionScheduling {
772 #[must_use]
773 pub const fn new(inner: ProductionScheduling) -> Self {
774 Self { inner }
775 }
776}
777
778impl OptimizationProblem for BinaryProductionScheduling {
779 type Solution = Vec<i8>;
780 type ObjectiveValue = f64;
781
782 fn description(&self) -> String {
783 self.inner.description()
784 }
785
786 fn size_metrics(&self) -> HashMap<String, usize> {
787 self.inner.size_metrics()
788 }
789
790 fn validate(&self) -> ApplicationResult<()> {
791 self.inner.validate()
792 }
793
794 fn to_qubo(&self) -> ApplicationResult<(crate::ising::QuboModel, HashMap<String, usize>)> {
795 self.inner.to_qubo()
796 }
797
798 fn evaluate_solution(
799 &self,
800 solution: &Self::Solution,
801 ) -> ApplicationResult<Self::ObjectiveValue> {
802 let schedule_solution = ProductionSchedule::from_binary(&self.inner, solution)?;
804 self.inner.evaluate_solution(&schedule_solution)
805 }
806
807 fn is_feasible(&self, solution: &Self::Solution) -> bool {
808 if let Ok(schedule_solution) = ProductionSchedule::from_binary(&self.inner, solution) {
810 self.inner.is_feasible(&schedule_solution)
811 } else {
812 false
813 }
814 }
815}
816
817pub fn create_benchmark_problems(
819 size: usize,
820) -> ApplicationResult<Vec<Box<dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>>>>
821{
822 let mut problems = Vec::new();
823
824 let processing_times = vec![
826 vec![5.0, 8.0, 3.0], vec![7.0, 4.0, 6.0], vec![6.0, 9.0, 2.0], ];
830 let due_dates = vec![15.0, 20.0, 18.0];
831
832 let mut small_scheduling = ProductionScheduling::new(3, 3, processing_times, due_dates)?;
833 small_scheduling.job_priorities = vec![1.0, 2.0, 1.5];
834
835 problems.push(Box::new(BinaryProductionScheduling::new(small_scheduling))
836 as Box<
837 dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>,
838 >);
839
840 if size >= 5 {
842 let large_processing_times: Vec<Vec<f64>> = (0..size)
843 .map(|i| (0..3).map(|j| 3.0 + ((i + j) as f64 * 2.0) % 8.0).collect())
844 .collect();
845 let large_due_dates: Vec<f64> = (0..size).map(|i| (i as f64).mul_add(5.0, 20.0)).collect();
846
847 let large_scheduling =
848 ProductionScheduling::new(size, 3, large_processing_times, large_due_dates)?;
849 problems.push(Box::new(BinaryProductionScheduling::new(large_scheduling))
850 as Box<
851 dyn OptimizationProblem<Solution = Vec<i8>, ObjectiveValue = f64>,
852 >);
853 }
854
855 let inspection_costs = vec![50.0, 75.0, 100.0];
857 let detection_probs = vec![
858 vec![0.8, 0.6, 0.9], vec![0.7, 0.9, 0.7], vec![0.9, 0.8, 0.8], ];
862 let defect_costs = vec![1000.0, 1500.0, 800.0];
863
864 let quality_control =
865 QualityControlOptimization::new(3, 3, inspection_costs, detection_probs, defect_costs)?;
866 Ok(problems)
869}
870
871pub fn solve_production_scheduling(
873 problem: &ProductionScheduling,
874 params: Option<AnnealingParams>,
875) -> ApplicationResult<ProductionSchedule> {
876 let (qubo, _var_map) = problem.to_qubo()?;
878
879 let ising = IsingModel::from_qubo(&qubo);
881
882 let annealing_params = params.unwrap_or_else(|| {
884 let mut p = AnnealingParams::default();
885 p.num_sweeps = 25_000;
886 p.num_repetitions = 30;
887 p.initial_temperature = 5.0;
888 p.final_temperature = 0.001;
889 p
890 });
891
892 let simulator = ClassicalAnnealingSimulator::new(annealing_params)
894 .map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
895
896 let result = simulator
897 .solve(&ising)
898 .map_err(|e| ApplicationError::OptimizationError(e.to_string()))?;
899
900 ProductionSchedule::from_binary(problem, &result.best_spins)
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907
908 #[test]
909 fn test_production_scheduling_creation() {
910 let processing_times = vec![vec![5.0, 8.0], vec![7.0, 4.0]];
911 let due_dates = vec![15.0, 20.0];
912
913 let scheduling = ProductionScheduling::new(2, 2, processing_times, due_dates)
914 .expect("ProductionScheduling creation should succeed");
915 assert_eq!(scheduling.num_jobs, 2);
916 assert_eq!(scheduling.num_machines, 2);
917 }
918
919 #[test]
920 fn test_makespan_calculation() {
921 let processing_times = vec![vec![5.0, 8.0], vec![7.0, 4.0]];
922 let due_dates = vec![15.0, 20.0];
923
924 let scheduling = ProductionScheduling::new(2, 2, processing_times, due_dates)
925 .expect("ProductionScheduling creation should succeed");
926
927 let schedule = ProductionSchedule {
928 job_assignments: vec![
929 JobAssignment {
930 job_id: 0,
931 machine_id: 0,
932 start_time: 0.0,
933 priority: 1.0,
934 },
935 JobAssignment {
936 job_id: 1,
937 machine_id: 1,
938 start_time: 0.0,
939 priority: 1.0,
940 },
941 ],
942 makespan: 0.0,
943 total_tardiness: 0.0,
944 resource_utilization: HashMap::new(),
945 metrics: SchedulingMetrics {
946 avg_flow_time: 0.0,
947 machine_utilization: Vec::new(),
948 on_time_rate: 0.0,
949 total_setup_time: 0.0,
950 efficiency_score: 0.0,
951 },
952 };
953
954 let makespan = scheduling.calculate_makespan(&schedule);
955 assert_eq!(makespan, 5.0); }
957
958 #[test]
959 fn test_tardiness_calculation() {
960 let processing_times = vec![vec![10.0], vec![15.0]];
961 let due_dates = vec![8.0, 12.0];
962
963 let mut scheduling = ProductionScheduling::new(2, 1, processing_times, due_dates)
964 .expect("ProductionScheduling creation should succeed");
965 scheduling.job_priorities = vec![1.0, 2.0];
966
967 let schedule = ProductionSchedule {
968 job_assignments: vec![
969 JobAssignment {
970 job_id: 0,
971 machine_id: 0,
972 start_time: 0.0,
973 priority: 1.0,
974 },
975 JobAssignment {
976 job_id: 1,
977 machine_id: 0,
978 start_time: 10.0,
979 priority: 2.0,
980 },
981 ],
982 makespan: 0.0,
983 total_tardiness: 0.0,
984 resource_utilization: HashMap::new(),
985 metrics: SchedulingMetrics {
986 avg_flow_time: 0.0,
987 machine_utilization: Vec::new(),
988 on_time_rate: 0.0,
989 total_setup_time: 0.0,
990 efficiency_score: 0.0,
991 },
992 };
993
994 let tardiness = scheduling.calculate_tardiness(&schedule);
995 assert_eq!(tardiness, 28.0);
999 }
1000
1001 #[test]
1002 fn test_quality_control_creation() {
1003 let costs = vec![50.0, 75.0];
1004 let detection_probs = vec![vec![0.8, 0.6], vec![0.7, 0.9]];
1005 let defect_costs = vec![1000.0, 1500.0];
1006
1007 let quality_control =
1008 QualityControlOptimization::new(2, 2, costs, detection_probs, defect_costs)
1009 .expect("QualityControlOptimization creation should succeed");
1010 assert_eq!(quality_control.num_stations, 2);
1011 assert_eq!(quality_control.num_parameters, 2);
1012 }
1013
1014 #[test]
1015 fn test_quality_score_calculation() {
1016 let costs = vec![50.0, 75.0];
1017 let detection_probs = vec![vec![0.8, 0.6], vec![0.7, 0.9]];
1018 let defect_costs = vec![1000.0, 1500.0];
1019
1020 let quality_control =
1021 QualityControlOptimization::new(2, 2, costs, detection_probs, defect_costs)
1022 .expect("QualityControlOptimization creation should succeed");
1023
1024 let allocation = vec![true, false]; let score = quality_control.calculate_quality_score(&allocation);
1026
1027 assert!((score - 0.665).abs() < 1e-6);
1029 }
1030
1031 #[test]
1032 fn test_benchmark_problems() {
1033 let problems =
1034 create_benchmark_problems(5).expect("create_benchmark_problems should succeed");
1035 assert_eq!(problems.len(), 2);
1036
1037 for problem in &problems {
1038 assert!(problem.validate().is_ok());
1039 let metrics = problem.size_metrics();
1040 assert!(metrics.contains_key("num_jobs"));
1041 assert!(metrics.contains_key("num_machines"));
1042 }
1043 }
1044}