Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/df/#490-squants' into df/#490-sq…
Browse files Browse the repository at this point in the history
…uants

# Conflicts:
#	src/main/scala/edu/ie3/simona/model/participant/WecModel.scala
  • Loading branch information
danielfeismann committed Jun 14, 2023
2 parents 5c0dbe2 + b17a517 commit d41c9e3
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,34 @@ package edu.ie3.simona.agent.participant.wec

import akka.actor.{ActorRef, FSM}
import edu.ie3.datamodel.models.input.system.WecInput
import edu.ie3.datamodel.models.result.system.{SystemParticipantResult, WecResult}
import edu.ie3.datamodel.models.result.system.{
SystemParticipantResult,
WecResult
}
import edu.ie3.simona.agent.ValueStore
import edu.ie3.simona.agent.participant.ParticipantAgent._
import edu.ie3.simona.agent.participant.ParticipantAgentFundamentals
import edu.ie3.simona.agent.participant.data.Data.PrimaryData.{ApparentPower, ZERO_POWER}
import edu.ie3.simona.agent.participant.data.Data.PrimaryData.{
ApparentPower,
ZERO_POWER
}
import edu.ie3.simona.agent.participant.data.Data.SecondaryData
import edu.ie3.simona.agent.participant.data.secondary.SecondaryDataService
import edu.ie3.simona.agent.participant.statedata.BaseStateData._
import edu.ie3.simona.agent.participant.statedata.{DataCollectionStateData, ParticipantStateData}
import edu.ie3.simona.agent.participant.statedata.{
DataCollectionStateData,
ParticipantStateData
}
import edu.ie3.simona.agent.participant.wec.WecAgent.neededServices
import edu.ie3.simona.agent.state.AgentState
import edu.ie3.simona.agent.state.AgentState.Idle
import edu.ie3.simona.config.SimonaConfig.WecRuntimeConfig
import edu.ie3.simona.event.notifier.ParticipantNotifierConfig
import edu.ie3.simona.exceptions.agent.{AgentInitializationException, InconsistentStateException, InvalidRequestException}
import edu.ie3.simona.exceptions.agent.{
AgentInitializationException,
InconsistentStateException,
InvalidRequestException
}
import edu.ie3.simona.model.participant.WecModel
import edu.ie3.simona.model.participant.WecModel.WecRelevantData
import edu.ie3.simona.ontology.messages.services.WeatherMessage.WeatherData
Expand Down Expand Up @@ -215,7 +228,7 @@ protected trait WecAgentFundamentals
WecRelevantData(
weatherData.windVel,
weatherData.temp,
Pascals(0d) // weather data does not support air pressure
None // weather data does not support air pressure
)

