permu_rs/
problems.rs

1//! The `problems` module contains some permutation based problem definitions. This problems are, the
2//! quadratic assignment problem (QAP), permutation flowshop scheduling problem (PFSP) and the linear ordering
3//! problem (LOP). 
4//! Problems are intended to be used through the `ProblemInstance` enum. Finally, the `ProblemType`enum is 
5//! provided in order to get the problem type from the instance's name.
6
7use std::io;
8use std::io::{BufReader, BufRead};
9use std::convert::{TryFrom, TryInto};
10use std::fmt::{Display, Debug};
11use std::fs::File;
12use std::cmp::max;
13
14use rand::distributions::range::SampleRange;
15use std::ops::Sub;
16
17use crate::errors::Error;
18use crate::permutation::PermuPopulation;
19
20/// Contains all problem types defined in this crate. Implents `TryFrom<&str>` trait, so it's
21/// useful to get the problem type from the instance's name.
22pub enum ProblemType {
23    Qap,
24    Pfsp,
25    Lop,
26}
27
28impl TryFrom<&str> for ProblemType {
29    type Error = Error;
30
31    fn try_from(path: &str) -> Result<Self, Self::Error> {
32        let splitted: Vec<&str> = path.split(".").collect();
33        
34        // Check if there's any extension
35        if splitted.len() < 2 {
36            return Err(Error::Io(
37                    io::Error::new(io::ErrorKind::InvalidInput, 
38                        "Instance extension not found")));
39        }
40        
41        match splitted[1] {
42            "dat" => Ok(ProblemType::Qap),
43            "fsp" => Ok(ProblemType::Pfsp),
44            "lop" => Ok(ProblemType::Lop),
45            _ => Err(Error::Io(
46                    io::Error::new(io::ErrorKind::InvalidInput, 
47                        format!("Wrong instance extension {}", splitted[1])))),
48        }
49    }
50}
51
52/// This enum contains problem definitions.  
53pub enum ProblemInstance {
54    /// Quadratic Assignment Problem (QAP)
55    Qap(usize, Vec<Vec<usize>>, Vec<Vec<usize>>),
56    /// Permutation Flowshop Scheduling Problem (PFSP) 
57    Pfsp(usize, usize, Vec<Vec<usize>>),
58    /// Linear Ordering Problem (LOP) 
59    Lop(usize, Vec<Vec<usize>>),
60}
61
62impl ProblemInstance {
63    
64    /// Returns the size of the instance. All soltions must 
65    /// be of the length of the problem's size.
66    pub fn size(&self) -> usize {
67        match self {
68            ProblemInstance::Qap(n, _, _) => *n,
69            ProblemInstance::Pfsp(n, _, _) => *n,
70            ProblemInstance::Lop(n, _) => *n,
71        } 
72    }
73    
74    /// Loads a `ProblemInstance` from a file given as a path.
75    ///
76    /// # Errors
77    /// Returns an `Error::Io` error if an error occurs loading the problem 
78    /// instance from the given path.
79    pub fn load(path: &str) -> Result<Self, Error> {
80        match ProblemType::try_from(path) {
81            Ok(ProblemType::Qap) => Ok(Qap::load(&path)?),
82            Ok(ProblemType::Pfsp) => Ok(Pfsp::load(&path)?),
83            Ok(ProblemType::Lop) => Ok(Lop::load(&path)?), 
84            Err(err) => panic!(err),
85        }
86    }
87    
88    /// Evaluates each solution of a given `PermuPopulation` and stores the fitness values inside a
89    /// given fitness vector.
90    ///
91    /// # Example
92    /// ```
93    /// use permu_rs::permutation::PermuPopulation;
94    /// use permu_rs::problems::ProblemInstance;
95    ///
96    /// let paths = ["PFSP/tai100_20_0.fsp", 
97    ///              "QAP/tai100a.dat",
98    ///              "/LOP/N-be75eec_150.lop"];
99    /// for name in paths.iter() {
100    ///     let path = format!("instances/{}", name);
101    ///     let instance = ProblemInstance::load(&path).unwrap();
102    ///     
103    ///     let pop = PermuPopulation::<u16>::random(100, instance.size());
104    ///     let mut fitness = vec![0; 100];
105    ///
106    ///     instance.evaluate(&pop, &mut fitness).unwrap();
107    /// }
108    /// ```
109    pub fn evaluate<T>(&self, 
110            solutions: &PermuPopulation<T>, 
111            fitness_vec: &mut Vec<usize>) -> Result<(), Error>
112        where T :
113            Copy +
114            From<u8> +
115            TryFrom<usize> +
116            TryInto<usize> +
117            Eq +
118            SampleRange +
119            PartialOrd +
120            Sub +
121            Display +
122            Debug 
123    {
124        match self {
125            ProblemInstance::Qap(_,_,_) => Qap::evaluate(self, solutions, fitness_vec),
126            ProblemInstance::Pfsp(_, _,_) => Pfsp::evaluate(self, solutions, fitness_vec),
127            ProblemInstance::Lop(_,_) => Lop::evaluate(self, solutions, fitness_vec),
128        } 
129    }
130}
131
132/// Contains basic functions all problem's must include.
133#[doc(hidden)]
134trait Problem {
135    /// Loads an instance of a problem from a specified path.
136    fn load(path: &str) -> Result<ProblemInstance, Error>;
137    
138    /// Evaluates a given solution (`Permutation`) returning it's fitness value.
139    fn evaluate<T>(instace: &ProblemInstance, 
140        solutions: &PermuPopulation<T>, 
141        fitness_vec: &mut Vec<usize>) -> Result<(), Error>
142        where T :
143            Copy +
144            From<u8> +
145            TryFrom<usize> +
146            TryInto<usize> +
147            Eq +
148            SampleRange +
149            PartialOrd +
150            Sub +
151            Display +
152            Debug;
153    
154    // Utility to convert a buffer into a matrix of the specified shape.
155    fn lines2matrix(buffer: &mut BufReader<File>, 
156        n_lines: usize, 
157        n_elems: usize) -> Result<Vec<Vec<usize>>, Error> {
158        
159        // Init the matrix
160        let mut matrix = vec![Vec::with_capacity(n_elems); n_lines];
161
162        for i_line in 0..n_lines {
163            // Read the line and split in withespaces
164            let mut line = String::new();
165            buffer.read_line(&mut line)?;
166            let line = line.split_whitespace();
167
168            // Parse all numbers from str to usize
169            let mut count = 0;
170            for str_num in line {
171                matrix[i_line].push(match str_num.trim().parse() {
172                    Ok(n) => n,
173                    Err(_) => return Err(Error::ParseError),
174                });
175                count += 1;
176            }
177
178            // Check if line length is ok
179            if count != n_elems {
180                return Err(Error::Io(
181                        io::Error::new(io::ErrorKind::InvalidData, 
182                            "All rows must have the same length as the instance size")));
183            }
184        }
185        Ok(matrix)
186    }
187}
188
189/// Quadratic Assignment Problem definition.
190#[doc(hidden)]
191struct Qap {}
192
193impl Problem for Qap {
194    
195    fn load(path: &str) -> Result<ProblemInstance, Error> {
196        // Open the file
197        let file = File::open(path)?;
198        let mut reader = BufReader::new(file);
199        
200        // Get instance's size
201        let mut size_str = String::new();
202        let _n = reader.read_line(&mut size_str); // Get size
203        
204        let size: usize = size_str.trim()
205            .parse()
206            .unwrap();
207
208        let distance = Self::lines2matrix(&mut reader, size, size)?;
209        let flow = Self::lines2matrix(&mut reader, size, size)?;
210
211        Ok(ProblemInstance::Qap(size, distance, flow))
212    }
213
214    fn evaluate<T>(instace: &ProblemInstance, 
215        solutions: &PermuPopulation<T>, 
216        fitness_vec: &mut Vec<usize>) -> Result<(), Error>
217        where T :
218            Copy +
219            From<u8> +
220            TryFrom<usize> +
221            TryInto<usize> +
222            Eq +
223            SampleRange +
224            PartialOrd +
225            Sub +
226            Display +
227            Debug {
228        
229        // Check instance type and get instace parameters
230        let (size, distance, flow) = match instace {
231            ProblemInstance::Qap(size, dist, flow) => (size, dist, flow),
232            _ => return Err(Error::IncorrectProblemInstance),
233        };
234
235        // Check if the solution's length matches with the size of the problem
236        if solutions.population[0].len() != *size {
237            return Err(Error::LengthError);
238        }
239
240        for (index, solution) in solutions.population.iter().enumerate() {
241            let mut fitness = 0; 
242            for i in 0..*size {
243                for j in 0..*size {
244
245                    let fact_a: usize = match solution.permu[i].try_into() {
246                        Ok(n) => n,
247                        Err(_) => return Err(Error::ParseError),
248                    };
249                    let fact_b: usize = match solution.permu[j].try_into() {
250                        Ok(n) => n,
251                        Err(_) => return Err(Error::ParseError),
252                    };
253
254                    let dist_ab = distance[i][j];
255                    let flow_ab = flow[fact_a][fact_b];
256
257                    fitness += dist_ab*flow_ab;
258                }
259            }
260            fitness_vec[index] = fitness;
261        }
262        Ok(())
263    }
264}
265
266/// Permutation Flowshop Scheduling Problem definition
267#[doc(hidden)]
268struct Pfsp {}
269
270impl Problem for Pfsp {
271
272    fn load(path: &str) -> Result<ProblemInstance, Error> {
273        // Open the file
274        let file = File::open(path)?;
275        let mut reader = BufReader::new(file);
276        
277        // Read size lines from matrix
278        let mut size_str = String::new();
279        let _n = reader.read_line(&mut size_str); // Ignore first line
280        size_str.clear();
281        let _n = reader.read_line(&mut size_str); // Get size
282        
283        // Parse instance's sizes
284        let mut splitted = size_str.split_whitespace();
285        let mut count = 0;
286        let mut sizes = vec![]; // n_jobs and n_machines
287        while count < 2 {
288            if let Some(item) = splitted.next() {
289                let num: usize = match item.trim().parse() {
290                    Ok(n) => n,
291                    Err(_) => continue,
292                };
293
294                sizes.push(num);
295                count += 1;
296
297            } else {
298                return Err(Error::Io(io::Error::new(
299                            io::ErrorKind::InvalidInput, 
300                            "Cannot find size inside instance file")));
301            }
302        }
303        // Ignore a line
304        let _n = reader.read_line(&mut size_str);
305
306        // Read the matrix
307        let matrix = Self::lines2matrix(&mut reader, sizes[1], sizes[0])?;
308        Ok(ProblemInstance::Pfsp(sizes[0], sizes[1], matrix))
309    }
310
311    fn evaluate<T>(instace: &ProblemInstance, 
312        solutions: &PermuPopulation<T>, 
313        fitness_vec: &mut Vec<usize>) -> Result<(), Error>
314        where T :
315            Copy +
316            From<u8> +
317            TryFrom<usize> +
318            TryInto<usize> +
319            Eq +
320            SampleRange +
321            PartialOrd +
322            Sub +
323            Display +
324            Debug {
325
326        // Check instance type and get params 
327        let (size, n_machines, matrix) = match instace {
328            ProblemInstance::Pfsp(n, m, mat) => (n, m, mat),
329            _ => return Err(Error::IncorrectProblemInstance),
330        };
331
332        // Check if solution length is correct
333        if solutions.population[0].len() != *size {
334            return Err(Error::LengthError);
335        }
336
337        for (index, solution) in solutions.population.iter().enumerate() {
338            let mut tft = 0;
339            let mut b = vec![0;*n_machines];  
340            for (job_i, job_n) in solution.permu.iter().enumerate() {
341                let mut pt = 0;
342                for machine in 0..*n_machines {
343
344                    let job: usize = match T::try_into(*job_n) {
345                        Ok(n) => n,
346                        Err(_) => return Err(Error::ParseError),
347                    };
348
349                    if job_i == 0 && machine == 0 {
350                        pt = matrix[machine][job];
351                    }
352                    else if job_i > 0 && machine == 0 {
353                        pt = b[machine] + matrix[machine][job];
354                    }
355                    else if job_i == 0 && machine > 0 {
356                        pt = b[machine-1] + matrix[machine][job];
357                    }
358                    else if job_i > 0 && machine > 0 {
359                        pt = max(b[machine-1], b[machine]) + matrix[machine][job];
360                    }
361
362                    b[machine] = pt;
363                }
364                tft += pt;
365            }
366            fitness_vec[index] = tft;
367        }
368        Ok(())
369    }
370}
371
372/// Linear Ordering Problem definition 
373#[doc(hidden)]
374struct Lop {}
375
376impl Problem for Lop {
377
378    fn load(path: &str) -> Result<ProblemInstance, Error> {
379        // Open the file
380        let file = File::open(path)?;
381        let mut reader = BufReader::new(file);
382        
383        // Get instance's size
384        let mut size_str = String::new();
385        let _n = reader.read_line(&mut size_str); // Get size
386        
387        let size: usize = size_str.trim()
388            .parse()
389            .unwrap();
390
391        let matrix = Self::lines2matrix(&mut reader, size, size)?;
392
393        Ok(ProblemInstance::Lop(size, matrix))
394    }
395
396    fn evaluate<T>(instace: &ProblemInstance, 
397        solutions: &PermuPopulation<T>, 
398        fitness_vec: &mut Vec<usize>) -> Result<(), Error>
399        where T :
400            Copy +
401            From<u8> +
402            TryFrom<usize> +
403            TryInto<usize> +
404            Eq +
405            SampleRange +
406            PartialOrd +
407            Sub +
408            Display +
409            Debug 
410    {
411        // Check instance type and get params 
412        let (size, matrix) = match instace {
413            ProblemInstance::Lop(n, mat) => (n, mat),
414            _ => return Err(Error::IncorrectProblemInstance),
415        };
416
417        // Check if the permu's and length and instance's size are correct
418        if solutions.population[0].len() != *size {
419            return Err(Error::LengthError);
420        }
421        
422        for (index, solution) in solutions.population.iter().enumerate() {
423            let mut fitness = 0;
424            (0..*size-1).for_each(|i| {
425                    (i+1..*size).for_each(|j| {
426
427                        let elem1 = match solution.permu[i].try_into() {
428                            Ok(a) => a,
429                            Err(_) => unreachable!(),
430                        };
431                        let elem2 = match solution.permu[j].try_into() {
432                            Ok(a) => a,
433                            Err(_) => unreachable!(),
434                        };
435
436                        fitness += matrix[elem1][elem2];
437                    });
438                });
439            fitness_vec[index] = fitness;
440        }
441        Ok(()) 
442    }
443}
444
445#[cfg(test)]
446mod test {
447
448    use crate::problems::*;
449    use std::convert::TryInto;
450    use crate::permutation::{PermuPopulation, Permutation};
451
452    #[test]
453    fn read_lop() {
454        let instance_path = "instances/LOP/N-be75eec_150.lop";
455        let ptype: ProblemType = instance_path.try_into().unwrap(); 
456        
457        if let ProblemType::Lop = ptype {
458        } else {
459            panic!("The instace type is not LOP");
460        }
461
462        let pop = PermuPopulation::<u8>::random(10, 150);
463        let instance = Lop::load(instance_path).unwrap(); 
464        let mut fitness = vec![0;10];
465
466        instance.evaluate(&pop, &mut fitness).unwrap();
467        
468        fitness.iter()
469            .for_each(|x| println!("permu fitness: {}", x));
470    }
471
472    #[test]
473    fn read_qap() {
474        let instance_path = "instances/QAP/tai20b.dat";
475        let ptype: ProblemType = instance_path.try_into().unwrap(); 
476        
477        if let ProblemType::Qap = ptype {
478        } else {
479            panic!("The instace type is not LOP");
480        }
481
482        let instance = Qap::load(instance_path).unwrap(); 
483
484        let mut permu = Permutation::<u8>::from_vec_unsec(
485            vec![12,6,18,16,7,2,5,3,14,0,13,9,15,1,8,10,4,19,17,11]);
486        let pop = PermuPopulation::<u8>::from_vec(vec![permu]);
487
488        let mut fitness = vec![0];
489        instance.evaluate(&pop, &mut fitness).unwrap();
490        assert_eq!(125551590, fitness[0]);
491    }
492
493    #[test]
494    fn read_pfsp() {
495        let instance_path = "instances/PFSP/tai20_5_0.fsp";
496
497        let ptype: ProblemType = instance_path.try_into().unwrap(); 
498        
499        if let ProblemType::Pfsp = ptype {
500        } else {
501            panic!("The instace type is not LOP");
502        }
503
504        let instance = Pfsp::load(instance_path).unwrap(); 
505
506        let mut permu = Permutation::<u8>::from_vec_unsec(
507            vec![11,12,0,13,14,9,10,5,2,19,18,17,7,4,3,8,1,15,6,16]);
508        permu.clone().invert(&mut permu).unwrap();
509        let pop = PermuPopulation::<u8>::from_vec(vec![permu]);
510
511        let mut fitness = vec![0];
512        instance.evaluate(&pop, &mut fitness).unwrap();
513        assert_eq!(14033, fitness[0]);
514    }
515
516    #[test]
517    fn test_load() {
518        use crate::permutation::PermuPopulation;
519
520        let paths = ["PFSP/tai100_20_0.fsp", 
521                     "QAP/tai100a.dat",
522                     "/LOP/N-be75eec_150.lop"];
523        for name in paths.iter() {
524            let path = format!("instances/{}", name);
525
526            let instance = match ProblemType::try_from(path.as_str()) {
527                Ok(ProblemType::Qap) => Qap::load(&path).unwrap(),
528                Ok(ProblemType::Pfsp) => Pfsp::load(&path).unwrap(),
529                Ok(ProblemType::Lop) => Lop::load(&path).unwrap(), 
530                Err(err) => panic!(err),
531            };
532            
533            let pop = PermuPopulation::<u16>::random(100, instance.size());
534            let mut fitness = vec![0; 100];
535
536            instance.evaluate(&pop, &mut fitness).unwrap();
537        }
538    }
539}