Skip to content

Commit

Permalink
use named classes in operations (#265)
Browse files Browse the repository at this point in the history
  • Loading branch information
erikerlandson authored Apr 6, 2022
1 parent 771fc13 commit 12adbf4
Show file tree
Hide file tree
Showing 34 changed files with 768 additions and 941 deletions.
9 changes: 4 additions & 5 deletions benchmarks/src/main/scala/coulomb/benchmarks/quantity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,16 @@ import org.openjdk.jmh.annotations.*
@Fork(1)
@BenchmarkMode(Array(Mode.Throughput))
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Warmup(iterations = 3, time = 1)
@Measurement(iterations = 10, time = 1)
@Warmup(iterations = 3, time = 2)
@Measurement(iterations = 10, time = 2)
class QuantityBenchmark:
import scala.language.implicitConversions
import coulomb.*
import coulomb.testing.units.{*, given}
import algebra.instances.all.given
import coulomb.ops.algebra.all.given
import coulomb.ops.standard.given
import coulomb.ops.resolution.standard.given
import coulomb.conversion.standard.all.given

import coulomb.policy.standard.given

var data: Vector[Quantity[Double, Meter]] = Vector.empty[Quantity[Double, Meter]]

Expand Down
19 changes: 5 additions & 14 deletions core/src/main/scala/coulomb/conversion/standard/scala.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,35 +26,26 @@ object scala:
// https://docs.scala-lang.org/scala3/reference/contextual/conversions.html

given ctx_Quantity_Conversion_1V1U[V, U]: Conversion[Quantity[V, U], Quantity[V, U]] =
new Conversion[Quantity[V, U], Quantity[V, U]]:
def apply(q: Quantity[V, U]): Quantity[V, U] = q
(q: Quantity[V, U]) => q

given ctx_Quantity_Conversion_1V2U[V, UF, UT](using
uc: UnitConversion[V, UF, UT]
): Conversion[Quantity[V, UF], Quantity[V, UT]] =
new Conversion[Quantity[V, UF], Quantity[V, UT]]:
def apply(q: Quantity[V, UF]): Quantity[V, UT] =
uc(q.value).withUnit[UT]
(q: Quantity[V, UF]) => uc(q.value).withUnit[UT]

given ctx_Quantity_Conversion_2V1U[U, VF, VT](using
vc: ValueConversion[VF, VT],
): Conversion[Quantity[VF, U], Quantity[VT, U]] =
new Conversion[Quantity[VF, U], Quantity[VT, U]]:
def apply(q: Quantity[VF, U]): Quantity[VT, U] =
vc(q.value).withUnit[U]
(q: Quantity[VF, U]) => vc(q.value).withUnit[U]

given ctx_Quantity_Conversion_2V2U[VF, UF, VT, UT](using
vc: ValueConversion[VF, VT],
uc: UnitConversion[VT, UF, UT]
): Conversion[Quantity[VF, UF], Quantity[VT, UT]] =
new Conversion[Quantity[VF, UF], Quantity[VT, UT]]:
def apply(q: Quantity[VF, UF]): Quantity[VT, UT] =
uc(vc(q.value)).withUnit[UT]
(q: Quantity[VF, UF]) => uc(vc(q.value)).withUnit[UT]

given ctx_DeltaQuantity_conversion_2V2U[B, VF, UF, VT, UT](using
vc: ValueConversion[VF, VT],
uc: DeltaUnitConversion[VT, B, UF, UT]
): Conversion[DeltaQuantity[VF, UF, B], DeltaQuantity[VT, UT, B]] =
new Conversion[DeltaQuantity[VF, UF, B], DeltaQuantity[VT, UT, B]]:
def apply(q: DeltaQuantity[VF, UF, B]): DeltaQuantity[VT, UT, B] =
uc(vc(q.value)).withDeltaUnit[UT, B]
(q: DeltaQuantity[VF, UF, B]) => uc(vc(q.value)).withDeltaUnit[UT, B]
88 changes: 47 additions & 41 deletions core/src/main/scala/coulomb/conversion/standard/unit.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,61 +22,67 @@ object unit:
import coulomb.rational.Rational

inline given ctx_UC_Rational[UF, UT]: UnitConversion[Rational, UF, UT] =
val c = coefficientRational[UF, UT]
new UnitConversion[Rational, UF, UT]:
def apply(v: Rational): Rational = c * v
new infra.RationalUC[UF, UT](coefficientRational[UF, UT])

inline given ctx_UC_Double[UF, UT]: UnitConversion[Double, UF, UT] =
val c = coefficientDouble[UF, UT]
new UnitConversion[Double, UF, UT]:
def apply(v: Double): Double = c * v
new infra.DoubleUC[UF, UT](coefficientDouble[UF, UT])

inline given ctx_UC_Float[UF, UT]: UnitConversion[Float, UF, UT] =
val c = coefficientFloat[UF, UT]
new UnitConversion[Float, UF, UT]:
def apply(v: Float): Float = c * v
new infra.FloatUC[UF, UT](coefficientFloat[UF, UT])

inline given ctx_TUC_Long[UF, UT]: TruncatingUnitConversion[Long, UF, UT] =
val nc = coefficientNumDouble[UF, UT]
val dc = coefficientDenDouble[UF, UT]
// using nc and dc is more efficient than using Rational directly in the conversion function
// but still gives us 53 bits of integer precision for exact rational arithmetic, and also
// graceful loss of precision if nc*v exceeds 53 bits
new TruncatingUnitConversion[Long, UF, UT]:
def apply(v: Long): Long = ((nc * v) / dc).toLong
new infra.LongTUC[UF, UT](coefficientNumDouble[UF, UT], coefficientDenDouble[UF, UT])

inline given ctx_TUC_Int[UF, UT]: TruncatingUnitConversion[Int, UF, UT] =
val nc = coefficientNumDouble[UF, UT]
val dc = coefficientDenDouble[UF, UT]
new TruncatingUnitConversion[Int, UF, UT]:
def apply(v: Int): Int = ((nc * v) / dc).toInt
new infra.IntTUC[UF, UT](coefficientNumDouble[UF, UT], coefficientDenDouble[UF, UT])

inline given ctx_DUC_Double[B, UF, UT]: DeltaUnitConversion[Double, B, UF, UT] =
val c = coefficientDouble[UF, UT]
val df = deltaOffsetDouble[UF, B]
val dt = deltaOffsetDouble[UT, B]
new DeltaUnitConversion[Double, B, UF, UT]:
def apply(v: Double): Double = ((v + df) * c) - dt
new infra.DoubleDUC[B, UF, UT](
coefficientDouble[UF, UT], deltaOffsetDouble[UF, B], deltaOffsetDouble[UT, B])

inline given ctx_DUC_Float[B, UF, UT]: DeltaUnitConversion[Float, B, UF, UT] =
val c = coefficientFloat[UF, UT]
val df = deltaOffsetFloat[UF, B]
val dt = deltaOffsetFloat[UT, B]
new DeltaUnitConversion[Float, B, UF, UT]:
def apply(v: Float): Float = ((v + df) * c) - dt
new infra.FloatDUC[B, UF, UT](
coefficientFloat[UF, UT], deltaOffsetFloat[UF, B], deltaOffsetFloat[UT, B])

inline given ctx_TDUC_Long[B, UF, UT]: TruncatingDeltaUnitConversion[Long, B, UF, UT] =
val nc = coefficientNumDouble[UF, UT]
val dc = coefficientDenDouble[UF, UT]
val df = deltaOffsetDouble[UF, B]
val dt = deltaOffsetDouble[UT, B]
new TruncatingDeltaUnitConversion[Long, B, UF, UT]:
def apply(v: Long): Long = (((nc * (v + df)) / dc) - dt).toLong
new infra.LongTDUC[B, UF, UT](
coefficientNumDouble[UF, UT], coefficientDenDouble[UF, UT],
deltaOffsetDouble[UF, B], deltaOffsetDouble[UT, B])

inline given ctx_TDUC_Int[B, UF, UT]: TruncatingDeltaUnitConversion[Int, B, UF, UT] =
val nc = coefficientNumDouble[UF, UT]
val dc = coefficientDenDouble[UF, UT]
val df = deltaOffsetDouble[UF, B]
val dt = deltaOffsetDouble[UT, B]
new TruncatingDeltaUnitConversion[Int, B, UF, UT]:
new infra.IntTDUC[B, UF, UT](
coefficientNumDouble[UF, UT], coefficientDenDouble[UF, UT],
deltaOffsetDouble[UF, B], deltaOffsetDouble[UT, B])

object infra:
class RationalUC[UF, UT](c: Rational) extends UnitConversion[Rational, UF, UT]:
def apply(v: Rational): Rational = c * v

class DoubleUC[UF, UT](c: Double) extends UnitConversion[Double, UF, UT]:
def apply(v: Double): Double = c * v

class FloatUC[UF, UT](c: Float) extends UnitConversion[Float, UF, UT]:
def apply(v: Float): Float = c * v

class LongTUC[UF, UT](nc: Double, dc: Double) extends TruncatingUnitConversion[Long, UF, UT]:
// using nc and dc is more efficient than using Rational directly in the conversion function
// but still gives us 53 bits of integer precision for exact rational arithmetic, and also
// graceful loss of precision if nc*v exceeds 53 bits
def apply(v: Long): Long = ((nc * v) / dc).toLong

class IntTUC[UF, UT](nc: Double, dc: Double) extends TruncatingUnitConversion[Int, UF, UT]:
def apply(v: Int): Int = ((nc * v) / dc).toInt

class DoubleDUC[B, UF, UT](c: Double, df: Double, dt: Double) extends DeltaUnitConversion[Double, B, UF, UT]:
def apply(v: Double): Double = ((v + df) * c) - dt

class FloatDUC[B, UF, UT](c: Float, df: Float, dt: Float) extends DeltaUnitConversion[Float, B, UF, UT]:
def apply(v: Float): Float = ((v + df) * c) - dt

class LongTDUC[B, UF, UT](nc: Double, dc: Double, df: Double, dt: Double) extends
TruncatingDeltaUnitConversion[Long, B, UF, UT]:
def apply(v: Long): Long = (((nc * (v + df)) / dc) - dt).toLong

class IntTDUC[B, UF, UT](nc: Double, dc: Double, df: Double, dt: Double) extends
TruncatingDeltaUnitConversion[Int, B, UF, UT]:
def apply(v: Int): Int = (((nc * (v + df)) / dc) - dt).toInt
6 changes: 6 additions & 0 deletions core/src/main/scala/coulomb/define/define.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ abstract class BaseUnit[U, Name, Abbv] extends NamedUnit[Name, Abbv]
* @tparam Abbv unit abbreviation
*/
abstract class DerivedUnit[U, D, Name, Abbv] extends NamedUnit[Name, Abbv]
object DerivedUnit:
given ctx_unit_to_DU[U, D, Name, Abbv]: scala.Conversion[Unit, DerivedUnit[U, D, Name, Abbv]] =
(_: Unit) => new infra.DU[U, D, Name, Abbv]
object infra:
class DU[U, D, Name, Abbv] extends DerivedUnit[U, D, Name, Abbv]

/**
* Delta Units represent units with an offset in their transforms, for example temperatures or times
Expand All @@ -47,3 +52,4 @@ abstract class DerivedUnit[U, D, Name, Abbv] extends NamedUnit[Name, Abbv]
* @tparam Abbv unit abbreviation
*/
abstract class DeltaUnit[U, D, O, Name, Abbv] extends DerivedUnit[U, D, Name, Abbv]

6 changes: 3 additions & 3 deletions core/src/main/scala/coulomb/deltaquantity.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,13 @@ object deltaquantity:
conv(ql.value).withDeltaUnit[U, B]

transparent inline def -[VR, UR](qr: DeltaQuantity[VR, UR, B])(using sub: DeltaSub[B, VL, UL, VR, UR]): Quantity[sub.VO, sub.UO] =
sub(ql, qr)
sub.eval(ql, qr)

transparent inline def -[VR, UR](qr: Quantity[VR, UR])(using sub: DeltaSubQ[B, VL, UL, VR, UR]): DeltaQuantity[sub.VO, sub.UO, B] =
sub(ql, qr)
sub.eval(ql, qr)

transparent inline def +[VR, UR](qr: Quantity[VR, UR])(using add: DeltaAddQ[B, VL, UL, VR, UR]): DeltaQuantity[add.VO, add.UO, B] =
add(ql, qr)
add.eval(ql, qr)

inline def ===[VR, UR](qr: DeltaQuantity[VR, UR, B])(using ord: DeltaOrd[B, VL, UL, VR, UR]): Boolean =
ord(ql, qr) == 0
Expand Down
48 changes: 0 additions & 48 deletions core/src/main/scala/coulomb/infra/meta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package coulomb.infra
import coulomb.rational.Rational
import coulomb.*
import coulomb.define.*
import coulomb.ops.*

object meta:
import scala.quoted.*
Expand All @@ -32,48 +31,6 @@ object meta:
case v if (v == 1) => '{ Rational.const1 }
case _ => '{ Rational(${Expr(r.n)}, ${Expr(r.d)}) }

def teToRational[E](using Quotes, Type[E]): Expr[Rational] =
import quotes.reflect.*
val rationalTE(v) = TypeRepr.of[E]
Expr(v)

def teToBigInt[E](using Quotes, Type[E]): Expr[BigInt] =
import quotes.reflect.*
val bigintTE(v) = TypeRepr.of[E]
Expr(v)

def teToDouble[E](using Quotes, Type[E]): Expr[Double] =
import quotes.reflect.*
val rationalTE(v) = TypeRepr.of[E]
Expr(v.toDouble)

def teToNonNegInt[E](using Quotes, Type[E]): Expr[coulomb.rational.typeexpr.NonNegInt[E]] =
import quotes.reflect.*
val rationalTE(v) = TypeRepr.of[E]
if ((v.d == 1) && (v.n >= 0) && (v.n.isValidInt)) then
'{ new coulomb.rational.typeexpr.NonNegInt[E] { val value = ${Expr(v.n.toInt)} } }
else
report.error(s"type expr ${typestr(TypeRepr.of[E])} is not a non-negative Int")
'{ new coulomb.rational.typeexpr.NonNegInt[E] { val value = 0 } }

def teToPosInt[E](using Quotes, Type[E]): Expr[coulomb.rational.typeexpr.PosInt[E]] =
import quotes.reflect.*
val rationalTE(v) = TypeRepr.of[E]
if ((v.d == 1) && (v.n > 0) && (v.n.isValidInt)) then
'{ new coulomb.rational.typeexpr.PosInt[E] { val value = ${Expr(v.n.toInt)} } }
else
report.error(s"type expr ${typestr(TypeRepr.of[E])} is not a positive Int")
'{ new coulomb.rational.typeexpr.PosInt[E] { val value = 0 } }

def teToInt[E](using Quotes, Type[E]): Expr[coulomb.rational.typeexpr.AllInt[E]] =
import quotes.reflect.*
val rationalTE(v) = TypeRepr.of[E]
if ((v.d == 1) && (v.n.isValidInt)) then
'{ new coulomb.rational.typeexpr.AllInt[E] { val value = ${Expr(v.n.toInt)} } }
else
report.error(s"type expr ${typestr(TypeRepr.of[E])} is not an Int")
'{ new coulomb.rational.typeexpr.AllInt[E] { val value = 0 } }

object rationalTE:
def unapply(using Quotes)(tr: quotes.reflect.TypeRepr): Option[Rational] =
import quotes.reflect.*
Expand Down Expand Up @@ -233,11 +190,6 @@ object meta:
val (nsig, dsig) = sortsig(tail)
if (e > 0) ((u, e) :: nsig, dsig) else (nsig, (u, -e) :: dsig)

def simplifiedUnit[U](using Quotes, Type[U]): Expr[SimplifiedUnit[U]] =
import quotes.reflect.*
simplify(TypeRepr.of[U]).asType match
case '[uo] => '{ new SimplifiedUnit[U] { type UO = uo } }

def simplify(using Quotes)(u: quotes.reflect.TypeRepr): quotes.reflect.TypeRepr =
import quotes.reflect.*
val (un, ud) = sortsig(stdsig(u))
Expand Down
33 changes: 13 additions & 20 deletions core/src/main/scala/coulomb/ops/ops.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,76 +21,73 @@ import scala.annotation.implicitNotFound
import coulomb.*

@implicitNotFound("Negation not defined in scope for Quantity[${V}, ${U}]")
abstract class Neg[V, U]:
def apply(q: Quantity[V, U]): Quantity[V, U]
abstract class Neg[V, U] extends (Quantity[V, U] => Quantity[V, U])

@implicitNotFound("Addition not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class Add[VL, UL, VR, UR]:
type VO
type UO
def apply(ql: Quantity[VL, UL], qr: Quantity[VR, UR]): Quantity[VO, UO]
val eval: (Quantity[VL, UL], Quantity[VR, UR]) => Quantity[VO, UO]

@implicitNotFound("Subtraction not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class Sub[VL, UL, VR, UR]:
type VO
type UO
def apply(ql: Quantity[VL, UL], qr: Quantity[VR, UR]): Quantity[VO, UO]
val eval: (Quantity[VL, UL], Quantity[VR, UR]) => Quantity[VO, UO]

@implicitNotFound("Multiplication not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class Mul[VL, UL, VR, UR]:
type VO
type UO
def apply(ql: Quantity[VL, UL], qr: Quantity[VR, UR]): Quantity[VO, UO]
val eval: (Quantity[VL, UL], Quantity[VR, UR]) => Quantity[VO, UO]

@implicitNotFound("Division not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class Div[VL, UL, VR, UR]:
type VO
type UO
def apply(ql: Quantity[VL, UL], qr: Quantity[VR, UR]): Quantity[VO, UO]
val eval: (Quantity[VL, UL], Quantity[VR, UR]) => Quantity[VO, UO]

@implicitNotFound("Truncating Division not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class TQuot[VL, UL, VR, UR]:
type VO
type UO
def apply(ql: Quantity[VL, UL], qr: Quantity[VR, UR]): Quantity[VO, UO]
val eval: (Quantity[VL, UL], Quantity[VR, UR]) => Quantity[VO, UO]

@implicitNotFound("Power not defined in scope for Quantity[${V}, ${U}] ^ ${P}")
abstract class Pow[V, U, P]:
type VO
type UO
def apply(q: Quantity[V, U]): Quantity[VO, UO]
val eval: Quantity[V, U] => Quantity[VO, UO]

@implicitNotFound("Truncating Power not defined in scope for Quantity[${V}, ${U}] ^ ${P}")
abstract class TPow[V, U, P]:
type VO
type UO
def apply(q: Quantity[V, U]): Quantity[VO, UO]
val eval: Quantity[V, U] => Quantity[VO, UO]

@implicitNotFound("Ordering not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class Ord[VL, UL, VR, UR]:
def apply(ql: Quantity[VL, UL], qr: Quantity[VR, UR]): Int
abstract class Ord[VL, UL, VR, UR] extends ((Quantity[VL, UL], Quantity[VR, UR]) => Int)

@implicitNotFound("Subtraction not defined in scope for DeltaQuantity[${VL}, ${UL}] and DeltaQuantity[${VR}, ${UR}]")
abstract class DeltaSub[B, VL, UL, VR, UR]:
type VO
type UO
def apply(ql: DeltaQuantity[VL, UL, B], qr: DeltaQuantity[VR, UR, B]): Quantity[VO, UO]
val eval: (DeltaQuantity[VL, UL, B], DeltaQuantity[VR, UR, B]) => Quantity[VO, UO]

@implicitNotFound("Subtraction not defined in scope for DeltaQuantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class DeltaSubQ[B, VL, UL, VR, UR]:
type VO
type UO
def apply(ql: DeltaQuantity[VL, UL, B], qr: Quantity[VR, UR]): DeltaQuantity[VO, UO, B]
val eval: (DeltaQuantity[VL, UL, B], Quantity[VR, UR]) => DeltaQuantity[VO, UO, B]

@implicitNotFound("Addition not defined in scope for DeltaQuantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]")
abstract class DeltaAddQ[B, VL, UL, VR, UR]:
type VO
type UO
def apply(ql: DeltaQuantity[VL, UL, B], qr: Quantity[VR, UR]): DeltaQuantity[VO, UO, B]
val eval: (DeltaQuantity[VL, UL, B], Quantity[VR, UR]) => DeltaQuantity[VO, UO, B]

@implicitNotFound("Ordering not defined in scope for DeltaQuantity[${VL}, ${UL}] and DeltaQuantity[${VR}, ${UR}]")
abstract class DeltaOrd[B, VL, UL, VR, UR]:
def apply(ql: DeltaQuantity[VL, UL, B], qr: DeltaQuantity[VR, UR, B]): Int
abstract class DeltaOrd[B, VL, UL, VR, UR] extends ((DeltaQuantity[VL, UL, B], DeltaQuantity[VR, UR, B]) => Int)

/** Resolve the operator output type for left and right argument types */
@implicitNotFound("No output type resolution in scope for argument value types ${VL} and ${VR}")
Expand All @@ -100,7 +97,3 @@ abstract class ValueResolution[VL, VR]:
@implicitNotFound("Unable to simplify unit type ${U}")
abstract class SimplifiedUnit[U]:
type UO

object SimplifiedUnit:
transparent inline given ctx_SimplifiedUnit[U]: SimplifiedUnit[U] =
${ coulomb.infra.meta.simplifiedUnit[U] }
Loading

0 comments on commit 12adbf4

Please sign in to comment.