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 Apr 30, 2017
1 parent bf67f58 commit f92dd88
Show file tree
Hide file tree
Showing 10 changed files with 338 additions and 23 deletions.
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)

/**
* @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,12 +2,12 @@ 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, SiBaseUnit, StrictlyPositiveQuantity, UnitConverter, UnitOfMeasure}

/**
*
* @author paxelord
* @since 1.2
* @since 0.6
*
* @param value Double
*/
Expand All @@ -20,10 +20,19 @@ final class MomentOfInertia private (val value: Double, val unit: MomentOfInerti
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 MassConversions[A](n: A)(implicit num: Numeric[A]) {
def kilogramMetersSquared = KilogramsMetersSquared(n)
def poundSquareFeet = PoundsSquareFeet(n)
}

implicit object MomentOfInertiaNumeric extends AbstractQuantityNumeric[MomentOfInertia](MomentOfInertia.primaryUnit)
}
41 changes: 34 additions & 7 deletions shared/src/main/scala/squants/motion/AngularAcceleration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ 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}

/**
*
* @author paxelord
* @since 1.2
* @since 0.6
*
* @param value Double
*/
Expand All @@ -18,9 +18,20 @@ final class AngularAcceleration private (val value: Double, val unit: AngularAcc
def dimension = AngularAcceleration

def toRadiansPerSecondSquared = to(RadiansPerSecondSquared)

def onRadius(that: Length): Acceleration = {
toRadiansPerSecondSquared * that / Seconds(1).squared
def toDegreesPerSecondSquared = to(DegreesPerSecondSquared)
def toGradsPerSecondSquared = to(GradsPerSecondSquared)
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 *(that: MomentOfInertia): Torque = {
Expand All @@ -42,7 +53,7 @@ object AngularAcceleration extends Dimension[AngularAcceleration] {
def units = Set(
RadiansPerSecondSquared,
DegreesPerSecondSquared,
GradiansPerSecondSquared,
GradsPerSecondSquared,
TurnsPerSecondSquared,
ArcminutesPerSecondSquared,
ArcsecondsPerSecondSquared)
Expand All @@ -66,7 +77,7 @@ object DegreesPerSecondSquared extends AngularAccelerationUnit {
val conversionFactor = Degrees.conversionFactor
}

object GradiansPerSecondSquared extends AngularAccelerationUnit {
object GradsPerSecondSquared extends AngularAccelerationUnit {
val symbol = Gradians.symbol + "/s²"
val conversionFactor = Gradians.conversionFactor
}
Expand All @@ -84,4 +95,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 = GradsPerSecondSquared(1)
lazy val turnPerSecondSquared = TurnsPerSecondSquared(1)

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

implicit object AngularAccelerationNumeric extends AbstractQuantityNumeric[AngularAcceleration](AngularAcceleration.primaryUnit)
}
10 changes: 8 additions & 2 deletions shared/src/main/scala/squants/motion/AngularVelocity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,14 @@ final class AngularVelocity private (val value: Double, val unit: AngularVelocit
def toGradsPerSecond = to(GradsPerSecond)
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 Down
23 changes: 18 additions & 5 deletions shared/src/main/scala/squants/motion/Torque.scala
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
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}

/**
*
* @author paxelord
* @since 1.2
* @since 0.6
*
* @param value Double
*/
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](n: A)(implicit num: Numeric[A]) {
def newtonMeters = NewtonMeters(n)
def poundFeet = PoundFeet(n)
}

implicit object TorqueNumeric extends AbstractQuantityNumeric[Torque](Torque.primaryUnit)
}
11 changes: 9 additions & 2 deletions shared/src/main/scala/squants/space/Angle.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +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
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 0.6
*/
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))
}
}
83 changes: 83 additions & 0 deletions shared/src/test/scala/squants/motion/AngularAccelerationSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package squants.motion

import org.scalatest.{FlatSpec, Matchers}
import squants.QuantityParseException
import squants.space.{Meters, Radians}
import squants.time.Seconds

/**
*
* @author paxelord
* @since 0.6
*/
class AngularAccelerationSpec extends FlatSpec with Matchers{

behavior of "AngularAcceleration and its Units of Measure"

it should "create values using UOM factories" in {
RadiansPerSecondSquared(1).toRadiansPerSecondSquared should be(1)
DegreesPerSecondSquared(1).toDegreesPerSecondSquared should be(1)
GradsPerSecondSquared(1).toGradsPerSecondSquared should be(1)
TurnsPerSecondSquared(1).toTurnsPerSecondSquared should be(1)
}

it should "create values from properly formatted Strings" in {
AngularAcceleration("10.22 rad/s²").get should be(RadiansPerSecondSquared(10.22))
AngularAcceleration("10.22 °/s²").get should be(DegreesPerSecondSquared(10.22))
AngularAcceleration("10.22 grad/s²").get should be(GradsPerSecondSquared(10.22))
AngularAcceleration("10.22 turns/s²").get should be(TurnsPerSecondSquared(10.22))
AngularAcceleration("10.22 zz").failed.get should be(QuantityParseException("Unable to parse AngularAcceleration", "10.22 zz"))
AngularAcceleration("zz rad/s²").failed.get should be(QuantityParseException("Unable to parse AngularAcceleration", "zz rad/s²"))
}

it should "properly convert to all supported Units of Measure" in {
val x = RadiansPerSecondSquared(1)
x.toRadiansPerSecondSquared should be(1)
x.toDegreesPerSecondSquared should be(Radians(1).toDegrees)
x.toGradsPerSecondSquared should be(Radians(1).toGradians)
x.toTurnsPerSecondSquared should be(Radians(1).toTurns)
}

it should "return properly formatted strings for all supported Units of Measure" in {
RadiansPerSecondSquared(1).toString(RadiansPerSecondSquared) should be("1.0 rad/s²")
DegreesPerSecondSquared(1).toString(DegreesPerSecondSquared) should be("1.0 °/s²")
GradsPerSecondSquared(1).toString(GradsPerSecondSquared) should be("1.0 grad/s²")
TurnsPerSecondSquared(1).toString(TurnsPerSecondSquared) should be("1.0 turns/s²")
}

it should "return AnglularVelocity when multiplied by Time" in {
RadiansPerSecondSquared(1) * Seconds(1) should be(RadiansPerSecond(1))
}

it should "return Length when angle is arc on circle" in {
RadiansPerSecondSquared(1) onRadius Meters(1) should be(MetersPerSecondSquared(1))
}

behavior of "AngularAccelerationConversions"

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

radianPerSecondSquared should be(RadiansPerSecondSquared(1))
degreePerSecondSquared should be(DegreesPerSecondSquared(1))
gradPerSecondSquared should be(GradsPerSecondSquared(1))
turnPerSecondSquared should be(TurnsPerSecondSquared(1))
}

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

val d = 10.22d
d.radiansPerSecondSquared should be(RadiansPerSecondSquared(d))
d.degreesPerSecondSquared should be(DegreesPerSecondSquared(d))
d.gradsPerSecondSquared should be(GradsPerSecondSquared(d))
d.turnsPerSecondSquared should be(TurnsPerSecondSquared(d))
}

it should "provide Numeric support" in {
import AngularAccelerationConversions.AngularAccelerationNumeric

val aas = List(RadiansPerSecondSquared(100), RadiansPerSecondSquared(10))
aas.sum should be(RadiansPerSecondSquared(110))
}
}
Loading

0 comments on commit f92dd88

Please sign in to comment.