1#![deny(missing_docs)]
5
6mod genetic;
7mod guillotine;
8mod maxrects;
9
10#[cfg(test)]
11mod tests;
12
13use genetic::population::Population;
14use genetic::unit::Unit;
15use guillotine::GuillotineBin;
16use maxrects::MaxRectsBin;
17
18use fnv::FnvHashSet;
19use rand::prelude::*;
20use rand::seq::SliceRandom;
21
22use std::borrow::Borrow;
23use std::cmp;
24use std::hash::{Hash, Hasher};
25
26#[cfg(feature = "serialize")]
27use serde::{Deserialize, Serialize};
28
29#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
31#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
32#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
33pub enum PatternDirection {
34 None,
36
37 ParallelToWidth,
39
40 ParallelToLength,
42}
43
44impl PatternDirection {
45 fn rotated(self) -> PatternDirection {
47 match self {
48 PatternDirection::None => PatternDirection::None,
49 PatternDirection::ParallelToWidth => PatternDirection::ParallelToLength,
50 PatternDirection::ParallelToLength => PatternDirection::ParallelToWidth,
51 }
52 }
53}
54
55impl Default for PatternDirection {
56 fn default() -> Self {
57 PatternDirection::None
58 }
59}
60
61#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
63#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
64#[derive(Clone, Debug)]
65pub struct CutPiece {
66 pub quantity: usize,
68
69 pub external_id: Option<usize>,
73
74 pub width: usize,
76
77 pub length: usize,
79
80 pub pattern_direction: PatternDirection,
82
83 pub can_rotate: bool,
85}
86
87#[derive(Clone, Debug)]
88pub(crate) struct CutPieceWithId {
89 pub(crate) id: usize,
90 pub(crate) external_id: Option<usize>,
91 pub(crate) width: usize,
92 pub(crate) length: usize,
93 pub(crate) pattern_direction: PatternDirection,
94 pub(crate) can_rotate: bool,
95}
96
97impl Hash for CutPieceWithId {
98 fn hash<H: Hasher>(&self, state: &mut H) {
99 self.id.hash(state);
100 }
101}
102impl PartialEq for CutPieceWithId {
103 fn eq(&self, other: &CutPieceWithId) -> bool {
104 self.id == other.id
105 }
106}
107impl Eq for CutPieceWithId {}
108
109#[derive(Clone, Debug)]
110pub(crate) struct UsedCutPiece {
111 pub(crate) id: usize,
112 pub(crate) external_id: Option<usize>,
113 pub(crate) rect: Rect,
114 pub(crate) pattern_direction: PatternDirection,
115 pub(crate) is_rotated: bool,
116 pub(crate) can_rotate: bool,
117}
118
119impl PartialEq for UsedCutPiece {
120 fn eq(&self, other: &UsedCutPiece) -> bool {
121 self.id == other.id
122 }
123}
124impl Eq for UsedCutPiece {}
125
126impl From<&UsedCutPiece> for CutPieceWithId {
127 fn from(used_cut_piece: &UsedCutPiece) -> Self {
128 let (width, length, pattern_direction) = if used_cut_piece.is_rotated {
129 (
130 used_cut_piece.rect.length,
131 used_cut_piece.rect.width,
132 used_cut_piece.pattern_direction.rotated(),
133 )
134 } else {
135 (
136 used_cut_piece.rect.width,
137 used_cut_piece.rect.length,
138 used_cut_piece.pattern_direction,
139 )
140 };
141
142 Self {
143 id: used_cut_piece.id,
144 external_id: used_cut_piece.external_id,
145 width,
146 length,
147 can_rotate: used_cut_piece.can_rotate,
148 pattern_direction,
149 }
150 }
151}
152
153impl From<&UsedCutPiece> for ResultCutPiece {
154 fn from(used_cut_piece: &UsedCutPiece) -> Self {
155 Self {
156 external_id: used_cut_piece.external_id,
157 x: used_cut_piece.rect.x,
158 y: used_cut_piece.rect.y,
159 width: used_cut_piece.rect.width,
160 length: used_cut_piece.rect.length,
161 pattern_direction: used_cut_piece.pattern_direction,
162 is_rotated: used_cut_piece.is_rotated,
163 }
164 }
165}
166
167#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
169#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
170#[derive(Clone, Debug, PartialEq, Eq)]
171pub struct ResultCutPiece {
172 pub external_id: Option<usize>,
174
175 pub x: usize,
177
178 pub y: usize,
180
181 pub width: usize,
183
184 pub length: usize,
186
187 pub pattern_direction: PatternDirection,
189
190 pub is_rotated: bool,
193}
194
195#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
198#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
199#[derive(Hash, Copy, Clone, Debug, Eq, PartialEq)]
200pub struct StockPiece {
201 pub width: usize,
203
204 pub length: usize,
206
207 pub pattern_direction: PatternDirection,
209
210 pub price: usize,
213
214 pub quantity: Option<usize>,
216}
217
218impl StockPiece {
219 fn fits_cut_piece(&self, cut_piece: &CutPieceWithId) -> bool {
221 let rect = Rect {
222 x: 0,
223 y: 0,
224 width: self.width,
225 length: self.length,
226 };
227
228 rect.fit_cut_piece(self.pattern_direction, cut_piece, false) != Fit::None
229 }
230
231 fn dec_quantity(&mut self) {
233 if let Some(ref mut quantity) = self.quantity {
234 *quantity -= 1;
235 }
236 }
237
238 fn inc_quantity(&mut self) {
240 if let Some(ref mut quantity) = self.quantity {
241 *quantity += 1;
242 }
243 }
244}
245
246#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
248#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
249#[derive(Clone, Debug)]
250pub struct ResultStockPiece {
251 pub width: usize,
253
254 pub length: usize,
256
257 pub pattern_direction: PatternDirection,
259
260 pub cut_pieces: Vec<ResultCutPiece>,
262
263 pub waste_pieces: Vec<Rect>,
265
266 pub price: usize,
268}
269
270#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
272#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
273#[derive(Copy, Clone, Debug, Default)]
274pub struct Rect {
275 x: usize,
277
278 y: usize,
280
281 width: usize,
283
284 length: usize,
286}
287
288impl Rect {
289 fn fit_cut_piece(
290 &self,
291 pattern_direction: PatternDirection,
292 cut_piece: &CutPieceWithId,
293 prefer_rotated: bool,
294 ) -> Fit {
295 let upright_fit = if cut_piece.pattern_direction == pattern_direction {
296 if cut_piece.width == self.width && cut_piece.length == self.length {
297 Some(Fit::UprightExact)
298 } else if cut_piece.width <= self.width && cut_piece.length <= self.length {
299 Some(Fit::Upright)
300 } else {
301 None
302 }
303 } else {
304 None
305 };
306
307 let rotated_fit =
308 if cut_piece.can_rotate && cut_piece.pattern_direction.rotated() == pattern_direction {
309 if cut_piece.length == self.width && cut_piece.width == self.length {
310 Some(Fit::RotatedExact)
311 } else if cut_piece.length <= self.width && cut_piece.width <= self.length {
312 Some(Fit::Rotated)
313 } else {
314 None
315 }
316 } else {
317 None
318 };
319
320 match (upright_fit, rotated_fit) {
321 (Some(upright_fit), Some(rotated_fit)) => {
322 if prefer_rotated {
323 rotated_fit
324 } else {
325 upright_fit
326 }
327 }
328 (Some(upright_fit), None) => upright_fit,
329 (None, Some(rotated_fit)) => rotated_fit,
330 (None, None) => Fit::None,
331 }
332 }
333
334 fn contains(&self, rect: &Rect) -> bool {
335 rect.x >= self.x
336 && rect.x + rect.width <= self.x + self.width
337 && rect.y >= self.y
338 && rect.y + rect.length <= self.y + self.length
339 }
340}
341
342impl From<&ResultCutPiece> for Rect {
343 fn from(cut_piece: &ResultCutPiece) -> Self {
344 Self {
345 x: cut_piece.x,
346 y: cut_piece.y,
347 width: cut_piece.width,
348 length: cut_piece.length,
349 }
350 }
351}
352
353#[derive(Copy, Clone, PartialEq, Eq)]
354enum Fit {
355 None,
356 UprightExact,
357 RotatedExact,
358 Upright,
359 Rotated,
360}
361
362impl Fit {
363 fn is_none(self) -> bool {
364 self == Fit::None
365 }
366
367 fn is_upright(self) -> bool {
368 self == Fit::Upright || self == Fit::UprightExact
369 }
370
371 fn is_rotated(self) -> bool {
372 self == Fit::Rotated || self == Fit::RotatedExact
373 }
374}
375
376trait Bin {
378 type Heuristic;
380
381 fn new(
383 width: usize,
384 length: usize,
385 blade_width: usize,
386 pattern_direction: PatternDirection,
387 price: usize,
388 ) -> Self;
389
390 fn fitness(&self) -> f64;
392
393 fn price(&self) -> usize;
394
395 fn remove_cut_pieces<I>(&mut self, cut_pieces: I) -> usize
397 where
398 I: Iterator,
399 I::Item: Borrow<UsedCutPiece>;
400
401 fn cut_pieces(&self) -> std::slice::Iter<'_, UsedCutPiece>;
403
404 fn possible_heuristics() -> Vec<Self::Heuristic>;
406
407 fn insert_cut_piece_with_heuristic(
410 &mut self,
411 cut_piece: &CutPieceWithId,
412 heuristic: &Self::Heuristic,
413 ) -> bool;
414
415 fn insert_cut_piece_random_heuristic<R>(
418 &mut self,
419 cut_piece: &CutPieceWithId,
420 rng: &mut R,
421 ) -> bool
422 where
423 R: Rng + ?Sized;
424
425 fn matches_stock_piece(&self, stock_piece: &StockPiece) -> bool;
427}
428
429struct OptimizerUnit<'a, B>
430where
431 B: Bin,
432{
433 bins: Vec<B>,
434
435 possible_stock_pieces: &'a [StockPiece],
437
438 available_stock_pieces: Vec<StockPiece>,
440
441 unused_cut_pieces: FnvHashSet<CutPieceWithId>,
443
444 blade_width: usize,
445}
446
447impl<'a, B> Clone for OptimizerUnit<'a, B>
448where
449 B: Bin + Clone,
450{
451 fn clone(&self) -> Self {
452 Self {
453 bins: self.bins.clone(),
454 possible_stock_pieces: self.possible_stock_pieces,
455 available_stock_pieces: self.available_stock_pieces.clone(),
456 unused_cut_pieces: self.unused_cut_pieces.clone(),
457 blade_width: self.blade_width,
458 }
459 }
460}
461
462impl<'a, B> OptimizerUnit<'a, B>
463where
464 B: Bin,
465{
466 fn with_random_heuristics<R>(
467 possible_stock_pieces: &'a [StockPiece],
468 cut_pieces: &[&CutPieceWithId],
469 blade_width: usize,
470 rng: &mut R,
471 ) -> Result<OptimizerUnit<'a, B>>
472 where
473 R: Rng + ?Sized,
474 {
475 let mut unit = OptimizerUnit {
476 bins: Vec::new(),
477 possible_stock_pieces,
478 available_stock_pieces: possible_stock_pieces.to_vec(),
479 unused_cut_pieces: Default::default(),
480 blade_width,
481 };
482
483 for cut_piece in cut_pieces {
484 if !unit.first_fit_random_heuristics(cut_piece, rng) {
485 unit.unused_cut_pieces.insert((*cut_piece).clone());
486 }
487 }
488
489 Ok(unit)
490 }
491
492 fn with_heuristic<R>(
493 possible_stock_pieces: &'a [StockPiece],
494 cut_pieces: &[&CutPieceWithId],
495 blade_width: usize,
496 heuristic: &B::Heuristic,
497 rng: &mut R,
498 ) -> Result<OptimizerUnit<'a, B>>
499 where
500 R: Rng + ?Sized,
501 {
502 let mut unit = OptimizerUnit {
503 bins: Vec::new(),
504 possible_stock_pieces,
505 available_stock_pieces: possible_stock_pieces.to_vec(),
506 unused_cut_pieces: Default::default(),
507 blade_width,
508 };
509
510 for cut_piece in cut_pieces {
511 if !unit.first_fit_with_heuristic(cut_piece, heuristic, rng) {
512 unit.unused_cut_pieces.insert((*cut_piece).clone());
513 }
514 }
515
516 Ok(unit)
517 }
518
519 pub(crate) fn generate_initial_units(
520 possible_stock_pieces: &'a [StockPiece],
521 mut cut_pieces: Vec<&CutPieceWithId>,
522 blade_width: usize,
523 random_seed: u64,
524 ) -> Result<Vec<OptimizerUnit<'a, B>>> {
525 let mut set = FnvHashSet::default();
526 for cut_piece in &cut_pieces {
527 set.insert((
528 cut_piece.width,
529 cut_piece.length,
530 cut_piece.can_rotate,
531 cut_piece.pattern_direction,
532 ));
533 }
534 let unique_cut_pieces = set.len();
535
536 let possible_heuristics = B::possible_heuristics();
537
538 let num_units = if cut_pieces.len() < 3 {
539 possible_heuristics.len()
540 } else {
541 let denom = if cut_pieces.len() > 1 {
542 (cut_pieces.len() as f64).log10()
543 } else {
544 1.0
545 };
546
547 cmp::max(
548 possible_heuristics.len() * 3,
549 (cut_pieces.len() as f64 / denom + ((unique_cut_pieces - 1) * 10) as f64) as usize,
550 )
551 };
552 let mut units = Vec::with_capacity(num_units);
553 let mut rng: StdRng = SeedableRng::seed_from_u64(random_seed);
554
555 cut_pieces.sort_by_key(|p| cmp::Reverse((p.width, p.length)));
556 for heuristic in &possible_heuristics {
557 units.push(OptimizerUnit::with_heuristic(
558 possible_stock_pieces,
559 &cut_pieces,
560 blade_width,
561 heuristic,
562 &mut rng,
563 )?);
564 }
565
566 if cut_pieces.len() > 2 {
567 for heuristic in &possible_heuristics {
568 cut_pieces.shuffle(&mut rng);
569 units.push(OptimizerUnit::with_heuristic(
570 possible_stock_pieces,
571 &cut_pieces,
572 blade_width,
573 heuristic,
574 &mut rng,
575 )?);
576 }
577
578 for _ in 0..num_units - units.len() {
579 cut_pieces.shuffle(&mut rng);
580 units.push(OptimizerUnit::with_random_heuristics(
581 possible_stock_pieces,
582 &cut_pieces,
583 blade_width,
584 &mut rng,
585 )?);
586 }
587 }
588 Ok(units)
589 }
590
591 fn first_fit_random_heuristics<R>(&mut self, cut_piece: &CutPieceWithId, rng: &mut R) -> bool
592 where
593 R: Rng + ?Sized,
594 {
595 for bin in self.bins.iter_mut() {
596 if bin.insert_cut_piece_random_heuristic(cut_piece, rng) {
597 return true;
598 }
599 }
600
601 self.add_to_new_bin(cut_piece, rng)
602 }
603
604 fn first_fit_with_heuristic<R>(
605 &mut self,
606 cut_piece: &CutPieceWithId,
607 heuristic: &B::Heuristic,
608 rng: &mut R,
609 ) -> bool
610 where
611 R: Rng + ?Sized,
612 {
613 for bin in self.bins.iter_mut() {
614 if bin.insert_cut_piece_with_heuristic(cut_piece, heuristic) {
615 return true;
616 }
617 }
618
619 self.add_to_new_bin(cut_piece, rng)
620 }
621
622 fn add_to_new_bin<R>(&mut self, cut_piece: &CutPieceWithId, rng: &mut R) -> bool
623 where
624 R: Rng + ?Sized,
625 {
626 let stock_pieces = self
627 .available_stock_pieces
628 .iter_mut()
629 .filter(|stock_piece| {
630 stock_piece.quantity != Some(0) && stock_piece.fits_cut_piece(cut_piece)
631 });
632
633 match stock_pieces.choose(rng) {
634 Some(stock_piece) => {
635 stock_piece.dec_quantity();
636
637 let mut bin = B::new(
638 stock_piece.width,
639 stock_piece.length,
640 self.blade_width,
641 stock_piece.pattern_direction,
642 stock_piece.price,
643 );
644 if !bin.insert_cut_piece_random_heuristic(cut_piece, rng) {
645 return false;
646 }
647 self.bins.push(bin);
648 true
649 }
650 None => false,
651 }
652 }
653
654 fn crossover<R>(&self, other: &OptimizerUnit<'a, B>, rng: &mut R) -> OptimizerUnit<'a, B>
655 where
656 R: Rng + ?Sized,
657 B: Clone,
658 {
659 if self.bins.len() < 2 && other.bins.len() < 2 {
662 return self.clone();
663 }
664
665 let cross_dest = rng.gen_range(0..=self.bins.len());
667 let cross_src_start = rng.gen_range(0..other.bins.len());
668 let cross_src_end = rng.gen_range(cross_src_start + 1..=other.bins.len());
669
670 let mut new_unit = OptimizerUnit {
672 bins: (&self.bins[..cross_dest])
673 .iter()
674 .chain((&other.bins[cross_src_start..cross_src_end]).iter())
675 .chain((&self.bins[cross_dest..]).iter())
676 .cloned()
677 .collect(),
678 possible_stock_pieces: self.possible_stock_pieces,
679 available_stock_pieces: self.possible_stock_pieces.to_vec(),
681 unused_cut_pieces: Default::default(),
683 blade_width: self.blade_width,
684 };
685
686 let mut unused_cut_pieces = self.unused_cut_pieces.clone();
687
688 other.bins[cross_src_start..cross_src_end]
690 .iter()
691 .for_each(|bin| {
692 if let Some(ref mut stock_piece) = new_unit
693 .available_stock_pieces
694 .iter_mut()
695 .find(|sp| bin.matches_stock_piece(sp))
696 {
697 for cut_piece in bin.cut_pieces() {
699 unused_cut_pieces.remove(&cut_piece.into());
700 }
701 stock_piece.dec_quantity();
702 } else {
703 panic!("Attempt to inject invalid bin in crossover operation. This shouldn't happen, and means there is a bug in the code.");
704 }
705 });
706
707 for i in (0..cross_dest)
710 .chain((cross_dest + cross_src_end - cross_src_start)..new_unit.bins.len())
711 .rev()
712 {
713 let bin = &mut new_unit.bins[i];
714 let stock_piece = new_unit
715 .available_stock_pieces
716 .iter_mut()
717 .find(|sp| sp.quantity != Some(0) && bin.matches_stock_piece(sp));
718 let injected_cut_pieces = (&other.bins[cross_src_start..cross_src_end])
719 .iter()
720 .flat_map(Bin::cut_pieces);
721 let num_removed_cut_pieces = bin.remove_cut_pieces(injected_cut_pieces);
722
723 if let (0, Some(stock_piece)) = (num_removed_cut_pieces, stock_piece) {
724 stock_piece.dec_quantity();
727 } else {
728 for cut_piece in bin.cut_pieces() {
732 unused_cut_pieces.insert(cut_piece.into());
733 }
734 new_unit.bins.remove(i);
735 }
736 }
737
738 unused_cut_pieces.retain(|cut_piece| !new_unit.first_fit_random_heuristics(cut_piece, rng));
740 new_unit.unused_cut_pieces = unused_cut_pieces;
741
742 for i in (0..new_unit.bins.len()).rev() {
744 if new_unit.bins[i].cut_pieces().next().is_none() {
745 let bin = &mut new_unit.bins[i];
746 if let Some(ref mut stock_piece) = new_unit
747 .available_stock_pieces
748 .iter_mut()
749 .find(|sp| sp.quantity != Some(0) && bin.matches_stock_piece(sp))
750 {
751 stock_piece.inc_quantity();
753 }
754 new_unit.bins.remove(i);
755 }
756 }
757
758 new_unit
759 }
760
761 fn mutate<R>(&mut self, rng: &mut R)
763 where
764 R: Rng + ?Sized,
765 {
766 if !self.bins.is_empty() && rng.gen_range(0..20) == 1 {
767 self.inversion(rng)
768 }
769 }
770
771 fn inversion<R>(&mut self, rng: &mut R)
773 where
774 R: Rng + ?Sized,
775 {
776 let start = rng.gen_range(0..self.bins.len());
777 let end = rng.gen_range(start..self.bins.len());
778 self.bins[start..end].reverse();
779 }
780}
781
782impl<'a, B> Unit for OptimizerUnit<'a, B>
783where
784 B: Bin + Send + Clone,
785{
786 fn fitness(&self) -> f64 {
787 let fitness = if self.bins.is_empty() {
788 0.0
789 } else {
790 self.bins.iter().fold(0.0, |acc, b| acc + b.fitness()) / self.bins.len() as f64
791 };
792
793 if self.unused_cut_pieces.is_empty() {
794 fitness
795 } else {
796 fitness - 1.0
799 }
800 }
801
802 fn breed_with<R>(&self, other: &OptimizerUnit<'a, B>, rng: &mut R) -> OptimizerUnit<'a, B>
803 where
804 R: Rng + ?Sized,
805 {
806 let mut new_unit = self.crossover(other, rng);
807 new_unit.mutate(rng);
808 new_unit
809 }
810}
811
812#[derive(Debug)]
814pub enum Error {
815 NoFitForCutPiece(CutPiece),
817}
818fn no_fit_for_cut_piece_error(cut_piece: &CutPieceWithId) -> Error {
819 Error::NoFitForCutPiece(CutPiece {
820 quantity: 1,
821 external_id: cut_piece.external_id,
822 width: cut_piece.width,
823 length: cut_piece.length,
824 can_rotate: cut_piece.can_rotate,
825 pattern_direction: cut_piece.pattern_direction,
826 })
827}
828type Result<T> = std::result::Result<T, Error>;
829
830#[cfg_attr(feature = "serialize", derive(Deserialize, Serialize))]
832#[cfg_attr(feature = "serialize", serde(rename_all = "camelCase"))]
833pub struct Solution {
834 pub fitness: f64,
837
838 pub stock_pieces: Vec<ResultStockPiece>,
840
841 #[cfg_attr(feature = "serialize", serde(skip))]
842 price: usize,
843}
844
845pub struct Optimizer {
848 stock_pieces: Vec<StockPiece>,
849 cut_pieces: Vec<CutPieceWithId>,
850 cut_width: usize,
851 random_seed: u64,
852 allow_mixed_stock_sizes: bool,
853}
854
855impl Default for Optimizer {
856 fn default() -> Self {
857 Self {
858 stock_pieces: Default::default(),
859 cut_pieces: Default::default(),
860 cut_width: Default::default(),
861 random_seed: Default::default(),
862 allow_mixed_stock_sizes: true,
863 }
864 }
865}
866
867impl Optimizer {
868 pub fn new() -> Self {
870 Default::default()
871 }
872
873 pub fn add_stock_piece(&mut self, stock_piece: StockPiece) -> &mut Self {
878 let mut existing_stock_piece = self.stock_pieces.iter_mut().find(|sp| {
879 sp.width == stock_piece.width
880 && sp.length == stock_piece.length
881 && sp.pattern_direction == stock_piece.pattern_direction
882 && sp.price == stock_piece.price
883 });
884
885 if let Some(ref mut existing_stock_piece) = existing_stock_piece {
886 match (&mut existing_stock_piece.quantity, stock_piece.quantity) {
887 (Some(ref mut existing_quantity), Some(quantity)) => {
888 *existing_quantity += quantity;
891 }
892 _ => {
893 existing_stock_piece.quantity = None;
896 }
897 }
898 } else {
899 self.stock_pieces.push(stock_piece);
901 }
902
903 self
904 }
905
906 pub fn add_stock_pieces<I>(&mut self, stock_pieces: I) -> &mut Self
911 where
912 I: IntoIterator<Item = StockPiece>,
913 {
914 stock_pieces.into_iter().for_each(|sp| {
915 self.add_stock_piece(sp);
916 });
917 self
918 }
919
920 pub fn add_cut_piece(&mut self, cut_piece: CutPiece) -> &mut Self {
922 for _ in 0..cut_piece.quantity {
923 let cut_piece = CutPieceWithId {
924 id: self.cut_pieces.len(),
925 external_id: cut_piece.external_id,
926 width: cut_piece.width,
927 length: cut_piece.length,
928 pattern_direction: cut_piece.pattern_direction,
929 can_rotate: cut_piece.can_rotate,
930 };
931
932 self.cut_pieces.push(cut_piece);
933 }
934
935 self
936 }
937
938 pub fn add_cut_pieces<I>(&mut self, cut_pieces: I) -> &mut Self
940 where
941 I: IntoIterator<Item = CutPiece>,
942 {
943 cut_pieces.into_iter().for_each(|dp| {
944 self.add_cut_piece(dp);
945 });
946 self
947 }
948
949 pub fn set_cut_width(&mut self, cut_width: usize) -> &mut Self {
952 self.cut_width = cut_width;
953 self
954 }
955
956 pub fn set_random_seed(&mut self, seed: u64) -> &mut Self {
959 self.random_seed = seed;
960 self
961 }
962
963 pub fn allow_mixed_stock_sizes(&mut self, allow: bool) -> &mut Self {
967 self.allow_mixed_stock_sizes = allow;
968 self
969 }
970
971 pub fn optimize_guillotine<F>(&self, progress_callback: F) -> Result<Solution>
976 where
977 F: Fn(f64),
978 {
979 self.optimize::<GuillotineBin, F>(progress_callback)
980 }
981
982 pub fn optimize_nested<F>(&self, progress_callback: F) -> Result<Solution>
987 where
988 F: Fn(f64),
989 {
990 self.optimize::<MaxRectsBin, F>(progress_callback)
991 }
992
993 fn optimize<B, F>(&self, progress_callback: F) -> Result<Solution>
994 where
995 B: Bin + Clone + Send + Into<ResultStockPiece>,
996 F: Fn(f64),
997 {
998 if self.cut_pieces.is_empty() {
1000 return Ok(Solution {
1001 fitness: 1.0,
1002 stock_pieces: Vec::new(),
1003 price: 0,
1004 });
1005 }
1006
1007 let size_set: FnvHashSet<(usize, usize)> = self
1008 .stock_pieces
1009 .iter()
1010 .map(|sp| (sp.width, sp.length))
1011 .collect();
1012
1013 let num_runs = size_set.len() + if self.allow_mixed_stock_sizes { 1 } else { 0 };
1014 let callback = |progress| {
1015 progress_callback(progress / num_runs as f64);
1016 };
1017
1018 let mut best_result = if self.allow_mixed_stock_sizes {
1019 self.optimize_with_stock_pieces::<B, _>(&self.stock_pieces.clone(), &callback)
1021 } else {
1022 Err(no_fit_for_cut_piece_error(&self.cut_pieces[0]))
1027 };
1028
1029 for (i, (width, length)) in size_set.iter().enumerate() {
1032 let stock_pieces: Vec<StockPiece> = self
1033 .stock_pieces
1034 .iter()
1035 .filter(|sp| sp.width == *width && sp.length == *length)
1036 .cloned()
1037 .collect();
1038
1039 let completed_runs = i + 1;
1040 if let Ok(solution) =
1041 self.optimize_with_stock_pieces::<B, _>(&stock_pieces, &|progress| {
1042 progress_callback((completed_runs as f64 + progress) / num_runs as f64);
1043 })
1044 {
1045 match best_result {
1046 Ok(ref best_solution) => {
1047 if solution.fitness < 0.0 || best_solution.fitness < 0.0 {
1050 if solution.fitness > best_solution.fitness {
1051 best_result = Ok(solution);
1052 }
1053 } else if solution.price < best_solution.price
1054 || (solution.price == best_solution.price
1055 && solution.fitness > best_solution.fitness)
1056 {
1057 best_result = Ok(solution);
1058 }
1059 }
1060 Err(_) => best_result = Ok(solution),
1061 }
1062 }
1063 }
1064
1065 if let Ok(ref mut solution) = &mut best_result {
1066 solution
1067 .stock_pieces
1068 .sort_by_key(|p| cmp::Reverse((p.width, p.length)));
1069 };
1070
1071 best_result
1072 }
1073
1074 fn optimize_with_stock_pieces<B, F>(
1075 &self,
1076 stock_pieces: &[StockPiece],
1077 progress_callback: &F,
1078 ) -> Result<Solution>
1079 where
1080 B: Bin + Clone + Send + Into<ResultStockPiece>,
1081 F: Fn(f64),
1082 {
1083 let cut_pieces: Vec<&CutPieceWithId> = self.cut_pieces.iter().collect();
1084
1085 let units: Vec<OptimizerUnit<B>> = OptimizerUnit::generate_initial_units(
1086 stock_pieces,
1087 cut_pieces,
1088 self.cut_width,
1089 self.random_seed,
1090 )?;
1091
1092 let population_size = units.len();
1093 let mut result_units = Population::new(units)
1094 .set_size(population_size)
1095 .set_rand_seed(self.random_seed)
1096 .set_breed_factor(0.5)
1097 .set_survival_factor(0.6)
1098 .epochs(100, progress_callback)
1099 .finish();
1100
1101 let best_unit = &mut result_units[0];
1102 if !best_unit.unused_cut_pieces.is_empty() {
1103 return Err(no_fit_for_cut_piece_error(
1104 best_unit.unused_cut_pieces.iter().next().unwrap(),
1105 ));
1106 }
1107
1108 let fitness = best_unit.fitness();
1109 let price = best_unit.bins.iter().map(|bin| bin.price()).sum();
1110
1111 let used_stock_pieces: Vec<ResultStockPiece> =
1112 best_unit.bins.drain(..).map(Into::into).collect();
1113
1114 Ok(Solution {
1115 fitness,
1116 stock_pieces: used_stock_pieces,
1117 price,
1118 })
1119 }
1120}