#---------------------------------------------------*-TCL-*-----
#
# Commands covered:  
#                    units::new
#                    units::convert
#                    units::reduce
#
#  This file contains a collection of tests for the units
#  conversion facility.
#
#  Copyright (C) Mayo Foundation.  All rights reserved.
#
#  October 30, 2000
#  Robert W. Techentin
#
#  "$Id: units.test,v 1.9 2010/12/22 15:15:00 techentin Exp $"
#
#----------------------------------------------------------------

source [file join \
	[file dirname [file dirname [file join [pwd] [info script]]]] \
	devtools testutilities.tcl]

testsNeedTcl     8.5
testsNeedTcltest 2

testing {
    useLocal units.tcl units
}

#-----------------------------------------------------------
test units-1.1 {new command exists} {
    list [catch {units::new} msg] $msg
} {1 {wrong # args: should be "units::new name baseUnits"}}

test units-1.2 {convert command exists} {
    list [catch {units::convert} msg] $msg
} {1 {wrong # args: should be "units::convert value targetUnits"}}

test units-1.3 {reduce command exists} {
    list [catch {units::reduce} msg] $msg
} {1 {wrong # args: should be "units::reduce unitString"}}



#-----------------------------------------------------------
#
# exercise new command
#
#-----------------------------------------------------------

test units-2.1 {new unit} {
    list [catch {units::new "longstretch" "42 meter"} msg] $msg
} {0 {}}

test units-2.2 {new unit primitive} {
    list [catch {units::new wongo -primitive} msg] $msg
} {0 {}}

test units-2.3 {new unit already exists} {
    list [catch {units::new "hertz" "/s"} msg] $msg
} {1 {unit 'hertz' is already defined}}

test units-2.4 {new primitive unit already exists} {
    list [catch {units::new "meter" -primitive} msg] $msg
} {1 {unit 'meter' is already defined}}

test units-2.5 {new unit with bogus base units} {
    list [catch {units::new "mankledoo" "kudos/smackdown"} msg] $msg
} {1 {'kudos/smackdown' cannot be reduced to primitive units}}

test units-2.6 {new unit name invalid characters} {
    list [catch {units::new "supper22" "meter/second"} msg] $msg
} {1 {non-alphabetic characters in unit name 'supper22'}}

test units-2.7 {new unit, then exercise it} {
    units::new breadloaf 0.152meter
    format "%.2f" [units::convert 1.0meter breadloaf]
} {6.58}


#-----------------------------------------------------------
#
# exercise convert command features
#
#-----------------------------------------------------------

test units-3.1 {convert number to number (no units)} {
    units::convert 1.0 1.0
} {1.0}

test units-3.2 {convert simple number} {
    units::convert 1.0 meter
} {1.0}

test units-3.3 {convert negative number} {
    units::convert -1.0 meter
} {-1.0}

test units-3.4 {convert to negative units} {
    units::convert 1.0 -1.0meter
} {-1.0}

test units-3.5 {convert identical unit} {
    units::convert 1.0meter meter
} {1.0}

test units-3.6 {convert plural units - with "s"} {
    units::convert 1.0meter meters
} {1.0}

test units-3.7 {convert plural units - with "es"} {
    units::convert 1.0meter meteres 
} {1.0}

test units-3.8 {convert prefixed plural units - with "s"} {
    units::convert 1000.0meter kilometers
} {1.0}

test units-3.9 {convert prefixed plural units - with "es"} {
    units::convert 1000.0meter kilometeres 
} {1.0}

test units-3.10 {convert abbreviated (not plural) units} {
    # Make sure that "ms", which is the abbreviation
    # for "millisecond" is not reduced to "meters"
    units::convert 1000.0ms second 
} {1.0}

test units-3.11 {convert prefixed units} {
    units::convert 1000.0meter kilometer
} {1.0}

test units-3.12 {convert units with fraction} {
    units::convert 1.0/second hertz
} {1.0}

test units-3.13 {convert power string} {
    units::convert 1.0meter^2 centimeter^2
} {10000.0}

test units-3.14 {convert complex fraction unit with power} {
    units::convert 1.0m-kg/s^2 newton
} {1.0}

test units-3.15 {convert non-primitive unit with power} {
    units::convert 1.0m^2-kg^2/s^4 newton^2
} {1.0}

test units-3.16 {convert complex recursive unit} {
    units::convert 1000.0Sv kilosievert
} {1.0}

test units-3.17 {convert value with space between number and unit} {
    units::convert "1000.0 meter" kilometer
} {1.0}

test units-3.18 {convert unit with space separators} {
    units::convert "1.0 m kg/s^2" newton
} {1.0}

test units-3.19 {convert unit with dash separators} {
    units::convert "1.0 m-kg/s^2" newton
} {1.0}

test units-3.20 {convert unit with asterisk separators} {
    units::convert "1.0 m*kg/s^2" newton
} {1.0}

test units-3.21 {convert blank unit to base unit} {
    units::convert "" meter
} {1.0}

test units-3.22 {convert blank unit to scaled unit} {
    units::convert "" mm
} {1000.0}

test units-3.23 {convert negative units with leading spaces} {
    units::convert " -1 degree / 2 s" degrees/second
} {-0.5}



#-----------------------------------------------------------
#
#  Convert scaled and prefixed unit
#
#-----------------------------------------------------------

test units-4.1 {convert simple value} {
    units::convert 60seconds second
} {60.0}

test units-4.2 {convert value to scaled target units} {
    units::convert 3600second 60second
} {60.0}

test units-4.3 {convert value with prefixed units} {
    units::convert 1.0kilosecond second
} {1000.0}

test units-4.4 {convert value with multiple prefixed units} {
    units::convert 1.0millimeter-kilosecond meter-second
} {1.0}

test units-4.5 {convert value with prefixed denominator units} {
    units::convert 1000.0meter/kilosecond meter/second
} {1.0}

test units-4.6 {convert value with prefixed numerator and denominator units} {
    units::convert 1.0kilometer/kilosecond meter/second
} {1.0}

test units-4.7 {convert value with multiple prefixed denominator units} {
    units::convert 1.0meter/kilosecond-milligram meter/second-gram
} {1.0}

test units-4.8 {convert value to prefixed target units} {
    units::convert 1.0second millisecond
} {1000.0}

test units-4.9 {convert value to prefixed denominator target units} {
    units::convert 1.0meter/second meter/kilosecond
} {1000.0}

test units-4.10 {convert integer zero value} {
    units::convert 0seconds second
} {0.0}

test units-4.11 {convert floating point zero value} {
    units::convert 0.0seconds second
} {0.0}


#-----------------------------------------------------------
#
#  Bad unit syntaxes
#
#-----------------------------------------------------------

test units-5.1 {bad unit string - too many "/"} {
    list [catch {units::convert 1.0 "m/s/s"} msg ] $msg
} {1 {invalid unit string 'm/s/s':  only one '/' allowed}}

test units-5.2 {bad unit string - invalid characters} {
    list [catch {units::convert 1.0 "m{}/s"} msg ] $msg
} {1 {invalid characters in unit string 'm{}/s'}}

test units-5.3 {bad unit string - invalid unit} {
    list [catch {units::convert 1.0 "foo^2"} msg ] $msg
} {1 {invalid unit name 'foo' in 'foo^2'}}

test units-5.4 {bad unit string - invalid non-integer exponent} {
    list [catch {units::convert 1.0 "m^2foo"} msg ] $msg
} {1 {invalid integer exponent in 'm^2foo'}}

test units-5.5 {bad unit string - multiple exponent characters} {
    list [catch {units::convert 1.0 "m^2^3"} msg ] $msg
} {1 {invalid integer exponent in 'm^2^3'}}

test units-5.6 {bad unit string - negative exponent} {
    list [catch {units::convert 1.0 "m^-2"} msg ] $msg
} {1 {invalid integer exponent in 'm^-2'}}

test units-5.7 {bad unit string - zero factor} {
    list [catch {units::convert 1.0 "meter/0*second"} msg ] $msg
} {1 {illegal zero factor in 'meter/0*second'}}

test units-5.8 {bad unit string - dot in unit name} {
    list [catch {units::convert 1.0 "meter.second"} msg ] $msg
} {1 {invalid non-alphabetic unit name in 'meter.second'}}

test units-5.9 {bad unit string - digit in unit name} {
    list [catch {units::convert 1.0 "meter2second"} msg ] $msg
} {1 {invalid non-alphabetic unit name in 'meter2second'}}





#-----------------------------------------------------------
#
# conversion errors
#
#-----------------------------------------------------------

test units-6.1 {conversion error - incompatible unit} {
    list [catch {units::convert 1.0meter seconds} msg ] $msg
} {1 {'1.0meter' and 'seconds' have incompatible units}}

test units-6.2 {conversion error - extra unit in value} {
    list [catch {units::convert 1.0meters/second seconds} msg ] $msg
} {1 {'1.0meters/second' and 'seconds' have incompatible units}}

test units-6.3 {conversion error - extra unit in targetUnits} {
    list [catch {units::convert 1.0meter meters/second} msg ] $msg
} {1 {'1.0meter' and 'meters/second' have incompatible units}}

test units-6.4 {convert base to blank target unit} {
    list [catch {units::convert "1.0 meter" ""} msg ] $msg
} {1 {'1.0 meter' and '' have incompatible units}}

test units-6.5 {convert base unit to scale factor} {
    list [catch {units::convert "1.0 meter" "1.0"} msg ] $msg
} {1 {'1.0 meter' and '1.0' have incompatible units}}


#-----------------------------------------------------------
#
#  Derived Units
#
#  Check derived units definitions by converting a named
#  derived unit into its abbreviated units.  
#
#-----------------------------------------------------------

test units-7.1 {derived raidian} {
    ::units::convert radian m/m
} {1.0}

test units-7.2 {derived steradian} {
    ::units::convert steradian m^2/m^2
} {1.0}

test units-7.3 {derived hertz} {
    ::units::convert hertz /s
} {1.0}

test units-7.4 {derived newton} {
    ::units::convert newton m-kg/s^2
} {1.0}

test units-7.5 {derived pascal} {
    ::units::convert pascal N/m^2
} {1.0}

test units-7.6 {derived joule} {
    ::units::convert joule N-m
} {1.0}

test units-7.7 {derived watt} {
    ::units::convert watt J/s
} {1.0}

test units-7.8 {derived coulomb} {
    ::units::convert coulomb s-A
} {1.0}

test units-7.9 {derived volt} {
    ::units::convert volt W/A
} {1.0}

test units-7.10 {derived farad} {
    ::units::convert farad C/V
} {1.0}

test units-7.11 {derived ohm} {
    ::units::convert ohm V/A
} {1.0}

test units-7.12 {derived siemens} {
    ::units::convert siemens A/V
} {1.0}

test units-7.13 {derived weber} {
    ::units::convert weber V-s
} {1.0}

test units-7.14 {derived tesla} {
    ::units::convert tesla Wb/m^2
} {1.0}

test units-7.15 {derived henry} {
    ::units::convert henry Wb/A
} {1.0}

test units-7.16 {derived lumen} {
    ::units::convert lumen cd-sr
} {1.0}

test units-7.17 {derived lux} {
    ::units::convert lux lm/m^2
} {1.0}

#-----------------------------------------------------------
#
#  Reduce Derived Units
#
#  Check derived unit definitions by reducing them
#  to primitive unit strings.
#
#-----------------------------------------------------------


test units-8.1 {reduced raidian} {
    #  radian, defined as meter/meter, should reduce
    #  to 1.0, but is not a primitive unit.
    ::units::reduce radian 
} {1.0}

test units-8.2 {reduced steradian} {
    #  steradian, defined as meter^2/meter^2, should reduce
    #  to 1.0, but is not a primitive unit.
    ::units::reduce steradian
} {1.0}

test units-8.3 {reduced hertz} {
    ::units::reduce hertz
} {1.0 / second}

test units-8.4 {reduced newton} {
    ::units::reduce newton
} {1000.0 gram meter / second second}

test units-8.5 {reduced pascal} {
    ::units::reduce pascal
} {1000.0 gram / meter second second}

test units-8.6 {reduced joule} {
    ::units::reduce joule 
} {1000.0 gram meter meter / second second}

test units-8.7 {reduced watt} {
    ::units::reduce watt 
} {1000.0 gram meter meter / second second second}

test units-8.8 {reduced coulomb} {
    ::units::reduce coulomb 
} {1.0 ampere second}

test units-8.9 {reduced volt} {
    ::units::reduce volt
} {1000.0 gram meter meter / ampere second second second}

test units-8.10 {reduced farad} {
    ::units::reduce farad
} {0.001 ampere ampere second second second second / gram meter meter}

test units-8.11 {reduced ohm} {
    ::units::reduce ohm 
} {1000.0 gram meter meter / ampere ampere second second second}

test units-8.12 {reduced siemens} {
    ::units::reduce siemens 
} {0.001 ampere ampere second second second / gram meter meter}

test units-8.13 {reduced weber} {
    ::units::reduce weber
} {1000.0 gram meter meter / ampere second second}

test units-8.14 {reduced tesla} {
    ::units::reduce tesla 
} {1000.0 gram / ampere second second}

test units-8.15 {reduced henry} {
    ::units::reduce henry
} {1000.0 gram meter meter / ampere ampere second second}

test units-8.16 {reduced lumen} {
    ::units::reduce lumen
#  The definition includes steradian, but the value cancels out
} {1.0 candela}

test units-8.17 {reduced lux} {
    ::units::reduce lux
#  The definition includes steradian, but the value cancels out
} {1.0 candela / meter meter}


#-----------------------------------------------------------
#
#  Reduce Non-SI Units
#
#  Check non-SI unit definitions by reducing them
#  to primitive unit strings.
#
#-----------------------------------------------------------


test units-9.1 {non-SI angstrom} {
    ::units::convert 1.0E10angstrom meter
} {1.0}

test units-9.2 {non-SI astronomicalUnit} {
    ::units::convert 1.0astronomicalUnit kilometer
} {149597900.0}

test units-9.3 {non-SI atmosphere} {
    ::units::convert 1.0atmosphere kilopascal
} {101.325}

test units-9.4 {non-SI bar} {
    ::units::convert 1.0bar kilopascal
} {100.0}

test units-9.5 {non-SI calorie} {
    ::units::convert 1.0calorie joule
} {4.1868}

test units-9.6 {non-SI curie} {
    ::units::convert 1.0millicurie becquerel
} {37000000.0}



#-----------------------------------------------------------
#
#  Special cases and potential errors
#
#-----------------------------------------------------------


test units-10.1 {leading zero is not octal} {
    format %5.3f [::units::convert 09mm meter]
} {0.009}

###
# Dimensional analysis tests
###
::units::new rankine {0.55555555555556 kelvin}
::units::new farhenheit {+ 459.67 rankine}
::units::new celsius {+ 273.15 kelvin}

test units-11.0 {Test kelvin -> rankine} {
    format %5.3f [::units::convert {451 rankine} kelvin]
} {250.556}

test units-11.2 {Test farhenheit -> farhenheit} {
    format %5.3f [::units::convert {50 farhenheit} farhenheit]
} {50.000}

test units-11.3 {Test kelvin -> rankine} {
    format %5.3f [::units::convert {50 celsius} celsius]
} {50.000}


proc C_to_K temp {
  return [format %5.3f [expr {$temp-273.15}]]
}
proc F_to_C temp {
  return [format %5.3f [expr {($temp-32)*5.0/9.0}]]
}
proc C_to_F temp {
  return [format %5.3f [expr {$temp*9.0/5.0+32}]]
}

###
# Cherry pick some known values
###
foreach {value_a unit_a value_b unit_b} {
  0 celsius 32 farhenheit
  100 celsius 212 farhenheit
  100 farhenheit 38 celsius
} {
  test units-11.4.$value_a [list Test Celcius -> Fahrenheight $value_a] {
    expr {int(round([::units::convert [list $value_a $unit_a] $unit_b]))}
  } $value_b
  test units-11.4.$value_b [list Test Fahrenheight -> Celcius $value_b] {
    expr {round([::units::convert [list $value_b $unit_b] $unit_a])}
  } $value_a
}
for {set test_temp -50} {$test_temp < 150} {incr test_temp 10} {
  test units-11.5.$test_temp [list Test Fahrenheight -> Celcius $test_temp] {
    format %5.3f [::units::convert [list $test_temp farhenheit] celsius]
  } [F_to_C $test_temp]
  test units-11.5.$test_temp [list Test Celcius -> Fahrenheight $test_temp] {
    format %5.3f [::units::convert [list $test_temp celsius] farhenheit]
  } [C_to_F $test_temp]
}


::units::new deg_f farhenheit


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

testsuiteCleanup
