quantrs2_anneal/applications/
manufacturing.rs

1//! Manufacturing Industry Optimization
2//!
3//! This module provides optimization solutions for the manufacturing industry,
4//! including production scheduling, resource allocation, quality control,
5//! and supply chain integration.
6
7use 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/// Production Scheduling Problem
18#[derive(Debug, Clone)]
19pub struct ProductionScheduling {
20    /// Number of jobs to schedule
21    pub num_jobs: usize,
22    /// Number of machines available
23    pub num_machines: usize,
24    /// Processing times for each job on each machine
25    pub processing_times: Vec<Vec<f64>>,
26    /// Setup times between jobs on machines
27    pub setup_times: Vec<Vec<Vec<f64>>>,
28    /// Job priorities
29    pub job_priorities: Vec<f64>,
30    /// Due dates for jobs
31    pub due_dates: Vec<f64>,
32    /// Machine capabilities (which jobs can run on which machines)
33    pub machine_capabilities: Vec<Vec<bool>>,
34    /// Resource requirements for each job
35    pub resource_requirements: Vec<HashMap<String, f64>>,
36    /// Available resources
37    pub available_resources: HashMap<String, f64>,
38    /// Quality constraints
39    pub quality_constraints: Vec<IndustryConstraint>,
40}
41
42impl ProductionScheduling {
43    /// Create a new production scheduling problem
44    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    /// Set setup times between jobs on machines
85    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    /// Set machine capabilities
97    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    /// Add resource requirement for a job
112    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    /// Set available resource capacity
129    pub fn set_resource_capacity(&mut self, resource: String, capacity: f64) {
130        self.available_resources.insert(resource, capacity);
131    }
132
133    /// Calculate makespan for a schedule
134    #[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    /// Calculate total tardiness
153    #[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    /// Calculate resource utilization
169    #[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        // Check that each job can be processed on at least one machine
230        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        // Check positive processing times
239        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; // Discretized time slots
255
256        // Binary variables: x[j][m][t] = 1 if job j starts on machine m at time t
257        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        // Objective: minimize makespan + weighted tardiness
275        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                        // Makespan penalty
284                        let makespan_penalty = completion_time * 0.1;
285                        builder.add_bias(var_idx, makespan_penalty);
286
287                        // Tardiness penalty
288                        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        // Constraint: each job scheduled exactly once
297        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            // Penalty for not scheduling exactly once
310            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        // Constraint: no overlapping jobs on same machine
321        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                        // Check if job would be running at this time
330                        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                // Penalty for multiple jobs running simultaneously
341                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        // Combined objective: makespan + weighted tardiness
364        Ok(makespan + tardiness)
365    }
366
367    fn is_feasible(&self, solution: &Self::Solution) -> bool {
368        // Check that all jobs are assigned
369        if solution.job_assignments.len() != self.num_jobs {
370            return false;
371        }
372
373        // Check machine capabilities
374        for assignment in &solution.job_assignments {
375            if !self.machine_capabilities[assignment.job_id][assignment.machine_id] {
376                return false;
377            }
378        }
379
380        // Check for overlapping jobs on same machine
381        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; // Overlap detected
397                }
398            }
399        }
400
401        // Check resource constraints
402        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/// Production schedule solution
414#[derive(Debug, Clone)]
415pub struct ProductionSchedule {
416    /// Job assignments
417    pub job_assignments: Vec<JobAssignment>,
418    /// Total makespan
419    pub makespan: f64,
420    /// Total tardiness
421    pub total_tardiness: f64,
422    /// Resource utilization
423    pub resource_utilization: HashMap<String, f64>,
424    /// Performance metrics
425    pub metrics: SchedulingMetrics,
426}
427
428/// Individual job assignment
429#[derive(Debug, Clone)]
430pub struct JobAssignment {
431    /// Job identifier
432    pub job_id: usize,
433    /// Assigned machine
434    pub machine_id: usize,
435    /// Start time
436    pub start_time: f64,
437    /// Priority
438    pub priority: f64,
439}
440
441/// Scheduling performance metrics
442#[derive(Debug, Clone)]
443pub struct SchedulingMetrics {
444    /// Average flow time
445    pub avg_flow_time: f64,
446    /// Machine utilization rates
447    pub machine_utilization: Vec<f64>,
448    /// On-time delivery rate
449    pub on_time_rate: f64,
450    /// Total setup time
451    pub total_setup_time: f64,
452    /// Efficiency score
453    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        // Decode job assignments from binary solution
465        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        // Calculate metrics
484        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        // Calculate machine utilization
513        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        // Calculate on-time delivery rate
525        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, // Simplified
554            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/// Quality Control Optimization Problem
679#[derive(Debug, Clone)]
680pub struct QualityControlOptimization {
681    /// Number of inspection stations
682    pub num_stations: usize,
683    /// Number of quality parameters
684    pub num_parameters: usize,
685    /// Inspection costs per station
686    pub inspection_costs: Vec<f64>,
687    /// Detection probabilities for each parameter at each station
688    pub detection_probabilities: Vec<Vec<f64>>,
689    /// Defect costs if not caught
690    pub defect_costs: Vec<f64>,
691    /// Station capacities
692    pub station_capacities: Vec<f64>,
693    /// Quality targets
694    pub quality_targets: Vec<f64>,
695    /// Process flow constraints
696    pub flow_constraints: Vec<IndustryConstraint>,
697}
698
699impl QualityControlOptimization {
700    /// Create new quality control optimization problem
701    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    /// Calculate total quality score
727    #[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/// Manufacturing Resource Planning (MRP) Problem
749#[derive(Debug, Clone)]
750pub struct ManufacturingResourcePlanning {
751    /// Bill of materials
752    pub bill_of_materials: HashMap<String, Vec<(String, f64)>>,
753    /// Lead times for materials
754    pub lead_times: HashMap<String, f64>,
755    /// Inventory levels
756    pub inventory_levels: HashMap<String, f64>,
757    /// Demand forecast
758    pub demand_forecast: HashMap<String, Vec<f64>>,
759    /// Supplier capabilities
760    pub supplier_capabilities: HashMap<String, Vec<String>>,
761    /// Cost structures
762    pub cost_structure: HashMap<String, f64>,
763}
764
765/// Binary wrapper for Production Scheduling that works with binary solutions
766#[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        // Convert binary solution to ProductionSchedule for evaluation
803        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        // Convert binary solution to ProductionSchedule for feasibility check
809        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
817/// Create benchmark manufacturing problems
818pub 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    // Problem 1: Small production scheduling
825    let processing_times = vec![
826        vec![5.0, 8.0, 3.0], // Job 0
827        vec![7.0, 4.0, 6.0], // Job 1
828        vec![6.0, 9.0, 2.0], // Job 2
829    ];
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    // Problem 2: Larger scheduling problem
841    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    // Problem 3: Quality control optimization
856    let inspection_costs = vec![50.0, 75.0, 100.0];
857    let detection_probs = vec![
858        vec![0.8, 0.6, 0.9], // Station 0
859        vec![0.7, 0.9, 0.7], // Station 1
860        vec![0.9, 0.8, 0.8], // Station 2
861    ];
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    // Note: QualityControlOptimization would need to implement OptimizationProblem trait
867
868    Ok(problems)
869}
870
871/// Solve production scheduling using quantum annealing
872pub fn solve_production_scheduling(
873    problem: &ProductionScheduling,
874    params: Option<AnnealingParams>,
875) -> ApplicationResult<ProductionSchedule> {
876    // Convert to QUBO
877    let (qubo, _var_map) = problem.to_qubo()?;
878
879    // Convert to Ising
880    let ising = IsingModel::from_qubo(&qubo);
881
882    // Set up annealing parameters
883    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    // Solve with classical annealing
893    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    // Convert solution back to production schedule
901    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); // max(5.0, 4.0)
956    }
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        // Job 0: completion at 10, due at 8, tardiness = 2 * 1.0 = 2.0
996        // Job 1: completion at 25, due at 12, tardiness = 13 * 2.0 = 26.0
997        // Total: 28.0
998        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]; // Only use station 0
1025        let score = quality_control.calculate_quality_score(&allocation);
1026
1027        // Expected: (0.8 * 0.95 + 0.6 * 0.95) / 2 = (0.76 + 0.57) / 2 = 0.665
1028        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}