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/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/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/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..01ac1e3c1 --- /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: + 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 new file mode 100644 index 000000000..fe6a19912 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/double.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 double: + 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..137024899 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/float.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 float: + 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..b758dc9a4 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/int.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 algebra.ring.TruncatedDivision + +object int: + 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..24a976c46 --- /dev/null +++ b/core/src/main/scala/coulomb/ops/algebra/long.scala @@ -0,0 +1,34 @@ +/* + * 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: + 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 = ??? diff --git a/core/src/main/scala/coulomb/ops/ops.scala b/core/src/main/scala/coulomb/ops/ops.scala index 166bbfbd8..2e033ea01 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("Truncating Division not defined in scope for Quantity[${VL}, ${UL}] and Quantity[${VR}, ${UR}]") +abstract class TQuot[VL, UL, VR, UR]: + type VO + type UO + def apply(vl: VL, vr: VR): VO @implicitNotFound("Power not defined in scope for Quantity[${V}, ${U}] ^ ${P}") abstract class Pow[V, U, P]: @@ -54,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/add.scala b/core/src/main/scala/coulomb/ops/standard/add.scala index dbde4392f..ecb3e39f0 100644 --- a/core/src/main/scala/coulomb/ops/standard/add.scala +++ b/core/src/main/scala/coulomb/ops/standard/add.scala @@ -18,64 +18,34 @@ 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 -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, 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 +54,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 +69,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/main/scala/coulomb/ops/standard/algebra.scala b/core/src/main/scala/coulomb/ops/standard/algebra.scala deleted file mode 100644 index 2ad4d6dae..000000000 --- a/core/src/main/scala/coulomb/ops/standard/algebra.scala +++ /dev/null @@ -1,104 +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 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 - -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 - -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/div.scala b/core/src/main/scala/coulomb/ops/standard/div.scala index 7e1159df5..0509a8663 100644 --- a/core/src/main/scala/coulomb/ops/standard/div.scala +++ b/core/src/main/scala/coulomb/ops/standard/div.scala @@ -18,29 +18,18 @@ package coulomb.ops.standard import scala.util.NotGiven -import coulomb.{`*`, `/`, `^`} +import algebra.ring.MultiplicativeGroup + +import coulomb.`/` import coulomb.ops.{Div, SimplifiedUnit, ValueResolution} import coulomb.conversion.{ValueConversion, UnitConversion} import coulomb.policy.AllowImplicitConversions - -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 +import coulomb.policy.AllowTruncation 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]: @@ -54,11 +43,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)) - diff --git a/core/src/main/scala/coulomb/ops/standard/mul.scala b/core/src/main/scala/coulomb/ops/standard/mul.scala index 6e19f34c5..83046b9da 100644 --- a/core/src/main/scala/coulomb/ops/standard/mul.scala +++ b/core/src/main/scala/coulomb/ops/standard/mul.scala @@ -18,35 +18,23 @@ 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 -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, - 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 +42,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)) diff --git a/core/src/main/scala/coulomb/ops/standard/neg.scala b/core/src/main/scala/coulomb/ops/standard/neg.scala index 7da950337..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,10 @@ 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: 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) 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/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))) diff --git a/core/src/main/scala/coulomb/ops/standard/pow.scala b/core/src/main/scala/coulomb/ops/standard/pow.scala index ff9f13140..bb37bb385 100644 --- a/core/src/main/scala/coulomb/ops/standard/pow.scala +++ b/core/src/main/scala/coulomb/ops/standard/pow.scala @@ -18,15 +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_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, e) -transparent inline given ctx_quantity_pow[V, U, E](using - alg: CanPow[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) + def apply(v: V): VO = alg.pow(v, e) +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/sub.scala b/core/src/main/scala/coulomb/ops/standard/sub.scala index 06d266e2a..08b8dc71a 100644 --- a/core/src/main/scala/coulomb/ops/standard/sub.scala +++ b/core/src/main/scala/coulomb/ops/standard/sub.scala @@ -18,64 +18,34 @@ 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 -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, 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 +54,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 +69,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))) 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..c0a4017ff --- /dev/null +++ b/core/src/main/scala/coulomb/ops/standard/tpow.scala @@ -0,0 +1,34 @@ +/* + * 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} +import coulomb.ops.algebra.TruncatingPower +import coulomb.rational.typeexpr + +transparent inline given ctx_quantity_tpow[V, U, E](using + 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, e) 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..e20fea7fd --- /dev/null +++ b/core/src/main/scala/coulomb/ops/standard/tquot.scala @@ -0,0 +1,52 @@ +/* + * 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: TruncatedDivision[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: 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)) diff --git a/core/src/main/scala/coulomb/quantity.scala b/core/src/main/scala/coulomb/quantity.scala index 3a23ddaef..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] @@ -114,9 +120,15 @@ 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] + 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 63e6c56d2..c6db457c7 100644 --- a/core/src/test/scala/coulomb/quantity.scala +++ b/core/src/test/scala/coulomb/quantity.scala @@ -19,6 +19,8 @@ import coulomb.testing.CoulombSuite class QuantitySuite extends CoulombSuite: import coulomb.* import coulomb.testing.units.{*, given} + import algebra.instances.all.given + import coulomb.ops.algebra.all.given test("lift via Quantity") { Quantity[Meter](3.14).assertQ[Double, Meter](3.14) @@ -57,7 +59,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) @@ -70,14 +71,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") { @@ -85,17 +85,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") { @@ -194,26 +193,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") { @@ -291,26 +289,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") { @@ -407,22 +404,20 @@ 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.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) + (5d.withUnit[Meter] `tquot` 2d.withUnit[Second]).assertQ[Double, Meter / Second](2) + (5f.withUnit[Meter] `tquot` 2f.withUnit[Second]).assertQ[Float, 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) + (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] `tquot` 2L.withUnit[Second]).assertQ[Long, Meter / Second](2) + (5.withUnit[Meter] `tquot` 2.withUnit[Second]).assertQ[Int, Meter / Second](2) } test("power standard") { @@ -442,48 +437,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) + // 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]") 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") { @@ -559,7 +554,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 @@ -567,11 +561,60 @@ 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) + } + +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) } diff --git a/units/src/main/scala/coulomb/units/mks.scala b/units/src/main/scala/coulomb/units/mks.scala new file mode 100644 index 000000000..3a7ca0867 --- /dev/null +++ b/units/src/main/scala/coulomb/units/mks.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 mks: + import coulomb.define.* + import coulomb.{`*`, `/`, `^`} + 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 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 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 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/mks/mks.scala b/units/src/main/scala/coulomb/units/mks/mks.scala deleted file mode 100644 index 17850de0c..000000000 --- a/units/src/main/scala/coulomb/units/mks/mks.scala +++ /dev/null @@ -1,40 +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.mks - -import coulomb.define.* -import coulomb.{`*`, `/`, `^`} - -import coulomb.units.si.* - -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 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 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 {} 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.scala b/units/src/main/scala/coulomb/units/si.scala new file mode 100644 index 000000000..ec4f19f0a --- /dev/null +++ b/units/src/main/scala/coulomb/units/si.scala @@ -0,0 +1,105 @@ +/* + * 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 + +// 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 Kilogram + given ctx_unit_Kilogram: BaseUnit[Kilogram, "kilogram", "kg"] 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 Mole + given ctx_unit_Mole: BaseUnit[Mole, "mole", "mol"] 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 {} + + 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 {} 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 deleted file mode 100644 index 43d5ff227..000000000 --- a/units/src/main/scala/coulomb/units/si/si.scala +++ /dev/null @@ -1,40 +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 - -import coulomb.define.* - -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 Second -given ctx_unit_Second: BaseUnit[Second, "second", "s"] 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 Candela -given ctx_unit_Candela: BaseUnit[Candela, "candela", "cd"] with {} - -final type Kelvin -given ctx_unit_Kelvin: BaseUnit[Kelvin, "Kelvin", "K"] with {}