I am using Tinkerpop Blueprints to create a OrientDB graph for a dataset with millions of nodes and 100M edges on a laptop with 16gb memory, 64-bit Ubuntu, 64-bit jvm.
Here are the results from our benchmarking – the vertices get added fine but each edge addition takes almost a second.
Edge Creation Latency
Can you suggest what we are not doing right wrt to the edge creation and how we can get it to improve to a more reasonable latency?
Here's the code associated with the above table:
package orientdbtest
import com.orientechnologies.orient.core.metadata.schema.OType
import com.tinkerpop.blueprints.{Direction, Edge, Vertex}
import com.tinkerpop.blueprints.impls.orient._
import com.orientechnologies.orient.core.intent.OIntentMassiveInsert
object Example {
def addRandomVertex(graph: OrientGraphNoTx, uuid: String) : Vertex = graph.addVertex("class:Random", "uuid", uuid)
def addRandomEdge(source: Vertex, target: Vertex, id: String) = graph.addEdge(null, source, target, id)
def createRandomNetworkDatabase(graph: OrientGraphNoTx, numNodes: Int, numEdges: Int, useLightWeightEdges: Boolean):(Long, Long) = {
if (graph.getVertexType("Random") == null) {
println("Creating random type")
val random_vertex_type: OrientVertexType = graph.createVertexType("Random")
random_vertex_type.createProperty("id", OType.STRING)
} else {
println("Random type exists")
}
val timeStartNodeCreation = System.currentTimeMillis()
val nodeList: List[Vertex] = Range(0,numNodes).toList.map(x => addRandomVertex(graph, x.toString()))
val timeEndNodeCreation = System.currentTimeMillis()
println("Time to create " + numNodes + " is " + (timeEndNodeCreation-timeStartNodeCreation))
val nodeListFirstHalf = nodeList.slice(0,nodeList.length/2)
val nodeListSecondHalf = nodeList.slice(nodeList.length/2+1,nodeList.length)
var edgeID = 1
if(useLightWeightEdges) graph.setUseLightweightEdges(true)
val timeStartEdgeCreation = System.currentTimeMillis()
// createEdges from first half to the second half
nodeListFirstHalf.foreach(sourceVertex => {
nodeListSecondHalf.foreach(targetVertex => {
while(edgeID < numEdges)
{
addRandomEdge(sourceVertex, targetVertex, edgeID.toString())
edgeID = edgeID +1
graph.commit()
}
})
})
val timeEndEdgeCreation = System.currentTimeMillis()
println("Time to create " + edgeID + " is " + (timeEndEdgeCreation-timeStartEdgeCreation))
(0L, 0L)
}
def main(args: Array[String]): Unit = {
val numNodes = 10
val numEdges = 25
val useLightWeightEdges = false
val uri: String = "plocal:target/database/random_sample_" + numNodes + "_" + numEdges + useLightWeightEdges.toString()
val graph: OrientGraphNoTx = new OrientGraphNoTx(uri)
graph.setKeepInMemoryReferences(false);
graph.getRawGraph().getLocalCache().setEnable(false)
graph.declareIntent(new OIntentMassiveInsert())
try {
createRandomNetworkDatabase(graph, numNodes, numEdges, useLightWeightEdges)
graph.declareIntent(null)
} finally {
graph.shutdown()
}
println("Adios")
}
}
Related
I'm trying to test a method in my Service Implementation with JUnit and MockK library.
PlanServiceFeatureImplTest.kt
#Test
fun `storeInstallmentPlan`(#MockK user: User) {
val debtId = 123L
val amount = BigDecimal.valueOf(1000)
val interval = Repeat.monthly
val firstPaymentDate = LocalDate.of(
2021, 11, 4
).atStartOfDay(Time.DEFAULT_TIME_ZONE).toOffsetDateTime()
val planDTO = InstallmentPlanDTO(
interval = interval,
firstPaymentDate = firstPaymentDate,
amount = amount,
installments = listOf()
)
val debt = Debt(
userId = 32L,
title = "debt1",
amount = amount,
category = DebtCategory.credit
)
val plan = InstallmentPlan(
id = 122L,
debtId = debtId,
interval = interval,
firstPaymentDate = firstPaymentDate,
amount = amount
)
val installment1 = Installment(
id = 34,
debtId = debtId,
recordId = 13
)
val installment2 = Installment(
id = 35,
debtId = debtId,
recordId = 14
)
val newStartDate = ZonedDateTime.parse("2021-10-05T00:00+02:00[Europe/Berlin]")
val newEndDate = ZonedDateTime.parse("2021-11-04T00:00+01:00[Europe/Berlin]")
val installments = listOf(
WalletRecord(
userId = debt.userId,
type = RecordType.debt_rate,
amount = plan.amount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = plan.interval,
startDate = newStartDate.toOffsetDateTime(),
endDate = newEndDate.toOffsetDateTime(),
)
)
val records = flowOf(
WalletRecord(
id = 43,
userId = debt.userId,
type = RecordType.debt_rate,
amount = plan.amount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = plan.interval,
startDate = newStartDate.toOffsetDateTime(),
endDate = newEndDate.toOffsetDateTime(),
)
)
every { user.tz } returns "Europe/Berlin"
coEvery { debtRepository.findById(debtId) } returns debt
coEvery { installmentPlanRepository.findByDebtId(debtId) } returns plan
coEvery {
installmentPlanRepository.save(
planDTO.copy(
amount = amount
).toEntity(
id = plan.id,
debtId = debtId
)
)
} returns InstallmentPlan(plan.id, debtId, interval, firstPaymentDate, amount )
coEvery { installmentRepository.findByDebtId(debtId) } returns flowOf(installment1, installment2)
coEvery {
installmentRepository.deleteAllById(listOf(installment1.id!!, installment2.id!!).asIterable())
} just Runs
coEvery { recordService.deleteAll(listOf(installment1.recordId, installment2.recordId)) } just Runs
coEvery { userService.findById(debt.userId) } returns user
coEvery { recordService.saveAll(installments) } returns records
coEvery {
installmentRepository.saveAll(
records.map {
Installment(
debtId = debtId,
recordId = it.id!!
)
}
).map { it.recordId }
} returns flowOf(43)
runBlocking { planService.storeInstallmentPlan(debtId, planDTO) }
}
PlanServiceFeatureImpl.kt
#Transactional
override suspend fun storeInstallmentPlan(debtId: Long, installmentPlanDTO: InstallmentPlanDTO): Flow<Long> {
val debt = debtRepository.findById(debtId) ?: throw NotFoundException("Could not find debt with id $debtId")
val installmentPlanId = installmentPlanRepository.findByDebtId(debtId)?.id
val minimumAmount =
BigDecimal.ONE.max(debt.amount.multiply(MINIMUM_INSTALLMENT_PERCENTAGE_OF_TOTAL_AMOUNT, MathContext.DECIMAL32))
.setScale(2, RoundingMode.HALF_UP)
.stripTrailingZeros()
val maximumAmount = debt.amount
val clampedAmount = maximumAmount.min(minimumAmount.max(installmentPlanDTO.amount))
val installmentPlan = installmentPlanDTO.copy(
amount = clampedAmount,
).toEntity(
id = installmentPlanId,
debtId = debtId,
)
installmentPlanRepository.save(installmentPlan)
// delete existing records associated with the installment plan
val existingInstallments = installmentRepository.findByDebtId(debtId).toList()
installmentRepository.deleteAllById(existingInstallments.map { it.id!! })
recordService.deleteAll(existingInstallments.map { it.recordId })
// calculate installments / records
/*
This calculation follows this invariant:
debt.amount = countOfFullInstallments * amount + lastInstallment
*/
val user = userService.findById(debt.userId)
?: throw NotFoundException("Could not find user that owns debt $debtId")
val zoneId = ZoneId.of(user.tz)
val firstPaymentDate = installmentPlan.firstPaymentDate.atZoneSameInstant(zoneId)
val countOfFullInstallments =
debt.amount.divide(
(if (installmentPlan.amount <= BigDecimal.ONE) BigDecimal.ONE else installmentPlan.amount),
MathContext.DECIMAL32
)
.setScale(0, RoundingMode.DOWN)
.intValueExact()
val lastInstallmentAmount = debt.amount - installmentPlan.amount * countOfFullInstallments.toBigDecimal()
val countOfInstallments = countOfFullInstallments + if (lastInstallmentAmount > BigDecimal.ZERO) 1 else 0
val installments = List(countOfInstallments) { i ->
val endDate =
addInterval(firstPaymentDate, installmentPlan.interval, i)
val startDate = addInterval(firstPaymentDate, installmentPlan.interval, i - 1)
.plusDays(1)
val recordAmount = if (i < countOfFullInstallments)
installmentPlan.amount
else lastInstallmentAmount
WalletRecord(
userId = debt.userId,
type = RecordType.debt_rate,
amount = recordAmount,
title = debt.title,
category = RecordCategory.debt_rate,
repeat = installmentPlan.interval,
startDate = startDate.toOffsetDateTime(),
endDate = endDate.toOffsetDateTime(),
)
}
val recordsFlow = recordService.saveAll(installments)
return installmentRepository.saveAll(recordsFlow.map {
Installment(
debtId = debtId,
recordId = it.id!!,
)
}).map { it.recordId }
}
I get this error:
no answer found for: InstallmentRepository(installmentRepository#4).saveAll(app.backend.plan.PlanServiceFeatureImpl$storeInstallmentPlan$suspendImpl$$inlined$map$1#31e1ec3)
io.mockk.MockKException: no answer found for: InstallmentRepository(installmentRepository#4).saveAll(app.backend.plan.PlanServiceFeatureImpl$storeInstallmentPlan$suspendImpl$$inlined$map$1#31e1ec3)
It is a lot of code, but since I don't know where the error comes from I provide the full code. In other cases with the error 'no answer found for' it provided me something like '[...]InstallmentRepository(...).saveAll([parameters here])' and not a path to the Unit under test.
Hope someone can help me with this.
You are trying to mock the following call:
installmentRepository.saveAll(recordsFlow.map {
Installment(
debtId = debtId,
recordId = it.id!!,
)
}).map { it.recordId }
But what you really need to mock is only saveAll(), not the map() after it as follows:
coEvery { installmentRepository.saveAll(
records.map {
Installment(
debtId = debtId,
recordId = it.id!!
)
}
)
} returns flowOf(Installment(debtId, 43))
If this does not work, try the following (with a less strict matching):
coEvery { installmentRepository.saveAll(ArgumentMatchers.any()) } returns flowOf(Installment(debtId, 43))
I am trying to solve the reconstruct itinerary problem (https://leetcode.com/problems/reconstruct-itinerary/) in Scala using functional approach. Java solution works but Scala doesn't. One reason I found out was the hashmap is being updated and every iteration has the latest hashmap (even when popping from recursion) which is weird.
Here is the solution in Java:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
public class Solution1 {
private void dfg(Map<String, PriorityQueue<String>> adj, LinkedList<String> result, String vertex){
PriorityQueue<String> pq = adj.get(vertex);
while (pq!=null && !pq.isEmpty()){
System.out.println("Before :"+adj.get(vertex));
String v = pq.poll();
System.out.println("After :"+ adj.get(vertex));
dfg(adj,result,v);
}
result.addFirst(vertex);
}
public List<String> findItinerary(List<List<String>> tickets){
Map<String,PriorityQueue<String>> adj = new HashMap<>();
for(List<String> ticket: tickets){
adj.putIfAbsent(ticket.get(0),new PriorityQueue<>());
adj.get(ticket.get(0)).add(ticket.get(1));
}
LinkedList<String> result = new LinkedList<>();
dfg(adj,result,"JFK");
//not reverse yet
return result;
}
public static void main(String[] args){
List<List<String>> tickets = new ArrayList<>();
List t1= new ArrayList();
t1.add("JFK");
t1.add("SFO");
tickets.add(t1);
List t2= new ArrayList();
t2.add("JFK");
t2.add("ATL");
tickets.add(t2);
List t3= new ArrayList();
t3.add("SFO");
t3.add("ATL");
tickets.add(t3);
List t4= new ArrayList();
t4.add("ATL");
t4.add("JFK");
tickets.add(t4);
List t5= new ArrayList();
t5.add("ATL");
t5.add("SFO");
tickets.add(t5);
System.out.println();
Solution1 s1 = new Solution1();
List<String> finalRes = s1.findItinerary(tickets);
for(String model : finalRes) {
System.out.print(model + " ");
}
}
}
Here is my solution in Scala which is not working:
package graph
class Itinerary {
}
case class Step(g: Map[String,List[String]],sort: List[String]=List())
object Solution {
def main(arr: Array[String]) = {
val tickets = List(List("JFK","SFO"),List("JFK","ATL"),List("SFO","ATL"),List("ATL","JFK"),List("ATL","SFO"))
println(findItinerary(tickets))
}
def findItinerary(tickets: List[List[String]]): List[String] = {
val g = tickets.foldLeft(Map[String,List[String]]())((m,t)=>{
val key=t(0)
val value= t(1)
m + (key->(m.getOrElse(key,Nil) :+ value).sorted)
})
println(g)
// g.keys.foldLeft(Step())((s,n)=> dfs(n,g,s)).sort.toList
dfs("JFK",Step(g)).sort.toList
}
def dfs(vertex: String,step: Step): Step = {
println("Input vertex " + vertex)
println("Input map "+ step.g)
val updatedStep= step.g.getOrElse(vertex,Nil).foldLeft(step) ((s,n)=>{
//println("Processing "+n+" of vertex "+vertex)
//delete link
val newG = step.g + (vertex->step.g.getOrElse(vertex,Nil).filter(v=>v!=n))
// println(newG)
dfs(n,step.copy(g=newG))
})
println("adding vertex to result "+vertex)
updatedStep.copy(sort = updatedStep.sort:+vertex)
}
}
Scala is sometimes approached as a "better" Java, but that's really very limiting. If you can get into the FP mindset, and study the Standard Library, you'll find that it's a whole new world.
def findItinerary(tickets: List[List[String]]): List[String] = {
def loop(from : String
,jump : Map[String,List[String]]
,acc : List[String]) : List[String] = jump.get(from) match {
case None => if (jump.isEmpty) from::acc else Nil
case Some(next::Nil) => loop(next, jump - from, from::acc)
case Some(nLst) =>
nLst.view.map{ next =>
loop(next, jump+(from->(nLst diff next::Nil)), from::acc)
}.find(_.lengthIs > 0).getOrElse(Nil)
}
loop("JFK"
,tickets.groupMap(_(0))(_(1)).map(kv => kv._1 -> kv._2.sorted)
,Nil).reverse
}
I am going to be honest that I didn't look through your code to see where the problem was. But, I got caught by the problem and decided to give it a go; here is the code:
(hope my code helps you)
type Airport = String // Refined 3 upper case letters.
final case class AirlineTiket(from: Airport, to: Airport)
object ReconstructItinerary {
// I am using cats NonEmptyList to improve type safety, but you can easily remove it from the code.
private final case class State(
currentAirport: Airport,
availableDestinations: Map[Airport, NonEmptyList[Airport]],
solution: List[Airport]
)
def apply(tickets: List[AirlineTiket])(start: Airport): Option[List[Airport]] = {
#annotation.tailrec
def loop(currentState: State, checkpoints: List[State]): Option[List[Airport]] = {
if (currentState.availableDestinations.isEmpty) {
// We used all the tickets, so we can return this solution.
Some((currentState.currentAirport :: currentState.solution).reverse)
} else {
val State(currentAirport, availableDestinations, solution) = currentState
availableDestinations.get(currentAirport) match {
case None =>
// We got into nowhere, lets see if we can return to a previous state...
checkpoints match {
case checkpoint :: remaining =>
// If we can return from there
loop(currentState = checkpoint, checkpoints = remaining)
case Nil =>
// If we can't, then we can say that there is no solution.
None
}
case Some(NonEmptyList(destination, Nil)) =>
// If from the current airport we can only travel to one destination, we will just follow that.
loop(
currentState = State(
currentAirport = destination,
availableDestinations - currentAirport,
currentAirport :: solution
),
checkpoints
)
case Some(NonEmptyList(destination, destinations # head :: tail)) =>
// If we can travel to more than one destination, we are going to try all in order.
val newCheckpoints = destinations.map { altDestination =>
val newDestinations = NonEmptyList(head = destination, tail = destinations.filterNot(_ == altDestination))
State(
currentAirport = altDestination,
availableDestinations.updated(key = currentAirport, value = newDestinations),
currentAirport :: solution
)
}
loop(
currentState = State(
currentAirport = destination,
availableDestinations.updated(key = currentAirport, value = NonEmptyList(head, tail)),
currentAirport :: solution
),
newCheckpoints ::: checkpoints
)
}
}
}
val availableDestinations = tickets.groupByNel(_.from).view.mapValues(_.map(_.to).sorted).toMap
loop(
currentState = State(
currentAirport = start,
availableDestinations,
solution = List.empty
),
checkpoints = List.empty
)
}
}
You can see the code running here.
I make KorGE game with dragonbone animation. How to mirror this dragonbones animation? I want to character look to the right instead of the left:)
val factory = KorgeDbFactory()
val skeDeferred = asyncImmediately { Json.parse(resourcesVfs["Ubbie/Ubbie_ske.json"].readString())!! }
val texDeferred = asyncImmediately { resourcesVfs["Ubbie/Ubbie_tex.json"].readString() }
val imgDeferred = asyncImmediately { resourcesVfs["Ubbie/Ubbie_tex.png"].readBitmap().mipmaps() }
factory.parseDragonBonesData(skeDeferred.await())
factory.parseTextureAtlasData(Json.parse(texDeferred.await())!!, imgDeferred.await())
val armatureDisplay = factory.buildArmatureDisplay("ubbie")!!.position(600, 720).scale(1)
armatureDisplay.animation.play("walk")
addUpdater {
this += armatureDisplay
}
SCALE_X = -1 gives mirror effect
val SCALE_X=-1
val SCALE_Y=1
...
val armatureDisplay = factory.buildArmatureDisplay("ubbie")!!.position(600, 720).scale(SCALE_X, SCALE_Y)
I need to check how long does a function need to run. I have the following functions which address the same task:
mixAnimalsA
fun mixAnimalsA(a1: Animal, a2: Animal) =
when (setOf(a1, a2)) {
setOf(Animal.OWL, Animal.Leopard) -> Beast.OWLPARD
setOf(Animal.ELEPHANT, Animal.BUTTERFLY) -> Beast.BUTTERPHANT
else -> throw Exception("Not possible combination")
}
mixAnimalsB
fun mixAnimalsB(a1: Animal, a2: Animal) =
when (setOf(a1, a2)) {
(c1 == Animal.OWL && c2 == Animal.Leopard) ||
(c2 == Animal.OWL && c1 == Animal.Leopard) -> Beast.OWLPARD
(c1 == Animal.ELEPHANT && c2 == Animal.BUTTERFLY) ||
(c2 == Animal.ELEPHANT && c1 == Animal.BUTTERFLY)-> Beast.BUTTERPHANT
else -> throw Exception("Not possible combination")
}
Animal and Beast are enumerations. How can I measure how long each function takes to run?
If you're looking for an in-code solution, you can use measureTimeMillis and measureNanoTime, like this:
val time = measureTimeMillis {
// call your function here
}
They return the measured time in milliseconds and nanoseconds, respectively.
Measure execution time and also keep the result
Standard Library
The standard library function measureTimedValue may be used to measure execution time and at the same time capture the result. This tuple of values is being exposed as a TimedValue(value: T, duration: Duration):
#ExperimentalTime
fun main() {
val (result: String, duration: Duration) = measureTimedValue {
operation()
}
print("Got $result after ${duration.inMilliseconds} ms.")
}
Note that this API is experimental and requires explicit opt-in.
Obsolete custom implementation
(This used to be my answer before the standard lib was extended)
If you want to measure the execution time and also access the measured function's return value afterward, here's a custom solution:
inline fun <R> executeAndMeasureTimeMillis(block: () -> R): Pair<R, Long> {
val start = System.currentTimeMillis()
val result = block()
return result to (System.currentTimeMillis() - start)
}
You can call it like this:
val (response, duration) = executeAndMeasureTimeMillis {
restTemplate.getForObject<AnyObject>(uri)
}
If it's enough to get the time as output on the console:
fun <T> timeIt(message: String = "", block: () -> T): T {
val start = System.currentTimeMillis()
val r = block()
val end = System.currentTimeMillis()
println("$message: ${end - start} ms")
return r
}
Usage:
val result = timeIt("note about the code") {
// do something...
}
Output (example):
note about the code: 1ms
For the benchmark of some code block and getting the result a same time, i do some refactor of the standard method in TimingKt class
to give us output generic result and at the same time display a given log.
Here is my example :
/**
* Executes the given block and show the elapsed time in milliseconds in a given message.
*
* #param block which will be bench marked
* #param logMessage log message to be displayed
*
* #return a generic result
*
*/
private fun <T> measureTime(block: () -> T, logMessage: String): T {
val start = System.currentTimeMillis()
val t = block()
val consumedTime = System.currentTimeMillis() - start
Log.d(TAG, "Calculation of $logMessage time :$consumedTime milliseconds")
return t
}
And how it will be used :
return measureTime({
// given block with return result
}, "message to be displayed typically the name of method which will be calculated")
This is my simple time test code.
class TimeCounter(val name: String) {
var totalTime: Long = 0
private set
var count: Int = 0
private set
var minTime: Long = Long.MAX_VALUE
private set
var maxTime: Long = Long.MIN_VALUE
private set
fun addTime(time: Long) {
this.count++
this.totalTime += time
if (this.minTime > time) {
this.minTime = time
}
if (this.maxTime < time) {
this.maxTime = time
}
}
val averageTime: Double
get() = this.totalTime / this.count.toDouble()
fun printTime() {
println("(time about : '$name'), totalTime : $totalTime, count : $count, " +
"average : $averageTime, minTime : $minTime, maxTime : $maxTime")
}
fun <T> runWithTimeCount(run: () -> T): T {
val startTime = System.currentTimeMillis()
return run().also {
this.addTime(System.currentTimeMillis() - startTime)
}
}
}
you can use 'TimeCounter' like this, (example)
var sum = 0
val testTimeCounter = TimeCounter("logic1")
for(i in 0 until 100){
sum += testTimeCounter.runWithTimeCount {
logic1(i) // your logic
}
}
println(sum)
testTimeCounter.printTime() // totalTime, average, minTime, maxTime
Execute function, measure its performance and logs performance - in same call
this solution will help folks who want to measure and log performance, execute function at same time, also is a cleaner approach when there is multiple performance measurement involved of different functions
Create functions as such:
//the inline performance measurement method
private inline fun <T> measurePerformanceInMS(
logger: (Long) -> Unit,
function: () -> T)
: T {
val startTime = System.currentTimeMillis()
val result: T = function.invoke()
val endTime = System.currentTimeMillis()
logger.invoke( endTime - startTime)
return result
}
//the logger function
fun logPerf(time: Long){
Log.d("TAG","PERFORMANCE IN MS: $time ms ")
}
//the function whose performance needs to be checked
fun longRunningFunction() : Int{
var x = 0
for (i in 1..20000) x++
return x
}
This way you can keep logging, performance computation and function execution under a single function call and no code replication needed.
If you require nano second measurement then use System.nanoTime()
USAGE :
val endResult = measurePerformanceInMS({time -> logPerf(time)}){
longRunningFunction()
}
NOTE : here the 'endResult' will carry the result of function whose performance was being measured.
I am working on an algorithm and it seems to be working fine, apart from one thing.
Let me first show you the code and then I will explain what the code does and what the problem is.
public Triple<List<ROUTE>, Integer, List<Customer>> LocalSearch()
{
int noImprLS = 0;
boolean initialization = false;
List<ROUTE> bestRoutes = startRoutes;
int bestProfit = profit;
List<Customer> bestU = u;
List<ROUTE> tempBestRoutes = startRoutes;
int tempBestProfit = profit;
List<Customer> tempBestU = u;
int tempBestDistance = totalDistance(tempBestRoutes);
ELIMINATOR e = new ELIMINATOR(bestU, bestRoutes, bestProfit, initialization, name, rnd);
while (noImprLS <= noImprUB)
{
System.out.print(noImprLS);
boolean improvement = false;
long starttime = System.nanoTime();
double timeE = 0;
for (int i = 1; i <= N; i++)
{
long starttimeE = System.nanoTime();
e = new ELIMINATOR(bestU, bestRoutes, bestProfit, initialization, name, rnd);
timeE = timeE + (System.nanoTime()-starttimeE)/1000000000.0;
POSTPROCEDURE pp = new POSTPROCEDURE(e.getRoutes(), profitRoutes(e.getRoutes()), e.getU(), name);
for (int p = 0; p < pp.getBestSolution().size(); p++)
{
ROUTE r = pp.getBestSolution().get(p);
addToPOOL(r);
}
int tempprofit = pp.getTP();
int tempdistance = pp.getTD();
if (tempprofit > tempBestProfit)
{
tempBestRoutes = pp.getBestSolution();
tempBestProfit = tempprofit;
tempBestU = pp.getU();
tempBestDistance = tempdistance;
}
else if (tempprofit == tempBestProfit)
{
if (tempdistance < tempBestDistance)
{
tempBestRoutes = pp.getBestSolution();
tempBestProfit = tempprofit;
tempBestU = pp.getU();
tempBestDistance = tempdistance;
}
}
}
if (tempBestProfit > bestProfit)
{
// Move to better neighbor
bestRoutes = tempBestRoutes;
bestProfit = tempBestProfit;
bestU = tempBestU;
noImprLS = 0;
improvement = true;
System.out.print(" total profit: " + bestProfit);
}
else if (tempBestProfit == bestProfit)
{
if (totalDistance(tempBestRoutes) < totalDistance(bestRoutes))
{
// Move to better neighbor
bestRoutes = tempBestRoutes;
bestProfit = tempBestProfit;
bestU = tempBestU;
noImprLS = 0;
improvement = true;
System.out.print(" total profit: " + bestProfit + " total distance: " + totalDistance(bestRoutes));
}
}
if (improvement == false)
{
noImprLS++;
}
long endtime = System.nanoTime();
double duration = (endtime - starttime)/1000000000.0;
System.out.print(" duration: " + duration + " timeE: " + timeE + "\n");
}
Explanation
I know that the code is quite lengthy, but it is all quite important. In this code, I am writing an algorithm for the Team Orienteering Problem with Time Windows (extensive case of the Vehicle Routing Problems). My aim is to find a good set of routes with maximum profit. In the example below, bestRoutes and tempBestRoutes consist of 4 different routes, profit (bestProfit/tempBestProfit) is equal to the total profit of these routes respectively, and (temp)bestU is a list of customers that are not included in my route yet.
The problem now is with ELIMINATOR. This method removes and adds some customers. The output of this class is used for PostProcedure that also changes some facts in the routes.
I hope it is kind of clear now what my code is doing. I am considering N neighbourhoods and I will choose the best one. If the best one is not better than my starting solution, I increase noImprLS with one. I keep on considering new nieghbours until my upperbound on the number of consecutive iterations without improvement is met.
Problem
The problem now is that if I have not found a better solution, and hence I keep on inserting the same routes and profit in ELIMINATOR, my computation time increases.
A few examples where duration indicates how long an iteration within the while loop takes, and timeE indicates what the total time of ELIMINATOR in the for loop is. It is clear that ELIMINATOR causees the duration to increase.
0 total profit: 800 duration: 0.486570471 timeE: 0.16644330999999998
0 total profit: 900 duration: 0.431213528 timeE: 0.11342619799999998
0 total profit: 950 duration: 0.444671005 timeE: 0.12090608200000001
0 total profit: 960 duration: 0.519406695 timeE: 0.16836757300000005
0 duration: 0.460473438 timeE: 0.137813155
1 duration: 0.572109775 timeE: 0.30774360900000003
2 duration: 0.698965292 timeE: 0.471859029
3 duration: 0.918376211 timeE: 0.686916669
4 duration: 1.165481175 timeE: 0.92621492
5 duration: 1.326080436 timeE: 1.0874366910000002
6 duration: 2.006102605 timeE: 1.674879135
7 duration: 2.787172112 timeE: 2.4276636639999993
8 duration: 2.042213493 timeE: 1.7967797849999998
9 duration: 2.652985618 timeE: 2.3503671230000003
10 duration: 2.422183993 timeE: 2.1859969810000006
The ELIMINATOR CODE:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
public class ELIMINATOR extends BASIS
{
private List<Customer> u;
private List<ROUTE> routes;
private int totalprofit;
private Random prob;
public ELIMINATOR(List<Customer> u, List<ROUTE> routes, int profit, boolean initialization, String name, Random rnd)
{
super(name);
this.u = u;
this.routes = routes;
this.totalprofit = profit;
this.prob = rnd;
if (initialization == true)
{
addCustomers();
for (ROUTE route : routes)
{
checkCorrectNess(route, "after adding procedure in eliminator");
}
}
else
{
removeCustomers();
for (ROUTE route : routes)
{
checkCorrectNess(route, "after removing procedure in eliminator");
}
addCustomers();
for (ROUTE route : routes)
{
checkCorrectNess(route, "after removing and adding procedure in eliminator");
}
}
}
public void removeCustomers()
{
double Ph = 0.1;
double Pl = 0.3;
double total_profit = totalprofit;
int num_customers = 0;
// Calculate the total profit and total number of customers in the routes
for(ROUTE route : routes)
{
num_customers = num_customers + (route.getLocations().size()-2);
}
// Calculate average profit
double average_profit = total_profit/num_customers;
// For each customer on each route, determine whether he/she will be removed
for(ROUTE r : routes)
{
List<RouteNode> route = r.getLocations();
int routesize = route.size();
int j = 1;
while (j < routesize-1)
{
boolean removed = false;
RouteNode node = route.get(j);
if (node.customer.getProfit() >= average_profit)
{
if (prob.nextDouble() < Ph)
{
removed = true;
RouteNode node_toberemoved = node;
int index_node = route.indexOf(node);
route.remove(index_node);
u.add(node.customer);
route = removal(route, node_toberemoved, index_node);
r.setLocations(route);
r.setDistance(distanceOneRoute(route));
r.setProfit(profitOneRoute(route));
checkCorrectNess(r, "remove customers eliminator");
}
}
else
{
if (prob.nextDouble() < Pl)
{
removed = true;
RouteNode node_toberemoved = node;
int index_node = route.indexOf(node);
route.remove(index_node);
u.add(node.customer);
route = removal(route, node_toberemoved, index_node);
r.setLocations(route);
r.setDistance(distanceOneRoute(route));
r.setProfit(profitOneRoute(route));
checkCorrectNess(r, "remove customers eliminator");
}
}
if (removed == false)
{
j++;
}
else
{
routesize = route.size();
total_profit = total_profit-node.customer.getProfit();
average_profit = total_profit/num_customers;
}
}
}
totalprofit = profitRoutes(routes);
}
public void addCustomers()
{
List<Customer> u_copy = new ArrayList<Customer>(u);
List<Customer> u_temp = new ArrayList<Customer>(u);
for (Customer c : u_temp)
{
boolean added = false;
for (ROUTE r : routes)
{
checkCorrectNess(r, "add customers eliminator");
if (added == true)
{
break;
}
Customer customer = c;
u_copy.remove(c);
List<RouteNode> route = r.getLocations();
for (int i = 0; i < route.size()-1; i++)
{
RouteNode possibleNode = new RouteNode();
possibleNode.customer = customer;
List<Integer> distances = calculateDistances(route.get(i), possibleNode, route.get(i+1));
// Calculate shift for customer under consideration
int arrivalTime = route.get(i).timeStartService+ route.get(i).customer.getService() + distances.get(0);
int wait = Math.max(0, customer.getOpeningTW()-arrivalTime);
int serviceDuration = customer.getService();
int shift = distances.get(0) + wait + serviceDuration + distances.get(2) - distances.get(1);
// Determine Start Service
int startServiceTime = Math.max(customer.getOpeningTW(), arrivalTime);
// Obtain waiting time of next customer
int waiting_next = route.get(i+1).wait;
// Obtain MaxShift of next customer
int maxShift = route.get(i+1).maxShift;
if (shift <= (waiting_next + maxShift) & startServiceTime <= customer.getClosingTW() )
{
// Customer can be inserted
added = true;
RouteNode newNode = new RouteNode();
newNode.customer = customer;
newNode.arrivalTime = arrivalTime;
newNode.timeStartService = startServiceTime;
newNode.shift = shift;
newNode.wait = wait;
int pos_insertion = i + 1;
route = ADD(route, newNode, pos_insertion);
r.setLocations(route);
r.setDistance(distanceOneRoute(route));
r.setProfit(profitOneRoute(route));
checkCorrectNess(r, "add customers eliminator");
// exit the last for loop
break;
}
}
}
if (added == false)
{
u_copy.add(c);
}
}
u = u_copy;
totalprofit = profitRoutes(routes);
}
/**
* Returns list of unvisited customers
* #return
*/
public List<Customer> getU()
{
return u;
}
/**
* Returns list of routes
* #return
*/
public List<ROUTE> getRoutes()
{
return routes;
}
}