it.unibo.alchemist.core.BatchEngine.kt Maven / Gradle / Ivy
/*
* Copyright (C) 2010-2023, Danilo Pianini and contributors
* listed, for each module, in the respective subproject's build.gradle.kts file.
*
* This file is part of Alchemist, and is distributed under the terms of the
* GNU General Public License, with a linking exception,
* as described in the file LICENSE in the Alchemist distribution's top directory.
*/
package it.unibo.alchemist.core
import com.google.common.collect.Sets
import it.unibo.alchemist.boundary.OutputMonitor
import it.unibo.alchemist.model.Actionable
import it.unibo.alchemist.model.Environment
import it.unibo.alchemist.model.Position
import it.unibo.alchemist.model.Time
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import java.util.function.Consumer
import java.util.function.Function
import java.util.stream.Collectors
/**
* This class implements a simulation. It offers a wide number of static
* factories to ease the creation process.
*
* @param concentration type
* @param [Position] type
*/
class BatchEngine> :
Engine {
private val outputReplayStrategy: OutputReplayStrategy
private val executeLock = Any()
private val updateLock = Any()
constructor(e: Environment?) : super(e) {
outputReplayStrategy = OutputReplayStrategy.Aggregate
}
constructor(e: Environment?, maxSteps: Long) : super(e, maxSteps) {
outputReplayStrategy = OutputReplayStrategy.Aggregate
}
constructor(e: Environment?, maxSteps: Long, t: Time?) : super(e, maxSteps, t) {
outputReplayStrategy = OutputReplayStrategy.Aggregate
}
constructor(e: Environment?, t: Time?) : super(e, t) {
outputReplayStrategy = OutputReplayStrategy.Aggregate
}
constructor(
e: Environment?,
maxSteps: Long,
t: Time?,
outputReplayStrategy: OutputReplayStrategy,
scheduler: BatchedScheduler?,
) : super(e, maxSteps, t, scheduler) {
this.outputReplayStrategy = outputReplayStrategy
}
/**
* Performs the next simulation step.
*/
override fun doStep() {
val batchedScheduler = scheduler as BatchedScheduler
val nextEvents = batchedScheduler.nextBatch
val batchSize = nextEvents.size
if (nextEvents.isEmpty()) {
newStatus(Status.TERMINATED)
LOGGER.info("No more reactions.")
return
}
val sortededNextEvents =
nextEvents.stream().sorted(Comparator.comparing(Actionable::tau)).collect(Collectors.toList())
val minSlidingWindowTime = sortededNextEvents[0].tau
val maxSlidingWindowTime = sortededNextEvents[sortededNextEvents.size - 1].tau
runBlocking {
val taskMapper =
Function { event: Actionable ->
async {
doEvent(
event,
minSlidingWindowTime,
)
}
}
val tasks = nextEvents.stream().map(taskMapper).collect(Collectors.toList())
try {
val futureResults = tasks.awaitAll()
val newStep = step + batchSize.toLong()
setCurrentStep(newStep)
val resultsOrderedByTime = futureResults
.sortedWith(Comparator.comparing { result: TaskResult -> result.eventTime })
setCurrentTime(if (maxSlidingWindowTime > time) maxSlidingWindowTime else time)
doStepDoneAllMonitors(resultsOrderedByTime)
} catch (e: InterruptedException) {
LOGGER.error(e.message, e)
Thread.currentThread().interrupt()
}
}
}
private fun doStepDoneAllMonitors(resultsOrderedByTime: List): Unit = when (outputReplayStrategy) {
is OutputReplayStrategy.Reply -> resultsOrderedByTime.forEach(::doStepDoneAllMonitors)
is OutputReplayStrategy.Aggregate -> doStepDoneAllMonitors(resultsOrderedByTime[resultsOrderedByTime.size - 1])
}
private fun doStepDoneAllMonitors(result: TaskResult) {
for (monitor: OutputMonitor in monitors) {
monitor.stepDone(environment, result.event, time, step)
}
}
private fun doEvent(nextEvent: Actionable, slidingWindowTime: Time): TaskResult {
validateEventExecutionTime(nextEvent, slidingWindowTime)
val currentLocalTime = nextEvent.tau
if (nextEvent.canExecute()) {
safeExecuteEvent(nextEvent)
safeUpdateEvent(nextEvent)
}
nextEvent.update(currentLocalTime, true, environment)
scheduler.updateReaction(nextEvent)
if (environment.isTerminated) {
newStatus(Status.TERMINATED)
LOGGER.info("Termination condition reached.")
}
return TaskResult(nextEvent, currentLocalTime)
}
private fun validateEventExecutionTime(nextEvent: Actionable, slidingWindowTime: Time) {
val scheduledTime = nextEvent.tau
if (!isEventTimeScheduledInFirstBatch(scheduledTime) && isEventScheduledBeforeCurrentTime(
scheduledTime,
slidingWindowTime,
)
) {
error(
nextEvent.toString() + " is scheduled in the past at time " + scheduledTime +
", current time is " + time +
". Problem occurred at step " + step,
)
}
}
private fun isEventTimeScheduledInFirstBatch(scheduledTime: Time): Boolean {
return scheduledTime.toDouble() == 0.0
}
private fun isEventScheduledBeforeCurrentTime(scheduledTime: Time, slidingWindowTime: Time): Boolean {
return scheduledTime < slidingWindowTime
}
private fun safeExecuteEvent(event: Actionable) {
synchronized(executeLock) {
/*
* This must be taken before execution, because the reaction
* might remove itself (or its node) from the environment.
*/
event.conditions.forEach {
it.reactionReady()
}
event.execute()
}
}
private fun safeUpdateEvent(event: Actionable) {
synchronized(updateLock) {
var toUpdate: Set> = dependencyGraph.outboundDependencies(event)
if (!afterExecutionUpdates.isEmpty()) {
afterExecutionUpdates.forEach(Consumer { obj: Update -> obj.performChanges() })
afterExecutionUpdates.clear()
toUpdate = Sets.union(
toUpdate,
dependencyGraph.outboundDependencies(event),
)
}
toUpdate.forEach(Consumer { r: Actionable? -> updateReaction(r) })
}
}
/**
* Safely set simulation status.
*/
override fun newStatus(next: Status) {
synchronized(this) { super.newStatus(next) }
}
private inner class TaskResult(val event: Actionable, val eventTime: Time)
/**
* This interface represents the way outputs are replied. It is meant for internal use.
*/
sealed class OutputReplayStrategy {
protected val name: String = requireNotNull(this::class.simpleName).lowercase()
/**
* Outputs are aggregated.
*/
data object Aggregate : OutputReplayStrategy()
/**
* Outputs are replied.
*/
data object Reply : OutputReplayStrategy()
/**
* Converts a [String] to the corresponding [OutputReplayStrategy], based on the name.
*/
fun String.toReplayStrategy(): OutputReplayStrategy = when (this.lowercase()) {
Aggregate.name -> Aggregate
Reply.name -> Reply
else ->
error(
"""
Invalid output reply strategy $this. Available choices: ${listOf(Aggregate, Reply).map { it.name }}
""".trimIndent(),
)
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy