diff --git a/shared/src/main/scala/squants/motion/VolumeAcceleration.scala b/shared/src/main/scala/squants/motion/VolumeAcceleration.scala new file mode 100644 index 00000000..089eff4d --- /dev/null +++ b/shared/src/main/scala/squants/motion/VolumeAcceleration.scala @@ -0,0 +1,102 @@ +/* *\ +** Squants ** +** ** +** Scala Quantities and Units of Measure Library and DSL ** +** (c) 2017, Transcriptic, Inc. ** +** ** +\* */ + +package squants.motion + +import squants.space._ +import squants.{ AbstractQuantityNumeric, Dimension, PrimaryUnit, Quantity, SiUnit, UnitConverter, UnitOfMeasure } +import squants.time.{ SecondTimeDerivative, Seconds, TimeDerivative, TimeSquared } + +/** + * Represents the time rate of change of VolumeFlow i.e. volume per time per time. + * + * @author Phil Sung + * @since 1.4 + * + * @param value Double + */ +final class VolumeAcceleration private (val value: Double, val unit: VolumeAccelerationUnit) + extends Quantity[VolumeAcceleration] + with TimeDerivative[VolumeFlow] + with SecondTimeDerivative[Volume] { + + def dimension = VolumeAcceleration + + protected[squants] def timeIntegrated = CubicMetersPerSecond(toCubicMetersPerSecondSquared) + protected[squants] def time = Seconds(1) + + def *(that: TimeSquared): Volume = this * that.time1 * that.time2 + + def toCubicMetersPerSecondSquared = to(CubicMetersPerSecondSquared) + def toLitresPerSecondSquared = to(LitresPerSecondSquared) + def toMillilitresPerSecondSquared = to(MillilitresPerSecondSquared) + def toMicrolitresPerSecondSquared = to(MicrolitresPerSecondSquared) +} + +object VolumeAcceleration extends Dimension[VolumeAcceleration] { + private[motion] def apply[A](n: A, unit: VolumeAccelerationUnit)(implicit num: Numeric[A]) = { + new VolumeAcceleration(num.toDouble(n), unit) + } + def apply = parse _ + def name = "VolumeAcceleration" + def primaryUnit = CubicMetersPerSecondSquared + def siUnit = CubicMetersPerSecondSquared + def units = Set( + CubicMetersPerSecondSquared, + LitresPerSecondSquared, + MillilitresPerSecondSquared, + MicrolitresPerSecondSquared + ) +} + +/** + * Base trait for units of [[squants.motion.VolumeAcceleration]] + * + * @author Phil Sung + * @since 1.4 + */ +trait VolumeAccelerationUnit extends UnitOfMeasure[VolumeAcceleration] with UnitConverter { + def apply[A](n: A)(implicit num: Numeric[A]) = VolumeAcceleration(n, this) +} + +object CubicMetersPerSecondSquared extends VolumeAccelerationUnit with PrimaryUnit with SiUnit { + val symbol = "m³/s²" +} + +object LitresPerSecondSquared extends VolumeAccelerationUnit { + val symbol = "L/s²" + val conversionFactor = Litres.conversionFactor / CubicMeters.conversionFactor +} + +object MillilitresPerSecondSquared extends VolumeAccelerationUnit { + val symbol = "mL/s²" + val conversionFactor = Millilitres.conversionFactor / CubicMeters.conversionFactor +} + +object MicrolitresPerSecondSquared extends VolumeAccelerationUnit { + val symbol = "µL/s²" + val conversionFactor = Microlitres.conversionFactor / CubicMeters.conversionFactor +} + +object VolumeAccelerationConversions { + lazy val cubicMetersPerSecondSquared = CubicMetersPerSecondSquared(1) + lazy val litresPerSecondSquared = LitresPerSecondSquared(1) + lazy val millilitresPerSecondSquared = MillilitresPerSecondSquared(1) + lazy val microlitresPerSecondSquared = MicrolitresPerSecondSquared(1) + + implicit class VolumeFlowConversions[A](n: A)(implicit num: Numeric[A]) { + def cubicMetersPerSecondSquared = CubicMetersPerSecondSquared(n) + def litresPerSecondSquared = LitresPerSecondSquared(n) + def millilitresPerSecondSquared = MillilitresPerSecondSquared(n) + def microlitresPerSecondSquared = MicrolitresPerSecondSquared(n) + } + + implicit object VolumeAccelerationNumeric extends AbstractQuantityNumeric[VolumeAcceleration]( + VolumeAcceleration.primaryUnit + ) +} diff --git a/shared/src/main/scala/squants/motion/VolumeFlow.scala b/shared/src/main/scala/squants/motion/VolumeFlow.scala index 2a3e7206..fd36d34b 100644 --- a/shared/src/main/scala/squants/motion/VolumeFlow.scala +++ b/shared/src/main/scala/squants/motion/VolumeFlow.scala @@ -20,10 +20,12 @@ import squants.time._ */ final class VolumeFlow private (val value: Double, val unit: VolumeFlowRateUnit) extends Quantity[VolumeFlow] + with TimeIntegral[VolumeAcceleration] with TimeDerivative[Volume] { def dimension = VolumeFlow + protected[squants] def timeDerived = CubicMetersPerSecondSquared(toCubicMetersPerSecond) protected[squants] def timeIntegrated = CubicMeters(toCubicMetersPerSecond) protected[squants] def time = Seconds(1) diff --git a/shared/src/main/scala/squants/space/Volume.scala b/shared/src/main/scala/squants/space/Volume.scala index bafb5122..8c175e06 100644 --- a/shared/src/main/scala/squants/space/Volume.scala +++ b/shared/src/main/scala/squants/space/Volume.scala @@ -11,8 +11,8 @@ package squants.space import squants._ import squants.energy.{ EnergyDensity, Joules } import squants.mass.{ ChemicalAmount, Kilograms } -import squants.motion.{ CubicMetersPerSecond, VolumeFlow } -import squants.time.TimeIntegral +import squants.motion.{ CubicMetersPerSecond, VolumeAcceleration, VolumeFlow } +import squants.time.{ SecondTimeIntegral, TimeIntegral, TimeSquared } /** * Represents a quantity of Volume (three-dimensional space) @@ -24,7 +24,8 @@ import squants.time.TimeIntegral */ final class Volume private (val value: Double, val unit: VolumeUnit) extends Quantity[Volume] - with TimeIntegral[VolumeFlow] { + with TimeIntegral[VolumeFlow] + with SecondTimeIntegral[VolumeAcceleration] { def dimension = Volume @@ -50,6 +51,9 @@ final class Volume private (val value: Double, val unit: VolumeUnit) case _ ⇒ SquareMeters(this.toCubicMeters / that.toMeters) } + def /(that: TimeSquared): VolumeAcceleration = this / that.time1 / that.time2 + def /(that: VolumeAcceleration): TimeSquared = (this / that.timeIntegrated) * time + def /(that: Mass) = ??? // returns SpecificVolume (inverse of Density) def /(that: ChemicalAmount) = ??? // return MolarVolume diff --git a/shared/src/test/scala/squants/motion/VolumeAccelerationSpec.scala b/shared/src/test/scala/squants/motion/VolumeAccelerationSpec.scala new file mode 100644 index 00000000..85b9aa25 --- /dev/null +++ b/shared/src/test/scala/squants/motion/VolumeAccelerationSpec.scala @@ -0,0 +1,85 @@ +/* *\ +** Squants ** +** ** +** Scala Quantities and Units of Measure Library and DSL ** +** (c) 2017, Transcriptic, Inc. ** +** ** +\* */ + +package squants.motion + +import org.scalatest.{ FlatSpec, Matchers } +import squants.space.CubicMeters +import squants.time.Seconds +import squants.{ CustomMatchers, QuantityParseException } + +class VolumeAccelerationSpec extends FlatSpec with Matchers with CustomMatchers { + behavior of "VolumeAcceleration and its Units of Measure" + + it should "create values using UOM factories" in { + CubicMetersPerSecondSquared(1).toCubicMetersPerSecondSquared should be(1) + LitresPerSecondSquared(1).toLitresPerSecondSquared should be(1) + MillilitresPerSecondSquared(1).toMillilitresPerSecondSquared should be(1) + MicrolitresPerSecondSquared(1).toMicrolitresPerSecondSquared should be(1) + } + + it should "create values from properly formatted Strings" in { + VolumeAcceleration("12.34 m³/s²").get should be(CubicMetersPerSecondSquared(12.34)) + VolumeAcceleration("12.34 L/s²").get should be(LitresPerSecondSquared(12.34)) + VolumeAcceleration("12.34 mL/s²").get should be(MillilitresPerSecondSquared(12.34)) + VolumeAcceleration("12.34 µL/s²").get should be(MicrolitresPerSecondSquared(12.34)) + VolumeAcceleration("12.34 zz").failed.get should be(QuantityParseException("Unable to parse VolumeAcceleration", "12.34 zz")) + VolumeAcceleration("zz m³/s²").failed.get should be(QuantityParseException("Unable to parse VolumeAcceleration", "zz m³/s²")) + } + + it should "properly convert to all supported units of Measure" in { + val x = CubicMetersPerSecondSquared(12.34) + x.toCubicMetersPerSecondSquared should be(12.34) + x.toLitresPerSecondSquared should be(12340.0 +- 0.00000001) + x.toMillilitresPerSecondSquared should be(12340000.0 +- 0.00000001) + x.toMicrolitresPerSecondSquared should be(12340000000.0 +- 0.00000001) + } + + it should "return properly formatted strings for all supported Units of Measure" in { + CubicMetersPerSecondSquared(1).toString(CubicMetersPerSecondSquared) should be("1.0 m³/s²") + LitresPerSecondSquared(1).toString(LitresPerSecondSquared) should be("1.0 L/s²") + MillilitresPerSecondSquared(1).toString(MillilitresPerSecondSquared) should be("1.0 mL/s²") + MicrolitresPerSecondSquared(1).toString(MicrolitresPerSecondSquared) should be("1.0 µL/s²") + } + + it should "return VolumeFlow when multiplied by Time" in { + CubicMetersPerSecondSquared(2) * Seconds(3) should be(CubicMetersPerSecond(6)) + } + + it should "return Volume when multiplied by TimeSquared" in { + CubicMetersPerSecondSquared(2) * Seconds(2).squared should be(CubicMeters(8)) + } + + behavior of "VolumeAccelerationConversions" + + it should "provide aliases for single unit values" in { + import VolumeAccelerationConversions._ + + cubicMetersPerSecondSquared should be(CubicMetersPerSecondSquared(1)) + litresPerSecondSquared should be(LitresPerSecondSquared(1)) + millilitresPerSecondSquared should be(MillilitresPerSecondSquared(1)) + microlitresPerSecondSquared should be(MicrolitresPerSecondSquared(1)) + } + + it should "provide implicit conversion from Double" in { + import VolumeAccelerationConversions._ + + val d = 12.34d + d.cubicMetersPerSecondSquared should be(CubicMetersPerSecondSquared(d)) + d.litresPerSecondSquared should be(LitresPerSecondSquared(d)) + d.millilitresPerSecondSquared should be(MillilitresPerSecondSquared(d)) + d.microlitresPerSecondSquared should be(MicrolitresPerSecondSquared(d)) + } + + it should "provide Numeric support" in { + import VolumeAccelerationConversions.VolumeAccelerationNumeric + implicit val tolerance = CubicMetersPerSecondSquared(0.00000001) + val vaList = List(CubicMetersPerSecondSquared(1), LitresPerSecondSquared(2)) + vaList.sum should beApproximately(LitresPerSecondSquared(1002)) + } +} diff --git a/shared/src/test/scala/squants/motion/VolumeFlowSpec.scala b/shared/src/test/scala/squants/motion/VolumeFlowSpec.scala index 33ca4c23..1b9f5790 100644 --- a/shared/src/test/scala/squants/motion/VolumeFlowSpec.scala +++ b/shared/src/test/scala/squants/motion/VolumeFlowSpec.scala @@ -66,6 +66,14 @@ class VolumeFlowSpec extends AnyFlatSpec with Matchers with CustomMatchers { CubicMetersPerSecond(1) * Seconds(1) should be(CubicMeters(1)) } + it should "return VolumeAcceleration when divided by Time" in { + CubicMetersPerSecond(8) / Seconds(2) should be(CubicMetersPerSecondSquared(4)) + } + + it should "return Time when divided by VolumeAcceleration" in { + CubicMetersPerSecond(8) / CubicMetersPerSecondSquared(2) should be(Seconds(4)) + } + behavior of "VolumeFlowConversions" it should "provide aliases for single unit values" in { diff --git a/shared/src/test/scala/squants/space/VolumeSpec.scala b/shared/src/test/scala/squants/space/VolumeSpec.scala index 3cb9ca71..99b46d85 100644 --- a/shared/src/test/scala/squants/space/VolumeSpec.scala +++ b/shared/src/test/scala/squants/space/VolumeSpec.scala @@ -11,7 +11,7 @@ package squants.space import squants.QuantityParseException import squants.energy.{ Joules, JoulesPerCubicMeter } import squants.mass.{ Kilograms, KilogramsPerCubicMeter } -import squants.motion.CubicMetersPerSecond +import squants.motion.{ CubicMetersPerSecond, CubicMetersPerSecondSquared } import squants.time.Seconds import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.matchers.should.Matchers @@ -162,10 +162,18 @@ class VolumeSpec extends AnyFlatSpec with Matchers { CubicMeters(1) / Seconds(1) should be(CubicMetersPerSecond(1)) } + it should "return VolumeAcceleration when divided by TimeSquared" in { + CubicMeters(8) / Seconds(2).squared should be(CubicMetersPerSecondSquared(2)) + } + it should "return Time when divided by VolumeFlowRate" in { CubicMeters(1) / CubicMetersPerSecond(1) should be(Seconds(1)) } + it should "return TimeSquared when divided by VolumeAcceleration" in { + CubicMeters(8) / CubicMetersPerSecondSquared(4) should be(Seconds(2) * Seconds(1)) + } + it should "return Length when cube rooted" in { CubicMeters(27).cubeRoot should be(Meters(3)) }