Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

unchained to on Measurement[SomeUnit] returns unchanged value and accepts unit of any quantity #18

Open
arkanoid87 opened this issue Mar 19, 2024 · 3 comments

Comments

@arkanoid87
Copy link

from std/sugar import dump
import unchained
import measuremancer

defUnit(YardMinute⁻¹)
defUnit(parsec•lbs)

proc main() =
  let x = 1.m
  let y = 2.s
  let xy = x / y
  let mx = x ± 0.1.m
  let my = y ± 0.1.s
  let mxy = mx / my
  dump x
  dump y
  dump xy
  dump x.to(yd)
  dump y.to(Minute)
  dump xy.to(YardMinute⁻¹)
  dump mx
  dump my
  dump mxy
  dump mx.to(yd)
  dump my.to(Minute)
  dump mxy.to(YardMinute⁻¹)
  dump mxy.to(Liter)
  dump mxy.to(slug)
  dump mxy.to(parsec•lbs)

when isMainModule:
  main()
x = 1 m
y = 2 s
xy = 0.5 m•s⁻¹
x.to(yd) = 1.09361 yd
y.to(Minute) = 0.0333333 min
xy.to(Yard•Minute⁻¹) = 32.8084 yd•min⁻¹
mx = 1.00 ± 0.100 Meter
my = 2.00 ± 0.100 Second
mxy = 0.500 ± 0.0559 Meter•Second⁻¹
mx.to(yd) = 1.00 ± 0.100 Yard
my.to(Minute) = 2.00 ± 0.100 Minute
mxy.to(Yard•Minute⁻¹) = 0.500 ± 0.0559 Yard•Minute⁻¹
mxy.to(Liter) = 0.500 ± 0.0559 Liter
mxy.to(slug) = 0.500 ± 0.0559 Slug
mxy.to(parsec•lbs) = 0.500 ± 0.0559 Parsec•Pound
@arkanoid87
Copy link
Author

arkanoid87 commented Mar 19, 2024

is it sufficient to wrap the unchained call?

from std/sugar import dump
import unchained
import measuremancer

defUnit(YardMinute⁻¹)
defUnit(parsec•lbs)

proc to[T: SomeUnit](x: Measurement[T], U: typedesc[SomeUnit]): Measurement[U] =
  x.value.to(U) ± x.error.to(U)

proc main() =
  let x = 1.m
  let y = 2.s
  let xy = x / y
  let mx = x ± 0.1.m
  let my = y ± 0.1.s
  let mxy = mx / my
  dump x
  dump y
  dump xy
  dump x.to(yd)
  dump y.to(Minute)
  dump xy.to(YardMinute⁻¹)
  dump mx
  dump my
  dump mxy
  dump mx.to(yd)
  dump my.to(Minute)
  dump mxy.to(YardMinute⁻¹)
  doAssert not compiles(mxy.to(Liter))
  doAssert not compiles(mxy.to(slug))
  doAssert not compiles(mxy.to(parsec•lbs))

when isMainModule:
  main()

EDIT: I think I've found where the PR should end :P

## Type conversion. TODO: make this more type safe, funnily enough

@arkanoid87
Copy link
Author

arkanoid87 commented Mar 20, 2024

with minimal edits to (currently private) to and changeType functions seems possible to use unchained to for val, uncer and der

import std/[importutils, tables, typetraits]
import unchained
import measuremancer {.all.}

privateAccess(Measurement)

proc changeType[T; U](tab: Derivatives[T], dtype: typedesc[U]): Derivatives[U] =
  result = initDerivatives[U]()
  for key, val in pairs(tab):
    let nkey = (val: key.val.to(U), uncer: key.uncer.to(U), tag: key.tag)
    result[nkey] = val.to(U)

proc to*[T: FloatLike, U](m: Measurement[T], dtype: typedesc[U]): Measurement[U] =
  when T is U:
    result = m
  else:
    result =
      initMeasurement[U](m.val.to(U), m.uncer.to(U), m.der.changeType(dtype), m.id)

