Skip to content

Commit 844f131

Browse files
Implement partial random moves
1 parent 33c483b commit 844f131

File tree

2 files changed

+99
-79
lines changed

2 files changed

+99
-79
lines changed

src/main/scala/eighties/h24/dynamic.scala

Lines changed: 62 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ package eighties.h24
33
import org.locationtech.jts.geom.{Coordinate, Envelope}
44
import org.locationtech.jts.index.strtree.STRtree
55
import eighties.h24.tools.random.multinomial
6-
import eighties.h24.social._
6+
import eighties.h24.social.*
77
import space.{Attraction, Index, Location, World}
8-
import eighties.h24.generation.{LCell, dayTimeSlice, nightTimeSlice}
8+
import eighties.h24.generation.{LCell, dayTimeSlice, nightTimeSlice, timeSlices}
99
import monocle.function.all.index
1010
import monocle.{Lens, Traversal}
11-
import tools.random._
12-
import monocle._
11+
import tools.random.*
12+
import monocle.*
1313

1414
import scala.reflect.ClassTag
1515
import scala.util.Random
@@ -19,24 +19,22 @@ object dynamic {
1919
import MoveMatrix._
2020

2121
def reachable[T](index: Index[T]): Seq[(Int, Int)] =
22-
for {
22+
for
2323
i <- 0 until index.sideI
2424
j <- 0 until index.sideJ
2525
if !index.cells(i)(j).isEmpty
26-
} yield (i, j)
26+
yield (i, j)
2727

28-
def randomMove[I: ClassTag](world: World[I], timeSlice: TimeSlice, ratio: Double, location: Lens[I, Location], stableDestinations: I => Map[TimeSlice, Location], random: Random): World[I] = {
28+
def stableDestinationOrRandomMove[I: ClassTag](world: World[I], timeSlice: TimeSlice, location: Lens[I, Location], stableDestinations: I => Map[TimeSlice, Location], random: Random): World[I] =
2929
val reach = reachable(Index.indexIndividuals(world, location.get))
3030
val rSize = reach.size
31-
def randomLocation = location.modify(l => if(random.nextDouble() < ratio) reach(random.nextInt(rSize)) else l)
31+
def randomLocation = location.replace(reach(random.nextInt(rSize)))
3232
def move(individual: I) = stableLocationOrMove(individual, timeSlice, stableDestinations, location, randomLocation)
3333
(World.allIndividuals[I] modify move) (world)
34-
}
3534

36-
def goBackHome[W, I](world: W, allIndividuals: Traversal[W, I], location: Lens[I, Location], home: I => Location): W = {
37-
def m = (individual: I) => location.set(home(individual))(individual)
35+
def goBackHome[W, I](world: W, allIndividuals: Traversal[W, I], location: Lens[I, Location], home: I => Location): W =
36+
def m = (individual: I) => location.replace(home(individual))(individual)
3837
(allIndividuals modify m)(world)
39-
}
4038

4139
object MoveMatrix {
4240

@@ -232,92 +230,114 @@ object dynamic {
232230

233231
}
234232

235-
def moveFlowDefaultOnOtherSex[I](cellMoves: Cell, individual: I, socialCategory: I => AggregatedSocialCategory) /*moves: MoveMatrix.CellMatrix, individualInCel: Individual)*/ = {
233+
def moveFlowDefaultOnOtherSex[I](cellMoves: Cell, individual: I, socialCategory: I => AggregatedSocialCategory) /*moves: MoveMatrix.CellMatrix, individualInCel: Individual)*/ =
236234
//val location = Individual.location.get(individual)
237235
//val cellMoves = moves(location._1)(location._2)
238236
val aggregatedCategory = socialCategory(individual)
239237
def myCategory = cellMoves.get(aggregatedCategory)
240238
def noSex = cellMoves.find { case(c, _) => Focus[AggregatedSocialCategory](_.age).get(c) == Focus[AggregatedSocialCategory](_.age).get(aggregatedCategory) && c.education == aggregatedCategory.education }.map(_._2)
241239
myCategory orElse noSex
242-
}
243240

244241
def sampleDestinationInMoveMatrix[I](cellMoves: Cell, individual: I, socialCategory: I => AggregatedSocialCategory, random: Random) =
245-
moveFlowDefaultOnOtherSex(cellMoves, individual, socialCategory).flatMap { m =>
246-
if(m.isEmpty) None else Some(multinomial(m.map{ m => MoveMatrix.Move.location.get(m) -> Focus[MoveMatrix.Move](_.ratio).get(m).toDouble })(random))
247-
}
242+
moveFlowDefaultOnOtherSex(cellMoves, individual, socialCategory).flatMap: m =>
243+
if m.isEmpty
244+
then None
245+
else
246+
Some:
247+
multinomial(m.map{ m => MoveMatrix.Move.location.get(m) -> Focus[MoveMatrix.Move](_.ratio).get(m).toDouble })(using random)
248248

249249
def stableLocationOrMove[I](individual: I, timeSlice: TimeSlice, stableDestinations: I => Map[TimeSlice, Location], location: Lens[I, Location], move: I => I) =
250-
stableDestinations(individual).get(timeSlice) match {
250+
stableDestinations(individual).get(timeSlice) match
251251
case None => move(individual)
252252
case Some(stableDestination) => location.set(stableDestination)(individual)
253-
}
254253

255-
def moveInMoveMatrix[I: ClassTag](world: World[I], locatedCell: LocatedCell, timeSlice: TimeSlice, stableDestination: I => Map[TimeSlice, Location], location: Lens[I, Location], home: I => Location, socialCategory: I => AggregatedSocialCategory, random: Random): World[I] = {
256-
def sampleMoveInMatrix[J](cellMoves: Cell, location: Lens[J, Location], socialCategory: J => AggregatedSocialCategory)(individual: J) =
257-
sampleDestinationInMoveMatrix(cellMoves, individual, socialCategory, random) match {
258-
case Some(destination) => location.set(destination)(individual)
254+
255+
def stableDestinationOrMoveInMoveMatrix[I: ClassTag](
256+
world: World[I],
257+
locatedCell: LocatedCell,
258+
timeSlice: TimeSlice,
259+
stableDestination: I => Map[TimeSlice, Location],
260+
location: Lens[I, Location],
261+
home: I => Location,
262+
socialCategory: I => AggregatedSocialCategory,
263+
randomRatio: Double,
264+
random: Random): World[I] =
265+
266+
def sampleMoveInMatrix(cellMoves: Cell, location: Lens[I, Location], socialCategory: I => AggregatedSocialCategory)(individual: I): I =
267+
sampleDestinationInMoveMatrix(cellMoves, individual, socialCategory, random) match
268+
case Some(destination) => location.replace(destination)(individual)
259269
case None => individual
260-
}
270+
271+
272+
lazy val reach = reachable(Index.indexIndividuals(world, location.get))
273+
lazy val rSize = reach.size
274+
275+
def randomMove(location: Lens[I, Location], random: Random) =
276+
location.replace(reach(random.nextInt(rSize)))
277+
278+
def move(cellMoves: => Cell, location: Lens[I, Location], socialCategory: I => AggregatedSocialCategory, random: Random) =
279+
def stableOrMoveInMatrix(individual: I) = stableLocationOrMove(individual, timeSlice, stableDestination, location, sampleMoveInMatrix(cellMoves, location, socialCategory))
280+
281+
if randomRatio <= 0.0
282+
then stableOrMoveInMatrix
283+
else
284+
if random.nextDouble() < randomRatio
285+
then randomMove(location, random)
286+
else stableOrMoveInMatrix
261287

262288
val newIndividuals = Array.ofDim[I](world.individuals.length)
263289
var index = 0
264290

265-
for {
291+
for
266292
(line, i) <- Focus[Index[I]](_.cells).get(Index.indexIndividuals(world, home)).zipWithIndex
267293
(individuals, j) <- line.zipWithIndex
268-
} {
294+
do
269295
lazy val cell = locatedCell(timeSlice, i, j)
270-
for {
296+
for
271297
individual <- individuals
272-
} {
273-
newIndividuals(index) = stableLocationOrMove(individual, timeSlice, stableDestination, location, sampleMoveInMatrix(cell, location, socialCategory))
298+
do
299+
newIndividuals(index) = move(cell, location, socialCategory, random)(individual)
274300
index += 1
275-
}
276-
}
301+
277302

278303
Focus[World[I]](_.individuals).set(newIndividuals)(world)
279-
}
280304

281-
def assignRandomDayLocation[I: ClassTag](world: World[I], locatedCell: LocatedCell, stableDestination: Lens[I, Map[TimeSlice, Location]], location: I => Location, home: I => Location, socialCategory: I => AggregatedSocialCategory, rng: Random) = {
305+
def assignRandomDayLocation[I: ClassTag](world: World[I], locatedCell: LocatedCell, stableDestination: Lens[I, Map[TimeSlice, Location]], location: I => Location, home: I => Location, socialCategory: I => AggregatedSocialCategory, rng: Random) =
282306
val newIndividuals = Array.ofDim[I](world.individuals.length)
283307
var index = 0
284308

285-
for {
309+
for
286310
(line, i) <- Focus[Index[I]](_.cells).get(Index.indexIndividuals(world, location)).zipWithIndex
287311
(individuals, j) <- line.zipWithIndex
288-
} {
312+
do
289313
val workTimeMovesFromCell = locatedCell(dayTimeSlice, i, j)
290314

291315
assert(workTimeMovesFromCell != null)
292316

293-
for {
317+
for
294318
individual <- individuals
295-
} {
319+
do
296320
def newIndividual =
297321
dynamic.sampleDestinationInMoveMatrix(workTimeMovesFromCell, individual, socialCategory, rng) match {
298322
case Some(d) => stableDestination.modify(_ + (dayTimeSlice -> d))(individual)
299323
case None => stableDestination.modify(_ + (dayTimeSlice -> home(individual)))(individual)
300324
}
301325
newIndividuals(index) = newIndividual
302326
index += 1
303-
}
304-
}
305327

306328
Focus[World[I]](_.individuals).set(newIndividuals)(world)
307-
}
308329

309330
def assignFixNightLocation[I: ClassTag](world: World[I], stableDestination: Lens[I, Map[TimeSlice, Location]], home: I => Location) =
310331
World.allIndividuals[I].modify { individual => stableDestination.modify(_ + (nightTimeSlice -> home(individual)))(individual) } (world)
311332

312-
def randomiseLocation[I: ClassTag](world: World[I], location: I => Location, home: Lens[I, Location], random: Random) = {
333+
def randomiseLocation[I: ClassTag](world: World[I], location: I => Location, home: Lens[I, Location], random: Random) =
313334
val reach = reachable(Index[I](world.individuals.iterator, location, world.sideI, world.sideJ))
314335
val reachSize = reach.size
315336

316337
def assign(individual: I): I =
317338
home.set(reach(random.nextInt(reachSize))) (individual)
318339

319340
(World.allIndividuals[I] modify assign)(world)
320-
}
321341

322342
def generateAttractions[I: ClassTag](world: World[I], proportion: Double, location: I => Location, random: Random) = {
323343
val reach = reachable(Index.indexIndividuals(world, location))

src/main/scala/eighties/h24/simulation.scala

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,11 @@ import scala.annotation.tailrec
2828
import scala.reflect.ClassTag
2929
import scala.util.Random
3030

31-
object simulation {
31+
object simulation:
3232

33-
sealed trait MoveType
34-
35-
object MoveType {
36-
case object Data extends MoveType
37-
case object Random extends MoveType
38-
case object No extends MoveType
39-
}
33+
enum MoveType:
34+
case Data, No, Random
35+
case PartialRandom(p: Double, slices: Seq[TimeSlice])
4036

4137
def simulateWorld[I: ClassTag](
4238
days: Int,
@@ -51,37 +47,43 @@ object simulation {
5147
home: I => Location,
5248
socialCategory: I => AggregatedSocialCategory,
5349
rng: Random,
54-
visitor: Option[(World[I], BoundingBox, Int, Option[(Int, Int)]) => Unit] = None): World[I] = {
55-
56-
57-
@tailrec def simulateOneDay(world: space.World[I], bb: BoundingBox, gridSize: Int, timeSlices: List[TimeSlice], locatedCell: LocatedCell, day: Int, slice: Int = 0): World[I] = {
58-
timeSlices match {
59-
case Nil => world
60-
case time :: t =>
61-
def moved = moveType match {
62-
case MoveType.Data => dynamic.moveInMoveMatrix(world, locatedCell, time, stableDestinations, location, home, socialCategory, rng)
63-
case MoveType.Random => dynamic.randomMove(world, time, 1.0, location, stableDestinations, rng)
64-
case MoveType.No => world
65-
}
50+
visitor: Option[(World[I], BoundingBox, Int, Option[(Int, Int)]) => Unit] = None): World[I] =
51+
52+
@tailrec def simulateOneDay(
53+
world: space.World[I],
54+
bb: BoundingBox,
55+
gridSize: Int,
56+
timeSlices: List[TimeSlice],
57+
locatedCell: LocatedCell,
58+
day: Int,
59+
slice: Int = 0): World[I] =
60+
timeSlices match
61+
case Nil => world
62+
case time :: t =>
63+
def moved = moveType match
64+
case MoveType.Data => dynamic.stableDestinationOrMoveInMoveMatrix(world, locatedCell, time, stableDestinations, location, home, socialCategory, 0.0, rng)
65+
case MoveType.PartialRandom(p, s) =>
66+
val randomMoveProbability = if s.contains(time) then p else 0.0
67+
dynamic.stableDestinationOrMoveInMoveMatrix(world, locatedCell, time, stableDestinations, location, home, socialCategory, randomMoveProbability, rng)
68+
case MoveType.Random => dynamic.stableDestinationOrRandomMove(world, time, location, stableDestinations, rng)
69+
case MoveType.No => world
70+
71+
val convicted = exchange(moved, day, slice, rng)
72+
visitor.foreach(_(convicted, bb, gridSize, Some((day, slice))))
73+
simulateOneDay(convicted, bb, gridSize, t, locatedCell, day, slice + 1)
6674

67-
val convicted = exchange(moved, day, slice, rng)
68-
visitor.foreach(_(convicted, bb, gridSize, Some((day, slice))))
69-
simulateOneDay(convicted, bb, gridSize, t, locatedCell, day, slice + 1)
70-
}
71-
}
7275

7376
// Ensures the world is not retained in memory
7477
var currentWorld = world()
7578
visitor.foreach(_(currentWorld, bbox, gridSize, None))
7679

77-
for {
80+
for
7881
day <- 0 until days
79-
} {
82+
do
8083
currentWorld = simulateOneDay(currentWorld, bbox, gridSize, timeSlices.toList, locatedCell, day)
81-
}
8284

8385
currentWorld
84-
}
86+
8587

8688
def simulate[I: ClassTag](
8789
days: Int,
@@ -95,20 +97,20 @@ object simulation {
9597
home: Lens[I, Location],
9698
socialCategory: I => AggregatedSocialCategory,
9799
rng: Random,
98-
visitor: Option[(World[I], BoundingBox, Int, Option[(Int, Int)]) => Unit] = None): World[I] = {
100+
visitor: Option[(World[I], BoundingBox, Int, Option[(Int, Int)]) => Unit] = None): World[I] =
99101

100102
def worldFeature = WorldFeature.load(population)
101103
val bbox = worldFeature.originalBoundingBox
102104
val gridSize = worldFeature.gridSize
103105
val moveMatrix = MoveMatrix.load(moves)
104106

105-
try {
107+
try
106108
def locatedCell: LocatedCell = (timeSlice: TimeSlice, i: Int, j: Int) => moveMatrix.get((i, j), timeSlice)
107109
def world = generateWorld(worldFeature.individualFeatures, buildIndividual, location, home, rng)
108110

109111
def populationWithMoves =
110-
moveType match {
111-
case MoveType.Data =>
112+
moveType match
113+
case MoveType.Data | _: MoveType.PartialRandom =>
112114
val fixedDay = assignRandomDayLocation(world, locatedCell, stableDestinations, location.get, home.get, socialCategory, rng)
113115
assignFixNightLocation(
114116
fixedDay,
@@ -117,7 +119,6 @@ object simulation {
117119
)
118120
case MoveType.Random => assignFixNightLocation(world, stableDestinations, home.get)
119121
case MoveType.No => assignFixNightLocation(world, stableDestinations, home.get)
120-
}
121122

122123
simulateWorld(
123124
days = days,
@@ -134,8 +135,8 @@ object simulation {
134135
rng = rng,
135136
visitor = visitor
136137
)
137-
} finally moveMatrix.close()
138-
}
138+
finally moveMatrix.close()
139+
139140

140141

141142
// def simulateWithVisitor[I: ClassTag](
@@ -203,4 +204,3 @@ object simulation {
203204
// } finally moveMatrix.close
204205
// }
205206

206-
}

0 commit comments

Comments
 (0)