You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
506 lines
14 KiB
506 lines
14 KiB
package sim
|
|
|
|
import model.HHCTypes
|
|
import scala.reflect.ClassTag
|
|
import scala.collection.mutable.{ArrayBuffer, ListBuffer}
|
|
import scala.collection.immutable.ArraySeq
|
|
import model.HHCEdge2
|
|
import util.Util
|
|
import model.Customer
|
|
import scala.io.BufferedSource
|
|
import model.Coord
|
|
import scala.util.Random
|
|
import com.typesafe.scalalogging.LazyLogging
|
|
import cats.implicits
|
|
import scala.util.chaining._
|
|
|
|
class HHCSim2(
|
|
epsilonMax: Int = 0,
|
|
iterations: Int = 0,
|
|
WLDMax: Float = 0,
|
|
customers: String Either ArraySeq[Customer]
|
|
) extends LazyLogging {
|
|
import HHCSim2._
|
|
import Ordering.Double.IeeeOrdering
|
|
|
|
private var adjMatrix: Option[GraphMatrix[Double]] = None
|
|
def go() = {
|
|
customers
|
|
.flatMap(checkEmpty)
|
|
.map(formAdjMatrix)
|
|
.tap(logGraph)
|
|
.tap(_.foreach(e => adjMatrix = Some(e)))
|
|
.map(e => mstUsingPrims(e))
|
|
.tap(logMST)
|
|
.map { case (mst, epsilon) => removeEdges(mst)(epsilon) }
|
|
.tap(logMST2)
|
|
.map { case (mstUpdated, removed) => findClusters(mstUpdated)(removed) }
|
|
.map {
|
|
case (mstUpdated, clusters, edgeMappings, removedEdges) =>
|
|
printClusters(clusters)
|
|
val e =
|
|
groupClusters(mstUpdated, clusters, edgeMappings, removedEdges)
|
|
printGroups(e._5)
|
|
e
|
|
}
|
|
.flatMap(value => {
|
|
val (mst, clusters, edgeMappings, removed, clusterGroups) = value
|
|
balanceWorkload(clusters, clusterGroups)
|
|
})
|
|
}
|
|
// def go2() = {
|
|
// val res = go().flatMap(value => {
|
|
// val (mst, clusters, edgeMappings, removed, clusterGroups) = value
|
|
// balanceWorkload(mst, clusters, clusterGroups)
|
|
// })
|
|
// res
|
|
// }
|
|
|
|
def printGraph(graph: GraphMatrix[Double]) = graph.foreach { e =>
|
|
e.foreach { d =>
|
|
print(f"$d%.2f, ")
|
|
}
|
|
println
|
|
}
|
|
|
|
def printGraph(graph: Graph[Double]) = {
|
|
for (i <- 0 until graph.size) {
|
|
print(s"$i: ")
|
|
for (edge <- graph(i)) {
|
|
print(f"(${edge.toNode}, ${edge.weight}%.2f), ")
|
|
}
|
|
println
|
|
}
|
|
}
|
|
|
|
def logGraph(maybeGraph: Either[String, GraphMatrix[Double]]): Unit =
|
|
maybeGraph.foreach(
|
|
graph => {
|
|
logger.whenDebugEnabled {
|
|
logger.debug("Graph: ")
|
|
printGraph(graph)
|
|
}
|
|
}
|
|
)
|
|
|
|
def logMST(maybeMST: Either[String, (Graph[Double], Double)]): Unit =
|
|
maybeMST.foreach(
|
|
t => {
|
|
logger.whenDebugEnabled {
|
|
logger.debug(s"Epsilon = ${t._2}")
|
|
logger.debug("MST: ")
|
|
printGraph(graph = t._1)
|
|
}
|
|
}
|
|
)
|
|
|
|
def logMST2(it: Either[String, (Graph[Double], RemovedEdges[Double])]): Unit =
|
|
it.foreach(
|
|
t => {
|
|
logger.whenDebugEnabled {
|
|
logger.debug(s"Removed Edges = ${t._2}")
|
|
logger.debug("MST: ")
|
|
printGraph(graph = t._1)
|
|
}
|
|
}
|
|
)
|
|
|
|
def printClusters(clusters: Clusters) = {
|
|
logger.whenDebugEnabled {
|
|
logger.debug("Clusters:")
|
|
var i = 0
|
|
clusters.foreach(e => {
|
|
print(s"Cluster-$i: ")
|
|
i += 1
|
|
e.foreach(f => print(s"$f "))
|
|
println
|
|
})
|
|
}
|
|
}
|
|
|
|
def printClusters(clusters: Array[List[Int]]) = {
|
|
logger.whenDebugEnabled {
|
|
logger.debug("Clusters:")
|
|
var i = 0
|
|
clusters.foreach(e => {
|
|
print(s"Cluster-$i: ")
|
|
i += 1
|
|
e.foreach(f => print(s"$f "))
|
|
println
|
|
})
|
|
}
|
|
}
|
|
|
|
def printGroups(groups: ArraySeq[List[(Int, Double)]]) = {
|
|
logger.whenDebugEnabled {
|
|
logger.debug("Neighbours: ")
|
|
var i = 0
|
|
groups.foreach(e => {
|
|
print(s"Cluster-$i: ")
|
|
i += 1
|
|
e.foreach(f => { print(f"(${f._1}%d, ${f._2}%.2f), ") })
|
|
println
|
|
})
|
|
}
|
|
}
|
|
|
|
def balanceWorkload[N: Numeric](
|
|
clusters: Clusters,
|
|
groups: ArraySeq[List[(Int, N)]]
|
|
) = {
|
|
customers.map(c => {
|
|
val mutClusters =
|
|
clusters.toArray
|
|
val clusterOrder = (0 until clusters.length).toArray
|
|
logger.debug(s"Initial order: $clusterOrder")
|
|
val k = mutClusters.size
|
|
for (_ <- 0 until iterations) {
|
|
val WL = mutClusters.view
|
|
.map(nodes => {
|
|
val customers = nodes.map(node => c(node))
|
|
val wl = customers.foldLeft(0f)(_ + _.workload)
|
|
wl
|
|
})
|
|
.to(ArraySeq)
|
|
logger.debug(s"$WL")
|
|
clusterOrder.sortInPlaceWith((x, y) => WL(x) < WL(y))
|
|
logger.debug(s"Current order $clusterOrder")
|
|
val (minWL, maxWL) = WL.foldLeft((99999f, 0f))((acc, wl) => {
|
|
val (currMin, currMax) = acc
|
|
if (wl < currMin) (wl, currMax)
|
|
else if (wl > currMax) (currMin, wl)
|
|
else acc
|
|
})
|
|
val WLD = maxWL - minWL
|
|
|
|
logger.debug(s"maxWL = $maxWL, minWL = $minWL")
|
|
logger.debug(s"WLD = $WLD")
|
|
if (WLD > WLDMax) {
|
|
val randNum = Random.between(((k / 2) + 1), k)
|
|
logger.debug(s"Rand Num=$randNum")
|
|
val effectiveNum = clusterOrder(randNum)
|
|
logger.debug(s"Chosen cluster = $effectiveNum")
|
|
val (s, _) = groups(effectiveNum)(0)
|
|
logger.debug(s"Nearest neighbour = $s")
|
|
// val avg = averagePairwiseDistance(mst(s))
|
|
val avg = adjMatrix
|
|
.map(m => averagePairwiseDistance(m)(mutClusters(s)))
|
|
.getOrElse(9999d)
|
|
// if (avg < epsilonMax) true else false
|
|
|
|
logger.debug(s"Pairwise average = $avg")
|
|
|
|
if (avg < epsilonMax) {
|
|
logger.debug("Average less than epsilonMax. Updating Clusters.")
|
|
if (!mutClusters(s).contains(effectiveNum)) {
|
|
mutClusters(s) = effectiveNum :: mutClusters(s)
|
|
}
|
|
// mutClusters.foreach(println)
|
|
printClusters(mutClusters)
|
|
}
|
|
}
|
|
}
|
|
ArraySeq.unsafeWrapArray(mutClusters)
|
|
})
|
|
}
|
|
|
|
def averagePairwiseDistance(graph: GraphMatrix[Double])(
|
|
cluster: List[Int]
|
|
) = {
|
|
val (sum, size) = cluster
|
|
.combinations(2)
|
|
.foldLeft((0d, 0))((acc, pair) => {
|
|
val (sum, size) = acc
|
|
val (i, j) = pair(0) -> pair(1)
|
|
logger.trace(s"$i $j - ")
|
|
logger.trace(s"weight = ${graph(i)(j)}")
|
|
(sum + graph(i)(j), size + 1)
|
|
})
|
|
|
|
val n = if (size == 0) 1 else size
|
|
sum / n
|
|
}
|
|
}
|
|
|
|
object HHCSim2 extends HHCTypes {
|
|
import Ordering.Implicits._
|
|
|
|
val composed = (formAdjMatrix _)
|
|
.andThen(e => mstUsingPrims(e))
|
|
.andThen { case (mst, epsilon) => removeEdges(mst)(epsilon) }
|
|
.andThen { case (mstUpdated, removed) => findClusters(mstUpdated)(removed) }
|
|
|
|
def getCustomers(
|
|
infile: BufferedSource
|
|
): String Either ArraySeq[Customer] = {
|
|
var customers: String Either ArraySeq[Customer] = Right(ArraySeq.empty)
|
|
val it = infile.getLines
|
|
@annotation.tailrec
|
|
def loop(
|
|
lst: ListBuffer[Customer],
|
|
iter: Iterator[String]
|
|
): String Either ListBuffer[Customer] = {
|
|
if (!iter.hasNext) Right(lst)
|
|
else {
|
|
val line = iter.next
|
|
val arr = line.split(",").map(_.trim)
|
|
arr match {
|
|
case Array(latitude, longitude, workLoad) => {
|
|
val cust =
|
|
for {
|
|
lat <- latitude.toDoubleOption
|
|
lon <- longitude.toDoubleOption
|
|
coord = Coord(lat, lon)
|
|
wl <- workLoad.toFloatOption
|
|
} yield (Customer(coord, wl))
|
|
cust match {
|
|
case Some(c) => loop(lst += c, iter)
|
|
case None => Left(s"Error reading customers at line - $line")
|
|
}
|
|
}
|
|
case _ => {
|
|
if (line.equals(" ") || line.contains("\n"))
|
|
Left("Error newline")
|
|
else {
|
|
Left(
|
|
"Error reading customers from" +
|
|
s" file at line - $line}"
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
try {
|
|
customers = loop(ListBuffer.empty, it).map(_.to(ArraySeq))
|
|
} catch {
|
|
case e: NumberFormatException =>
|
|
customers = Left(
|
|
s"Expected number but received string ${e.getMessage()}"
|
|
)
|
|
case e: NullPointerException =>
|
|
customers = Left("Input was null")
|
|
}
|
|
customers
|
|
}
|
|
|
|
def checkEmpty(
|
|
customers: IndexedSeq[Customer]
|
|
): Either[String, IndexedSeq[Customer]] =
|
|
customers.length match {
|
|
case 0 => Left("Error input was empty")
|
|
case _ => Right(customers)
|
|
}
|
|
|
|
def formAdjMatrix(customers: IndexedSeq[Customer]): GraphMatrix[Double] = {
|
|
val n = customers.length
|
|
val edges: Array[Array[Double]] = Array.ofDim(n, n)
|
|
for (i <- 0 until n) {
|
|
for (j <- i until n) {
|
|
val weight =
|
|
Util.getHaversineDistance(
|
|
customers(i).location,
|
|
customers(j).location
|
|
)
|
|
edges(i)(j) = weight
|
|
edges(j)(i) = weight
|
|
}
|
|
}
|
|
edges
|
|
}
|
|
|
|
def mstUsingPrims[T: Numeric](
|
|
edges: GraphMatrix[T]
|
|
): (Graph[T], T) = {
|
|
val num = implicitly[Numeric[T]]
|
|
val n = edges.length
|
|
val selected: Array[Boolean] = Array.fill(n)(false)
|
|
|
|
selected(0) = true
|
|
|
|
val adjList =
|
|
Array.fill(n)(ListBuffer.empty[HHCEdge2[T]])
|
|
|
|
var sum = 0
|
|
var count = 0
|
|
|
|
for (_ <- 0 until n - 1) {
|
|
var min = 999999
|
|
var x = 0
|
|
var y = 0
|
|
for (i <- 0 until n) {
|
|
if (selected(i) == true) {
|
|
for (j <- 0 until n) {
|
|
if (selected(j) == false && edges(i)(j) != 0) {
|
|
if (min > num.toInt(edges(i)(j))) {
|
|
min = num.toInt(edges(i)(j))
|
|
x = i
|
|
y = j
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sum += num.toInt(edges(x)(y))
|
|
adjList(x) += HHCEdge2(x, y, edges(x)(y))
|
|
adjList(y) += HHCEdge2(y, x, edges(x)(y))
|
|
selected(y) = true
|
|
}
|
|
val adjList2 = adjList.map(l => {
|
|
count += l.size
|
|
l.toList
|
|
})
|
|
// adjList2.foreach(println)
|
|
(ArraySeq.unsafeWrapArray(adjList2), num.fromInt(sum / (count / 2)))
|
|
}
|
|
|
|
def removeEdges[N: Ordering](
|
|
mst: Graph[N]
|
|
)(epsilon: N): (Graph[N], RemovedEdges[N]) = {
|
|
|
|
val removed = ListBuffer.empty[HHCEdge2[N]]
|
|
|
|
val result = ArraySeq.tabulate(mst.length) { i =>
|
|
val (filtered, rm) = mst(i)
|
|
// .view
|
|
// .filter(e => (e.fromNode <= e.toNode))
|
|
.partition(_.weight <= epsilon)
|
|
|
|
// val rm2 = rm.filter(e => (e.fromNode <= e.toNode))
|
|
|
|
removed ++= rm
|
|
|
|
filtered
|
|
}
|
|
println
|
|
// result.foreach(println)
|
|
// println
|
|
(result, removed.toList)
|
|
}
|
|
|
|
def DFS[T: Numeric](start: Int)(graph: Graph[T]) = {
|
|
val visited = Array.fill(graph.size)(false)
|
|
val buf = ListBuffer[Int]()
|
|
def loop(
|
|
start: Int,
|
|
graph: Graph[T],
|
|
visited: Array[Boolean]
|
|
): Unit = {
|
|
visited(start) = true
|
|
buf += start
|
|
// val iter = graph(start).iterator
|
|
// while (iter.hasNext) {
|
|
// val edge = iter.next
|
|
// if (!visited(edge.toNode))
|
|
// loop(edge.toNode, graph, visited)
|
|
// }
|
|
for (edge <- graph(start)) {
|
|
if (!visited(edge.toNode))
|
|
loop(edge.toNode, graph, visited)
|
|
}
|
|
}
|
|
|
|
loop(start, graph, visited)
|
|
buf.toList
|
|
}
|
|
|
|
def findClusters[T: Numeric](mstUpdated: Graph[T])(
|
|
removedEdges: RemovedEdges[T]
|
|
): (Graph[T], Clusters, ArraySeq[List[HHCEdge2[T]]], RemovedEdges[T]) = {
|
|
val visited = Array.fill[Boolean](mstUpdated.length)(false)
|
|
var removedEdges2 = removedEdges
|
|
val egdeMappings =
|
|
Array.fill(mstUpdated.length)(List.empty[HHCEdge2[T]])
|
|
val nodeToClusterMappings = Array.fill(mstUpdated.length)(0)
|
|
val result = ArraySeq.tabulate(mstUpdated.length) { i =>
|
|
{
|
|
val buf = ListBuffer[Int]()
|
|
visited(i) match {
|
|
case true =>
|
|
case false if (!mstUpdated(i).isEmpty) => {
|
|
val nodes = DFS(i)(mstUpdated)
|
|
buf ++= nodes
|
|
val (nds, rms) = assignEdges(nodes, removedEdges2)
|
|
removedEdges2 = rms
|
|
egdeMappings(i) = nds
|
|
for (j <- nodes) visited(j) = true
|
|
}
|
|
case false => {
|
|
buf += i
|
|
val (nds, rms) = assignEdges(List(i), removedEdges2)
|
|
egdeMappings(i) = nds
|
|
removedEdges2 = rms
|
|
}
|
|
}
|
|
buf.toList
|
|
}
|
|
}
|
|
|
|
// println(s"Removed edges size: ${removedEdges2.size}")
|
|
|
|
(
|
|
mstUpdated,
|
|
result.filterNot(_.isEmpty),
|
|
ArraySeq.unsafeWrapArray(egdeMappings.filterNot(_.isEmpty)),
|
|
removedEdges
|
|
)
|
|
}
|
|
|
|
def assignEdges[T: Ordering](
|
|
nodes: List[Int],
|
|
removedEdges: RemovedEdges[T]
|
|
) = {
|
|
val it = nodes.iterator
|
|
@annotation.tailrec
|
|
def loop(
|
|
nodes: List[Int],
|
|
removedEdges: RemovedEdges[T],
|
|
iter: Iterator[Int],
|
|
edges: List[HHCEdge2[T]]
|
|
): (List[HHCEdge2[T]], RemovedEdges[T]) = {
|
|
if (!iter.hasNext) (edges, removedEdges)
|
|
else if (!edges.isEmpty) (edges, removedEdges)
|
|
else {
|
|
val node = iter.next
|
|
val (filt, rm) =
|
|
removedEdges.partition(e => e.fromNode == node)
|
|
loop(nodes, rm, iter, filt)
|
|
}
|
|
}
|
|
val (edges, removedEdgesUpdated) =
|
|
loop(nodes, removedEdges, it, List.empty[HHCEdge2[T]])
|
|
(edges, removedEdgesUpdated)
|
|
}
|
|
|
|
def mapNodestoClusters[N: Ordering](mst: Graph[N], clusters: Clusters) = {
|
|
val arr = Array.fill(mst.size)(0)
|
|
for (i <- 0 until clusters.size) {
|
|
val nodes = clusters(i)
|
|
for (node <- nodes) {
|
|
arr(node) = i
|
|
}
|
|
}
|
|
ArraySeq.unsafeWrapArray(arr)
|
|
}
|
|
|
|
def groupClusters[N: Ordering](
|
|
mst: Graph[N],
|
|
clusters: Clusters,
|
|
edgeMappings: ArraySeq[List[HHCEdge2[N]]],
|
|
removed: RemovedEdges[N]
|
|
) = {
|
|
val nodeMap = mapNodestoClusters(mst, clusters)
|
|
val groups = ArraySeq.tabulate(edgeMappings.size)(i => {
|
|
val buf = ListBuffer.empty[(Int, N)]
|
|
val lst = edgeMappings(i)
|
|
lst.foreach(e => {
|
|
buf += nodeMap(e.toNode) -> e.weight
|
|
})
|
|
buf.sortWith {
|
|
case ((_, weight1), (_, weight2)) =>
|
|
weight1 < weight2
|
|
}.toList
|
|
})
|
|
(mst, clusters, edgeMappings, removed, groups)
|
|
}
|
|
}
|