Skip to content

Commit

Permalink
Add conversion to units relating to rotational dynamics units and add…
Browse files Browse the repository at this point in the history
… unit tests
  • Loading branch information
PhilipAxelrod committed May 13, 2017
1 parent 4468209 commit 4f277d3
Show file tree
Hide file tree
Showing 12 changed files with 365 additions and 41 deletions.
5 changes: 0 additions & 5 deletions shared/src/main/scala/squants/Quantity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,3 @@ abstract class Quantity[A <: Quantity[A]] extends Serializable with Ordered[A] {
def map(f: Double Double): A = unit(f(value))
}

abstract class StrictlyPositiveQuantity[A <: StrictlyPositiveQuantity[A]](value: Double) extends Quantity[A] {self: A
if(value < 0) {
throw new IllegalArgumentException("Cannot create negative StrictlyPositiveQuantity")
}
}
8 changes: 8 additions & 0 deletions shared/src/main/scala/squants/mass/Mass.scala
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ final class Mass private (val value: Double, val unit: MassUnit)
def /(that: AreaDensity): Area = SquareMeters(toKilograms / that.toKilogramsPerSquareMeter)
def /(that: Area): AreaDensity = KilogramsPerSquareMeter(toKilograms / that.toSquareMeters)

/**
* Moment of inertia of a point mass with with this mass and the given
* radius from the center of rotation
* @param radius length to center of rotation
* @return moment of inertia of a point mass with given mass and radius
*/
def onRadius(radius: Length): MomentOfInertia = KilogramsMetersSquared(toKilograms * radius.squared.toSquareMeters)

def toMicrograms = to(Micrograms)
def toMilligrams = to(Milligrams)
def toGrams = to(Grams)
Expand Down
29 changes: 25 additions & 4 deletions shared/src/main/scala/squants/mass/MomentOfIntertia.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package squants.mass

import squants.motion.{AngularAcceleration, NewtonMeters, Torque}
import squants.space.{Feet, Meters}
import squants.{Dimension, PrimaryUnit, SiBaseUnit, StrictlyPositiveQuantity, UnitConverter, UnitOfMeasure}
import squants.{AbstractQuantityNumeric, Dimension, Length, PrimaryUnit, Quantity, SiBaseUnit, UnitConverter, UnitOfMeasure}

