--[[ This file contains the unit tests for the physical.Quantity class. Copyright (c) 2020 Thomas Jenni Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ]]-- local lu = require("luaunit") package.path = "../src/?.lua;" .. package.path local physical = require("physical") local N = physical.Number local Q = physical.Quantity function dump(o) if type(o) == 'table' then local s = '{ ' for k,v in pairs(o) do if type(k) ~= 'number' then k = '"'..k..'"' end if getmetatable(v) == physical.Unit then s = s .. '['..k..'] = ' .. v.symbol .. ',' else s = s .. '['..k..'] = ' .. dump(v) .. ',' end end return s .. '}\n' else return tostring(o) end end TestQuantity = {} -- Quantity.new(o=nil) function TestQuantity:testEmptyConstructor() local q = Q() lu.assertEquals( q.value, 1 ) end function TestQuantity:testNumberConstructor() local q = Q(42) lu.assertEquals( q.value, 42 ) lu.assertTrue( q.dimension:iszero() ) end function TestQuantity:testCopyConstructor() local q = Q(73*_m) lu.assertEquals( q.value, 73 ) lu.assertTrue( q.dimension == _m.dimension ) end -- Quantity.defineBase(symbol,name,dimension) -- Quantity.define(symbol, name, o, tobase, frombase) function TestQuantity:testToString() N.seperateUncertainty = true lu.assertEquals( tostring(5 * _m), "5 * _m" ) lu.assertEquals( tostring(5 * _m^2), "5.0 * _m^2" ) lu.assertEquals( tostring(5 * _km/_h), "5.0 * _km / _h" ) lu.assertEquals( tostring( N(2.7,0.04) * _g/_cm^3), "(2.70 +/- 0.04) * _g / _cm^3" ) end function TestQuantity:testToSIUnitX() N.seperateUncertainty = false lu.assertEquals( (5 * _m):tosiunitx(), "\\SI{5}{\\meter}" ) lu.assertEquals( (5 * _m):tosiunitx("x=1"), "\\SI[x=1]{5}{\\meter}" ) lu.assertEquals( (5 * _m):tosiunitx("x=5",1), "\\num[x=5]{5}" ) lu.assertEquals( (5 * _m):tosiunitx("x=5",2), "\\si[x=5]{\\meter}" ) lu.assertEquals( (5 * _m^2):tosiunitx(), "\\SI{5.0}{\\meter\\tothe{2}}" ) lu.assertEquals( (56 * _km):tosiunitx(), "\\SI{56}{\\kilo\\meter}" ) lu.assertEquals( (5 * _km/_h):tosiunitx(), "\\SI{5.0}{\\kilo\\meter\\per\\hour}" ) lu.assertEquals( (4.81 * _J / (_kg * _K) ):tosiunitx(), "\\SI{4.81}{\\joule\\per\\kilogram\\per\\kelvin}" ) lu.assertEquals( (N(2.7,0.04) * _g/_cm^3):tosiunitx(), "\\SI{2.70(4)}{\\gram\\per\\centi\\meter\\tothe{3}}" ) end function TestQuantity.addError() local l = 5*_m + 10*_s end function TestQuantity:testAdd() local l = 5*_m + 10*_m lu.assertEquals( l.value, 15 ) lu.assertEquals( l.dimension, _m.dimension ) local msg = "Error: Cannot add '5 * _m' to '10 * _s', because they have different dimensions." lu.assertErrorMsgContains(msg, TestQuantity.addError ) end function TestQuantity.subtractError() local l = 5*_m - 10*_s end function TestQuantity:testSubtract() local l = 5*_m - 15*_m lu.assertEquals( l.value, -10 ) lu.assertEquals( l.dimension, _m.dimension ) local msg = "Error: Cannot subtract '10 * _s' from '5 * _m', because they have different dimensions." lu.assertErrorMsgContains(msg, TestQuantity.subtractError ) end function TestQuantity:testUnaryMinus() local l = -5*_m lu.assertEquals( l.value, -5 ) lu.assertEquals( l.dimension, _m.dimension ) end function TestQuantity:testMultiply() local A = 5*_m * 10 * _m lu.assertEquals( A.value, 50 ) lu.assertEquals( A.dimension, (_m^2).dimension ) end function TestQuantity:testMultiplyOfTemperatures() local m_1 = 5 * _kg local m_2 = 3 * _kg local T_1 = 20 * _degC local T_2 = 40 * _degC -- if one multiplies a temperature by another quantity -- the temperature will be interpreted as a temperature difference local T_m = ( (m_1*T_1 + m_2*T_2) / (m_1 + m_2) ):to(_degC, false) lu.assertEquals( T_m.value, 27.5 ) local m_1 = 5 * _kg local m_2 = 3 * _kg local T_1 = 20 * _degF local T_2 = 40 * _degF -- if one multiplies a temperature by another quantity -- the temperature will be interpreted as a temperature difference. local T_m = ( (m_1*T_1 + m_2*T_2) / (m_1 + m_2) ):to(_degC, false) lu.assertAlmostEquals( T_m.value, 15.277777777778, 1e-3 ) end function TestQuantity:testMultiplyWithNumber() local one = N(1,0.1) * _1 lu.assertEquals( one.value, N(1,0.1) ) lu.assertEquals( one.dimension, (_1).dimension ) local mu = ( N(1,0.1) * _u_0 ):to(_N/_A^2) lu.assertAlmostEquals( mu.value._dx, 1.256e-6, 1e-3 ) lu.assertEquals( mu.dimension, (_N/_A^2).dimension ) end function TestQuantity.divideError() local l = 7*_m / ((2*6 - 12)*_s) end function TestQuantity:testDivide() local v = 7*_m / (2*_s) lu.assertEquals( v.value, 3.5 ) lu.assertEquals( v.dimension, (_m/_s).dimension ) lu.assertError( divideError ) end -- test power function function TestQuantity:testPow() local V = (5*_m)^3 lu.assertEquals( V.value, 125 ) lu.assertEquals( V.dimension, (_m^3).dimension ) end function TestQuantity:testPowWithQuantityAsExponent() local V = (5*_m)^(24*_m / (12*_m)) lu.assertAlmostEquals( V.value, 25, 0.00001 ) lu.assertEquals( V.dimension, (_m^2).dimension ) end -- test isclose function function TestQuantity:testisclose() local rho1 = 19.3 * _g / _cm^3 local rho2 = 19.2 * _g / _cm^3 lu.assertTrue( rho1:isclose(rho2,0.1) ) lu.assertTrue( rho1:isclose(rho2,10 * _percent) ) lu.assertFalse( rho1:isclose(rho2,0.1 * _percent) ) end -- test min function function TestQuantity:testMin() local l = Q.min(-2.5,5) lu.assertEquals( l.value, -2.5 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (3 * _cm):min(5 * _dm) lu.assertEquals( l.value, 3 ) lu.assertEquals( l.dimension, _m.dimension ) local q1 = 20 * _cm local q2 = 10 * _dm local q3 = 1 * _m lu.assertEquals( Q.min(q1,q2,q3), q1 ) local q1 = 20 * _A lu.assertEquals( Q.min(q1), q1 ) local q1 = N(10,1) * _s local q2 = N(9,1) * _s lu.assertEquals( q1:min(q2), q2 ) end -- test max function function TestQuantity:testMax() local l = Q.max(-2.5,5) lu.assertEquals( l.value, 5 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (3 * _m):max(5 * _m) lu.assertEquals( l.value, 5 ) lu.assertEquals( l.dimension, _m.dimension ) local q1 = 20 * _cm local q2 = 10 * _dm local q3 = 1 * _m lu.assertEquals( Q.max(q1,q2,q3), q2 ) local q1 = 20 * _A lu.assertEquals( Q.max(q1), q1 ) local q1 = N(10,1) * _s local q2 = N(9,1) * _s lu.assertEquals( q1:max(q2), q1 ) end -- test absolute value function function TestQuantity:testAbs() local l = Q.abs(-2.5) lu.assertEquals( l.value, 2.5 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (-45.3 * _m):abs() lu.assertEquals( l.value, 45.3 ) lu.assertEquals( l.dimension, _m.dimension ) local l = ( N(-233,3) * _m^2):abs() lu.assertEquals( l.value, N(233,3) ) lu.assertEquals( l.dimension, (_m^2).dimension ) end -- test square root function function TestQuantity:testSqrt() local l = (103 * _cm^2):sqrt() lu.assertEquals( l.value, 10.148891565092219 ) lu.assertEquals( l.dimension, _m.dimension ) local l = (N(103,2) * _cm^2):sqrt() lu.assertAlmostEquals( l.value._x, 10.148891565092219, 0.0001 ) lu.assertAlmostEquals( l.value._dx, 0.098532927816429, 0.0001 ) lu.assertEquals( l.dimension, _m.dimension ) end -- test logarithm function function TestQuantity.logError() local l = (100 * _s):log() end function TestQuantity:testLog() lu.assertError( logError ) local l = Q.log(2) lu.assertAlmostEquals( l.value, 0.693147180559945, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = Q.log(3 * _1) lu.assertAlmostEquals( l.value, 1.09861228866811, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = Q.log(2, 10) lu.assertAlmostEquals( l.value, 0.301029995663981, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = Q.log(4, 10 * _1) lu.assertAlmostEquals( l.value, 0.602059991327962, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = Q.log(4 * _1, 8 * _1) lu.assertAlmostEquals( l.value, 0.666666666666666, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (100 * _1):log() lu.assertAlmostEquals( l.value, 4.605170185988091, 0.0001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (600 * _m / (50000 * _cm)):log() lu.assertAlmostEquals( l.value, 0.182321556793955, 0.0001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = ( N(100,0) * _1 ):log() lu.assertAlmostEquals( l.value._x, 4.605170185988091, 0.0001 ) lu.assertEquals( l.dimension, _1.dimension ) end function TestQuantity:testLogAdvanced() local L_I1 = N(125,0.1) * _dB local I_0 = 1e-12 * _W/_m^2 local I_1 = ( I_0 * 10^(L_I1/10) ):to(_W/_m^2) local l = (I_1/I_0):log(10) lu.assertAlmostEquals( l.value._x, 12.5, 0.001 ) end -- test exponential function function TestQuantity.expError() local l = (100 * _s):log() end function TestQuantity:testExp() lu.assertError( expError ) local l = (-2*_m/(5*_m)):exp() lu.assertAlmostEquals( l.value, 0.670320046035639, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = Q.exp(2) lu.assertAlmostEquals( l.value, 7.38905609893065, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) end -- test sine function function TestQuantity.sinError() local l = (100 * _s):sin() end function TestQuantity:testSin() lu.assertError( sinError ) local l = Q.sin(1.570796326794897) lu.assertAlmostEquals( l.value, 1, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (45 * _deg):sin() lu.assertAlmostEquals( l.value, 0.707106781186548, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) end -- test cosine function function TestQuantity.cosError() local l = (100 * _s):cos() end function TestQuantity:testCos() lu.assertError( cosError ) local l = Q.cos(0) lu.assertAlmostEquals( l.value, 1, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (50 * _deg):cos() lu.assertAlmostEquals( l.value, 0.642787609686539, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) end -- test tangens function function TestQuantity.tanError() local l = (100 * _s):tan() end function TestQuantity:testTan() lu.assertError( tanError ) local l = Q.tan(0.785398163397448) lu.assertAlmostEquals( l.value, 1, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (50 * _deg):tan() lu.assertAlmostEquals( l.value, 1.19175359259421, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) end -- test asin function function TestQuantity.asinError() local l = (100 * _s):asin() end function TestQuantity:testAsin() lu.assertError( asinError ) local l = Q.asin(0.785398163397448) lu.assertAlmostEquals( l.value, 0.903339110766512, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (0.5 * _1):asin() lu.assertAlmostEquals( l.value, 0.523598775598299, 0.000001 ) lu.assertEquals( l.dimension, _rad.dimension ) end -- test acos function function TestQuantity.acosError() local l = (100 * _s):acos() end function TestQuantity:testAcos() lu.assertError( acosError ) local l = Q.acos(0.785398163397448) lu.assertAlmostEquals( l.value, 0.667457216028384, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (0.5 * _1):acos() lu.assertAlmostEquals( l.value, 1.047197551196598, 0.000001 ) lu.assertEquals( l.dimension, _rad.dimension ) end -- test atan function function TestQuantity.atanError() local l = (100 * _s):atan() end function TestQuantity:testAtan() lu.assertError( atanError ) local l = Q.atan(0.785398163397448) lu.assertAlmostEquals( l.value, 0.665773750028354, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (0.5 * _1):atan() lu.assertAlmostEquals( l.value, 0.463647609000806, 0.000001 ) lu.assertEquals( l.dimension, _rad.dimension ) end -- test sinh function function TestQuantity.sinhError() local l = (100 * _s):sinh() end function TestQuantity:testSinh() lu.assertError( sinhError ) local l = Q.sinh(2) lu.assertAlmostEquals( l.value, 3.626860407847019, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (0.75 * _1):sinh() lu.assertAlmostEquals( l.value, 0.82231673193583, 1e-9 ) local l = (N(0.75,0.01) * _1):sinh() lu.assertAlmostEquals( l.value:__tonumber(), 0.82231673193583, 1e-9 ) end -- test cosh function function TestQuantity.coshError() local l = (100 * _s):cosh() end function TestQuantity:testCosh() lu.assertError( coshError ) local l = Q.cosh(2) lu.assertAlmostEquals( l.value, 3.762195691083631, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (0.25 * _1):cosh() lu.assertAlmostEquals( l.value, 1.031413099879573, 1e-9 ) local l = (N(0.25,0.01) * _1):cosh() lu.assertAlmostEquals( l.value:__tonumber(), 1.031413099879573, 1e-9 ) end -- test tanh function function TestQuantity.tanhError() local l = (100 * _s):tanh() end function TestQuantity:testTanh() lu.assertError( tanhError ) local l = Q.tanh(2) lu.assertAlmostEquals( l.value, 0.964027580075817, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (0.5 * _1):tanh() lu.assertAlmostEquals( l.value, 0.46211715726001, 1e-9 ) local l = (N(0.5,0.01) * _1):tanh() lu.assertAlmostEquals( l.value:__tonumber(), 0.46211715726001, 1e-9 ) end -- test asinh function function TestQuantity.asinhError() local l = (100 * _s):asinh() end function TestQuantity:testAsinh() lu.assertError( asinhError ) local l = Q.asinh(1) lu.assertAlmostEquals( l.value, 0.881373587019543, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (2 * _1):asinh() lu.assertAlmostEquals( l.value, 1.44363547517881, 1e-9 ) local l = (N(2,0.01) * _1):asinh() lu.assertAlmostEquals( l.value:__tonumber(), 1.44363547517881, 1e-9 ) end -- test acosh function function TestQuantity.acoshError() local l = (100 * _s):acosh() end function TestQuantity:testAcosh() lu.assertError( acoshError ) local l = Q.acosh(1.5) lu.assertAlmostEquals( l.value, 0.962423650119207, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (1.2 * _1):acosh() lu.assertAlmostEquals( l.value, 0.622362503714779, 1e-9 ) local l = (N(1.2,0.01) * _1):acosh() lu.assertAlmostEquals( l.value:__tonumber(), 0.622362503714779, 1e-9 ) end -- test atanh function function TestQuantity.atanhError() local l = (100 * _s):atanh() end function TestQuantity:testAtanh() lu.assertError( atanhError ) local l = Q.atanh(0.5) lu.assertAlmostEquals( l.value, 0.549306144334055, 0.000001 ) lu.assertEquals( l.dimension, _1.dimension ) local l = (0.9 * _1):atanh() lu.assertAlmostEquals( l.value, 1.47221948958322, 1e-9 ) local l = (N(0.9,0.01) * _1):atanh() lu.assertAlmostEquals( l.value:__tonumber(), 1.47221948958322, 1e-9 ) end -- test less than function TestQuantity:testLessThan() local Pi = 3.1415926535897932384626433832795028841971693993751 local l_D = 5 * _m local d_D = N(0.25,0.001) * _mm local d = N(5,0.01) * _cm local l = N(10,0.01) * _cm local I_max = N(1,0.001) * _A local B = N(0.5,0.001) * _mT local Nw = ( B*l/(_u_0*I_max) ):to(_1) local N_max = (l/d_D):to(_1) local l_max = (Nw*Pi*d):to(_m) lu.assertTrue(l_D < l_max) end -- test less than zero function TestQuantity:testLessThanZero() lu.assertTrue(1*_1 > 0) lu.assertTrue(0*_1 > -1) lu.assertTrue(-1*_1 < 0) lu.assertTrue(0 < 1*_1) lu.assertTrue(-1 < 0*_1) lu.assertTrue(0 > -1*_1) end return TestQuantity