Skip to content

Commit

Permalink
internalized tick-transform and continued migration to kotlinx-datetime
Browse files Browse the repository at this point in the history
  • Loading branch information
holgerbrandl committed Apr 8, 2023
1 parent 74d451d commit 5e54276
Show file tree
Hide file tree
Showing 46 changed files with 468 additions and 450 deletions.
30 changes: 17 additions & 13 deletions build.gradle.kts
@@ -1,7 +1,7 @@
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
kotlin("jvm") version "1.8.0"
kotlin("jvm") version "1.8.20"
`maven-publish`
signing

Expand All @@ -12,8 +12,8 @@ plugins {
}

group = "com.github.holgerbrandl"
version = "0.8.100"
//version = "0.9-SNAPSHOT"
//version = "0.8.100"
version = "0.9-SNAPSHOT"


repositories {
Expand All @@ -25,35 +25,39 @@ dependencies {
api("org.apache.commons:commons-math3:3.6.1")
// note updated postponed because of regression errors
api("io.insert-koin:koin-core:3.1.2")
implementation(kotlin("reflect"))
implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.20")

api("com.github.holgerbrandl:jsonbuilder:0.10")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")

// api("io.github.microutils:kotlin-logging:1.12.5")
// api("org.slf4j:slf4j-simple:1.7.32")

implementation("com.google.code.gson:gson:2.10")
implementation("com.google.code.gson:gson:2.10.1")

// implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0")
// implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.0")

testImplementation(kotlin("test-junit"))
testImplementation("io.kotest:kotest-assertions-core:5.5.4")
implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.4.0")


testImplementation("org.jetbrains.kotlin:kotlin-test-junit:1.8.20")
testImplementation("io.kotest:kotest-assertions-core:5.5.5")

// **TODO** move to api to require users to pull it in if needed
implementation("com.github.holgerbrandl:krangl:0.18.4") // must needed for kravis
implementation("com.github.holgerbrandl:kdfutils:1.0")
implementation("com.github.holgerbrandl:kdfutils:1.2-SNAPSHOT")
testImplementation("com.github.holgerbrandl:kdfutils:1.2-SNAPSHOT")

compileOnly("com.github.holgerbrandl:kravis:0.8.6")
testImplementation("com.github.holgerbrandl:kravis:0.8.6")
compileOnly("com.github.holgerbrandl:kravis:0.9.95")
testImplementation("com.github.holgerbrandl:kravis:0.9.95")

compileOnly("org.jetbrains.lets-plot:lets-plot-kotlin-jvm:4.1.0")
testImplementation("org.jetbrains.lets-plot:lets-plot-batik:2.5.1")
compileOnly("org.jetbrains.lets-plot:lets-plot-kotlin-jvm:4.3.0")
testImplementation("org.jetbrains.lets-plot:lets-plot-batik:3.1.0")
// testImplementation("org.jetbrains.lets-plot:lets-plot-jfx:1.5.4")

//experimental dependencies use for experimentation
testImplementation("com.thoughtworks.xstream:xstream:1.4.19")
testImplementation("com.thoughtworks.xstream:xstream:1.4.20")

//https://youtrack.jetbrains.com/issue/KT-44197

Expand Down
6 changes: 1 addition & 5 deletions docs/roadmap.md
Expand Up @@ -2,14 +2,10 @@

## Next steps

## v0.8

---



## v0.9

**TODO** remove all direct imports of krangl api

---
**TODO** pathfinding example
Expand Down
1 change: 1 addition & 0 deletions docs/userguide/docs/changes.md
Expand Up @@ -6,6 +6,7 @@ Developer snapshots are deposited on maven-central starting with v0.8.90+

Major
* Changed API to always favor `kotlin.time.Duration` to express durations. Previously untyped `Numbers` were used that often led to confusion in larger simulations models. Evey simulation environment has now a `DurationUnit` such as seconds, hours, etc. (defaulting to minutes if not specified). To indicate this breaking change to the user in her IDE, new [opt-in](https://kotlinlang.org/docs/opt-in-requirements.html) annotations were introduced
* Migrated use of `Instant` to `kotlinx.datetime.Instant` for better API consistency

Minor

Expand Down
Expand Up @@ -17,10 +17,9 @@ import kotlin.time.Duration.Companion.milliseconds
fun main() {
application {
// setup simulation model
val sim = object : Environment() {
val sim = object : Environment(durationUnit = DurationUnit.SECONDS) {
init {
ClockSync(tickDuration = 10.milliseconds, syncsPerTick = 100)
tickTransform = TickTransform(DurationUnit.SECONDS)
}

// instantiate components (not fully worked out here)
Expand Down
4 changes: 2 additions & 2 deletions modules/optimization/build.gradle.kts
Expand Up @@ -13,8 +13,8 @@ repositories {
dependencies {

api("com.github.holgerbrandl:kalasim:0.9-SNAPSHOT")
api("org.optaplanner:optaplanner-core:8.34.0.Final")
api("org.optaplanner:optaplanner-benchmark:8.34.0.Final")
api("org.optaplanner:optaplanner-core:8.36.0.Final")
api("org.optaplanner:optaplanner-benchmark:8.36.0.Final")

api("ch.qos.logback:logback-classic:1.4.5")

Expand Down
2 changes: 1 addition & 1 deletion modules/persistence/src/main/kotlin/Kyro.kt
Expand Up @@ -59,7 +59,7 @@ public fun EmergencyRoom.testSim() = apply {
// waitingLine.timlengthOfStayMonitor.display().show()

val arrivals = get<ComponentGenerator<Patient>>().history
// arrivals.asDataFrame().print()
// arrivals.toDataFrame().print()


val df = arrivals.map {
Expand Down
2 changes: 1 addition & 1 deletion modules/webui/src/main/kotlin/org/kalasim/webui/SimUI.kt
Expand Up @@ -9,7 +9,7 @@ import org.springframework.context.ApplicationContextInitializer
import org.springframework.context.ConfigurableApplicationContext
import org.springframework.stereotype.Component
import java.io.File
import java.time.Duration
import kotlin.time.Duration
import javax.annotation.PostConstruct


Expand Down
Expand Up @@ -9,7 +9,7 @@ import org.openrndr.application
import org.openrndr.color.ColorRGBa
import org.openrndr.math.IntVector2
import org.openrndr.math.Vector2
import java.time.Duration
import kotlin.time.Duration

// evolve a sim while running the visualization
fun main() = application {
Expand Down
Expand Up @@ -13,7 +13,7 @@ import org.openrndr.ffmpeg.ScreenRecorder
import org.openrndr.shape.Circle
import org.openrndr.svg.loadSVG
import java.lang.Thread.sleep
import java.time.Instant
import kotlinx.datetime.Instant
import kotlin.math.roundToInt
import kotlin.random.Random
import kotlin.system.exitProcess
Expand Down
Expand Up @@ -21,7 +21,7 @@ fun main() {
// val waterSupply = sims.withIndex().map { (idx, sim) ->
// sim.base.refinery.levelTimeline//.statistics()
// .stepFun()
// .asDataFrame()
// .toDataFrame()
// .addColumn("num_harvesters") { sim.harvesters.size }
// .addColumn("run") { idx }
// }.bindRows()
Expand Down
22 changes: 10 additions & 12 deletions src/main/kotlin/org/kalasim/ClockSync.kt
@@ -1,16 +1,13 @@
package org.kalasim

import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import org.kalasim.misc.ComponentTrackingConfig
import org.kalasim.misc.DependencyContext
import org.koin.core.Koin
import java.time.Duration.between
import java.time.Duration.ofMillis
import java.time.Instant
import kotlin.math.abs
import kotlin.math.roundToLong
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.toJavaDuration
import kotlin.time.Duration.Companion.milliseconds

/**
* A component that allows to synchronize wall time to simulation. See the [user manual](https://www.kalasim.org/advanced/#clock-synchronization) for an in-depth overview about clock synchronization.
Expand Down Expand Up @@ -55,19 +52,20 @@ class ClockSync(

// set the start time if not yet done (happens only after changing tickDuration
syncStartTicks = syncStartTicks ?: env.now
syncStartWT = syncStartWT ?: Instant.now()
val now = Clock.System.now()
syncStartWT = syncStartWT ?: now


val simTimeSinceSyncStart = ofMillis(((env.now - syncStartTicks!!) * tickDurationMs).roundToLong())
val wallTimeSinceSyncStart = between(syncStartWT, Instant.now())
val simTimeSinceSyncStart = ((env.now - syncStartTicks!!) * tickDurationMs).roundToLong().milliseconds
val wallTimeSinceSyncStart = now - syncStartWT!!

val sleepDuration = simTimeSinceSyncStart - wallTimeSinceSyncStart

// if (abs(sleepDuration.toMillis()) > 5000L) {
// println("sim $simTimeSinceSyncStart wall $wallTimeSinceSyncStart; Resync by sleep for ${sleepDuration}ms")
// }

if (maxDelay != null && sleepDuration.abs() > maxDelay.toJavaDuration()) {
if (maxDelay != null && sleepDuration > maxDelay) {
throw ClockOverloadException(
env.now,
"Maximum delay between wall clock and simulation clock exceeded at time ${env.now}. " +
Expand All @@ -76,9 +74,9 @@ class ClockSync(
}

// simulation is too fast if value is larger
if (sleepDuration > Duration.ZERO.toJavaDuration()) {
if (sleepDuration > Duration.ZERO) {
// wait accordingly to let wall clock catch up
Thread.sleep(sleepDuration.toMillis())
Thread.sleep(sleepDuration.inWholeMilliseconds)
}

// wait until the next sync event is due
Expand Down
23 changes: 11 additions & 12 deletions src/main/kotlin/org/kalasim/Component.kt
Expand Up @@ -13,7 +13,6 @@ import org.koin.core.Koin
import kotlin.math.*
import kotlin.reflect.KFunction1
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes


internal const val EPS = 1E-8
Expand Down Expand Up @@ -1242,7 +1241,7 @@ open class Component(
* @param priority If a component has the same time on the event list, this component is sorted according to
* the priority. An event with a higher priority will be scheduled first.
*/
@OptIn(NumericDuration::class)
@OptIn(AmbiguousDuration::class)
suspend fun SequenceScope<Component>.hold(
duration: Duration,
description: String? = null,
Expand Down Expand Up @@ -1283,7 +1282,7 @@ open class Component(
* the priority. An event with a higher priority will be scheduled first.
*/
// @Deprecated("Use Duration instead of Number")
@NumericDuration
@AmbiguousDuration
suspend fun SequenceScope<Component>.hold(
duration: Number? = null,
description: String? = null,
Expand All @@ -1304,7 +1303,7 @@ open class Component(
* @param priority If a component has the same time on the event list, this component is sorted according to
* the priority. An event with a higher priority will be scheduled first.
*/
@NumericDuration
@AmbiguousDuration
fun hold(
duration: Number? = null,
description: String? = null,
Expand Down Expand Up @@ -1817,14 +1816,14 @@ fun Component.toLifeCycleRecord(): ComponentLifecycleRecord {
c.name,
c.creationTime,
inDataSince = if(c.isData) c.stateTimeline.statsData().timepoints.last() else null,
(histogram[DATA] ?: 0.0).asTickTime(),
(histogram[CURRENT] ?: 0.0).asTickTime(),
(histogram[STANDBY] ?: 0.0).asTickTime(),
(histogram[PASSIVE] ?: 0.0).asTickTime(),
(histogram[INTERRUPTED] ?: 0.0).asTickTime(),
(histogram[SCHEDULED] ?: 0.0).asTickTime(),
(histogram[REQUESTING] ?: 0.0).asTickTime(),
(histogram[WAITING] ?: 0.0).asTickTime(),
(histogram[DATA] ?: 0.0).toTickTime(),
(histogram[CURRENT] ?: 0.0).toTickTime(),
(histogram[STANDBY] ?: 0.0).toTickTime(),
(histogram[PASSIVE] ?: 0.0).toTickTime(),
(histogram[INTERRUPTED] ?: 0.0).toTickTime(),
(histogram[SCHEDULED] ?: 0.0).toTickTime(),
(histogram[REQUESTING] ?: 0.0).toTickTime(),
(histogram[WAITING] ?: 0.0).toTickTime(),
)
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/org/kalasim/ComponentGenerator.kt
Expand Up @@ -3,7 +3,7 @@ package org.kalasim
import org.apache.commons.math3.distribution.RealDistribution
import org.kalasim.analysis.snapshot.ComponentGeneratorSnapshot
import org.kalasim.misc.DependencyContext
import org.kalasim.misc.NumericDuration
import org.kalasim.misc.AmbiguousDuration
import org.koin.core.Koin
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
Expand Down Expand Up @@ -32,7 +32,7 @@ class ComponentGenerator<T>(
*
* For supported arguments see https://www.kalasim.org/component/#component-generator
*/
@NumericDuration
@AmbiguousDuration
constructor(
iat: RealDistribution,
startAt: TickTime? = null,
Expand Down
22 changes: 10 additions & 12 deletions src/main/kotlin/org/kalasim/Environment.kt
Expand Up @@ -18,7 +18,7 @@ import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.dsl.koinApplication
import org.koin.dsl.module
import java.time.Instant
import kotlinx.datetime.Instant
import java.util.*
import kotlin.time.*
import kotlin.time.Duration.Companion.days
Expand Down Expand Up @@ -170,10 +170,10 @@ open class Environment(


/** Allows to transform ticks to wall time (represented by `java.time.Instant`) */
override var tickTransform: TickTransform = TickTransform(durationUnit)
internal var tickTransform: TickTransform = TickTransform(durationUnit)

// var startDate: Instant? = null
var offsetTransform: OffsetTransform? = null
var startTime: Instant? = null
// get() = if(tickTransform is OffsetTransform) (tickTransform as OffsetTransform) else null


Expand Down Expand Up @@ -238,8 +238,6 @@ open class Environment(
}

// curComponent = main


}

fun enableConsoleLogger() {
Expand Down Expand Up @@ -275,10 +273,10 @@ open class Environment(
* @param priority If a component has the same time on the event list, the main component is sorted according to
* the priority. An event with a higher priority will be scheduled first.
*/
@OptIn(NumericDuration::class)
@OptIn(AmbiguousDuration::class)
fun run(
duration: Duration? = null, priority: Priority = NORMAL
) = run(duration?.asTicks(), null, priority)
duration: Duration? = null, until:Instant? = null, priority: Priority = NORMAL
) = run(duration?.asTicks(), until?.toTickTime(), priority)

// /**
// * Start execution of the simulation. See https://www.kalasim.org/basics/#running-a-simulation
Expand All @@ -299,10 +297,10 @@ open class Environment(
* @param priority If a component has the same time on the event list, the main component is sorted according to
* the priority. An event with a higher priority will be scheduled first.
*/
@OptIn(NumericDuration::class)
@OptIn(AmbiguousDuration::class)
fun run(
until: Instant, priority: Priority = NORMAL
) = run(until = until.asTickTime(), priority = priority)
) = run(until = until.toTickTime(), priority = priority)

/**
* Start execution of the simulation
Expand All @@ -316,7 +314,7 @@ open class Environment(
* @param priority If a component has the same time on the event list, the main component is sorted according to
* the priority. An event with a higher priority will be scheduled first.
*/
@NumericDuration
@AmbiguousDuration
fun run(
duration: Number? = null, until: TickTime? = null, priority: Priority = NORMAL, urgent: Boolean = false
) {
Expand Down Expand Up @@ -504,7 +502,7 @@ open class Environment(
}

fun wall2TickTime(instant: Instant): TickTime {
val offsetDuration = java.time.Duration.between(startDate, instant).toKotlinDuration()
val offsetDuration = instant - startDate!!

return TickTime(tickTransform.durationAsTicks(offsetDuration))
}
Expand Down
12 changes: 6 additions & 6 deletions src/main/kotlin/org/kalasim/Resource.kt
Expand Up @@ -437,19 +437,19 @@ data class ResourceTimelineSegment(

val Resource.timeline: List<ResourceTimelineSegment>
get() {
val capStats = capacityTimeline.statsData().asList().asDataFrame()
val capStats = capacityTimeline.statsData().asList().toDataFrame()
.add("metric") { ResourceMetric.Capacity }
val claimStats = claimedTimeline.statsData().asList().asDataFrame()
val claimStats = claimedTimeline.statsData().asList().toDataFrame()
.add("metric") { ResourceMetric.Claimed }
val occStats = occupancyTimeline.statsData().asList().asDataFrame()
val occStats = occupancyTimeline.statsData().asList().toDataFrame()
.add("metric") { ResourceMetric.Occupancy }
val availStats = occupancyTimeline.statsData().asList().asDataFrame()
val availStats = occupancyTimeline.statsData().asList().toDataFrame()
.add("metric") { ResourceMetric.Availability }

val requesters = requesters.sizeTimeline.statsData().asList().asDataFrame()
val requesters = requesters.sizeTimeline.statsData().asList().toDataFrame()
.add("metric") { ResourceMetric.Requesters }.convert("value").to<Double>()

val claimers = claimers.sizeTimeline.statsData().asList().asDataFrame()
val claimers = claimers.sizeTimeline.statsData().asList().toDataFrame()
.add("metric") { ResourceMetric.Claimers }
.convert("value").to<Double>()

Expand Down

0 comments on commit 5e54276

Please sign in to comment.