Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rotational dynamics units #227

Merged
merged 3 commits into from
May 13, 2017
Merged
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
2 changes: 1 addition & 1 deletion shared/src/main/scala/squants/Quantity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -306,5 +306,5 @@ abstract class Quantity[A <: Quantity[A]] extends Serializable with Ordered[A] {
* @return
*/
def map(f: Double ⇒ Double): A = unit(f(value))

}

18 changes: 12 additions & 6 deletions shared/src/main/scala/squants/energy/Energy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@
package squants.energy

import squants._
import squants.electro.{ Coulombs, ElectricCharge, ElectricPotential, Volts }
import squants.mass.{ ChemicalAmount, Kilograms }
import squants.motion.Newtons
import squants.electro.{Coulombs, ElectricCharge, ElectricPotential, Volts}
import squants.mass.{ChemicalAmount, Kilograms}
import squants.motion.{NewtonMeters, Newtons, Torque}
import squants.space.CubicMeters
import squants.thermal.{ JoulesPerKelvin, Kelvin, ThermalCapacity }
import squants.time.{ Time, _ }
import squants.thermal.{JoulesPerKelvin, Kelvin, ThermalCapacity}
import squants.time.{Time, _}

/**
* Represents a quantity of energy
Expand Down Expand Up @@ -46,7 +46,7 @@ final class Energy private (val value: Double, val unit: EnergyUnit)
def /(that: ThermalCapacity) = Kelvin(toJoules / that.toJoulesPerKelvin)

def /(that: ChemicalAmount) = ??? // return MolarEnergy
def /(that: Angle) = ??? // return Torque (dimensionally equivalent to energy as Angles are dimensionless)
def /(that: Angle): Torque = NewtonMeters(toJoules / that.toRadians)
def /(that: Area) = ??? // Insolation, Energy Area Density

def /(that: TimeSquared): PowerRamp = this / that.time1 / that.time2
Expand All @@ -71,6 +71,12 @@ final class Energy private (val value: Double, val unit: EnergyUnit)
def toMBtus = to(MBtus)
def toMMBtus = to(MMBtus)
def toErgs = to(Ergs)

/**
* Energy and torque have the same unit, so convert appropriately
* @return numerically equivalent value in newton-meters
*/
def asTorque = NewtonMeters(toJoules)
}

/**
Expand Down
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
73 changes: 73 additions & 0 deletions shared/src/main/scala/squants/mass/MomentOfIntertia.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package squants.mass

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

/**
*
* @author paxelord
* @since 1.2
*
* @param value Double
*/
final class MomentOfInertia private (val value: Double, val unit: MomentOfInertiaUnit)
extends Quantity[MomentOfInertia]{

def dimension = MomentOfInertia

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

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

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)
}
}

object MomentOfInertia extends Dimension[MomentOfInertia] {
private[mass] def apply[A](n: A, unit: MomentOfInertiaUnit)(implicit num: Numeric[A]) = new MomentOfInertia(num.toDouble(n), unit)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could the constructors here return an Option to account for negative values?

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'm not 100% sure that's a good idea. People might be confused why they are getting a None.get Exception, whereas as throwing an IlleagalArgumentException would be more descriptive.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Throwing exceptions isn't very functional. If Option isn't suitable, we can look at Either which has a place to place an error data structure.

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 will try experimenting with Option and Either, and try to figure out what is most natural

Copy link
Contributor Author

@PhilipAxelrod PhilipAxelrod May 1, 2017

Choose a reason for hiding this comment

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

On a side note, I am starting to second guess whether this class should even exist. Maybe Squants should only provide compile time safety of units, and not tell people how to represent quantities, even if they are doing unusual things like having negative Mass or MomentOfInertia.

This might hinder people trying to explain legitimate scientific phenomena: https://en.wikipedia.org/wiki/Negative_mass

Copy link
Contributor Author

@PhilipAxelrod PhilipAxelrod May 1, 2017

Choose a reason for hiding this comment

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

One compromise would be to have the strictly positive feature optional, enabled by default. Someone who needs to use negative mass would have to explicitly disable the strictly positive feature. I'm not completely sure how that would work though.

def apply = parse _
def name = "MomentOfInertia"
def primaryUnit = KilogramsMetersSquared
def siUnit = KilogramsMetersSquared
def units = Set(KilogramsMetersSquared, PoundsSquareFeet)
}

trait MomentOfInertiaUnit extends UnitOfMeasure[MomentOfInertia] with UnitConverter {
def apply[A](n: A)(implicit num: Numeric[A]) = {
MomentOfInertia(num.toDouble(n), this)
}
}

object KilogramsMetersSquared extends MomentOfInertiaUnit with PrimaryUnit with SiBaseUnit {
val symbol = Kilograms.symbol + "‧" + Meters.symbol + "²"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Are we using exponentials in unicode like this in other places?

Copy link
Contributor Author

@PhilipAxelrod PhilipAxelrod May 1, 2017

Choose a reason for hiding this comment

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

In Acceleration and Area, unicode exponentials are used.

}

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)
}
112 changes: 112 additions & 0 deletions shared/src/main/scala/squants/motion/AngularAcceleration.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package squants.motion