val power = wecModel.calculatePower(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ package edu.ie3.simona.model.participant
import edu.ie3.simona.agent.participant.data.Data.PrimaryData.ApparentPower
import edu.ie3.simona.model.SystemComponent
import edu.ie3.simona.model.participant.control.QControl
import edu.ie3.util.quantities.PowerSystemUnits._
import edu.ie3.util.scala.OperationInterval
import edu.ie3.util.scala.quantities.{DefaultQuantities, Megavars, ReactivePower}
import edu.ie3.util.scala.quantities.{
DefaultQuantities,
Megavars,
ReactivePower
}
import squants.energy.Kilowatts

import java.util.UUID
Expand Down
101 changes: 54 additions & 47 deletions src/main/scala/edu/ie3/simona/model/participant/WecModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -16,26 +16,18 @@ import edu.ie3.simona.model.participant.WecModel.{
import edu.ie3.simona.model.participant.control.QControl
import edu.ie3.simona.model.system.Characteristic
import edu.ie3.simona.model.system.Characteristic.XYPair
import edu.ie3.util.quantities.EmptyQuantity
import edu.ie3.util.quantities.PowerSystemUnits.{
KILOGRAM_PER_CUBIC_METRE,
KILOWATT,
MEGAWATT
}
import edu.ie3.util.quantities.QuantityUtils.RichQuantityDouble

import edu.ie3.util.quantities.PowerSystemUnits._
import edu.ie3.util.scala.OperationInterval
import squants.Velocity
import squants.energy.{Kilowatts, Megawatts}
import squants.energy.{Kilowatts, Watts}
import squants.mass.{Kilograms, KilogramsPerCubicMeter}
import squants.motion.MetersPerSecond
import squants.space.SquareMeters
import squants.thermal.JoulesPerKelvin
import tech.units.indriya.ComparableQuantity

import squants.{Each, Velocity}
import tech.units.indriya.unit.Units._

import java.time.ZonedDateTime
import java.util.UUID
import javax.measure.quantity._
import scala.collection.SortedSet

/** A wind energy converter model used for calculating output power of a wind
Expand Down Expand Up @@ -66,23 +58,26 @@ final case class WecModel(
operationInterval: OperationInterval,
scalingFactor: Double,
qControl: QControl,
sRated: ComparableQuantity[Power],
sRated: squants.Power,
cosPhiRated: Double,
rotorArea: ComparableQuantity[Area],
rotorArea: squants.Area,
betzCurve: WecCharacteristic
) extends SystemParticipant[WecRelevantData](
uuid,
id,
operationInterval,
scalingFactor,
qControl,
Kilowatts(sRated.to(KILOWATT).getValue.doubleValue),
sRated,
cosPhiRated
) {

/** Universal gas constant
/** Universal gas constant, actually in J/(K * mol)
*/
private val R = JoulesPerKelvin(8.31446261815324d)

/** Molar mass of air, actually in kg/mol
*/
private val AIR_MOLAR_MASS = Kilograms(0.0289647d)

/** Calculate the active power output of the [[WecModel]]. First determine the
Expand All @@ -96,22 +91,20 @@ final case class WecModel(
override protected def calculateActivePower(
wecData: WecRelevantData
): squants.Power = {
val activePower = determinePower(wecData).to(MEGAWATT)
val pMax = sMax.toKilowatts.asKiloWatt.multiply(cosPhiRated).to(MEGAWATT)
val activePower = determinePower(wecData)
val pMax = sMax * cosPhiRated

Megawatts(
(if (activePower.isGreaterThan(pMax)) {
logger.warn(
"The fed in active power is higher than the estimated maximum active power of this plant ({} > {}). " +
"Did you provide wrong weather input data?",
activePower,
pMax
)
(if (activePower > pMax) {
logger.warn(
"The fed in active power is higher than the estimated maximum active power of this plant ({} > {}). " +
"Did you provide wrong weather input data?",
activePower,
pMax
} else {
activePower
}).multiply(-1).to(MEGAWATT).getValue.doubleValue
)
)
pMax
} else {
activePower
}) * (-1)
}

/** Determine the turbine output power with the air density ρ, the wind
Expand All @@ -127,19 +120,27 @@ final case class WecModel(
*/
private def determinePower(
wecData: WecRelevantData
): ComparableQuantity[Power] = {
): squants.Power = {
val betzCoefficient = determineBetzCoefficient(wecData.windVelocity)

/** air density in kg/m³
*/
val airDensity =
calculateAirDensity(wecData.temperature, wecData.airPressure)
val v = wecData.windVelocity
val cubedVelocity = v.multiply(v).multiply(v)
calculateAirDensity(
wecData.temperature,
wecData.airPressure
).toKilogramsPerCubicMeter

cubedVelocity
.multiply(0.5)
.multiply(betzCoefficient)
.multiply(airDensity.to(KILOGRAM_PER_CUBIC_METRE))
.multiply(rotorArea.to(SQUARE_METRE))
.asType(classOf[Power])
val v = wecData.windVelocity.toMetersPerSecond

/** cubed velocity in m³/s³
*/
val cubedVelocity = v * v * v

// Combined, we get (kg * m²)/s³, which is Watts
Watts(
cubedVelocity * 0.5 * betzCoefficient.toEach * airDensity * rotorArea.toSquareMeters
)
}

/** The coefficient is dependent on the wind velocity v. Therefore use v to
Expand Down Expand Up @@ -178,7 +179,11 @@ final case class WecModel(
case None =>
KilogramsPerCubicMeter(1.2041d)
case Some(pressure) =>
AIR_MOLAR_MASS * (pressure.toPascals)/(R * temperature.toKelvinScale)
// kg * mol^-1 * J * m^-3 * J^-1 * K * mol * K^-1
// = kg * m^-3
KilogramsPerCubicMeter(
AIR_MOLAR_MASS.toKilograms * pressure.toPascals / (R.toJoulesPerKelvin * temperature.toKelvinScale)
)
}
}
}
Expand Down Expand Up @@ -208,8 +213,8 @@ object WecModel {
.SortedSet[XYPair[Velocity, squants.Dimensionless]]() ++
input.getPoints.asScala.map(p =>
XYPair[Velocity, squants.Dimensionless](
p.getX.to(METRE_PER_SECOND),
p.getY
MetersPerSecond(p.getX.to(METRE_PER_SECOND).getValue.doubleValue),
Each(p.getY.to(PU).getValue.doubleValue)
)
)
)
Expand All @@ -228,7 +233,7 @@ object WecModel {
final case class WecRelevantData(
windVelocity: squants.Velocity,
temperature: squants.Temperature,
airPressure: squants.motion.Pressure
airPressure: Option[squants.motion.Pressure]
) extends CalcRelevantData

def apply(
Expand All @@ -249,9 +254,11 @@ object WecModel {
operationInterval,
scalingFactor,
QControl(inputModel.getqCharacteristics),
inputModel.getType.getsRated,
Kilowatts(inputModel.getType.getsRated.to(KILOWATT).getValue.doubleValue),
inputModel.getType.getCosPhiRated,
inputModel.getType.getRotorArea,
SquareMeters(
inputModel.getType.getRotorArea.to(SQUARE_METRE).getValue.doubleValue
),
WecCharacteristic(inputModel.getType.getCpCharacteristic)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ trait Characteristic[A <: Quantity[A], O <: Quantity[O]] {

object Characteristic {
final case class XYPair[A <: Quantity[A], O <: Quantity[O]](
x: A,
y: O
x: A,
y: O
) extends Ordered[XYPair[A, O]] {

/** The pairs are ordered by their x value first. If two pairs have the same
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,19 @@ import edu.ie3.datamodel.models.input.system.characteristic.WecCharacteristicInp
import edu.ie3.datamodel.models.input.system.type.WecTypeInput
import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils
import edu.ie3.util.TimeUtil
import edu.ie3.util.quantities.EmptyQuantity
import edu.ie3.util.scala.quantities.Sq
import scala.Option
import spock.lang.Shared
import spock.lang.Specification
import spock.lang.Unroll
import squants.energy.Watts$

import squants.Each$
import squants.motion.MetersPerSecond$
import squants.motion.Pascals$
import squants.thermal.Celsius$

import edu.ie3.util.scala.quantities.Sq
import static edu.ie3.datamodel.models.StandardUnits.*
import static edu.ie3.simona.model.participant.WecModel.WecRelevantData
import static edu.ie3.util.quantities.PowerSystemUnits.*
import static edu.ie3.util.quantities.QuantityUtil.equals
import static tech.units.indriya.quantity.Quantities.getQuantity

class WecModelTest extends Specification {
Expand Down Expand Up @@ -98,18 +99,20 @@ class WecModelTest extends Specification {
wecModel.uuid() == inputModel.getUuid()
wecModel.id() == inputModel.getId()
wecModel.scalingFactor() == 1
wecModel.sRated() == inputModel.getType().getsRated()
wecModel.sRated() == Kilowatts(inputModel.getType.getsRated.to(KILOWATT).getValue.doubleValue)
wecModel.cosPhiRated() == inputModel.getType().getCosPhiRated()
wecModel.rotorArea() == inputModel.getType().getRotorArea()
wecModel.rotorArea() == SquareMeters(inputModel.getType.getRotorArea.to(SQUARE_METRE).getValue.doubleValue)
wecModel.betzCurve() == new WecModel.WecCharacteristic$().apply(inputModel.getType().getCpCharacteristic())
}

@Unroll
def "Check active power output depending on velocity #velocity m/s"() {
given:
def wecModel = buildWecModel()
def wecData = new WecRelevantData(getQuantity(velocity, METRE_PER_SECOND),
getQuantity(20, CELSIUS), getQuantity(101325, PASCAL))
def wecData = new WecRelevantData(
Sq.create(velocity, MetersPerSecond$.MODULE$),
Sq.create(20, Celsius$.MODULE$),
Sq.create(101325, Pascals$.MODULE$))

when:
def result = wecModel.calculateActivePower(wecData)
Expand Down Expand Up @@ -157,11 +160,11 @@ class WecModelTest extends Specification {
def "Check determineBetzCoefficient method with wind velocity #velocity m/s:"() {
given:
def wecModel = buildWecModel()
def windVel = getQuantity(velocity, WIND_VELOCITY)
def windVel = Sq.create(velocity, MetersPerSecond$.MODULE$)

when:
def betzFactor = wecModel.determineBetzCoefficient(windVel)
def expected = getQuantity(betzResult, PU)
def expected = Sq.create(betzResult, Each$.MODULE$)

then:
betzFactor == expected
Expand All @@ -180,18 +183,17 @@ class WecModelTest extends Specification {
def "Check calculateAirDensity method with temperature #temperature degrees Celsius and air pressure #pressure Pascal:"() {
given:
def wecModel = buildWecModel()
def temperatureV = getQuantity(temperature, CELSIUS)
def pressureV = EmptyQuantity.of(PASCAL)
def temperatureV = Sq.create(temperature, Celsius$.MODULE$)
def pressureV = Option.empty()

when:
if (pressure > 0) {
pressureV = getQuantity(pressure, PASCAL)
pressureV = Some(Sq.create(pressure, Pascals$.MODULE$))
}
def airDensity = wecModel.calculateAirDensity(temperatureV, pressureV).to(KILOGRAM_PER_CUBIC_METRE)
def expected = getQuantity(densityResult, KILOGRAM_PER_CUBIC_METRE)
def airDensity = wecModel.calculateAirDensity(temperatureV, pressureV).toKilogramsPerCubicMeter()

then:
equals(airDensity, expected, TOLERANCE)
Math.abs(airDensity - densityResult) < TOLERANCE

where:
temperature | pressure || densityResult
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,16 @@ import squants.{Dimensionless, Each}
import scala.collection.SortedSet

trait CharacteristicTestData {
protected val xy1: XYPair[Dimensionless, Dimensionless] = XYPair(Each(1), Each(2))
protected val xy2: XYPair[Dimensionless, Dimensionless] = XYPair(Each(2), Each(4))
protected val xy3: XYPair[Dimensionless, Dimensionless] = XYPair(Each(3), Each(8))
protected val xy4: XYPair[Dimensionless, Dimensionless] = XYPair(Each(4), Each(16))
protected val xy5: XYPair[Dimensionless, Dimensionless] = XYPair(Each(5), Each(32))
protected val xy1: XYPair[Dimensionless, Dimensionless] =
XYPair(Each(1), Each(2))
protected val xy2: XYPair[Dimensionless, Dimensionless] =
XYPair(Each(2), Each(4))
protected val xy3: XYPair[Dimensionless, Dimensionless] =
XYPair(Each(3), Each(8))
protected val xy4: XYPair[Dimensionless, Dimensionless] =
XYPair(Each(4), Each(16))
protected val xy5: XYPair[Dimensionless, Dimensionless] =
XYPair(Each(5), Each(32))

object TestCharacteristic
extends Characteristic[Dimensionless, Dimensionless] {
Expand Down

0 comments on commit d41c9e3

Please sign in to comment.