Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,4 @@ Please read [SUPPORT](SUPPORT.md) for how to connect and get into contact with t
[github:create-from-template]: https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/creating-a-repository-from-a-template
[kotlin]: https://kotlinlang.org/
[gradle]: https://gradle.org/

2 changes: 1 addition & 1 deletion measure/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ plugins {
`java-library`

// Apply dokka plugin to allow extraction of ducumentation from KDoc comments
id("org.jetbrains.dokka") version "1.4.20"
id("org.jetbrains.dokka") version "1.9.20"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The build complained that this plugin could not be found.


// Make sure we can publish to maven
`maven-publish`
Expand Down
12 changes: 10 additions & 2 deletions measure/src/main/kotlin/com/alliander/open/measure/Measure.kt
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,16 @@ data class Measure<U : Units>(val amount: BigDecimal, val units: U) : Comparable
val absoluteFactor = (factor `in` base).abs()
return dividend.roundToMultiple(absoluteFactor, roundingMode) * base
}

}

private fun BigDecimal.roundToMultiple(factor: BigDecimal, roundingMode: RoundingMode): BigDecimal =
this.divide(factor, 0, roundingMode) * factor
this.divide(factor, 0, roundingMode) * factor

operator fun <U : Units> BigDecimal.times(m: Measure<U>): Measure<U> =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In some cases you want to multiply your measure with some dimensionless value. e.g. when calculating the effectiveness of an consumer equipment on the congested element effectiveness * (4 * megaWatt))

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasnt this functionality already present in measure 1.3? From MeasureKt.class

public fun <A : com.alliander.measure.Units, B : A> baseUnits(first: A, second: B): A { /* compiled code */ }

public operator fun <A : com.alliander.measure.Units, B : A> A.compareTo(other: B): kotlin.Int { /* compiled code */ }

public operator fun <U : com.alliander.measure.Units> java.math.BigDecimal.times(units: U): com.alliander.measure.Measure<U> { /* compiled code */ }

public operator fun <U : com.alliander.measure.Units> kotlin.Double.times(units: U): com.alliander.measure.Measure<U> { /* compiled code */ }

public operator fun <U : com.alliander.measure.Units> kotlin.Float.times(units: U): com.alliander.measure.Measure<U> { /* compiled code */ }

public operator fun <U : com.alliander.measure.Units> kotlin.Int.times(units: U): com.alliander.measure.Measure<U> { /* compiled code */ }

public operator fun <U : com.alliander.measure.Units> kotlin.Long.times(units: U): com.alliander.measure.Measure<U> { /* compiled code */ }


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, what you are referring to is a multiplication with a Unit --> 13 * kiloWatt

What this does is a multiplication with a Meaure:

val measure: Measure<Power> = 4 * megaWatt
val newMeasure: Measure<Power> = 3 * measure // 12 MW 

Measure(amount = m.amount.times(this), units = m.units)

operator fun <U : Units> Int.times(m: Measure<U>): Measure<U> =
BigDecimal.valueOf(this.toLong()) * m

operator fun <U : Units> Long.times(m: Measure<U>): Measure<U> =
BigDecimal.valueOf(this) * m
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,60 @@

package com.alliander.open.measure

import com.alliander.open.measure.Current.Companion.ampere
import com.alliander.open.measure.Energy.Companion.joule
import com.alliander.open.measure.Power.Companion.watt
import com.alliander.open.measure.Time.Companion.seconds
import com.alliander.open.measure.Voltage.Companion.volt
import java.math.BigDecimal

@JvmName("powerTimesTime")
operator fun Measure<Power>.times(duration: Measure<Time>): Measure<Energy> {
val p = this `as` watt
val dt = duration `as` seconds
val p = this `in` watt
val dt = duration `in` seconds
return Measure(p * dt, joule)
}

@JvmName("currentTimesVoltage")
operator fun Measure<Current>.times(voltage: Measure<Voltage>): Measure<Power> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P = I V

val i = this `in` ampere
val v = voltage `in` volt
return Measure(i * v, watt)
}

@JvmName("voltageTimesCurrent")
operator fun Measure<Voltage>.times(current: Measure<Current>): Measure<Power> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P = V I

val v = this `in` volt
val i = current `in` ampere
return Measure(i * v, watt)
}

@JvmName("powerDivVoltage")
operator fun Measure<Power>.div(voltage: Measure<Voltage>): Measure<Current> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I = P / V

val p = this `in` watt
val v = voltage `in` volt
return Measure(p / v, ampere)
}

@JvmName("powerDivCurrent")
operator fun Measure<Power>.div(current: Measure<Current>): Measure<Voltage> {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

V = P / I

val p = this `in` watt
val i = current `in` ampere
return Measure(p / i, volt)
}

class Voltage(suffix: String, ratio: BigDecimal = BigDecimal.ONE) : Units(suffix, ratio) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Voltage definition, I don't expect that we will use more then kiloVolts (our distribution grid is max 380kV)

companion object {
val volt = Voltage("V")
val kiloVolt = Voltage("kV", 1_000.toBigDecimal())
}
}

return Measure(p.amount * dt.amount, joule)
class Current(suffix: String, ratio: BigDecimal = BigDecimal.ONE) : Units(suffix, ratio) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current definition, I don't expect that we will use more then kA, that already seems really high to me.

companion object {
val ampere = Current("A")
val kiloAmpere = Current("kA", 1_000.toBigDecimal())
}
}

class Power(suffix: String, ratio: BigDecimal = BigDecimal.ONE) : Units(suffix, ratio) {
Expand All @@ -30,7 +74,7 @@ class Energy(suffix: String, ratio: BigDecimal = BigDecimal.ONE) : Units(suffix,
val kiloJoule = Energy("kJ", 1_000.toBigDecimal())
val megaJoule = Energy("MJ", 1_000_000.toBigDecimal())
val kiloWattHour = Energy("kWh", 3_600_000.toBigDecimal())
val megaWattHour = Energy("mWh", 3_600_000_000.toBigDecimal())
val megaWattHour = Energy("MWh", 3_600_000_000.toBigDecimal())
}
}