import squants.mass.MomentOfInertia
import squants.space._
import squants.time.{Seconds, Time, TimeDerivative}
import squants.{AbstractQuantityNumeric, Dimension, Length, PrimaryUnit, Quantity, SiUnit, UnitConverter, UnitOfMeasure}

/**
*
* @author paxelord
* @since 1.2
*
* @param value Double
*/
final class AngularAcceleration private (val value: Double, val unit: AngularAccelerationUnit)
extends Quantity[AngularAcceleration] with TimeDerivative[AngularVelocity] {

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 *(that: MomentOfInertia): Torque = {
NewtonMeters(toRadiansPerSecondSquared * that.toKilogramsMetersSquared)
}

override protected[squants] def timeIntegrated: AngularVelocity = RadiansPerSecond(toRadiansPerSecondSquared)

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 _
def name = "AngularAcceleration"
def primaryUnit = RadiansPerSecondSquared
def siUnit = RadiansPerSecondSquared
def units = Set(
RadiansPerSecondSquared,
DegreesPerSecondSquared,
GradiansPerSecondSquared,
TurnsPerSecondSquared,
ArcminutesPerSecondSquared,
ArcsecondsPerSecondSquared)
}

trait AngularAccelerationUnit extends UnitOfMeasure[AngularAcceleration] with
UnitConverter {
def apply[A](n: A)(implicit num: Numeric[A]) = {
AngularAcceleration(num.toDouble(n), this)
}

val conversionFactor: Double
}

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

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

object GradiansPerSecondSquared extends AngularAccelerationUnit {
val symbol = Gradians.symbol + "/s²"
val conversionFactor = Gradians.conversionFactor
}

object TurnsPerSecondSquared extends AngularAccelerationUnit {
val symbol = Turns.symbol + "/s²"
val conversionFactor = Turns.conversionFactor
}

object ArcminutesPerSecondSquared extends AngularAccelerationUnit {
val symbol = Arcminutes.symbol + "/s²"
val conversionFactor = Arcminutes.conversionFactor
}

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)
}
33 changes: 27 additions & 6 deletions shared/src/main/scala/squants/motion/AngularVelocity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ package squants.motion

import squants._
import squants.space.{Degrees, Gradians, Turns}
import squants.time.TimeDerivative
import squants.time.{TimeDerivative, TimeIntegral}

/**
* @author garyKeorkunian
Expand All @@ -20,16 +20,28 @@ import squants.time.TimeDerivative
*
*/
final class AngularVelocity private (val value: Double, val unit: AngularVelocityUnit)
extends Quantity[AngularVelocity] with TimeDerivative[Angle] {
extends Quantity[AngularVelocity] with TimeDerivative[Angle] with TimeIntegral[AngularAcceleration]{
def dimension = AngularVelocity

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)

/**
* 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)

protected[squants] def timeDerived: AngularAcceleration = RadiansPerSecondSquared(toRadiansPerSecond)

protected[squants] def time: Time = Seconds(1)
}

Expand All @@ -39,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 @@ -55,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 @@ -68,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
61 changes: 61 additions & 0 deletions shared/src/main/scala/squants/motion/Torque.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package squants.motion

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

/**
*
* @author paxelord
* @since 1.2
*
* @param value Double
*/
final class Torque private (val value: Double, val unit: TorqueUnit)
extends Quantity[Torque] {

def dimension = Torque

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

def / (that: MomentOfInertia): AngularAcceleration = {
RadiansPerSecondSquared(toNewtonMeters / that.toKilogramsMetersSquared)
}
}

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 = "Torque"
def primaryUnit = NewtonMeters
def siUnit = NewtonMeters
def units = Set(NewtonMeters, PoundFeet)
}

trait TorqueUnit extends UnitOfMeasure[Torque] with UnitConverter {
def apply[A](n: A)(implicit num: Numeric[A]) = {
Torque(num.toDouble(n), this)
}
}

object NewtonMeters extends TorqueUnit with PrimaryUnit with SiBaseUnit {
val symbol = Newtons.symbol + "‧" + Meters.symbol
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this a times symbol? Are we using the same on other units?

Copy link
Contributor Author

@PhilipAxelrod PhilipAxelrod May 1, 2017

Choose a reason for hiding this comment

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

This is meant to be the times symbol. I looked though squants, and I think this is the first time we have a times symbol in a unit. It might be clearer to replace the current bullet with a splat *

In a future PR, it might be useful to have have a Integral[Unit] and Derivative[Unit], similar to TimeDerivative and TimeIntegral, to allow for easy extendability of units. For example, Energy would extend Integral[Force] with Integral[Length]. Or even better, something along the lines of Integral[Integrand, RespectTo] But that's for a future PR.

}

object PoundFeet extends TorqueUnit {
val symbol = Pounds.symbol + "‧" + Feet.symbol
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)
}
Loading