├── .classpath
├── .gitignore
├── .project
├── .settings
├── org.eclipse.jdt.core.prefs
└── org.scala-ide.sdt.core.prefs
├── LICENSE
├── README.md
├── lib
├── jfreechart
│ ├── iText-2.1.5.jar
│ ├── jcommon-1.0.16.jar
│ └── jfreechart-1.0.13.jar
└── watchmaker
│ ├── google-collect-1.0.jar
│ ├── uncommons-maths-1.2.2.jar
│ └── watchmaker-framework-0.7.1.jar
├── project_report
├── imgs
│ ├── poisson1.pdf
│ ├── thpack8-2_pmx,listmutation,rotation,pop40.pdf
│ └── thpack8-2_pmx,listmutation,rotation,replacement,pop40.pdf
├── references.bib
├── report.pdf
└── report.tex
├── screenshots
├── animation.gif
└── plot.png
└── src
├── ea
├── containerloading
│ ├── ContainerLoader.scala
│ ├── ContainerProblem.scala
│ ├── EvolutionaryContainerLoading.scala
│ ├── Geometry.scala
│ ├── GroupingMutation.scala
│ ├── Main.scala
│ ├── PackingEvaluator.scala
│ ├── RotationMutation.scala
│ ├── StackRotationMutation.scala
│ ├── SurfaceFinder.scala
│ ├── test
│ │ ├── ContainerLoaderTest.scala
│ │ └── HelpersTest.scala
│ └── vis
│ │ └── CandidateViewer.scala
└── watchmaker
│ └── Implicits.scala
└── org
└── uncommons
└── watchmaker
└── framework
└── operators
└── ListOrderCrossover2.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /bin/
2 | /.cache-main
3 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | ea-containerloading
4 |
5 |
6 |
7 |
8 |
9 | org.scala-ide.sdt.core.scalabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.scala-ide.sdt.core.scalanature
16 | org.eclipse.jdt.core.javanature
17 |
18 |
19 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | #Thu Dec 16 19:44:40 CET 2010
2 | eclipse.preferences.version=1
3 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
4 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
5 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
6 | org.eclipse.jdt.core.compiler.compliance=1.6
7 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
8 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
9 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
10 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
11 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
12 | org.eclipse.jdt.core.compiler.source=1.6
13 |
--------------------------------------------------------------------------------
/.settings/org.scala-ide.sdt.core.prefs:
--------------------------------------------------------------------------------
1 | #Tue Dec 21 23:48:27 CET 2010
2 | Xcheck-null=false
3 | Xcheckinit=false
4 | Xdisable-assertions=false
5 | Xelide-below=2000
6 | Xexperimental=false
7 | Xfatal-warnings=false
8 | Xfuture=false
9 | Xlog-implicits=false
10 | Xmigration=false
11 | Xno-uescape=false
12 | Xplugin=
13 | Xplugin-require=
14 | Xpluginsdir=misc\\scala-devel\\plugins
15 | Xwarninit=false
16 | Yclosure-elim=false
17 | Ydead-code=false
18 | Ydetach=false
19 | Yinline=false
20 | Ylinearizer=rpo
21 | Yno-generic-signatures=false
22 | Yno-imports=false
23 | Yno-predefs=false
24 | Yrecursion=0
25 | Yself-in-annots=false
26 | Ysqueeze=on
27 | Ystruct-dispatch=poly-cache
28 | Ywarn-dead-code=false
29 | deprecation=false
30 | eclipse.preferences.version=1
31 | g=vars
32 | no-specialization=false
33 | optimise=false
34 | scala.compiler.useProjectSettings=false
35 | target=jvm-1.5
36 | unchecked=false
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011, Maik Riechert
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions
6 | are met:
7 | 1. Redistributions of source code must retain the above copyright
8 | notice, this list of conditions and the following disclaimer.
9 | 2. Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | 3. The name of the author may not be used to endorse or promote products
13 | derived from this software without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Evolutionary Container Loading
2 |
3 | Student project for solving the container loading problem using evolutionary algorithms.
4 |
5 | Built using
6 | [Scala](http://www.scala-lang.org/),
7 | [Watchmaker](http://watchmaker.uncommons.org/),
8 | [Java 3D](https://java3d.java.net/), and
9 | [JFreeChart](http://www.jfree.org/jfreechart/).
10 |
11 | For a detailed description of the methods and background have a look at the [project report](project_report/report.pdf) (German).
12 |
13 | 
14 |
15 | Visualization of the best candidate found. Note that a realistic loading order of the container is not taken into account.
16 |
17 | 
18 |
19 | A plot showing the evolution of the solution.
--------------------------------------------------------------------------------
/lib/jfreechart/iText-2.1.5.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/lib/jfreechart/iText-2.1.5.jar
--------------------------------------------------------------------------------
/lib/jfreechart/jcommon-1.0.16.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/lib/jfreechart/jcommon-1.0.16.jar
--------------------------------------------------------------------------------
/lib/jfreechart/jfreechart-1.0.13.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/lib/jfreechart/jfreechart-1.0.13.jar
--------------------------------------------------------------------------------
/lib/watchmaker/google-collect-1.0.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/lib/watchmaker/google-collect-1.0.jar
--------------------------------------------------------------------------------
/lib/watchmaker/uncommons-maths-1.2.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/lib/watchmaker/uncommons-maths-1.2.2.jar
--------------------------------------------------------------------------------
/lib/watchmaker/watchmaker-framework-0.7.1.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/lib/watchmaker/watchmaker-framework-0.7.1.jar
--------------------------------------------------------------------------------
/project_report/imgs/poisson1.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/project_report/imgs/poisson1.pdf
--------------------------------------------------------------------------------
/project_report/imgs/thpack8-2_pmx,listmutation,rotation,pop40.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/project_report/imgs/thpack8-2_pmx,listmutation,rotation,pop40.pdf
--------------------------------------------------------------------------------
/project_report/imgs/thpack8-2_pmx,listmutation,rotation,replacement,pop40.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/project_report/imgs/thpack8-2_pmx,listmutation,rotation,replacement,pop40.pdf
--------------------------------------------------------------------------------
/project_report/references.bib:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/project_report/references.bib
--------------------------------------------------------------------------------
/project_report/report.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/project_report/report.pdf
--------------------------------------------------------------------------------
/project_report/report.tex:
--------------------------------------------------------------------------------
1 | \documentclass[a4paper,abstracton,12pt]{scrartcl}
2 |
3 | \usepackage[ngerman]{babel}
4 | \usepackage[T1]{fontenc}
5 | \usepackage[ansinew]{inputenc}
6 | %\usepackage[bitstream-charter]{mathdesign}
7 | \usepackage{lmodern}
8 | \usepackage{bibgerm}
9 |
10 | \usepackage{xcolor}
11 | \usepackage{graphicx}
12 | \usepackage{listings}
13 | \usepackage{alltt}
14 | \usepackage{amsmath}
15 | \usepackage{amssymb}
16 | \usepackage{ziffer}
17 | \usepackage{tabularx}
18 |
19 | \title{Container Loading}
20 | \author{Maik Riechert (10INM)}
21 |
22 | \definecolor{lightblue}{rgb}{0.8,0.85,1}
23 | \definecolor{LinkColor}{rgb}{0,0,0}
24 | \usepackage[
25 | pdftitle={Container Loading},
26 | pdfauthor={Maik Riechert}]{hyperref}
27 | \hypersetup{colorlinks=true,
28 | linkcolor=LinkColor,
29 | citecolor=LinkColor,
30 | filecolor=LinkColor,
31 | menucolor=LinkColor,
32 | urlcolor=LinkColor,
33 | pdfstartview=,
34 | pdfpagemode=UseNone
35 | }
36 |
37 | \begin{document}
38 |
39 | \maketitle
40 |
41 | \section{Problembeschreibung}
42 |
43 | Dieser Artikel bezieht sich auf die Projektarbeit im Fach \emph{Evolution�re Algorithmen} an der HTWK Leipzig. Ein schwer zu l�sendes kombinatorisches Problem soll mithilfe von evolution�ren Algorithmen approximiert werden.
44 |
45 | Der Autor untersuchte das sogenannte Beh�lterproblem, auch bekannt als dreidimensionales \emph{Bin Packing} oder \emph{Container Loading}. Es ist NP-schwer und bei gro�en Probleminstanzen nur approximiert l�sbar.
46 |
47 | Beim allgemeinen klassischen Bin Packing werden eine Anzahl von Objekten in mehrere Beh�lter verteilt, sodass dieser nicht "`�berl�uft"' und die ben�tigte Beh�lteranzahl minimal wird. Dies kann sich auf den verf�gbaren Raum, aber auch Objektgewichte oder �hnliches beziehen. Allen Problemklassen gemeinsam ist, dass f�r jeden Beh�lter eine oder mehrere Bedingungen erf�llt werden m�ssen, z.B. das Maximalgewicht oder das verf�gbare Volumen.
48 |
49 | In dieser Arbeit wird der Fall betrachtet, dass nur ein einziger Container $C$ mit der Gr��e $(B_C,H_C,T_C)$ existiert, in den $n$ quaderf�rmige Kisten $K_i$ ($0 \le i < n$) teilweise verschiedener Gr��e $(B_i,H_i,T_i)$ und erlaubter Drehung $D_i$ m�glichst platzsparend beladen werden sollen. Die erlaubte Drehung legt fest, ob die Breite und Tiefe in die Vertikale gestellt werden kann. Da die H�he sich schon in der Vertikalen befindet, muss dies nicht festgelegt werden. Ebenso ist eine Drehung mit der H�he als Achse immer erlaubt -- jeweils um 90�. F�r jede Kiste ist also $D_i$ wie folgt festgelegt:
50 |
51 | \begin{equation}
52 | D_i = (B_i^{(D)}, T_i^{(D)})
53 | \end{equation}
54 |
55 | \begin{equation}
56 | D_i.* = \begin{cases}
57 | 1, & \text{Kante als Vertikale erlaubt}\\
58 | 0, & \text{sonst}
59 | \end{cases}
60 | \end{equation}
61 |
62 | Die Schwierigkeit ergibt sich vor allem daraus, dass meist viel mehr Kisten vorhanden sind, als theoretisch in den Container passen w�rden. Es muss also eine Kistenmenge gefunden werden, bei deren Beladung $L$ die Platznutzung $N$ im Container maximal bzw. die Platzverschwendung $V$ minimal wird. $L$ ist eine Menge von Tupeln, wobei jedes Tupel eine zu beladende Kiste mit dem Kistenindex $idx_i$, der Position $p_i$ im Container, sowie der Rotation $r_i$ beschreibt.
63 |
64 | \begin{equation}
65 | L = \{(idx_1,p_1,r_1),(idx_2,p_2,r_2),\ldots\} \ \text{mit} \ p_i = (x_i, y_i, z_i), r_i = (b_i, t_i, v_i)
66 | \end{equation}
67 |
68 | \begin{equation}
69 | N = \sum_{i\in L.idx} B_i \cdot H_i \cdot T_i
70 | \end{equation}
71 |
72 | \begin{equation}
73 | V = B_C \cdot H_C \cdot T_C - N
74 | \end{equation}
75 |
76 | $i\in L.idx$ beschreibt hier die Menge aller $idx$-Elemente aus jedem Tupel in $L$, d.h. $i\in L.idx = \{idx_1, idx_2, \ldots\}$.
77 |
78 | \section{Modellierung}
79 |
80 | Das vorliegende Problem soll durch einen genetischen Algorithmus m�glichst optimal approximiert oder, falls alle gegebenen Kisten theoretisch in den Container passen, im Idealfall gel�st werden. Da ein solcher Algorithmus bei praktischer Anwendung sehr oft ausgef�hrt und zeitnah gute Ergebnisse liefern muss, steht vorallem die ben�tigte Zeit f�r die Erreichung solcher Ergebnisse im Vordergrund.
81 |
82 | Im Folgenden wird das Format des Genotyps, eine Methode f�r dessen Dekodierung, sowie die Fitnessfunktion beschrieben.
83 |
84 | \subsection{Datenrepr�sentation}
85 |
86 | Die triviale unver�nderte Repr�sentation des Ph�notyps $L$ als Genotyp, d.h. als Menge von Tupeln mit Positions- und Rotationsangaben der Kisten, ist als Ergebnis f�r den Benutzer zwar w�nschenswert, aber v�llig ungeeignet f�r die Verarbeitung im evolution�ren Algorithmus. Jede Positions- oder Rotations�nderung einer Kiste w�rde in den meisten F�llen zu einem ung�ltigen Individuum f�hren, da sich Kisten �berschneiden w�rden.
87 |
88 | Der g�ngige und auch hier gew�hlte Ansatz ist es, die Beladungsreihenfolge als Genotyp zu benutzen. Die Dekodierungsfunktion stellt damit eine Heuristik dar, welche deterministisch eine Beladung mit gegebener Reihenfolge simuliert und damit die Positionsangaben erzeugt, was wiederum den Ph�notyp ergibt.
89 |
90 | Die Rotationseigenschaft kann nun entweder Teil des Genotyps sein, oder genau wie die Position in der Dekodierungsfunktion gefunden bzw. festgelegt werden. Hier ist die Rotation Teil des Genotyps $I.G$, welcher eine Permutation darstellt, in dessen Tupeln die Rotationskomponente ge�ndert werden kann.
91 |
92 | \begin{equation}
93 | I.G = ((idx_1, r_1),(idx_2, r_2),\ldots,(idx_n, r_n))
94 | \end{equation}
95 |
96 | Es ist zu beachten, dass der Genotyp \emph{jede} Kiste enth�lt. Die Dekodierungsfunktion muss also entscheiden, welche der Kisten nicht beladen werden, falls kein Platz mehr im Container vorhanden ist. Dieses Vorgehen ist n�tig, da die Information, welche Kisten weggelassen werden m�ssen, hier noch nicht existiert und weil sich eine �ndernde Permutationsgr��e schlecht im (evolution�ren) Algorithmus nutzen lassen w�rde.
97 |
98 | \emph{Anmerkung:} In der Implementierung wird aus Effizienzgr�nden statt dem Index ein Tupel bestehend aus Index, Kistengr��e und erlaubten Drehungen verwendet, um das st�ndige Abfragen dieser Daten aus einer zentralen Datenstruktur zu vermeiden.
99 |
100 | \subsection{Dekodierung}
101 |
102 | In der Literatur werden viele Beladungsheuristiken beschrieben, welche aber vor allem bei rein deterministischen Algorithmen eingesetzt werden \cite{bortfeldt2008}\cite{wenqi2009}\cite{eley2002}\cite{bortfeldt1997}.
103 | Die Beladungsreihenfolge wird dabei meist erst in der Heuristik gew�hlt und ist nicht vorher festgelegt. Aus diesem Grund und weil der Autor ein eigenes Verfahren entwickeln wollte, werden die Heuristiken in der Literatur nicht weiter betrachtet. Da bei evolution�ren Algorithmen eine hohe Generationszahl h�ufig bessere Ergebnisse liefert, stand hier eine eher simple Heuristik mit schneller Ausf�hrungszeit im Vordergrund.
104 |
105 | Die hier benutzte Heuristik stellt sicher, dass es keine Freir�ume unter den Kisten gibt und Kisten immer von oben beladen werden. Der Ablauf ist wie folgt:
106 |
107 | \begin{alltt}
108 | dec(G: Genotyp): Ph�notyp
109 | L \(\leftarrow\emptyset\)
110 | for i \(\leftarrow\) 0 to |G|-1
111 | P \(\leftarrow\) sucheFreienPlatz(G(i))
112 | if P \(\in\emptyset\)
113 | return L
114 | L \(\leftarrow\) L \(\cup\) (G(i) \(\cup\) P)
115 | return L
116 | \end{alltt}
117 |
118 | Sobald die erste Kiste nicht mehr in den Container passt, wird abgebrochen. Das Ergebnis der Dekodierung ist die konkrete Beladung $L$.
119 |
120 | \begin{equation}
121 | L = dec(I.G) = \{(idx_1,\mathbf{p_1},r_1),\ldots,(idx_k,\mathbf{p_k},r_k)\} \ \text{mit}\ k \le n
122 | \end{equation}
123 |
124 | Die Suche eines freien Platzes k�nnte naiv so umgesetzt werden, dass f�r jede Position $(x,y,z)$ im Container gepr�ft wird, ob die Kiste komplett auf ein oder mehreren anderen Kisten stehen w�rde und ob die zu beladende Kiste eine andere geometrisch schneiden w�rde. Der Rechenaufwand steigt hier exponentiell mit der Anzahl der schon beladenen Kisten an. Eine andere L�sung musste also gefunden werden.
125 |
126 | Die hier benutzte Platzsuche basiert auf dem Paradigma der dynamischen Programmierung und benutzt eine Datenstruktur, welche die Bedingung, dass jede Kiste vollst�ndig auf anderen Kisten oder dem Boden stehen muss, direkt garantiert.
127 |
128 | Der Beladungszustand des Containers wird auf eine Matrix $M$ abgebildet, wobei die Indizes den $(x,z)$-Koordinaten und die Werte der aktuellen Beladungsh�he entsprechen. Die Suche nach einem geeigneten Platz beschr�nkt sich nun auf die Suche nach einem Rechteck mit dem Kistenboden als Gr��e in der Matrix, wobei alle Werte innerhalb des Rechtecks den selben Wert und damit die selbe Beladungsh�he haben m�ssen. Rechtecke, dessen Werte gr��er als $H_C - H_i$ sind, k�nnen sofort ignoriert werden, da die Kiste �ber die Containerdecke ragen w�rde.
129 |
130 | \begin{equation}
131 | M = \begin{pmatrix}
132 | 0 & 0 & 0 & 0 & 0 & 0 & 0\\
133 | 4 & 4 & 2 & 2 & 2 & 0 & 0\\
134 | 4 & 4 & 2 & 2 & 2 & 0 & 0\\
135 | 0 & 0 & 2 & 2 & 2 & 1 & 1\\
136 | 0 & 0 & 0 & 0 & 0 & 1 & 1
137 | \end{pmatrix}
138 | \end{equation}
139 |
140 | \begin{equation}
141 | R = \left\{\begin{pmatrix}
142 | 0 & 0\\
143 | 0 & 0\\
144 | 0 & 0
145 | \end{pmatrix}, \begin{pmatrix}
146 | 2 & 2\\
147 | 2 & 2\\
148 | 2 & 2
149 | \end{pmatrix}, \begin{pmatrix}
150 | 2 & 2\\
151 | 2 & 2\\
152 | 2 & 2
153 | \end{pmatrix}\right\}
154 | \end{equation}
155 |
156 | In diesem Beispiel ist ein Rechteck der Gr��e $2 \times 3$ gesucht. Alle Elemente in $R$ sind g�ltig, wobei der Algorithmus beim ersten Treffer abbricht und keine weiteren Eigenschaften, wie z.B. maximale Ber�hrungsfl�che, untersucht. Der Suchalgorithmus beginnt stets bei $(0,0)$, pr�ft eine Zeile, und f�hrt die Suche in der folgenden Zeile fort. Dadurch ist auch garantiert, dass Kisten nicht wahllos, sondern immer an andere Kisten bzw. den Containerw�nden platziert werden. Sobald eine Kiste platziert wird, werden die Werte des Rechtecks in der Matrix um die Kistenh�he erh�ht. Die Laufzeit f�r das Finden eines Rechtecks betr�gt hier $O(B_C \cdot B_T)$.
157 |
158 | \emph{Anmerkung:} Es ist zu beachten, dass dieser Algorithmus einen hohen Speicherverbrauch hat. Bei jedem Dekodiervorgang muss ein zweidimensionales Array der Gr��e $B_C \times B_T$ angelegt werden, was bei Testinstanzen mit einer Containergr��e von $10000 \times 10000$ zu einem Speicherverbrauch von ca. 500MB f�hrt (JVM). Allerdings haben die in der Literatur am meisten benutzten Instanzen eine Gr��e von $233 \times 587$ und $[2000,2800] \times [3000,6000]$, weswegen dies hier kein besonderes Problem darstellte.
159 |
160 | \subsection{Fitnessfunktion}
161 |
162 | Die Fitness eines Ph�notyps ist das Verh�ltnis der Summe der Volumina aller aufgeladenen Kisten zum Containervolumen. Es stellt damit die prozentuale Raumnutzung dar und eignet sich zum Vergleich mit Ergebnissen aus der Literatur.
163 |
164 | \begin{equation}
165 | F(L) = \frac{\sum_{i\in L.idx} B_i \cdot H_i \cdot T_i}{B_C \cdot H_C \cdot T_C}
166 | \end{equation}
167 |
168 | \section{Selektion}
169 |
170 | Das Beladungsproblem hat die Eigenschaft sehr viele lokale Optima zu besitzen. Um diesem Problem zu begegnen, war schnell klar, dass die Selektion einen hohen Fokus auf Erforschung haben und erst zum Ende eine Feinabstimmung bewirken sollte.
171 |
172 | Basierend auf einer Evaluation mehrerer Methoden der Fitnessskalierung von Hopgood und Mierzejewska \cite{transformranking}, wurde deutlich, dass eine reine fitnessproportionale oder rangbasierte Selektion, oder auch das stochastische universelle Sampling, in den wenigsten F�llen bei G�telandschaften mit vielen lokalen Optima zu guten Ergebnissen f�hren. Die Skalierung von Fitnesswerten wird vorallem angewandt, um den Selektionsdruck zu Beginn niedrig zu halten (Erforschung), und ihn mit fortschreitender Generationszahl stark ansteigen zu lassen (Feinabstimmung).
173 |
174 | In diesem Projekt wurde die Sigmaskalierung in Verbindung mit stochastischem universellen Sampling angewandt, da bei dieser Methode der zus�tzliche Rechenaufwand im Rahmen bleibt und gleichzeitig sehr gute Ergebnisse bei der 2D-Schwefel sowie der 2D-Griewank Funktion von Hopgood und Mierzejewska erzielt wurden. Ein weiterer Vorteil ist, dass die Methode nicht von einem Parameter abh�ngt, der erst h�tte gefunden werden m�ssen.
175 |
176 | Bei der Sigmaskalierung wird die Durchschnittsfitness $\overline{F}$ und dessen Standardabweichung $\sigma$ in der Population ber�cksichtigt. Solange die Standardabweichung relativ gro� ist, stellt die Skalierung sicher, dass diese solange wie m�glich erhalten bleibt, um einen m�glichst gro�en Suchbereich zu erforschen. Durch den geringen Selektionsdruck passierte es h�ufig, dass sehr gute Individuen verloren gingen und nicht wieder gefunden wurden. Deshalb wird hier zus�tzlich Elitismus mit einem Individuum benutzt, sodass das beste Individuum auf jeden Fall in die n�chste Generation gelangt. Wenn die Population dann langsam konvergiert, wird die Standardabweichung geringer, was wiederum zur Folge hat, dass Individuen mit einer G�te �ber dem Durchschnitt stark bevorzugt werden und damit der Selektionsdruck ansteigt.
177 |
178 | \begin{equation}
179 | F_S = \begin{cases}
180 | 1 & ,\sigma = 0\\
181 | 1 + \frac{F - \overline{F}}{2 \cdot \sigma} & ,\sigma > 0
182 | \end{cases}
183 | \end{equation}
184 |
185 | \emph{Anmerkung:} Bei sehr schlechten Individuen kann es passieren, dass $F_S$ einen negativen Wert annimmt. In diesem Fall wird die skalierte Fitness durch eine sehr kleine positive Fitness ersetzt, hier $0,1$.
186 |
187 | \section{Operatoren}
188 |
189 | In diesem Abschnitt werden die benutzten bzw. entwickelten Operatoren in der Reihenfolge ihrer Integration in das Projekt beschrieben.
190 |
191 | \subsection{Rekombination}
192 |
193 | Die Wahl des Rekombinationsoperators beschr�nkt sich auf solche, die garantieren, dass keine Elemente nach der Rekombination doppelt vorkommen. Hierbei ist zu beachten, dass ein Element von einem anderen nur anhand seiner eindeutigen ID (Kistenindex $idx_i$) unterschieden wird -- die Rotationskomponente wird ignoriert.
194 |
195 | Der Zweck der Rekombination ist vor allem die Verbindung guter Eigenschaften zweier Individuen. Der Autor konnte kein Verfahren finden, welches dieses Kriterium f�r das Beladungsproblem erf�llt. Der Hauptgrund ist, dass die Dekodierung sehr empfindlich auf �nderungen der Permutation reagiert. Umso weiter am Anfang der Permutation ein Element ver�ndert wird, umso gr��er ist die Wahrscheinlichkeit, dass eine ab diesem Element komplett verschiedene Beladung resultiert. Da bei der Rekombination gr��ere Teile ge�ndert werden, f�hrt dies h�ufig zu schlechteren Individuuen, die erst durch Mutation optimiert werden m�ssen. Folglich �hnelt die Wirkung einer Rekombination auf dieses Problem einer mischenden Mutation.
196 |
197 | Dennoch wurde eine angepasste Version des PMX-Operators \cite{pmx} (partially mapped crossover) benutzt, da positive Effekte nicht endg�ltig ausgeschlossen werden konnten. Selbst, wenn die Rekombination nur wie eine mischende Mutation wirkt, h�tte dies keine Nachteile, da dadurch zumindest eine �ber die Zeit konstante Erforschung des Suchraums gew�hrleistet ist. Der PM-Crossover wurde dahingehend angepasst, dass das linke Ende des jeweils zu �bernehmenden Teilst�cks stets beim ersten Element beginnt. Dadurch wird eine �berm��ige Zerst�rung des Genotyps verhindert. Die gew�hlte Rekombinationswahrscheinlichkeit betr�gt $p_x = 0,5$. Werte �ber $0,5$ hatten einen zu gro�en Einfluss auf die Durchschnittsfitness und sorgten daf�r, dass diese sich nicht wesentlich �ber viele Generationen verbesserte -- zu viele Individuen wurden zerst�rt.
198 |
199 | \emph{Anmerkung:} Es wurde keine Abbildungsrekombination benutzt, da diese in dem verwendeten Framework nicht mitgeliefert wird und kein weiterer Vorteil f�r eine spezielle Art der Rekombination erkannt wurde.
200 |
201 | \subsection{Mutation}
202 |
203 | Da hier die Rekombination nur bedingt zu besseren Individuen f�hrt, lag der Fokus auf der Entwicklung geeigneter Mutationsoperatoren. Im Folgenden wird jeder benutzte bzw. entworfene Operator beschrieben und dessen Sinnhaftigkeit untersucht.
204 |
205 | \subsubsection{Vertauschung}
206 |
207 | Der wohl einfachste und naheliegendste Mutationsoperator f�r Permutationen ist die Vertauschung. Es werden zuf�llig zwei Elemente im Genotyp gew�hlt und miteinander vertauscht.
208 |
209 | Die Umsetzung dieses Operators l�sst gewisse Freir�ume offen. Zum einen ist festzulegen, wie viele Vertauschungen in einem Aufruf durchgef�hrt werden. Zum anderen muss entschieden werden, ob die Positionen zuf�llig gew�hlt werden oder abh�ngig voneinander sein sollen. Dies ergibt die zwei Parameter Vertauschungsanzahl $N$ und Schrittweite $S$. Soll genau ein Element mit irgendeinem anderen getauscht werden, w�re $N=1$ und $S \sim U(1,|I.G|)$, wobei $U$ f�r eine Uniformverteilung steht. Das erste Element wird dabei immer uniform gew�hlt und ist nicht von den Parametern abh�ngig.
210 |
211 | Da schon kleine Ver�nderungen die Fitness stark beeinflussen k�nnen, wurde hier $N = S \sim P_1$ benutzt, wobei $P_\lambda$ der Poisson-Verteilung wie in Abbildung \ref{fig:poisson1} entspricht. Damit ist gew�hrleistet, dass die Anzahl der Vertauschungen und die Schrittweite stets gering bleiben, aber dennoch mit niedriger Wahrscheinlichkeit auch h�her sein k�nnen. Durch Benutzung dieser Verteilung ist ebenfalls eine Mutationswahrscheinlichkeit als dritter Parameter nicht zwingend n�tig, da $P_1(X=0) \approx 0,4$ ist und somit h�ufig keine Vertauschung auftritt -- im Fall $N=0$ oder $S=0$.
212 |
213 | \begin{figure}
214 | \centering
215 | \includegraphics[width=0.4\textwidth]{imgs/poisson1.pdf}
216 | \caption{Poisson-Verteilung mit $\lambda = 1$}
217 | \label{fig:poisson1}
218 | \end{figure}
219 |
220 | \subsubsection{Einzel-Rotation}
221 |
222 | Da die Rotationsinformation Teil des Genotyps ist, muss diese ebenfalls mutiert werden. Daf�r wurde die Einzel-Rotation entwickelt, welche abh�ngig von einer Mutationswahrscheinlichkeit zuf�llig ein Element der Permutation w�hlt und dann dessen Rotationskomponente zuf�llig �ndert. Die Wahl der neuen Rotation erfolgt mit einer Uniformverteilung, wobei es erlaubt ist, dass die alte Rotation wieder gew�hlt wird. F�r die Mutationswahrscheinlichkeit wurde mit $p_r = 0,3$ ein Wert gefunden, bei dem eine fr�he Konvergenz verhindert und die Erforschung des Suchraums gef�rdert wird.
223 |
224 | \emph{Anmerkung:} Dieser Operator wurde anfangs noch ohne Mutationswahrscheinlichkeit umgesetzt. Um dennoch eine solche zu simulieren, wurde der \emph{Split-Evolution} Operator des benutzten Frameworks verwendet. Dieser erlaubt es, zwei Tupel von Operatoren jeweils auf einen Teil der Population getrennt anzuwenden. Auf $90\%$ der Population wurde der PM-Crossover und die Vertauschungsmutation angewandt. Die restlichen $10\%$ wurden der Einzel-Rotation unterzogen.
225 |
226 | \subsubsection{Ersetzung}
227 |
228 | Zum Zeitpunkt der Implementierung aller vorheriger Operatoren ben�tigte ein Durchlauf des genetischen Algorithmus mit 60 Generationen und einer Populationsgr��e von 40 knapp zwei Stunden (Intel Core 2 Duo mit $2,8$ GHz). Unter diesen Voraussetzungen entstand h�ufig das in Abbildung \ref{fig:thpack8-2_pmx,listmutation,rotation,pop40} zu sehende Szenario. Es ist zu erkennen, dass die Population schnell konvergiert und offensichtlich keine hohe Diversit�t erhalten werden konnte.
229 |
230 | \begin{figure}
231 | \centering
232 | \includegraphics[width=0.80\textwidth]{imgs/thpack8-2_pmx,listmutation,rotation,pop40.pdf}
233 | \caption{Fitnessverlauf mit 40 Individuen und den Operatoren PMX, Vertauschung und Einzel-Rotation; Testinstanz THPACK8-2 aus \cite{packlib2}}
234 | \label{fig:thpack8-2_pmx,listmutation,rotation,pop40}
235 | \end{figure}
236 |
237 | Aufgrund sp�terer Erfahrungen hat sich gezeigt, dass die Erh�hung der Populationsgr��e der einzig gute Kompromiss ist. Da die Geschwindigkeit allerdings zu diesem Zeitpunkt noch relativ niedrig war, hat der Autor nach anderen L�sungen gesucht. Eine davon ist die Implementierung des Ersetzungsoperators. Abh�ngig von einer Mutationswahrscheinlichkeit ersetzt dieser Operator ein Individuum komplett durch ein zuf�llig neu generiertes, wobei es keine Abh�ngigkeiten zwischen beiden Genotypen gibt. Das Ziel dabei ist, dass immer wieder neues Genmaterial einflie�en soll, welches letztlich die Diversit�t beg�nstigen soll. Die Mutationswahrscheinlichkeit wurde hier mit $p_e = 0,1$ festgelegt, da bei h�heren Werten der Suchfortschritt immer weiter sinken w�rde. In Abbildung \ref{fig:thpack8-2_pmx,listmutation,rotation,replacement,pop40} ist zu sehen, dass der Ersetzungsoperator die Standardabweichung relativ konstant h�lt und somit f�r eine gute Diversit�t sorgt.
238 |
239 | \begin{figure}
240 | \centering
241 | \includegraphics[width=0.80\textwidth]{imgs/thpack8-2_pmx,listmutation,rotation,replacement,pop40.pdf}
242 | \caption{Fitnessverlauf mit 40 Individuen und den Operatoren PMX, Vertauschung, Einzel-Rotation und Ersetzung; Testinstanz THPACK8-2}
243 | \label{fig:thpack8-2_pmx,listmutation,rotation,replacement,pop40}
244 | \end{figure}
245 |
246 | \subsubsection{Typ-Rotation}
247 |
248 | Durch diverse Optimierungen am Algorithmus, der Ausf�hrungsumgebung sowie der Kompilierung konnte die Geschwindigkeit mehr als verdoppelt werden, sodass die Populationsgr��e von 40 auf 100 bei gleichbleibender Gesamtzeit erh�ht werden konnte. Auch wenn dies zur weiteren Steigerung der Diversit�t und teilweise besseren Ergebnissen f�hrte, waren die erreichten Werte noch recht bescheiden im Vergleich zu denen in der Literatur (z.B. $F=0,91$ f�r Probleminstanz THPACK8-2 durch Eley \cite{eley2002}). Das Ziel war es nun, m�glichst geschickt weitere Mutationsoperatoren zu entwerfen, welche speziell auf das Beladungsproblem bezogen sein und besondere Eigenschaften von typischen Probleminstanzen ber�cksichtigen sollten.
249 |
250 | Beim Betrachten des f�r Instanz THPACK1-10 visuell dargestellten L�sungskandidaten in \cite{eley2002} wurde offensichtlich, dass eine gute L�sung vor allem dann entsteht, wenn Kisten des selben Typs aneinander oder aufeinander gestapelt werden. Dieser Umstand war direkt von der Heterogenit�t des gegebenen Kistenbestands abh�ngig. Eine schwache Heterogenit�t, d.h. wenige verschiedene Kistengr��en, konnte vorteilhaft benutzt werden, um in einem gewissen Ma�e eine Stapelung zu forcieren bzw. zu f�rdern und damit eine effiziente Beladung zu erreichen.
251 |
252 | Da Kisten gleicher Gr��e in den meisten F�llen nur aufeinander gestapelt werden k�nnen, wenn sie die selbe Rotation haben, wurde der Operator Typ-Rotation entworfen. Dieser w�hlt abh�ngig von einer Mutationswahrscheinlichkeit zuf�llig eine Kiste im Genotyp aus und �ndert genau wie die Einzel-Rotation die Rotationskomponente. Zus�tzlich wird allen weiteren Kisten im Genotyp mit derselben Gr��e die selbe Rotationskomponente zugewiesen. Die Hoffnung ist, dass zuf�llig in der Permutation nebeneinander liegende Kisten selber Gr��e (aber unterschiedlicher Rotation) nun gestapelt werden k�nnen, da die Rotationskomponenten gleichgesetzt wurden. Einige Tests mit dem Operator lieferten keine offensichtlichen Verbesserungen, weshalb ein Hypothesentest durchgef�hrt wurde.
253 |
254 | \begin{table}
255 | \caption{Fitnesswerte, rotierte sowie ausgelassene Kisten f�r THPACK1-10 nach 5 Minuten mit und ohne Typ-Rotation}
256 | \label{tbl:stackrotation}
257 | \centering
258 | \begin{tabular}{l|l|c|c}
259 | & Fitness & rotierte Kisten & ausgelassene Kisten \\
260 | \hline
261 | ohne Typ-Rotation & 0,8386 & 3 & 23 \\
262 | & 0,8358 & 4 & 21 \\
263 | & 0,8440 & 3 & 27 \\
264 | & 0,8437 & 3 & 24 \\
265 | & 0,8165 & 5 & 28 \\
266 | & 0,8250 & 2 & 21 \\
267 | & 0,8386 & 3 & 23 \\
268 | \hline
269 | mit Typ-Rotation & 0,8494 & 28 & 15 \\
270 | & 0,8611 & 82 & 20 \\
271 | & 0,8386 & 38 & 23 \\
272 | & 0,8511 & 23 & 25 \\
273 | & 0,8511 & 46 & 21 \\
274 | & 0,8541 & 4 & 22 \\
275 | & 0,8226 & 55 & 26 \\
276 | \end{tabular}
277 | \end{table}
278 |
279 | Getestet wurde mit THPACK1-10 und den bisher genannten Parameterwerten. Nach jeweils 5 Minuten wurde die Evolution angehalten. Aus Tabelle \ref{tbl:stackrotation} ergibt sich f�r den t-Test $t=1,9992$, was als nicht wirklich statistisch signifikant interpretiert werden kann. Allerdings f�llt in den Testdaten der letzte Fitnesswert auf, welcher als Ausrutscher gesehen werden k�nnte. Wird dieser Wert ignoriert, ist $t=3,253$, was statistisch als sehr signifikant betrachtet werden kann.
280 |
281 | Es folgt, dass der Operator eine leichte Tendenz zu besseren L�sungskandidaten aufweist. Dennoch sind die Unterschiede zu gering. Eine Ursache daf�r k�nnte sein, dass die Wahrscheinlichkeit, dass gleichgro�e Kisten in der Permutation direkt nebeneinander liegen, zu niedrig ist.
282 |
283 | \subsubsection{Clusterbildung}
284 |
285 | Um das Potential der Typ-Rotation auszunutzen, war es also wichtig, gleichgro�e Kisten m�glichst nah oder direkt nebeneinander in der Permutation zu platzieren. Zu diesem Zweck wurde der Clusterbildungsoperator entworfen. Der Ablauf des Operators ist im Folgenden zu sehen:
286 |
287 | \begin{alltt}
288 | cluster(G: Genotyp): Genotyp
289 | R \(\leftarrow\) clone(G)
290 | refIdx \(\leftarrow\) U(0,|G|-1)
291 | refBox \(\leftarrow\) G(refIdx)
292 | freeIdx \(\leftarrow\) -1
293 | for i \(\leftarrow\) idx + 1 to |G|-1
294 | box \(\leftarrow\) R(i)
295 | if box.size = refBox.size && box.rotation = refBox.rotation
296 | if freeIdx != -1
297 | swap(R, freeIdx, i)
298 | freeIdx \(\leftarrow\) freeIdx + 1
299 | else if freeIdx = -1
300 | freeIdx \(\leftarrow\) i
301 | return R
302 | \end{alltt}
303 |
304 | Der Operator w�hlt also zun�chst zuf�llig eine Referenzkiste. Danach werden alle darauf folgenden Kisten auf Typgleichheit untersucht. Typgleichheit ist hier gegeben, wenn die Gr��e und Rotation �bereinstimmen. Sobald eine Kiste mit verschiedenem Typ gefunden wurde, gilt dies als ein freier Platz. Wird nun wieder eine typgleiche Kiste gefunden, wird diese mit der auf dem freien Platz getauscht. Das Ergebnis ist, dass alle Kisten selber Gr��e und Rotation, die hinter der Referenzkiste liegen, direkt hinter diese platziert werden und damit eine Kette entsteht. Die Hoffnung ist, dass typgleiche Ketten zu einem besseren L�sungskandidaten f�hren.
305 |
306 | Als Mutationswahrscheinlichkeit wurde $p_c = 0,3$ gew�hlt. Die Durchschnittsfitness hat sich bei THPACK1-10 tats�chlich erheblich verbessert und betrug bei 5 Durchl�ufen $0,8751$ -- im Vergleich zu $0,8469$, als nur Typ-Rotation benutzt wurde. Ein Hypothesentest ist hier nicht n�tig. Allerdings w�re es interessant, zu pr�fen, ob der Clusterbildungsoperator auch ohne die Typ-Rotation gute Ergebnisse liefert, oder ob beide Operatoren nur im Zusammenspiel gut funktionieren. Tabelle \ref{tbl:stackrotationvsgrouping} zeigt die experimentellen Ergebnisse, woraus sich f�r den t-Test $t=3,7225$ ergibt, was als statistisch sehr signifikant gesehen werden kann. Die Werte im zweiten Teil lassen auch erkennen, dass eine Evolution mit Clusterbildung ohne Typ-Rotation sehr h�ufig in bestimmten lokalen Optima landet.
307 |
308 | \begin{table}
309 | \caption{Fitnesswerte, rotierte sowie ausgelassene Kisten f�r THPACK1-10 nach 5 Minuten mit und ohne Typ-Rotation und mit Clusterbildung}
310 | \label{tbl:stackrotationvsgrouping}
311 | \centering
312 | \begin{tabular}{l|l|c|c}
313 | & Fitness & rotierte Kisten & ausgelassene Kisten \\
314 | \hline
315 | Typ-Rotation und & 0,8756 & 58 & 26 \\
316 | Clusterbildung & 0,8656 & 49 & 36 \\
317 | & 0,8738 & 32 & 29 \\
318 | & 0,8716 & 11 & 34 \\
319 | & 0,8890 & 40 & 25 \\
320 | \hline
321 | nur Clusterbildung & 0,8593 & 2 & 35 \\
322 | & 0,8615 & 3 & 35 \\
323 | & 0,8593 & 1 & 35 \\
324 | & 0,8615 & 3 & 35 \\
325 | & 0,8615 & 3 & 35 \\
326 | \end{tabular}
327 | \end{table}
328 |
329 | \section{Ergebnisse}
330 |
331 | Das Projekt wird abgeschlossen, indem f�r eine Reihe von leichten sowie schwierigen Probleminstanzen ein Vergleich mit Ergebnissen aus der Literatur hergestellt wird. Dem Autor ist bewusst, dass es sich nicht nur um evolution�re Algorithmen handelt, sondern auch um komplexe deterministische Heuristiken. Da ein umfassender Algorithmenvergleich den Rahmen dieses Projekts �bersteigen w�rde, werden hier lediglich die Fitnesswerte zum Vergleich aufgelistet. Die folgenden Probleminstanzen wurden dabei benutzt.
332 |
333 | \begin{center}
334 | \begin{tabular}{l|l|c|c|l|c}
335 | Instanz \cite{packlib2} & Container & Typen & Kisten & Schwierigkeit & eigenes Limit \\
336 | \hline
337 | THPACK1-10 & $233 \times 220 \times 587$ & 3 & 130 & schwer & 5 Minuten\\
338 | THPACK8-1 & $2000 \times 1000 \times 3000$ & 7 & 100 & einfach & 5 Minuten \\
339 | THPACK8-2 & $2000 \times 1000 \times 3000$ & 7 & 175 & schwer & 30 Minuten\\
340 | THPACK8-4 & $2000 \times 1100 \times 3000$ & 7 & 100 & einfach & 5 Minuten\\
341 | THPACK8-5 & $2000 \times 900 \times 3000$ & 7 & 120 & einfach/mittel & 10 Minuten\\
342 | THPACK8-7 & $2400 \times 1300 \times 3500$ & 8 & 200 & schwer & 30 Minuten\\
343 | THPACK8-12 & $2400 \times 1000 \times 3200$ & 7 & 120 & mittel & 30 Minuten\\
344 | \end{tabular}
345 | \end{center}
346 |
347 | Das beste Ergebnis (hier in \%) nach f�nfmaliger Ausf�hrung ist rechts aufgelistet. Zahlen in Klammern bedeuten ausgelassene Kisten.
348 |
349 | \begin{center}
350 | \begin{tabularx}{\textwidth}{c|X|X|X|X|l}
351 | Instanz & Ngoi et al. (1994) & Bischoff et al. (1995) & Bischoff und Rat. (1995) & Eley (2002) & selbst \\
352 | \hline
353 | 1-10 & - & - & - & 88.7 (o.A.) & 88.9 (25) \\
354 | 8-1 & 62.5 & 62.5 & 62.5 & 62.5 & 62.5 \\
355 | 8-2 & 80.7 (54) & 89.7 (23) & 90.0 (35) & 90.8 (53) & 83.93 (24) \\
356 | 8-4 & 55.0 & 55.0 & 55.0 & 55.0 & 55.0 \\
357 | 8-5 & 77.2 & 77.2 & 77.2 & 77.2 & 77.2 \\
358 | 8-7 & 81.8 (10) & 83.9 (1) & 78.7 (18) & 84.7 & 76.7 (20) \\
359 | 8-12 & 78.5 & 76.5 (3) & 78.5 & 78.5 & 77.4 (2) \\
360 | \end{tabularx}
361 | \end{center}
362 |
363 | \bibliographystyle{gerplain}
364 | \bibliography{references}
365 |
366 | \end{document}
367 |
--------------------------------------------------------------------------------
/screenshots/animation.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/screenshots/animation.gif
--------------------------------------------------------------------------------
/screenshots/plot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/letmaik/evolutionary-container-loading/a6e0e6db7f721f33f9ffd6cae4a0ac26af189817/screenshots/plot.png
--------------------------------------------------------------------------------
/src/ea/containerloading/ContainerLoader.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import javax.media.j3d._
4 | import javax.vecmath._
5 | import util.control.Breaks._
6 | import scala.math._
7 |
8 | case class LoadedContainer(container: Container, loadedBoxes: Seq[LoadedBox], skippedBoxes: Seq[Box])
9 | case class LoadedBox(box: Box, rotation: BoxRotation, position: Position3D)
10 |
11 | object ContainerLoader {
12 |
13 | /**
14 | * Beladung vom Container mit Layer-Ansatz (relativ langsam und speicherintensiv)
15 | *
16 | * Eine Höhenkarte (Layer) wird als 2-Dimensionales Array gepflegt, sodass immer
17 | * Positionen für Kisten gesucht werden, die von oben "drauffallen" können.
18 | * Eine gültige Position für eine Kiste ist eine Fläche in der Karte mit der selben Höhe
19 | * und mit Größe der Box.
20 | *
21 | * Vorteile:
22 | * - Kisten haben keine Hohlräume unter sich
23 | * - Kisten werden immer von oben beladen
24 | *
25 | * Nachteile:
26 | * - keine Kriterien für Bevorzugung "guter" Plätze, z.B. maximale Berührungsfläche
27 | */
28 | def loadLayer(container: Container, boxLoadingOrder: Seq[(Box, BoxRotation)]): LoadedContainer = {
29 |
30 | val layer = Array.ofDim[Int](container.size.depth, container.size.width)
31 |
32 | val surfaceFinder = new SurfaceFinder(layer, isFlat = true)
33 |
34 | var loadedBoxes: List[LoadedBox] = Nil
35 | var skippedBoxes: List[Box] = Nil
36 | var stopLoading = false
37 | for ((box, rotation) <- boxLoadingOrder) {
38 | if (!stopLoading) {
39 |
40 | val rotatedBoxSize = rotation.rotateDimensions(box.size)
41 | val maxHeight = container.size.height - rotatedBoxSize.height
42 |
43 | val possiblePositions =
44 | surfaceFinder.findFlatSurfaces(rotatedBoxSize.width, rotatedBoxSize.depth, maxHeight)
45 |
46 | if (possiblePositions.isEmpty) {
47 | stopLoading = true
48 | skippedBoxes ::= box
49 | } else {
50 |
51 | val firstPosition = possiblePositions(0)
52 | val x = firstPosition.x
53 | val z = firstPosition.y
54 | val y = layer(z)(x)
55 |
56 | loadedBoxes ::= LoadedBox(box, rotation, Position3D(x, y, z))
57 |
58 | surfaceFinder.updateArea(
59 | x, z, rotatedBoxSize.width, rotatedBoxSize.depth, y + rotatedBoxSize.height)
60 | }
61 | } else {
62 | skippedBoxes ::= box
63 | }
64 | }
65 |
66 | new LoadedContainer(container, loadedBoxes, skippedBoxes)
67 | }
68 |
69 | /*
70 | * Speicheraufwand:
71 | * a) Layer: 2-dimensionales Int-Array mit width x depth vom Container
72 | * Jeder Wert stellt die Höhe des Layers an der jeweiligen Position dar.
73 | * 10Mb Java Heap bei 1000x1000
74 | * 450Mb Java Heap bei 10000x10000
75 | * Bei http://people.brunel.ac.uk/~mastjjb/jeb/orlib/files/thpack1.txt
76 | * ist es im Bereich von 500x500, beim größten Beispiel bis 6000x2800
77 | * -> immer noch relativ viel, problematisch bei paralleler Fitnessberechnung
78 | *
79 | */
80 |
81 | }
82 |
83 |
84 |
--------------------------------------------------------------------------------
/src/ea/containerloading/ContainerProblem.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | case class Box(id: Int, size: Dimension3D, constraints: BoxConstraints = BoxConstraints(true, true))
4 | case class BoxConstraints(widthVertical: Boolean, depthVertical: Boolean) {
5 |
6 | private val widthVerticalRotations =
7 | if (widthVertical) List(BoxRotation(true, false, false), BoxRotation(true, false, true))
8 | else List()
9 |
10 | private val depthVerticalRotations =
11 | if (depthVertical) List(BoxRotation(false, true, false), BoxRotation(false, true, true))
12 | else List()
13 |
14 | val allowedRotations =
15 | BoxRotation(false, false, false) :: BoxRotation(false, false, true) ::
16 | widthVerticalRotations ::: depthVerticalRotations
17 | }
18 | case class BoxRotation(widthVertical: Boolean, depthVertical: Boolean, rotateHorizontal: Boolean) {
19 | require(!(widthVertical && depthVertical))
20 |
21 | def rotateDimensions(size: Dimension3D): Dimension3D = {
22 | var w = size.width
23 | var h = size.height
24 | var d = size.depth
25 |
26 | if (widthVertical) {
27 | val foo = w
28 | w = h
29 | h = foo
30 | } else if (depthVertical) {
31 | val foo = h
32 | h = d
33 | d = foo
34 | }
35 |
36 | if (rotateHorizontal) {
37 | val foo = w
38 | w = d
39 | d = foo
40 | }
41 | Dimension3D(w, h, d)
42 | }
43 |
44 | /**
45 | * Hack, damit Boxengleichheit bei selber id erzwungen wird (und Rotation nicht als Unterschied gilt)
46 | * Grund: sonst kann crossover doppelte Boxen erzeugen, wenn Rotation anders ist
47 | */
48 | override def equals(that: Any): Boolean = true
49 | override def hashCode: Int = 42
50 | }
51 | case class Container(size: Dimension3D)
52 |
53 | class ContainerProblem(val container: Container, val boxSizeFrequencies: Map[Dimension3D, (Int, BoxConstraints)]) {
54 |
55 | def this(containerSize: Dimension3D, boxSizeFrequencies: Map[Dimension3D, (Int, BoxConstraints)]) =
56 | this(Container(containerSize), boxSizeFrequencies)
57 |
58 | val boxIds = (0 until boxSizeFrequencies.values.map(f => f._1).sum).toList
59 |
60 | /**
61 | * weist jeder boxId eine Box zu
62 | */
63 | private val boxIndexMapping: Map[Int, Box] = {
64 |
65 | val boxReferences: List[Int] = calculateOriginalBoxIndices
66 | val boxes = boxSizeFrequencies.keys.toList
67 | // TODO geht doch sicher auch eleganter...
68 | val mapping = collection.mutable.Map[Int, Box]()
69 | for (id <- boxIds) {
70 | val size = boxes(boxReferences(id))
71 | val boxConstraints = boxSizeFrequencies(size)._2
72 | mapping += (id -> Box(id, size, boxConstraints))
73 | }
74 | Map(mapping.toList: _*)
75 | }
76 |
77 | val boxes = boxIndexMapping.values
78 |
79 | def boxFromId(id: Int): Box = boxIndexMapping(id)
80 |
81 | /**
82 | * Boxes of same type (dimension) get same indices
83 | * First index is 0
84 | *
85 | * @return e.g. List(0,0,1,1,1,2,2,3,4,5,6,6)
86 | */
87 | private def calculateOriginalBoxIndices(): List[Int] = {
88 | var indices = List[Int]()
89 |
90 | for (boxCount <- boxSizeFrequencies) {
91 | val currentBoxIndex = indices match {
92 | case Nil => 0
93 | case head :: tail => head + 1
94 | }
95 | val newBoxes = List.fill(boxCount._2._1)(currentBoxIndex)
96 | indices = newBoxes ::: indices
97 | }
98 |
99 | return indices.reverse
100 | }
101 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/EvolutionaryContainerLoading.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import org.uncommons.watchmaker.framework._
4 | import org.uncommons.watchmaker.framework.operators._
5 | import org.uncommons.watchmaker.framework.factories._
6 | import org.uncommons.watchmaker.framework.termination._
7 | import org.uncommons.watchmaker.framework.islands._
8 | import org.uncommons.maths.random._
9 | import org.uncommons.maths.random.Probability
10 | import org.uncommons.maths.number.ConstantGenerator
11 | import ea.watchmaker.Implicits._
12 | import scala.collection.JavaConversions._
13 | import java.util.{ List => jList, ArrayList => jArrayList }
14 |
15 | case class IslandConfig(epochLength: Int, migrantCount: Int)
16 |
17 | class EvolutionaryContainerLoading(
18 | val islands: Option[IslandConfig],
19 | selectionStrategy: SelectionStrategy[_ >: jList[(Box, BoxRotation)]],
20 | populationSize: Int,
21 | eliteCount: Int,
22 | crossoverProbability: Probability,
23 | termination: TerminationCondition*) {
24 |
25 | var listeners = List[PopulationData[_ <: jList[(Box, BoxRotation)]] => Unit]()
26 | var islandListeners = List[(Int, PopulationData[_ <: jList[(Box, BoxRotation)]]) => Unit]()
27 |
28 | def runEvolution(problem: ContainerProblem): Seq[(Box, BoxRotation)] = {
29 |
30 | val rng = new MersenneTwisterRNG
31 |
32 | val boxesAndRotation = problem.boxes.map(box => (box, BoxRotation(false, false, false))).toList
33 | val candidateFactory = new ListPermutationFactory(boxesAndRotation)
34 |
35 | val pipelineWithRotation = new SplitEvolution(
36 | new EvolutionPipeline(List(
37 | new ListOrderCrossover2[(Box, BoxRotation)](crossoverProbability),
38 | new ListOrderMutation[(Box, BoxRotation)](new PoissonGenerator(1, rng), new PoissonGenerator(1, rng)),
39 | new RotationMutation(new Probability(0.3)),
40 | new StackRotationMutation(new Probability(0.2)),
41 | new GroupingMutation(new Probability(0.3)))),
42 | new Replacement[jList[(Box, BoxRotation)]](candidateFactory, new Probability(0.7)),
43 | 0.9)
44 |
45 | val pipelineWithoutRotation = new SplitEvolution(
46 | new EvolutionPipeline(List(
47 | new ListOrderCrossover2[(Box, BoxRotation)](crossoverProbability),
48 | new ListOrderMutation[(Box, BoxRotation)](new PoissonGenerator(1, rng), new PoissonGenerator(1, rng)),
49 | new RotationMutation(new Probability(0.3)))),
50 | new Replacement[jList[(Box, BoxRotation)]](candidateFactory, new Probability(0.7)),
51 | 0.9)
52 |
53 | val fitnessEvaluator = new CachingFitnessEvaluator(new PackingEvaluator(problem))
54 |
55 | if (islands.isDefined) {
56 | val islandConfig = islands.get
57 |
58 | // // multiple islands without differences
59 | // val islandEvolution = new IslandEvolution[jList[(Box, BoxRotation)]](
60 | // 10,
61 | // new RingMigration,
62 | // candidateFactory,
63 | // pipelineWithRotation,
64 | // fitnessEvaluator,
65 | // selectionStrategy,
66 | // rng
67 | // )
68 |
69 | // 2 islands: one with rotation, one without
70 | val islandWithRotation = new GenerationalEvolutionEngine[jList[(Box, BoxRotation)]](
71 | candidateFactory,
72 | pipelineWithRotation,
73 | fitnessEvaluator,
74 | selectionStrategy,
75 | rng)
76 |
77 | val islandWithoutRotation = new GenerationalEvolutionEngine[jList[(Box, BoxRotation)]](
78 | candidateFactory,
79 | pipelineWithoutRotation,
80 | fitnessEvaluator,
81 | selectionStrategy,
82 | rng)
83 |
84 | val islandEvolution = new IslandEvolution[jList[(Box, BoxRotation)]](
85 | List(islandWithRotation, islandWithoutRotation),
86 | new RingMigration,
87 | true, rng)
88 |
89 | islandEvolution.addEvolutionObserver(new IslandEvolutionObserver[jList[(Box, BoxRotation)]] {
90 | def islandPopulationUpdate(islandIndex: Int, data: PopulationData[_ <: jList[(Box, BoxRotation)]]) = {
91 | islandListeners.foreach(_(islandIndex, data))
92 | }
93 | def populationUpdate(data: PopulationData[_ <: jList[(Box, BoxRotation)]]) = {
94 | listeners.foreach(_(data))
95 | }
96 | })
97 |
98 | islandEvolution.evolve(populationSize, eliteCount, islandConfig.epochLength, islandConfig.migrantCount, termination: _*)
99 | } else {
100 |
101 | val engine = new GenerationalEvolutionEngine[jList[(Box, BoxRotation)]](
102 | candidateFactory,
103 | pipelineWithRotation,
104 | fitnessEvaluator,
105 | selectionStrategy,
106 | rng)
107 |
108 | listeners.foreach(engine.addEvolutionObserver(_))
109 |
110 | engine.evolve(populationSize, eliteCount, termination: _*)
111 | }
112 |
113 | }
114 |
115 | def addListener(listener: PopulationData[_ <: jList[(Box, BoxRotation)]] => Unit) = {
116 | this.listeners ::= listener
117 | }
118 |
119 | def removeListener(listener: PopulationData[_ <: jList[(Box, BoxRotation)]] => Unit) = {
120 | this.listeners = this.listeners.filterNot(_ == listener)
121 | }
122 |
123 | def addIslandListener(listener: (Int, PopulationData[_ <: jList[(Box, BoxRotation)]]) => Unit) = {
124 | this.islandListeners ::= listener
125 | }
126 |
127 | def removeIslandListener(listener: (Int, PopulationData[_ <: jList[(Box, BoxRotation)]]) => Unit) = {
128 | this.islandListeners = this.islandListeners.filterNot(_ == listener)
129 | }
130 |
131 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/Geometry.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | case class Dimension2D(width: Int, height: Int) {
4 | val area = (width: Long) * (height: Long)
5 | }
6 | case class Dimension3D(width: Int, height: Int, depth: Int) {
7 | val volume = (width: Long) * (height: Long) * (depth: Long)
8 | override def toString = width.toString + "x" + height.toString + "x" + depth.toString
9 | }
10 | case class Position2D(x: Int, y: Int)
11 | case class Position3D(x: Int, y: Int, z: Int)
--------------------------------------------------------------------------------
/src/ea/containerloading/GroupingMutation.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import scala.collection.JavaConversions._
4 | import java.util.{ List => jList, ArrayList => jArrayList }
5 | import java.util.Random
6 | import org.uncommons.watchmaker.framework.EvolutionaryOperator
7 | import org.uncommons.maths.number.{ NumberGenerator, ConstantGenerator }
8 | import org.uncommons.maths.random.Probability
9 |
10 | /**
11 | * Mutation: Clusterbildung
12 | */
13 | class GroupingMutation(mutationProbability: NumberGenerator[Probability]) extends EvolutionaryOperator[jList[(Box, BoxRotation)]] {
14 |
15 | def this(mutationProbability: Probability) = this(new ConstantGenerator(mutationProbability))
16 |
17 | def apply(selectedCandidates: jList[jList[(Box, BoxRotation)]], rng: Random): jList[jList[(Box, BoxRotation)]] = {
18 |
19 | val result = new jArrayList[jList[(Box, BoxRotation)]](selectedCandidates.size)
20 | for (candidate <- selectedCandidates) {
21 |
22 | val newCandidate = new jArrayList(candidate)
23 |
24 | if (mutationProbability.nextValue.nextEvent(rng)) {
25 | val boxIdx = rng.nextInt(candidate.size)
26 |
27 | val refBox = candidate.get(boxIdx)
28 | val refBoxDimension = refBox._1.size
29 | val refBoxRotation = refBox._2
30 |
31 | var freeIdx = -1
32 | for (index <- (boxIdx + 1) until newCandidate.size) {
33 | val box = newCandidate.get(index)
34 | if (box._1.size == refBoxDimension && box._2 == refBoxRotation) {
35 | if (freeIdx != -1) {
36 | java.util.Collections.swap(newCandidate, freeIdx, index)
37 | freeIdx += 1
38 | }
39 | } else if (freeIdx == -1) {
40 | freeIdx = index
41 | }
42 | }
43 | }
44 |
45 | result.add(newCandidate)
46 | }
47 |
48 | result
49 | }
50 |
51 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/Main.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import org.uncommons.watchmaker.framework.selection._
4 | import org.uncommons.watchmaker.framework.termination._
5 | import org.uncommons.watchmaker.framework._
6 | import org.uncommons.maths.random.Probability
7 | import scala.collection.JavaConversions._
8 | import ea.containerloading.vis._
9 | import org.jfree.chart._
10 | import org.jfree.data.xy._
11 |
12 | import java.awt.Graphics2D;
13 | import java.awt.geom.Rectangle2D;
14 | import java.io.BufferedOutputStream;
15 | import java.io.File;
16 | import java.io.FileOutputStream;
17 | import java.io.IOException;
18 | import java.io.OutputStream;
19 | import java.text.SimpleDateFormat;
20 | import com.lowagie.text.Document;
21 | import com.lowagie.text.DocumentException;
22 | import com.lowagie.text.Rectangle;
23 | import com.lowagie.text.pdf.DefaultFontMapper;
24 | import com.lowagie.text.pdf.FontMapper;
25 | import com.lowagie.text.pdf.PdfContentByte;
26 | import com.lowagie.text.pdf.PdfTemplate;
27 | import com.lowagie.text.pdf.PdfWriter;
28 |
29 | import java.text.{DecimalFormat}
30 |
31 | object Main {
32 |
33 | var currentEpoch = 0
34 |
35 | def main(args : Array[String]) : Unit = {
36 |
37 | // val problem = new ContainerProblem(
38 | // containerSize = Dimension3D(50, 50, 50),
39 | // boxSizeFrequencies =
40 | // Map(Dimension3D(10,10,12) -> (5, BoxConstraints(widthVertical = true, depthVertical = true)),
41 | // Dimension3D(20,10,10) -> (15, BoxConstraints(widthVertical = true, depthVertical = true)),
42 | // Dimension3D(30,10,20) -> (10, BoxConstraints(widthVertical = true, depthVertical = true)),
43 | // Dimension3D(50,10,10) -> (5, BoxConstraints(widthVertical = true, depthVertical = true))
44 | // ))
45 |
46 | // val problem = new ContainerProblem(
47 | // containerSize = Dimension3D(10, 10, 10),
48 | // boxSizeFrequencies =
49 | // Map(Dimension3D(10,10,5) -> (2, BoxConstraints(widthVertical = true, depthVertical = true))
50 | // ))
51 |
52 | // // thpack1 - 1
53 | // val problem = new ContainerProblem(
54 | // containerSize = Dimension3D(233, 220, 587),
55 | // boxSizeFrequencies =
56 | // Map(Dimension3D(76,30,108) -> (40, BoxConstraints(widthVertical = false, depthVertical = false)),
57 | // Dimension3D(43,25,110) -> (33, BoxConstraints(widthVertical = true, depthVertical = false)),
58 | // Dimension3D(81,55,92) -> (39, BoxConstraints(widthVertical = true, depthVertical = true))
59 | // ))
60 |
61 | // // thpack1 - 10
62 | // // eley's best result with Algorithm 1: 0.8869
63 | // val problem = new ContainerProblem(
64 | // containerSize = Dimension3D(233, 220, 587),
65 | // boxSizeFrequencies =
66 | // Map(Dimension3D(66,31,68) -> (44, BoxConstraints(widthVertical = false, depthVertical = false)),
67 | // Dimension3D(34,26,120) -> (41, BoxConstraints(widthVertical = true, depthVertical = false)),
68 | // Dimension3D(76,60,95) -> (45, BoxConstraints(widthVertical = true, depthVertical = true))
69 | // ))
70 |
71 | // // thpack2 - 1
72 | // val problem = new ContainerProblem(
73 | // containerSize = Dimension3D(233, 220, 587),
74 | // boxSizeFrequencies =
75 | // Map(Dimension3D(76,30,108) -> (24, BoxConstraints(widthVertical = false, depthVertical = false)),
76 | // Dimension3D(43,25,110) -> (7, BoxConstraints(widthVertical = true, depthVertical = false)),
77 | // Dimension3D(81,55,92) -> (22, BoxConstraints(widthVertical = true, depthVertical = true)),
78 | // Dimension3D(33,28,81) -> (13, BoxConstraints(widthVertical = true, depthVertical = false)),
79 | // Dimension3D(99,73,120) -> (15, BoxConstraints(widthVertical = true, depthVertical = true))
80 | // ))
81 |
82 | // // thpack3 - 1
83 | // val problem = new ContainerProblem(
84 | // containerSize = Dimension3D(233, 220, 587),
85 | // boxSizeFrequencies =
86 | // Map(Dimension3D(76,30,108) -> (24, BoxConstraints(widthVertical = false, depthVertical = false)),
87 | // Dimension3D(43,25,110) -> (9, BoxConstraints(widthVertical = true, depthVertical = false)),
88 | // Dimension3D(81,55,92) -> (8, BoxConstraints(widthVertical = true, depthVertical = true)),
89 | // Dimension3D(33,28,81) -> (11, BoxConstraints(widthVertical = true, depthVertical = false)),
90 | // Dimension3D(99,73,120) -> (11, BoxConstraints(widthVertical = true, depthVertical = true)),
91 | // Dimension3D(70,48,111) -> (10, BoxConstraints(widthVertical = true, depthVertical = false)),
92 | // Dimension3D(72,46,98) -> (12, BoxConstraints(widthVertical = true, depthVertical = false)),
93 | // Dimension3D(66,31,95) -> (9, BoxConstraints(widthVertical = false, depthVertical = false))
94 | // ))
95 |
96 | // // thpack7 - 1
97 | // val problem = new ContainerProblem(
98 | // containerSize = Dimension3D(233, 220, 587),
99 | // boxSizeFrequencies =
100 | // Map(Dimension3D(76,30,108) -> (10, BoxConstraints(widthVertical = false, depthVertical = false)),
101 | // Dimension3D(43,25,110) -> (6, BoxConstraints(widthVertical = true, depthVertical = false)),
102 | // Dimension3D(81,55,92) -> (5, BoxConstraints(widthVertical = true, depthVertical = true)),
103 | // Dimension3D(33,28,81) -> (5, BoxConstraints(widthVertical = true, depthVertical = false)),
104 | // Dimension3D(99,73,120) -> (6, BoxConstraints(widthVertical = true, depthVertical = true)),
105 | // Dimension3D(70,48,111) -> (4, BoxConstraints(widthVertical = true, depthVertical = false)),
106 | // Dimension3D(72,46,98) -> (7, BoxConstraints(widthVertical = true, depthVertical = false)),
107 | // Dimension3D(66,31,95) -> (6, BoxConstraints(widthVertical = false, depthVertical = false)),
108 | // Dimension3D(84,30,85) -> (5, BoxConstraints(widthVertical = false, depthVertical = false)),
109 | // Dimension3D(32,25,71) -> (5, BoxConstraints(widthVertical = true, depthVertical = false)),
110 | // Dimension3D(34,25,36) -> (4, BoxConstraints(widthVertical = true, depthVertical = true)),
111 | // Dimension3D(67,62,97) -> (8, BoxConstraints(widthVertical = true, depthVertical = true)),
112 | // Dimension3D(25,23,33) -> (2, BoxConstraints(widthVertical = true, depthVertical = true)),
113 | // Dimension3D(27,26,95) -> (5, BoxConstraints(widthVertical = true, depthVertical = false)),
114 | // Dimension3D(81,44,94) -> (8, BoxConstraints(widthVertical = true, depthVertical = false)),
115 | // Dimension3D(39,38,41) -> (4, BoxConstraints(widthVertical = true, depthVertical = true)),
116 | // Dimension3D(74,65,104) -> (4, BoxConstraints(widthVertical = true, depthVertical = true)),
117 | // Dimension3D(41,36,52) -> (4, BoxConstraints(widthVertical = true, depthVertical = true)),
118 | // Dimension3D(78,34,104) -> (6, BoxConstraints(widthVertical = false, depthVertical = false)),
119 | // Dimension3D(77,46,83) -> (6, BoxConstraints(widthVertical = true, depthVertical = true))
120 | // ))
121 |
122 | // // thpack8 - 1
123 | // val problem = new ContainerProblem(
124 | // containerSize = Dimension3D(2000, 1000, 3000),
125 | // boxSizeFrequencies =
126 | // Map(Dimension3D(375,300,400) -> (24, BoxConstraints(widthVertical = false, depthVertical = false)),
127 | // Dimension3D(400,150,400) -> (10, BoxConstraints(widthVertical = false, depthVertical = false)),
128 | // Dimension3D(150,200,300) -> (11, BoxConstraints(widthVertical = false, depthVertical = false)),
129 | // Dimension3D(200,275,900) -> (14, BoxConstraints(widthVertical = false, depthVertical = false)),
130 | // Dimension3D(150,275,800) -> (6, BoxConstraints(widthVertical = false, depthVertical = false)),
131 | // Dimension3D(150,200,1500) -> (20, BoxConstraints(widthVertical = false, depthVertical = false)),
132 | // Dimension3D(200,200,900) -> (15, BoxConstraints(widthVertical = false, depthVertical = false))
133 | // ))
134 |
135 | // thpack8 - 2
136 | val problem = new ContainerProblem(
137 | containerSize = Dimension3D(2000, 1000, 3000),
138 | boxSizeFrequencies =
139 | Map(Dimension3D(375,250,400) -> (29, BoxConstraints(widthVertical = false, depthVertical = false)),
140 | Dimension3D(400,150,400) -> (37, BoxConstraints(widthVertical = false, depthVertical = false)),
141 | Dimension3D(300,200,300) -> (34, BoxConstraints(widthVertical = false, depthVertical = false)),
142 | Dimension3D(375,400,500) -> (19, BoxConstraints(widthVertical = false, depthVertical = false)),
143 | Dimension3D(275,200,800) -> (16, BoxConstraints(widthVertical = false, depthVertical = false)),
144 | Dimension3D(350,350,450) -> (17, BoxConstraints(widthVertical = false, depthVertical = false)),
145 | Dimension3D(200,125,200) -> (23, BoxConstraints(widthVertical = false, depthVertical = false))
146 | ))
147 |
148 | // // thpack8 - 4
149 | // val problem = new ContainerProblem(
150 | // containerSize = Dimension3D(2000, 1100, 3000),
151 | // boxSizeFrequencies =
152 | // Map(Dimension3D(375,200,400) -> (16, BoxConstraints(widthVertical = false, depthVertical = false)),
153 | // Dimension3D(250,250,400) -> (23, BoxConstraints(widthVertical = false, depthVertical = false)),
154 | // Dimension3D(300,200,300) -> (17, BoxConstraints(widthVertical = false, depthVertical = false)),
155 | // Dimension3D(500,500,225) -> (9, BoxConstraints(widthVertical = false, depthVertical = false)),
156 | // Dimension3D(400,275,800) -> (8, BoxConstraints(widthVertical = false, depthVertical = false)),
157 | // Dimension3D(200,275,600) -> (17, BoxConstraints(widthVertical = false, depthVertical = false)),
158 | // Dimension3D(200,275,900) -> (10, BoxConstraints(widthVertical = false, depthVertical = false))
159 | // ))
160 |
161 | // // thpack8 - 5
162 | // val problem = new ContainerProblem(
163 | // containerSize = Dimension3D(2000, 900, 3000),
164 | // boxSizeFrequencies =
165 | // Map(Dimension3D(375,200,400) -> (35, BoxConstraints(widthVertical = false, depthVertical = false)),
166 | // Dimension3D(250,225,300) -> (15, BoxConstraints(widthVertical = false, depthVertical = false)),
167 | // Dimension3D(500,100,500) -> (25, BoxConstraints(widthVertical = false, depthVertical = false)),
168 | // Dimension3D(250,250,800) -> (10, BoxConstraints(widthVertical = false, depthVertical = false)),
169 | // Dimension3D(200,200,1500)-> (20, BoxConstraints(widthVertical = false, depthVertical = false)),
170 | // Dimension3D(200,200,900) -> (15, BoxConstraints(widthVertical = false, depthVertical = false))
171 | // ))
172 |
173 | // // thpack8 - 6
174 | // val problem = new ContainerProblem(
175 | // containerSize = Dimension3D(2400, 1000, 3500),
176 | // boxSizeFrequencies =
177 | // Map(Dimension3D(375,250,400) -> (34, BoxConstraints(widthVertical = false, depthVertical = false)),
178 | // Dimension3D(275,225,400) -> (37, BoxConstraints(widthVertical = false, depthVertical = false)),
179 | // Dimension3D(250,125,300) -> (23, BoxConstraints(widthVertical = false, depthVertical = false)),
180 | // Dimension3D(450,225,500) -> (27, BoxConstraints(widthVertical = false, depthVertical = false)),
181 | // Dimension3D(275,200,1500)-> (25, BoxConstraints(widthVertical = false, depthVertical = false)),
182 | // Dimension3D(400,300,600) -> (23, BoxConstraints(widthVertical = false, depthVertical = false)),
183 | // Dimension3D(200,200,900) -> (14, BoxConstraints(widthVertical = false, depthVertical = false)),
184 | // Dimension3D(350,300,700) -> (17, BoxConstraints(widthVertical = false, depthVertical = false))
185 | // ))
186 |
187 | // // thpack8 - 7
188 | // val problem = new ContainerProblem(
189 | // containerSize = Dimension3D(2400, 1300, 3500),
190 | // boxSizeFrequencies =
191 | // Map(Dimension3D(375,250,400) -> (34, BoxConstraints(widthVertical = false, depthVertical = false)),
192 | // Dimension3D(275,225,400) -> (37, BoxConstraints(widthVertical = false, depthVertical = false)),
193 | // Dimension3D(250,125,300) -> (23, BoxConstraints(widthVertical = false, depthVertical = false)),
194 | // Dimension3D(450,225,500) -> (27, BoxConstraints(widthVertical = false, depthVertical = false)),
195 | // Dimension3D(275,200,1500)-> (25, BoxConstraints(widthVertical = false, depthVertical = false)),
196 | // Dimension3D(400,300,600) -> (23, BoxConstraints(widthVertical = false, depthVertical = false)),
197 | // Dimension3D(200,200,900) -> (14, BoxConstraints(widthVertical = false, depthVertical = false)),
198 | // Dimension3D(350,300,700) -> (17, BoxConstraints(widthVertical = false, depthVertical = false))
199 | // ))
200 |
201 | // // thpack8 - 12
202 | // val problem = new ContainerProblem(
203 | // containerSize = Dimension3D(2400, 1000, 3200),
204 | // boxSizeFrequencies =
205 | // Map(Dimension3D(275,200,900) -> (10, BoxConstraints(widthVertical = false, depthVertical = false)),
206 | // Dimension3D(350,275,400) -> (33, BoxConstraints(widthVertical = false, depthVertical = false)),
207 | // Dimension3D(300,250,1200)-> (10, BoxConstraints(widthVertical = false, depthVertical = false)),
208 | // Dimension3D(375,275,500) -> (27, BoxConstraints(widthVertical = false, depthVertical = false)),
209 | // Dimension3D(400,200,800) -> (15, BoxConstraints(widthVertical = false, depthVertical = false)),
210 | // Dimension3D(300,225,600) -> (25, BoxConstraints(widthVertical = false, depthVertical = false))
211 | // ))
212 |
213 | val userAbort = new UserAbort
214 |
215 | val runner = new EvolutionaryContainerLoading(
216 | //islands = Some(IslandConfig(epochLength = 20, migrantCount = 1)),
217 | islands = None,
218 | new SigmaScaling,
219 | //new RankSelection,
220 | //new RouletteWheelSelection,
221 | populationSize = 100,
222 | eliteCount = 1,
223 | crossoverProbability = Probability.EVENS, //new Probability(0.3)
224 | //new TargetFitness(0.846, true),
225 | //new GenerationCount(60),
226 | userAbort,
227 | new ElapsedTime(30*60*1000)
228 | )
229 |
230 | showAbortWindow(userAbort)
231 |
232 | val fitnessFormat = new DecimalFormat("0.0000")
233 | val meanStdDevFitnessSeries = new YIntervalSeries("Mean Fitness with " + '\u03C3')
234 | val maxFitnessSeries = new XYSeries("Max Fitness")
235 |
236 | val island0MeanStdDevFitness = new YIntervalSeries("Mean Fitness with " + '\u03C3' + " (Island 0)")
237 | val island1MeanStdDevFitness = new YIntervalSeries("Mean Fitness with " + '\u03C3' + " (Island 1)")
238 | val island0MaxFitness = new XYSeries("Max Fitness (Island 0)")
239 | val island1MaxFitness = new XYSeries("Max Fitness (Island 1)")
240 |
241 | runner.addListener(popData => {
242 | val evoType = if (runner.islands.isDefined) "epoch" else "generation"
243 | println(evoType + " " + (popData.getGenerationNumber + 1) + " - " +
244 | "best fitness: " + fitnessFormat.format(popData.getBestCandidateFitness) + " " +
245 | "mean fitness: " + fitnessFormat.format(popData.getMeanFitness) + " " +
246 | "std dev: " + fitnessFormat.format(popData.getFitnessStandardDeviation) + " -- " +
247 | popData.getElapsedTime / 1000 + "s so far")
248 |
249 | meanStdDevFitnessSeries.add(
250 | popData.getGenerationNumber + 1,
251 | popData.getMeanFitness,
252 | popData.getMeanFitness - popData.getFitnessStandardDeviation,
253 | popData.getMeanFitness + popData.getFitnessStandardDeviation)
254 |
255 | maxFitnessSeries.add(popData.getGenerationNumber + 1, popData.getBestCandidateFitness)
256 |
257 | currentEpoch += 1
258 | })
259 |
260 | runner.addIslandListener((islandIndex, popData) => {
261 | val realGenerationNumber =
262 | (popData.getGenerationNumber + 1) +
263 | currentEpoch * runner.islands.get.epochLength
264 |
265 | println("island " + islandIndex + ", " +
266 | "generation " + (popData.getGenerationNumber + 1) + " (" + realGenerationNumber + ") - " +
267 | "best fitness: " + fitnessFormat.format(popData.getBestCandidateFitness) + " " +
268 | "mean fitness: " + fitnessFormat.format(popData.getMeanFitness) + " " +
269 | "std dev: " + fitnessFormat.format(popData.getFitnessStandardDeviation) + " -- " +
270 | popData.getElapsedTime / 1000 + "s so far")
271 |
272 | islandIndex match {
273 | case 0 => island0MeanStdDevFitness.add(
274 | realGenerationNumber,
275 | popData.getMeanFitness,
276 | popData.getMeanFitness - popData.getFitnessStandardDeviation,
277 | popData.getMeanFitness + popData.getFitnessStandardDeviation)
278 | island0MaxFitness.add(realGenerationNumber, popData.getBestCandidateFitness)
279 | case 1 => island1MeanStdDevFitness.add(
280 | realGenerationNumber,
281 | popData.getMeanFitness,
282 | popData.getMeanFitness - popData.getFitnessStandardDeviation,
283 | popData.getMeanFitness + popData.getFitnessStandardDeviation)
284 | island1MaxFitness.add(realGenerationNumber, popData.getBestCandidateFitness)
285 | case _ =>
286 | }
287 | })
288 |
289 | val bestBoxLoadingOrder = runner.runEvolution(problem)
290 |
291 | val meanStdDevFitnessData = new YIntervalSeriesCollection
292 | meanStdDevFitnessData.addSeries(meanStdDevFitnessSeries)
293 | val maxFitnessData = new XYSeriesCollection(maxFitnessSeries)
294 | val chart = createStatisticalXYLineChart(
295 | null, //"Container size: " + problem.container.size + ", boxes: " + problem.boxes.size,
296 | if (runner.islands.isDefined) "Epoch" else "Generation",
297 | "Fitness",
298 | meanStdDevFitnessData, maxFitnessData,
299 | if (runner.islands.isDefined) Some("Generation") else None,
300 | if (runner.islands.isDefined) {
301 | val c = new YIntervalSeriesCollection
302 | c.addSeries(island0MeanStdDevFitness)
303 | c.addSeries(island1MeanStdDevFitness)
304 | Some(c)
305 | } else None,
306 | if (runner.islands.isDefined) {
307 | val c = new XYSeriesCollection
308 | c.addSeries(island0MaxFitness)
309 | c.addSeries(island1MaxFitness)
310 | Some(c)
311 | } else None
312 | )
313 | val chartFrame = new org.jfree.chart.ChartFrame("Fitness", chart)
314 | addSaveAsPdfMenuButton(chartFrame)
315 | chartFrame.pack
316 | chartFrame.setVisible(true)
317 |
318 | val loadingResult = ContainerLoader.loadLayer(problem.container, bestBoxLoadingOrder)
319 |
320 | println("skipped: " + loadingResult.skippedBoxes.length)
321 |
322 | val rotatedBoxes = loadingResult.loadedBoxes.filter { b =>
323 | b.rotation match {
324 | case BoxRotation(false,false,false) => false
325 | case _ => true
326 | } }
327 | println("rotated boxes: " + rotatedBoxes.size)
328 |
329 | CandidateViewer.showCandidate(loadingResult)
330 | }
331 |
332 | private def showAbortWindow(trigger: UserAbort) = {
333 | val frame = new javax.swing.JFrame
334 | val button = new javax.swing.JButton("request abort")
335 | button.addActionListener(new java.awt.event.ActionListener {
336 | def actionPerformed(e: java.awt.event.ActionEvent) = {
337 | trigger.abort
338 | }
339 | })
340 | frame.add(button)
341 | frame.pack
342 | frame.setVisible(true)
343 | }
344 |
345 | private def createStatisticalXYLineChart(title: String, xLabel: String, yLabel: String, data: IntervalXYDataset, data2: XYDataset, xLabel2: Option[String], data3: Option[IntervalXYDataset], data4: Option[XYDataset]): JFreeChart = {
346 |
347 | val xAxis = new org.jfree.chart.axis.NumberAxis(xLabel)
348 | xAxis.setStandardTickUnits(org.jfree.chart.axis.NumberAxis.createIntegerTickUnits)
349 |
350 | val renderer = new org.jfree.chart.renderer.xy.DeviationRenderer(true, false)
351 | val renderer2 = new org.jfree.chart.renderer.xy.XYLineAndShapeRenderer(true, false)
352 | val leftPlot = new org.jfree.chart.plot.XYPlot(data2, xAxis, null, renderer2)
353 | leftPlot.setDataset(1, data)
354 | leftPlot.setRenderer(1, renderer)
355 | leftPlot.setOrientation(org.jfree.chart.plot.PlotOrientation.VERTICAL)
356 |
357 | renderer.setAutoPopulateSeriesPaint(false)
358 | renderer.setSeriesPaint(0, java.awt.Color.BLACK)
359 | renderer2.setAutoPopulateSeriesPaint(false)
360 | renderer2.setSeriesPaint(0, java.awt.Color.GREEN)
361 |
362 | val yAxis = new org.jfree.chart.axis.NumberAxis(yLabel)
363 | val combinedPlot = new org.jfree.chart.plot.CombinedRangeXYPlot(yAxis)
364 | combinedPlot.add(leftPlot, 1)
365 |
366 | if (xLabel2.isDefined && data3.isDefined && data4.isDefined) {
367 | val renderer3 = new org.jfree.chart.renderer.xy.DeviationRenderer(true, false)
368 | renderer3.setAutoPopulateSeriesPaint(false)
369 | renderer3.setSeriesPaint(0, java.awt.Color.RED)
370 | renderer3.setSeriesPaint(1, java.awt.Color.BLUE)
371 | renderer3.setSeriesFillPaint(0, java.awt.Color.RED)
372 | renderer3.setSeriesFillPaint(1, java.awt.Color.BLUE)
373 |
374 | val renderer4 = new org.jfree.chart.renderer.xy.XYLineAndShapeRenderer(true, false)
375 | renderer4.setAutoPopulateSeriesPaint(false)
376 | renderer4.setSeriesPaint(0, java.awt.Color.ORANGE)
377 | renderer4.setSeriesPaint(1, java.awt.Color.MAGENTA)
378 |
379 | val xAxis = new org.jfree.chart.axis.NumberAxis(xLabel2.get)
380 | xAxis.setStandardTickUnits(org.jfree.chart.axis.NumberAxis.createIntegerTickUnits)
381 | val rightPlot = new org.jfree.chart.plot.XYPlot(data4.get, xAxis, null, renderer4)
382 | rightPlot.setDataset(1, data3.get)
383 | rightPlot.setRenderer(1, renderer3)
384 |
385 | rightPlot.setOrientation(org.jfree.chart.plot.PlotOrientation.VERTICAL)
386 | combinedPlot.add(rightPlot, 2)
387 | }
388 |
389 | val chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, combinedPlot, true)
390 | val theme = new org.jfree.chart.StandardChartTheme("JFree")
391 | theme.apply(chart)
392 | val font = new java.awt.Font("Arial Unicode MS", java.awt.Font.PLAIN, 12);
393 | chart.getLegend.setItemFont(font)
394 | chart
395 | }
396 |
397 | private def addSaveAsPdfMenuButton(frame: ChartFrame) = {
398 | val savePdfItem = new javax.swing.JMenuItem("Save as PDF...")
399 |
400 | savePdfItem.addActionListener(new java.awt.event.ActionListener {
401 | def actionPerformed(e: java.awt.event.ActionEvent) = {
402 | val fileChooser = new javax.swing.JFileChooser
403 | val filter = new org.jfree.ui.ExtensionFileFilter("PDF file", ".pdf")
404 | fileChooser.addChoosableFileFilter(filter)
405 |
406 | val option = fileChooser.showSaveDialog(frame)
407 | if (option == javax.swing.JFileChooser.APPROVE_OPTION) {
408 | var filename = fileChooser.getSelectedFile.getPath
409 | if (!filename.endsWith(".pdf")) {
410 | filename += ".pdf"
411 | }
412 | saveChartAsPDF(
413 | new File(filename),
414 | frame.getChartPanel.getChart,
415 | Dimension2D(frame.getChartPanel.getWidth, frame.getChartPanel.getHeight))
416 | }
417 | }
418 | })
419 |
420 | frame.getChartPanel.getPopupMenu.add(savePdfItem)
421 | }
422 |
423 | private def saveChartAsPDF(file: File, chart: JFreeChart, size: Dimension2D) = {
424 | val mapper = new DefaultFontMapper
425 | val jreBinPath = System.getProperty("sun.boot.library.path")
426 | val jreFontsPath = jreBinPath.substring(0, jreBinPath.size - 4) + "\\lib\\fonts"
427 | mapper.insertDirectory(jreFontsPath)
428 | val pp = mapper.getBaseFontParameters("Arial Unicode MS")
429 | if (pp != null) {
430 | pp.encoding = com.lowagie.text.pdf.BaseFont.IDENTITY_H
431 | }
432 | val out = new BufferedOutputStream(new FileOutputStream(file))
433 | val pagesize = new Rectangle(size.width, size.height)
434 | val document = new Document(pagesize, 50, 50, 50, 50)
435 | val writer = PdfWriter.getInstance(document, out)
436 | document.open
437 | val cb = writer.getDirectContent
438 | val tp = cb.createTemplate(size.width, size.height)
439 | val g2 = tp.createGraphics(size.width, size.height, mapper)
440 | val r2D = new Rectangle2D.Double(0, 0, size.width, size.height)
441 | chart.draw(g2, r2D)
442 | g2.dispose
443 | cb.addTemplate(tp, 0, 0)
444 | document.close
445 | out.close
446 | }
447 | }
448 |
--------------------------------------------------------------------------------
/src/ea/containerloading/PackingEvaluator.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import org.uncommons.watchmaker.framework.FitnessEvaluator
4 | import java.util.{ List => jList }
5 | import scala.collection.JavaConversions._
6 |
7 | class PackingEvaluator(problem: ContainerProblem) extends FitnessEvaluator[jList[(Box, BoxRotation)]] {
8 |
9 | def getFitness(candidate: jList[(Box, BoxRotation)], population: jList[_ <: jList[(Box, BoxRotation)]]): Double = {
10 |
11 | val loadingResult = ContainerLoader.loadLayer(problem.container, candidate)
12 |
13 | // calculate and return space utilization ratio
14 | val spaceUsed = loadingResult.loadedBoxes.map(_.box.size.volume).sum
15 | val ratio = (spaceUsed: Double) / (problem.container.size.volume: Double)
16 | return ratio
17 | }
18 |
19 | def isNatural = true
20 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/RotationMutation.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import scala.collection.JavaConversions._
4 | import java.util.{ List => jList, ArrayList => jArrayList }
5 | import java.util.Random
6 | import org.uncommons.watchmaker.framework.EvolutionaryOperator
7 | import org.uncommons.maths.number.{ NumberGenerator, ConstantGenerator }
8 | import org.uncommons.maths.random.Probability
9 |
10 | /**
11 | * Mutation: Einzel-Rotation
12 | */
13 | class RotationMutation(mutationProbability: NumberGenerator[Probability]) extends EvolutionaryOperator[jList[(Box, BoxRotation)]] {
14 |
15 | def this(mutationProbability: Probability) = this(new ConstantGenerator(mutationProbability))
16 |
17 | def apply(selectedCandidates: jList[jList[(Box, BoxRotation)]], rng: Random): jList[jList[(Box, BoxRotation)]] = {
18 |
19 | val result = new jArrayList[jList[(Box, BoxRotation)]](selectedCandidates.size)
20 | for (candidate <- selectedCandidates) {
21 | val newCandidate = new jArrayList(candidate)
22 |
23 | if (mutationProbability.nextValue.nextEvent(rng)) {
24 | // mutate just one box's rotation for now
25 | val boxIndex = rng.nextInt(newCandidate.size)
26 | val box = newCandidate.get(boxIndex)._1
27 |
28 | val rotationsCount = box.constraints.allowedRotations.size
29 | val rotationIndex = rng.nextInt(rotationsCount)
30 | val newRotation = box.constraints.allowedRotations(rotationIndex)
31 |
32 | newCandidate.set(boxIndex, (box, newRotation))
33 | }
34 |
35 | result.add(newCandidate)
36 | }
37 |
38 | result
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/StackRotationMutation.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import scala.collection.JavaConversions._
4 | import java.util.{ List => jList, ArrayList => jArrayList }
5 | import java.util.Random
6 | import org.uncommons.watchmaker.framework.EvolutionaryOperator
7 | import org.uncommons.maths.number.{ NumberGenerator, ConstantGenerator }
8 | import org.uncommons.maths.random.Probability
9 |
10 | /**
11 | * Mutation: Typ-Rotation
12 | *
13 | * Selects a random box and rotates it and all boxes of the same dimension/type.
14 | *
15 | */
16 | class StackRotationMutation(mutationProbability: NumberGenerator[Probability]) extends EvolutionaryOperator[jList[(Box, BoxRotation)]] {
17 |
18 | def this(mutationProbability: Probability) = this(new ConstantGenerator(mutationProbability))
19 |
20 | def apply(selectedCandidates: jList[jList[(Box, BoxRotation)]], rng: Random): jList[jList[(Box, BoxRotation)]] = {
21 |
22 | val result = new jArrayList[jList[(Box, BoxRotation)]](selectedCandidates.size)
23 | for (candidate <- selectedCandidates) {
24 |
25 | val newCandidate = new jArrayList(candidate)
26 |
27 | if (mutationProbability.nextValue.nextEvent(rng)) {
28 | val box = candidate.get(rng.nextInt(candidate.size))._1
29 |
30 | val rotationsCount = box.constraints.allowedRotations.size
31 | val newRotation = box.constraints.allowedRotations(rng.nextInt(rotationsCount))
32 |
33 | var index = 0
34 | while (index <= candidate.length - 1) {
35 | val boxToTest = candidate.get(index)._1
36 | if (boxToTest.size equals box.size) {
37 | newCandidate.set(index, (boxToTest, newRotation))
38 | }
39 | index += 1
40 | }
41 | }
42 |
43 | result.add(newCandidate)
44 | }
45 |
46 | result
47 | }
48 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/SurfaceFinder.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading
2 |
3 | import util.control.Breaks._
4 |
5 | class SurfaceFinder(matrix: Array[Array[Int]], isFlat: Boolean = false) {
6 |
7 | private val matrixHeight = matrix.length
8 | private val matrixWidth = matrix(0).length
9 | private val helperMatrix = createHelperMatrix(isFlat)
10 |
11 | /**
12 | * O(n^2), with the help of Nikita Rybak, see link:
13 | * http://stackoverflow.com/questions/4656706/how-to-find-same-value-rectangular-areas-of-a-given-size-in-a-matrix-most-efficie/4657191#4657191
14 | */
15 | def findFlatSurfaces(surfaceWidth: Int, surfaceHeight: Int, maxValue: Int): List[Position2D] = {
16 |
17 | var resultPositions: List[Position2D] = Nil
18 |
19 | for (y <- 0 to matrixHeight - surfaceHeight) {
20 | var current_width = 0
21 | for (x <- 0 until matrixWidth) {
22 | if (helperMatrix(y)(x) < surfaceHeight - 1) {
23 | // this column has different numbers in it, no game
24 | current_width = 0
25 | } else if (current_width > 0 && matrix(y)(x) != matrix(y)(x - 1)) {
26 | // this column should consist of the same numbers as the one before
27 | current_width = 1
28 | } else {
29 | current_width += 1
30 | if (current_width >= surfaceWidth && matrix(y)(x) <= maxValue) {
31 | // TODO im Moment wird first fit benutzt -> nur 1 Ergebnis notwendig!
32 | //resultPositions ::= Position2D(x - surfaceWidth + 1, y)
33 | return List(Position2D(x - surfaceWidth + 1, y))
34 | }
35 | }
36 | }
37 | }
38 |
39 | return resultPositions
40 | }
41 |
42 | def updateArea(x: Int, y: Int, width: Int, height: Int, value: Int) = {
43 | // adjust matrix -> set new value in given region
44 | for {
45 | matrixX <- x until x + width
46 | matrixY <- y until y + height
47 | } {
48 | matrix(matrixY)(matrixX) = value
49 | }
50 |
51 | updateHelperMatrixCols(this.helperMatrix, x, x + width - 1, y + height - 1)
52 | }
53 |
54 | private def createHelperMatrix(isFlat: Boolean) = {
55 | val helperMatrix = Array.ofDim[Int](matrixHeight, matrixWidth)
56 | if (isFlat) {
57 | setupFlatHelperMatrix(helperMatrix)
58 | } else {
59 | updateHelperMatrixCols(helperMatrix, 0, matrixWidth - 1, matrixHeight - 1)
60 | }
61 | helperMatrix
62 | }
63 |
64 | private def setupFlatHelperMatrix(helperMatrix: Array[Array[Int]]) = {
65 | for {
66 | y <- 0 until matrixHeight
67 | x <- 0 until matrixWidth
68 | } {
69 | helperMatrix(y)(x) = matrixHeight - 1 - y
70 | }
71 | }
72 |
73 | private def updateHelperMatrixCols(helperMatrix: Array[Array[Int]], fromCol: Int, toCol: Int, fromRow: Int) = {
74 | val fromRowReal = if (fromRow == matrixHeight - 1) fromRow - 1 else fromRow
75 | for {
76 | y <- fromRowReal to 0 by -1
77 | x <- fromCol to toCol
78 | } {
79 | if (matrix(y)(x) == matrix(y + 1)(x)) {
80 | helperMatrix(y)(x) = helperMatrix(y + 1)(x) + 1
81 | } else {
82 | helperMatrix(y)(x) = 0
83 | }
84 | }
85 | }
86 |
87 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/test/ContainerLoaderTest.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading.test
2 |
3 | import ea.containerloading._
4 | import ea.containerloading.vis._
5 |
6 | import javax.vecmath._
7 | import javax.media.j3d._
8 | import scala.collection.JavaConversions._
9 |
10 | import org.junit._
11 | import Assert._
12 |
13 | class ContainerLoaderTest {
14 |
15 | @Test
16 | def loadWithLayerApproach() = {
17 |
18 | val container = Container(Dimension3D(10, 10, 10))
19 | val r = BoxRotation(false, false, false)
20 | val boxLoadingOrder = List(
21 | (Box(1, Dimension3D(5, 5, 5)), r),
22 | (Box(2, Dimension3D(5, 5, 5)), r),
23 | (Box(3, Dimension3D(5, 5, 5)), r),
24 | (Box(4, Dimension3D(5, 5, 5)), r),
25 | (Box(5, Dimension3D(5, 5, 5)), r),
26 | (Box(6, Dimension3D(5, 5, 5)), r),
27 | (Box(7, Dimension3D(5, 5, 5)), r),
28 | (Box(8, Dimension3D(5, 5, 5)), r))
29 |
30 | val loadingResult = ContainerLoader.loadLayer(container, boxLoadingOrder)
31 | println(loadingResult.loadedBoxes.map(b => b.position))
32 | assertSame(0, loadingResult.skippedBoxes.length)
33 |
34 | val boxes: Seq[Bounds] = loadingResult.loadedBoxes.map(box =>
35 | new BoundingBox(
36 | new Point3d(box.position.x, box.position.y, box.position.z),
37 | new Point3d(
38 | box.position.x + box.box.size.width,
39 | box.position.y + box.box.size.height,
40 | box.position.z + box.box.size.depth)))
41 |
42 | for (box <- boxes) {
43 | val otherBoxes = (boxes filterNot (_ == box)).toArray
44 | assertFalse(box.intersect(otherBoxes))
45 | }
46 |
47 | CandidateViewer.showCandidate(loadingResult)
48 | }
49 |
50 | }
--------------------------------------------------------------------------------
/src/ea/containerloading/test/HelpersTest.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading.test
2 |
3 | import ea.containerloading._
4 |
5 | import org.junit._
6 | import Assert._
7 |
8 | class HelpersTest {
9 |
10 | @Test
11 | def findFlatSurfaces() = {
12 |
13 | val layer = Array(
14 | Array(0, 0, 0, 0, 0, 0, 0),
15 | Array(4, 0, 2, 0, 0, 0, 0),
16 | Array(0, 0, 0, 0, 0, 0, 0),
17 | Array(0, 1, 1, 5, 0, 0, 0))
18 |
19 | val surfaceFinder = new SurfaceFinder(layer)
20 | val positions = surfaceFinder.findFlatSurfaces(3, 3, 0)
21 |
22 | assertSame(3, positions.length)
23 | assert(Set(Position2D(3, 0), Position2D(4, 0), Position2D(4, 1)).subsetOf(positions.toSet))
24 | }
25 |
26 | }
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/ea/containerloading/vis/CandidateViewer.scala:
--------------------------------------------------------------------------------
1 | package ea.containerloading.vis
2 |
3 | import ea.containerloading._
4 | import com.sun.j3d.utils.universe._
5 | import com.sun.j3d.utils.geometry.{ Box => gBox }
6 | import com.sun.j3d.utils.behaviors.mouse._
7 | import javax.media.j3d._
8 | import javax.vecmath._
9 |
10 | object CandidateViewer {
11 |
12 | def showCandidate(loaded: LoadedContainer) = {
13 |
14 | val universe = new SimpleUniverse
15 | universe.getViewer.getView.setTransparencySortingPolicy(View.TRANSPARENCY_SORT_GEOMETRY)
16 | universe.getViewer.getView.setDepthBufferFreezeTransparent(false)
17 |
18 | val scene = new BranchGroup
19 | setupMouseNavigation(universe, scene)
20 |
21 | // lighten up
22 | val lightColor = new Color3f(0.3f, 0.3f, 0.3f)
23 | val bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0), 100.0)
24 |
25 | val light1 = new AmbientLight
26 | light1.setInfluencingBounds(bounds)
27 | scene.addChild(light1)
28 |
29 | val light2Direction = new Vector3f(4.0f, -7.0f, -12.0f)
30 | val light2 = new DirectionalLight(lightColor, light2Direction)
31 | light2.setInfluencingBounds(bounds)
32 | scene.addChild(light2)
33 |
34 | // draw container
35 | val norm: Float = loaded.container.size.width max
36 | loaded.container.size.depth max
37 | loaded.container.size.height
38 |
39 | val appearance = new Appearance
40 | appearance.setColoringAttributes(new ColoringAttributes(1.0f, 1.0f, 1.0f, ColoringAttributes.NICEST))
41 |
42 | val polyAttrbutes = new PolygonAttributes
43 | polyAttrbutes.setPolygonMode(PolygonAttributes.POLYGON_LINE)
44 | polyAttrbutes.setCullFace(PolygonAttributes.CULL_NONE)
45 | appearance.setPolygonAttributes(polyAttrbutes)
46 |
47 | val container = new gBox(
48 | // jeweils /2, da Box die Hälfte der Breite/Höhe/Tiefe erwartet
49 | // (wie bei Kreis -> statt Durchmesser, Radius)
50 | // außerdem minimale Vergrößerung um 0.0001, damit die äußeren Boxen
51 | // nicht das weiße Drahtgitter vom Container übermalen
52 | (loaded.container.size.width / norm) / 2 + 0.0001f,
53 | (loaded.container.size.height / norm) / 2 + 0.0001f,
54 | (loaded.container.size.depth / norm) / 2 + 0.0001f, appearance)
55 |
56 | scene.addChild(container)
57 |
58 | // add everything to universe
59 | universe.addBranchGraph(scene)
60 |
61 | // reset view
62 | universe.getViewingPlatform.setNominalViewingTransform
63 |
64 | val rng = new scala.util.Random
65 | // draw boxes
66 | for (box <- loaded.loadedBoxes.reverse) {
67 | val boxAppearance = new Appearance
68 | val color = new Color3f(rng.nextFloat, rng.nextFloat, rng.nextFloat)
69 | val material = new Material
70 | material.setAmbientColor(color)
71 | boxAppearance.setMaterial(material)
72 | boxAppearance.setColoringAttributes(new ColoringAttributes(color, ColoringAttributes.NICEST))
73 | boxAppearance.setTransparencyAttributes(new TransparencyAttributes(TransparencyAttributes.NICEST, 0.3f))
74 |
75 | val boxPAppearance = new Appearance
76 | boxPAppearance.setColoringAttributes(boxAppearance.getColoringAttributes)
77 | boxPAppearance.setPolygonAttributes(polyAttrbutes)
78 |
79 | val boxSize = box.rotation.rotateDimensions(box.box.size)
80 | val gBox = new gBox((boxSize.width / norm) / 2, (boxSize.height / norm) / 2, (boxSize.depth / norm) / 2, boxAppearance)
81 | val gBoxPolygon = new gBox((boxSize.width / norm) / 2, (boxSize.height / norm) / 2, (boxSize.depth / norm) / 2, boxPAppearance)
82 |
83 | val transform = new Transform3D
84 | transform.setTranslation(
85 | new Vector3f(
86 | gBox.getXdimension // move box edge to origin
87 | - container.getXdimension // move box edge to container edge
88 | + box.position.x / norm, // move box to final position
89 | gBox.getYdimension - container.getYdimension + box.position.y / norm,
90 | gBox.getZdimension - container.getZdimension + box.position.z / norm))
91 | val tg = new TransformGroup(transform)
92 | tg.addChild(gBox)
93 | tg.addChild(gBoxPolygon)
94 |
95 | val boxScene = new BranchGroup
96 | boxScene.addChild(tg)
97 | universe.addBranchGraph(boxScene)
98 | Thread.sleep(500)
99 | }
100 | println("all boxes were drawn")
101 | }
102 |
103 | private def setupMouseNavigation(universe: SimpleUniverse, scene: BranchGroup) = {
104 |
105 | val vpTrans = universe.getViewingPlatform.getViewPlatformTransform
106 | val mouseBounds = new BoundingSphere(new Point3d, 1000.0)
107 |
108 | val mouseRotate = new MouseRotate(MouseBehavior.INVERT_INPUT)
109 | mouseRotate.setTransformGroup(vpTrans)
110 | mouseRotate.setSchedulingBounds(mouseBounds)
111 | scene.addChild(mouseRotate)
112 |
113 | val mouseTranslate = new MouseTranslate(MouseBehavior.INVERT_INPUT)
114 | mouseTranslate.setTransformGroup(vpTrans)
115 | mouseTranslate.setSchedulingBounds(mouseBounds)
116 | scene.addChild(mouseTranslate)
117 |
118 | val mouseZoom = new MouseZoom(MouseBehavior.INVERT_INPUT)
119 | mouseZoom.setTransformGroup(vpTrans)
120 | mouseZoom.setSchedulingBounds(mouseBounds)
121 | scene.addChild(mouseZoom)
122 | }
123 | }
--------------------------------------------------------------------------------
/src/ea/watchmaker/Implicits.scala:
--------------------------------------------------------------------------------
1 | package ea.watchmaker
2 |
3 | import org.uncommons.watchmaker.framework._
4 |
5 | object Implicits {
6 |
7 | implicit def closure2Observer[T](listener: (PopulationData[_ <: T]) => Unit): EvolutionObserver[T] =
8 | new EvolutionObserver[T] {
9 | def populationUpdate(data: PopulationData[_ <: T]) = {
10 | listener(data)
11 | }
12 | }
13 |
14 | }
--------------------------------------------------------------------------------
/src/org/uncommons/watchmaker/framework/operators/ListOrderCrossover2.java:
--------------------------------------------------------------------------------
1 | //=============================================================================
2 | // Copyright 2006-2010 Daniel W. Dyer
3 | //
4 | // Licensed under the Apache License, Version 2.0 (the "License");
5 | // you may not use this file except in compliance with the License.
6 | // You may obtain a copy of the License at
7 | //
8 | // http://www.apache.org/licenses/LICENSE-2.0
9 | //
10 | // Unless required by applicable law or agreed to in writing, software
11 | // distributed under the License is distributed on an "AS IS" BASIS,
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | // See the License for the specific language governing permissions and
14 | // limitations under the License.
15 | //=============================================================================
16 | package org.uncommons.watchmaker.framework.operators;
17 |
18 | import java.util.ArrayList;
19 | import java.util.HashMap;
20 | import java.util.List;
21 | import java.util.Map;
22 | import java.util.Random;
23 | import org.uncommons.maths.number.ConstantGenerator;
24 | import org.uncommons.maths.number.NumberGenerator;
25 | import org.uncommons.maths.random.Probability;
26 |
27 | /**
28 | * Rekombination: PMX mit linkem Element = 0
29 | *
30 | *
31 | * Implements ordered cross-over between arbitrary lists. The algorithm is
32 | * the Partially Mapped Cross-over (PMX) algorithm.
33 | * @param The component type of the lists that are combined.
34 | * @author Daniel Dyer
35 | */
36 | public class ListOrderCrossover2 extends AbstractCrossover>
37 | {
38 | /**
39 | * Creates a cross-over operator with a cross-over probability of 1.
40 | */
41 | public ListOrderCrossover2()
42 | {
43 | this(Probability.ONE);
44 | }
45 |
46 |
47 | /**
48 | * Creates a cross-over operator with the specified cross-over probability.
49 | * @param crossoverProbability The probability that cross-over will be performed
50 | * for any given pair.
51 | */
52 | public ListOrderCrossover2(Probability crossoverProbability)
53 | {
54 | super(2, // Requires exactly two cross-over points.
55 | crossoverProbability);
56 | }
57 |
58 |
59 | /**
60 | * Creates a cross-over operator where cross-over may or may not be applied to a
61 | * given pair of parents depending on the {@code crossoverProbability}.
62 | * @param crossoverProbabilityVariable The probability that, once selected,
63 | * a pair of parents will be subjected to cross-over rather than
64 | * being copied, unchanged, into the output population.
65 | */
66 | public ListOrderCrossover2(NumberGenerator crossoverProbabilityVariable)
67 | {
68 | super(new ConstantGenerator(2), // Requires exactly two cross-over points.
69 | crossoverProbabilityVariable);
70 | }
71 |
72 |
73 |
74 | /**
75 | * {@inheritDoc}
76 | */
77 | @Override
78 | protected List> mate(List parent1,
79 | List parent2,
80 | int numberOfCrossoverPoints,
81 | Random rng)
82 | {
83 | assert numberOfCrossoverPoints == 2 : "Expected number of cross-over points to be 2.";
84 |
85 | if (parent1.size() != parent2.size())
86 | {
87 | throw new IllegalArgumentException("Cannot perform cross-over with different length parents.");
88 | }
89 |
90 | List offspring1 = new ArrayList(parent1); // Use a random-access list for performance.
91 | List offspring2 = new ArrayList(parent2);
92 |
93 | // Change by neo
94 | int point1 = 0; //rng.nextInt(parent1.size());
95 | int point2 = rng.nextInt(parent1.size());
96 |
97 | int length = point2 - point1;
98 | if (length < 0)
99 | {
100 | length += parent1.size();
101 | }
102 |
103 | Map mapping1 = new HashMap(length * 2); // Big enough map to avoid re-hashing.
104 | Map mapping2 = new HashMap(length * 2);
105 | for (int i = 0; i < length; i++)
106 | {
107 | int index = (i + point1) % parent1.size();
108 | T item1 = offspring1.get(index);
109 | T item2 = offspring2.get(index);
110 | offspring1.set(index, item2);
111 | offspring2.set(index, item1);
112 | mapping1.put(item1, item2);
113 | mapping2.put(item2, item1);
114 | }
115 |
116 | checkUnmappedElements(offspring1, mapping2, point1, point2);
117 | checkUnmappedElements(offspring2, mapping1, point1, point2);
118 |
119 | List> result = new ArrayList>(2);
120 | result.add(offspring1);
121 | result.add(offspring2);
122 | return result;
123 | }
124 |
125 |
126 | /**
127 | * Checks elements that are outside of the partially mapped section to
128 | * see if there are any duplicate items in the list. If there are, they
129 | * are mapped appropriately.
130 | */
131 | private void checkUnmappedElements(List offspring,
132 | Map mapping,
133 | int mappingStart,
134 | int mappingEnd)
135 | {
136 | for (int i = 0; i < offspring.size(); i++)
137 | {
138 | if (!isInsideMappedRegion(i, mappingStart, mappingEnd))
139 | {
140 | T mapped = offspring.get(i);
141 | while (mapping.containsKey(mapped))
142 | {
143 | mapped = mapping.get(mapped);
144 | }
145 | offspring.set(i, mapped);
146 | }
147 | }
148 | }
149 |
150 |
151 | /**
152 | * Checks whether a given list position is within the partially mapped
153 | * region used for cross-over.
154 | * @param position The list position to check.
155 | * @param startPoint The starting index (inclusive) of the mapped region.
156 | * @param endPoint The end index (exclusive) of the mapped region.
157 | * @return True if the specified position is in the mapped region, false
158 | * otherwise.
159 | */
160 | private boolean isInsideMappedRegion(int position,
161 | int startPoint,
162 | int endPoint)
163 | {
164 | boolean enclosed = (position < endPoint && position >= startPoint);
165 | boolean wrapAround = (startPoint > endPoint && (position >= startPoint || position < endPoint));
166 | return enclosed || wrapAround;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------