/**
*
Expand All @@ -12,18 +12,27 @@ import squants.{Dimension, PrimaryUnit, SiBaseUnit, StrictlyPositiveQuantity, Un
* @param value Double
*/
final class MomentOfInertia private (val value: Double, val unit: MomentOfInertiaUnit)
extends StrictlyPositiveQuantity[MomentOfInertia](value){
extends Quantity[MomentOfInertia]{

def dimension = MomentOfInertia

def toKilogramsMetersSquared = to(KilogramsMetersSquared)
def toPoundsSquareFeet = to(PoundsSquareFeet)

def *(angularAcceleration: AngularAcceleration): Torque = {
val kilogramsMetersSquared = toKilogramsMetersSquared
val radiansPerSecondSquared = angularAcceleration.toRadiansPerSecondSquared

NewtonMeters(kilogramsMetersSquared * radiansPerSecondSquared)
NewtonMeters(toKilogramsMetersSquared * radiansPerSecondSquared)
}

/**
* For a point mass with the given MomentOfInertia rotating with a center of
* rotation at the given radius, return the mass of the point mass
* @param radius distance to axis of rotation
* @return mass of point mass with given radius and MomentOfInertia
*/
def atCenter(radius: Length): Mass = {
Kilograms(toKilogramsMetersSquared / radius.squared.toSquareMeters)
}
}

Expand All @@ -49,4 +58,16 @@ object KilogramsMetersSquared extends MomentOfInertiaUnit with PrimaryUnit with
object PoundsSquareFeet extends MomentOfInertiaUnit {
val symbol = Pounds.symbol + "" + Feet.symbol + "²"
val conversionFactor = Pounds.conversionFactor * math.pow(Feet.conversionFactor, 2D)
}

object MomentOfInertiaConversions {
lazy val kilogramMetersSquared = KilogramsMetersSquared(1)
lazy val poundSquareFeet = PoundsSquareFeet(1)

implicit class MomentOfInertiaConversions[A](val n: A) extends AnyVal {
def kilogramMetersSquared(implicit num: Numeric[A]) = KilogramsMetersSquared(n)
def poundSquareFeet(implicit num: Numeric[A]) = PoundsSquareFeet(n)
}

implicit object MomentOfInertiaNumeric extends AbstractQuantityNumeric[MomentOfInertia](MomentOfInertia.primaryUnit)
}
39 changes: 32 additions & 7 deletions shared/src/main/scala/squants/motion/AngularAcceleration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package squants.motion
import squants.mass.MomentOfInertia
import squants.space._
import squants.time.{Seconds, Time, TimeDerivative}
import squants.{Dimension, Length, PrimaryUnit, Quantity, SiUnit, UnitConverter, UnitOfMeasure}
import squants.{AbstractQuantityNumeric, Dimension, Length, PrimaryUnit, Quantity, SiUnit, UnitConverter, UnitOfMeasure}

/**
*
Expand All @@ -18,10 +18,20 @@ final class AngularAcceleration private (val value: Double, val unit: AngularAcc
def dimension = AngularAcceleration

def toRadiansPerSecondSquared = to(RadiansPerSecondSquared)
def toDegreesPerSecondSquared = to(DegreesPerSecondSquared)
def toGradsPerSecondSquared = to(GradiansPerSecondSquared)
def toTurnsPerSecondSquared = to(TurnsPerSecondSquared)
def toArcminutesPerSecondSquared = to(ArcminutesPerSecondSquared)
def toArcsecondsPerSecondSquared = to(ArcsecondsPerSecondSquared)

/**
* linear acceleration of an object rotating with this angular acceleration
* and the given radius from the center of rotation
* @param radius the distance from the center of rotation
* @return linear acceleration with given angular acceleration and radius
*/
def onRadius(radius: Length): Acceleration = toRadiansPerSecondSquared * radius / Seconds(1).squared

def onRadius(that: Length): Acceleration = {
toRadiansPerSecondSquared * that / Seconds(1).squared
}

def *(that: MomentOfInertia): Torque = {
NewtonMeters(toRadiansPerSecondSquared * that.toKilogramsMetersSquared)
Expand All @@ -32,7 +42,6 @@ final class AngularAcceleration private (val value: Double, val unit: AngularAcc
override protected[squants] def time: Time = Seconds(1)
}


object AngularAcceleration extends Dimension[AngularAcceleration] {
private[motion] def apply[A](n: A, unit: AngularAccelerationUnit)(implicit num: Numeric[A]) = new AngularAcceleration(num.toDouble(n), unit)
def apply = parse _
Expand All @@ -58,11 +67,11 @@ trait AngularAccelerationUnit extends UnitOfMeasure[AngularAcceleration] with
}

object RadiansPerSecondSquared extends AngularAccelerationUnit with PrimaryUnit with SiUnit{
override val symbol: String = "rad/s²"
val symbol = Radians.symbol + "/s²"
}

object DegreesPerSecondSquared extends AngularAccelerationUnit {
val symbol = "°/s²"
val symbol = Degrees.symbol + "/s²"
val conversionFactor = Degrees.conversionFactor
}

Expand All @@ -84,4 +93,20 @@ object ArcminutesPerSecondSquared extends AngularAccelerationUnit {
object ArcsecondsPerSecondSquared extends AngularAccelerationUnit{
val symbol = Arcseconds.symbol + "/s²"
val conversionFactor = Arcseconds.conversionFactor
}

object AngularAccelerationConversions {
lazy val radianPerSecondSquared = RadiansPerSecondSquared(1)
lazy val degreePerSecondSquared = DegreesPerSecondSquared(1)
lazy val gradPerSecondSquared = GradiansPerSecondSquared(1)
lazy val turnPerSecondSquared = TurnsPerSecondSquared(1)

implicit class AngularAccelerationConversions[A](val n: A) extends AnyVal {
def radiansPerSecondSquared(implicit num: Numeric[A]) = RadiansPerSecondSquared(n)
def degreesPerSecondSquared(implicit num: Numeric[A]) = DegreesPerSecondSquared(n)
def gradsPerSecondSquared(implicit num: Numeric[A]) = GradiansPerSecondSquared(n)
def turnsPerSecondSquared(implicit num: Numeric[A]) = TurnsPerSecondSquared(n)
}

implicit object AngularAccelerationNumeric extends AbstractQuantityNumeric[AngularAcceleration](AngularAcceleration.primaryUnit)
}
29 changes: 22 additions & 7 deletions shared/src/main/scala/squants/motion/AngularVelocity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,18 @@ final class AngularVelocity private (val value: Double, val unit: AngularVelocit

def toRadiansPerSecond = to(RadiansPerSecond)
def toDegreesPerSecond = to(DegreesPerSecond)
def toGradsPerSecond = to(GradsPerSecond)
@deprecated(message = "Potentially confusing naming. Use toGradiansPerSecond instead.", since = "Squants 1.2")
def toGradsPerSecond = to(GradiansPerSecond)
def toGradiansPerSecond = to(GradiansPerSecond)
def toTurnsPerSecond = to(TurnsPerSecond)

def onRadius(that: Length): Velocity = {
toRadiansPerSecond * that / Seconds(1)
}
/**
* linear velocity of an object rotating with this angular velocity
* and the given radius from the center of rotation
* @param radius the distance from the center of rotation
* @return linear velocity with given angular velocity and radius
*/
def onRadius(radius: Length): Velocity = toRadiansPerSecond * radius / Seconds(1)

protected[squants] def timeIntegrated: Angle = Radians(toRadiansPerSecond)

Expand All @@ -45,7 +51,7 @@ object AngularVelocity extends Dimension[AngularVelocity] {
def name = "AngularVelocity"
def primaryUnit = RadiansPerSecond
def siUnit = RadiansPerSecond
def units = Set(RadiansPerSecond, DegreesPerSecond, GradsPerSecond, TurnsPerSecond)
def units = Set(RadiansPerSecond, DegreesPerSecond, GradiansPerSecond, TurnsPerSecond)
}

trait AngularVelocityUnit extends UnitOfMeasure[AngularVelocity] with UnitConverter {
Expand All @@ -61,6 +67,12 @@ object DegreesPerSecond extends AngularVelocityUnit {
val conversionFactor = Degrees.conversionFactor * Radians.conversionFactor
}

object GradiansPerSecond extends AngularVelocityUnit {
val symbol = "grad/s"
val conversionFactor = Gradians.conversionFactor * Radians.conversionFactor
}

@deprecated(message = "Potentially confusing naming. Use GradiansPerSecond instead.", since = "Squants 1.2")
object GradsPerSecond extends AngularVelocityUnit {
val symbol = "grad/s"
val conversionFactor = Gradians.conversionFactor * Radians.conversionFactor
Expand All @@ -74,13 +86,16 @@ object TurnsPerSecond extends AngularVelocityUnit {
object AngularVelocityConversions {
lazy val radianPerSecond = RadiansPerSecond(1)
lazy val degreePerSecond = DegreesPerSecond(1)
lazy val gradPerSecond = GradsPerSecond(1)
lazy val gradPerSecond = GradiansPerSecond(1)
lazy val gradiansPerSecond = GradiansPerSecond(1)
lazy val turnPerSecond = TurnsPerSecond(1)

implicit class AngularVelocityConversions[A](n: A)(implicit num: Numeric[A]) {
def radiansPerSecond = RadiansPerSecond(n)
def degreesPerSecond = DegreesPerSecond(n)
def gradsPerSecond = GradsPerSecond(n)
@deprecated(message = "Potentially confusing naming. Use gradiansPerSecond instead.", since = "Squants 1.2")
def gradsPerSecond = GradiansPerSecond(n)
def gradiansPerSecond = GradiansPerSecond(n)
def turnsPerSecond = TurnsPerSecond(n)
}

Expand Down
21 changes: 17 additions & 4 deletions shared/src/main/scala/squants/motion/Torque.scala
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package squants.motion

import squants.mass.{MomentOfInertia, Pounds}
import squants.mass.{Kilograms, MomentOfInertia, Pounds}
import squants.space.{Feet, Meters}
import squants.{Dimension, Energy, PrimaryUnit, Quantity, SiBaseUnit, UnitConverter, UnitOfMeasure}
import squants.{AbstractQuantityNumeric, Dimension, Energy, PrimaryUnit, Quantity, SiBaseUnit, UnitConverter, UnitOfMeasure}

/**
*
Expand All @@ -17,6 +17,7 @@ final class Torque private (val value: Double, val unit: TorqueUnit)
def dimension = Torque

def toNewtonMeters = to(NewtonMeters)
def toPoundFeet = to(PoundFeet)

def / (that: MomentOfInertia): AngularAcceleration = {
RadiansPerSecondSquared(toNewtonMeters / that.toKilogramsMetersSquared)
Expand All @@ -26,7 +27,7 @@ final class Torque private (val value: Double, val unit: TorqueUnit)
object Torque extends Dimension[Torque] {
private[motion] def apply[A](n: A, unit: TorqueUnit)(implicit num: Numeric[A]) = new Torque(num.toDouble(n), unit)
def apply = parse _
def name = "MomentOfInertia"
def name = "Torque"
def primaryUnit = NewtonMeters
def siUnit = NewtonMeters
def units = Set(NewtonMeters, PoundFeet)
Expand All @@ -44,5 +45,17 @@ object NewtonMeters extends TorqueUnit with PrimaryUnit with SiBaseUnit {

object PoundFeet extends TorqueUnit {
val symbol = Pounds.symbol + "" + Feet.symbol
val conversionFactor = Pounds.conversionFactor * Feet.conversionFactor
val conversionFactor = PoundForce.conversionFactor * Feet.conversionFactor
}

object TorqueConversions {
lazy val newtonMeters = NewtonMeters(1)
lazy val poundFeet = PoundFeet(1)

implicit class TorqueConversions[A](val n: A) extends AnyVal {
def newtonMeters(implicit num: Numeric[A]) = NewtonMeters(n)
def poundFeet(implicit num: Numeric[A]) = PoundFeet(n)
}

implicit object TorqueNumeric extends AbstractQuantityNumeric[Torque](Torque.primaryUnit)
}
12 changes: 9 additions & 3 deletions shared/src/main/scala/squants/space/Angle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ final class Angle private (val value: Double, val unit: AngleUnit)
def asin = math.asin(toRadians)
def acos = math.acos(toRadians)

def onRadius(that: Length): Length = {
toRadians * that
}
/**
* length of the arc traveled by a point on the rim of a circle with this
* angle traveled and the given (constant) radius from the center of
* rotation
* @param radius the distance from the center of rotation
* @return arc length with given arc measure and radius
*/
def onRadius(radius: Length): Length = toRadians * radius


protected def timeDerived: AngularVelocity = RadiansPerSecond(toRadians)

Expand Down
10 changes: 7 additions & 3 deletions shared/src/test/scala/squants/mass/MassSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@

package squants.mass

import org.scalatest.{ FlatSpec, Matchers }
import org.scalatest.{FlatSpec, Matchers}
import squants.motion._
import squants.space.{ CubicMeters, SquareMeters }
import squants.space.{CubicMeters, Meters, SquareMeters}
import squants.time.Seconds
import squants.{ MetricSystem, QuantityParseException }
import squants.{MetricSystem, QuantityParseException}

/**
* @author garyKeorkunian
Expand Down Expand Up @@ -147,6 +147,10 @@ class MassSpec extends FlatSpec with Matchers {
Kilograms(1) / KilogramsPerSquareMeter(1) should be(SquareMeters(1))
}

it should "return MomentOfInertia when onRadius of Length" in {
Kilograms(1) onRadius Meters(1) should be(KilogramsMetersSquared(1))
}

behavior of "MassConversions"

it should "provide aliases for single unit values" in {
Expand Down
77 changes: 77 additions & 0 deletions shared/src/test/scala/squants/mass/MomentOfInertiaSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package squants.mass

import org.scalatest.{FlatSpec, Matchers}
import squants.CustomMatchers
import squants.motion.{AngularAccelerationConversions, NewtonMeters, RadiansPerSecondSquared}
import squants.space.{Feet, Meters}

/**
*
* @author paxelord
* @since 1.2
**/
class MomentOfInertiaSpec extends FlatSpec with Matchers with CustomMatchers {

behavior of "MomentOfInertia and its Units of Measure"

val unitValueSi = KilogramsMetersSquared(1)

it should "create values using UOM factories" in {
KilogramsMetersSquared(10.22).toKilogramsMetersSquared should be(10.22)
PoundsSquareFeet(10.22).toPoundsSquareFeet should be(10.22)
}

it should "create values from properly formatted Strings" in {
MomentOfInertia("10.22 kg‧m²").get should be(KilogramsMetersSquared(10.22))
MomentOfInertia("10.22 lb‧ft²").get should be(PoundsSquareFeet(10.22))
}

it should "properly convert to all supported Units of Measure" in {
implicit val tolerance = 1e-10
val a = KilogramsMetersSquared(1)

val kilogramsToPounds = 1D / Pounds.conversionFactor
val metersToFeet = 1D / Feet.conversionFactor
a.toPoundsSquareFeet should beApproximately(kilogramsToPounds * metersToFeet * metersToFeet)

val b = PoundsSquareFeet(1)
val poundsToKilograms = Pounds.conversionFactor
val feetToMeters = Feet.conversionFactor
b.toKilogramsMetersSquared should beApproximately(poundsToKilograms * feetToMeters * feetToMeters)
}

it should "return properly formatted strings for all supported Units of Measure" in {
PoundsSquareFeet(1).toString(PoundsSquareFeet) should be("1.0 lb‧ft²")
unitValueSi.toString(KilogramsMetersSquared) should be("1.0 kg‧m²")
}

it should "return Torque when multiplied by AngularAcceleration" in {
unitValueSi * RadiansPerSecondSquared(1) should be(NewtonMeters(1))
}

it should "return mass when atCenter is called" in {
unitValueSi atCenter Meters(1) should be(Kilograms(1))
}

it should "provide aliases for single unit values" in {
import MomentOfInertiaConversions._

poundSquareFeet should be(PoundsSquareFeet(1))
kilogramMetersSquared should be(KilogramsMetersSquared(1))
}

it should "provide implicit conversion from Double" in {
import MomentOfInertiaConversions._

val d = 10.22
d.kilogramMetersSquared should be(KilogramsMetersSquared(d))
d.poundSquareFeet should be(PoundsSquareFeet(d))
}

it should "provide Numeric support" in {
import MomentOfInertiaConversions._

val momentOfInertiaList = List(KilogramsMetersSquared(100), KilogramsMetersSquared(10))
momentOfInertiaList.sum should be(KilogramsMetersSquared(110))
}
}
Loading

0 comments on commit 4f277d3

Please sign in to comment.