# ---------------------

type
  MLength = Length | Measurement[Length]
  MTime = Time | Measurement[Time]
  Foo[TLength: MLength, TTime: MTime] = object
    x: TLength
    y: TTime

func init[TLength: MLength, TTime: MTime](T: typedesc[Foo], x: TLength, y: TTime): T =
  T[TLength, TTime](x: x, y: y)

proc convertUnitsPairs*[T1](x: T1, T2: typedesc): T2 {.inline.} =
  for xname, xf in fieldPairs(x):
    for rname, rf in fieldPairs(result):
      when xname == rname:
        when xf is Measurement:
          rf = xf.to(genericParams(rf.type).get(0))
        else:
          rf = xf.to(type(rf))

# ---------------------

from std/sugar import dump
defUnit(YardMinute⁻¹)
defUnit(parsec•lbs)

proc testSimple() =
  let x = 1.m
  let y = 2.s
  let xy = x / y
  let mx = x ± 0.1.m
  let my = y ± 0.1.s
  let mxy = mx / my
  dump x
  dump y
  dump xy
  dump x.to(yd)
  dump y.to(Minute)
  dump xy.to(YardMinute⁻¹)
  dump mx
  dump my
  dump mxy
  dump mx.to(yd)
  dump my.to(Minute)
  dump mxy.to(YardMinute⁻¹)
  doAssert not compiles(mxy.to(Liter))
  doAssert not compiles(mxy.to(slug))
  doAssert not compiles(mxy.to(parsec•lbs))
  let o1 = Foo.init(1.m, 2.s)
  let o2 = o1.convertUnitsPairs(Foo[Yard, Minute])
  dump o1
  dump o2
  doAssert not compiles(o1.convertUnitsPairs(Foo[Liter, slug]))
  let m1 = Foo.init(1.m ± 0.001.m, 2.s ± 0.001.s)
  let m2 = m1.convertUnitsPairs(Foo[Measurement[Yard], Measurement[Minute]])
  dump m1
  dump m2
  doAssert not compiles(m1.convertUnitsPairs(Foo[Liter, slug]))

when isMainModule:
  testSimple()
x = 1 m
y = 2 s
xy = 0.5 m•s⁻¹
x.to(yd) = 1.09361 yd
y.to(Minute) = 0.0333333 min
xy.to(Yard•Minute⁻¹) = 32.8084 yd•min⁻¹
mx = 1.00 ± 0.100 Meter
my = 2.00 ± 0.100 Second
mxy = 0.500 ± 0.0559 Meter•Second⁻¹
mx.to(yd) = 1.09 ± 0.109 Yard
my.to(Minute) = 0.0333 ± 0.00167 Minute
mxy.to(Yard•Minute⁻¹) = 32.8 ± 3.67 Yard•Minute⁻¹
o1 = (x: 1 m, y: 2 s)
o2 = (x: 1.09361 yd, y: 0.0333333 min)
m1 = (x: 1.00 ± 0.00100 Meter, y: 2.00 ± 0.00100 Second)
m2 = (x: 1.09 ± 0.00109 Yard, y: 0.0333 ± 1.67e-05 Minute)

@Vindaar
Copy link
Member

Vindaar commented Mar 21, 2024

Hey! Sorry, been away for a few days.

The first issue is that to is just an Unchained detail for type conversion. So if we wish to support this in Measuremancer here, at the very least we need to adjust the FloatLike concept to include a to availability check and add a to procedure for SomeNumber or similar.

But more importantly, the issue is that it gets tricky with the other operations. It may very well be possible to solve this neatly, but it's not super straight forward. Feel free to give it a try and open a PR, but you need to make sure all the math operations on Measuement objects still work (including using Unchained units), and that the uncertainties are correctly converted.

I might take a closer look at it one of these days myself.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants