From 65ae6795d0a66cf4f993bea1dc6f07ca1e92b9d5 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Wed, 16 Apr 2025 18:51:02 +0200 Subject: [PATCH 01/11] feat: add terminator metrics stable for steps --- .../terminators/MetricsStableForSteps.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt new file mode 100644 index 0000000000..fe6571ee14 --- /dev/null +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt @@ -0,0 +1,38 @@ +package it.unibo.alchemist.model.terminators + +import it.unibo.alchemist.model.Environment +import it.unibo.alchemist.model.Position +import it.unibo.alchemist.model.TerminationPredicate + +class MetricsStableForSteps( + private val stepInterval: Long, + private val equalInterval: Long, + private val metricsToCheck: (Environment>) -> Map, +): TerminationPredicate> { + private var stepsChecked: Long = 0 + private var equalSuccess: Long = 0 + private var lastUpdatedMetrics: Map = emptyMap() + + init { + require(stepInterval > 0 && equalInterval > 0) { "check and equal interval must be positive" } + } + + override fun invoke(environment: Environment>): Boolean { + val metrics: Map = metricsToCheck(environment) + require(metrics.isNotEmpty()) { + "There should be at least one metric to check." + } + return if (lastUpdatedMetrics == metrics) { + if (++stepsChecked >= stepInterval){ + stepsChecked = 0 + ++equalSuccess >= equalInterval + } + else false + } else { + stepsChecked = 0 + equalSuccess = 0 + lastUpdatedMetrics = metrics + false + } + } +} From 4ee965f92d7f7b65195ca5cfc853fb7cb9a16200 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Wed, 16 Apr 2025 18:51:17 +0200 Subject: [PATCH 02/11] feat: add terminator metrics stable for time --- .../model/terminators/MetricsStableForTime.kt | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt new file mode 100644 index 0000000000..15926f6a63 --- /dev/null +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt @@ -0,0 +1,62 @@ +package it.unibo.alchemist.model.terminators + +import it.unibo.alchemist.model.Environment +import it.unibo.alchemist.model.Position +import it.unibo.alchemist.model.TerminationPredicate +import it.unibo.alchemist.model.Time +import it.unibo.alchemist.model.Time.Companion.ZERO + +/** + * [stableForTime] for how much time the [metricsToCheck] should be stable. + * [timeIntervalToCheck] every time-step it should check, if zero it checks at every invocation. + * [equalTimes] how many times it should be stable. + */ +class MetricsStableForTime +@JvmOverloads +constructor( + private val stableForTime: Time, + private val timeIntervalToCheck: Time = ZERO, + private val equalTimes: Long, + private val metricsToCheck: (Environment>) -> Map, +): TerminationPredicate> { + private var timeStabilitySuccess: Time = ZERO + private var lastChecked: Time = ZERO + private var equalSuccess: Long = 0 + private var lastUpdatedMetrics: Map = emptyMap() + + init { + require(stableForTime > ZERO) { + "The amount of time to check the stability should be more than zero." + } + } + + override fun invoke(environment: Environment>): Boolean { + val simulationTime = environment.simulation.time + val checkedInterval = simulationTime - lastChecked + return when { + checkedInterval >= timeIntervalToCheck -> { + val metrics: Map = metricsToCheck(environment) + require(metrics.isNotEmpty()) { + "There should be at least one metric to check." + } + lastChecked = simulationTime + when { + lastUpdatedMetrics == metrics -> { + timeStabilitySuccess += checkedInterval + if(timeStabilitySuccess >= stableForTime) { + timeStabilitySuccess = ZERO + ++equalSuccess >= equalTimes + } else false + } + else -> { + timeStabilitySuccess = ZERO + equalSuccess = 0 + lastUpdatedMetrics = metrics + false + } + } + } + else -> false + } + } +} From c766ebf1562b37bc8c777ce9effe1ed3f0adf4b9 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Tue, 22 Apr 2025 17:57:30 +0200 Subject: [PATCH 03/11] docs: add documentation --- .../terminators/MetricsStableForSteps.kt | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt index fe6571ee14..6a704baec3 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt @@ -4,11 +4,15 @@ import it.unibo.alchemist.model.Environment import it.unibo.alchemist.model.Position import it.unibo.alchemist.model.TerminationPredicate +/** + * [stepInterval] for how many steps the [metricsToCheck] should be stable. + * [equalInterval] how many times it should be stable. + */ class MetricsStableForSteps( private val stepInterval: Long, private val equalInterval: Long, private val metricsToCheck: (Environment>) -> Map, -): TerminationPredicate> { +) : TerminationPredicate> { private var stepsChecked: Long = 0 private var equalSuccess: Long = 0 private var lastUpdatedMetrics: Map = emptyMap() @@ -22,17 +26,17 @@ class MetricsStableForSteps( require(metrics.isNotEmpty()) { "There should be at least one metric to check." } - return if (lastUpdatedMetrics == metrics) { - if (++stepsChecked >= stepInterval){ - stepsChecked = 0 + return when { + lastUpdatedMetrics == metrics -> { + if (++stepsChecked >= stepInterval) stepsChecked = 0 ++equalSuccess >= equalInterval } - else false - } else { - stepsChecked = 0 - equalSuccess = 0 - lastUpdatedMetrics = metrics - false + else -> { + stepsChecked = 0 + equalSuccess = 0 + lastUpdatedMetrics = metrics + false + } } } } From e2cd86829d5cda0944ccb4e8d953368da1ba057c Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Tue, 22 Apr 2025 18:09:01 +0200 Subject: [PATCH 04/11] refactor: move reset params outside --- .../model/terminators/MetricsStableForSteps.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt index 6a704baec3..8a6e131619 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt @@ -32,11 +32,15 @@ class MetricsStableForSteps( ++equalSuccess >= equalInterval } else -> { - stepsChecked = 0 - equalSuccess = 0 - lastUpdatedMetrics = metrics + reset(metrics) false } } } + + private fun reset(metrics: Map) { + stepsChecked = 0 + equalSuccess = 0 + lastUpdatedMetrics = metrics + } } From e403143115c9230f018d65fde6da40bbcf0f8d15 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Tue, 22 Apr 2025 18:10:30 +0200 Subject: [PATCH 05/11] refactor: move reset params outside --- .../model/terminators/MetricsStableForTime.kt | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt index 15926f6a63..b11e934e41 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt @@ -18,7 +18,7 @@ constructor( private val timeIntervalToCheck: Time = ZERO, private val equalTimes: Long, private val metricsToCheck: (Environment>) -> Map, -): TerminationPredicate> { +) : TerminationPredicate> { private var timeStabilitySuccess: Time = ZERO private var lastChecked: Time = ZERO private var equalSuccess: Long = 0 @@ -35,23 +35,18 @@ constructor( val checkedInterval = simulationTime - lastChecked return when { checkedInterval >= timeIntervalToCheck -> { - val metrics: Map = metricsToCheck(environment) - require(metrics.isNotEmpty()) { - "There should be at least one metric to check." + val metrics: Map = metricsToCheck(environment).also { + require(it.isNotEmpty()) { "There should be at least one metric to check." } } lastChecked = simulationTime when { lastUpdatedMetrics == metrics -> { timeStabilitySuccess += checkedInterval - if(timeStabilitySuccess >= stableForTime) { - timeStabilitySuccess = ZERO - ++equalSuccess >= equalTimes - } else false + if (timeStabilitySuccess >= stableForTime) timeStabilitySuccess = ZERO + ++equalSuccess >= equalTimes } else -> { - timeStabilitySuccess = ZERO - equalSuccess = 0 - lastUpdatedMetrics = metrics + reset(metrics) false } } @@ -59,4 +54,10 @@ constructor( else -> false } } + + private fun reset(metrics: Map) { + timeStabilitySuccess = ZERO + equalSuccess = 0 + lastUpdatedMetrics = metrics + } } From c2a94febac0aafb5f4201414128135f47edd845d Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Fri, 16 May 2025 15:09:13 +0200 Subject: [PATCH 06/11] feat: add single stable metrics termination for both steps and time checks --- .../terminators/MetricsStableForSteps.kt | 46 ------- .../model/terminators/MetricsStableForTime.kt | 63 ---------- .../model/terminators/StableMetrics.kt | 114 ++++++++++++++++++ 3 files changed, 114 insertions(+), 109 deletions(-) delete mode 100644 alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt delete mode 100644 alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt create mode 100644 alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt deleted file mode 100644 index 8a6e131619..0000000000 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForSteps.kt +++ /dev/null @@ -1,46 +0,0 @@ -package it.unibo.alchemist.model.terminators - -import it.unibo.alchemist.model.Environment -import it.unibo.alchemist.model.Position -import it.unibo.alchemist.model.TerminationPredicate - -/** - * [stepInterval] for how many steps the [metricsToCheck] should be stable. - * [equalInterval] how many times it should be stable. - */ -class MetricsStableForSteps( - private val stepInterval: Long, - private val equalInterval: Long, - private val metricsToCheck: (Environment>) -> Map, -) : TerminationPredicate> { - private var stepsChecked: Long = 0 - private var equalSuccess: Long = 0 - private var lastUpdatedMetrics: Map = emptyMap() - - init { - require(stepInterval > 0 && equalInterval > 0) { "check and equal interval must be positive" } - } - - override fun invoke(environment: Environment>): Boolean { - val metrics: Map = metricsToCheck(environment) - require(metrics.isNotEmpty()) { - "There should be at least one metric to check." - } - return when { - lastUpdatedMetrics == metrics -> { - if (++stepsChecked >= stepInterval) stepsChecked = 0 - ++equalSuccess >= equalInterval - } - else -> { - reset(metrics) - false - } - } - } - - private fun reset(metrics: Map) { - stepsChecked = 0 - equalSuccess = 0 - lastUpdatedMetrics = metrics - } -} diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt deleted file mode 100644 index b11e934e41..0000000000 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/MetricsStableForTime.kt +++ /dev/null @@ -1,63 +0,0 @@ -package it.unibo.alchemist.model.terminators - -import it.unibo.alchemist.model.Environment -import it.unibo.alchemist.model.Position -import it.unibo.alchemist.model.TerminationPredicate -import it.unibo.alchemist.model.Time -import it.unibo.alchemist.model.Time.Companion.ZERO - -/** - * [stableForTime] for how much time the [metricsToCheck] should be stable. - * [timeIntervalToCheck] every time-step it should check, if zero it checks at every invocation. - * [equalTimes] how many times it should be stable. - */ -class MetricsStableForTime -@JvmOverloads -constructor( - private val stableForTime: Time, - private val timeIntervalToCheck: Time = ZERO, - private val equalTimes: Long, - private val metricsToCheck: (Environment>) -> Map, -) : TerminationPredicate> { - private var timeStabilitySuccess: Time = ZERO - private var lastChecked: Time = ZERO - private var equalSuccess: Long = 0 - private var lastUpdatedMetrics: Map = emptyMap() - - init { - require(stableForTime > ZERO) { - "The amount of time to check the stability should be more than zero." - } - } - - override fun invoke(environment: Environment>): Boolean { - val simulationTime = environment.simulation.time - val checkedInterval = simulationTime - lastChecked - return when { - checkedInterval >= timeIntervalToCheck -> { - val metrics: Map = metricsToCheck(environment).also { - require(it.isNotEmpty()) { "There should be at least one metric to check." } - } - lastChecked = simulationTime - when { - lastUpdatedMetrics == metrics -> { - timeStabilitySuccess += checkedInterval - if (timeStabilitySuccess >= stableForTime) timeStabilitySuccess = ZERO - ++equalSuccess >= equalTimes - } - else -> { - reset(metrics) - false - } - } - } - else -> false - } - } - - private fun reset(metrics: Map) { - timeStabilitySuccess = ZERO - equalSuccess = 0 - lastUpdatedMetrics = metrics - } -} diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt new file mode 100644 index 0000000000..7ebe996a91 --- /dev/null +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt @@ -0,0 +1,114 @@ +package it.unibo.alchemist.model.terminators + +import it.unibo.alchemist.model.Environment +import it.unibo.alchemist.model.Position +import it.unibo.alchemist.model.TerminationPredicate +import it.unibo.alchemist.model.Time +import it.unibo.alchemist.model.Time.Companion.INFINITY +import it.unibo.alchemist.model.Time.Companion.ZERO +import kotlin.Long.Companion.MAX_VALUE +import kotlin.require + +/** + * [stableForTotalSteps] for how many steps the [metricsToCheck] should be stable, + * zero if taking into account just the time. + * [checkStepInterval] every step it should check, if zero it checks at every invocation. + * [stableForTotalTime] for how much time the [metricsToCheck] should be stable, + * zero if taking into account just the steps. + * [checkTimeInterval] every time-step it should check, if zero it checks at every invocation. + * Both [stableForTotalSteps] and [stableForTotalTime] can be used together to check for stability. + */ +class StableMetrics( + private val stableForTotalSteps: Long, + private val checkStepInterval: Long, + private val stableForTotalTime: Time, + private val checkTimeInterval: Time, + private val metricsToCheck: (Environment>) -> Map, +) : TerminationPredicate> { + // steps-related checks + private var lastStepsChecked: Long = 0 + private var stableSteps: Long = 0 + private var lastUpdatedMetricsSteps: Map = emptyMap() + + // time-related checks + private var lastTimeChecked: Time = ZERO + private var stableTime: Time = ZERO + private var lastUpdatedMetricsTime: Map = emptyMap() + init { + require((stableForTotalTime > ZERO) || (stableForTotalSteps > 0)) { + "At least one of the stability conditions (stableForTime or stableForSteps) must be greater than zero." + } + require(checkTimeInterval <= stableForTotalTime) { + "The time interval to check should be less than or equal to the stable time." + } + require(checkStepInterval <= stableForTotalSteps) { + "The step interval to check should be less than or equal to the stable steps." + } + } + + override fun invoke(environment: Environment>): Boolean = when { + stableForTotalTime > ZERO && stableForTotalSteps > 0 -> { + val timeStable = checkTime(environment) + val stepStable = checkSteps(environment) + timeStable && stepStable + } + stableForTotalTime > ZERO -> checkTime(environment) + stableForTotalSteps > 0 -> checkSteps(environment) + else -> error("This should never happen, at least one of the stability conditions must be greater than zero.") + } + + private fun checkSteps(environment: Environment>): Boolean { + require(stableForTotalSteps > 0) { "Steps check and equal interval must be positive." } + val currentStep = environment.simulation.step + val checkedStepsInterval = currentStep - lastStepsChecked + return when { + stableSteps == MAX_VALUE -> true + checkedStepsInterval >= checkStepInterval -> { + // update the last checked step and get the current metrics + lastStepsChecked = currentStep + val currentMetrics = metricsToCheck(environment).also { + require(it.isNotEmpty()) { "There should be at least one metric to check." } + } + when { + lastUpdatedMetricsSteps == currentMetrics -> { // metrics are the same as before = stable + stableSteps += checkedStepsInterval + return (stableSteps >= stableForTotalSteps).also { if (it) stableSteps = MAX_VALUE } + } + else -> { // reset the counters + stableSteps = 0 + lastUpdatedMetricsSteps = currentMetrics + false + } + } + } + else -> false + } + } + + private fun checkTime(environment: Environment>): Boolean { + require(stableForTotalTime > ZERO) { "The amount of time to check the stability should be more than zero." } + val currentTime = environment.simulation.time + val checkedTimeInterval = currentTime - lastTimeChecked + return when { + stableTime == INFINITY -> true + checkedTimeInterval >= checkTimeInterval -> { + lastTimeChecked = currentTime + val currentMetrics = metricsToCheck(environment).also { + require(it.isNotEmpty()) { "There should be at least one metric to check." } + } + when { + lastUpdatedMetricsTime == currentMetrics -> { // metrics are the same as before = stable + stableTime += checkedTimeInterval + return (stableTime >= stableForTotalTime).also { if (it) stableTime = INFINITY } + } + else -> { // reset the counters + stableTime = ZERO + lastUpdatedMetricsTime = currentMetrics + false + } + } + } + else -> false + } + } +} From ab6d7ae3765476ce03424464f8dd489f7e315963 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Mon, 19 May 2025 17:58:22 +0200 Subject: [PATCH 07/11] refactor: try to not have duplicate code --- .../model/terminators/StableMetrics.kt | 157 +++++++++++------- 1 file changed, 97 insertions(+), 60 deletions(-) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt index 7ebe996a91..fcfa789fa4 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt @@ -7,7 +7,6 @@ import it.unibo.alchemist.model.Time import it.unibo.alchemist.model.Time.Companion.INFINITY import it.unibo.alchemist.model.Time.Companion.ZERO import kotlin.Long.Companion.MAX_VALUE -import kotlin.require /** * [stableForTotalSteps] for how many steps the [metricsToCheck] should be stable, @@ -16,24 +15,39 @@ import kotlin.require * [stableForTotalTime] for how much time the [metricsToCheck] should be stable, * zero if taking into account just the steps. * [checkTimeInterval] every time-step it should check, if zero it checks at every invocation. - * Both [stableForTotalSteps] and [stableForTotalTime] can be used together to check for stability. */ class StableMetrics( - private val stableForTotalSteps: Long, - private val checkStepInterval: Long, - private val stableForTotalTime: Time, - private val checkTimeInterval: Time, + private val stableForTotalSteps: Long, // total steps to be stable + private val checkStepInterval: Long, // steps interval to check + private val stableForTotalTime: Time, // total time to be stable + private val checkTimeInterval: Time, // time interval to check private val metricsToCheck: (Environment>) -> Map, ) : TerminationPredicate> { - // steps-related checks - private var lastStepsChecked: Long = 0 - private var stableSteps: Long = 0 - private var lastUpdatedMetricsSteps: Map = emptyMap() + constructor( + stableForTotalSteps: Long, + checkStepInterval: Long, + metricsToCheck: (Environment>) -> Map, + ) : this( + stableForTotalSteps = stableForTotalSteps, + checkStepInterval = checkStepInterval, + stableForTotalTime = ZERO, + checkTimeInterval = ZERO, + metricsToCheck = metricsToCheck, + ) + constructor( + stableForTotalTime: Time, + checkTimeInterval: Time, + metricsToCheck: (Environment>) -> Map, + ) : this( + stableForTotalSteps = 0, + checkStepInterval = 0, + stableForTotalTime = stableForTotalTime, + checkTimeInterval = checkTimeInterval, + metricsToCheck = metricsToCheck, + ) - // time-related checks - private var lastTimeChecked: Time = ZERO - private var stableTime: Time = ZERO - private var lastUpdatedMetricsTime: Map = emptyMap() + private val stepTracker by lazy { StepMetricTracker() } + private val timeTracker by lazy { TimeMetricTracker() } init { require((stableForTotalTime > ZERO) || (stableForTotalSteps > 0)) { "At least one of the stability conditions (stableForTime or stableForSteps) must be greater than zero." @@ -47,36 +61,41 @@ class StableMetrics( } override fun invoke(environment: Environment>): Boolean = when { - stableForTotalTime > ZERO && stableForTotalSteps > 0 -> { - val timeStable = checkTime(environment) - val stepStable = checkSteps(environment) - timeStable && stepStable + stableForTotalTime > DEFAULT_TIME && stableForTotalSteps > DEFAULT_STEP -> { + val stableSteps = checkStability(environment, stepTracker, STEP_MAX_VALUE, DEFAULT_STEP) + val stableTime = checkStability(environment, timeTracker, TIME_MAX_VALUE, DEFAULT_TIME) + stableTime && stableSteps } - stableForTotalTime > ZERO -> checkTime(environment) - stableForTotalSteps > 0 -> checkSteps(environment) - else -> error("This should never happen, at least one of the stability conditions must be greater than zero.") + stableForTotalTime > DEFAULT_TIME -> + checkStability(environment, timeTracker, TIME_MAX_VALUE, DEFAULT_TIME) + stableForTotalSteps > DEFAULT_STEP -> + checkStability(environment, stepTracker, STEP_MAX_VALUE, DEFAULT_STEP) + else -> error("This should never happen.") } - private fun checkSteps(environment: Environment>): Boolean { - require(stableForTotalSteps > 0) { "Steps check and equal interval must be positive." } - val currentStep = environment.simulation.step - val checkedStepsInterval = currentStep - lastStepsChecked + private fun > checkStability( + env: Environment>, + tracker: MetricTracker, + maxValue: Type, + defaultValue: Type, + ): Boolean = with(tracker) { + val current = current(env) + val interval = evaluateInterval(current) return when { - stableSteps == MAX_VALUE -> true - checkedStepsInterval >= checkStepInterval -> { - // update the last checked step and get the current metrics - lastStepsChecked = currentStep - val currentMetrics = metricsToCheck(environment).also { + stableValue >= maxValue -> true + shouldBeChecked(interval) -> { + lastChecked = current + val currentMetrics = metricsToCheck(env).also { require(it.isNotEmpty()) { "There should be at least one metric to check." } } when { - lastUpdatedMetricsSteps == currentMetrics -> { // metrics are the same as before = stable - stableSteps += checkedStepsInterval - return (stableSteps >= stableForTotalSteps).also { if (it) stableSteps = MAX_VALUE } + currentMetrics == lastMetrics -> { + increaseStability(interval) + return (stableValue >= totalStability).also { if (it) stableValue = maxValue } } - else -> { // reset the counters - stableSteps = 0 - lastUpdatedMetricsSteps = currentMetrics + else -> { + stableValue = defaultValue + lastMetrics = currentMetrics false } } @@ -85,30 +104,48 @@ class StableMetrics( } } - private fun checkTime(environment: Environment>): Boolean { - require(stableForTotalTime > ZERO) { "The amount of time to check the stability should be more than zero." } - val currentTime = environment.simulation.time - val checkedTimeInterval = currentTime - lastTimeChecked - return when { - stableTime == INFINITY -> true - checkedTimeInterval >= checkTimeInterval -> { - lastTimeChecked = currentTime - val currentMetrics = metricsToCheck(environment).also { - require(it.isNotEmpty()) { "There should be at least one metric to check." } - } - when { - lastUpdatedMetricsTime == currentMetrics -> { // metrics are the same as before = stable - stableTime += checkedTimeInterval - return (stableTime >= stableForTotalTime).also { if (it) stableTime = INFINITY } - } - else -> { // reset the counters - stableTime = ZERO - lastUpdatedMetricsTime = currentMetrics - false - } - } - } - else -> false + private interface MetricTracker> { + var lastChecked: Type + var stableValue: Type + var lastMetrics: Map + val totalStability: Type + val checkInterval: Type + fun current(env: Environment>): Type + fun evaluateInterval(current: Type): Type + fun shouldBeChecked(interval: Type): Boolean = interval >= checkInterval + fun increaseStability(interval: Type) + } + + private inner class StepMetricTracker : MetricTracker { + override var lastChecked = DEFAULT_STEP + override var stableValue = DEFAULT_STEP + override var lastMetrics: Map = emptyMap() + override val totalStability = stableForTotalSteps + override val checkInterval = checkStepInterval + override fun current(env: Environment>): Long = env.simulation.step + override fun evaluateInterval(current: Long): Long = current - lastChecked + override fun increaseStability(interval: Long) { + stableValue += interval + } + } + + private inner class TimeMetricTracker : MetricTracker { + override var lastChecked = DEFAULT_TIME + override var stableValue = DEFAULT_TIME + override var lastMetrics: Map = emptyMap() + override val totalStability = stableForTotalTime + override val checkInterval = checkTimeInterval + override fun current(env: Environment>): Time = env.simulation.time + override fun evaluateInterval(current: Time): Time = current - lastChecked + override fun increaseStability(interval: Time) { + stableValue += interval } } + + companion object { + const val STEP_MAX_VALUE: Long = MAX_VALUE + const val DEFAULT_STEP: Long = 0L + val DEFAULT_TIME: Time = ZERO + val TIME_MAX_VALUE: Time = INFINITY + } } From a072071df466c6d7648cddc342fe0906b9f61f19 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Thu, 3 Jul 2025 14:25:39 +0200 Subject: [PATCH 08/11] docs: add missing documentation --- .../model/terminators/StableMetrics.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt index fcfa789fa4..1e6e98a577 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt @@ -142,10 +142,28 @@ class StableMetrics( } } + /** + * Companion object for StableMetrics. + * + * Provides constants relevant to the stability checks, such as maximum + * and default values for steps and time. + */ companion object { + /** + * The maximum value for steps, used to determine when the step stability is reached. + */ const val STEP_MAX_VALUE: Long = MAX_VALUE + /** + * The default value for steps, used when no stability is required. + */ const val DEFAULT_STEP: Long = 0L - val DEFAULT_TIME: Time = ZERO + /** + * The maximum value for time, used to determine when the time stability is reached. + */ val TIME_MAX_VALUE: Time = INFINITY + /** + * The default time value, used when no stability is required. + */ + val DEFAULT_TIME: Time = ZERO } } From 27979afc696d2016e47be260c95a008c8cfdfa02 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Thu, 3 Jul 2025 14:28:58 +0200 Subject: [PATCH 09/11] refactor: remove useless parentheses --- .../it/unibo/alchemist/model/terminators/StableMetrics.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt index 1e6e98a577..11fdb64832 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt @@ -49,7 +49,7 @@ class StableMetrics( private val stepTracker by lazy { StepMetricTracker() } private val timeTracker by lazy { TimeMetricTracker() } init { - require((stableForTotalTime > ZERO) || (stableForTotalSteps > 0)) { + require(stableForTotalTime > ZERO || (stableForTotalSteps > 0)) { "At least one of the stability conditions (stableForTime or stableForSteps) must be greater than zero." } require(checkTimeInterval <= stableForTotalTime) { From 0e41504151354d3529c3868510d01f49962f8b9c Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Mon, 14 Jul 2025 12:31:23 +0200 Subject: [PATCH 10/11] style: add missing space --- .../it/unibo/alchemist/model/terminators/StableMetrics.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt index 11fdb64832..dbaa3a4542 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt @@ -153,14 +153,17 @@ class StableMetrics( * The maximum value for steps, used to determine when the step stability is reached. */ const val STEP_MAX_VALUE: Long = MAX_VALUE + /** * The default value for steps, used when no stability is required. */ const val DEFAULT_STEP: Long = 0L + /** * The maximum value for time, used to determine when the time stability is reached. */ val TIME_MAX_VALUE: Time = INFINITY + /** * The default time value, used when no stability is required. */ From 5a9b59e173c8da0332cf600cb56dcd45b3866e51 Mon Sep 17 00:00:00 2001 From: Angela Cortecchia Date: Mon, 14 Jul 2025 12:33:54 +0200 Subject: [PATCH 11/11] style: remove useless parentheses --- .../it/unibo/alchemist/model/terminators/StableMetrics.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt index dbaa3a4542..db1165c231 100644 --- a/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt +++ b/alchemist-implementationbase/src/main/kotlin/it/unibo/alchemist/model/terminators/StableMetrics.kt @@ -49,7 +49,7 @@ class StableMetrics( private val stepTracker by lazy { StepMetricTracker() } private val timeTracker by lazy { TimeMetricTracker() } init { - require(stableForTotalTime > ZERO || (stableForTotalSteps > 0)) { + require(stableForTotalTime > ZERO || stableForTotalSteps > 0) { "At least one of the stability conditions (stableForTime or stableForSteps) must be greater than zero." } require(checkTimeInterval <= stableForTotalTime) {