coulomb-runtime

The coulomb-runtime package implements RuntimeQuantity and RuntimeUnit. Its primary use case at the time of this documentation is to support runtime I/O, for example the coulomb-pureconfig package.

Quick Start

packages

libraryDependencies += "com.manyangled" %% "coulomb-runtime" % "0.8.0"

// dependencies
libraryDependencies += "com.manyangled" %% "coulomb-core" % "0.8.0"

// coulomb predefined units
libraryDependencies += "com.manyangled" %% "coulomb-units" % "0.8.0"

import

// fundamental coulomb types and methods
// these include RuntimeUnit and RuntimeQuantity
import coulomb.*
import coulomb.syntax.*

// algebraic definitions
import algebra.instances.all.given
import coulomb.ops.algebra.all.given

// unit and value type policies for operations
import coulomb.policy.standard.given
import scala.language.implicitConversions

// unit definitions
import coulomb.units.si.{*, given}
import coulomb.units.si.prefixes.{*, given}
import coulomb.units.info.{*, given}
import coulomb.units.time.{*, given}

// runtime definitions
import coulomb.conversion.runtimes.mapping.MappingCoefficientRuntime

examples

RuntimeUnit is the core data structure of the coulomb-runtime package. It is a parallel runtime implementation of the standard static unit types and analysis defined in coulomb-core.

The RuntimeUnit.of method makes it easy to create RuntimeUnit values from static unit types. Additionally, you can apply the standard unit type operators *, / and ^ to build up unit expressions.

// create RuntimeUnit values from static unit types
val k = RuntimeUnit.of[Kilo]
val d = RuntimeUnit.of[Meter]
val t = RuntimeUnit.of[Second]

// Build up unit expression from other expressions
val kps = (k * d) / t
// kps: Div = Div(
//   num = Mul(
//     lhs = UnitType(path = "coulomb.units.si$.prefixes$.Kilo"),
//     rhs = UnitType(path = "coulomb.units.si$.Meter")
//   ),
//   den = UnitType(path = "coulomb.units.si$.Second")
// )

// values can be displayed with toString for readability
kps.toString
// res0: String = "(Kilo*Meter)/Second"

The RuntimeUnit.of method can be used with static unit types of arbitrary form.

val kps2 = RuntimeUnit.of[Kilo * Meter / Second]
kps2.toString
// res1: String = "(Kilo*Meter)/Second"

A RuntimeQuantity is a value paired with a RuntimeUnit, and is the runtime analog of Quantity. The following example demonstrates some ways to create RuntimeQuantity objects.

// a RuntimeQuantity is a value paired with a RuntimeUnit
val rq = RuntimeQuantity(1f, kps)
// rq: RuntimeQuantity[Float] = RuntimeQuantity(
//   value = 1.0F,
//   unit = Div(
//     num = Mul(
//       lhs = UnitType(path = "coulomb.units.si$.prefixes$.Kilo"),
//       rhs = UnitType(path = "coulomb.units.si$.Meter")
//     ),
//     den = UnitType(path = "coulomb.units.si$.Second")
//   )
// )

// declare a RuntimeQuantity with a given RuntimeUnit
1f.withRuntimeUnit(kps).toString
// res2: String = "RuntimeQuantity(1.0,(Kilo*Meter)/Second)"

// an equivalent RuntimeQuantity based on a static unit type
1f.withRuntimeUnit[(Kilo * Meter) / Second].toString
// res3: String = "RuntimeQuantity(1.0,(Kilo*Meter)/Second)"

It is also possible to convert from RuntimeQuantity to Quantity. This is accomplished using a CoefficientRuntime in context.

As this example shows, you can list package names, which will import any unit types into the CoefficientRuntime.

The CoefficientRuntime bridges the gap between runtime unit expresions and static unit types. It is what allows loading unit aware configurations, for example in coulomb-pureconfig.

// declare a coefficient runtime that knows about SI units and prefixes
given given_CRT: CoefficientRuntime = MappingCoefficientRuntime.of[
    "coulomb.units.si" *:
    "coulomb.units.si.prefixes" *:
    EmptyTuple
]

With a CoefficientRuntime, we can convert from runtime quantities to static typed quantities. We cannot know at compile time if these conversions will succeed, so these operations return an Either value.

// reconstruct the equivalent Quantity
rq.toQuantity[Float, Kilo * Meter / Second]
// res4: Either[String, Quantity[Float, /[*[Kilo, Meter], Second]]] = Right(
//   value = 1.0F
// )

// valid conversions of value types or unit types will also succeed
rq.toQuantity[Double, Meter / Second]
// res5: Either[String, Quantity[Double, /[Meter, Second]]] = Right(
//   value = 1000.0
// )

// attempting to convert to incompatible units will fail
rq.toQuantity[Float, Second]
// res6: Either[String, Quantity[Float, Second]] = Left(
//   value = "non-convertible units: (Kilo*Meter)/Second, Second"
// )