diff --git a/README.md b/README.md index 21e2fe4..ecc7929 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,10 @@ Solver for the Electric Vehicle Routing Problem with Time Windows.

- +
+ Image Source

-[Image Source](http://ls11-www.cs.tu-dortmund.de/people/chimani/VehicleRouting/) - ## Problem description In the E-VRPTW, a set of customers must be served by a fleet of battery electric vehicles (BEV). The E-VRPTW extends the well-know [VRPTW](https://en.wikipedia.org/wiki/Vehicle_routing_problem), @@ -20,7 +19,7 @@ More formal, the EVRPTW is defined on a complete directed graph G = (V,A) consisting of a set of depot, customer, and recharging station nodes and a set of edges. Each edge _(i, j)_ has a distance _dij_ and a travel time _tij_ associated and each traveled edge consumes the amount of _r_ x _dij_ of the remaining battery charge of the vehicle, -where _h_ denotes the constant charge consumption rate. Furthermore, a set of homogeneous +where _r_ denotes the constant charge consumption rate. Furthermore, a set of homogeneous vehicles is given, where each vehicle has a maximal capacity of _C_ and is located, full loaded at the depot. Each node has a positive demand _qi_, a service time _si_ and a time window [ _ei_, _li_ ] assigned. The service must start within the given time window (thus, @@ -34,13 +33,31 @@ served within their given time window. All routes must begin and end in the depo the vehicle capacity and battery capacity must be respected. The objective function is to minimize the total traveled distance. +## Implementation Details + +Our E-VRPTW Solver consists of two parts: +1. Construction heuristic: [Time-Oriented, Nearest-Neighbor Heuristic](https://pubsonline.informs.org/doi/abs/10.1287/opre.35.2.254?journalCode=opre) (Solomon 1987) +2. Metaheuristic: For this part, we tried to implement a Hybrid VNS/TS metaheuristic proposed in the paper [The Electric Vehicle-Routing Problem with Time Windows and Recharging Stations](https://pubsonline.informs.org/doi/10.1287/trsc.2013.0490), +however our solution is not the most efficient one and requires further optimizations (especially in route representation) + +Model objects like `EVRPTWInstance`, `Customer`, `Node` have been copied from the [E-VRPTW Solution Verifier (Java)](https://github.com/ghiermann/evrptw-verifier) implemented by @ghiermann. + +See our [presentation slides](https://docs.google.com/presentation/d/1WnySkapfZkM57kC_8XTIKsyNZOhBhiNAdNTUWY201tQ/edit?usp=sharing) for more details. + ## Usage -// to be written +See Main.kt for instance/plotting/performance-recording customizations. + ## Contributions Special thanks to my dear friend and colleague [David Molnar](https://github.com/dmolnar99) -for participating in this project. +for participating in this project: + + + +| [
David Molnar](https://github.com/dmolnar99)
[🤔](#ideas "Ideas and Planning") [💻](https://github.com/fuvidani/clickbait-defeater/commits?author=dmolnar99 "Code") [⚠️](https://github.com/fuvidani/clickbait-defeater/commits?author=dmolnar99 "Tests") | +| :---: | + ## License This project is licensed under the [MIT License](https://opensource.org/licenses/MIT). Feel free to diff --git a/build.gradle b/build.gradle index e16ae2b..13b54cf 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'kotlin-allopen' apply plugin: 'application' group ='at.ac.tuwien.otl' -version = '1.0-SNAPSHOT' +version = '1.0.0' mainClassName = 'at.ac.tuwien.otl.Main' repositories { @@ -32,6 +32,8 @@ dependencies { compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8" ktlint "com.github.shyiko:ktlint:0.23.0" compile fileTree(dir: 'libs', include: '*.jar') + compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.7' + compile group: 'org.graphstream', name: 'gs-core', version: '1.3' testImplementation 'org.junit.jupiter:junit-jupiter-api:5.0.3' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.0.3' diff --git a/plots/c103_21.png b/plots/c103_21.png new file mode 100644 index 0000000..7881c43 Binary files /dev/null and b/plots/c103_21.png differ diff --git a/plots/c105_21.png b/plots/c105_21.png new file mode 100644 index 0000000..3254cb4 Binary files /dev/null and b/plots/c105_21.png differ diff --git a/plots/c204_21.png b/plots/c204_21.png new file mode 100644 index 0000000..1b14e4f Binary files /dev/null and b/plots/c204_21.png differ diff --git a/plots/r102_21.png b/plots/r102_21.png new file mode 100644 index 0000000..7c9ff5a Binary files /dev/null and b/plots/r102_21.png differ diff --git a/plots/r107_21.png b/plots/r107_21.png new file mode 100644 index 0000000..0574f30 Binary files /dev/null and b/plots/r107_21.png differ diff --git a/plots/r205_21.png b/plots/r205_21.png new file mode 100644 index 0000000..dbc44d9 Binary files /dev/null and b/plots/r205_21.png differ diff --git a/plots/r211_21.png b/plots/r211_21.png new file mode 100644 index 0000000..31754d4 Binary files /dev/null and b/plots/r211_21.png differ diff --git a/plots/rc101_21.png b/plots/rc101_21.png new file mode 100644 index 0000000..916c4df Binary files /dev/null and b/plots/rc101_21.png differ diff --git a/plots/rc106_21.png b/plots/rc106_21.png new file mode 100644 index 0000000..ecfe06d Binary files /dev/null and b/plots/rc106_21.png differ diff --git a/plots/rc203_21.png b/plots/rc203_21.png new file mode 100644 index 0000000..ac3099d Binary files /dev/null and b/plots/rc203_21.png differ diff --git a/solutions/c103_21_sol.txt b/solutions/c103_21_sol.txt index 14b1086..87c5966 100644 --- a/solutions/c103_21_sol.txt +++ b/solutions/c103_21_sol.txt @@ -1,32 +1,14 @@ # solution for c103_21 -2249.872316017405 -D0, C20, C21, C22, C24, C29, C30, C26, C7, C3, D0 -D0, C67, C65, C63, C62, C69, C68, C64, C41, C42, C44, D0 -D0, C23, C25, C27, C28, C34, C36, D0 -D0, C75, C1, C2, C4, C6, C9, D0 -D0, C46, C45, C48, C50, C51, C52, C47, C43, D0 -D0, C90, C89, C88, C85, C84, C83, C86, C87, C91, D0 -D0, C74, C72, D0 -D0, C40, C49, D0 -D0, C10, C11, C5, D0 -D0, C66, D0 -D0, C8, D0 -D0, C98, C95, C96, C99, D0 -D0, C32, C31, C33, D0 -D0, C61, D0 -D0, C17, C18, C15, D0 -D0, C59, C55, D0 -D0, C14, D0 -D0, C12, D0 -D0, C82, D0 -D0, S12, C39, C38, C37, C35, D0 -D0, S4, C94, C93, C92, C97, C100, D0 -D0, C13, D0 -D0, C19, D0 -D0, S7, C16, D0 -D0, C57, D0 -D0, S16, C53, C56, C54, D0 -D0, S14, C60, C58, D0 -D0, S20, C78, C79, C77, C76, C81, D0 -D0, S18, C80, D0 -D0, S20, C73, C70, C71, D0 +1040.6671460064022 +D0, C98, C96, C95, C94, C92, C93, C97, C100, C99, S3, D0 +D0, S0, C52, C32, C33, C29, C24, C22, C21, D0 +D0, S0, C91, C89, C88, C85, C84, C82, C83, C86, C87, C90, D0 +D0, S1, C73, C70, C71, C76, C78, C81, S19, C63, D0 +D0, C41, C40, C44, C45, C48, C51, C50, C47, C43, C46, C49, C42, D0 +D0, C59, C60, C58, C56, C53, S16, C54, C55, C57, D0 +D0, S0, C20, C25, C27, C28, C26, C10, C11, C9, C8, D0 +D0, C23, C30, C34, C36, C39, C38, C37, C35, C31, S13, D0 +D0, C75, C1, C2, C4, C6, C7, C3, C5, D0 +D0, C67, C74, S19, C79, C77, C80, C72, C61, C62, D0 +D0, C65, C69, C68, C64, C66, D0 +D0, S7, C12, C14, C16, C19, C18, C17, C15, C13, D0 diff --git a/solutions/c105_21_sol.txt b/solutions/c105_21_sol.txt index f382e31..36cb148 100644 --- a/solutions/c105_21_sol.txt +++ b/solutions/c105_21_sol.txt @@ -1,30 +1,14 @@ # solution for c105_21 -2029.2143728128362 -D0, C20, C21, C25, C27, C30, C28, C26, C6, C7, D0 -D0, C67, C65, C62, C64, C68, C69, C48, C46, C47, C43, D0 -D0, C23, C22, C29, C32, C33, C31, D0 -D0, C63, C74, C40, C42, C44, C45, C51, C50, D0 -D0, C75, C1, C98, C95, D0 -D0, C24, C34, C36, D0 -D0, C41, C59, C55, D0 -D0, C90, C87, C86, C82, C83, C84, C85, C88, D0 -D0, C96, C2, C4, C3, D0 -D0, C12, C10, D0 -D0, S14, C60, C58, C56, C53, C54, C57, D0 -D0, C14, C11, D0 -D0, S7, C16, C19, C18, C17, C15, D0 -D0, S12, C39, C38, C37, C35, D0 -D0, S4, C94, C92, C93, C97, C100, C99, D0 -D0, S20, C73, C70, C79, C77, C81, D0 -D0, S20, C71, C76, C78, C72, D0 -D0, C5, D0 -D0, S18, C80, D0 -D0, C9, D0 -D0, C89, D0 -D0, C61, D0 -D0, C91, D0 -D0, C49, D0 -D0, C8, D0 -D0, C66, D0 -D0, C52, D0 -D0, C13, D0 +1034.4610777347145 +D0, S0, C41, C40, C42, C44, C45, C48, C46, C47, C43, D0 +D0, C90, C87, C86, C82, C83, C84, C85, C88, C89, C91, D0 +D0, C20, C24, C32, C33, C31, S11, C51, C50, C52, C49, D0 +D0, S0, C63, S19, C73, C70, C71, C76, C78, C81, S19, D0 +D0, S0, C21, C22, C25, C27, C30, C28, C26, C10, C11, C9, C8, D0 +D0, C59, C60, C58, C56, C53, S16, C54, C55, C57, D0 +D0, C65, C74, S19, C79, C77, C80, C72, C61, C66, D0 +D0, S0, S0, S7, C12, C14, C16, C19, C18, C17, C15, C13, D0 +D0, C23, C29, C34, C36, C39, C38, C37, C35, S13, D0 +D0, C75, C1, C2, C4, C6, C7, C3, C5, D0 +D0, C67, C62, C64, C68, C69, D0 +D0, S3, C98, C96, C95, C94, C92, C93, C97, C100, C99, D0 diff --git a/solutions/c204_21_sol.txt b/solutions/c204_21_sol.txt index 57dc2b1..c14908a 100644 --- a/solutions/c204_21_sol.txt +++ b/solutions/c204_21_sol.txt @@ -1,17 +1,6 @@ # solution for c204_21 -1553.1684430384391 -D0, C93, C5, C75, C2, C1, C99, C100, C97, C95, C94, C98, C7, C3, C4, C89, C91, C90, D0 -D0, C20, C22, C24, C30, C25, C9, C11, C13, C17, C19, C23, D0 -D0, C48, C43, C42, C41, C45, C50, C51, C47, C46, C44, C57, C55, C54, C59, D0 -D0, C67, C63, C62, C66, C69, C68, C65, C64, C61, C74, C87, C77, C88, D0 -D0, C8, C10, C12, C26, C28, C29, C27, D0 -D0, C84, C78, C85, C86, D0 -D0, C49, C53, C56, C58, C60, C52, D0 -D0, C34, C36, C37, C35, C33, C39, C38, C31, C32, C6, D0 -D0, C76, C71, C79, C96, D0 -D0, C73, C70, C81, D0 -D0, C83, C82, C92, D0 -D0, C15, C14, C16, C18, D0 -D0, C40, C72, D0 -D0, C80, D0 -D0, C21, D0 +656.6589467777569 +D0, S0, C8, C10, C11, C9, C13, C15, C12, C14, C16, C19, C18, C17, C25, C23, C26, S10, C28, C34, C36, C39, C38, C37, C35, C31, C33, C32, C6, C29, C30, C27, C24, C22, C21, D0 +D0, S0, C20, S0, C89, C4, C3, C7, C95, C94, C92, C97, C100, C99, C98, C1, C2, C75, C5, C93, D0 +D0, S0, S0, C90, C91, C88, C86, C84, C83, C82, C85, S20, C76, C71, C70, C73, C80, C79, C81, C78, C77, C87, C96, C74, C72, C61, C64, C62, C63, D0 +D0, C48, C43, C41, C42, C47, S13, C52, C50, C51, C45, C46, C44, C40, C60, C58, C56, C53, C54, C59, C57, C55, C49, C65, C68, C69, C66, C67, S0, D0 diff --git a/solutions/r102_21_sol.txt b/solutions/r102_21_sol.txt index 52d9457..edac6f5 100644 --- a/solutions/r102_21_sol.txt +++ b/solutions/r102_21_sol.txt @@ -1,41 +1,24 @@ # solution for r102_21 -2649.679439695821 -D0, C53, C28, C40, C21, C2, C13, D0 -D0, C94, C97, C98, C93, C100, C91, C59, D0 -D0, C89, C18, C60, C5, C99, C27, D0 -D0, C96, C6, C95, C87, D0 -D0, C1, C70, C10, C52, D0 -D0, C31, C62, C7, D0 -D0, C58, C22, C75, D0 -D0, C3, C81, C33, C50, D0 -D0, C74, C56, C72, D0 -D0, C68, C24, C80, D0 -D0, C61, C16, C85, C92, D0 -D0, C8, C45, C83, D0 -D0, C69, C26, D0 -D0, C55, C54, D0 -D0, S4, C9, C34, C78, C29, D0 -D0, S19, C25, C4, C73, D0 -D0, S8, C47, C36, C48, C82, D0 -D0, C84, D0 -D0, C12, D0 -D0, C79, C77, D0 -D0, C76, D0 -D0, S7, C19, C11, C63, D0 -D0, C88, D0 -D0, C17, D0 -D0, S14, C43, C15, C42, D0 -D0, S18, C39, C67, C23, D0 -D0, S6, C90, C30, D0 -D0, S6, C32, C20, C51, D0 -D0, S10, C46, D0 -D0, C37, D0 -D0, C57, D0 -D0, S12, C86, C44, D0 -D0, S4, C66, C71, D0 -D0, S11, C38, C14, D0 -D0, C41, D0 -D0, S4, C35, D0 -D0, S4, C65, S5, D0 -D0, S8, C64, S9, D0 -D0, S8, C49, D0 +1620.8181754580394 +D0, S0, C18, C8, C46, C36, S8, C47, C48, C82, D0 +D0, C70, C30, S0, D0 +D0, C19, S8, S8, C64, C49, S8, D0 +D0, C89, C5, C84, C45, C83, S9, D0 +D0, C1, C66, C71, S4, C20, D0 +D0, C21, C74, C56, S17, C75, C72, C73, C53, D0 +D0, C6, D0 +D0, S19, C25, C55, C54, C4, C26, D0 +D0, S14, C43, C15, C42, C87, C57, C2, D0 +D0, C50, C69, C27, D0 +D0, C31, C10, C32, C90, S6, C63, C62, C88, D0 +D0, C28, C68, C24, C80, C12, D0 +D0, C40, C58, D0 +D0, C61, C16, C91, C85, C93, C99, D0 +D0, C96, C59, C98, C92, C95, C13, D0 +D0, C3, C33, C79, C77, C76, D0 +D0, C94, S11, C38, C14, C100, C97, D0 +D0, S18, C39, C67, C23, S17, C22, C41, D0 +D0, S7, C11, C7, C52, D0 +D0, S0, C81, S4, S4, C34, C78, C29, D0 +D0, S4, C65, C35, S4, C9, C51, D0 +D0, C60, S10, C17, C86, C44, C37, D0 diff --git a/solutions/r107_21_sol.txt b/solutions/r107_21_sol.txt index 7ebe9cb..0156d75 100644 --- a/solutions/r107_21_sol.txt +++ b/solutions/r107_21_sol.txt @@ -1,38 +1,16 @@ # solution for r107_21 -2534.1151825762677 -D0, C53, C58, C13, C94, C95, C97, C59, C93, C98, C100, C6, D0 -D0, C28, C26, C40, C21, C74, C22, C73, D0 -D0, C89, C18, C60, C83, C5, C84, D0 -D0, C50, C33, C81, C9, D0 -D0, C96, C61, C16, C91, C85, C92, D0 -D0, C31, C70, C10, C62, C88, C27, D0 -D0, C2, C75, C72, D0 -D0, C1, C51, C3, C77, D0 -D0, C80, C68, C24, C54, D0 -D0, C52, C48, C82, D0 -D0, C69, C30, D0 -D0, C45, C8, D0 -D0, C7, C19, D0 -D0, C55, C4, D0 -D0, C14, C99, D0 -D0, S19, C25, C67, D0 -D0, S7, C11, C63, C32, C90, D0 -D0, S14, C43, C15, C42, C87, C57, D0 -D0, S8, C47, C49, D0 -D0, C12, D0 -D0, C56, D0 -D0, C79, C78, D0 -D0, C20, D0 -D0, C76, D0 -D0, S4, C34, C35, D0 -D0, S17, C23, C39, C41, D0 -D0, C17, D0 -D0, C37, D0 -D0, C29, D0 -D0, C44, D0 -D0, S12, C86, D0 -D0, S8, C36, C46, D0 -D0, S4, C66, C71, D0 -D0, S11, C38, D0 -D0, S4, C65, D0 -D0, S8, C64, D0 +1265.6463007559603 +D0, C83, C8, C45, C17, C84, S12, C86, C44, C100, C37, D0 +D0, C96, C93, C61, C16, C85, C91, S11, C38, C14, D0 +D0, C94, C95, C97, C92, C98, C59, C99, C6, D0 +D0, C89, C5, C60, C7, C52, D0 +D0, C53, C40, C21, C56, S17, C75, C72, C73, C74, C22, C41, D0 +D0, S0, C1, C10, C62, S7, C63, C90, C32, C30, C70, D0 +D0, S0, C18, C48, C47, S8, C49, C64, C11, S7, C88, D0 +D0, C20, S4, C65, C66, S3, C69, C27, D0 +D0, C26, C54, C24, C29, S1, C80, C12, D0 +D0, S17, C23, C39, C67, C4, S19, C25, C55, D0 +D0, S0, C31, C19, S8, C36, C46, C82, D0 +D0, C50, C51, C33, C81, C78, C34, C35, S4, C71, C9, D0 +D0, C58, C2, S14, C15, C43, C42, C57, C87, C13, D0 +D0, C28, C68, C79, C3, C77, C76, D0 diff --git a/solutions/r205_21_sol.txt b/solutions/r205_21_sol.txt index 2a44227..bee7ee9 100644 --- a/solutions/r205_21_sol.txt +++ b/solutions/r205_21_sol.txt @@ -1,10 +1,9 @@ # solution for r205_21 -1364.418713755443 -D0, C27, C28, C53, C58, C40, C73, C22, C41, C56, C4, C12, C3, C29, C24, C51, D0 -D0, C13, C95, C59, C99, C92, C100, C98, C37, C91, C85, C61, C16, C44, C86, C38, C17, C45, C82, C62, C11, C63, D0 -D0, C26, C21, C72, C74, C75, C57, C87, C97, C94, C6, C89, C60, C83, C93, C96, C15, D0 -D0, C52, C7, C88, C31, C69, C50, C30, C20, C66, C71, C9, C35, C47, D0 -D0, C1, C70, C10, C90, C32, C64, C49, C36, D0 -D0, C76, C77, C79, C81, C33, C78, C34, C68, C55, C39, C43, C84, C18, D0 -D0, C2, C42, C14, C5, C8, C46, C48, C19, D0 -D0, C80, C54, C25, C67, C23, C65, D0 +1009.4129651594476 +D0, S0, S0, D0 +D0, C52, C7, C88, C31, C69, C30, C20, C50, D0 +D0, S0, C13, C95, C92, C59, C99, C5, C8, C46, C48, C19, S5, C10, C32, C90, C63, C64, C49, C36, C47, C11, C62, D0 +D0, S0, C70, C1, C76, C77, C79, C33, C81, C78, C34, C65, C66, C71, C35, C9, C51, C3, C29, C24, C12, D0 +D0, C26, C54, C80, C68, C28, C27, C53, C58, D0 +D0, C21, C72, C23, C67, C25, C55, C39, S17, C75, C74, C57, S14, C43, C87, C97, C37, C98, C93, C85, C16, C44, C38, C86, C17, C45, C82, D0 +D0, C2, C42, C14, C100, C91, C61, C84, C83, C60, C18, C89, C6, C94, C96, C15, C41, C22, C56, C4, C73, C40, S0, D0 diff --git a/solutions/r211_21_sol.txt b/solutions/r211_21_sol.txt index 0dc26c8..bf10cbc 100644 --- a/solutions/r211_21_sol.txt +++ b/solutions/r211_21_sol.txt @@ -1,7 +1,6 @@ # solution for r211_21 -1062.1554791026529 -D0, C27, C28, C26, C21, C72, C74, C75, C56, C23, C39, C25, C55, C4, C73, C22, C41, C57, C87, C97, C37, C98, C85, C61, C16, C91, C93, C96, C94, C6, C89, C60, C45, C17, C86, D0 -D0, C53, C58, C13, C95, C92, C59, C99, C5, C84, C83, C8, C46, C48, C19, C11, C63, C90, C32, C30, C20, C51, C9, C35, C71, C66, C10, C62, C31, C69, C3, C12, C40, D0 -D0, C52, C18, C7, C88, C70, C1, C50, C76, C77, C68, C80, C54, C24, C29, C78, C33, C82, C47, C36, C49, C64, D0 -D0, C2, C42, C100, C14, C44, C38, C43, C15, D0 -D0, C79, C81, C34, C65, C67, D0 +789.6589580510812 +D0, S0, C28, C26, C21, C72, C23, C67, C39, C25, C55, C54, C68, C76, C77, C3, C79, C29, C24, C80, C12, C4, C56, C75, C41, C22, C74, C73, C40, D0 +D0, C52, C18, C7, C88, C31, C70, C1, C50, C33, C81, C78, C34, C35, C65, C71, C66, C20, C9, C51, C30, C69, C27, S0, S0, D0 +D0, S0, C53, C58, C2, C42, C14, C44, C86, C38, C43, C15, C57, C87, C97, C96, C94, C6, D0 +D0, S0, C13, C95, C92, C37, C98, C100, C91, C16, C61, C85, C93, C59, C99, C5, C84, C8, C46, C19, C11, C62, C10, C32, C90, C63, C64, C49, C36, C47, C48, C82, C45, C17, C83, C60, C89, D0 diff --git a/solutions/rc101_21_sol.txt b/solutions/rc101_21_sol.txt index ac24d31..fc4fa63 100644 --- a/solutions/rc101_21_sol.txt +++ b/solutions/rc101_21_sol.txt @@ -1,43 +1,21 @@ # solution for rc101_21 -3435.890895175693 -D0, C69, C55, C100, C70, C68, C81, C54, D0 -D0, C80, C91, C95, C56, C92, C96, D0 -D0, C66, C65, C99, C52, C86, D0 -D0, C90, C98, D0 -D0, C53, C88, C82, C83, D0 -D0, C93, C71, C72, C41, D0 -D0, C64, C84, C51, D0 -D0, C57, C74, D0 -D0, C62, C85, D0 -D0, C94, D0 -D0, C61, D0 -D0, C42, C44, C43, D0 -D0, C14, C47, C12, D0 -D0, C2, C6, C7, D0 -D0, C78, C73, D0 -D0, C10, C11, C15, D0 -D0, C4, C45, C46, D0 -D0, C13, C9, D0 -D0, C79, C60, D0 -D0, C87, D0 -D0, S16, C19, C49, C22, C24, C20, C48, D0 -D0, C67, D0 -D0, C63, D0 -D0, S4, C37, C38, C39, C40, D0 -D0, S11, C59, C58, D0 -D0, S17, C76, C89, D0 -D0, S4, C36, C35, D0 -D0, C8, D0 -D0, C3, D0 -D0, C50, D0 -D0, S16, C18, C23, C25, C21, D0 -D0, S7, C5, C1, D0 -D0, S19, C32, C30, C29, D0 -D0, S12, C17, D0 -D0, S12, C16, D0 -D0, S14, C77, D0 -D0, S20, C31, C26, C28, D0 -D0, S18, C33, C34, D0 -D0, S11, C97, D0 -D0, S14, C75, S15, D0 -D0, S20, C27, D0 +1863.2108225038276 +D0, S17, C76, S16, C18, C23, C25, C21, C48, D0 +D0, S0, C95, S19, C31, C29, C27, C34, C67, D0 +D0, C62, S19, C32, C33, C94, D0 +D0, C66, C19, C49, C24, C22, C20, S15, S0, D0 +D0, C100, S7, C4, C45, C8, C3, C5, C46, C1, D0 +D0, C57, C59, C87, S11, C9, C97, D0 +D0, C64, C83, C82, C90, D0 +D0, C93, C71, S20, C26, C28, C30, C50, D0 +D0, C65, S13, C99, C52, C10, C11, C12, C98, D0 +D0, C68, C72, C54, C96, D0 +D0, C53, C14, C13, C47, C15, S12, C16, C17, D0 +D0, S3, C42, C44, C43, C40, C35, C61, D0 +D0, C85, C63, S17, C89, C51, D0 +D0, C55, C2, C6, C7, D0 +D0, C70, C88, D0 +D0, S0, S0, C69, C79, C78, C73, C60, S9, D0 +D0, C80, C91, C84, C56, C92, D0 +D0, S14, C58, C75, C77, C74, C86, S13, D0 +D0, S4, S4, C36, C37, C38, C39, C41, C81, D0 diff --git a/solutions/rc106_21_sol.txt b/solutions/rc106_21_sol.txt index 6d00810..f8b6604 100644 --- a/solutions/rc106_21_sol.txt +++ b/solutions/rc106_21_sol.txt @@ -1,38 +1,17 @@ # solution for rc106_21 -3078.24610349508 -D0, C69, C55, C100, C70, C68, C61, C81, D0 -D0, C80, C91, C95, C84, C56, C92, C94, C96, D0 -D0, C65, C66, C64, C57, C52, C82, C90, D0 -D0, C53, C88, C98, C60, D0 -D0, C93, C71, C72, C41, D0 -D0, C99, C87, C86, D0 -D0, C83, C24, C22, C20, D0 -D0, C62, C85, D0 -D0, C54, D0 -D0, C42, C44, C43, D0 -D0, C2, C4, C45, C46, C6, D0 -D0, C14, C47, C12, D0 -D0, C78, C73, D0 -D0, C10, C11, C15, D0 -D0, C67, C50, D0 -D0, C13, C9, D0 -D0, C79, D0 -D0, C63, D0 -D0, C7, C8, D0 -D0, S16, C19, C49, C18, C23, C25, C21, C48, D0 -D0, C38, C39, D0 -D0, S4, C37, C36, C35, C40, D0 -D0, C74, D0 -D0, S11, C59, C58, D0 -D0, C51, D0 -D0, S17, C76, C89, D0 -D0, C3, D0 -D0, C1, D0 -D0, S7, C5, D0 -D0, S12, C17, C16, D0 -D0, S14, C77, D0 -D0, S19, C32, C30, C31, C29, D0 -D0, S18, C33, C28, C34, D0 -D0, S11, C97, D0 -D0, S14, C75, S15, D0 -D0, S20, C26, C27, D0 +1508.3642291107653 +D0, S0, C68, D0 +D0, C62, S19, C33, C32, C30, C31, C34, C50, D0 +D0, S0, S3, C42, C44, C43, C39, C41, C72, C54, C96, C81, D0 +D0, S0, C66, C64, C84, C95, C91, C92, D0 +D0, C70, C6, C46, C8, C7, C60, S9, D0 +D0, C80, C38, C37, C36, C35, S4, C40, C61, D0 +D0, C65, C57, S14, S14, C77, C25, C23, C21, C48, D0 +D0, C85, C63, S17, C76, C89, C51, C56, D0 +D0, S16, C18, C19, C49, C20, C22, C24, C83, D0 +D0, C93, C71, S20, C27, C26, C28, C29, C67, C94, D0 +D0, S11, C87, C59, C75, C58, C74, C86, S13, D0 +D0, C69, S9, C79, C73, C78, C88, C98, C82, C90, D0 +D0, C53, C47, C14, C12, C11, C15, C16, S12, C17, D0 +D0, C55, C100, C2, C4, C45, C5, C3, S7, C1, D0 +D0, C99, C52, C10, C13, C9, S11, C97, D0 diff --git a/solutions/rc203_21_sol.txt b/solutions/rc203_21_sol.txt index 27596a9..561d48a 100644 --- a/solutions/rc203_21_sol.txt +++ b/solutions/rc203_21_sol.txt @@ -1,11 +1,10 @@ # solution for rc203_21 -1639.8953568370507 -D0, C80, C94, C93, C67, C62, C50, C34, C31, C32, C28, C26, C33, C63, C51, C49, C19, C48, C18, C21, C23, C25, C24, C22, C83, C91, D0 -D0, C69, C98, C53, C60, C55, C100, C70, C2, C6, C45, C5, C3, C7, C14, C47, C11, C10, C9, C13, C87, C86, C52, D0 -D0, C81, C95, C65, C99, C74, C59, C97, C75, C58, C57, C20, C56, C66, D0 -D0, C90, C79, C73, C78, C43, C35, C36, C44, C42, C40, C54, C61, C68, D0 -D0, C85, C76, C77, C16, C17, C82, C88, D0 -D0, C96, C41, C37, C72, C1, C8, C4, C46, C12, C15, D0 -D0, C38, C39, C71, C84, C89, D0 -D0, C64, C92, C27, D0 -D0, C30, C29, D0 +1000.4260910736659 +D0, S0, C69, C60, C79, C73, C78, C88, C98, C82, D0 +D0, C95, C85, C76, C51, C56, C91, C92, C80, D0 +D0, C81, C38, C41, C37, C35, C36, C40, C43, C44, C42, C39, C71, C93, S0, D0 +D0, S0, C90, C14, C47, C17, C11, C10, C9, C13, C16, C15, C12, C53, D0 +D0, C65, C99, C59, C97, C75, C58, C77, C25, C23, C21, C48, C18, C19, C49, C20, C22, C24, C57, C74, C87, C86, C52, C83, S15, C64, C66, D0 +D0, S0, S0, C96, C72, C54, C61, D0 +D0, C94, C67, C62, C50, C34, C32, C30, C31, C29, C27, C26, C28, C33, C89, C63, C84, D0 +D0, C55, C100, C2, C6, C7, C45, C1, C3, C5, C8, C46, C4, C70, C68, D0 diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/EVRPTWSolutionVisualizer.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/EVRPTWSolutionVisualizer.kt new file mode 100644 index 0000000..2276a3e --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/EVRPTWSolutionVisualizer.kt @@ -0,0 +1,102 @@ +package at.ac.tuwien.otl.evrptw + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import org.graphstream.graph.implementations.AbstractEdge +import org.graphstream.graph.implementations.MultiGraph +import org.graphstream.graph.implementations.MultiNode +import org.graphstream.ui.graphicGraph.GraphicEdge +import java.awt.Color +import java.io.File + +/** + *

About this class

+ + *

Description of this class

+ * + * @author David Molnar + * @version 1.0.0 + * @since 1.0.0 + */ +class EVRPTWSolutionVisualizer { + private var edgeIdCounter = 1000 + + fun visualizeSolution(instance: EVRPTWInstance) { + val solution = loadSolutionForInstance(instance) + plotSolution(instance, solution) + } + + private fun plotSolution(instance: EVRPTWInstance, solution: EVRPTWSolution) { + val graph = MultiGraph(instance.name) + graph.addAttribute("ui.screenshot", "plots/${instance.name}.png") + graph.addAttribute("ui.quality") + graph.addAttribute("ui.antialias") + + // plot depot + graph.addNode(instance.depot.id.toString()) + val depotNode = graph.getNode(instance.depot.id.toString()) + depotNode.setAttribute("xy", instance.getLocation(instance.depot).x, instance.getLocation(instance.depot).y) +// depotNode.setAttribute("ui.label", instance.depot.id) + depotNode.addAttribute("ui.style", "fill-color: red;") + + // plot recharge stations + for (station in instance.rechargingStations) { + graph.addNode(station.id.toString()) + val stationNode = graph.getNode(station.id.toString()) + stationNode.setAttribute("xy", station.location.x, station.location.y) +// stationNode.setAttribute("ui.label", station.name) + stationNode.addAttribute("ui.style", "fill-color: #2E8B57;") + } + + // plot customers + for (customer in instance.customers) { + graph.addNode(customer.id.toString()) + val customerNode = graph.getNode(customer.id.toString()) + customerNode.setAttribute("xy", customer.location.x, customer.location.y) +// customerNode.setAttribute("ui.label", customer.name) + } + + // plot routes + for (route in solution.routes) { + val edgeColor = Color((Math.random() * 0x1000000).toInt()) + for (i in 0 until route.size - 1) { + graph.addEdge((++edgeIdCounter).toString(), route[i].id, route[i + 1].id) + val edge = graph.getEdge(edgeIdCounter.toString()) + edge.setAttribute("ui.style", "size: 3px; fill-color: rgb(${edgeColor.red}, ${edgeColor.green}, ${edgeColor.blue});") + } + } + + graph.display(false) + } + + private fun loadSolutionForInstance(instance: EVRPTWInstance): EVRPTWSolution { + File("solutions/" + instance.name + "_sol.txt").bufferedReader().use { reader -> + reader.readLine() // skip first line + + val cost = reader.readLine().toDouble() + + val routes = mutableListOf>() + + val iter = reader.lines().iterator() + while (iter.hasNext()) { + val line = iter.next() + + val nodeStrings = line.replace(" ", "").split(",") + + val route = mutableListOf() + + for (nodeString in nodeStrings) { + when { + nodeString.startsWith("D") -> route.add(instance.depot) + nodeString.startsWith("S") -> route.add(instance.rechargingStationMap[nodeString] as EVRPTWInstance.Node) + else -> route.add(instance.customerMap[nodeString] as EVRPTWInstance.Node) + } + } + + routes.add(route) + } + + return EVRPTWSolution(instance, routes, cost) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/EVRPTWSolver.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/EVRPTWSolver.kt index 1b1d3d9..2e1327a 100644 --- a/src/main/kotlin/at/ac/tuwien/otl/evrptw/EVRPTWSolver.kt +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/EVRPTWSolver.kt @@ -3,6 +3,7 @@ package at.ac.tuwien.otl.evrptw import at.ac.tuwien.otl.evrptw.construction.IConstructionHeuristic import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.metaheuristic.IMetaHeuristic /** *

About this class

@@ -15,7 +16,9 @@ import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution */ class EVRPTWSolver { - fun solve(instance: EVRPTWInstance, constructionHeuristic: IConstructionHeuristic): EVRPTWSolution { - return constructionHeuristic.generateSolution(instance) + fun solve(instance: EVRPTWInstance, constructionHeuristic: IConstructionHeuristic, metaHeuristic: IMetaHeuristic): EVRPTWSolution { + val solution = constructionHeuristic.generateSolution(instance) + + return metaHeuristic.improveSolution(solution) } } \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/Executor.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/Executor.kt new file mode 100644 index 0000000..7f844e9 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/Executor.kt @@ -0,0 +1,27 @@ +package at.ac.tuwien.otl.evrptw + +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class Executor private constructor() { + + companion object { + private var customExecutorService: ExecutorService? = null + + fun getExecutorService(): ExecutorService { + if (customExecutorService == null) { + customExecutorService = Executors.newFixedThreadPool(10) + } + return customExecutorService!! + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/Main.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/Main.kt index d22a896..5aa47c9 100644 --- a/src/main/kotlin/at/ac/tuwien/otl/evrptw/Main.kt +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/Main.kt @@ -3,8 +3,8 @@ package at.ac.tuwien.otl.evrptw import at.ac.tuwien.otl.evrptw.construction.TimeOrientedNearestNeighbourHeuristic import at.ac.tuwien.otl.evrptw.verifier.EVRPTWRouteVerifier import at.ac.tuwien.otl.evrptw.loader.InstanceLoader -import java.util.HashSet import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance +import at.ac.tuwien.otl.evrptw.metaheuristic.HybridVnsTsMetaHeuristic import java.util.concurrent.TimeUnit /** @@ -23,14 +23,27 @@ class Main { private val testInstances = listOf("c101C10", "r102C10", "rc201C10") private val instances = listOf("c103_21", "c105_21", "c204_21", "r102_21", "r107_21", "r205_21", "r211_21", "rc101_21", "rc106_21", "rc203_21") private val constructionHeuristic = TimeOrientedNearestNeighbourHeuristic(false) + private val metaHeuristic = HybridVnsTsMetaHeuristic() private val solver = EVRPTWSolver() private const val rampUpRuns = 20 private const val measuredRuns = 30 + val instanceToInitTemperatureMap = mapOf( + instances[0] to 1237.55, + instances[1] to 995.23, + instances[2] to 841.04, + instances[3] to 1167.86, + instances[4] to 1354.36, + instances[5] to 399.49, + instances[6] to 296.37, + instances[7] to 1664.92, + instances[8] to 1638.65, + instances[9] to 675.69 + ) @JvmStatic fun main(args: Array) { // Ramp-up phase, ignore runtimes - for (j in 1..rampUpRuns) { + /*for (j in 1..rampUpRuns) { for (i in 0..9) { runAlgorithmOnInstance(i, false) } @@ -49,6 +62,24 @@ class Main { println("\nAvg. runtime for each instance across $measuredRuns runs") for (i in 0..9) { println("instanceId: $i, avg. runtime: ${TimeUnit.NANOSECONDS.toMillis(instanceRuntimeMap[i]!!.average().toLong())} ms") + }*/ +// println(Random().nextInt(1-1) + 1) + for (i in 6 until 7) { + runAlgorithmOnInstance(i, false) + } + Executor.getExecutorService().shutdown() + +// plotSolutions() + } + + private fun plotSolutions() { + val visualizer = EVRPTWSolutionVisualizer() + val instanceLoader = InstanceLoader() + + for (i in 0 until 10) { + val instanceString = instances[i] + val instance = instanceLoader.load(instanceString) + visualizer.visualizeSolution(instance) } } @@ -57,7 +88,7 @@ class Main { val instanceLoader = InstanceLoader() val instance = instanceLoader.load(instanceString) val start = System.nanoTime() - val solution = solver.solve(instance, constructionHeuristic) + val solution = solver.solve(instance, constructionHeuristic, metaHeuristic) val durationInNano = System.nanoTime() - start val nodesMissing = allNodesInRoutes(instance, solution.routes) diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWInstance.java b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWInstance.java index 5f3d01c..f4b6e51 100644 --- a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWInstance.java +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWInstance.java @@ -17,6 +17,9 @@ package at.ac.tuwien.otl.evrptw.dto; import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance.Node.TimeWindow; +import org.apache.commons.lang3.builder.EqualsBuilder; +import org.apache.commons.lang3.builder.HashCodeBuilder; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -160,7 +163,7 @@ public double getDemand(Node node) { return customers.get((int) node.id - (rechargingStations.size() + 1)).demand; } - private Node.Location getLocation(Node node) { + public Node.Location getLocation(Node node) { if(node.id == depot.id) return depot.location; else if(node.id > rechargingStations.size()) return customers .get((int) node.id - (rechargingStations.size() + 1)).location; @@ -186,6 +189,40 @@ public double getRechargingRate(Node node) { return rechargingStations.get(node.id - 1).rechargingRate; } + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof EVRPTWInstance)) return false; + + final EVRPTWInstance that = (EVRPTWInstance) o; + + return new EqualsBuilder() + .append(getName(), that.getName()) + .append(getDepot(), that.getDepot()) + .append(getCustomers(), that.getCustomers()) + .append(getCustomerMap(), that.getCustomerMap()) + .append(getRechargingStations(), that.getRechargingStations()) + .append(getRechargingStationMap(), that.getRechargingStationMap()) + .append(getNodes(), that.getNodes()) + .append(getVehicleType(), that.getVehicleType()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getName()) + .append(getDepot()) + .append(getCustomers()) + .append(getCustomerMap()) + .append(getRechargingStations()) + .append(getRechargingStationMap()) + .append(getNodes()) + .append(getVehicleType()) + .toHashCode(); + } + public static class Node { final int id; @@ -197,6 +234,26 @@ public int getId() { this.id = id; } + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof Node)) return false; + + final Node node = (Node) o; + + return new EqualsBuilder() + .append(getId(), node.getId()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getId()) + .toHashCode(); + } + public static class Location { final double x, y; @@ -212,6 +269,28 @@ public double getX() { public double getY() { return y; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof Location)) return false; + + final Location location = (Location) o; + + return new EqualsBuilder() + .append(getX(), location.getX()) + .append(getY(), location.getY()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getX()) + .append(getY()) + .toHashCode(); + } } public static class TimeWindow { @@ -229,6 +308,28 @@ public double getStart() { public double getEnd() { return end; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof TimeWindow)) return false; + + final TimeWindow that = (TimeWindow) o; + + return new EqualsBuilder() + .append(getStart(), that.getStart()) + .append(getEnd(), that.getEnd()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(getStart()) + .append(getEnd()) + .toHashCode(); + } } } @@ -270,6 +371,36 @@ public double getServiceTime() { return serviceTime; } + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof Customer)) return false; + + final Customer customer = (Customer) o; + + return new EqualsBuilder() + .appendSuper(super.equals(o)) + .append(getDemand(), customer.getDemand()) + .append(getServiceTime(), customer.getServiceTime()) + .append(getName(), customer.getName()) + .append(getLocation(), customer.getLocation()) + .append(getTimeWindow(), customer.getTimeWindow()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .appendSuper(super.hashCode()) + .append(getName()) + .append(getLocation()) + .append(getDemand()) + .append(getTimeWindow()) + .append(getServiceTime()) + .toHashCode(); + } + @Override public String toString() { return name; @@ -300,6 +431,32 @@ public TimeWindow getTimeWindow() { return timeWindow; } + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof Depot)) return false; + + final Depot depot = (Depot) o; + + return new EqualsBuilder() + .appendSuper(super.equals(o)) + .append(getName(), depot.getName()) + .append(getLocation(), depot.getLocation()) + .append(getTimeWindow(), depot.getTimeWindow()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .appendSuper(super.hashCode()) + .append(getName()) + .append(getLocation()) + .append(getTimeWindow()) + .toHashCode(); + } + @Override public String toString() { return name; @@ -337,6 +494,34 @@ public double getRechargingRate() { return rechargingRate; } + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof RechargingStation)) return false; + + final RechargingStation that = (RechargingStation) o; + + return new EqualsBuilder() + .appendSuper(super.equals(o)) + .append(getRechargingRate(), that.getRechargingRate()) + .append(getName(), that.getName()) + .append(getLocation(), that.getLocation()) + .append(getTimeWindow(), that.getTimeWindow()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .appendSuper(super.hashCode()) + .append(getName()) + .append(getLocation()) + .append(getTimeWindow()) + .append(getRechargingRate()) + .toHashCode(); + } + @Override public String toString() { return name; @@ -351,6 +536,28 @@ private VehicleType(int id, String name) { this.id = id; this.name = name; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof VehicleType)) return false; + + final VehicleType that = (VehicleType) o; + + return new EqualsBuilder() + .append(id, that.id) + .append(name, that.name) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .append(id) + .append(name) + .toHashCode(); + } } public static class BEVehicleType extends VehicleType { @@ -381,5 +588,33 @@ public double getEnergyConsumption() { public double getFixedCosts() { return fixedCosts; } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + + if (!(o instanceof BEVehicleType)) return false; + + final BEVehicleType that = (BEVehicleType) o; + + return new EqualsBuilder() + .appendSuper(super.equals(o)) + .append(getEnergyCapacity(), that.getEnergyCapacity()) + .append(getEnergyConsumption(), that.getEnergyConsumption()) + .append(getLoadCapacity(), that.getLoadCapacity()) + .append(getFixedCosts(), that.getFixedCosts()) + .isEquals(); + } + + @Override + public int hashCode() { + return new HashCodeBuilder(17, 37) + .appendSuper(super.hashCode()) + .append(getEnergyCapacity()) + .append(getEnergyConsumption()) + .append(getLoadCapacity()) + .append(getFixedCosts()) + .toHashCode(); + } } } diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWSolution.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWSolution.kt index 5aa6422..dd7901c 100644 --- a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWSolution.kt +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/EVRPTWSolution.kt @@ -1,6 +1,11 @@ package at.ac.tuwien.otl.evrptw.dto +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.ALPHA +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.BETA +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.GAMMA +import at.ac.tuwien.otl.evrptw.verifier.EVRPTWRouteVerifier import java.io.File +import java.util.stream.Collectors /** *

About this class

@@ -12,10 +17,130 @@ import java.io.File * @since 0.1.0 */ data class EVRPTWSolution( - private val instance: EVRPTWInstance, + val instance: EVRPTWInstance, val routes: MutableList>, - var cost: Double + val cost: Double, + val originOperator: Operator = Operator.NONE ) { + + val fitnessValue: FitnessValue = calculateFitnessValue() + + private fun calculateFitnessValue(): FitnessValue { + // val violations = EVRPTWRouteVerifier.calculateViolations(instance, routes) + var capacityViolations = 0.0 + var timeWindowViolations = 0.0 + var batteryCapacityViolations = 0.0 + val routeViolations = mutableListOf() + for (route in routes) { + val violations = EVRPTWRouteVerifier.calculateViolationOfRoute(instance, route) + capacityViolations += violations.capacityViolation + timeWindowViolations += violations.timeWindowViolation + batteryCapacityViolations += violations.batteryCapacityViolation + routeViolations.add(violations) + } + return FitnessValue( + cost, + capacityViolations, + timeWindowViolations, + batteryCapacityViolations, + routeViolations + ) + } + + private fun calculateTotalCapacityViolation(): Double { + var result = 0.0 + + for (route in routes) { + val demandSum = route + .stream() + .filter { it is EVRPTWInstance.Customer } + .mapToDouble { (it as EVRPTWInstance.Customer).demand } + .sum() + result += Math.max(demandSum - instance.vehicleCapacity, 0.0) + } + + return result + } + + private fun calculateTotalTimeWindowViolation(): Double { + return Math.max(EVRPTWRouteVerifier.calculateTotalTimeWindowViolation(instance, routes), 0.0) + } + + private fun calculateTotalBatteryCapacityViolation(): Double { + var result = 0.0 + + for (route in routes) { + var batteryViolation = 0.0 + var lastNotCustomerIndex = 0 + for (nodeIndex in 0 until route.size) { + if (route[nodeIndex] is EVRPTWInstance.Depot || route[nodeIndex] is EVRPTWInstance.RechargingStation) { + val currentViolation = batteryDemandTo(route, lastNotCustomerIndex, nodeIndex) + batteryViolation += Math.max(currentViolation - instance.vehicleEnergyCapacity, 0.0) + lastNotCustomerIndex = nodeIndex + } + } + result += batteryViolation + } + + return result + } + + private fun batteryDemandTo(route: List, startIndex: Int, nodeIndex: Int): Double { + if (startIndex == nodeIndex) { + return 0.0 + } + return batteryDemandTo(route, startIndex, nodeIndex - 1) + instance.getTravelDistance( + route[nodeIndex - 1], + route[nodeIndex] + ) * instance.vehicleEnergyConsumption + } + + /** + * Copy constructor. + */ + constructor(solution: EVRPTWSolution) : this(solution.instance, solution.routes + .stream() + .map { + it + .stream() + .map { node -> + when (node) { + is EVRPTWInstance.Depot -> EVRPTWInstance.Depot( + node.id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end + ) + is EVRPTWInstance.Customer -> EVRPTWInstance.Customer( + node.id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end, + node.demand, + node.serviceTime + ) + else -> EVRPTWInstance.RechargingStation( + (node as EVRPTWInstance.RechargingStation).id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end, + node.rechargingRate + ) + } + } + .collect(Collectors.toList()) + .toMutableList() + } + .collect(Collectors.toList()) + .toMutableList(), + solution.cost, solution.originOperator) + fun writeToFile() { File("solutions/" + instance.name + "_sol.txt").bufferedWriter().use { out -> out.write("# solution for " + instance.name) @@ -34,4 +159,101 @@ data class EVRPTWSolution( } } } + + fun copyOfRoutes(): MutableList> { + return routes + .stream() + .map { + it + .stream() + .map { node -> + when (node) { + is EVRPTWInstance.Depot -> EVRPTWInstance.Depot( + node.id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end + ) + is EVRPTWInstance.Customer -> EVRPTWInstance.Customer( + node.id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end, + node.demand, + node.serviceTime + ) + else -> EVRPTWInstance.RechargingStation( + (node as EVRPTWInstance.RechargingStation).id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end, + node.rechargingRate + ) + } + } + .collect(Collectors.toList()) + .toMutableList() + } + .collect(Collectors.toList()) + .toMutableList() + } + + fun copyOfRoute(route: List): MutableList { + return route.stream().map { node -> + when (node) { + is EVRPTWInstance.Depot -> EVRPTWInstance.Depot( + node.id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end + ) + is EVRPTWInstance.Customer -> EVRPTWInstance.Customer( + node.id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end, + node.demand, + node.serviceTime + ) + else -> EVRPTWInstance.RechargingStation( + (node as EVRPTWInstance.RechargingStation).id, + node.name, + node.location.x, + node.location.y, + node.timeWindow.start, + node.timeWindow.end, + node.rechargingRate + ) + } + } + .collect(Collectors.toList()) + .toMutableList() + } } + +data class FitnessValue( + val totalTravelDistance: Double, + val totalCapacityViolation: Double, + val totalTimeWindowViolation: Double, + val totalBatteryCapacityViolation: Double, + val routeViolations: List +) { + val fitness = + totalTravelDistance + (ALPHA * totalCapacityViolation) + (BETA * totalTimeWindowViolation) + (GAMMA * totalBatteryCapacityViolation) +} + +data class Violations( + val capacityViolation: Double, + val timeWindowViolation: Double, + val batteryCapacityViolation: Double +) \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/ExchangeSequence.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/ExchangeSequence.kt new file mode 100644 index 0000000..fa35973 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/ExchangeSequence.kt @@ -0,0 +1,15 @@ +package at.ac.tuwien.otl.evrptw.dto + +/** + *

About this class

+ + *

Description of this class

+ * + * @author David Molnar + * @version 1.0.0 + * @since 1.0.0 + */ +data class ExchangeSequence( + val nodes: List, + val startIndex: Int +) \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/NeighbourhoodStructure.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/NeighbourhoodStructure.kt new file mode 100644 index 0000000..2fb8675 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/NeighbourhoodStructure.kt @@ -0,0 +1,35 @@ +package at.ac.tuwien.otl.evrptw.dto + +/** + *

About this class

+ + *

Description of this class

+ * + * @author David Molnar + * @version 1.0.0 + * @since 1.0.0 + */ +data class NeighbourhoodStructure( + val numberOfInvolvedRoutes: Int, + val maxVertices: Int +) { + companion object { + val STRUCTURES = mapOf( + 1 to NeighbourhoodStructure(2, 1), + 2 to NeighbourhoodStructure(2, 2), + 3 to NeighbourhoodStructure(2, 3), + 4 to NeighbourhoodStructure(2, 4), + 5 to NeighbourhoodStructure(2, 5), + 6 to NeighbourhoodStructure(3, 1), + 7 to NeighbourhoodStructure(3, 2), + 8 to NeighbourhoodStructure(3, 3), + 9 to NeighbourhoodStructure(3, 4), + 10 to NeighbourhoodStructure(3, 5), + 11 to NeighbourhoodStructure(4, 1), + 12 to NeighbourhoodStructure(4, 2), + 13 to NeighbourhoodStructure(4, 3), + 14 to NeighbourhoodStructure(4, 4), + 15 to NeighbourhoodStructure(4, 5) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/Operator.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/Operator.kt new file mode 100644 index 0000000..698e346 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/Operator.kt @@ -0,0 +1,18 @@ +package at.ac.tuwien.otl.evrptw.dto + +/** + *

About this class

+ *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 25.06.2018 + */ +enum class Operator { + NONE, + TWO_OPT, + TWO_OR_OPT, + EXCHANGE, + RELOCATE, + STATION_IN_RE +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/Route.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/Route.kt index 0ab0187..1c7e695 100644 --- a/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/Route.kt +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/dto/Route.kt @@ -180,4 +180,17 @@ class Route( log.info(message) } } + + companion object { + fun calculateTotalDistance(routes: List>, instance: EVRPTWInstance): Double { + var result = 0.0 + + for (route in routes) { + for (nodeIndex in 0 until route.size - 1) { + result += instance.getTravelDistance(route[nodeIndex], route[nodeIndex + 1]) + } + } + return result + } + } } \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/Constants.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/Constants.kt new file mode 100644 index 0000000..aba440f --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/Constants.kt @@ -0,0 +1,39 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class Constants private constructor() { + + companion object { + const val N_DIST = 50 + const val N_FEAS = 5 + const val N_TABU = 50 + const val N_PENALTY = 2 + const val COOLING_FACTOR = 0.9 + const val TABU_TENURE_MIN = 15 + const val TABU_TENURE_MAX = 30 + const val NO_CHANGE_THRESHOLD = 3 + const val ALPHA_DEFAULT = 1.0 + const val BETA_DEFAULT = 10.0 + const val GAMMA_DEFAULT = 10.0 + const val VIOLATION_FACTOR_MIN = 1.0 + const val VIOLATION_FACTOR_BELOW_MIN_DESCENT_RATE = 0.1 + const val VIOLATION_FACTOR_ABSOLUTE_MIN = 0.1 + const val VIOLATION_FACTOR_INCREASE_RATE = 2.0 + const val VIOLATION_FACTOR_DECREASE_RATE = 5.0 + const val ALPHA_STARTING = 1.0 + const val BETA_STARTING = 2.0 + const val GAMMA_STARTING = 2.0 + var ALPHA = ALPHA_STARTING + var BETA = BETA_STARTING + var GAMMA = GAMMA_STARTING + val FIBONACCI = arrayOf(1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/HybridVnsTsMetaHeuristic.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/HybridVnsTsMetaHeuristic.kt new file mode 100644 index 0000000..02c328c --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/HybridVnsTsMetaHeuristic.kt @@ -0,0 +1,252 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic + +/* ktlint-disable no-wildcard-imports */ +import at.ac.tuwien.otl.evrptw.Main +import at.ac.tuwien.otl.evrptw.dto.* +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.ALPHA +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.ALPHA_DEFAULT +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.ALPHA_STARTING +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.BETA +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.BETA_DEFAULT +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.BETA_STARTING +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.COOLING_FACTOR +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.FIBONACCI +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.GAMMA +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.GAMMA_DEFAULT +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.GAMMA_STARTING +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.NO_CHANGE_THRESHOLD +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.N_DIST +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.N_FEAS +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.VIOLATION_FACTOR_ABSOLUTE_MIN +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.VIOLATION_FACTOR_BELOW_MIN_DESCENT_RATE +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.VIOLATION_FACTOR_DECREASE_RATE +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.VIOLATION_FACTOR_INCREASE_RATE +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.VIOLATION_FACTOR_MIN +import at.ac.tuwien.otl.evrptw.metaheuristic.tabusearch.TabuSearch +import at.ac.tuwien.otl.evrptw.verifier.EVRPTWRouteVerifier +import java.util.Random +import java.util.logging.Logger +import kotlin.streams.toList + +/** + *

About this class

+ + *

Description of this class

+ * + * @author David Molnar + * @version 1.0.0 + * @since 1.0.0 + */ +class HybridVnsTsMetaHeuristic(private val logEnabled: Boolean = true) : IMetaHeuristic { + private val log: Logger = Logger.getLogger(this.javaClass.name) + private val neighbourSolutionGenerator = ShakingNeighbourSolutionGenerator() + private val tabuSearch = TabuSearch(logEnabled) + private val random = Random(java.lang.Double.doubleToLongBits(Math.random())) + private val lastSavedSolutions = mutableListOf() + private var temperature = 0.0 + private var thresholdCounter = 0 + private var infeasibleSequenceCounter = 0 + private var feasibleSequenceCounter = 0 + + override fun improveSolution(evrptwSolution: EVRPTWSolution): EVRPTWSolution { + temperature = Main.instanceToInitTemperatureMap[evrptwSolution.instance.name]!! + resetParameters() + + var bestFeasibleSolution = evrptwSolution + var bestSolution = evrptwSolution + var k = 1 + var i = 0 + var feasibilityPhase = true + + while (feasibilityPhase || (!feasibilityPhase && i < N_DIST)) { + println("VNS Iteration: $i") + val newSolution = neighbourSolutionGenerator.generateRandomPoint(bestSolution, k) + val optimizedNewSolution = tabuSearch.apply(newSolution) + lastSavedSolutions.add(optimizedNewSolution) + if (lastSavedSolutions.size > NO_CHANGE_THRESHOLD) { + lastSavedSolutions.removeAt(0) + } + adjustParameters() + + if (acceptSimulatedAnnealing(optimizedNewSolution, bestSolution)) { + log("$$$ New best solution $$$. Cost: ${optimizedNewSolution.cost}" + "Cap-Violation: ${optimizedNewSolution.fitnessValue.totalCapacityViolation}, " + + "TW-Violation: ${optimizedNewSolution.fitnessValue.totalTimeWindowViolation}, " + + "Bat-Violation: ${optimizedNewSolution.fitnessValue.totalBatteryCapacityViolation}, " + + "Fitness: ${optimizedNewSolution.fitnessValue.fitness}") + bestSolution = optimizedNewSolution + if (optimizedNewSolution.fitnessValue.fitness == optimizedNewSolution.cost && optimizedNewSolution.cost < bestFeasibleSolution.cost) { + log("New best feasible solution with cost ${optimizedNewSolution.cost}") + bestFeasibleSolution = optimizedNewSolution + } + k = 1 + } else { + k = (k % NeighbourhoodStructure.STRUCTURES.size) + 1 + } + + if (feasibilityPhase) { + if (!feasible(bestSolution)) { + if (i == N_FEAS) { + log("!!!!!!! Splitting routes !!!!!!") + bestSolution = addVehicle(bestSolution) + i = -1 + } + } else { + feasibilityPhase = false + i = -1 + } + } + i++ + } + return bestFeasibleSolution + } + + private fun resetParameters() { + ALPHA = ALPHA_STARTING + BETA = BETA_STARTING + GAMMA = GAMMA_STARTING + lastSavedSolutions.clear() + thresholdCounter = 0 + infeasibleSequenceCounter = 0 + feasibleSequenceCounter = 0 + } + + private fun adjustParameters() { + val lastSolution = lastSavedSolutions.last() + if (lastSolution.fitnessValue.fitness == lastSolution.cost) { + thresholdCounter++ + } else { + thresholdCounter-- + } + if (thresholdCounter == NO_CHANGE_THRESHOLD) { + decreaseRateAlpha() + decreaseRateBeta() + decreaseRateGamma() + thresholdCounter = 0 + infeasibleSequenceCounter = 0 + feasibleSequenceCounter++ + log("-- VIOLATION FACTORS DECREASED = ($ALPHA, $BETA, $GAMMA). Fibonacci multiplier: ${FIBONACCI[feasibleSequenceCounter - 1]}") + } else if (thresholdCounter == -NO_CHANGE_THRESHOLD) { + val numberOfCapacityViolations = + lastSavedSolutions.stream().filter { it.fitnessValue.totalCapacityViolation > 0.0 }.toList().size + val numberOfTimeWindowViolations = + lastSavedSolutions.stream().filter { it.fitnessValue.totalTimeWindowViolation > 0.0 }.toList().size + val numberOfBatteryCapacityViolations = + lastSavedSolutions.stream().filter { it.fitnessValue.totalBatteryCapacityViolation > 0.0 }.toList().size + val indexOfMultiplier = infeasibleSequenceCounter % FIBONACCI.size + if (numberOfCapacityViolations > 0 && ALPHA < VIOLATION_FACTOR_MIN) { + ALPHA = VIOLATION_FACTOR_MIN + } else { + ALPHA += VIOLATION_FACTOR_INCREASE_RATE * numberOfCapacityViolations * FIBONACCI[indexOfMultiplier] + } + if (numberOfTimeWindowViolations > 0 && BETA < VIOLATION_FACTOR_MIN) { + BETA = VIOLATION_FACTOR_MIN + } else { + BETA += VIOLATION_FACTOR_INCREASE_RATE * numberOfTimeWindowViolations * FIBONACCI[indexOfMultiplier] + } + if (numberOfBatteryCapacityViolations > 0 && GAMMA < VIOLATION_FACTOR_MIN) { + GAMMA = VIOLATION_FACTOR_MIN + } else { + GAMMA += VIOLATION_FACTOR_INCREASE_RATE * numberOfBatteryCapacityViolations * FIBONACCI[indexOfMultiplier] + } + thresholdCounter = 0 + infeasibleSequenceCounter++ + feasibleSequenceCounter = 0 + log("++ VIOLATION FACTORS INCREASED = ($ALPHA, $BETA, $GAMMA). Fibonacci multiplier: ${FIBONACCI[infeasibleSequenceCounter - 1]}") + } + } + + private fun decreaseRateAlpha() { + val indexOfMultiplier = feasibleSequenceCounter % FIBONACCI.size + ALPHA = when { + ALPHA > ALPHA_DEFAULT -> ALPHA_DEFAULT + ALPHA <= VIOLATION_FACTOR_MIN -> Math.max(ALPHA - (VIOLATION_FACTOR_BELOW_MIN_DESCENT_RATE * FIBONACCI[indexOfMultiplier]), VIOLATION_FACTOR_ABSOLUTE_MIN) + else -> Math.max(ALPHA - VIOLATION_FACTOR_DECREASE_RATE, VIOLATION_FACTOR_MIN) + } + } + + private fun decreaseRateBeta() { + val indexOfMultiplier = feasibleSequenceCounter % FIBONACCI.size + BETA = when { + BETA > BETA_DEFAULT -> BETA_DEFAULT + BETA <= VIOLATION_FACTOR_MIN -> Math.max(BETA - (VIOLATION_FACTOR_BELOW_MIN_DESCENT_RATE * FIBONACCI[indexOfMultiplier]), VIOLATION_FACTOR_ABSOLUTE_MIN) + else -> Math.max(BETA - VIOLATION_FACTOR_DECREASE_RATE, VIOLATION_FACTOR_MIN) + } + } + + private fun decreaseRateGamma() { + val indexOfMultiplier = feasibleSequenceCounter % FIBONACCI.size + GAMMA = when { + GAMMA > GAMMA_DEFAULT -> GAMMA_DEFAULT + GAMMA <= VIOLATION_FACTOR_MIN -> Math.max(GAMMA - (VIOLATION_FACTOR_BELOW_MIN_DESCENT_RATE * FIBONACCI[indexOfMultiplier]), VIOLATION_FACTOR_ABSOLUTE_MIN) + else -> Math.max(GAMMA - VIOLATION_FACTOR_DECREASE_RATE, VIOLATION_FACTOR_MIN) + } + } + + private fun acceptSimulatedAnnealing(optimizedNewSolution: EVRPTWSolution, bestSolution: EVRPTWSolution): Boolean { + val accept: Boolean + accept = if (optimizedNewSolution.fitnessValue.fitness == optimizedNewSolution.cost) { + when { + bestSolution.fitnessValue.fitness != bestSolution.cost -> { + log("Accepting new feasible solution, because current best is infeasible") + true + } + optimizedNewSolution.fitnessValue.fitness < bestSolution.fitnessValue.fitness -> { + log("Accepting new feasible solution, because new is better than current best feasible") + true + } + else -> false + } + } else { + if (optimizedNewSolution.fitnessValue.fitness < bestSolution.fitnessValue.fitness) { + val exponent = + (-Math.abs(optimizedNewSolution.fitnessValue.fitness - bestSolution.fitnessValue.fitness)) / temperature + val probabilityAccept = Math.exp(exponent) + val randomNumber = random.nextDouble() + log("SA accept probability: $randomNumber < $probabilityAccept") + randomNumber < probabilityAccept + } else { + false + } + } + temperature *= COOLING_FACTOR + return accept + } + + private fun feasible(solution: EVRPTWSolution): Boolean { + return EVRPTWRouteVerifier(solution.instance).verify(solution.routes, solution.cost, false) + } + + private fun addVehicle(solution: EVRPTWSolution): EVRPTWSolution { + val newRoutes = mutableListOf>() + for (routeIndex in 0 until solution.fitnessValue.routeViolations.size) { + val routeViolation = solution.fitnessValue.routeViolations[routeIndex] + val route = solution.routes[routeIndex] + if (route.size > 3 && (routeViolation.batteryCapacityViolation > 0.0 || routeViolation.timeWindowViolation > 0.0 || routeViolation.capacityViolation > 0.0)) { + newRoutes.addAll(splitRoute(route, solution.instance)) + } else { + newRoutes.add(solution.copyOfRoute(solution.routes[routeIndex])) + } + } + return EVRPTWSolution(solution.instance, newRoutes, Route.calculateTotalDistance(newRoutes, solution.instance)) + } + + private fun splitRoute( + route: List, + instance: EVRPTWInstance + ): MutableList> { + val newRoutes = mutableListOf>() + val firstRoute = route.subList(0, route.size / 2).toMutableList() + firstRoute.add(instance.depot) + val secondRoute = route.subList(route.size / 2, route.size).toMutableList() + secondRoute.add(0, instance.depot) + newRoutes.add(firstRoute) + newRoutes.add(secondRoute) + return newRoutes + } + + private fun log(message: String) { + if (logEnabled) { + log.info(message) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/IMetaHeuristic.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/IMetaHeuristic.kt new file mode 100644 index 0000000..f31c0e5 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/IMetaHeuristic.kt @@ -0,0 +1,16 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution + +/** + *

About this class

+ + *

Description of this class

+ * + * @author David Molnar + * @version 1.0.0 + * @since 1.0.0 + */ +interface IMetaHeuristic { + fun improveSolution(evrptwSolution: EVRPTWSolution): EVRPTWSolution +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/ShakingNeighbourSolutionGenerator.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/ShakingNeighbourSolutionGenerator.kt new file mode 100644 index 0000000..24d9c18 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/ShakingNeighbourSolutionGenerator.kt @@ -0,0 +1,81 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic + +/* ktlint-disable no-wildcard-imports */ +import at.ac.tuwien.otl.evrptw.dto.* +import java.util.Random + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class ShakingNeighbourSolutionGenerator { + + private val random = Random(java.lang.Double.doubleToLongBits(Math.random())) + + fun generateRandomPoint(solution: EVRPTWSolution, neighbour: Int): EVRPTWSolution { + val resultRoutes = solution.copyOfRoutes() + + val routes: MutableList> = mutableListOf() + val neighbourhoodStructure = NeighbourhoodStructure.STRUCTURES[neighbour]!! + + if (resultRoutes.size < neighbourhoodStructure.numberOfInvolvedRoutes) { + throw RuntimeException("ERROR: fewer routes than involved routes") + } + + for (i in 0 until neighbourhoodStructure.numberOfInvolvedRoutes) { + val index = random.nextInt(resultRoutes.size) + + routes.add(resultRoutes[index]) + resultRoutes.removeAt(index) + } + + val exchangeSequences = mutableListOf() + + for (route in routes) { + val upperBound = Math.min(neighbourhoodStructure.maxVertices, route.size - 2) + if (upperBound == 0) { + println("ERROR UPPER BOUND. Route: $route") + } + val numberOfSuccessiveVertices = if (upperBound == 1) { + 1 + } else { + random.nextInt(upperBound) + 1 + } + val startIndex = random.nextInt((route.size - numberOfSuccessiveVertices - 1)) + 1 + + val exchangeSequence = ExchangeSequence( + route.subList( + startIndex, + startIndex + numberOfSuccessiveVertices + ).toList(), startIndex + ) + exchangeSequences.add(exchangeSequence) + + route.subList(startIndex, startIndex + numberOfSuccessiveVertices).clear() + } + + for (i in 0 until routes.size) { + val startIndex = exchangeSequences[i].startIndex + val sequence: List = if (i == 0) { + exchangeSequences[exchangeSequences.size - 1].nodes + } else { + exchangeSequences[i - 1].nodes + } + + routes[i].addAll(startIndex, sequence) + } + + resultRoutes.addAll(routes) + + return EVRPTWSolution( + solution.instance, + resultRoutes, + Route.calculateTotalDistance(resultRoutes, solution.instance) + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/INeighbourhoodExplorer.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/INeighbourhoodExplorer.kt new file mode 100644 index 0000000..e8e1399 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/INeighbourhoodExplorer.kt @@ -0,0 +1,24 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +interface INeighbourhoodExplorer { + + /** + * Explores (enumerates) every possible solution in the search space + * of this neighbourhood based on the initial solution and returns them + * in a list. + * + * @param initialSolution an initial solution for which the neighbours should + * be calculated + * @return a list of neighbour solutions, not necessarily sorted + */ + fun exploreEverySolution(initialSolution: T, startAtIncl: Int, endAtExcl: Int): List +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/InterIntraRouteExchangeExplorer.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/InterIntraRouteExchangeExplorer.kt new file mode 100644 index 0000000..ff64895 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/InterIntraRouteExchangeExplorer.kt @@ -0,0 +1,84 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.dto.Operator +import at.ac.tuwien.otl.evrptw.dto.Route + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class InterIntraRouteExchangeExplorer : INeighbourhoodExplorer { + + /** + * Explores (enumerates) every possible solution in the search space + * of this neighbourhood based on the initial solution and returns them + * in a list. + * + * @param initialSolution an initial solution for which the neighbours should + * be calculated + * @return a list of neighbour solutions, not necessarily sorted + */ + override fun exploreEverySolution(initialSolution: EVRPTWSolution, startAtIncl: Int, endAtExcl: Int): List { + val result = mutableListOf() + for (routeIndex in startAtIncl until endAtExcl) { + val route = initialSolution.routes[routeIndex] + + for (secondRouteIndex in routeIndex until initialSolution.routes.size) { + val secondRoute = initialSolution.routes[secondRouteIndex] + for (nodeOfFirstRoute in 1 until route.size - 1) { // start at 1 and end -1 before due to depot + if (route[nodeOfFirstRoute] is EVRPTWInstance.RechargingStation) { + // skip exchange for Recharging stations + continue + } + for (nodeOfSecondRoute in 1 until secondRoute.size - 1) { + if (secondRoute[nodeOfSecondRoute] is EVRPTWInstance.RechargingStation) { + // skip exchange for Recharging stations + continue + } + if (secondRouteIndex != routeIndex || nodeOfFirstRoute != nodeOfSecondRoute) { + val neighbourSolution = performNodeSwap( + initialSolution, + routeIndex, + nodeOfFirstRoute, + secondRouteIndex, + nodeOfSecondRoute + ) + result.add(neighbourSolution) + } + } + } + } + } + return result + } + + private fun performNodeSwap( + initialSolution: EVRPTWSolution, + routeIndex: Int, + nodeOfFirstRoute: Int, + secondRouteIndex: Int, + nodeOfSecondRoute: Int + ): EVRPTWSolution { + val routes = initialSolution.copyOfRoutes() + + val node1 = routes[routeIndex][nodeOfFirstRoute] + val node2 = routes[secondRouteIndex][nodeOfSecondRoute] + + routes[routeIndex][nodeOfFirstRoute] = node2 + routes[secondRouteIndex][nodeOfSecondRoute] = node1 + + return EVRPTWSolution( + initialSolution.instance, + routes, + Route.calculateTotalDistance(routes, initialSolution.instance), + Operator.EXCHANGE + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/InterIntraRouteRelocateExplorer.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/InterIntraRouteRelocateExplorer.kt new file mode 100644 index 0000000..865f06f --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/InterIntraRouteRelocateExplorer.kt @@ -0,0 +1,75 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.dto.Operator +import at.ac.tuwien.otl.evrptw.dto.Route + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class InterIntraRouteRelocateExplorer : INeighbourhoodExplorer { + /** + * Explores (enumerates) every possible solution in the search space + * of this neighbourhood based on the initial solution and returns them + * in a list. + * + * @param initialSolution an initial solution for which the neighbours should + * be calculated + * @return a list of neighbour solutions, not necessarily sorted + */ + override fun exploreEverySolution(initialSolution: EVRPTWSolution, startAtIncl: Int, endAtExcl: Int): List { + val result = mutableListOf() + for (routeIndex in startAtIncl until endAtExcl) { + val route = initialSolution.routes[routeIndex] + if (route.size > 3) { + for (secondRouteIndex in routeIndex until initialSolution.routes.size) { + val secondRoute = initialSolution.routes[secondRouteIndex] + for (nodeOfFirstRoute in 1 until route.size - 1) { // start at 1 and end -1 before due to depot + for (nodeOfSecondRoute in 1 until secondRoute.size - 1) { + if (routeIndex == secondRouteIndex && nodeOfSecondRoute <= nodeOfFirstRoute) { + continue + } + val neighbourSolution = performRelocation( + initialSolution, + routeIndex, + nodeOfFirstRoute, + secondRouteIndex, + nodeOfSecondRoute + ) + result.add(neighbourSolution) + } + } + } + } + } + return result + } + + private fun performRelocation( + initialSolution: EVRPTWSolution, + routeIndex: Int, + nodeOfFirstRoute: Int, + secondRouteIndex: Int, + nodeOfSecondRoute: Int + ): EVRPTWSolution { + val routes = initialSolution.copyOfRoutes() + + val node = routes[routeIndex][nodeOfFirstRoute] + + routes[secondRouteIndex].add(nodeOfSecondRoute, node) + routes[routeIndex].removeAt(nodeOfFirstRoute) + + return EVRPTWSolution( + initialSolution.instance, + routes, + Route.calculateTotalDistance(routes, initialSolution.instance), + Operator.RELOCATE + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/StationInReExplorer.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/StationInReExplorer.kt new file mode 100644 index 0000000..6b30f82 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/StationInReExplorer.kt @@ -0,0 +1,78 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.dto.Operator +import at.ac.tuwien.otl.evrptw.dto.Route + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class StationInReExplorer : INeighbourhoodExplorer { + + override fun exploreEverySolution(initialSolution: EVRPTWSolution, startAtIncl: Int, endAtExcl: Int): List { + val result = mutableListOf() + for (routeIndex in startAtIncl until endAtExcl) { + val route = initialSolution.routes[routeIndex] + for (nodeIndex in 1 until route.size) { + if (route[nodeIndex] is EVRPTWInstance.RechargingStation && route.size > 3) { + result.add(performStationRemoval(initialSolution, routeIndex, nodeIndex)) + } else { + if (route[nodeIndex - 1] !is EVRPTWInstance.RechargingStation) { + for (station in initialSolution.instance.rechargingStations) { + val neighbourSolution = performStationInsertion(initialSolution, routeIndex, nodeIndex, station) + result.add(neighbourSolution) + } + } + } + } + } + return result + } + + private fun performStationRemoval( + initialSolution: EVRPTWSolution, + routeIndex: Int, + nodeIndex: Int + ): EVRPTWSolution { + val routes = initialSolution.copyOfRoutes() + routes[routeIndex].removeAt(nodeIndex) + return EVRPTWSolution( + initialSolution.instance, + routes, + Route.calculateTotalDistance(routes, initialSolution.instance) + ) + } + + private fun performStationInsertion( + initialSolution: EVRPTWSolution, + routeIndex: Int, + nodeIndex: Int, + station: EVRPTWInstance.RechargingStation + ): EVRPTWSolution { + val routes = initialSolution.copyOfRoutes() + val stationToInsert = EVRPTWInstance.RechargingStation( + station.id, + station.name, + station.location.x, + station.location.y, + station.timeWindow.start, + station.timeWindow.end, + station.rechargingRate + ) + + routes[routeIndex].add(nodeIndex, stationToInsert) + return EVRPTWSolution( + initialSolution.instance, + routes, + Route.calculateTotalDistance(routes, initialSolution.instance), + Operator.STATION_IN_RE + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/TwoOptArcExchangeExplorer.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/TwoOptArcExchangeExplorer.kt new file mode 100644 index 0000000..305bb66 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/TwoOptArcExchangeExplorer.kt @@ -0,0 +1,79 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.dto.Operator +import at.ac.tuwien.otl.evrptw.dto.Route + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class TwoOptArcExchangeExplorer : + INeighbourhoodExplorer { + + override fun exploreEverySolution(initialSolution: EVRPTWSolution, startAtIncl: Int, endAtExcl: Int): List { + val result = mutableListOf() + for (routeIndex in startAtIncl until endAtExcl) { + val route = initialSolution.routes[routeIndex] + + for (secondRouteIndex in (routeIndex + 1) until initialSolution.routes.size) { + val secondRoute = initialSolution.routes[secondRouteIndex] + for (nodeOfFirstRoute in 1 until route.size - 1) { // start at 1 and end -1 before due to depot + for (nodeOfSecondRoute in 1 until secondRoute.size - 1) { + val neighbourSolution = performTwoOptExchange( + initialSolution, + routeIndex, + nodeOfFirstRoute, + secondRouteIndex, + nodeOfSecondRoute + ) + result.add(neighbourSolution) + } + } + } + } + return result + } + + private fun performTwoOptExchange( + solution: EVRPTWSolution, + firstRouteIndex: Int, + firstRouteNodeIndex: Int, + secondRouteIndex: Int, + secondRouteNoteIndex: Int + ): EVRPTWSolution { + val routes = solution.copyOfRoutes() + + val firstRoute = routes[firstRouteIndex] + val secondRoute = routes[secondRouteIndex] + + val newFirstRoute = firstRoute.subList(0, firstRouteNodeIndex + 1).toList() + secondRoute.subList( + secondRouteNoteIndex, + secondRoute.size + ).toList() + val newSecondRoute = secondRoute.subList(0, secondRouteNoteIndex).toList() + firstRoute.subList( + firstRouteNodeIndex + 1, + firstRoute.size + ).toList() + + routes[firstRouteIndex].clear() + routes[firstRouteIndex].addAll(newFirstRoute) + + routes[secondRouteIndex].clear() + routes[secondRouteIndex].addAll(newSecondRoute) + + if (newFirstRoute.size <= 2) { + routes.removeAt(firstRouteIndex) + } + if (newSecondRoute.size <= 2) { + routes.removeAt(secondRouteIndex) + } + + return EVRPTWSolution(solution.instance, routes, Route.calculateTotalDistance(routes, solution.instance), Operator.TWO_OPT) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/TwoOrOptExplorer.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/TwoOrOptExplorer.kt new file mode 100644 index 0000000..4bc0a11 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/TwoOrOptExplorer.kt @@ -0,0 +1,87 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.dto.Operator +import at.ac.tuwien.otl.evrptw.dto.Route + +/** + *

About this class

+ *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 26.06.2018 + */ +class TwoOrOptExplorer : INeighbourhoodExplorer { + /** + * Explores (enumerates) every possible solution in the search space + * of this neighbourhood based on the initial solution and returns them + * in a list. + * + * @param initialSolution an initial solution for which the neighbours should + * be calculated + * @return a list of neighbour solutions, not necessarily sorted + */ + override fun exploreEverySolution( + initialSolution: EVRPTWSolution, + startAtIncl: Int, + endAtExcl: Int + ): List { + val result = mutableListOf() + for (routeIndex in startAtIncl until endAtExcl) { + val route = initialSolution.routes[routeIndex] + if (route.size >= 5) { + for (nodeIndex in 1 until route.size - 2) { + if (route[nodeIndex] is EVRPTWInstance.Customer && route[nodeIndex + 1] is EVRPTWInstance.Customer) { + if (nodeIndex > 1) { + for (nodeBefore in 1 until nodeIndex) { + result.add(performOrOptBackwards(initialSolution, routeIndex, nodeIndex, nodeBefore)) + } + } + if (nodeIndex + 1 != route.size - 2) { + for (nodeAfter in nodeIndex + 2 until route.size - 1) { + result.add(performOrOptForwards(initialSolution, routeIndex, nodeIndex, nodeAfter)) + } + } + } + } + } + } + return result + } + + private fun performOrOptBackwards(solution: EVRPTWSolution, routeIndex: Int, indexOfChainFirstNode: Int, insertBeforeIndex: Int): EVRPTWSolution { + val routes = solution.copyOfRoutes() + val route = routes[routeIndex] + val node1 = route[indexOfChainFirstNode] + val node2 = route[indexOfChainFirstNode + 1] + val chain = listOf(node1, node2) + route.removeAll(chain) + route.addAll(insertBeforeIndex, chain) + + return EVRPTWSolution( + solution.instance, + routes, + Route.calculateTotalDistance(routes, solution.instance), + Operator.TWO_OR_OPT + ) + } + + private fun performOrOptForwards(solution: EVRPTWSolution, routeIndex: Int, indexOfChainFirstNode: Int, insertAfterIndex: Int): EVRPTWSolution { + val routes = solution.copyOfRoutes() + val route = routes[routeIndex] + val node1 = route[indexOfChainFirstNode] + val node2 = route[indexOfChainFirstNode + 1] + val chain = listOf(node1, node2) + route.removeAll(chain) + route.addAll(insertAfterIndex - 1, chain) + + return EVRPTWSolution( + solution.instance, + routes, + Route.calculateTotalDistance(routes, solution.instance), + Operator.TWO_OR_OPT + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/INeighbourhoodExplorerCallable.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/INeighbourhoodExplorerCallable.kt new file mode 100644 index 0000000..9f4265e --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/INeighbourhoodExplorerCallable.kt @@ -0,0 +1,31 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.callable + +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.INeighbourhoodExplorer +import java.util.concurrent.Callable + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +abstract class INeighbourhoodExplorerCallable( + private val initialSolution: T, + private val startAtIncl: Int, + private val endAtIncl: Int, + private val explorer: INeighbourhoodExplorer +) : Callable> { + + /** + * Computes a result, or throws an exception if unable to do so. + * + * @return computed result + * @throws Exception if unable to compute a result + */ + override fun call(): List { + return explorer.exploreEverySolution(initialSolution, startAtIncl, endAtIncl) + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/InterIntraRouteExchangeExplorerCallable.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/InterIntraRouteExchangeExplorerCallable.kt new file mode 100644 index 0000000..6b4919c --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/InterIntraRouteExchangeExplorerCallable.kt @@ -0,0 +1,20 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.callable + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.InterIntraRouteExchangeExplorer + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class InterIntraRouteExchangeExplorerCallable( + initialSolution: EVRPTWSolution, + startAtIncl: Int, + endAtIncl: Int, + explorer: InterIntraRouteExchangeExplorer +) : INeighbourhoodExplorerCallable(initialSolution, startAtIncl, endAtIncl, explorer) \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/InterIntraRouteRelocateExplorerCallable.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/InterIntraRouteRelocateExplorerCallable.kt new file mode 100644 index 0000000..b1822f7 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/InterIntraRouteRelocateExplorerCallable.kt @@ -0,0 +1,20 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.callable + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.InterIntraRouteRelocateExplorer + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class InterIntraRouteRelocateExplorerCallable( + initialSolution: EVRPTWSolution, + startAtIncl: Int, + endAtIncl: Int, + explorer: InterIntraRouteRelocateExplorer +) : INeighbourhoodExplorerCallable(initialSolution, startAtIncl, endAtIncl, explorer) \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/StationInReExplorerCallable.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/StationInReExplorerCallable.kt new file mode 100644 index 0000000..94d390b --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/StationInReExplorerCallable.kt @@ -0,0 +1,21 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.callable + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.StationInReExplorer + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class StationInReExplorerCallable( + initialSolution: EVRPTWSolution, + startAtIncl: Int, + endAtIncl: Int, + explorer: StationInReExplorer +) : + INeighbourhoodExplorerCallable(initialSolution, startAtIncl, endAtIncl, explorer) \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/TwoOptArcExchangeExplorerCallable.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/TwoOptArcExchangeExplorerCallable.kt new file mode 100644 index 0000000..eeaa4ea --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/TwoOptArcExchangeExplorerCallable.kt @@ -0,0 +1,21 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.callable + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.TwoOptArcExchangeExplorer + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class TwoOptArcExchangeExplorerCallable( + initialSolution: EVRPTWSolution, + startAtIncl: Int, + endAtIncl: Int, + explorer: TwoOptArcExchangeExplorer +) : + INeighbourhoodExplorerCallable(initialSolution, startAtIncl, endAtIncl, explorer) \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/TwoOrOptExplorerCallable.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/TwoOrOptExplorerCallable.kt new file mode 100644 index 0000000..236d6ff --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/neighbourhood/callable/TwoOrOptExplorerCallable.kt @@ -0,0 +1,20 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.callable + +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.TwoOrOptExplorer + +/** + *

About this class

+ *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 26.06.2018 + */ +class TwoOrOptExplorerCallable( + initialSolution: EVRPTWSolution, + startAtIncl: Int, + endAtIncl: Int, + explorer: TwoOrOptExplorer +) : + INeighbourhoodExplorerCallable(initialSolution, startAtIncl, endAtIncl, explorer) \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/tabusearch/TabuSearch.kt b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/tabusearch/TabuSearch.kt new file mode 100644 index 0000000..abbe021 --- /dev/null +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/metaheuristic/tabusearch/TabuSearch.kt @@ -0,0 +1,103 @@ +package at.ac.tuwien.otl.evrptw.metaheuristic.tabusearch + +/* ktlint-disable no-wildcard-imports */ +import at.ac.tuwien.otl.evrptw.Executor +import at.ac.tuwien.otl.evrptw.dto.EVRPTWSolution +import at.ac.tuwien.otl.evrptw.metaheuristic.Constants.Companion.N_TABU +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.* +import at.ac.tuwien.otl.evrptw.metaheuristic.neighbourhood.callable.* +import java.util.logging.Logger +import java.util.stream.Collectors + +/** + *

About this class

+ * + *

Description

+ * + * @author Daniel Fuevesi + * @version 1.0.0 + * @since 1.0.0 + */ +class TabuSearch(private val logEnabled: Boolean = true) { + private val log: Logger = Logger.getLogger(this.javaClass.name) + + fun apply(solution: EVRPTWSolution): EVRPTWSolution { + var overallBestSolution = EVRPTWSolution(solution) // create deep copy + var bestCandidate = overallBestSolution + val tabuMap = mutableMapOf(bestCandidate to 1) // add incoming solution to prevent its reversal + var iteration = 0 + + while (iteration < N_TABU) { + bestCandidate = bestSolutionOfNeighbourhoods(bestCandidate, tabuMap) + updateTabuMap(bestCandidate, tabuMap) + + if (evaluateSolution(bestCandidate) < evaluateSolution(overallBestSolution)) { + overallBestSolution = bestCandidate + log( + "New local optimum found. Cost: ${overallBestSolution.cost}, " + + "Cap-Violation: ${overallBestSolution.fitnessValue.totalCapacityViolation}, " + + "TW-Violation: ${overallBestSolution.fitnessValue.totalTimeWindowViolation}, " + + "Bat-Violation: ${overallBestSolution.fitnessValue.totalBatteryCapacityViolation}, " + + "Induced by operator: ${overallBestSolution.originOperator}" + ) + } + iteration++ + } + log("Tabu search done") + return overallBestSolution + } + + private fun bestSolutionOfNeighbourhoods( + solution: EVRPTWSolution, + tabuMap: Map + ): EVRPTWSolution { + val solutionsOfAllNeighbourhoods = parallelExploreNeighbourhoods(solution) + + val solutionsNotInTabu = solutionsOfAllNeighbourhoods.filter { !tabuMap.contains(it) } + + if (solutionsNotInTabu.isEmpty()) { + log("NO SOLUTIONS AVAILABLE THAT ARE EITHER FEASIBLE OR NOT IN TABU LIST") + return solution + } + return solutionsNotInTabu.sortedBy { it.fitnessValue.fitness }.first() + } + + private fun parallelExploreNeighbourhoods(solution: EVRPTWSolution): List { + val callableList = mutableListOf>() + val numberOfRoutes = solution.routes.size + val middleOfRoutes = numberOfRoutes / 2 + callableList.add(TwoOptArcExchangeExplorerCallable(solution, 0, middleOfRoutes, TwoOptArcExchangeExplorer())) + callableList.add(TwoOptArcExchangeExplorerCallable(solution, middleOfRoutes, numberOfRoutes, TwoOptArcExchangeExplorer())) + callableList.add(TwoOrOptExplorerCallable(solution, 0, middleOfRoutes, TwoOrOptExplorer())) + callableList.add(TwoOrOptExplorerCallable(solution, middleOfRoutes, numberOfRoutes, TwoOrOptExplorer())) + callableList.add(StationInReExplorerCallable(solution, 0, middleOfRoutes, StationInReExplorer())) + callableList.add(StationInReExplorerCallable(solution, middleOfRoutes, numberOfRoutes, StationInReExplorer())) + callableList.add(InterIntraRouteExchangeExplorerCallable(solution, 0, middleOfRoutes, InterIntraRouteExchangeExplorer())) + callableList.add(InterIntraRouteExchangeExplorerCallable(solution, middleOfRoutes, numberOfRoutes, InterIntraRouteExchangeExplorer())) + callableList.add(InterIntraRouteRelocateExplorerCallable(solution, 0, middleOfRoutes, InterIntraRouteRelocateExplorer())) + callableList.add(InterIntraRouteRelocateExplorerCallable(solution, middleOfRoutes, numberOfRoutes, InterIntraRouteRelocateExplorer())) + val results = Executor.getExecutorService().invokeAll(callableList) + return results.stream().flatMap { it.get().stream() }.collect(Collectors.toList()).toList() + } + + private fun updateTabuMap(solution: EVRPTWSolution, tabuMap: MutableMap) { + // remove elements older than some constant + // don't forget to consider aspiration criteria + tabuMap + .filter { it.value >= 15 } + .keys.toList() + .forEach { tabuMap.remove(it) } + tabuMap[solution] = 0 + tabuMap.entries.forEach { tabuMap[it.key] = it.value + 1 } + } + + private fun evaluateSolution(solution: EVRPTWSolution): Double { + return solution.fitnessValue.fitness + } + + private fun log(message: String) { + if (logEnabled) { + log.info(message) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/at/ac/tuwien/otl/evrptw/verifier/EVRPTWRouteVerifier.java b/src/main/kotlin/at/ac/tuwien/otl/evrptw/verifier/EVRPTWRouteVerifier.java index b131847..c020d40 100644 --- a/src/main/kotlin/at/ac/tuwien/otl/evrptw/verifier/EVRPTWRouteVerifier.java +++ b/src/main/kotlin/at/ac/tuwien/otl/evrptw/verifier/EVRPTWRouteVerifier.java @@ -18,6 +18,8 @@ import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance; import at.ac.tuwien.otl.evrptw.dto.EVRPTWInstance.Node; +import at.ac.tuwien.otl.evrptw.dto.Violations; + import java.util.List; import java.util.Locale; @@ -47,10 +49,10 @@ public boolean verify(List> routes, double cost, boolean detailedMode return Double.isFinite(c); } - public double calculateTotalCost(List> routes) { + public double calculateTotalCost(List> routes, boolean detailedMode) { double c = 0.0; for(int i = 0; i < routes.size(); i++) { - c = c + calculate(routes.get(i), i + 1, true); + c = c + calculate(routes.get(i), i + 1, detailedMode); } return c; } @@ -100,8 +102,8 @@ public double calculate(List route, int id, boolean detailedMode) { timeInNode = instance.getServiceTime(arriveAtNode); if(instance.isRechargingStation(arriveAtNode)) { double refuelTime = - (instance.getVehicleEnergyCapacity() - y) * // used fuel - instance.getRechargingRate(arriveAtNode); // refill rate + (instance.getVehicleEnergyCapacity() - y) * // used fuel + instance.getRechargingRate(arriveAtNode); // refill rate timeInNode = refuelTime; @@ -130,14 +132,14 @@ public double calculate(List route, int id, boolean detailedMode) { boolean infeasible = qInf > 0.0 || TW > 0.0 || yInfeasible > 0.0; if(detailedMode) { System.out.println( - String.format(Locale.ENGLISH, - "Route %d: %s" + "\n" + - "- load:\t\t%4.0f" + "\n" + - "- distance:\t%7.3f" + "\n" + - "- duration:\t%7.3f" + "\n" + - "- violations:\t %s", - id, route.toString(), q, distance, D, (infeasible) ? "YES" : "none" - )); + String.format(Locale.ENGLISH, + "Route %d: %s" + "\n" + + "- load:\t\t%4.0f" + "\n" + + "- distance:\t%7.3f" + "\n" + + "- duration:\t%7.3f" + "\n" + + "- violations:\t %s", + id, route.toString(), q, distance, D, (infeasible) ? "YES" : "none" + )); if(infeasible) { if(qInf > 0.0) System.out.println(String.format(Locale.ENGLISH, "-- load:\t%4.0f", qInf)); @@ -151,4 +153,173 @@ public double calculate(List route, int id, boolean detailedMode) { return Double.POSITIVE_INFINITY; else return distance; } + + public static double calculateTotalTimeWindowViolation(final EVRPTWInstance instance, final List> routes) { + double c = 0.0; + for (int i = 0; i < routes.size(); i++) { + c = c + calculateTimeWindowViolation(instance, routes.get(i), i + 1); + } + return c; + } + + private static double calculateTimeWindowViolation(EVRPTWInstance instance, List route, int id) { + int from = 1; + int to = route.size() - 1; + + Node prevNode = route.get(0); + + double q = 0.0; + double distance = 0.0; + + double y = instance.getVehicleEnergyCapacity(); + double yInfeasible = 0.0; + + double ST = instance.getServiceTime(prevNode); + double D = ST; + double TW = 0.0; + double E = instance.getTimewindow(prevNode).getStart(); + double L = instance.getTimewindow(prevNode).getEnd(); + double deltaWT = 0.0; + double deltaTW = 0.0; + + for (int i = from; i <= to; i++) { + Node arriveAtNode = route.get(i); + + double timeInNode = 0.0; + double travelTime = 0.0; + double delta = 0.0; + + travelTime = instance.getTravelTime(prevNode, arriveAtNode); + distance = distance + instance.getTravelDistance(prevNode, arriveAtNode); + + delta = D - TW + travelTime; + + double travelDistance = instance.getTravelDistance(prevNode, arriveAtNode); + y -= (travelDistance * instance.getVehicleEnergyConsumption()); + if (y < 0.0) { + yInfeasible -= y; + y = 0.0; + } + + deltaWT = Math.max(0, instance.getTimewindow(arriveAtNode).getStart() - delta - L); + deltaTW = Math.max(0, E + delta - instance.getTimewindow(arriveAtNode).getEnd()); + + timeInNode = instance.getServiceTime(arriveAtNode); + if (instance.isRechargingStation(arriveAtNode)) { + double refuelTime = + (instance.getVehicleEnergyCapacity() - y) * // used fuel + instance.getRechargingRate(arriveAtNode); // refill rate + + timeInNode = refuelTime; + + y = instance.getVehicleEnergyCapacity(); + } else { + // do nothing + } + + if (instance.isRechargingStation(arriveAtNode)) { + delta += timeInNode; + deltaWT = Math.max(0, instance.getTimewindow(arriveAtNode).getStart() - delta - L); + deltaTW = Math.max(0, E + delta - instance.getTimewindow(arriveAtNode).getEnd()); + } else + q = q + instance.getDemand(arriveAtNode); + + D = D + timeInNode + deltaWT + travelTime; + ST += timeInNode; + TW = TW + deltaTW; + E = Math.max(instance.getTimewindow(arriveAtNode).getStart() - delta, E) - deltaWT; + L = Math.min(instance.getTimewindow(arriveAtNode).getEnd() - delta, L) + deltaTW; + + prevNode = arriveAtNode; + } + return TW; + } + + public static Violations calculateViolations(final EVRPTWInstance instance, final List> routes) { + double capacityViolations = 0.0; + double timeWindowViolations = 0.0; + double batteryCapacityViolations = 0.0; + for (int i = 0; i < routes.size(); i++) { + final Violations violations = calculateViolationOfRoute(instance, routes.get(i)); + capacityViolations += violations.getCapacityViolation(); + timeWindowViolations += violations.getTimeWindowViolation(); + batteryCapacityViolations += violations.getBatteryCapacityViolation(); + } + return new Violations(capacityViolations, timeWindowViolations, batteryCapacityViolations); + } + + public static Violations calculateViolationOfRoute(EVRPTWInstance instance, List route) { + int from = 1; + int to = route.size() - 1; + + Node prevNode = route.get(0); + + double q = 0.0; + double distance = 0.0; + + double y = instance.getVehicleEnergyCapacity(); + double yInfeasible = 0.0; + + double ST = instance.getServiceTime(prevNode); + double D = ST; + double TW = 0.0; + double E = instance.getTimewindow(prevNode).getStart(); + double L = instance.getTimewindow(prevNode).getEnd(); + double deltaWT = 0.0; + double deltaTW = 0.0; + + for (int i = from; i <= to; i++) { + Node arriveAtNode = route.get(i); + + double timeInNode = 0.0; + double travelTime = 0.0; + double delta = 0.0; + + travelTime = instance.getTravelTime(prevNode, arriveAtNode); + distance = distance + instance.getTravelDistance(prevNode, arriveAtNode); + + delta = D - TW + travelTime; + + double travelDistance = instance.getTravelDistance(prevNode, arriveAtNode); + y -= (travelDistance * instance.getVehicleEnergyConsumption()); + if (y < 0.0) { + yInfeasible -= y; + y = 0.0; + } + + deltaWT = Math.max(0, instance.getTimewindow(arriveAtNode).getStart() - delta - L); + deltaTW = Math.max(0, E + delta - instance.getTimewindow(arriveAtNode).getEnd()); + + timeInNode = instance.getServiceTime(arriveAtNode); + if (instance.isRechargingStation(arriveAtNode)) { + double refuelTime = + (instance.getVehicleEnergyCapacity() - y) * // used fuel + instance.getRechargingRate(arriveAtNode); // refill rate + + timeInNode = refuelTime; + + y = instance.getVehicleEnergyCapacity(); + } else { + // do nothing + } + + if (instance.isRechargingStation(arriveAtNode)) { + delta += timeInNode; + deltaWT = Math.max(0, instance.getTimewindow(arriveAtNode).getStart() - delta - L); + deltaTW = Math.max(0, E + delta - instance.getTimewindow(arriveAtNode).getEnd()); + } else + q = q + instance.getDemand(arriveAtNode); + + D = D + timeInNode + deltaWT + travelTime; + ST += timeInNode; + TW = TW + deltaTW; + E = Math.max(instance.getTimewindow(arriveAtNode).getStart() - delta, E) - deltaWT; + L = Math.min(instance.getTimewindow(arriveAtNode).getEnd() - delta, L) + deltaTW; + + prevNode = arriveAtNode; + } + + double qInf = Math.max(0.0, q - instance.getVehicleCapacity()); + return new Violations(qInf, TW, yInfeasible); + } }