Expand All @@ -41,4 +85,3 @@ class Time(suffix: String, ratio: BigDecimal = BigDecimal.ONE) : Units(suffix, r
val hours = Time("h", 3_600.toBigDecimal())
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -8,36 +8,21 @@ import com.alliander.open.measure.Energy.Companion.joule
import com.alliander.open.measure.Energy.Companion.kiloJoule
import com.alliander.open.measure.Energy.Companion.kiloWattHour
import com.alliander.open.measure.Energy.Companion.megaJoule
import com.alliander.open.measure.Energy.Companion.megaWattHour
import com.alliander.open.measure.Power.Companion.kiloWatt
import com.alliander.open.measure.Power.Companion.megaWatt
import com.alliander.open.measure.Power.Companion.watt
import com.alliander.open.measure.Time.Companion.hours
import com.alliander.open.measure.Time.Companion.minutes
import com.alliander.open.measure.Time.Companion.seconds
import com.alliander.open.measure.extension.sum
import io.kotest.core.spec.style.StringSpec
import io.kotest.data.headers
import io.kotest.data.row
import io.kotest.data.table
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
import io.kotest.property.checkAll
import java.math.BigDecimal
import java.math.RoundingMode.UP

class MeasureTest : StringSpec({
"Joule, Watt and seconds are different units" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved this to a more elaborate test in the new test file: The units have the correct measure

joule shouldNotBe watt
joule shouldNotBe seconds

watt shouldNotBe joule
watt shouldNotBe seconds

seconds shouldNotBe joule
seconds shouldNotBe watt
}

"joules and kiloJoules can be added" {
val left = 1000 * joule
val right = 1 * kiloJoule
Expand All @@ -47,15 +32,6 @@ class MeasureTest : StringSpec({
sum shouldBe 2000 * joule
}

"power times time is energy" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move to the new test file

val power = 10 * kiloWatt
val duration = 15 * minutes

val energy = power * duration

energy `as` megaJoule shouldBe 9 * megaJoule
}

"joules can be added" {
checkAll { leftQuantity: Int, rightQuantity: Int ->
val left = leftQuantity * joule
Expand Down Expand Up @@ -123,14 +99,6 @@ class MeasureTest : StringSpec({
}
}

"energySum returns correct amount of energy when given a list of measures of energy" {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was already covered by another test.

val list = listOf(100 * joule, 1 * megaWattHour, 1 * megaJoule)
val result = list.sum()

val expectedResult = 3601000100 * joule
result shouldBe expectedResult
}

"roundUpToNextMultiple processes different units correctly" {
io.kotest.data.forAll(
table(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// SPDX-FileCopyrightText: 2021-2022 Alliander N.V.
//
// SPDX-License-Identifier: MPL-2.0

package com.alliander.open.measure

import com.alliander.open.measure.Current.Companion.ampere
import com.alliander.open.measure.Current.Companion.kiloAmpere
import com.alliander.open.measure.Energy.Companion.joule
import com.alliander.open.measure.Energy.Companion.kiloJoule
import com.alliander.open.measure.Energy.Companion.kiloWattHour
import com.alliander.open.measure.Energy.Companion.megaJoule
import com.alliander.open.measure.Energy.Companion.megaWattHour
import com.alliander.open.measure.Power.Companion.kiloWatt
import com.alliander.open.measure.Power.Companion.megaWatt
import com.alliander.open.measure.Power.Companion.watt
import com.alliander.open.measure.Time.Companion.hours
import com.alliander.open.measure.Time.Companion.minutes
import com.alliander.open.measure.Time.Companion.seconds
import com.alliander.open.measure.Voltage.Companion.kiloVolt
import com.alliander.open.measure.Voltage.Companion.volt
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.should
import io.kotest.matchers.shouldBe
import io.kotest.matchers.types.beInstanceOf

class SpecificUnitsTest : StringSpec({
"The units have the correct measure" {
joule should beInstanceOf<Energy>()
kiloJoule should beInstanceOf<Energy>()
megaJoule should beInstanceOf<Energy>()
kiloWattHour should beInstanceOf<Energy>()
megaWattHour should beInstanceOf<Energy>()

watt should beInstanceOf<Power>()
kiloWatt should beInstanceOf<Power>()
megaWatt should beInstanceOf<Power>()

seconds should beInstanceOf<Time>()
minutes should beInstanceOf<Time>()
hours should beInstanceOf<Time>()

volt should beInstanceOf<Voltage>()
kiloVolt should beInstanceOf<Voltage>()

ampere should beInstanceOf<Current>()
kiloAmpere should beInstanceOf<Current>()
}

"power times time is energy" {
val power = 10 * kiloWatt
val duration = 15 * minutes

val energy = power * duration

energy `as` megaJoule shouldBe 9 * megaJoule
}

"current times voltage is power" {
val current = 10 * ampere
val voltage = 15 * volt

val power = current * voltage

power `as` watt shouldBe 150 * watt
}

"voltage times current is power" {
val current = 10 * ampere
val voltage = 15 * volt

val power = voltage * current

power `as` watt shouldBe 150 * watt
}

"power divided by voltage is current" {
val power = 150 * watt
val voltage = 15 * volt

val current = power / voltage

current `as` ampere shouldBe 10 * ampere
}

"power divided by current is voltage" {
val power = 150 * watt
val current = 10 * ampere

val voltage = power / current

voltage `as` volt shouldBe 15 * volt
}
})