From 3d2bedeebac120d0ebfba653da00de1302df1040 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Sun, 6 Mar 2022 10:37:03 -0700 Subject: [PATCH 01/17] port Add to tl algebra --- build.sbt | 1 + .../main/scala/coulomb/ops/standard/add.scala | 18 ++++++++++-------- core/src/test/scala/coulomb/quantity.scala | 1 + 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/build.sbt b/build.sbt index 94a3100b6..ef0f99515 100644 --- a/build.sbt +++ b/build.sbt @@ -35,6 +35,7 @@ lazy val core = crossProject(JVMPlatform, JSPlatform/*, NativePlatform*/) .in(file("core")) .settings(name := "coulomb-core") .settings(commonSettings :_*) + .settings(libraryDependencies += "org.typelevel" %%% "algebra" % "2.7.0") lazy val units = crossProject(JVMPlatform, JSPlatform/*, NativePlatform*/) .crossType(CrossType.Pure) diff --git a/core/src/main/scala/coulomb/ops/standard/add.scala b/core/src/main/scala/coulomb/ops/standard/add.scala index dbde4392f..4d2413657 100644 --- a/core/src/main/scala/coulomb/ops/standard/add.scala +++ b/core/src/main/scala/coulomb/ops/standard/add.scala @@ -18,6 +18,8 @@ package coulomb.ops.standard import scala.util.NotGiven +import algebra.ring.AdditiveSemigroup + import coulomb.ops.{Add, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions @@ -58,24 +60,24 @@ transparent inline given ctx_add_1V1U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, equ: UR =:= UL, - alg: CanAdd[VL] + alg: AdditiveSemigroup[VL] ): Add[VL, UL, VR, UR] = new Add[VL, UL, VR, UR]: type VO = VL type UO = UL - def apply(vl: VL, vr: VR): VL = alg.add(vl, eqv(vr)) + def apply(vl: VL, vr: VR): VL = alg.plus(vl, eqv(vr)) transparent inline given ctx_add_1V2U[VL, UL, VR, UR](using ice: AllowImplicitConversions, eqv: VR =:= VL, neu: NotGiven[UR =:= UL], ucv: UnitConversion[VL, UR, UL], - alg: CanAdd[VL] + alg: AdditiveSemigroup[VL] ): Add[VL, UL, VR, UR] = new Add[VL, UL, VR, UR]: type VO = VL type UO = UL - def apply(vl: VL, vr: VR): VL = alg.add(vl, ucv(eqv(vr))) + def apply(vl: VL, vr: VR): VL = alg.plus(vl, ucv(eqv(vr))) transparent inline given ctx_add_2V1U[VL, UL, VR, UR](using ice: AllowImplicitConversions, @@ -84,12 +86,12 @@ transparent inline given ctx_add_2V1U[VL, UL, VR, UR](using vres: ValueResolution[VL, VR], vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], - alg: CanAdd[vres.VO] + alg: AdditiveSemigroup[vres.VO] ): Add[VL, UL, VR, UR] = new Add[VL, UL, VR, UR]: type VO = vres.VO type UO = UL - def apply(vl: VL, vr: VR): VO = alg.add(vlvo(vl), vrvo(vr)) + def apply(vl: VL, vr: VR): VO = alg.plus(vlvo(vl), vrvo(vr)) transparent inline given ctx_add_2V2U[VL, UL, VR, UR](using ice: AllowImplicitConversions, @@ -99,10 +101,10 @@ transparent inline given ctx_add_2V2U[VL, UL, VR, UR](using vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], ucvo: UnitConversion[vres.VO, UR, UL], - alg: CanAdd[vres.VO] + alg: AdditiveSemigroup[vres.VO] ): Add[VL, UL, VR, UR] = new Add[VL, UL, VR, UR]: type VO = vres.VO type UO = UL - def apply(vl: VL, vr: VR): VO = alg.add(vlvo(vl), ucvo(vrvo(vr))) + def apply(vl: VL, vr: VR): VO = alg.plus(vlvo(vl), ucvo(vrvo(vr))) diff --git a/core/src/test/scala/coulomb/quantity.scala b/core/src/test/scala/coulomb/quantity.scala index 63e6c56d2..007358b93 100644 --- a/core/src/test/scala/coulomb/quantity.scala +++ b/core/src/test/scala/coulomb/quantity.scala @@ -19,6 +19,7 @@ import coulomb.testing.CoulombSuite class QuantitySuite extends CoulombSuite: import coulomb.* import coulomb.testing.units.{*, given} + import algebra.instances.all.given test("lift via Quantity") { Quantity[Meter](3.14).assertQ[Double, Meter](3.14) From db7cf20436c5d6242cef6c17d1ea9072d3c23341 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Sun, 6 Mar 2022 10:42:15 -0700 Subject: [PATCH 02/17] port Sub to tl algebra --- .../main/scala/coulomb/ops/standard/sub.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/core/src/main/scala/coulomb/ops/standard/sub.scala b/core/src/main/scala/coulomb/ops/standard/sub.scala index 06d266e2a..b71429033 100644 --- a/core/src/main/scala/coulomb/ops/standard/sub.scala +++ b/core/src/main/scala/coulomb/ops/standard/sub.scala @@ -18,6 +18,8 @@ package coulomb.ops.standard import scala.util.NotGiven +import algebra.ring.AdditiveGroup + import coulomb.ops.{Sub, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions @@ -58,24 +60,24 @@ transparent inline given ctx_sub_1V1U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, equ: UR =:= UL, - alg: CanSub[VL] + alg: AdditiveGroup[VL] ): Sub[VL, UL, VR, UR] = new Sub[VL, UL, VR, UR]: type VO = VL type UO = UL - def apply(vl: VL, vr: VR): VL = alg.sub(vl, eqv(vr)) + def apply(vl: VL, vr: VR): VL = alg.minus(vl, eqv(vr)) transparent inline given ctx_sub_1V2U[VL, UL, VR, UR](using ice: AllowImplicitConversions, eqv: VR =:= VL, neu: NotGiven[UR =:= UL], ucv: UnitConversion[VL, UR, UL], - alg: CanSub[VL] + alg: AdditiveGroup[VL] ): Sub[VL, UL, VR, UR] = new Sub[VL, UL, VR, UR]: type VO = VL type UO = UL - def apply(vl: VL, vr: VR): VL = alg.sub(vl, ucv(eqv(vr))) + def apply(vl: VL, vr: VR): VL = alg.minus(vl, ucv(eqv(vr))) transparent inline given ctx_sub_2V1U[VL, UL, VR, UR](using ice: AllowImplicitConversions, @@ -84,12 +86,12 @@ transparent inline given ctx_sub_2V1U[VL, UL, VR, UR](using vres: ValueResolution[VL, VR], vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], - alg: CanSub[vres.VO] + alg: AdditiveGroup[vres.VO] ): Sub[VL, UL, VR, UR] = new Sub[VL, UL, VR, UR]: type VO = vres.VO type UO = UL - def apply(vl: VL, vr: VR): VO = alg.sub(vlvo(vl), vrvo(vr)) + def apply(vl: VL, vr: VR): VO = alg.minus(vlvo(vl), vrvo(vr)) transparent inline given ctx_sub_2V2U[VL, UL, VR, UR](using ice: AllowImplicitConversions, @@ -99,9 +101,9 @@ transparent inline given ctx_sub_2V2U[VL, UL, VR, UR](using vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], ucvo: UnitConversion[vres.VO, UR, UL], - alg: CanSub[vres.VO] + alg: AdditiveGroup[vres.VO] ): Sub[VL, UL, VR, UR] = new Sub[VL, UL, VR, UR]: type VO = vres.VO type UO = UL - def apply(vl: VL, vr: VR): VO = alg.sub(vlvo(vl), ucvo(vrvo(vr))) + def apply(vl: VL, vr: VR): VO = alg.minus(vlvo(vl), ucvo(vrvo(vr))) From 2296aff673541e2a7cdd35c4bc31b68ca775d27a Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Sun, 6 Mar 2022 10:45:52 -0700 Subject: [PATCH 03/17] port Mul to tl algebra --- core/src/main/scala/coulomb/ops/standard/mul.scala | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/coulomb/ops/standard/mul.scala b/core/src/main/scala/coulomb/ops/standard/mul.scala index 6e19f34c5..ec954f12d 100644 --- a/core/src/main/scala/coulomb/ops/standard/mul.scala +++ b/core/src/main/scala/coulomb/ops/standard/mul.scala @@ -18,7 +18,9 @@ package coulomb.ops.standard import scala.util.NotGiven -import coulomb.{`*`, `/`, `^`} +import algebra.ring.MultiplicativeSemigroup + +import coulomb.`*` import coulomb.ops.{Mul, SimplifiedUnit, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions @@ -40,13 +42,13 @@ transparent inline given ctx_mul_Float_2U[UL, UR](using su: SimplifiedUnit[UL * transparent inline given ctx_mul_1V2U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, - alg: CanMul[VL], + alg: MultiplicativeSemigroup[VL], su: SimplifiedUnit[UL * UR] ): Mul[VL, UL, VR, UR] = new Mul[VL, UL, VR, UR]: type VO = VL type UO = su.UO - def apply(vl: VL, vr: VR): VL = alg.mul(vl, eqv(vr)) + def apply(vl: VL, vr: VR): VL = alg.times(vl, eqv(vr)) transparent inline given ctx_mul_2V2U[VL, UL, VR, UR](using nev: NotGiven[VL =:= VR], @@ -54,11 +56,11 @@ transparent inline given ctx_mul_2V2U[VL, UL, VR, UR](using vres: ValueResolution[VL, VR], vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], - alg: CanMul[vres.VO], + alg: MultiplicativeSemigroup[vres.VO], su: SimplifiedUnit[UL * UR] ): Mul[VL, UL, VR, UR] = new Mul[VL, UL, VR, UR]: type VO = vres.VO type UO = su.UO - def apply(vl: VL, vr: VR): VO = alg.mul(vlvo(vl), vrvo(vr)) + def apply(vl: VL, vr: VR): VO = alg.times(vlvo(vl), vrvo(vr)) From fe5580058185aee474a0b1fca4939f60092cdf48 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Sun, 6 Mar 2022 11:04:06 -0700 Subject: [PATCH 04/17] Div with TruncatedDivision is failing --- .../scala/coulomb/ops/standard/algebra.scala | 13 ++---------- .../main/scala/coulomb/ops/standard/div.scala | 20 ++++++++++++++++++- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/core/src/main/scala/coulomb/ops/standard/algebra.scala b/core/src/main/scala/coulomb/ops/standard/algebra.scala index 2ad4d6dae..be6e2f3a8 100644 --- a/core/src/main/scala/coulomb/ops/standard/algebra.scala +++ b/core/src/main/scala/coulomb/ops/standard/algebra.scala @@ -22,6 +22,7 @@ import scala.math.{Fractional, Integral, Numeric} import coulomb.rational.Rational import coulomb.policy.AllowTruncation +/* abstract class CanAdd[V]: def add(vl: V, vr: V): V @@ -45,18 +46,8 @@ object CanMul: inline given ctx_CanMul_Numeric[V](using num: Numeric[V]): CanMul[V] = new CanMul[V]: def mul(vl: V, vr: V): V = num.times(vl, vr) +*/ -abstract class CanDiv[V]: - def div(vl: V, vr: V): V - -object CanDiv: - inline given ctx_CanDiv_Fractional[V](using num: Fractional[V]): CanDiv[V] = - new CanDiv[V]: - def div(vl: V, vr: V): V = num.div(vl, vr) - - inline given ctx_CanDiv_Integral[V](using num: Integral[V], at: AllowTruncation): CanDiv[V] = - new CanDiv[V]: - def div(vl: V, vr: V): V = num.quot(vl, vr) abstract class CanPow[V, E]: def pow(v: V): V diff --git a/core/src/main/scala/coulomb/ops/standard/div.scala b/core/src/main/scala/coulomb/ops/standard/div.scala index 7e1159df5..aa972a0c1 100644 --- a/core/src/main/scala/coulomb/ops/standard/div.scala +++ b/core/src/main/scala/coulomb/ops/standard/div.scala @@ -18,10 +18,14 @@ package coulomb.ops.standard import scala.util.NotGiven -import coulomb.{`*`, `/`, `^`} +import algebra.ring.MultiplicativeGroup +import algebra.ring.TruncatedDivision + +import coulomb.`/` import coulomb.ops.{Div, SimplifiedUnit, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions +import coulomb.policy.AllowTruncation transparent inline given ctx_div_Double_2U[UL, UR](using su: SimplifiedUnit[UL / UR]): Div[Double, UL, Double, UR] = @@ -62,3 +66,17 @@ transparent inline given ctx_div_2V2U[VL, UL, VR, UR](using type UO = su.UO def apply(vl: VL, vr: VR): VO = alg.div(vlvo(vl), vrvo(vr)) +abstract class CanDiv[V]: + def div(vl: V, vr: V): V + +object CanDiv: + inline given ctx_CanDiv_MultiplicativeGroup[V](using alg: MultiplicativeGroup[V]): CanDiv[V] = + new CanDiv[V]: + def div(vl: V, vr: V): V = alg.div(vl, vr) + + inline given ctx_CanDiv_TruncatedDivision[V](using + nmg: NotGiven[MultiplicativeGroup[V]], + at: AllowTruncation, + alg: TruncatedDivision[V]): CanDiv[V] = + new CanDiv[V]: + def div(vl: V, vr: V): V = alg.tquot(vl, vr) From e78652a531ebe0c8548f9dd7fcac632ee0ad9942 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Mon, 7 Mar 2022 09:52:19 -0700 Subject: [PATCH 05/17] / and tquot --- core/src/main/scala/coulomb/ops/ops.scala | 12 +++- .../main/scala/coulomb/ops/standard/div.scala | 20 +----- .../scala/coulomb/ops/standard/tquot.scala | 67 +++++++++++++++++++ core/src/main/scala/coulomb/quantity.scala | 4 ++ core/src/test/scala/coulomb/quantity.scala | 18 +++-- 5 files changed, 90 insertions(+), 31 deletions(-) create mode 100644 core/src/main/scala/coulomb/ops/standard/tquot.scala diff --git a/core/src/main/scala/coulomb/ops/ops.scala b/core/src/main/scala/coulomb/ops/ops.scala index 166bbfbd8..e1062bb58 100644 --- a/core/src/main/scala/coulomb/ops/ops.scala +++ b/core/src/main/scala/coulomb/ops/ops.scala @@ -20,6 +20,10 @@ import scala.annotation.implicitNotFound import coulomb.* +@implicitNotFound("Negation not defined in scope for Quantity[${V}, ${U}]") +abstract class Neg[V, U]: + def apply(v: V): V + @implicitNotFound("Addition not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]") abstract class Add[VL, UL, VR, UR]: type VO @@ -44,9 +48,11 @@ abstract class Div[VL, UL, VR, UR]: type UO def apply(vl: VL, vr: VR): VO -@implicitNotFound("Negation not defined in scope for Quantity[${V}, ${U}]") -abstract class Neg[V, U]: - def apply(v: V): V +@implicitNotFound("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(vl: VL, vr: VR): VO @implicitNotFound("Power not defined in scope for Quantity[${V}, ${U}] ^ ${P}") abstract class Pow[V, U, P]: diff --git a/core/src/main/scala/coulomb/ops/standard/div.scala b/core/src/main/scala/coulomb/ops/standard/div.scala index aa972a0c1..1dbecafb8 100644 --- a/core/src/main/scala/coulomb/ops/standard/div.scala +++ b/core/src/main/scala/coulomb/ops/standard/div.scala @@ -19,7 +19,6 @@ package coulomb.ops.standard import scala.util.NotGiven import algebra.ring.MultiplicativeGroup -import algebra.ring.TruncatedDivision import coulomb.`/` import coulomb.ops.{Div, SimplifiedUnit, ValueResolution} @@ -44,7 +43,7 @@ transparent inline given ctx_div_Float_2U[UL, UR](using su: SimplifiedUnit[UL / transparent inline given ctx_div_1V2U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, - alg: CanDiv[VL], + alg: MultiplicativeGroup[VL], su: SimplifiedUnit[UL / UR] ): Div[VL, UL, VR, UR] = new Div[VL, UL, VR, UR]: @@ -58,25 +57,10 @@ transparent inline given ctx_div_2V2U[VL, UL, VR, UR](using vres: ValueResolution[VL, VR], vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], - alg: CanDiv[vres.VO], + alg: MultiplicativeGroup[vres.VO], su: SimplifiedUnit[UL / UR] ): Div[VL, UL, VR, UR] = new Div[VL, UL, VR, UR]: type VO = vres.VO type UO = su.UO def apply(vl: VL, vr: VR): VO = alg.div(vlvo(vl), vrvo(vr)) - -abstract class CanDiv[V]: - def div(vl: V, vr: V): V - -object CanDiv: - inline given ctx_CanDiv_MultiplicativeGroup[V](using alg: MultiplicativeGroup[V]): CanDiv[V] = - new CanDiv[V]: - def div(vl: V, vr: V): V = alg.div(vl, vr) - - inline given ctx_CanDiv_TruncatedDivision[V](using - nmg: NotGiven[MultiplicativeGroup[V]], - at: AllowTruncation, - alg: TruncatedDivision[V]): CanDiv[V] = - new CanDiv[V]: - def div(vl: V, vr: V): V = alg.tquot(vl, vr) diff --git a/core/src/main/scala/coulomb/ops/standard/tquot.scala b/core/src/main/scala/coulomb/ops/standard/tquot.scala new file mode 100644 index 000000000..7dfc6bb45 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/standard/tquot.scala @@ -0,0 +1,67 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.standard + +import scala.util.NotGiven + +import algebra.ring.TruncatedDivision + +import coulomb.`/` +import coulomb.ops.{TQuot, SimplifiedUnit, ValueResolution} +import coulomb.conversion.{ValueConversion} +import coulomb.policy.AllowImplicitConversions +import coulomb.policy.AllowTruncation + +transparent inline given ctx_tquot_1V2U[VL, UL, VR, UR](using + // https://github.com/lampepfl/dotty/issues/14585 + eqv: VR =:= VL, + alg: CanTQuot[VL], + su: SimplifiedUnit[UL / UR] + ): TQuot[VL, UL, VR, UR] = + new TQuot[VL, UL, VR, UR]: + type VO = VL + type UO = su.UO + def apply(vl: VL, vr: VR): VL = alg.tquot(vl, eqv(vr)) + +transparent inline given ctx_tquot_2V2U[VL, UL, VR, UR](using + nev: NotGiven[VL =:= VR], + ice: AllowImplicitConversions, + vres: ValueResolution[VL, VR], + vlvo: ValueConversion[VL, vres.VO], + vrvo: ValueConversion[VR, vres.VO], + alg: CanTQuot[vres.VO], + su: SimplifiedUnit[UL / UR] + ): TQuot[VL, UL, VR, UR] = + new TQuot[VL, UL, VR, UR]: + type VO = vres.VO + type UO = su.UO + def apply(vl: VL, vr: VR): VO = alg.tquot(vlvo(vl), vrvo(vr)) + +// if algebra ever implements TruncatedDivision for Int and Long I can get rid of this +abstract class CanTQuot[V]: + def tquot(vl: V, vr: V): V + +object CanTQuot: + inline given ctx_TruncatedDivision_CanTQuot[V](using td: TruncatedDivision[V]): CanTQuot[V] = + new CanTQuot[V]: + def tquot(vl: V, vr: V): V = td.tquot(vl, vr) + + inline given ctx_Integral_CanTQuot[V](using + ntd: NotGiven[TruncatedDivision[V]], + num: scala.math.Integral[V]): CanTQuot[V] = + new CanTQuot[V]: + def tquot(vl: V, vr: V): V = num.quot(vl, vr) diff --git a/core/src/main/scala/coulomb/quantity.scala b/core/src/main/scala/coulomb/quantity.scala index 3a23ddaef..c3b82d5ad 100644 --- a/core/src/main/scala/coulomb/quantity.scala +++ b/core/src/main/scala/coulomb/quantity.scala @@ -114,9 +114,13 @@ extension[VL, UL](ql: Quantity[VL, UL]) transparent inline def /[VR, UR](qr: Quantity[VR, UR])(using div: Div[VL, UL, VR, UR]): Quantity[div.VO, div.UO] = div(ql.value, qr.value).withUnit[div.UO] + transparent inline def tquot[VR, UR](qr: Quantity[VR, UR])(using tq: TQuot[VL, UL, VR, UR]): Quantity[tq.VO, tq.UO] = + tq(ql.value, qr.value).withUnit[tq.UO] + transparent inline def pow[P](using pow: Pow[VL, UL, P]): Quantity[pow.VO, pow.UO] = pow(ql.value).withUnit[pow.UO] + inline def ===[VR, UR](qr: Quantity[VR, UR])(using ord: Ord[VL, UL, VR, UR]): Boolean = ord(ql.value, qr.value) == 0 diff --git a/core/src/test/scala/coulomb/quantity.scala b/core/src/test/scala/coulomb/quantity.scala index 007358b93..103336170 100644 --- a/core/src/test/scala/coulomb/quantity.scala +++ b/core/src/test/scala/coulomb/quantity.scala @@ -20,6 +20,8 @@ class QuantitySuite extends CoulombSuite: import coulomb.* import coulomb.testing.units.{*, given} import algebra.instances.all.given + import algebra.ring.TruncatedDivision + import algebra.ring.TruncatedDivision.given test("lift via Quantity") { Quantity[Meter](3.14).assertQ[Double, Meter](3.14) @@ -408,22 +410,18 @@ class QuantitySuite extends CoulombSuite: assertCE("5.withUnit[Meter] / 2.withUnit[Second]") } - test("division standard truncating") { - import coulomb.policy.allowTruncation.given + test("truncating division standard") { + //import coulomb.policy.allowTruncation.given import coulomb.policy.allowImplicitConversions.given import coulomb.ops.standard.given import coulomb.ops.resolution.standard.given import coulomb.conversion.standard.given - (5L.withUnit[Meter] / 2d.withUnit[Second]).assertQ[Double, Meter / Second](2.5) - (5L.withUnit[Meter] / 2f.withUnit[Second]).assertQ[Float, Meter / Second](2.5) - (5L.withUnit[Meter] / 2L.withUnit[Second]).assertQ[Long, Meter / Second](2) - (5L.withUnit[Meter] / 2.withUnit[Second]).assertQ[Long, Meter / Second](2) + (5L.withUnit[Meter] `tquot` 2L.withUnit[Second]).assertQ[Long, Meter / Second](2) + (5L.withUnit[Meter] `tquot` 2.withUnit[Second]).assertQ[Long, Meter / Second](2) - (5.withUnit[Meter] / 2d.withUnit[Second]).assertQ[Double, Meter / Second](2.5) - (5.withUnit[Meter] / 2f.withUnit[Second]).assertQ[Float, Meter / Second](2.5) - (5.withUnit[Meter] / 2L.withUnit[Second]).assertQ[Long, Meter / Second](2) - (5.withUnit[Meter] / 2.withUnit[Second]).assertQ[Int, Meter / Second](2) + (5.withUnit[Meter] `tquot` 2L.withUnit[Second]).assertQ[Long, Meter / Second](2) + (5.withUnit[Meter] `tquot` 2.withUnit[Second]).assertQ[Int, Meter / Second](2) } test("power standard") { From da94832312e77df839b8d0f54de1d0d712ce7e4f Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Mon, 7 Mar 2022 13:29:07 -0700 Subject: [PATCH 06/17] neg --- core/src/main/scala/coulomb/ops/standard/neg.scala | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/main/scala/coulomb/ops/standard/neg.scala b/core/src/main/scala/coulomb/ops/standard/neg.scala index 7da950337..71e54ea6e 100644 --- a/core/src/main/scala/coulomb/ops/standard/neg.scala +++ b/core/src/main/scala/coulomb/ops/standard/neg.scala @@ -19,6 +19,7 @@ package coulomb.ops.standard import scala.util.NotGiven import coulomb.ops.Neg +import algebra.ring.AdditiveGroup inline given ctx_quantity_neg_Double[U]: Neg[Double, U] = new Neg[Double, U]: @@ -28,7 +29,7 @@ inline given ctx_quantity_neg_Float[U]: Neg[Float, U] = new Neg[Float, U]: def apply(v: Float): Float = -v -inline given ctx_quantity_neg[V, U](using alg: CanNeg[V]): Neg[V, U] = +inline given ctx_quantity_neg[V, U](using alg: AdditiveGroup[V]): Neg[V, U] = new Neg[V, U]: - def apply(v: V): V = alg.neg(v) + def apply(v: V): V = alg.negate(v) From 746798bafd957554982707b5eb6cdb8690ab23a7 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Mon, 7 Mar 2022 13:29:43 -0700 Subject: [PATCH 07/17] pow and tpow --- core/src/main/scala/coulomb/infra/meta.scala | 18 ++++++ core/src/main/scala/coulomb/ops/ops.scala | 8 ++- .../scala/coulomb/ops/standard/algebra.scala | 40 +----------- .../main/scala/coulomb/ops/standard/pow.scala | 44 +++++++++++++ .../scala/coulomb/ops/standard/tpow.scala | 64 +++++++++++++++++++ .../scala/coulomb/ops/standard/tquot.scala | 6 ++ core/src/main/scala/coulomb/quantity.scala | 2 + .../scala/coulomb/rational/rational.scala | 14 ++++ core/src/test/scala/coulomb/quantity.scala | 57 +++++++++-------- 9 files changed, 186 insertions(+), 67 deletions(-) create mode 100644 core/src/main/scala/coulomb/ops/standard/tpow.scala diff --git a/core/src/main/scala/coulomb/infra/meta.scala b/core/src/main/scala/coulomb/infra/meta.scala index 1dde4c089..69322b09b 100644 --- a/core/src/main/scala/coulomb/infra/meta.scala +++ b/core/src/main/scala/coulomb/infra/meta.scala @@ -59,6 +59,24 @@ object meta: 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.* diff --git a/core/src/main/scala/coulomb/ops/ops.scala b/core/src/main/scala/coulomb/ops/ops.scala index e1062bb58..2e033ea01 100644 --- a/core/src/main/scala/coulomb/ops/ops.scala +++ b/core/src/main/scala/coulomb/ops/ops.scala @@ -48,7 +48,7 @@ abstract class Div[VL, UL, VR, UR]: type UO def apply(vl: VL, vr: VR): VO -@implicitNotFound("Division not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]") +@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 @@ -60,6 +60,12 @@ abstract class Pow[V, U, P]: type UO def apply(v: V): VO +@implicitNotFound("Truncating Power not defined in scope for Quantity[${V}, ${U}] ^ ${P}") +abstract class TPow[V, U, P]: + type VO + type UO + def apply(v: V): VO + @implicitNotFound("Ordering not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]") abstract class Ord[VL, UL, VR, UR]: def apply(vl: VL, vr: VR): Int diff --git a/core/src/main/scala/coulomb/ops/standard/algebra.scala b/core/src/main/scala/coulomb/ops/standard/algebra.scala index be6e2f3a8..bcb98edb1 100644 --- a/core/src/main/scala/coulomb/ops/standard/algebra.scala +++ b/core/src/main/scala/coulomb/ops/standard/algebra.scala @@ -46,45 +46,6 @@ object CanMul: inline given ctx_CanMul_Numeric[V](using num: Numeric[V]): CanMul[V] = new CanMul[V]: def mul(vl: V, vr: V): V = num.times(vl, vr) -*/ - - -abstract class CanPow[V, E]: - def pow(v: V): V - -object CanPow: - import coulomb.rational.typeexpr - import scala.math - - inline given ctx_CanPow_Double[E]: CanPow[Double, E] = - val e: Double = typeexpr.double[E] - new CanPow[Double, E]: - def pow(v: Double): Double = math.pow(v, e) - - inline given ctx_CanPow_Float[E]: CanPow[Float, E] = - val e: Double = typeexpr.double[E] - new CanPow[Float, E]: - def pow(v: Float): Float = math.pow(v.toDouble, e).toFloat - - inline given ctx_CanPow_Long[E](using gez: typeexpr.NonNegInt[E]): CanPow[Long, E] = - new CanPow[Long, E]: - def pow(v: Long): Long = math.pow(v.toDouble, gez.value.toDouble).toLong - - inline given ctx_CanPow_Long_trunc[E](using NotGiven[typeexpr.NonNegInt[E]], AllowTruncation): - CanPow[Long, E] = - val e = typeexpr.double[E] - new CanPow[Long, E]: - def pow(v: Long): Long = math.pow(v.toDouble, e).toLong - - inline given ctx_CanPow_Int[E](using gez: typeexpr.NonNegInt[E]): CanPow[Int, E] = - new CanPow[Int, E]: - def pow(v: Int): Int = math.pow(v.toDouble, gez.value.toDouble).toInt - - inline given ctx_CanPow_Int_trunc[E](using NotGiven[typeexpr.NonNegInt[E]], AllowTruncation): - CanPow[Int, E] = - val e = typeexpr.double[E] - new CanPow[Int, E]: - def pow(v: Int): Int = math.pow(v.toDouble, e).toInt abstract class CanNeg[V]: def neg(v: V): V @@ -93,3 +54,4 @@ object CanNeg: inline given ctx_CanNeg_Numeric[V](using num: Numeric[V]): CanNeg[V] = new CanNeg[V]: def neg(v: V): V = num.negate(v) +*/ diff --git a/core/src/main/scala/coulomb/ops/standard/pow.scala b/core/src/main/scala/coulomb/ops/standard/pow.scala index ff9f13140..d23638602 100644 --- a/core/src/main/scala/coulomb/ops/standard/pow.scala +++ b/core/src/main/scala/coulomb/ops/standard/pow.scala @@ -30,3 +30,47 @@ transparent inline given ctx_quantity_pow[V, U, E](using type UO = su.UO def apply(v: V): VO = alg.pow(v) +abstract class CanPow[V, E]: + def pow(v: V): V + +object CanPow: + import coulomb.rational.typeexpr + import algebra.ring.MultiplicativeSemigroup + import algebra.ring.MultiplicativeGroup + + inline given ctx_FracPow_CanPow[V, E](using fp: FracPow[V]): CanPow[V, E] = + val e: Double = typeexpr.double[E] + new CanPow[V, E]: + def pow(v: V): V = fp.pow(v, e) + + inline given ctx_MultiplicativeGroup_CanPow[V, E](using + nfp: NotGiven[FracPow[V]], + mgp: MultiplicativeGroup[V], + aie: typeexpr.AllInt[E]): CanPow[V, E] = + val e: Int = aie.value + new CanPow[V, E]: + def pow(v: V): V = mgp.pow(v, e) + + inline given ctx_MultiplicativeSemigroup_CanPow[V, E](using + nfp: NotGiven[FracPow[V]], + nmg: NotGiven[MultiplicativeGroup[V]], + msg: MultiplicativeSemigroup[V], + pie: typeexpr.PosInt[E]): CanPow[V, E] = + val e: Int = pie.value + new CanPow[V, E]: + def pow(v: V): V = msg.pow(v, e) + + // there is no typelevel community typeclass that expresses the concept + // "supports raising to fractional powers without truncation" + // The closest thing is spire NRoot, but it is also defined on truncating integer types, + // so it is not helpful for distinguishing "pow" from "tpow", and in any case requires spire + // https://github.com/typelevel/spire/issues/741 + abstract class FracPow[V]: + def pow(v: V, e: Double): V + + object FracPow: + given ctx_Double_is_FracPow: FracPow[Double] with + def pow(v: Double, e: Double): Double = math.pow(v, e) + + given ctx_Float_is_FracPow: FracPow[Float] with + def pow(v: Float, e: Double): Float = math.pow(v, e).toFloat diff --git a/core/src/main/scala/coulomb/ops/standard/tpow.scala b/core/src/main/scala/coulomb/ops/standard/tpow.scala new file mode 100644 index 000000000..ef3349f78 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/standard/tpow.scala @@ -0,0 +1,64 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.standard + +import scala.util.NotGiven + +import coulomb.`^` +import coulomb.ops.{TPow, SimplifiedUnit} + +transparent inline given ctx_quantity_tpow[V, U, E](using + alg: CanTPow[V, E], + su: SimplifiedUnit[U ^ E] + ): TPow[V, U, E] = + new TPow[V, U, E]: + type VO = V + type UO = su.UO + def apply(v: V): VO = alg.tpow(v) + +abstract class CanTPow[V, E]: + def tpow(v: V): V + +object CanTPow: + // there is no typelevel community typeclass that expresses the concept + // "supports raising to fractional powers with truncation" + // The closest thing is spire NRoot, but it does not formally distinguish between + // "pow" and "tpow" or which types support which operation, + // so it is not helpful for distinguishing "pow" from "tpow", and in any case requires spire + // https://github.com/typelevel/spire/issues/741 + + import coulomb.rational.typeexpr + + inline given ctx_Double_CanTPow[E]: CanTPow[Double, E] = + val e: Double = typeexpr.double[E] + new CanTPow[Double, E]: + def tpow(v: Double): Double = math.pow(v, e).toLong.toDouble + + inline given ctx_Float_CanTPow[E]: CanTPow[Float, E] = + val e: Double = typeexpr.double[E] + new CanTPow[Float, E]: + def tpow(v: Float): Float = math.pow(v.toDouble, e).toLong.toFloat + + inline given ctx_Long_CanTPow[E]: CanTPow[Long, E] = + val e: Double = typeexpr.double[E] + new CanTPow[Long, E]: + def tpow(v: Long): Long = math.pow(v.toDouble, e).toLong + + inline given ctx_Int_CanTPow[E]: CanTPow[Int, E] = + val e: Double = typeexpr.double[E] + new CanTPow[Int, E]: + def tpow(v: Int): Int = math.pow(v.toDouble, e).toInt diff --git a/core/src/main/scala/coulomb/ops/standard/tquot.scala b/core/src/main/scala/coulomb/ops/standard/tquot.scala index 7dfc6bb45..4609c2d5a 100644 --- a/core/src/main/scala/coulomb/ops/standard/tquot.scala +++ b/core/src/main/scala/coulomb/ops/standard/tquot.scala @@ -65,3 +65,9 @@ object CanTQuot: num: scala.math.Integral[V]): CanTQuot[V] = new CanTQuot[V]: def tquot(vl: V, vr: V): V = num.quot(vl, vr) + + inline given ctx_Fractional_CanTQuot[V](using + ntd: NotGiven[TruncatedDivision[V]], + num: scala.math.Fractional[V]): CanTQuot[V] = + new CanTQuot[V]: + def tquot(vl: V, vr: V): V = num.fromInt(num.toInt(num.div(vl, vr))) diff --git a/core/src/main/scala/coulomb/quantity.scala b/core/src/main/scala/coulomb/quantity.scala index c3b82d5ad..33da3a30d 100644 --- a/core/src/main/scala/coulomb/quantity.scala +++ b/core/src/main/scala/coulomb/quantity.scala @@ -120,6 +120,8 @@ extension[VL, UL](ql: Quantity[VL, UL]) transparent inline def pow[P](using pow: Pow[VL, UL, P]): Quantity[pow.VO, pow.UO] = pow(ql.value).withUnit[pow.UO] + transparent inline def tpow[P](using tp: TPow[VL, UL, P]): Quantity[tp.VO, tp.UO] = + tp(ql.value).withUnit[tp.UO] inline def ===[VR, UR](qr: Quantity[VR, UR])(using ord: Ord[VL, UL, VR, UR]): Boolean = ord(ql.value, qr.value) == 0 diff --git a/core/src/main/scala/coulomb/rational/rational.scala b/core/src/main/scala/coulomb/rational/rational.scala index a6d8a1a0c..d4ec8dd0d 100644 --- a/core/src/main/scala/coulomb/rational/rational.scala +++ b/core/src/main/scala/coulomb/rational/rational.scala @@ -157,3 +157,17 @@ object typeexpr: object NonNegInt: // interesting, this has to be 'transparent' to work with NotGiven transparent inline given ctx_NonNegInt[E]: NonNegInt[E] = ${ meta.teToNonNegInt[E] } + + @implicitNotFound("type expr ${E} is not a positive Int") + abstract class PosInt[E]: + val value: Int + + object PosInt: + transparent inline given ctx_PosInt[E]: PosInt[E] = ${ meta.teToPosInt[E] } + + @implicitNotFound("type expr ${E} is not an Int") + abstract class AllInt[E]: + val value: Int + + object AllInt: + transparent inline given ctx_AllInt[E]: AllInt[E] = ${ meta.teToInt[E] } diff --git a/core/src/test/scala/coulomb/quantity.scala b/core/src/test/scala/coulomb/quantity.scala index 103336170..edad1cea2 100644 --- a/core/src/test/scala/coulomb/quantity.scala +++ b/core/src/test/scala/coulomb/quantity.scala @@ -417,6 +417,9 @@ class QuantitySuite extends CoulombSuite: import coulomb.ops.resolution.standard.given import coulomb.conversion.standard.given + (5d.withUnit[Meter] `tquot` 2d.withUnit[Second]).assertQ[Double, Meter / Second](2) + (5f.withUnit[Meter] `tquot` 2f.withUnit[Second]).assertQ[Float, Meter / Second](2) + (5L.withUnit[Meter] `tquot` 2L.withUnit[Second]).assertQ[Long, Meter / Second](2) (5L.withUnit[Meter] `tquot` 2.withUnit[Second]).assertQ[Long, Meter / Second](2) @@ -441,48 +444,48 @@ class QuantitySuite extends CoulombSuite: 2f.withUnit[Meter].pow[1 / 2].assertQD[Float, Meter ^ (1 / 2)](1.4142135623730951) 2f.withUnit[Meter].pow[-1 / 2].assertQD[Float, 1 / (Meter ^ (1 / 2))](0.7071067811865476) - 2L.withUnit[Meter].pow[0].assertQ[Long, 1](1) + // integer types are supported via multiplicative semigroup + assertCE("2L.withUnit[Meter].pow[0]") 2L.withUnit[Meter].pow[2].assertQ[Long, Meter ^ 2](4) assertCE("2L.withUnit[Meter].pow[-1]") assertCE("2L.withUnit[Meter].pow[1 / 2]") assertCE("2L.withUnit[Meter].pow[-1 / 2]") - 2.withUnit[Meter].pow[0].assertQ[Int, 1](1) + assertCE("2.withUnit[Meter].pow[0]") 2.withUnit[Meter].pow[2].assertQ[Int, Meter ^ 2](4) assertCE("2.withUnit[Meter].pow[-1]") assertCE("2.withUnit[Meter].pow[1 / 2]") assertCE("2.withUnit[Meter].pow[-1 / 2]") } - test("power standard truncating") { - import coulomb.policy.allowTruncation.given + test("truncating power standard") { import coulomb.ops.standard.given import coulomb.ops.resolution.standard.given import coulomb.conversion.standard.given - 10d.withUnit[Meter].pow[0].assertQ[Double, 1](1) - 10d.withUnit[Meter].pow[2].assertQ[Double, Meter ^ 2](100) - 10d.withUnit[Meter].pow[-1].assertQD[Double, 1 / Meter](0.1) - 10d.withUnit[Meter].pow[1 / 2].assertQD[Double, Meter ^ (1 / 2)](3.1622776601683795) - 10d.withUnit[Meter].pow[-1 / 2].assertQD[Double, 1 / (Meter ^ (1 / 2))](0.31622776601683794) - - 10f.withUnit[Meter].pow[0].assertQ[Float, 1](1) - 10f.withUnit[Meter].pow[2].assertQ[Float, Meter ^ 2](100) - 10f.withUnit[Meter].pow[-1].assertQD[Float, 1 / Meter](0.1) - 10f.withUnit[Meter].pow[1 / 2].assertQD[Float, Meter ^ (1 / 2)](3.1622776601683795) - 10f.withUnit[Meter].pow[-1 / 2].assertQD[Float, 1 / (Meter ^ (1 / 2))](0.31622776601683794) - - 10L.withUnit[Meter].pow[0].assertQ[Long, 1](1) - 10L.withUnit[Meter].pow[2].assertQ[Long, Meter ^ 2](100) - 10L.withUnit[Meter].pow[-1].assertQ[Long, 1 / Meter](0) - 10L.withUnit[Meter].pow[1 / 2].assertQ[Long, Meter ^ (1 / 2)](3) - 10L.withUnit[Meter].pow[-1 / 2].assertQ[Long, 1 / (Meter ^ (1 / 2))](0) - - 10.withUnit[Meter].pow[0].assertQ[Int, 1](1) - 10.withUnit[Meter].pow[2].assertQ[Int, Meter ^ 2](100) - 10.withUnit[Meter].pow[-1].assertQ[Int, 1 / Meter](0) - 10.withUnit[Meter].pow[1 / 2].assertQ[Int, Meter ^ (1 / 2)](3) - 10.withUnit[Meter].pow[-1 / 2].assertQ[Int, 1 / (Meter ^ (1 / 2))](0) + 10d.withUnit[Meter].tpow[0].assertQ[Double, 1](1) + 10d.withUnit[Meter].tpow[2].assertQ[Double, Meter ^ 2](100) + 10d.withUnit[Meter].tpow[-1].assertQ[Double, 1 / Meter](0) + 10d.withUnit[Meter].tpow[1 / 2].assertQ[Double, Meter ^ (1 / 2)](3) + 10d.withUnit[Meter].tpow[-1 / 2].assertQ[Double, 1 / (Meter ^ (1 / 2))](0) + + 10f.withUnit[Meter].tpow[0].assertQ[Float, 1](1) + 10f.withUnit[Meter].tpow[2].assertQ[Float, Meter ^ 2](100) + 10f.withUnit[Meter].tpow[-1].assertQ[Float, 1 / Meter](0) + 10f.withUnit[Meter].tpow[1 / 2].assertQ[Float, Meter ^ (1 / 2)](3) + 10f.withUnit[Meter].tpow[-1 / 2].assertQ[Float, 1 / (Meter ^ (1 / 2))](0) + + 10L.withUnit[Meter].tpow[0].assertQ[Long, 1](1) + 10L.withUnit[Meter].tpow[2].assertQ[Long, Meter ^ 2](100) + 10L.withUnit[Meter].tpow[-1].assertQ[Long, 1 / Meter](0) + 10L.withUnit[Meter].tpow[1 / 2].assertQ[Long, Meter ^ (1 / 2)](3) + 10L.withUnit[Meter].tpow[-1 / 2].assertQ[Long, 1 / (Meter ^ (1 / 2))](0) + + 10.withUnit[Meter].tpow[0].assertQ[Int, 1](1) + 10.withUnit[Meter].tpow[2].assertQ[Int, Meter ^ 2](100) + 10.withUnit[Meter].tpow[-1].assertQ[Int, 1 / Meter](0) + 10.withUnit[Meter].tpow[1 / 2].assertQ[Int, Meter ^ (1 / 2)](3) + 10.withUnit[Meter].tpow[-1 / 2].assertQ[Int, 1 / (Meter ^ (1 / 2))](0) } test("constants in simplified unit types") { From 79993dd09eb4d3fcf080d65f219a8369904950e9 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Mon, 7 Mar 2022 13:51:21 -0700 Subject: [PATCH 08/17] remove algebra.scala --- .../scala/coulomb/ops/standard/algebra.scala | 57 ------------------- 1 file changed, 57 deletions(-) delete mode 100644 core/src/main/scala/coulomb/ops/standard/algebra.scala diff --git a/core/src/main/scala/coulomb/ops/standard/algebra.scala b/core/src/main/scala/coulomb/ops/standard/algebra.scala deleted file mode 100644 index bcb98edb1..000000000 --- a/core/src/main/scala/coulomb/ops/standard/algebra.scala +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2022 Erik Erlandson - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package coulomb.ops.standard - -import scala.util.NotGiven -import scala.math.{Fractional, Integral, Numeric} - -import coulomb.rational.Rational -import coulomb.policy.AllowTruncation - -/* -abstract class CanAdd[V]: - def add(vl: V, vr: V): V - -object CanAdd: - inline given ctx_CanAdd_Numeric[V](using num: Numeric[V]): CanAdd[V] = - new CanAdd[V]: - def add(vl: V, vr: V): V = num.plus(vl, vr) - -abstract class CanSub[V]: - def sub(vl: V, vr: V): V - -object CanSub: - inline given ctx_CanSub_Numeric[V](using num: Numeric[V]): CanSub[V] = - new CanSub[V]: - def sub(vl: V, vr: V): V = num.minus(vl, vr) - -abstract class CanMul[V]: - def mul(vl: V, vr: V): V - -object CanMul: - inline given ctx_CanMul_Numeric[V](using num: Numeric[V]): CanMul[V] = - new CanMul[V]: - def mul(vl: V, vr: V): V = num.times(vl, vr) - -abstract class CanNeg[V]: - def neg(v: V): V - -object CanNeg: - inline given ctx_CanNeg_Numeric[V](using num: Numeric[V]): CanNeg[V] = - new CanNeg[V]: - def neg(v: V): V = num.negate(v) -*/ From ec6d5765d8c7801315373c43d79202eb6740febc Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Mon, 7 Mar 2022 15:31:52 -0700 Subject: [PATCH 09/17] explicit truncating conversions --- .../scala/coulomb/conversion/conversion.scala | 6 ++ .../conversion/standard/standard.scala | 20 +++--- core/src/main/scala/coulomb/quantity.scala | 6 ++ core/src/test/scala/coulomb/quantity.scala | 71 +++++++++---------- 4 files changed, 53 insertions(+), 50 deletions(-) diff --git a/core/src/main/scala/coulomb/conversion/conversion.scala b/core/src/main/scala/coulomb/conversion/conversion.scala index cafbcbef9..56b5fc91f 100644 --- a/core/src/main/scala/coulomb/conversion/conversion.scala +++ b/core/src/main/scala/coulomb/conversion/conversion.scala @@ -22,6 +22,12 @@ import scala.annotation.implicitNotFound @implicitNotFound("No value conversion in scope for value types {VF} => {VT}") abstract class ValueConversion[VF, VT] extends (VF => VT) +@implicitNotFound("No truncating value conversion in scope for value types {VF} => {VT}") +abstract class TruncatingValueConversion[VF, VT] extends (VF => VT) + /** Convert a value of type V from implied units UF to UT */ @implicitNotFound("No unit conversion in scope for value type {V}, unit types {UF} => {UT}") abstract class UnitConversion[V, UF, UT] extends (V => V) + +@implicitNotFound("No truncating unit conversion in scope for value type {V}, unit types {UF} => {UT}") +abstract class TruncatingUnitConversion[V, UF, UT] extends (V => V) diff --git a/core/src/main/scala/coulomb/conversion/standard/standard.scala b/core/src/main/scala/coulomb/conversion/standard/standard.scala index 7ad8262f8..f10081038 100644 --- a/core/src/main/scala/coulomb/conversion/standard/standard.scala +++ b/core/src/main/scala/coulomb/conversion/standard/standard.scala @@ -16,7 +16,7 @@ package coulomb.conversion.standard -import coulomb.conversion.{ValueConversion, UnitConversion} +import coulomb.conversion.* import coulomb.{coefficient, withUnit, Quantity} import coulomb.policy.{AllowTruncation, AllowImplicitConversions} @@ -50,16 +50,16 @@ inline given ctx_VC_Long[VF](using num: Integral[VF]): ValueConversion[VF, Long] new ValueConversion[VF, Long]: def apply(v: VF): Long = num.toLong(v) -inline given ctx_VC_Long_tr[VF](using num: Fractional[VF], at: AllowTruncation): ValueConversion[VF, Long] = - new ValueConversion[VF, Long]: +inline given ctx_TVC_Long[VF](using num: Fractional[VF]): TruncatingValueConversion[VF, Long] = + new TruncatingValueConversion[VF, Long]: def apply(v: VF): Long = num.toLong(v) inline given ctx_VC_Int[VF](using num: Integral[VF]): ValueConversion[VF, Int] = new ValueConversion[VF, Int]: def apply(v: VF): Int = num.toInt(v) -inline given ctx_VC_Int_tr[VF](using num: Fractional[VF], at: AllowTruncation): ValueConversion[VF, Int] = - new ValueConversion[VF, Int]: +inline given ctx_TVC_Int_tr[VF](using num: Fractional[VF]): TruncatingValueConversion[VF, Int] = + new TruncatingValueConversion[VF, Int]: def apply(v: VF): Int = num.toInt(v) // unit conversions that discard fractional values can be imported from @@ -76,19 +76,17 @@ inline given ctx_UC_Float[UF, UT]: new UnitConversion[Float, UF, UT]: def apply(v: Float): Float = c * v -inline given ctx_UC_Long[UF, UT](using AllowTruncation): - UnitConversion[Long, UF, UT] = +inline given ctx_TUC_Long[UF, UT]: TruncatingUnitConversion[Long, UF, UT] = val nc = coulomb.conversion.infra.coefficientNumDouble[UF, UT] val dc = coulomb.conversion.infra.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 UnitConversion[Long, UF, UT]: + new TruncatingUnitConversion[Long, UF, UT]: def apply(v: Long): Long = ((nc * v) / dc).toLong -inline given ctx_UC_Int[UF, UT](using AllowTruncation): - UnitConversion[Int, UF, UT] = +inline given ctx_TUC_Int[UF, UT]: TruncatingUnitConversion[Int, UF, UT] = val nc = coulomb.conversion.infra.coefficientNumDouble[UF, UT] val dc = coulomb.conversion.infra.coefficientDenDouble[UF, UT] - new UnitConversion[Int, UF, UT]: + new TruncatingUnitConversion[Int, UF, UT]: def apply(v: Int): Int = ((nc * v) / dc).toInt diff --git a/core/src/main/scala/coulomb/quantity.scala b/core/src/main/scala/coulomb/quantity.scala index 33da3a30d..c51d19126 100644 --- a/core/src/main/scala/coulomb/quantity.scala +++ b/core/src/main/scala/coulomb/quantity.scala @@ -80,6 +80,7 @@ end quantity import coulomb.ops.* import coulomb.rational.Rational import coulomb.conversion.{ValueConversion, UnitConversion} +import coulomb.conversion.{TruncatingValueConversion, TruncatingUnitConversion} inline def showUnit[U]: String = ${ coulomb.infra.show.show[U] } inline def showUnitFull[U]: String = ${ coulomb.infra.show.showFull[U] } @@ -99,6 +100,11 @@ extension[VL, UL](ql: Quantity[VL, UL]) inline def toUnit[U](using conv: UnitConversion[VL, UL, U]): Quantity[VL, U] = conv(ql.value).withUnit[U] + inline def tToValue[V](using conv: TruncatingValueConversion[VL, V]): Quantity[V, UL] = + conv(ql.value).withUnit[UL] + inline def tToUnit[U](using conv: TruncatingUnitConversion[VL, UL, U]): Quantity[VL, U] = + conv(ql.value).withUnit[U] + inline def unary_-(using neg: Neg[VL, UL]): Quantity[VL, UL] = neg(ql.value).withUnit[UL] diff --git a/core/src/test/scala/coulomb/quantity.scala b/core/src/test/scala/coulomb/quantity.scala index edad1cea2..a494c72f8 100644 --- a/core/src/test/scala/coulomb/quantity.scala +++ b/core/src/test/scala/coulomb/quantity.scala @@ -60,7 +60,6 @@ class QuantitySuite extends CoulombSuite: 1L.withUnit[Meter].toValue[Float].assertQ[Float, Meter](1) 1L.withUnit[Meter].toValue[Double].assertQ[Double, Meter](1) - // fp to truncating types is an error without importing truncating rules assertCE("1f.withUnit[Meter].toValue[Int]") assertCE("1f.withUnit[Meter].toValue[Long]") 1f.withUnit[Meter].toValue[Float].assertQ[Float, Meter](1) @@ -73,14 +72,13 @@ class QuantitySuite extends CoulombSuite: } test("toValue standard truncating") { - import coulomb.policy.allowTruncation.given import coulomb.conversion.standard.given - 1.5f.withUnit[Meter].toValue[Int].assertQ[Int, Meter](1) - 0.999f.withUnit[Meter].toValue[Long].assertQ[Long, Meter](0) + 1.5f.withUnit[Meter].tToValue[Int].assertQ[Int, Meter](1) + 0.999f.withUnit[Meter].tToValue[Long].assertQ[Long, Meter](0) - 1.5d.withUnit[Meter].toValue[Int].assertQ[Int, Meter](1) - 0.999d.withUnit[Meter].toValue[Long].assertQ[Long, Meter](0) + 1.5d.withUnit[Meter].tToValue[Int].assertQ[Int, Meter](1) + 0.999d.withUnit[Meter].tToValue[Long].assertQ[Long, Meter](0) } test("toUnit standard") { @@ -88,17 +86,16 @@ class QuantitySuite extends CoulombSuite: 1.5f.withUnit[Minute].toUnit[Second].assertQ[Float, Second](90) 1d.withUnit[Meter].toUnit[Yard].assertQD[Double, Yard](1.0936132983377078) - // conversions on truncating types require importing truncating conversion rules + assertCE("1.withUnit[Minute].toUnit[Second]") assertCE("1L.withUnit[Yard].toUnit[Meter]") } test("toUnit standard truncating") { - import coulomb.policy.allowTruncation.given import coulomb.conversion.standard.given - 1.withUnit[Minute].toUnit[Second].assertQ[Int, Second](60) - 1L.withUnit[Yard].toUnit[Meter].assertQ[Long, Meter](0) + 1.withUnit[Minute].tToUnit[Second].assertQ[Int, Second](60) + 1L.withUnit[Yard].tToUnit[Meter].assertQ[Long, Meter](0) } test("implicit conversions") { @@ -197,26 +194,25 @@ class QuantitySuite extends CoulombSuite: } test("addition standard truncating") { - import coulomb.policy.allowTruncation.given import coulomb.policy.allowImplicitConversions.given import coulomb.ops.standard.given import coulomb.ops.resolution.standard.given import coulomb.conversion.standard.given - (1L.withUnit[Second] + 1L.withUnit[Minute]).assertQ[Long, Second](61) - (1L.withUnit[Second] + 1.withUnit[Minute]).assertQ[Long, Second](61) - (1.withUnit[Second] + 1L.withUnit[Minute]).assertQ[Long, Second](61) - (1.withUnit[Second] + 1.withUnit[Minute]).assertQ[Int, Second](61) + (1L.withUnit[Second] + 1L.withUnit[Minute].tToUnit[Second]).assertQ[Long, Second](61) + (1L.withUnit[Second] + 1.withUnit[Minute].tToUnit[Second]).assertQ[Long, Second](61) + (1.withUnit[Second] + 1L.withUnit[Minute].tToUnit[Second]).assertQ[Long, Second](61) + (1.withUnit[Second] + 1.withUnit[Minute].tToUnit[Second]).assertQ[Int, Second](61) // integer truncation can cause loss of precision - (1L.withUnit[Meter] + 1L.withUnit[Yard]).assertQ[Long, Meter](1) - (1L.withUnit[Meter] + 1.withUnit[Yard]).assertQ[Long, Meter](1) - (1.withUnit[Meter] + 1L.withUnit[Yard]).assertQ[Long, Meter](1) - (1.withUnit[Meter] + 1.withUnit[Yard]).assertQ[Int, Meter](1) + (1L.withUnit[Meter] + 1L.withUnit[Yard].tToUnit[Meter]).assertQ[Long, Meter](1) + (1L.withUnit[Meter] + 1.withUnit[Yard].tToUnit[Meter]).assertQ[Long, Meter](1) + (1.withUnit[Meter] + 1L.withUnit[Yard].tToUnit[Meter]).assertQ[Long, Meter](1) + (1.withUnit[Meter] + 1.withUnit[Yard].tToUnit[Meter]).assertQ[Int, Meter](1) // fp effects (e.g. (3 * 0.3333).toInt -> 0) are avoided when possible - (1.withUnit[Yard] + 3.withUnit[Foot]).assertQ[Int, Yard](2) - (1L.withUnit[Yard] + 3L.withUnit[Foot]).assertQ[Long, Yard](2) + (1.withUnit[Yard] + 3.withUnit[Foot].tToUnit[Yard]).assertQ[Int, Yard](2) + (1L.withUnit[Yard] + 3L.withUnit[Foot].tToUnit[Yard]).assertQ[Long, Yard](2) } test("subtraction standard strict") { @@ -294,26 +290,25 @@ class QuantitySuite extends CoulombSuite: } test("subtraction standard truncating") { - import coulomb.policy.allowTruncation.given import coulomb.policy.allowImplicitConversions.given import coulomb.ops.standard.given import coulomb.ops.resolution.standard.given import coulomb.conversion.standard.given - (61L.withUnit[Second] - 1L.withUnit[Minute]).assertQ[Long, Second](1) - (61L.withUnit[Second] - 1.withUnit[Minute]).assertQ[Long, Second](1) - (61.withUnit[Second] - 1L.withUnit[Minute]).assertQ[Long, Second](1) - (61.withUnit[Second] - 1.withUnit[Minute]).assertQ[Int, Second](1) + (61L.withUnit[Second] - 1L.withUnit[Minute].tToUnit[Second]).assertQ[Long, Second](1) + (61L.withUnit[Second] - 1.withUnit[Minute].tToUnit[Second]).assertQ[Long, Second](1) + (61.withUnit[Second] - 1L.withUnit[Minute].tToUnit[Second]).assertQ[Long, Second](1) + (61.withUnit[Second] - 1.withUnit[Minute].tToUnit[Second]).assertQ[Int, Second](1) // integer truncation can cause loss of precision - (2L.withUnit[Meter] - 1L.withUnit[Yard]).assertQ[Long, Meter](2) - (2L.withUnit[Meter] - 1.withUnit[Yard]).assertQ[Long, Meter](2) - (2.withUnit[Meter] - 1L.withUnit[Yard]).assertQ[Long, Meter](2) - (2.withUnit[Meter] - 1.withUnit[Yard]).assertQ[Int, Meter](2) + (2L.withUnit[Meter] - 1L.withUnit[Yard].tToUnit[Meter]).assertQ[Long, Meter](2) + (2L.withUnit[Meter] - 1.withUnit[Yard].tToUnit[Meter]).assertQ[Long, Meter](2) + (2.withUnit[Meter] - 1L.withUnit[Yard].tToUnit[Meter]).assertQ[Long, Meter](2) + (2.withUnit[Meter] - 1.withUnit[Yard].tToUnit[Meter]).assertQ[Int, Meter](2) // fp effects (e.g. (3 * 0.3333).toInt -> 0) are avoided when possible - (2.withUnit[Yard] - 3.withUnit[Foot]).assertQ[Int, Yard](1) - (2L.withUnit[Yard] - 3L.withUnit[Foot]).assertQ[Long, Yard](1) + (2.withUnit[Yard] - 3.withUnit[Foot].tToUnit[Yard]).assertQ[Int, Yard](1) + (2L.withUnit[Yard] - 3L.withUnit[Foot].tToUnit[Yard]).assertQ[Long, Yard](1) } test("multiplication standard strict") { @@ -411,7 +406,6 @@ class QuantitySuite extends CoulombSuite: } test("truncating division standard") { - //import coulomb.policy.allowTruncation.given import coulomb.policy.allowImplicitConversions.given import coulomb.ops.standard.given import coulomb.ops.resolution.standard.given @@ -444,7 +438,7 @@ class QuantitySuite extends CoulombSuite: 2f.withUnit[Meter].pow[1 / 2].assertQD[Float, Meter ^ (1 / 2)](1.4142135623730951) 2f.withUnit[Meter].pow[-1 / 2].assertQD[Float, 1 / (Meter ^ (1 / 2))](0.7071067811865476) - // integer types are supported via multiplicative semigroup + // positive integer exponents are supported via multiplicative semigroup assertCE("2L.withUnit[Meter].pow[0]") 2L.withUnit[Meter].pow[2].assertQ[Long, Meter ^ 2](4) assertCE("2L.withUnit[Meter].pow[-1]") @@ -561,7 +555,6 @@ class QuantitySuite extends CoulombSuite: } test("equality standard truncating") { - import coulomb.policy.allowTruncation.given import coulomb.policy.allowImplicitConversions.given import coulomb.ops.standard.given import coulomb.ops.resolution.standard.given @@ -569,11 +562,11 @@ class QuantitySuite extends CoulombSuite: assertEquals(2L.withUnit[(1 / 2) * Meter] === 1d.withUnit[Meter], true) assertEquals(2L.withUnit[(1 / 2) * Meter] === 2f.withUnit[Meter], false) - assertEquals(2L.withUnit[(1 / 2) * Meter] === 1L.withUnit[Meter], true) - assertEquals(2L.withUnit[(1 / 2) * Meter] === 2.withUnit[Meter], false) + assertEquals(2L.withUnit[(1 / 2) * Meter].tToUnit[Meter] === 1L.withUnit[Meter], true) + assertEquals(2L.withUnit[(1 / 2) * Meter].tToUnit[Meter] === 2.withUnit[Meter], false) assertEquals(1.withUnit[(1 / 2) * Meter] === 1d.withUnit[Meter], false) assertEquals(2.withUnit[(1 / 2) * Meter] === 1f.withUnit[Meter], true) - assertEquals(1.withUnit[(1 / 2) * Meter] === 1L.withUnit[Meter], false) - assertEquals(2.withUnit[(1 / 2) * Meter] === 1.withUnit[Meter], true) + assertEquals(1.withUnit[(1 / 2) * Meter].tToUnit[Meter] === 1L.withUnit[Meter], false) + assertEquals(2.withUnit[(1 / 2) * Meter].tToUnit[Meter] === 1.withUnit[Meter], true) } From 540299eddaff610a4396ab030f1e08fc70541af5 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Thu, 10 Mar 2022 16:23:30 -0700 Subject: [PATCH 10/17] populate some coulomb.ops.algebra instances --- .../scala/coulomb/ops/algebra/algebra.scala | 35 +++++++++++++++++ .../main/scala/coulomb/ops/algebra/all.scala | 23 +++++++++++ .../scala/coulomb/ops/algebra/double.scala | 39 +++++++++++++++++++ .../scala/coulomb/ops/algebra/float.scala | 39 +++++++++++++++++++ .../main/scala/coulomb/ops/algebra/int.scala | 37 ++++++++++++++++++ .../main/scala/coulomb/ops/algebra/long.scala | 36 +++++++++++++++++ 6 files changed, 209 insertions(+) create mode 100644 core/src/main/scala/coulomb/ops/algebra/algebra.scala create mode 100644 core/src/main/scala/coulomb/ops/algebra/all.scala create mode 100644 core/src/main/scala/coulomb/ops/algebra/double.scala create mode 100644 core/src/main/scala/coulomb/ops/algebra/float.scala create mode 100644 core/src/main/scala/coulomb/ops/algebra/int.scala create mode 100644 core/src/main/scala/coulomb/ops/algebra/long.scala diff --git a/core/src/main/scala/coulomb/ops/algebra/algebra.scala b/core/src/main/scala/coulomb/ops/algebra/algebra.scala new file mode 100644 index 000000000..9bc947afe --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/algebra.scala @@ -0,0 +1,35 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.algebra + +import scala.annotation.implicitNotFound + +// there is no typelevel community typeclass that expresses the concept +// "supports raising to fractional powers, without truncation" +// The closest thing is spire NRoot, but it is also defined on truncating integer types, +// so it is not helpful for distinguishing "pow" from "tpow", and in any case requires spire +// https://github.com/typelevel/spire/issues/741 + +@implicitNotFound("Fractional power not defined for value type ${V}") +abstract class FractionalPower[V]: + /** returns v^e */ + def pow(v: V, e: Double): V + +@implicitNotFound("Truncating power not defined for value type ${V}") +abstract class TruncatingPower[V]: + /** returns v^e, truncated to integer value (toward zero) */ + def tpow(v: V, e: Double): V diff --git a/core/src/main/scala/coulomb/ops/algebra/all.scala b/core/src/main/scala/coulomb/ops/algebra/all.scala new file mode 100644 index 000000000..28ae95903 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/all.scala @@ -0,0 +1,23 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.algebra + +object all + extends IntInstances + with LongInstances + with FloatInstances + with DoubleInstances diff --git a/core/src/main/scala/coulomb/ops/algebra/double.scala b/core/src/main/scala/coulomb/ops/algebra/double.scala new file mode 100644 index 000000000..41e4be983 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/double.scala @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.algebra + +import algebra.ring.TruncatedDivision + +object double extends DoubleInstances + +trait DoubleInstances: + given ctx_Double_is_FractionalPower: FractionalPower[Double] with + def pow(v: Double, e: Double): Double = math.pow(v, e) + + given ctx_Double_is_TruncatingPower: TruncatingPower[Double] with + def tpow(v: Double, e: Double): Double = math.pow(v, e).toLong.toDouble + + given ctx_Double_is_TruncatedDivision: TruncatedDivision[Double] with + def tquot(x: Double, y: Double): Double = (x / y).toLong.toDouble + // I don't care about these + def tmod(x: Double, y: Double): Double = ??? + def fquot(x: Double, y: Double): Double = ??? + def fmod(x: Double, y: Double): Double = ??? + def abs(a: Double): Double = ??? + def additiveCommutativeMonoid: algebra.ring.AdditiveCommutativeMonoid[Double] = ??? + def order: cats.kernel.Order[Double] = ??? + def signum(a: Double): Int = ??? diff --git a/core/src/main/scala/coulomb/ops/algebra/float.scala b/core/src/main/scala/coulomb/ops/algebra/float.scala new file mode 100644 index 000000000..1a927c491 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/float.scala @@ -0,0 +1,39 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.algebra + +import algebra.ring.TruncatedDivision + +object float extends FloatInstances + +trait FloatInstances: + given ctx_Float_is_FractionalPower: FractionalPower[Float] with + def pow(v: Float, e: Double): Float = math.pow(v.toDouble, e).toFloat + + given ctx_Float_is_TruncatingPower: TruncatingPower[Float] with + def tpow(v: Float, e: Double): Float = math.pow(v.toDouble, e).toLong.toFloat + + given ctx_Float_is_TruncatedDivision: TruncatedDivision[Float] with + def tquot(x: Float, y: Float): Float = (x / y).toLong.toFloat + // I don't care about these + def tmod(x: Float, y: Float): Float = ??? + def fquot(x: Float, y: Float): Float = ??? + def fmod(x: Float, y: Float): Float = ??? + def abs(a: Float): Float = ??? + def additiveCommutativeMonoid: algebra.ring.AdditiveCommutativeMonoid[Float] = ??? + def order: cats.kernel.Order[Float] = ??? + def signum(a: Float): Int = ??? diff --git a/core/src/main/scala/coulomb/ops/algebra/int.scala b/core/src/main/scala/coulomb/ops/algebra/int.scala new file mode 100644 index 000000000..9e06fc198 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/int.scala @@ -0,0 +1,37 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.algebra + +import algebra.ring.TruncatedDivision + +object int extends IntInstances + +trait IntInstances: + given ctx_Int_is_TruncatingPower: TruncatingPower[Int] with + def tpow(v: Int, e: Double): Int = math.pow(v.toDouble, e).toInt + + given ctx_Int_is_TruncatedDivision: TruncatedDivision[Int] with + def tquot(x: Int, y: Int): Int = x / y + // I don't care about these + def tmod(x: Int, y: Int): Int = ??? + def fquot(x: Int, y: Int): Int = ??? + def fmod(x: Int, y: Int): Int = ??? + def abs(a: Int): Int = ??? + def additiveCommutativeMonoid: algebra.ring.AdditiveCommutativeMonoid[Int] = ??? + def order: cats.kernel.Order[Int] = ??? + def signum(a: Int): Int = ??? + diff --git a/core/src/main/scala/coulomb/ops/algebra/long.scala b/core/src/main/scala/coulomb/ops/algebra/long.scala new file mode 100644 index 000000000..ebddd6ba9 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/long.scala @@ -0,0 +1,36 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.algebra + +import algebra.ring.TruncatedDivision + +object long extends LongInstances + +trait LongInstances: + given ctx_Long_is_TruncatingPower: TruncatingPower[Long] with + def tpow(v: Long, e: Double): Long = math.pow(v.toDouble, e).toLong + + given ctx_Long_is_TruncatedDivision: TruncatedDivision[Long] with + def tquot(x: Long, y: Long): Long = x / y + // I don't care about these + def tmod(x: Long, y: Long): Long = ??? + def fquot(x: Long, y: Long): Long = ??? + def fmod(x: Long, y: Long): Long = ??? + def abs(a: Long): Long = ??? + def additiveCommutativeMonoid: algebra.ring.AdditiveCommutativeMonoid[Long] = ??? + def order: cats.kernel.Order[Long] = ??? + def signum(a: Long): Int = ??? From b30eae2049f695dcdca3b9e38b01d0bb454c8b51 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Thu, 10 Mar 2022 16:40:27 -0700 Subject: [PATCH 11/17] refactor use of algebras --- .../main/scala/coulomb/ops/standard/pow.scala | 78 ++++++++----------- .../scala/coulomb/ops/standard/tpow.scala | 40 ++-------- .../scala/coulomb/ops/standard/tquot.scala | 25 +----- core/src/test/scala/coulomb/quantity.scala | 3 +- 4 files changed, 40 insertions(+), 106 deletions(-) diff --git a/core/src/main/scala/coulomb/ops/standard/pow.scala b/core/src/main/scala/coulomb/ops/standard/pow.scala index d23638602..bb37bb385 100644 --- a/core/src/main/scala/coulomb/ops/standard/pow.scala +++ b/core/src/main/scala/coulomb/ops/standard/pow.scala @@ -18,59 +18,45 @@ package coulomb.ops.standard import scala.util.NotGiven +import algebra.ring.MultiplicativeSemigroup +import algebra.ring.MultiplicativeGroup + import coulomb.`^` import coulomb.ops.{Pow, SimplifiedUnit} +import coulomb.rational.typeexpr +import coulomb.ops.algebra.FractionalPower -transparent inline given ctx_quantity_pow[V, U, E](using - alg: CanPow[V, E], +transparent inline given ctx_pow_FractionalPower[V, U, E](using + alg: FractionalPower[V], su: SimplifiedUnit[U ^ E] ): Pow[V, U, E] = + val e = typeexpr.double[E] new Pow[V, U, E]: type VO = V type UO = su.UO - def apply(v: V): VO = alg.pow(v) - -abstract class CanPow[V, E]: - def pow(v: V): V - -object CanPow: - import coulomb.rational.typeexpr - import algebra.ring.MultiplicativeSemigroup - import algebra.ring.MultiplicativeGroup - - inline given ctx_FracPow_CanPow[V, E](using fp: FracPow[V]): CanPow[V, E] = - val e: Double = typeexpr.double[E] - new CanPow[V, E]: - def pow(v: V): V = fp.pow(v, e) - - inline given ctx_MultiplicativeGroup_CanPow[V, E](using - nfp: NotGiven[FracPow[V]], - mgp: MultiplicativeGroup[V], - aie: typeexpr.AllInt[E]): CanPow[V, E] = - val e: Int = aie.value - new CanPow[V, E]: - def pow(v: V): V = mgp.pow(v, e) + def apply(v: V): VO = alg.pow(v, e) - inline given ctx_MultiplicativeSemigroup_CanPow[V, E](using - nfp: NotGiven[FracPow[V]], - nmg: NotGiven[MultiplicativeGroup[V]], - msg: MultiplicativeSemigroup[V], - pie: typeexpr.PosInt[E]): CanPow[V, E] = - val e: Int = pie.value - new CanPow[V, E]: - def pow(v: V): V = msg.pow(v, e) - - // there is no typelevel community typeclass that expresses the concept - // "supports raising to fractional powers without truncation" - // The closest thing is spire NRoot, but it is also defined on truncating integer types, - // so it is not helpful for distinguishing "pow" from "tpow", and in any case requires spire - // https://github.com/typelevel/spire/issues/741 - abstract class FracPow[V]: - def pow(v: V, e: Double): V - - object FracPow: - given ctx_Double_is_FracPow: FracPow[Double] with - def pow(v: Double, e: Double): Double = math.pow(v, e) +transparent inline given ctx_pow_MultiplicativeGroup[V, U, E](using + nfp: NotGiven[FractionalPower[V]], + alg: MultiplicativeGroup[V], + aie: typeexpr.AllInt[E], + su: SimplifiedUnit[U ^ E] + ): Pow[V, U, E] = + val e = aie.value + new Pow[V, U, E]: + type VO = V + type UO = su.UO + def apply(v: V): VO = alg.pow(v, e) - given ctx_Float_is_FracPow: FracPow[Float] with - def pow(v: Float, e: Double): Float = math.pow(v, e).toFloat +transparent inline given ctx_pow_MultiplicativeSemigroup[V, U, E](using + nfp: NotGiven[FractionalPower[V]], + nmg: NotGiven[MultiplicativeGroup[V]], + alg: MultiplicativeSemigroup[V], + pie: typeexpr.PosInt[E], + su: SimplifiedUnit[U ^ E] + ): Pow[V, U, E] = + val e = pie.value + new Pow[V, U, E]: + type VO = V + type UO = su.UO + def apply(v: V): VO = alg.pow(v, e) diff --git a/core/src/main/scala/coulomb/ops/standard/tpow.scala b/core/src/main/scala/coulomb/ops/standard/tpow.scala index ef3349f78..c0a4017ff 100644 --- a/core/src/main/scala/coulomb/ops/standard/tpow.scala +++ b/core/src/main/scala/coulomb/ops/standard/tpow.scala @@ -20,45 +20,15 @@ import scala.util.NotGiven import coulomb.`^` import coulomb.ops.{TPow, SimplifiedUnit} +import coulomb.ops.algebra.TruncatingPower +import coulomb.rational.typeexpr transparent inline given ctx_quantity_tpow[V, U, E](using - alg: CanTPow[V, E], + alg: TruncatingPower[V], su: SimplifiedUnit[U ^ E] ): TPow[V, U, E] = + val e = typeexpr.double[E] new TPow[V, U, E]: type VO = V type UO = su.UO - def apply(v: V): VO = alg.tpow(v) - -abstract class CanTPow[V, E]: - def tpow(v: V): V - -object CanTPow: - // there is no typelevel community typeclass that expresses the concept - // "supports raising to fractional powers with truncation" - // The closest thing is spire NRoot, but it does not formally distinguish between - // "pow" and "tpow" or which types support which operation, - // so it is not helpful for distinguishing "pow" from "tpow", and in any case requires spire - // https://github.com/typelevel/spire/issues/741 - - import coulomb.rational.typeexpr - - inline given ctx_Double_CanTPow[E]: CanTPow[Double, E] = - val e: Double = typeexpr.double[E] - new CanTPow[Double, E]: - def tpow(v: Double): Double = math.pow(v, e).toLong.toDouble - - inline given ctx_Float_CanTPow[E]: CanTPow[Float, E] = - val e: Double = typeexpr.double[E] - new CanTPow[Float, E]: - def tpow(v: Float): Float = math.pow(v.toDouble, e).toLong.toFloat - - inline given ctx_Long_CanTPow[E]: CanTPow[Long, E] = - val e: Double = typeexpr.double[E] - new CanTPow[Long, E]: - def tpow(v: Long): Long = math.pow(v.toDouble, e).toLong - - inline given ctx_Int_CanTPow[E]: CanTPow[Int, E] = - val e: Double = typeexpr.double[E] - new CanTPow[Int, E]: - def tpow(v: Int): Int = math.pow(v.toDouble, e).toInt + def apply(v: V): VO = alg.tpow(v, e) diff --git a/core/src/main/scala/coulomb/ops/standard/tquot.scala b/core/src/main/scala/coulomb/ops/standard/tquot.scala index 4609c2d5a..e20fea7fd 100644 --- a/core/src/main/scala/coulomb/ops/standard/tquot.scala +++ b/core/src/main/scala/coulomb/ops/standard/tquot.scala @@ -29,7 +29,7 @@ import coulomb.policy.AllowTruncation transparent inline given ctx_tquot_1V2U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, - alg: CanTQuot[VL], + alg: TruncatedDivision[VL], su: SimplifiedUnit[UL / UR] ): TQuot[VL, UL, VR, UR] = new TQuot[VL, UL, VR, UR]: @@ -43,31 +43,10 @@ transparent inline given ctx_tquot_2V2U[VL, UL, VR, UR](using vres: ValueResolution[VL, VR], vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], - alg: CanTQuot[vres.VO], + alg: TruncatedDivision[vres.VO], su: SimplifiedUnit[UL / UR] ): TQuot[VL, UL, VR, UR] = new TQuot[VL, UL, VR, UR]: type VO = vres.VO type UO = su.UO def apply(vl: VL, vr: VR): VO = alg.tquot(vlvo(vl), vrvo(vr)) - -// if algebra ever implements TruncatedDivision for Int and Long I can get rid of this -abstract class CanTQuot[V]: - def tquot(vl: V, vr: V): V - -object CanTQuot: - inline given ctx_TruncatedDivision_CanTQuot[V](using td: TruncatedDivision[V]): CanTQuot[V] = - new CanTQuot[V]: - def tquot(vl: V, vr: V): V = td.tquot(vl, vr) - - inline given ctx_Integral_CanTQuot[V](using - ntd: NotGiven[TruncatedDivision[V]], - num: scala.math.Integral[V]): CanTQuot[V] = - new CanTQuot[V]: - def tquot(vl: V, vr: V): V = num.quot(vl, vr) - - inline given ctx_Fractional_CanTQuot[V](using - ntd: NotGiven[TruncatedDivision[V]], - num: scala.math.Fractional[V]): CanTQuot[V] = - new CanTQuot[V]: - def tquot(vl: V, vr: V): V = num.fromInt(num.toInt(num.div(vl, vr))) diff --git a/core/src/test/scala/coulomb/quantity.scala b/core/src/test/scala/coulomb/quantity.scala index a494c72f8..70d840eff 100644 --- a/core/src/test/scala/coulomb/quantity.scala +++ b/core/src/test/scala/coulomb/quantity.scala @@ -20,8 +20,7 @@ class QuantitySuite extends CoulombSuite: import coulomb.* import coulomb.testing.units.{*, given} import algebra.instances.all.given - import algebra.ring.TruncatedDivision - import algebra.ring.TruncatedDivision.given + import coulomb.ops.algebra.all.given test("lift via Quantity") { Quantity[Meter](3.14).assertQ[Double, Meter](3.14) From a72a408e1bffbedf7a4ea40b8611aa8a001eab2d Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Thu, 10 Mar 2022 16:56:15 -0700 Subject: [PATCH 12/17] scala.math.Ordering -> cats.kernel.Order --- core/src/main/scala/coulomb/ops/standard/ord.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/main/scala/coulomb/ops/standard/ord.scala b/core/src/main/scala/coulomb/ops/standard/ord.scala index 42885eeba..6c4490d2c 100644 --- a/core/src/main/scala/coulomb/ops/standard/ord.scala +++ b/core/src/main/scala/coulomb/ops/standard/ord.scala @@ -17,7 +17,8 @@ package coulomb.ops.standard import scala.util.NotGiven -import scala.math.Ordering + +import cats.kernel.Order import coulomb.ops.{Ord, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} @@ -27,7 +28,7 @@ transparent inline given ctx_ord_1V1U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, equ: UR =:= UL, - ord: Ordering[VL] + ord: Order[VL] ): Ord[VL, UL, VR, UR] = new Ord[VL, UL, VR, UR]: def apply(vl: VL, vr: VR): Int = ord.compare(vl, eqv(vr)) @@ -37,7 +38,7 @@ transparent inline given ctx_ord_1V2U[VL, UL, VR, UR](using eqv: VR =:= VL, neu: NotGiven[UR =:= UL], ucv: UnitConversion[VL, UR, UL], - ord: Ordering[VL] + ord: Order[VL] ): Ord[VL, UL, VR, UR] = new Ord[VL, UL, VR, UR]: def apply(vl: VL, vr: VR): Int = ord.compare(vl, ucv(eqv(vr))) @@ -49,7 +50,7 @@ transparent inline given ctx_ord_2V1U[VL, UL, VR, UR](using vres: ValueResolution[VL, VR], vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], - ord: Ordering[vres.VO] + ord: Order[vres.VO] ): Ord[VL, UL, VR, UR] = new Ord[VL, UL, VR, UR]: def apply(vl: VL, vr: VR): Int = ord.compare(vlvo(vl), vrvo(vr)) @@ -62,7 +63,7 @@ transparent inline given ctx_ord_2V2U[VL, UL, VR, UR](using vlvo: ValueConversion[VL, vres.VO], vrvo: ValueConversion[VR, vres.VO], ucvo: UnitConversion[vres.VO, UR, UL], - ord: Ordering[vres.VO] + ord: Order[vres.VO] ): Ord[VL, UL, VR, UR] = new Ord[VL, UL, VR, UR]: def apply(vl: VL, vr: VR): Int = ord.compare(vlvo(vl), ucvo(vrvo(vr))) From 1ee822fada9cd73c40c264fbe78286c3eb430af5 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Fri, 11 Mar 2022 16:33:17 -0700 Subject: [PATCH 13/17] refactor si, si.prefixes, mks, mksa to use export --- .../main/scala/coulomb/units/mks/mks.scala | 42 +++++--- units/src/main/scala/coulomb/units/mksa.scala | 50 ++++++++++ .../coulomb/units/si/prefixes/prefixes.scala | 80 --------------- .../src/main/scala/coulomb/units/si/si.scala | 97 ++++++++++++++++--- 4 files changed, 157 insertions(+), 112 deletions(-) create mode 100644 units/src/main/scala/coulomb/units/mksa.scala delete mode 100644 units/src/main/scala/coulomb/units/si/prefixes/prefixes.scala diff --git a/units/src/main/scala/coulomb/units/mks/mks.scala b/units/src/main/scala/coulomb/units/mks/mks.scala index 17850de0c..3a7ca0867 100644 --- a/units/src/main/scala/coulomb/units/mks/mks.scala +++ b/units/src/main/scala/coulomb/units/mks/mks.scala @@ -14,27 +14,37 @@ * limitations under the License. */ -package coulomb.units.mks +package coulomb.units -import coulomb.define.* -import coulomb.{`*`, `/`, `^`} +object mks: + import coulomb.define.* + import coulomb.{`*`, `/`, `^`} + import coulomb.units.si.* -import coulomb.units.si.* + // exports are safe to "union" from multiple imports + export coulomb.units.si.{ + Meter, ctx_unit_Meter, + Kilogram, ctx_unit_Kilogram, + Second, ctx_unit_Second + } -final type Radian -given ctx_unit_Radian: DerivedUnit[Radian, 1, 1, "radian", "rad"] with {} + final type Radian + given ctx_unit_Radian: DerivedUnit[Radian, 1, 1, "radian", "rad"] with {} -final type Steradian -given ctx_unit_Steradian: DerivedUnit[Steradian, 1, 1, "steradian", "sr"] with {} + final type Steradian + given ctx_unit_Steradian: DerivedUnit[Steradian, 1, 1, "steradian", "sr"] with {} -final type Hertz -given ctx_unit_Hertz: DerivedUnit[Hertz, 1 / Second, 1, "hertz", "Hz"] with {} + final type Hertz + given ctx_unit_Hertz: DerivedUnit[Hertz, 1 / Second, 1, "hertz", "Hz"] with {} -final type Newton -given ctx_unit_Newton: DerivedUnit[Newton, Kilogram * Meter / (Second ^ 2), 1, "newton", "N"] with {} + final type Newton + given ctx_unit_Newton: DerivedUnit[Newton, Kilogram * Meter / (Second ^ 2), 1, "newton", "N"] with {} -final type Joule -given ctx_unit_Joule: DerivedUnit[Joule, Newton * Meter, 1, "joule", "J"] with {} + final type Joule + given ctx_unit_Joule: DerivedUnit[Joule, Newton * Meter, 1, "joule", "J"] with {} -final type Watt -given ctx_unit_Watt: DerivedUnit[Watt, Joule / Second, 1, "watt", "W"] with {} + final type Watt + given ctx_unit_Watt: DerivedUnit[Watt, Joule / Second, 1, "watt", "W"] with {} + + final type Pascal + given ctx_unit_Pascal: DerivedUnit[Pascal, Newton / (Meter ^ 2), 1, "pascal", "Pa"] with {} diff --git a/units/src/main/scala/coulomb/units/mksa.scala b/units/src/main/scala/coulomb/units/mksa.scala new file mode 100644 index 000000000..5b8bb3dc2 --- /dev/null +++ b/units/src/main/scala/coulomb/units/mksa.scala @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.units + +object mksa: + import coulomb.define.* + import coulomb.{`*`, `/`, `^`} + import coulomb.units.si.* + import coulomb.units.mks.* + + export coulomb.units.si.{Ampere, ctx_unit_Ampere} + export coulomb.units.mks.{*, given} + + final type Coulomb + given ctx_unit_Coulomb: DerivedUnit[Coulomb, Ampere * Second, 1, "coulomb", "C"] with {} + + final type Volt + given ctx_unit_Volt: DerivedUnit[Volt, Watt / Ampere, 1, "volt", "V"] with {} + + final type Ohm + given ctx_unit_Ohm: DerivedUnit[Ohm, Volt / Ampere, 1, "ohm", "Ω"] with {} + + final type Farad + given ctx_unit_Farad: DerivedUnit[Farad, Coulomb / Volt, 1, "farad", "F"] with {} + + final type Siemens + given ctx_unit_Siemens: DerivedUnit[Siemens, 1 / Ohm, 1, "siemens", "S"] with {} + + final type Weber + given ctx_unit_Weber: DerivedUnit[Weber, Volt * Second, 1, "weber", "Wb"] with {} + + final type Tesla + given ctx_unit_Tesla: DerivedUnit[Tesla, Weber / (Meter ^ 2), 1, "tesla", "T"] with {} + + final type Henry + given ctx_unit_Henry: DerivedUnit[Henry, Weber / Ampere, 1, "henry", "H"] with {} diff --git a/units/src/main/scala/coulomb/units/si/prefixes/prefixes.scala b/units/src/main/scala/coulomb/units/si/prefixes/prefixes.scala deleted file mode 100644 index 4cf61b13b..000000000 --- a/units/src/main/scala/coulomb/units/si/prefixes/prefixes.scala +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright 2022 Erik Erlandson - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package coulomb.units.si.prefixes - -import coulomb.define.* -import coulomb.{`/`, `^`} - -final type Deca -given ctx_unit_Deca: PrefixUnit[Deca, 10, "deca", "da"] with {} - -final type Hecto -given ctx_unit_Hecto: PrefixUnit[Hecto, 100, "hecto", "h"] with {} - -final type Kilo -given ctx_unit_Kilo: PrefixUnit[Kilo, 10 ^ 3, "kilo", "k"] with {} - -final type Mega -given ctx_unit_Mega: PrefixUnit[Mega, 10 ^ 6, "mega", "M"] with {} - -final type Giga -given ctx_unit_Giga: PrefixUnit[Giga, 10 ^ 9, "giga", "G"] with {} - -final type Tera -given ctx_unit_Tera: PrefixUnit[Tera, 10 ^ 12, "tera", "T"] with {} - -final type Peta -given ctx_unit_Peta: PrefixUnit[Peta, 10 ^ 15, "peta", "P"] with {} - -final type Exa -given ctx_unit_Exa: PrefixUnit[Exa, 10 ^ 18, "exa", "E"] with {} - -final type Zetta -given ctx_unit_Zetta: PrefixUnit[Zetta, 10 ^ 21, "zetta", "Z"] with {} - -final type Yotta -given ctx_unit_Yotta: PrefixUnit[Yotta, 10 ^ 24, "yotta", "Y"] with {} - -final type Deci -given ctx_unit_Deci: PrefixUnit[Deci, 1 / 10, "deci", "d"] with {} - -final type Centi -given ctx_unit_Centi: PrefixUnit[Centi, 1 / 100, "centi", "c"] with {} - -final type Milli -given ctx_unit_Milli: PrefixUnit[Milli, 10 ^ -3, "milli", "m"] with {} - -final type Micro -given ctx_unit_Micro: PrefixUnit[Micro, 10 ^ -6, "micro", "μ"] with {} - -final type Nano -given ctx_unit_Nano: PrefixUnit[Nano, 10 ^ -9, "nano", "n"] with {} - -final type Pico -given ctx_unit_Pico: PrefixUnit[Pico, 10 ^ -12, "pico", "p"] with {} - -final type Femto -given ctx_unit_Femto: PrefixUnit[Femto, 10 ^ -15, "femto", "f"] with {} - -final type Atto -given ctx_unit_Atto: PrefixUnit[Atto, 10 ^ -18, "atto", "a"] with {} - -final type Zepto -given ctx_unit_Zepto: PrefixUnit[Zepto, 10 ^ -21, "zepto", "z"] with {} - -final type Yocto -given ctx_unit_Yocto: PrefixUnit[Yocto, 10 ^ -24, "yocto", "y"] with {} diff --git a/units/src/main/scala/coulomb/units/si/si.scala b/units/src/main/scala/coulomb/units/si/si.scala index 43d5ff227..ec4f19f0a 100644 --- a/units/src/main/scala/coulomb/units/si/si.scala +++ b/units/src/main/scala/coulomb/units/si/si.scala @@ -14,27 +14,92 @@ * limitations under the License. */ -package coulomb.units.si +package coulomb.units -import coulomb.define.* +// to use with 'export' these need to be in objects +object si: + import coulomb.define.* -final type Meter -given ctx_unit_Meter: BaseUnit[Meter, "meter", "m"] with {} + final type Meter + given ctx_unit_Meter: BaseUnit[Meter, "meter", "m"] with {} -final type Kilogram -given ctx_unit_Kilogram: BaseUnit[Kilogram, "kilogram", "kg"] with {} + final type Kilogram + given ctx_unit_Kilogram: BaseUnit[Kilogram, "kilogram", "kg"] with {} -final type Second -given ctx_unit_Second: BaseUnit[Second, "second", "s"] with {} + final type Second + given ctx_unit_Second: BaseUnit[Second, "second", "s"] with {} -final type Ampere -given ctx_unit_Ampere: BaseUnit[Ampere, "ampere", "A"] with {} + final type Ampere + given ctx_unit_Ampere: BaseUnit[Ampere, "ampere", "A"] with {} -final type Mole -given ctx_unit_Mole: BaseUnit[Mole, "mole", "mol"] with {} + final type Mole + given ctx_unit_Mole: BaseUnit[Mole, "mole", "mol"] with {} -final type Candela -given ctx_unit_Candela: BaseUnit[Candela, "candela", "cd"] with {} + final type Candela + given ctx_unit_Candela: BaseUnit[Candela, "candela", "cd"] with {} -final type Kelvin -given ctx_unit_Kelvin: BaseUnit[Kelvin, "Kelvin", "K"] with {} + final type Kelvin + given ctx_unit_Kelvin: BaseUnit[Kelvin, "Kelvin", "K"] with {} + + object prefixes: + import coulomb.{`/`, `^`} + + final type Deka + given ctx_unit_Deka: PrefixUnit[Deka, 10, "deka", "da"] with {} + + final type Hecto + given ctx_unit_Hecto: PrefixUnit[Hecto, 100, "hecto", "h"] with {} + + final type Kilo + given ctx_unit_Kilo: PrefixUnit[Kilo, 10 ^ 3, "kilo", "k"] with {} + + final type Mega + given ctx_unit_Mega: PrefixUnit[Mega, 10 ^ 6, "mega", "M"] with {} + + final type Giga + given ctx_unit_Giga: PrefixUnit[Giga, 10 ^ 9, "giga", "G"] with {} + + final type Tera + given ctx_unit_Tera: PrefixUnit[Tera, 10 ^ 12, "tera", "T"] with {} + + final type Peta + given ctx_unit_Peta: PrefixUnit[Peta, 10 ^ 15, "peta", "P"] with {} + + final type Exa + given ctx_unit_Exa: PrefixUnit[Exa, 10 ^ 18, "exa", "E"] with {} + + final type Zetta + given ctx_unit_Zetta: PrefixUnit[Zetta, 10 ^ 21, "zetta", "Z"] with {} + + final type Yotta + given ctx_unit_Yotta: PrefixUnit[Yotta, 10 ^ 24, "yotta", "Y"] with {} + + final type Deci + given ctx_unit_Deci: PrefixUnit[Deci, 1 / 10, "deci", "d"] with {} + + final type Centi + given ctx_unit_Centi: PrefixUnit[Centi, 1 / 100, "centi", "c"] with {} + + final type Milli + given ctx_unit_Milli: PrefixUnit[Milli, 10 ^ -3, "milli", "m"] with {} + + final type Micro + given ctx_unit_Micro: PrefixUnit[Micro, 10 ^ -6, "micro", "μ"] with {} + + final type Nano + given ctx_unit_Nano: PrefixUnit[Nano, 10 ^ -9, "nano", "n"] with {} + + final type Pico + given ctx_unit_Pico: PrefixUnit[Pico, 10 ^ -12, "pico", "p"] with {} + + final type Femto + given ctx_unit_Femto: PrefixUnit[Femto, 10 ^ -15, "femto", "f"] with {} + + final type Atto + given ctx_unit_Atto: PrefixUnit[Atto, 10 ^ -18, "atto", "a"] with {} + + final type Zepto + given ctx_unit_Zepto: PrefixUnit[Zepto, 10 ^ -21, "zepto", "z"] with {} + + final type Yocto + given ctx_unit_Yocto: PrefixUnit[Yocto, 10 ^ -24, "yocto", "y"] with {} From c39fc9173297bd968a17ffbfdc865e4e510e013c Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Fri, 11 Mar 2022 16:36:42 -0700 Subject: [PATCH 14/17] tweak directories --- units/src/main/scala/coulomb/units/{mks => }/mks.scala | 0 units/src/main/scala/coulomb/units/{si => }/si.scala | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename units/src/main/scala/coulomb/units/{mks => }/mks.scala (100%) rename units/src/main/scala/coulomb/units/{si => }/si.scala (100%) diff --git a/units/src/main/scala/coulomb/units/mks/mks.scala b/units/src/main/scala/coulomb/units/mks.scala similarity index 100% rename from units/src/main/scala/coulomb/units/mks/mks.scala rename to units/src/main/scala/coulomb/units/mks.scala diff --git a/units/src/main/scala/coulomb/units/si/si.scala b/units/src/main/scala/coulomb/units/si.scala similarity index 100% rename from units/src/main/scala/coulomb/units/si/si.scala rename to units/src/main/scala/coulomb/units/si.scala From 15b3dd5d37e1ef6f16ce21fd9b36c729ab20969b Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Sat, 12 Mar 2022 06:18:16 -0700 Subject: [PATCH 15/17] refactor algebra to use export --- core/src/main/scala/coulomb/ops/algebra/all.scala | 10 +++++----- core/src/main/scala/coulomb/ops/algebra/double.scala | 4 +--- core/src/main/scala/coulomb/ops/algebra/float.scala | 4 +--- core/src/main/scala/coulomb/ops/algebra/int.scala | 4 +--- core/src/main/scala/coulomb/ops/algebra/long.scala | 4 +--- 5 files changed, 9 insertions(+), 17 deletions(-) diff --git a/core/src/main/scala/coulomb/ops/algebra/all.scala b/core/src/main/scala/coulomb/ops/algebra/all.scala index 28ae95903..01ac1e3c1 100644 --- a/core/src/main/scala/coulomb/ops/algebra/all.scala +++ b/core/src/main/scala/coulomb/ops/algebra/all.scala @@ -16,8 +16,8 @@ package coulomb.ops.algebra -object all - extends IntInstances - with LongInstances - with FloatInstances - with DoubleInstances +object all: + export coulomb.ops.algebra.int.{*,given} + export coulomb.ops.algebra.long.{*,given} + export coulomb.ops.algebra.float.{*,given} + export coulomb.ops.algebra.double.{*,given} diff --git a/core/src/main/scala/coulomb/ops/algebra/double.scala b/core/src/main/scala/coulomb/ops/algebra/double.scala index 41e4be983..fe6a19912 100644 --- a/core/src/main/scala/coulomb/ops/algebra/double.scala +++ b/core/src/main/scala/coulomb/ops/algebra/double.scala @@ -18,9 +18,7 @@ package coulomb.ops.algebra import algebra.ring.TruncatedDivision -object double extends DoubleInstances - -trait DoubleInstances: +object double: given ctx_Double_is_FractionalPower: FractionalPower[Double] with def pow(v: Double, e: Double): Double = math.pow(v, e) diff --git a/core/src/main/scala/coulomb/ops/algebra/float.scala b/core/src/main/scala/coulomb/ops/algebra/float.scala index 1a927c491..137024899 100644 --- a/core/src/main/scala/coulomb/ops/algebra/float.scala +++ b/core/src/main/scala/coulomb/ops/algebra/float.scala @@ -18,9 +18,7 @@ package coulomb.ops.algebra import algebra.ring.TruncatedDivision -object float extends FloatInstances - -trait FloatInstances: +object float: given ctx_Float_is_FractionalPower: FractionalPower[Float] with def pow(v: Float, e: Double): Float = math.pow(v.toDouble, e).toFloat diff --git a/core/src/main/scala/coulomb/ops/algebra/int.scala b/core/src/main/scala/coulomb/ops/algebra/int.scala index 9e06fc198..b758dc9a4 100644 --- a/core/src/main/scala/coulomb/ops/algebra/int.scala +++ b/core/src/main/scala/coulomb/ops/algebra/int.scala @@ -18,9 +18,7 @@ package coulomb.ops.algebra import algebra.ring.TruncatedDivision -object int extends IntInstances - -trait IntInstances: +object int: given ctx_Int_is_TruncatingPower: TruncatingPower[Int] with def tpow(v: Int, e: Double): Int = math.pow(v.toDouble, e).toInt diff --git a/core/src/main/scala/coulomb/ops/algebra/long.scala b/core/src/main/scala/coulomb/ops/algebra/long.scala index ebddd6ba9..24a976c46 100644 --- a/core/src/main/scala/coulomb/ops/algebra/long.scala +++ b/core/src/main/scala/coulomb/ops/algebra/long.scala @@ -18,9 +18,7 @@ package coulomb.ops.algebra import algebra.ring.TruncatedDivision -object long extends LongInstances - -trait LongInstances: +object long: given ctx_Long_is_TruncatingPower: TruncatingPower[Long] with def tpow(v: Long, e: Double): Long = math.pow(v.toDouble, e).toLong From 3b56b9d5b9b81e3819fdc7cb012b9f77b75fde2f Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Sat, 12 Mar 2022 07:25:57 -0700 Subject: [PATCH 16/17] factor value-specific optimizations to separate import --- .../main/scala/coulomb/ops/standard/add.scala | 32 -------- .../main/scala/coulomb/ops/standard/div.scala | 14 ---- .../main/scala/coulomb/ops/standard/mul.scala | 14 ---- .../main/scala/coulomb/ops/standard/neg.scala | 10 --- .../ops/standard/optimizations/all.scala | 21 ++++++ .../ops/standard/optimizations/double.scala | 75 +++++++++++++++++++ .../ops/standard/optimizations/float.scala | 75 +++++++++++++++++++ .../main/scala/coulomb/ops/standard/sub.scala | 32 -------- 8 files changed, 171 insertions(+), 102 deletions(-) create mode 100644 core/src/main/scala/coulomb/ops/standard/optimizations/all.scala create mode 100644 core/src/main/scala/coulomb/ops/standard/optimizations/double.scala create mode 100644 core/src/main/scala/coulomb/ops/standard/optimizations/float.scala diff --git a/core/src/main/scala/coulomb/ops/standard/add.scala b/core/src/main/scala/coulomb/ops/standard/add.scala index 4d2413657..ecb3e39f0 100644 --- a/core/src/main/scala/coulomb/ops/standard/add.scala +++ b/core/src/main/scala/coulomb/ops/standard/add.scala @@ -24,38 +24,6 @@ import coulomb.ops.{Add, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions -transparent inline given ctx_add_Double_1U[U]: Add[Double, U, Double, U] = - new Add[Double, U, Double, U]: - type VO = Double - type UO = U - def apply(vl: Double, vr: Double): Double = vl + vr - -transparent inline given ctx_add_Float_1U[U]: Add[Float, U, Float, U] = - new Add[Float, U, Float, U]: - type VO = Float - type UO = U - def apply(vl: Float, vr: Float): Float = vl + vr - -transparent inline given ctx_add_Double_2U[UL, UR](using - AllowImplicitConversions, - NotGiven[UL =:= UR] - ): Add[Double, UL, Double, UR] = - val c = coulomb.conversion.infra.coefficientDouble[UR, UL] - new Add[Double, UL, Double, UR]: - type VO = Double - type UO = UL - def apply(vl: Double, vr: Double): Double = vl + (c * vr) - -transparent inline given ctx_add_Float_2U[UL, UR](using - AllowImplicitConversions, - NotGiven[UL =:= UR] - ): Add[Float, UL, Float, UR] = - val c = coulomb.conversion.infra.coefficientFloat[UR, UL] - new Add[Float, UL, Float, UR]: - type VO = Float - type UO = UL - def apply(vl: Float, vr: Float): Float = vl + (c * vr) - transparent inline given ctx_add_1V1U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, diff --git a/core/src/main/scala/coulomb/ops/standard/div.scala b/core/src/main/scala/coulomb/ops/standard/div.scala index 1dbecafb8..0509a8663 100644 --- a/core/src/main/scala/coulomb/ops/standard/div.scala +++ b/core/src/main/scala/coulomb/ops/standard/div.scala @@ -26,20 +26,6 @@ import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions import coulomb.policy.AllowTruncation -transparent inline given ctx_div_Double_2U[UL, UR](using su: SimplifiedUnit[UL / UR]): - Div[Double, UL, Double, UR] = - new Div[Double, UL, Double, UR]: - type VO = Double - type UO = su.UO - def apply(vl: Double, vr: Double): Double = vl / vr - -transparent inline given ctx_div_Float_2U[UL, UR](using su: SimplifiedUnit[UL / UR]): - Div[Float, UL, Float, UR] = - new Div[Float, UL, Float, UR]: - type VO = Float - type UO = su.UO - def apply(vl: Float, vr: Float): Float = vl / vr - transparent inline given ctx_div_1V2U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, diff --git a/core/src/main/scala/coulomb/ops/standard/mul.scala b/core/src/main/scala/coulomb/ops/standard/mul.scala index ec954f12d..83046b9da 100644 --- a/core/src/main/scala/coulomb/ops/standard/mul.scala +++ b/core/src/main/scala/coulomb/ops/standard/mul.scala @@ -25,20 +25,6 @@ import coulomb.ops.{Mul, SimplifiedUnit, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions -transparent inline given ctx_mul_Double_2U[UL, UR](using su: SimplifiedUnit[UL * UR]): - Mul[Double, UL, Double, UR] = - new Mul[Double, UL, Double, UR]: - type VO = Double - type UO = su.UO - def apply(vl: Double, vr: Double): Double = vl * vr - -transparent inline given ctx_mul_Float_2U[UL, UR](using su: SimplifiedUnit[UL * UR]): - Mul[Float, UL, Float, UR] = - new Mul[Float, UL, Float, UR]: - type VO = Float - type UO = su.UO - def apply(vl: Float, vr: Float): Float = vl * vr - transparent inline given ctx_mul_1V2U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, diff --git a/core/src/main/scala/coulomb/ops/standard/neg.scala b/core/src/main/scala/coulomb/ops/standard/neg.scala index 71e54ea6e..0427b0bbc 100644 --- a/core/src/main/scala/coulomb/ops/standard/neg.scala +++ b/core/src/main/scala/coulomb/ops/standard/neg.scala @@ -16,19 +16,9 @@ package coulomb.ops.standard -import scala.util.NotGiven - import coulomb.ops.Neg import algebra.ring.AdditiveGroup -inline given ctx_quantity_neg_Double[U]: Neg[Double, U] = - new Neg[Double, U]: - def apply(v: Double): Double = -v - -inline given ctx_quantity_neg_Float[U]: Neg[Float, U] = - new Neg[Float, U]: - def apply(v: Float): Float = -v - inline given ctx_quantity_neg[V, U](using alg: AdditiveGroup[V]): Neg[V, U] = new Neg[V, U]: def apply(v: V): V = alg.negate(v) diff --git a/core/src/main/scala/coulomb/ops/standard/optimizations/all.scala b/core/src/main/scala/coulomb/ops/standard/optimizations/all.scala new file mode 100644 index 000000000..17312db99 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/standard/optimizations/all.scala @@ -0,0 +1,21 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.standard.optimizations + +object all: + export double.given + export float.given diff --git a/core/src/main/scala/coulomb/ops/standard/optimizations/double.scala b/core/src/main/scala/coulomb/ops/standard/optimizations/double.scala new file mode 100644 index 000000000..2d3cd960e --- /dev/null +++ b/core/src/main/scala/coulomb/ops/standard/optimizations/double.scala @@ -0,0 +1,75 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.standard.optimizations + +import scala.util.NotGiven + +import coulomb.{`*`, `/`, `^`} +import coulomb.ops.* +import coulomb.policy.AllowImplicitConversions + +object double: + inline given ctx_quantity_neg_Double[U]: Neg[Double, U] = + new Neg[Double, U]: + def apply(v: Double): Double = -v + + transparent inline given ctx_add_Double_1U[U]: Add[Double, U, Double, U] = + new Add[Double, U, Double, U]: + type VO = Double + type UO = U + def apply(vl: Double, vr: Double): Double = vl + vr + + transparent inline given ctx_add_Double_2U[UL, UR](using + AllowImplicitConversions, + NotGiven[UL =:= UR] + ): Add[Double, UL, Double, UR] = + val c = coulomb.conversion.infra.coefficientDouble[UR, UL] + new Add[Double, UL, Double, UR]: + type VO = Double + type UO = UL + def apply(vl: Double, vr: Double): Double = vl + (c * vr) + + transparent inline given ctx_sub_Double_1U[U]: Sub[Double, U, Double, U] = + new Sub[Double, U, Double, U]: + type VO = Double + type UO = U + def apply(vl: Double, vr: Double): Double = vl - vr + + transparent inline given ctx_sub_Double_2U[UL, UR](using + AllowImplicitConversions, + NotGiven[UL =:= UR] + ): Sub[Double, UL, Double, UR] = + val c = coulomb.conversion.infra.coefficientDouble[UR, UL] + new Sub[Double, UL, Double, UR]: + type VO = Double + type UO = UL + def apply(vl: Double, vr: Double): Double = vl - (c * vr) + + transparent inline given ctx_mul_Double_2U[UL, UR](using su: SimplifiedUnit[UL * UR]): + Mul[Double, UL, Double, UR] = + new Mul[Double, UL, Double, UR]: + type VO = Double + type UO = su.UO + def apply(vl: Double, vr: Double): Double = vl * vr + + transparent inline given ctx_div_Double_2U[UL, UR](using su: SimplifiedUnit[UL / UR]): + Div[Double, UL, Double, UR] = + new Div[Double, UL, Double, UR]: + type VO = Double + type UO = su.UO + def apply(vl: Double, vr: Double): Double = vl / vr + diff --git a/core/src/main/scala/coulomb/ops/standard/optimizations/float.scala b/core/src/main/scala/coulomb/ops/standard/optimizations/float.scala new file mode 100644 index 000000000..2daa6cf8a --- /dev/null +++ b/core/src/main/scala/coulomb/ops/standard/optimizations/float.scala @@ -0,0 +1,75 @@ +/* + * Copyright 2022 Erik Erlandson + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package coulomb.ops.standard.optimizations + +import scala.util.NotGiven + +import coulomb.{`*`, `/`, `^`} +import coulomb.ops.* +import coulomb.policy.AllowImplicitConversions + +object float: + inline given ctx_quantity_neg_Float[U]: Neg[Float, U] = + new Neg[Float, U]: + def apply(v: Float): Float = -v + + transparent inline given ctx_add_Float_1U[U]: Add[Float, U, Float, U] = + new Add[Float, U, Float, U]: + type VO = Float + type UO = U + def apply(vl: Float, vr: Float): Float = vl + vr + + transparent inline given ctx_add_Float_2U[UL, UR](using + AllowImplicitConversions, + NotGiven[UL =:= UR] + ): Add[Float, UL, Float, UR] = + val c = coulomb.conversion.infra.coefficientFloat[UR, UL] + new Add[Float, UL, Float, UR]: + type VO = Float + type UO = UL + def apply(vl: Float, vr: Float): Float = vl + (c * vr) + + transparent inline given ctx_sub_Float_1U[U]: Sub[Float, U, Float, U] = + new Sub[Float, U, Float, U]: + type VO = Float + type UO = U + def apply(vl: Float, vr: Float): Float = vl - vr + + transparent inline given ctx_sub_Float_2U[UL, UR](using + AllowImplicitConversions, + NotGiven[UL =:= UR] + ): Sub[Float, UL, Float, UR] = + val c = coulomb.conversion.infra.coefficientFloat[UR, UL] + new Sub[Float, UL, Float, UR]: + type VO = Float + type UO = UL + def apply(vl: Float, vr: Float): Float = vl - (c * vr) + + transparent inline given ctx_mul_Float_2U[UL, UR](using su: SimplifiedUnit[UL * UR]): + Mul[Float, UL, Float, UR] = + new Mul[Float, UL, Float, UR]: + type VO = Float + type UO = su.UO + def apply(vl: Float, vr: Float): Float = vl * vr + + transparent inline given ctx_div_Float_2U[UL, UR](using su: SimplifiedUnit[UL / UR]): + Div[Float, UL, Float, UR] = + new Div[Float, UL, Float, UR]: + type VO = Float + type UO = su.UO + def apply(vl: Float, vr: Float): Float = vl / vr + diff --git a/core/src/main/scala/coulomb/ops/standard/sub.scala b/core/src/main/scala/coulomb/ops/standard/sub.scala index b71429033..08b8dc71a 100644 --- a/core/src/main/scala/coulomb/ops/standard/sub.scala +++ b/core/src/main/scala/coulomb/ops/standard/sub.scala @@ -24,38 +24,6 @@ import coulomb.ops.{Sub, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions -transparent inline given ctx_sub_Double_1U[U]: Sub[Double, U, Double, U] = - new Sub[Double, U, Double, U]: - type VO = Double - type UO = U - def apply(vl: Double, vr: Double): Double = vl - vr - -transparent inline given ctx_sub_Float_1U[U]: Sub[Float, U, Float, U] = - new Sub[Float, U, Float, U]: - type VO = Float - type UO = U - def apply(vl: Float, vr: Float): Float = vl - vr - -transparent inline given ctx_sub_Double_2U[UL, UR](using - AllowImplicitConversions, - NotGiven[UL =:= UR] - ): Sub[Double, UL, Double, UR] = - val c = coulomb.conversion.infra.coefficientDouble[UR, UL] - new Sub[Double, UL, Double, UR]: - type VO = Double - type UO = UL - def apply(vl: Double, vr: Double): Double = vl - (c * vr) - -transparent inline given ctx_sub_Float_2U[UL, UR](using - AllowImplicitConversions, - NotGiven[UL =:= UR] - ): Sub[Float, UL, Float, UR] = - val c = coulomb.conversion.infra.coefficientFloat[UR, UL] - new Sub[Float, UL, Float, UR]: - type VO = Float - type UO = UL - def apply(vl: Float, vr: Float): Float = vl - (c * vr) - transparent inline given ctx_sub_1V1U[VL, UL, VR, UR](using // https://github.com/lampepfl/dotty/issues/14585 eqv: VR =:= VL, From 6d2c43cd07a529c82732c5e36755da736b4a34b5 Mon Sep 17 00:00:00 2001 From: Erik Erlandson Date: Sat, 12 Mar 2022 08:19:34 -0700 Subject: [PATCH 17/17] unit test optimized operations --- core/src/test/scala/coulomb/quantity.scala | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/core/src/test/scala/coulomb/quantity.scala b/core/src/test/scala/coulomb/quantity.scala index 70d840eff..c6db457c7 100644 --- a/core/src/test/scala/coulomb/quantity.scala +++ b/core/src/test/scala/coulomb/quantity.scala @@ -569,3 +569,52 @@ class QuantitySuite extends CoulombSuite: assertEquals(1.withUnit[(1 / 2) * Meter].tToUnit[Meter] === 1L.withUnit[Meter], false) assertEquals(2.withUnit[(1 / 2) * Meter].tToUnit[Meter] === 1.withUnit[Meter], true) } + +class OptimizedQuantitySuite extends CoulombSuite: + import coulomb.* + import coulomb.testing.units.{*, given} + import algebra.instances.all.given + import coulomb.ops.algebra.all.given + + import coulomb.policy.allowImplicitConversions.given + import coulomb.conversion.standard.given + + // I need to test with optimizations in scope + // it would be ideal to somehow compile the main QuantitySuite with + // and without these optimizations + import coulomb.ops.standard.optimizations.all.given + import coulomb.ops.standard.given + // it is important that non-optimized be imported at the same scope level (or higher) + // otherwise it will override the optimizations if it is at narrower scope + + // I could also use a way to specifically verify that the optimizations are being + // applied. Currently I am just modifying the optimizations to be wrong and see if the tests fail + + test("negation standard") { + (-(7d.withUnit[Liter])).assertQ[Double, Liter](-7) + (-(7f.withUnit[Liter])).assertQ[Float, Liter](-7) + } + + test("addition") { + (1d.withUnit[Second] + 1d.withUnit[Second]).assertQ[Double, Second](2) + (1f.withUnit[Meter] + 1f.withUnit[Meter]).assertQ[Float, Meter](2) + (1d.withUnit[Kilo * Second] + 1d.withUnit[Second]).assertQD[Double, Kilo * Second](1.001) + (1f.withUnit[Meter] + 1f.withUnit[Yard]).assertQD[Float, Meter](1.9144) + } + + test("subtraction") { + (3d.withUnit[Second] - 1d.withUnit[Second]).assertQ[Double, Second](2) + (3f.withUnit[Meter] - 1f.withUnit[Meter]).assertQ[Float, Meter](2) + (1d.withUnit[Kilo * Second] - 1d.withUnit[Second]).assertQD[Double, Kilo * Second](0.999) + (1f.withUnit[Meter] - 1f.withUnit[Yard]).assertQD[Float, Meter](0.0856) + } + + test("multiplication") { + (2d.withUnit[Meter] * 3d.withUnit[Meter]).assertQ[Double, Meter ^ 2](6) + (2f.withUnit[Meter / Kilogram] * 3f.withUnit[Kilogram / Second]).assertQ[Float, Meter / Second](6) + } + + test("division") { + (12d.withUnit[Meter] / 3d.withUnit[Second]).assertQ[Double, Meter / Second](4) + (12f.withUnit[Meter / Kilogram] / 3f.withUnit[Second / Kilogram]).assertQ[Float, Meter / Second](4) + }