diff --git a/CHANGELOG.md b/CHANGELOG.md index f119adf622..06f00e6e80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -86,6 +86,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Simplifying ThermalHouse [#940](https://github.com/ie3-institute/simona/issues/940) - Prepare ThermalStorageTestData for Storage without storageVolumeLvlMin [#894](https://github.com/ie3-institute/simona/issues/894) - Renamed `ActivityStartTrigger`, `ScheduleTriggerMessage`, `CompletionMessage` in UML Diagrams[#675](https://github.com/ie3-institute/simona/issues/675) +- Simplifying quantity integration in QuantityUtil [#973](https://github.com/ie3-institute/simona/issues/973) - Prepare ThermalStorageTestData for Storage without storageVolumeLvlMin [#894](https://github.com/ie3-institute/simona/issues/894) ### Fixed diff --git a/build.gradle b/build.gradle index c95d8e7366..5d3c289349 100644 --- a/build.gradle +++ b/build.gradle @@ -25,12 +25,12 @@ ext { javaVersion = JavaVersion.VERSION_17 scalaVersion = '2.13' - scalaBinaryVersion = '2.13.14' + scalaBinaryVersion = '2.13.15' pekkoVersion = '1.1.1' jtsVersion = '1.20.0' confluentKafkaVersion = '7.4.0' tscfgVersion = '1.1.3' - scapegoatVersion = '3.0.0' + scapegoatVersion = '3.0.3' testContainerVersion = '0.41.4' @@ -103,7 +103,7 @@ dependencies { /* testing */ testImplementation 'org.spockframework:spock-core:2.3-groovy-4.0' testImplementation 'org.scalatestplus:mockito-3-4_2.13:3.2.10.0' - testImplementation 'org.mockito:mockito-core:5.13.0' // mocking framework + testImplementation 'org.mockito:mockito-core:5.14.1' // mocking framework testImplementation "org.scalatest:scalatest_${scalaVersion}:3.2.19" testRuntimeOnly 'com.vladsch.flexmark:flexmark-all:0.64.8' //scalatest html output testImplementation group: 'org.pegdown', name: 'pegdown', version: '1.6.0' diff --git a/gradle/scripts/scoverage.gradle b/gradle/scripts/scoverage.gradle index 4e95c17262..a998819c42 100644 --- a/gradle/scripts/scoverage.gradle +++ b/gradle/scripts/scoverage.gradle @@ -3,7 +3,7 @@ // https://github.com/scoverage/gradle-scoverage/issues/109 for details scoverage { - scoverageVersion = "2.1.1" + scoverageVersion = "2.2.1" scoverageScalaVersion = scalaBinaryVersion coverageOutputHTML = false coverageOutputXML = true diff --git a/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala b/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala index 4ca6d188fb..b55678ef83 100644 --- a/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala +++ b/src/main/scala/edu/ie3/util/scala/quantities/QuantityUtil.scala @@ -7,14 +7,13 @@ package edu.ie3.util.scala.quantities import edu.ie3.simona.exceptions.QuantityException -import edu.ie3.util.quantities.{QuantityUtil => PSQuantityUtil} import squants.time.{Hours, TimeDerivative, TimeIntegral} import squants.{Quantity, Seconds, UnitOfMeasure} import tech.units.indriya.ComparableQuantity import tech.units.indriya.function.Calculus import tech.units.indriya.quantity.Quantities -import scala.collection.mutable +import scala.collection.immutable.SortedMap import scala.util.{Failure, Try} object QuantityUtil { @@ -120,27 +119,39 @@ object QuantityUtil { lastValue: Q, ) - /* Determine the starting and ending value for the integral */ - val startValue = startingValue(values, windowStart) - val (lastTick, lastValue) = endingValue(values, windowEnd) - val valuesWithinWindow = mutable.LinkedHashMap.newBuilder - .addAll( - (values filter { case (tick, _) => - tick >= windowStart && tick <= windowEnd - }).toSeq - .sortBy(_._1) + val sortedValues = SortedMap.from(values) + + /* Determine the unit from the first best value */ + val unit = sortedValues.values.headOption + .map(_.unit) + .getOrElse( + throw new QuantityException( + "Unable to determine unit for dummy starting value." + ) ) - .result() + val zeroValue = unit(0d) + + /* the first relevant value for integration is placed before or at windowStart */ + val startValue = sortedValues + .rangeUntil(windowStart + 1) + .lastOption + .map { case (_, value) => + value + } + .getOrElse(zeroValue) - /* We need a value at the window end, so if the last value is not exactly there, replicate it at that point */ - if (windowEnd > lastTick) - valuesWithinWindow.addOne(windowEnd -> lastValue) + /* Filtering out values outside the specified time window. + Excluding the value at first tick because the fold below starts with it. + At the end, we add a dummy value (we only care about the ending tick). + */ + val valuesWithinWindow = sortedValues.range(windowStart + 1, windowEnd) + + (windowEnd -> zeroValue) /* Actually determining the integral, but sweeping over values and summing up everything */ valuesWithinWindow .foldLeft( IntegrationState( - startValue * Hours(0), + zeroValue * Hours(0), windowStart, startValue, ) @@ -158,67 +169,4 @@ object QuantityUtil { .currentIntegral } - /** Determine the starting value for the integration - * - * @param values - * Mapping of ticks to values - * @param windowStart - * Tick, where the integration window starts - * @tparam Q - * Type of quantity to account for - * @return - * Either the first value before the window starts or 0, if not - * apparent - */ - private def startingValue[Q <: squants.Quantity[Q]]( - values: Map[Long, Q], - windowStart: Long, - ): Q = { - values - .filter { case (tick, _) => - tick <= windowStart - } - .maxOption[(Long, Q)](Ordering.by(_._1)) match { - case Some((_, value)) => value - case None => - val unit = values.headOption - .map(_._2.unit) - .getOrElse( - throw new QuantityException( - "Unable to determine unit for dummy starting value." - ) - ) - unit(0d) - } - } - - /** Determine the last value for the integration - * - * @param values - * Mapping of ticks to values - * @param windowEnd - * Tick, where the integration window ends - * @tparam Q - * Type of quantity to account for - * @return - * Last entry before the integration window ends and it's corresponding - * tick - */ - private def endingValue[Q <: Quantity[Q]]( - values: Map[Long, Q], - windowEnd: Long, - ): (Long, Q) = { - values - .filter { case (tick, _) => - tick <= windowEnd - } - .maxOption[(Long, Q)](Ordering.by(_._1)) match { - case Some(tickToValue) => tickToValue - case None => - throw new QuantityException( - "Cannot integrate over an empty set of values." - ) - } - } - } diff --git a/src/test/scala/edu/ie3/util/quantities/QuantityUtilSpec.scala b/src/test/scala/edu/ie3/util/quantities/QuantityUtilSpec.scala index 1c1edb1f44..5f43a6a58b 100644 --- a/src/test/scala/edu/ie3/util/quantities/QuantityUtilSpec.scala +++ b/src/test/scala/edu/ie3/util/quantities/QuantityUtilSpec.scala @@ -6,7 +6,6 @@ package edu.ie3.util.quantities -import edu.ie3.simona.exceptions.QuantityException import edu.ie3.simona.test.common.UnitSpec import edu.ie3.util.scala.quantities.QuantityUtil import org.scalatest.prop.TableDrivenPropertyChecks @@ -28,57 +27,6 @@ class QuantityUtilSpec extends UnitSpec with TableDrivenPropertyChecks { ) "Integrating over quantities" when { - "determining the start value" should { - val startingValue = - PrivateMethod[Power](Symbol("startingValue")) - - "throw an exception, if values are empty and unit of \"empty\" quantity cannot be determined" in { - intercept[QuantityException] { - QuantityUtil invokePrivate startingValue( - Map.empty[Long, Power], - 1L, - ) - }.getMessage shouldBe "Unable to determine unit for dummy starting value." - } - - "bring default value, if there is nothing before window starts" in { - QuantityUtil invokePrivate startingValue( - values, - 1L, - ) should be - unit(0d) - - } - - "bring correct value, if there is something before window starts" in { - QuantityUtil invokePrivate startingValue( - values, - 2L, - ) should be - unit(5d) - - } - } - - "determining the end value" should { - val endingValue = - PrivateMethod[(Long, Power)](Symbol("endingValue")) - - "throw and exception, if there is no value before the window ends" in { - intercept[QuantityException] { - QuantityUtil invokePrivate endingValue(values, 1L) - }.getMessage shouldBe "Cannot integrate over an empty set of values." - } - - "bring correct value, if there is something before window ends" in { - QuantityUtil invokePrivate endingValue(values, 2L) match { - case (tick, value) => - tick shouldBe 2L - value should approximate(unit(5d)) - } - } - } - "actually integrating" should { "lead to correct values" in { val cases = Table( @@ -94,7 +42,7 @@ class QuantityUtilSpec extends UnitSpec with TableDrivenPropertyChecks { values, windowStart, windowEnd, - ) =~ expectedResult + ) should approximate(expectedResult) } } }