summaryrefslogtreecommitdiff
path: root/Master/texmf-dist/doc/lualatex
diff options
context:
space:
mode:
authorKarl Berry <karl@freefriends.org>2020-09-06 21:19:54 +0000
committerKarl Berry <karl@freefriends.org>2020-09-06 21:19:54 +0000
commitda539a5083a4e436ce339377122bf143859ab814 (patch)
treee57c25090a1763a3cf8028b42eeb23ad326ed580 /Master/texmf-dist/doc/lualatex
parentba5b524ffb7b31ed52f06d40fa7cd2877124e46a (diff)
lua-physical (6sep20)
git-svn-id: svn://tug.org/texlive/trunk@56278 c570f23f-e606-0410-a88d-b1316a301751
Diffstat (limited to 'Master/texmf-dist/doc/lualatex')
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/LICENSE21
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/README.md403
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.bib57
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.pdfbin0 -> 311374 bytes
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.tex3086
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua2759
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua36
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua238
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua964
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua314
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua515
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua609
-rw-r--r--Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua95
13 files changed, 9097 insertions, 0 deletions
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/LICENSE b/Master/texmf-dist/doc/lualatex/lua-physical/LICENSE
new file mode 100644
index 00000000000..408f06f14d2
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+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.
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/README.md b/Master/texmf-dist/doc/lualatex/lua-physical/README.md
new file mode 100644
index 00000000000..e4696639aff
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/README.md
@@ -0,0 +1,403 @@
+# lua-physical
+
+Author: Thomas Jenni
+
+Version: 1.0.1
+
+Date: 2020-09-05
+
+License: MIT
+
+This is a pure Lua library which provides functions and object for doing computation with physical quantities.
+
+In general, a physical quantity consists of a numerical value called magnitude and a unit. There are restrictions to the allowed mathematical operations with physical quantities. For example the quantity time cannot be added to the quantity mass. Furthermore, new quantities can be obtained by multiplication or division from other base quantities. The density is such an example. It can be calculated by dividing the mass of a body by its volume.
+
+
+## Usage
+
+The library can be loaded with the command `require("physical")`. Like probably no other Lua library, lua-physical pollutes the global namespace with objects that represent physical units. This is in order to simplify the entry of physical quantities. By convention, each unit starts with an underscore in order to distinguish them from other variables. A basic example is the following:
+
+```
+> require("physical")
+> a = 1.4 * _m
+> b = 2 * _m
+> A = (a*b):to(_m^2)
+> print(A)
+2.8 * _m^2
+```
+
+In the above example, two length a and b are defined and then multiplied. The result is an area. The unit of this area is `_m*_m` and has to be explicitly converted to `_m^2` be the `:to()` command. The next example is slightly more complicated.
+
+```
+> require("physical")
+> m1 = 22 * _kg
+> m2 = 5.972e24 * _kg
+> r = 6371 * _km
+> F_G = (_Gc * m1 * m2 / r^2):to(_N)
+> print(F_G)
+2.16032(10)e2 * _N
+```
+
+The goal of the above example is to calculate the gravitational force on a body with mass `22 kg` sitting on the surface of the earth. As one can see, the result is given with an uncertainty in parentheses. This is because the gravitational constant is not exactly known. Lua-physical can deal with uncertainties. One can give an uncertainty explicitly by instantiating `physical.Number()`.
+
+### Temperature units
+Temperatur units as `_degC` or `_degF` are treated as temperature differences. An absolute conversion can be done and has to be called explicitly. The `:to()`-function has therefore a second argument, which is by default false. That means temperatures are by default treated as temperature differences. If it is true, absolute conversion is used.
+```
+> physical = require("physical")
+> T_1 = 15 * _degC
+> print(T_1)
+15 * _degC
+> print(T_1:to(_K))
+15.0 * _K
+> print(T_1:to(_K,true))
+288.15 * _K
+```
+
+### Uncertainty
+
+```
+> physical = require("physical")
+> a = physical.Number(2,0.1) * _m
+> A = (a^2):to(_m^2)
+> V = (a^3):to(_m^3)
+> print(a)
+2.00(10) * _m
+> print(A)
+4.0(4) * _m^2
+> print(V)
+8.0(12) * _m^3
+```
+
+The uncertainty gets propagated by the Gaussian rule for completely uncorrelated uncertainties, i.e. they are added in quadrature. In the above example it is assumed, that the three sides of the cube were measured independently from each other and that the uncertainties of these measurements are not correlated. If one prefers another way of printing uncertainties, there are a few formatting options.
+```
+> physical = require("physical")
+> local l = physical.Number(20.453,0.002) * _m
+
+> physical.Number.format = physical.Number.SCIENTIFIC
+
+> physical.Number.seperateUncertainty = true
+> print(l)
+(2.0453 +/- 0.0002)e1 * _m
+
+> physical.Number.seperateUncertainty = false
+> print(l)
+2.0453(2)e1 * _m
+
+> physical.Number.format = physical.Number.DECIMAL
+
+> physical.Number.seperateUncertainty = true
+> print(l)
+(20.453 +/- 0.002) * _m
+
+> physical.Number.seperateUncertainty = false
+20.453(2) * _m
+```
+
+One can define, whether the uncertainty should be printed in the plus-minus notation or in the parentheses notation.
+
+### Latex
+
+Since Latex now supports lua, this library is able to generate latex output. It uses the siunitx package notation, see [ctan.org](https://www.ctan.org/pkg/siunitx?lang=en).
+
+```
+> physical = require("physical")
+> E = 210 * _MeV
+> print(E:tosiunitx())
+\SI{210}{\mega\electronvolt}
+```
+
+
+
+
+### Physical Data
+Besides some physical constans, lua-physical has an isotope database. The data was taken from the Atomic Mass Evaluation - AME2016 by the [Chinese Atomic Mass Data Center](https://www-nds.iaea.org/amdc/). The data was parsed and converted into a Lua table. One can access the data via the command `physical.Data.Isotope(name,key)`.
+
+```
+> physical = require("physical")
+
+> E_b = physical.Data.Isotope("235U","BindingEnergyPerNucleon")
+> print(E_b)
+7.590914(5)e3 * _keV
+
+> T_12 = physical.Data.Isotope("210Po","HalfLife")
+> print(T_12:to(_d))
+1.38376(2)e2 * _d
+```
+
+
+
+
+
+## List of Units and Prefixes
+
+### Base Units
+The SI defines seven base units. For dimensionless quantities the unit `_1` can be used.
+
+| Symbol | Name | Dimension |
+| -------|----------|---------------------|
+| `_1` | Number | Dimensionless |
+| `_m` | Meter | Length |
+| `_kg` | Kilogram | Mass |
+| `_s` | Second | Time |
+| `_A` | Ampere | Electric Current |
+| `_K` | Kelvin | Temperature |
+| `_mol` | Mole | Amount of Substance |
+| `_cd` | Candela | Luminous Intensity |
+
+Source: (NIST)[http://physics.nist.gov/cuu/Units/units.html]
+
+
+### SI-Prefixes
+Most of the SI Units can have prefixes, i.e. `_km, _hL, _ms, _uJ`.
+
+| Symbol | Name | Factor | | Symbol | Name | Factor |
+| -------|-------|--------|---| -------|-------|--------|
+| `Y` | Yotta | 10^24 | | `y` | Yocto | 10^-24 |
+| `Z` | Zetta | 10^21 | | `z` | Zepto | 10^-21 |
+| `E` | Exa | 10^18 | | `a` | Atto | 10^-18 |
+| `P` | Peta | 10^15 | | `f` | Femto | 10^-15 |
+| `T` | Tera | 10^12 | | `p` | Pico | 10^-12 |
+| `G` | Giga | 10^9 | | `n` | Nano | 10^-9 |
+| `M` | Mega | 10^6 | | `u` | Micro | 10^-6 |
+| `k` | Kilo | 10^3 | | `m` | Milli | 10^-3 |
+| `h` | Hecto | 10^2 | | `c` | Centi | 10^-2 |
+| `da` | Deca | 10^1 | | `d` | Deci | 10^-1 |
+
+Source: (NIST)[http://physics.nist.gov/cuu/Units/prefixes.html]
+
+
+### Derived SI Units
+| Symbol | Name | Definition | Dimension |
+| --------|----------------|-----------------|-----------------------------|
+| `_rad` | Radian | `_1` | Plane Angle (Dimensionless) |
+| `_sr` | Steradian | `_rad^2` | Solid Angle (Dimensionless) |
+| `_Hz` | Hertz | `1/_s` | Frequency |
+| `_N` | Newton | `_kg*_m/_s^2` | Force |
+| `_Pa` | Pascal | `_N/_m^2` | Pressure |
+| `_J` | Joule | `_N*_m` | Energy |
+| `_W` | Watt | `_J/_s` | Power |
+| `_C` | Coulomb | `_A*_s` | Electric Charge |
+| `_V` | Volt | `_J/_C` | Electric Potential |
+| `_F` | Farad | `_C/_V` | Electric Capacitance |
+| `_Ohm` | Ohm | `_V/_A` | Electric Resistance |
+| `_S` | Siemens | `_A/_V` | Electric Conductance |
+| `_Wb` | Weber | `_V*_s` | Magnetic Flux |
+| `_T` | Tesla | `_Wb/_m^2` | Magnetic Flux Density |
+| `_H` | Henry | `_Wb/_A` | Inductance |
+| `_lm` | Lumen | `_cd*_sr` | Luminous Flux |
+| `_lx` | Lux | `_lm/_m^2` | Illuminance |
+| `_Bq` | Becquerel | `1/_s` | Radioactivity |
+| `_Gy` | Gray | `_J/_kg` | Absorbed Dose |
+| `_Sv` | Sievert | `_J/_kg` | Equivalent Dose |
+| `_kat` | katal | `_mol/_s` | Catalytic Activity |
+| `_degC` | Degree Celsius | `x/_K - 273.15` | Temperature |
+
+Source: (NIST)[http://physics.nist.gov/cuu/Units/units.html]
+
+### Mathematical Constants
+
+| Symbol | Name | Definition |
+| --------|--------------|-------------------------------------------------------|
+| `_Pi` | pi | `3.1415926535897932384626433832795028841971693993751` |
+| `_E` | eulernumber | `2.7182818284590452353602874713526624977572470936999` |
+
+## Physical Constants
+
+| Symbol | Name | Definition |
+| ----------|---------------------------------------|-------------------------------------|
+| `_c` | Speed of Light | `299792458 * _m/_s` |
+| `_Gc` | Gravitational Constant | `6.67408(31)e-11 * _m^3/(_kg*_s^2)` |
+| `_h_P` | Planck Constant | `6.626070040(81)e-34 * _J*_s` |
+| `_h_Pbar` | Reduced Planck Constant | `_hP/(2*Pi)` |
+| `_e` | Elementary Charge | `1.6021766208(98)e-19 * _C` |
+| `_u_0` | Vacuum Permeability | `4e-7*Pi * _N*_A^2` |
+| `_e_0` | Vacuum Permitivity | `1/(_u0*_c^2)` |
+| `_u` | Atomic Mass Unit | `1.66053904(2)e-27 * _kg` |
+| `_m_e` | Electron Rest Mass | `9.10938356(11)e-31 * _kg` |
+| `_m_p` | Proton Mass | `1.672621898(21)e-27 * _kg` |
+| `_m_n` | Neutron Mass | `1.674927471(21)e-27 * _kg` |
+| `_u_B` | Bohr Magneton | `_e*_hPbar/(2*_m_e)` |
+| `_u_N` | Nuclear Magneton | `_e*_hPbar/(2*_m_p)` |
+| `_alpha` | Finestructure Constant | `_u0*_e^2*_c/(2*_hP)` |
+| `_Ry` | Rydberg Constant | `_alpha^2*_m_e*_c/(2*_hP)` |
+| `_N_A` | Avogadro Constant | `6.022140857(74)e23 /_mol` |
+| `_R` | Molar Gas Constant | `8.3144598(48) * _J/(_K*_mol)` |
+| `_sigma` | Stefan-Boltzmann Constant | `Pi^2*_k_B^4/(60*_h_Pbar^3*_c^2)` |
+| `_g_0` | Standard Acceleration of Gravity | `9.80665 * _m/_s^2` |
+
+Source: (NIST)[http://physics.nist.gov/cuu/Constants/]
+
+## Nominal Astronomical Units
+
+| Symbol | Name | Definition |
+| ----------|---------------------------------------|-------------------------------------|
+| `_R_sun` | Nominal Solar Radius | `6.957e8 * _m` |
+| `_S_sun` | Nominal Solar Irradiance | `1361 * _W/_m^2` |
+| `_L_sun` | Nominal Solar Luminosity | `3.828e26 * _W` |
+| `_T_sun` | Nominal Solar Effective Temperature | `5772 * _K` |
+| `_GM_sun` | Nominal Solar Mass Parameter | `1.3271244e20 * _m^3 * _s^-2` |
+| `_Re_E` | Nominal Terrestrial Equatorial Radius | `6.3781e6 * _m` |
+| `_Rp_E` | Nominal Terrestrial Polar Radius | `6.3568e6 * _m` |
+| `_GM_J` | Nominal Terrestrial Mass Parameter | `3.986 004e14 * _m^3 * _s^-2` |
+| `_Re_J` | Nominal Jovian Equatorial Radius | `7.1492e7 * _m` |
+| `_Rp_J` | Nominal Jovian Polar Radius | `6.6854e7 * _m` |
+| `_GM_J` | Nominal Jovian Mass Parameter | `1.2668653e17 * _m^3 * _s^-2` |
+
+Source: (IAU 2015 Resolution B3)[https://www.iau.org/static/resolutions/IAU2015_English.pdf]
+
+### Non-SI Units accepted for use with the SI
+
+| Symbol | Name | Definition | Dimension |
+| -------------|---------------------------------|---------------------|-----------------------------|
+| `_percent` | Percent | `0.01 * _1` | Dimensionless |
+| `_permille` | Permille | `0.001 * _1` | Dimensionless |
+| `_dB` | Decibel | `_1` | Dimensionless |
+| `_deg` | Degree | `(Pi/180) * _rad` | Plane Angle (Dimensionless) |
+| `_arcmin` | Arc Minute | `_deg/60` | Plane Angle (Dimensionless) |
+| `_arcsec` | Arc Second | `_arcmin/60` | Plane Angle (Dimensionless) |
+| `_au` | Astronomical Unit | `149597870700 * _m` | Length |
+| `_ly` | Lightyear | `_c*_a` | Length |
+| `_pc` | Parsec | `(648000/Pi)*_au` | Length |
+| `_angstrom` | Angstrom | `1e-10 * _m` | Length |
+| `_fermi` | Fermi | `1e-15 * _m` | Length |
+| `_are` | Are | `1e2 * _m^2` | Area |
+| `_hectare` | Hectare | `1e4 * _m^2` | Area |
+| `_barn` | Barn | `1e-28 * _m^2` | Area |
+| `_L` | Liter | `0.001 * _m^3` | Volume |
+| `_t` | Tonne | `1000 * _kg` | Mass |
+| `_svedberg` | Svedberg | `1e-13 * _s` | Time |
+| `_min` | Minute | `60 * _s` | Time |
+| `_h` | Hour | `60 * _min` | Time |
+| `_d` | Day | `24 * _h` | Time |
+| `_wk` | Week | `7 * _d` | Time |
+| `_a` | Julian Year | `365.25 * _d` | Time |
+| `_bar` | Bar | `100000 * _Pa` | Pressure |
+| `_atm` | Standard Atmosphere | `101325 * _Pa` | Pressure |
+| `_at` | Technical Atmosphere | `_g0 * _kg/_m^2` | Pressure |
+| `_mmHg` | Millimeter of Mercury | `133.322387415*_Pa` | Pressure |
+| `_cal` | Thermochemical Calorie | `4.184 * _J` | Energy |
+| `_cal_IT` | International Calorie | `4.1868 * _J` | Energy |
+| `_g_TNT` | Gram of TNT | `1e3 * _cal` | Energy |
+| `_t_TNT` | Ton of TNT | `1e9 * _cal` | Energy |
+| `_eV` | Electron-Volt | `_e * _V` | Energy |
+| `_Wh` | Watt-Hour | `_W*_h` | Energy |
+| `_VA` | Volt-Ampere | `_V*_A` | Power |
+| `_PS` | Metric Horsepower | `75*_g0*_kg*_m/_s` | Power |
+| `_Ah` | Ampere-Hour | `_A*_h` | Electric Charge |
+| `_PI` | Poiseuille | `_Pa*_s` | Dynamic Viscosity |
+
+Source: (NIST)[http://physics.nist.gov/cuu/Units/outside.html]
+
+### Other Metric Units
+
+| Symbol | Name | Definition | Dimension |
+| --------|-------------------|--------------------|-----------------------------|
+| `_tsp` | Metric Teaspoon | `0.005 * _L` | Volume |
+| `_Tbsp` | Metric Tablespoon | `3 * _tsp` | Volume |
+| `_gon` | Gradian | `Pi/200*_rad` | Plane Angle (Dimensionless) |
+| `_tr` | Turn | `2*Pi*_rad` | Plane Angle (Dimensionless) |
+| `_sp` | Spat | `4*Pi*_sr` | Solid Angle (Dimensionless) |
+| `_kp` | Kilopond | `_g0*_kg` | Force |
+| `_Ci` | Curie | `3.7e10 * _Bq` | Radioactivity |
+| `_rd` | Rad | `0.01 * _Gy` | Absorbed Dose |
+| `_rem` | Rem | `0.01 * _Sv` | Equivalent Dose |
+| `_Ro` | Roentgen | `2.58e-4 * _C/_kg` | Ionic Dose |
+
+Source: (NIST)[http://physics.nist.gov/cuu/Units/outside.html]
+
+## Imperial Units
+
+| Symbol | Name | Definition | Dimension |
+| ----------|-----------------------|----------------------------|-------------|
+| `_in` | Inch | `0.0254 * _m` | Length |
+| `_th` | Thou | `0.001 * _in` | Length |
+| `_pc` | Pica | `_in/6` | Length |
+| `_pt` | Point | `_in/72` | Length |
+| `_hh` | Hand | `4 * _in` | Length |
+| `_ft` | Foot | `12 * _in` | Length |
+| `_yd` | Yard | `3 * _ft` | Length |
+| `_rd` | Rod | `5.5 * _yd` | Length |
+| `_ch` | Chain | `4 * _rd` | Length |
+| `_fur` | Furlong | `10 * _ch` | Length |
+| `_mi` | Mile | `8 * _fur` | Length |
+| `_lea` | League | `3 * _mi` | Length |
+| `_nmi` | Nautical Mile | `1852 * _m` | Length |
+| `_nlea` | Nautical League | `3 * _nmi` | Length |
+| `_cb` | Cable | `_nmi/10` | Length |
+| `_ftm` | Fathom | `6 * _ft` | Length |
+| `_ac` | Acre | `43560 * _ft^2` | Area |
+| `_gal` | Gallon | `4.54609 * _L` | Volume |
+| `_qt` | Quart | `_gal/4` | Volume |
+| `_pint` | Pint | `_qt/2` | Volume |
+| `_cup` | Cup | `_pint/2` | Volume |
+| `_gi` | Gill | `_pint/4` | Volume |
+| `_fl_oz` | Fluid Ounce | `_gi/5` | Volume |
+| `_fl_dr` | Fluid Dram | `_fl_oz/8` | Volume |
+| `_gr` | Grain | `64.79891 * _mg` | Mass |
+| `_lb` | Pound | `7000 * _gr` | Mass |
+| `_oz` | Ounce | `_lb/16` | Mass |
+| `_dr` | Dram | `_oz/256` | Mass |
+| `_st` | Stone | `14 * _lb` | Mass |
+| `_qtr` | Quarter | `2*_st` | Mass |
+| `_cwt` | Hundredweight | `4*_qtr` | Mass |
+| `_ton` | Long Ton | `20*_cwt` | Mass |
+| `_lb_t` | Troy Pound | `5760*_gr` | Mass |
+| `_oz_t` | Troy Ounce | `_lb_t/12` | Mass |
+| `_pwt` | Pennyweight | `24 * _gr` | Mass |
+| `_fir` | Firkin | `56*_lb` | Mass |
+| `_sen` | Sennight | `7*_d` | Time |
+| `_ftn` | Fortnight | `14*_d` | Time |
+| `_degF` | Degree Fahrenheit | `(x/_K + 459.67)*(5/9)` | Temperature |
+| `_degR` | Degree Rankine | `(x/_K)*(5/9)` | Temperature |
+| `_kn` | Knot | `_nmi/_h` | Velocity |
+| `_lbf` | Pound Force | `_lb*_g0` | Force |
+| `_pdl` | Poundal | `_lb*_ft/_s^2` | Force |
+| `_slug` | Slug | `_lbf*_s^2/_ft` | Mass |
+| `_psi` | Pound per Square Inch | `_lbf/_in^2` | Pressure |
+| `_BTU_it` | British Thermal Unit | `1055.05585262 * _J` | Energy |
+| `_BTU_th` | British Thermal Unit | `(1897.83047608/1.8) * _J` | Energy |
+| `_hp` | Horsepower | `33000*_ft*_lbf/_min` | Power |
+
+Source: (Wikipedia)[https://en.wikipedia.org/wiki/Imperial_units]
+
+### US Customary and Survey Units
+
+| Symbol | Name | Definition | Dimension |
+| ------------|-------------------|------------------|-----------|
+| `_in_US` | US Survey Inch | `_m/39.37` | Length |
+| `_hh_US` | US Survey Hand | `4 * _in_US` | Length |
+| `_ft_US` | US Survey Foot | `3 * _hh_US` | Length |
+| `_li_US` | US Survey Link | `0.66 * _ft_US` | Length |
+| `_yd_US` | US Survey Yard | `3 * _ft_US` | Length |
+| `_rod_US` | US Survey Rod | `5.5 * _yd_US` | Length |
+| `_ch_US` | US Survey Chain | `4 * _rd_US` | Length |
+| `_fur_US` | US Survey Furlong | `10 * _ch_US` | Length |
+| `_mi_US` | US Survey Mile | `8 * _fur_US` | Length |
+| `_lea_US` | US Survey League | `3 * _mi_US` | Length |
+| `_ftm_US` | US Survey Fathom | `72 * _in_US` | Length |
+| `_cbl_US` | US Survey Cable | `120 * _ftm_US` | Length |
+| `_ac_US` | US Survey Acre | `_ch * _fur_US` | Area |
+| `_gal_US` | US Gallon | `231 * _in^3` | Volume |
+| `_qt_US` | US Quart | `_gal_US/4` | Volume |
+| `_pint_US` | US Pint | `_qt_US/2` | Volume |
+| `_cup_US` | US Cup | `_pint_US/2` | Volume |
+| `_gi_US` | US Gill | `_pint_US/4` | Volume |
+| `_fl_oz_US` | US Fluid Ounce | `_gi_US/4` | Volume |
+| `_Tbsp_US` | US Tablespoon | `_fl_oz_US/2` | Volume |
+| `_tsp_US` | US Teaspoon | `_Tbsp_US/3` | Volume |
+| `_fl_dr_US` | US Fluid Dram | `_fl_oz_US/8` | Volume |
+| `_qtr_US` | US Quarter | `25 * _lb` | Mass |
+| `_cwt_US` | US Hundredweight | `4 * _qtr_US` | Mass |
+| `_ton_US` | Short Ton | `20*_cwt_US` | Mass |
+
+Source: (Wikipedia)[https://en.wikipedia.org/wiki/United_States_customary_units]
+
+
+
+
+
+
+
+
+
+
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.bib b/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.bib
new file mode 100644
index 00000000000..f675d92457b
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.bib
@@ -0,0 +1,57 @@
+%% This BibTeX bibliography file was created using BibDesk.
+%% https://bibdesk.sourceforge.io/
+
+%% Created for Thomas Jenni at 2019-04-12 20:58:33 +0200
+
+
+%% Saved with string encoding Unicode (UTF-8)
+
+
+
+@electronic{bipm18,
+ Author = {Bureau International des Poids et Mesures},
+ Date-Added = {2019-04-12 20:53:47 +0200},
+ Date-Modified = {2019-04-12 20:58:26 +0200},
+ Month = {November},
+ Title = {Resolutions of the 26th CGPM},
+ Url = {https://www.bipm.org/utils/common/pdf/CGPM-2018/26th-CGPM-Resolutions.pdf},
+ Year = {2018}}
+
+@electronic{nist19,
+ Date-Added = {2019-04-12 17:05:08 +0200},
+ Date-Modified = {2019-04-12 17:28:29 +0200},
+ Lastchecked = {12.4.2019},
+ Month = {August},
+ Title = {Webpage https://physics.nist.gov/cuu/index.html},
+ Url = {https://physics.nist.gov/cuu/index.html},
+ Year = {2019},
+ Bdsk-Url-1 = {https://physics.nist.gov/cuu/index.html}}
+
+@booklet{bipm06,
+ Author = {Bureau International des Poids et Mesures},
+ Date-Added = {2019-03-19 13:45:11 +0000},
+ Date-Modified = {2019-04-12 20:57:27 +0200},
+ Title = {The International System of Units (SI)},
+ Volume = {8th edition},
+ Year = {2006}}
+
+@electronic{bipmunits18,
+ Date-Added = {2018-09-03 09:58:37 +0200},
+ Date-Modified = {2019-04-12 17:28:42 +0200},
+ Lastchecked = {3.9.2018},
+ Month = {September},
+ Title = {Webpage https://www.bipm.org/en/measurement-units/},
+ Url = {https://www.bipm.org/en/measurement-units/},
+ Year = {2018},
+ Bdsk-Url-1 = {https://www.bipm.org/en/measurement-units/}}
+
+@article{iau16,
+ Author = {{Pr{\v{s}}a} et al.},
+ Date-Modified = {2019-04-12 17:26:47 +0200},
+ Journal = {The Astronomical Journal},
+ Month = {August},
+ Pages = {41},
+ Title = {Nominal Values for Selected Solar and Planetary Quantities: IAU 2015 Resolution B3},
+ Volume = {152},
+ Year = {2016},
+ Bdsk-Url-1 = {https://doi.org/10.3847/0004-6256/152/2/41}}
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.pdf b/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.pdf
new file mode 100644
index 00000000000..52e7668c0da
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.pdf
Binary files differ
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.tex b/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.tex
new file mode 100644
index 00000000000..3beae19b749
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/lua-physical.tex
@@ -0,0 +1,3086 @@
+%!TEX program = lualatex
+
+% 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.
+
+\documentclass{ltxdoc}
+
+\usepackage[english]{babel}
+\usepackage{amsmath}
+\usepackage{lualatex-math}
+\usepackage{hyperref}
+\usepackage{luacode}
+\usepackage{listings}
+\usepackage{siunitx}
+\usepackage{tabularx}
+\usepackage{float}
+\usepackage{ulem}
+\usepackage{xcolor}
+\usepackage{tcolorbox}
+\usepackage{imakeidx}
+\usepackage{ifthen}
+\usepackage{perpage}
+\usepackage{multicol}
+
+
+% lua code and lua-physical command definitions
+\begin{luacode}
+physical = require("physical")
+N = physical.Number
+Q = physical.Quantity
+\end{luacode}
+
+\newcommand{\q}[1]{%
+ \directlua{tex.print(physical.Quantity.tosiunitx(#1,"add-decimal-zero=true,scientific-notation=fixed,exponent-to-prefix=false"))}%
+}
+
+\newcommand{\qs}[1]{%
+ \directlua{tex.print(physical.Quantity.tosiunitx(#1,"scientific-notation=true,exponent-to-prefix=false,round-integer-to-decimal=true"))}%
+}
+
+\newcommand{\qu}[1]{%
+ \directlua{tex.print(physical.Quantity.tosiunitx(#1,nil,2))}%
+}
+
+
+% config siunitx
+\sisetup{
+ output-decimal-marker = {.},
+ per-mode = symbol,
+ separate-uncertainty = true,
+ add-decimal-zero = true,
+ exponent-product = \cdot,
+ round-mode=off
+}
+
+\DeclareSIUnit\fahrenheit{\ensuremath{{}^{\circ}}F}
+
+% config listings
+\lstdefinelanguage{lua}
+{
+ morekeywords={
+ for,end,function,do,if,else,elseif,then,
+ tex.print,tex.sprint,io.read,io.open,string.find,string.explode,require
+ },
+ morecomment=[l]{--},
+ morecomment=[s]{--[[}{]]},
+ morestring=[b]''
+}
+
+\lstset{
+ numberstyle=\footnotesize\color{green!50!black},
+ keywordstyle=\ttfamily\bfseries\color{black},
+ basicstyle=\ttfamily\footnotesize,
+ commentstyle=\itshape\color{gray},
+ stringstyle=\ttfamily,
+ tabsize=2,
+ numbers=right,
+ showstringspaces=false,
+ breaklines=true,
+ breakindent=30pt,
+ morekeywords={},
+ abovecaptionskip=11pt,
+ belowcaptionskip=11pt,
+ xleftmargin=20pt,
+ xrightmargin=25pt,
+ frame=single,
+ framerule=0.5pt,
+ framesep=5pt,
+ frameround=tttt,
+ framexleftmargin=15pt,
+ framexrightmargin=20pt,
+ rulecolor=\color{green!50!black},
+ mathescape=false,
+ captionpos=t,
+ escapechar=`,
+ moredelim=**[is][\color{red}]{@}{@},
+}
+
+
+
+% no paragraph indent
+\setlength\parindent{0pt}
+
+% set emph italic
+\renewcommand{\emph}[1]{\textit{#1}}
+
+% lualatex logo
+\newcommand{\LuaLaTeX}{Lua\LaTeX}
+
+% left bar
+\newenvironment{leftbar}
+{%
+\begin{tcolorbox}[colframe=green!50!black, colback=white, arc=5pt, boxrule=0.5pt,]%
+}
+{%
+\end{tcolorbox}%
+}
+
+% style for table header
+\newcommand\thead[1]{#1}
+
+% create index
+\newcommand{\Index}[1]{#1\index{#1}}
+\makeindex[name=cur,title={Index of Currencies}]
+\makeindex[name=unit,title={Index of Units}]
+\makeindex[name=lua,title={Index of Lua Classes and Methods}]
+
+% reset footnotes numbers for each page.
+\MakePerPage{footnote}
+
+
+
+
+\begin{document}
+
+\lstset{language=[LaTex]Tex}
+
+\title{The \textsc{lua-physical} library \\\ \\\normalsize Version 1.0.1}
+\author{Thomas Jenni}
+\date{\today}
+\maketitle
+
+
+\begin{abstract}
+\noindent |lua-physical| is a pure Lua library, which provides functions and objects for the computation of physical quantities. A physical quantity is the product of a numerical value and a physical unit. The package has been written, to simplify the creation physics problem sets. The package provides units of the SI and the imperial system. Furthermore, an almost complete set of international currencies are supported, however without realtime exchange rates. In order to display the numbers with measurement uncertainties, the package is able to perform gaussian error propagation.
+\end{abstract}
+
+
+
+\tableofcontents
+
+
+\newpage
+\section{Introduction}
+
+The author of this package is a physics teacher at the high school \emph{Kantonsschule Zug}, Switzerland. The main use of this package is to write physics problem sets. \LuaLaTeX{} does make it possible to integrate physical calculations directly. The package has been in use since 2016. Many bugs have been found and fixed. Nevertheless it still is possible, that some were not found yet. Therefore the author recommends not to use this package in industry or science. If one does so, it's the responsability of the user to check results for plausability. If the user finds some bugs, they can be reported at github.com or directly to the author (\texttt{thomas.jenni (at) ksz.ch}).
+
+
+
+
+
+
+\section{Loading}
+
+This package is a pure Lua library. Therefore one has to require it explicitly by calling |require("physical")|. For printing physical quanties, the |siunitx| is supported. It's recommended to define a macro like |\q| to convert the lua quantity object to a |siunitx| expression.
+
+The following \LaTeX{} preamble loads the |lua-physical| package and creates a macro |\q| for printing physical quantities.
+\nopagebreak
+\begin{lstlisting}[language=Tex, caption=basic preamble, label=lst:basic preamble]
+ \usepackage{lua-physical}
+ \usepackage{siunitx}
+
+ % configure siunitx
+ \sisetup{
+ output-decimal-marker = {.},
+ per-mode = symbol,
+ separate-uncertainty = true,
+ add-decimal-zero = true,
+ exponent-product = \cdot,
+ round-mode = off
+ }
+
+ % load the lua-physical package
+ \begin{luacode*}
+ physical = require("physical")
+ N = physical.Number
+ \end{luacode*}
+
+ % print a physical quantity
+ \newcommand{\q}[1]{%
+ \directlua{tex.print(physical.Quantity.tosiunitx(#1,"scientific-notation=fixed,exponent-to-prefix=false"))}%
+ }
+\end{lstlisting}
+
+
+\subsection{Dependencies}
+
+In principle this library is standalone, but it is compatible with the |siunitx| package. Calculation results can be written to \LuaLaTeX{} directly by calling the |tosiunitx()| method. If the preamble above is used, the printing is done by the |\q{}| macro.
+
+
+\subsection{License}
+This code is freely distributable under the terms of the MIT license.\\
+
+Copyright (c) 2019 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.
+
+
+
+%------------------------------------------------------------
+\newpage
+\section{Usage}
+\label{ch:usage}
+%------------------------------------------------------------
+
+Given the basic preamble, units can be used in lua code directly. By convention, all units have an underscore in front of them, i.e.\ meter is |_m|, second is |_s|. All available units are listed in section~\ref{ch:Supported Units}. The following example illustrates the use of this library.
+
+\begin{lstlisting}[caption=Velocity of a car.,label=lst:Car Velocity]
+\begin{luacode}
+ s = 10 * _m
+ t = 2 * _s
+ v = s/t
+\end{luacode}
+
+A car travels $\q{s}$ in $\q{t}$. calculate its velocity.
+$$
+ v=\frac{s}{t} = \frac{\q{s}}{\q{t}} = \uuline{\q{v}}
+$$
+\end{lstlisting}
+
+\begin{luacode}
+ s = 10 * _m
+ t = 2 * _s
+
+ v = s/t
+\end{luacode}
+
+\leftbar
+A car travels $\q{s}$ in $\q{t}$. Calculate its velocity.
+$$
+ v=\frac{s}{t} = \frac{\q{s}}{\q{t}} = \uuline{\q{v}}
+$$
+\endleftbar
+
+In the above listing~\ref{lst:Car Velocity}, the variable |s| stands for displacement and has the unit meter |_m|, the variable |t| stands for time and is given in seconds |_s|. If mathematical operations are done on them, new physical quantities are created. In the problem above, the velocity |v| is calculated by dividing |s| by |t|. The instance |v| has the derived unit \si{\m\per\s}. By using the macro |\q{}| all quantities can be printed to the \LuaLaTeX{} code directly.
+
+
+
+
+%------------------------------------------------------------
+\subsection{Unit conversion}
+
+Very often, the result of a calculation has to be converted to another unit. In listing~\ref{lst:Volume of Cuboid} the task is to calculate the volume of a cuboid. The length of the edges are given in different units. The volume is calculated by multipling all three lengths, the unit of the result is \si{\cm\mm\m}. If the unit \si{\cm\cubed} is preferred, it has to be converted explicitly. The conversion function is called |to()| and is available on all physical quantitiy instances. At first this looks a bit cumbersome. The reason of this behaviour is, that the software is not able to guess the unit of the result. In many cases, like in the example here, it's not clear what unit the result sould have. Therefore the user has always to give the target unit explicitly.
+
+\begin{lstlisting}[caption=Volume of a cuboid.,label=lst:Volume of Cuboid]
+\begin{luacode}
+ a = 12 * _cm
+ b = 150 * _mm
+ c = 1.5 * _m
+
+ V = a*b*c
+\end{luacode}
+
+Find the volume of a rectangular cuboid with lengths $\q{a}$,
+$\q{b}$ and $\q{c}$.
+$$
+ V= a \cdot b \cdot c
+ = \q{a} \cdot \q{b} \cdot \q{c}
+ = \q{V}
+ = \uuline{\q{V:to(_dm^3)}}
+$$
+\end{lstlisting}
+
+\begin{luacode}
+ a = 12 * _cm
+ b = 150 * _mm
+ c = 1.5 * _m
+
+ V = a*b*c
+\end{luacode}
+
+\leftbar
+Find the volume of a rectangular cuboid with lengths $\q{a}$, $\q{b}$ and $\q{c}$.
+$$
+ V= a \cdot b \cdot c
+ = \q{a} \cdot \q{b} \cdot \q{c}
+ = \q{V}
+ = \uuline{\q{V:to(_dm^3)}}
+$$
+\endleftbar
+
+
+
+
+%------------------------------------------------------------
+\subsubsection{Temperature Conversion}
+
+Most physical units transform linearly. Exceptions are the unit degree Celsius |_degC| and degree Fahrenheit |_degF|. These units are ambigous and can be interpreted as temperature differences or as an absolute temperatures. In the latter case, the conversion to base units is not a linear, but an affine transformation. This is because degree Celsius and degree Fahrenheit scales have their zero points at different temperatures compared to the unit Kelvin.
+
+By default |_degC| and |_degF| units are temperature differences. If one wants to have it converted absolutely, it has to be done adding / subtracting |_degC_0 = 273.15*_K| or |_degF_0 = (273.15 - 32*(5/9)) * _K|,
+the zero point temperatures of the scales.
+
+
+In the following problem, listing~\ref{lst:temperature conversion}, the task is to convert temperatures given in the unit degree Celsius and degree Fahrenheit to Kelvin.
+
+\begin{lstlisting}[caption=Temperature conversion.,label=lst:temperature conversion]
+\begin{luacode}
+ theta_1 = 110 * _degC
+ T_1 = ( theta_1 + _degC_0 ):to(_K)
+
+ T_2 = 100 * _K
+ theta_2 = ( T_2 - _degC_0 ):to(_degC)
+
+ theta_3 = 212 * _degF
+ T_3 = ( theta_3 + _degF_0 ):to(_K)
+
+ T_4 = 100 * _K
+ theta_4 = ( T_4 - _degF_0 ):to(_degF)
+
+ theta_5 = 100 * _degC
+ theta_6 = ( ( theta_5 + _degC_0 ):to(_K) - _degF_0):to(_degF)
+\end{luacode}
+
+\begin{align*}
+ \q{theta_1} &\mathrel{\widehat{=}} \q{T_1} \\
+ %
+ \q{theta_2} &\mathrel{\widehat{=}} \q{T_2} \\
+ %
+ \q{theta_3} &\mathrel{\widehat{=}} \q{T_3} \\
+ %
+ \q{theta_4} &\mathrel{\widehat{=}} \q{T_4} \\
+ %
+ \q{theta_5} &\mathrel{\widehat{=}} \q{theta_6} \\
+\end{align*}
+\end{lstlisting}
+
+\begin{luacode}
+ theta_1 = 110 * _degC
+ T_1 = ( theta_1 + _degC_0 ):to(_K)
+
+ T_2 = 100 * _K
+ theta_2 = ( T_2 - _degC_0 ):to(_degC)
+
+ theta_3 = 212 * _degF
+ T_3 = ( theta_3 + _degF_0 ):to(_K)
+
+ T_4 = 100 * _K
+ theta_4 = ( T_4 - _degF_0 ):to(_degF)
+
+ theta_5 = 100 * _degC
+ theta_6 = ( ( theta_5 + _degC_0 ):to(_K) - _degF_0):to(_degF)
+\end{luacode}
+
+\leftbar
+\begin{align*}
+ \q{theta_1} &\mathrel{\widehat{=}} \q{T_1} \\
+ %
+ \q{theta_2} &\mathrel{\widehat{=}} \q{T_2} \\
+ %
+ \q{theta_3} &\mathrel{\widehat{=}} \q{T_3} \\
+ %
+ \q{theta_4} &\mathrel{\widehat{=}} \q{T_4} \\
+ %
+ \q{theta_5} &\mathrel{\widehat{=}} \q{theta_6}
+\end{align*}
+\endleftbar
+
+
+
+
+%------------------------------------------------------------
+\subsection{Uncertainty Propagation}
+
+The package supports uncertainty propagation. To create a number with an uncertainty, an instance of |physical.Number| has to be created, see listing~\ref{lst:rectangular area}. It has to be remembered, that |N| is a alias for |physical.Number|. The first argument of the constructor |N(mean, uncertainty)| is the mean value and the second one the uncertainty of the measurement. If the proposed preamble \ref{lst:basic preamble} is used, the uncertainty is by default seperated from the mean value by a plus-minus sign.
+
+For the uncertainty propagation the gaussian formula
+$$
+ \Delta f = \sqrt{ \left(\frac{\partial f}{x_1} \cdot \Delta x_1\right)^2 + \dots + \left(\frac{\partial f}{x_n} \cdot \Delta x_2 \right)^2 }
+$$
+is used. This formula is a good estimation for the uncertainty $\Delta f$, if the quantities $x_1, \dots, x_n$ the function $f$ depends on, have no correlation. Further, the function $f$ has to change linear, if quantities $x_i$ are changed in the range of their uncertainties.
+
+
+
+\begin{lstlisting}[caption=Uncertainty in area calculation.,label=lst:rectangular area]
+\begin{luacode}
+ a = N(2,0.1) * _m
+ b = N(3,0.1) * _m
+
+ A = (a*b):to(_m^2)
+\end{luacode}
+
+Calculate the area of a rectangle with lengths $\q{a}$ and $\q{b}$.
+$$
+ A = a \cdot b
+ = \q{a} \cdot \q{b}
+ = \uuline{\q{A}}
+$$
+\end{lstlisting}
+
+\begin{luacode}
+ a = N(2,0.1) * _m
+ b = N(3,0.1) * _m
+
+ A = (a*b):to(_m^2)
+\end{luacode}
+
+\leftbar
+Calculate the area of a rectangle with lengths $\q{a}$ and $\q{b}$.
+$$
+ A = a \cdot b
+ = \q{a} \cdot \q{b}
+ = \uuline{\q{A}}
+$$
+\endleftbar
+
+
+Instead of printing the uncertainties, one can use the uncertainty calculation to provide significant digits and omit it.
+
+In the following problem, listing~\ref{lst:volume of ideal gas}, the task is to find the volume of an ideal gas. Given are pressure |p| in |_bar|, amount of substance |n| in |_mol| and temperature |T| in degree celsius |_degC|. In order to do the calculation, one has to convert |T|, which is given as an absolute temperature in degree celsius to the base unit Kelvin first. By setting |N.omitUncertainty = true|, all uncertainties are not printed.
+
+\begin{lstlisting}[caption=Volume of an ideal gas.,label=lst:volume of ideal gas]
+\begin{luacode}
+ N.omitUncertainty = true
+ p = N(1.013,0.0001) * _bar
+ n = N(1,0.01) * _mol
+ T = N(30,0.1) * _degC
+
+ V = ( n * _R * (T + _degC_0):to(_K) / p ):to(_L)
+\end{luacode}
+
+An ideal gas ($\q{n}$) has a pressure of $\q{p}$ and a temperature of $\q{T}$. Calculate the volume of the gas.
+$$
+ V=\frac{ \q{n} \cdot \q{_R} \cdot \q{(T + _degC_0):to(_K)} }{ \q{p} }
+ = \q{V}
+ = \uuline{\q{V}}
+$$
+\end{lstlisting}
+
+
+\begin{luacode}
+ N.omitUncertainty = true
+ p = N(1.013,0.0001) * _bar
+ n = N(1,0.01) * _mol
+ T = N(30,0.1) * _degC
+
+ V = ( n * _R * (T + _degC_0):to(_K) / p ):to(_L)
+\end{luacode}
+
+\leftbar
+An ideal gas ($\q{n}$) has a pressure of $\q{p}$ and a temperature of $\q{T}$. Calculate the volume of the gas.
+
+$$
+ V=\frac{ \q{n} \cdot \q{(_R*N(1,0.001)):to(_J/(_mol*_K))} \cdot \q{(T + _degC_0):to(_K)} }{ \q{p} }
+ = \uuline{\q{V}}
+$$
+\endleftbar
+
+This example shows, that the result has only two digits. If more digits are needed, the uncertainties of the given quantities should be smaller.
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{Mathematical operations}
+
+Two physical quantities with identical dimensions can be added or subtracted. The library checks the validity of those operations and throws an error if two addends haven't the same dimensions.
+%
+\begin{lstlisting}[caption={Addition and subtraction},label=lst:addition and subtraction]
+l_1 = 1 * _m
+l_2 = 2 * _cm
+t = 2 * _s
+
+l_1 + t
+Error: Cannot add '1* _m' to '2 * _s', because they have different dimensions.
+
+l_1 + l_2
+102.0 * _cm
+\end{lstlisting}
+
+New physical quantities can be created by division and multiplication. As long as no division by zero is made, no errors should occur.
+%
+\begin{lstlisting}[caption=Multiplication and Division,label=lst:multiplication and division]
+l_1 = 1 * _m
+l_2 = 2 * _cm
+
+(l_1 * l_2):to(_m^2)
+0.02 * _m^2
+
+(l_1 / l_2):to(_1)
+50.0 * _1
+\end{lstlisting}
+
+
+Physical quantities can be exponentiated. The library doesn't check, if the result has units with non integer exponents.
+%
+\begin{lstlisting}[caption=Exponentiation,label=lst:exponentiation]
+l = 5 * _m
+A = l^2
+
+A:to(_m^2)
+25.0 * _m^2
+
+A:sqrt()
+5.0 * _m
+
+A^0.5
+5.0 * _m
+\end{lstlisting}
+
+Exponential functions an the logarithms should have dimensionless arguments. The library throws an error if that's not the case.
+%
+\begin{lstlisting}[caption=Exponential function and logarithm,label=lst:exp and log]
+N_0 = 1000 * _1
+lambda = Q.log(2)/(2*_h)
+t = 50 * _min
+
+N_0 * Q.exp(-lambda * t)
+749.15353843834 * _1
+\end{lstlisting}
+
+
+
+
+
+
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\section{Supported Units}
+\label{ch:Supported Units}
+%------------------------------------------------------------
+
+All supported units are listed in this section. Subsection~\ref{ch:base units} lists the seven base units of the International System of Units (SI). In subsection~\ref{ch:constants} mathematical and physical constants are defined. The subsection~\ref{ch:coherent derived units} contains all coherent derived units from the SI system and \ref{ch:non-si accepted} those which are accepted to use with the SI.
+
+The subsection~\ref{ch:nominal astronomical units} lists nominal astronomical units, which are proposed by \cite{iau16}.
+
+Subsection~\ref{ch:non-si} lists units, which are common but outside of the SI system. The subsections~\ref{ch:imperial units} and \ref{ch:us customary units} are dedicated to imperial and U.S. customary units. The last subsection~\ref{ch:currencies} containts international currencies.
+
+
+\renewcommand{\arraystretch}{1.5}
+
+%------------------------------------------------------------
+\subsection{Prefixes}
+
+All SI units have prefixed versions, i.e.\ |_us| microsecond, |_cm| centimeter, |_mN| millinewton, see table~\ref{tab:SI prefixes}. Some units of data processing, like |_bit| have prefixes which are powers of 2. They are called binary or IEC prefixes, see table~\ref{tab:IEC prefixes} \cite[121]{bipm06}.
+
+\begin{table}[H]
+\centering
+
+\begin{multicols}{2}
+
+\begin{tabularx}{\linewidth}{%
+ >{\setlength\hsize{1\hsize}}X%
+ l%
+ >{\setlength\hsize{1\hsize}}X%
+}
+
+\thead{Prefix} & \thead{Symbol} & \thead{Definition} \\\hline
+
+yotta & |Y| & |1e24| \\
+zetta & |Z| & |1e21| \\
+exa & |E| & |1e18| \\
+peta & |P| & |1e15| \\
+tera & |T| & |1e12| \\
+giga & |G| & |1e9| \\
+mega & |M| & |1e6| \\
+kilo & |k| & |1e3| \\
+hecto & |h| & |1e2| \\
+deca & |da| & |1e1| \\
+
+\hline
+
+\end{tabularx}
+
+
+\columnbreak
+
+
+\begin{tabularx}{\linewidth}{%
+ >{\setlength\hsize{1\hsize}}X%
+ l%
+ >{\setlength\hsize{1\hsize}}X%
+}
+
+\thead{Prefix} & \thead{Symbol} & \thead{Definition} \\\hline
+
+deci & |d| & |1e-1| \\
+centi & |c| & |1e-2| \\
+milli & |m| & |1e-3| \\
+micro & |u| & |1e-6| \\
+nano & |n| & |1e-9| \\
+pico & |p| & |1e-12| \\
+femto & |f| & |1e-15| \\
+atto & |a| & |1e-18| \\
+zepto & |z| & |1e-21| \\
+yocto & |y| & |1e-23| \\
+
+\hline
+
+\end{tabularx}
+
+\end{multicols}
+
+\caption{SI prefixes \cite[121]{bipm06}}
+\label{tab:SI prefixes}
+
+\end{table}
+
+
+
+\begin{table}[H]
+\centering
+
+\begin{tabularx}{0.7\linewidth}{%
+ >{\setlength\hsize{0.5\hsize}}X%
+ l%
+ >{\setlength\hsize{1.5\hsize}}X%
+}
+
+\thead{Prefix} & \thead{Symbol} & \thead{Definition} \\\hline
+
+kibi & |Ki| & |1024| \\
+mebi & |Mi| & |1048576| \\
+gibi & |Gi| & |1073741824| \\
+tebi & |Ti| & |1099511627776| \\
+pebi & |Pi| & |1125899906842624| \\
+exbi & |Ei| & |1152921504606846976| \\
+zebi & |Zi| & |1180591620717411303424| \\
+yobi & |Yi| & |1208925819614629174706176| \\
+
+\hline
+
+\end{tabularx}
+
+\caption{IEC prefixes \cite[121]{bipm06}}
+\label{tab:IEC prefixes}
+
+\end{table}
+
+
+
+
+%------------------------------------------------------------
+\subsection{Base Units}
+\label{ch:base units}
+
+The |lua-physical| library has nine base quantities. These are the seven basis units or basis quantities of the SI system \cite{bipm18} and in addition the base quantity of information |_bit| and of currency |_EUR|. All other quantities are derived from these base units.
+
+\begin{table}[H]
+\centering
+\begin{tabularx}{\linewidth}{%
+ >{\setlength\hsize{0.5\hsize}}X%
+ l%
+ l%
+ l%
+ >{\setlength\hsize{1.5\hsize}}X%
+}
+
+\thead{Quantity} & \thead{Unit} & \thead{Symbol} & \thead{Dim.} & \thead{Definition} \\\hline
+
+number \protect\footnotemark &
+-- &
+|_1| &
+$\mathrm{1}$ &
+The dimensionless number one. \\
+
+time &
+second &
+|_s| &
+$\mathrm{T}$ &
+The SI unit of time. It is defined by taking the fixed numerical value of the caesium frequency $\Delta \nu_{Cs}$, the unperturbed ground-state hyperfine transition frequency of the caesium 133 atom, to be \num{9192631770} when expressed in the unit $\qu{_s^-1}$. \\
+
+length &
+meter &
+|_m| &
+$\mathrm{L}$ &
+The SI unit of length. It is defined by taking the fixed numercial value of the speed of light in vacuum $c$ to be $\num{299792458}$ when expressed in the unit of $\q{_m/_s}$. \\
+
+\hline
+
+\end{tabularx}
+\end{table}
+
+\footnotetext[1]{
+ The number one is a unit with dimension zero. Stricly speaking it is not a base unit.
+}
+
+
+\begin{table}[H]
+\centering
+\begin{tabularx}{\linewidth}{%
+ >{\setlength\hsize{0.5\hsize}}X%
+ l%
+ l%
+ l%
+ >{\setlength\hsize{1.5\hsize}}X%
+}
+
+\thead{Quantity} & \thead{Unit} & \thead{Symbol} & \thead{Dim.} & \thead{Definition} \\\hline
+
+mass &
+kilogram &
+|_kg| &
+$\mathrm{M}$ &
+The SI unit of mass. It is defined by taking the fixed numerical value of the Planck constant $h$ to be $\qs{(_h_P/(_J*_s)):to()}$ when expressed in $\qu{_m^2*_kg/_s}$.\\
+
+electric \newline current &
+ampere &
+|_A| &
+$\mathrm{I}$ &
+The SI unit of electric current. It is defined by taking the fixed numerical value of the elementary charge $e$ to be $\qs{(_e/_C):to()}$ when expressed in $\qu{_A*_s}$.\\
+
+thermodynamic \newline temperature &
+kelvin &
+|_K| &
+$\mathrm{K}$ \protect\footnotemark &
+The SI unit of the thermodynamic temperature. It is defineed by taking the fixed numerical value of the Boltzmann constant $k_B$ to be $\qs{(_k_B/(_J/_K)):to()}$ when expressed in $\q{_kg*_m^2*_s^-2*_K^-1}$\\
+
+amount of \newline substance &
+mole &
+|_mol| &
+$\mathrm{N}$ &
+The SI unit of amount of substance. One mole contains exactly $\qs{(_N_A*_mol):to()}$ elementary entities. This number is the fixed numerical value of the Avogadro constant $N_A$ when expressed in $\qu{1/_mol}$.\\
+
+
+luminous \newline intensity &
+candela &
+|_cd| &
+$\mathrm{J}$ &
+The SI unit of luminous intensity in a given direction. It is defined by taking the fixed numerical value of the luminous efficacy of monochromatic radiation of frequency $\qs{540e12 * _Hz}$, $K_{cd}$, to be $683$ when expressed in the unit $\qu{_cd*_sr*_kg^-1*_m^-2*_s^3}$.\\
+
+
+information &
+bit &
+|_bit| &
+$\mathrm{B}$ &
+The smallest amount of information. \\
+
+
+currency &
+euro &
+|_EUR| &
+$\mathrm{C}$ &
+The value of the currency Euro. \\\hline
+
+\end{tabularx}
+\caption{Base units}
+\end{table}
+
+
+\footnotetext[1]{
+ The SI symbol for the dimension of temperature is $\mathrm{\Theta}$, but all symbols of this library consist of roman letters, numbers and underscores only. Therefore the symbol for the dimension of the thermodynamic temperature is the letter $\mathrm{K}$.
+}
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{Constants}
+\label{ch:constants}
+
+All physical constants are taken from the NIST webpage \cite{nist19}.
+
+% print table row constant
+\newcommand{\printconstant}[2]{
+ \directlua{tex.print(#1.unit.name)} &
+ |#1| &
+ \mbox{|#2|}\index[unit]{\directlua{tex.print(#1.unit.name)} \texttt{\directlua{tex.print( strtoidx("#1") )}}}\\
+}
+
+\begin{table}[H]
+\centering
+\begin{tabularx}{\linewidth}{%
+ l%
+ l%
+ >{\setlength\hsize{1\hsize}}X%
+}
+
+\thead{Name} & \thead{Symbol} & \thead{Definition} \\\hline
+
+\printconstant{_Pi}{3.1415926535897932384626433832795028841971 * _1}
+\printconstant{_E}{2.7182818284590452353602874713526624977572 * _1}
+
+\printconstant{_c}{299792458 * _m/_s}
+
+\printconstant{_Gc}{N(6.67408e-11,3.1e-15) * _m^3/(_kg*_s^2)}
+
+\printconstant{_h_P}{6.62607015e-34 * _J*_s}
+\printconstant{_h_Pbar}{_h_P/(2*_Pi)}
+
+\printconstant{_e}{1.602176634e-19 * _C}
+
+\printconstant{_u_0}{4e-7*Pi * _N/_A^2}
+
+\printconstant{_e_0}{1/(_u_0*_c^2)}
+
+\printconstant{_u}{N(1.66053904e-27, 2e-35) * _kg}
+\printconstant{_m_e}{N(9.10938356e-31, 1.1e-38) * _kg}
+\printconstant{_m_p}{N(1.672621898e-27, 2.1e-35) * _kg}
+\printconstant{_m_n}{N(1.674927471e-27, 2.1e-35) * _kg}
+
+\printconstant{_u_B}{_e*_h_Pbar/(2*_m_e)}
+\printconstant{_u_N}{_e*_h_Pbar/(2*_m_p)}
+
+
+\printconstant{_u_e}{N(-928.4764620e-26,5.7e-32) * _J/_T}
+\printconstant{_u_p}{N(1.4106067873e-26,9.7e-35) * _J/_T}
+\printconstant{_u_n}{N(-0.96623650e-26,2.3e-26) * _J/_T}
+
+\printconstant{_alpha}{_u_0*_e^2*_c/(2*_h_P)}
+
+\printconstant{_Ry}{_alpha^2*_m_e*_c/(2*_h_P)}
+
+\printconstant{_N_A}{6.02214076e23/_mol}
+
+\printconstant{_k_B}{1.380649e-23 * _J/_K}
+
+\printconstant{_R}{N(8.3144598, 4.8e-6) * _J/(_K*_mol)}
+
+\printconstant{_sigma}{_Pi^2*_k_B^4/(60*_h_Pbar^3*_c^2)}
+
+\printconstant{_g_0}{9.80665 * _m/_s^2}
+
+
+\hline
+
+\end{tabularx}
+\caption{Physical and mathematical constants}
+\label{tab:constants}
+\end{table}
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{Coherent derived units in the SI}
+\label{ch:coherent derived units}
+
+All units in this section are coherent derived units from the SI base units with special names, \cite[118]{bipm06}.
+
+% lua function for printing dimension names.
+\begin{luacode}
+function getdim(q)
+ local str = q.dimension:__tostring()
+
+ str = string.gsub(str,"%[","")
+ str = string.gsub(str,"%]","")
+
+ return str
+end
+
+function strtoidx(str)
+ local s,n = string.gsub(str,"%_","\\_")
+ return s
+end
+\end{luacode}
+
+% print unit table
+\newcommand{\unittable}[1]{
+ \begin{table}[H]
+ \centering
+ \begin{tabularx}{\linewidth}{%
+ >{\setlength\hsize{1\hsize}}X%
+ l%
+ l%
+ >{\setlength\hsize{1\hsize}}X%
+ }
+ \thead{Quantity} & \thead{Unit} & \thead{Symbol} & \thead{Definition} \\\hline
+
+ #1
+
+ \hline
+ \end{tabularx}
+ \end{table}
+}
+
+% print unit table row
+\newcommand{\printunit}[3][]{
+ \ifthenelse{\equal{#1}{}}{
+ \directlua{tex.print(getdim(#2))}
+ }{
+ #1
+ } &
+ \directlua{tex.print(#2.unit.name)} &
+ |#2| &
+ \mbox{|#3|}\index[unit]{\directlua{tex.print(#2.unit.name)} \texttt{\directlua{tex.print( strtoidx("#2") )}}} \\
+}
+
+\unittable{
+ \printunit[Plane Angle\protect\footnotemark]{_rad}{_1}
+ \printunit[Solid Angle\protect\footnotemark]{_sr}{_rad^2}
+ \printunit{_Hz}{1/_s}
+ \printunit{_N}{_kg*_m/_s^2}
+ \printunit{_Pa}{_N/_m^2}
+ \printunit[Energy]{_J}{_N*_m}
+ \printunit{_W}{_J/_s}
+ \printunit{_C}{_A*_s}
+ \printunit{_V}{_J/_C}
+ \printunit{_F}{_C/_V}
+ \printunit{_Ohm}{_V/_A}
+ \printunit[Electric Conductance\protect\footnotemark]{_S}{_A/_V}
+ \printunit{_Wb}{_V*_s}
+ \printunit{_T}{_Wb/_m^2}
+ \printunit{_H}{_Wb/_A}
+ \printunit[Temperature\protect\footnotemark]{_degC}{_K}
+ \printunit[Luminous Flux]{_lm}{_cd*_sr}
+ \printunit{_lx}{_lm/_m^2}
+ \printunit[Activity]{_Bq}{1/_s}
+ \printunit{_Gy}{_J/_kg}
+ \printunit[Dose Equivalent]{_Sv}{_J/_kg}
+ \printunit{_kat}{_mol/_s}
+}
+
+
+\footnotetext[1]{
+ In the SI system, the quantity Plane Angle has the dimension of a number.
+}
+
+\footnotetext[2]{
+ In the SI system, the quantity Solid Angle has the dimension of a number.
+}
+
+\footnotetext[3]{
+ The unit \texttt{\_PS} stands for peta siemens and is in conflict with the metric version of the unit horsepower (german Pferdestärke). Since the latter is more common than peta siemens, \texttt{\_PS} is defined to be the metric version of horsepower.
+}
+
+\footnotetext[4]{
+ The unit \texttt{\_degC} is by default interpreted as a temperature difference.
+}
+
+
+
+
+% https://www.bipm.org/utils/common/pdf/si_brochure_8_en.pdf
+%------------------------------------------------------------
+\subsection{Non-SI units accepted for use with the SI}
+\label{ch:non-si accepted}
+
+There are a few units with dimension $\mathrm{1}$. \cite[124]{bipm06}.
+
+\unittable{
+ \printunit[Time]{_min}{60 * _s}
+ \printunit[ ]{_h}{60 * _min}
+ \printunit[ ]{_d}{24 * _h}
+
+ \printunit[Plane Angle]{_deg}{(_Pi/180) * _rad}
+ \printunit[ ]{_arcmin}{_deg/60}
+ \printunit[ ]{_arcsec}{_arcmin/60}
+
+ \printunit{_hectare}{1e4 * _m^2}
+
+ \printunit{_L}{1e-3 * _m^3}
+
+ \printunit[Mass]{_t}{1e3 * _kg}
+}
+
+
+\subsection{Nominal Astronomical Units}
+\label{ch:nominal astronomical units}
+
+The nominal values of solar, terrestrial and jovial quantities are taken from IAU Resolution B3 \cite{iau16}.
+
+\unittable{
+ \printunit[Length]{_R_S_nom}{6.957e8 * _m}
+ \printunit[Irradiance]{_S_S_nom}{1361 * _W/_m^2}
+ \printunit[Radiant Flux]{_L_S_nom}{3.828e26 * _W}
+ \printunit[Temperature]{_T_S_nom}{5772 * _K}
+ \printunit[Mass Parameter]{_GM_S_nom}{1.3271244e20 * _m^3*_s^-2}
+
+
+ \printunit[Length]{_Re_E_nom}{6.3781e6 * _m}
+ \printunit[Length]{_Rp_E_nom}{6.3568e6 * _m}
+ \printunit[Mass Parameter]{_GM_E_nom}{3.986004e14 * _m^3*_s^-2}
+
+ \printunit[Length]{_Re_J_nom}{7.1492e7 * _m}
+ \printunit[Length]{_Rp_J_nom}{6.6854e7 * _m}
+ \printunit[Mass Parameter]{_GM_J_nom}{1.2668653e17 * _m^3*_s^-2}
+}
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{Other Non-SI units}
+\label{ch:non-si}
+
+The unit Bel is only available with prefix decibel, because |_B| is the unit byte.
+
+\unittable{
+ \printunit[Length]{_angstrom}{1e-10 * _m}
+ \printunit[ ]{_fermi}{1e-15 * _m}
+
+ \printunit[Time]{_svedberg}{1e-13 * _s}
+ \printunit[ ]{_wk}{7 * _d}
+ \printunit[ ]{_a}{365.25 * _d}
+
+ \printunit[ ]{_au}{149597870700 * _m}
+ \printunit[ ]{_ls}{_c*_s}
+ \printunit[ ]{_ly}{_c*_a}
+ \printunit[ ]{_pc}{(648000/_Pi) * _au}
+
+ \printunit{_barn}{1e-28 * _m^2}
+ \printunit[ ]{_are}{1e2 * _m^2}
+
+ \printunit{_tsp}{5e-3 * _L}
+ \printunit[ ]{_Tbsp}{3 * _tsp}
+
+ \printunit[Plane Angle]{_gon}{(Pi/200) * _rad}
+ \printunit[ ]{_tr}{2*Pi * _rad}
+
+ \printunit[Solid Angle]{_sp}{4*Pi * _sr}
+
+ \printunit{_kp}{_kg*_g_0}
+
+ \printunit{_bar}{1e5 * _Pa}
+ \printunit[ ]{_atm}{101325 * _Pa}
+ \printunit[ ]{_at}{_kp/_cm^2}
+ \printunit[ ]{_mmHg}{133.322387415 * _Pa}
+ \printunit[ ]{_Torr}{(101325/760) * _Pa}
+
+ \printunit[Energy]{_cal}{4.184 * _J}
+ \printunit[ ]{_cal_IT}{4.1868 * _J}
+ \printunit[ ]{_g_TNT}{1e3 * _cal}
+ \printunit[ ]{_t_TNT}{1e9 * _cal}
+}
+
+\unittable{
+ \printunit[ ]{_eV}{_e*_V}
+ \printunit[ ]{_Ws}{_W*_s}
+ \printunit[ ]{_Wh}{_W*_h}
+
+ \printunit{_VA}{_V*_A}
+
+ \printunit{_As}{_A*_s}
+ \printunit[ ]{_Ah}{_A*_h}
+
+ \printunit[Information]{_nibble}{4 * _bit}
+ \printunit[ ]{_B}{8 * _bit}
+
+ \printunit[Information \newline Transfer Rate]{_bps}{_bit/_s}
+
+ \printunit[Number]{_percent}{1e-2 * _1}
+ \printunit[ ]{_permille}{1e-3 * _1}
+ \printunit[ ]{_ppm}{1e-6 * _1}
+ \printunit[ ]{_ppb}{1e-9 * _1}
+ \printunit[ ]{_ppt}{1e-12 * _1}
+ \printunit[ ]{_ppq}{1e-15 * _1}
+
+ \printunit[ ]{_dB}{_1}
+
+ \printunit{_PS}{75 * _g_0*_kg*_m/_s}
+
+ \printunit[Activity]{_Ci}{3.7e10 * _Bq}
+ \printunit[Absorbed Dose]{_Rad}{1e-2 * _Gy}
+ \printunit[Dose Equivalent]{_rem}{1e-2 * _Sv}
+
+ \printunit[Viscosity]{_Pl}{_Pa*_s}
+}
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{Imperial Units}
+\label{ch:imperial units}
+
+\unittable{
+ \printunit[Length]{_in}{2.54e-2 * _m}
+ \printunit[ ]{_th}{1e-3 * _in}
+ \printunit[DTP Point\protect\footnotemark]{_pt}{_in/72}
+ \printunit[ ]{_pica}{12 * _pt}
+ \printunit[ ]{_hh}{4 * _in}
+ \printunit[ ]{_ft}{12 * _in}
+ \printunit[ ]{_yd}{3 * _ft}
+ \printunit[ ]{_rd}{5.5 * _yd}
+ \printunit[ ]{_ch}{4 * _rd}
+ \printunit[ ]{_fur}{10 * _ch}
+ \printunit[ ]{_mi}{8 * _fur}
+ \printunit[ ]{_lea}{3*_mi}
+
+ \printunit[ ]{_nmi}{1852 * _m}
+ \printunit[ ]{_nlea}{3 * _nmi}
+ \printunit[ ]{_cbl}{0.1 * _nmi}
+ \printunit[ ]{_ftm}{6 * _ft}
+
+ \printunit{_kn}{_nmi/_h}
+
+ \printunit{_ac}{10 * _ch^2}
+
+ \printunit{_gal}{4.54609*_L}
+ \printunit[ ]{_qt}{_gal/4}
+ \printunit[ ]{_pint}{_qt/2}
+ \printunit[ ]{_cup}{_pint/2}
+ \printunit[ ]{_gi}{_pint/4}
+ \printunit[ ]{_fl_oz}{_gi/5}
+ \printunit[ ]{_fl_dr}{_fl_oz/8}
+}
+
+\footnotetext[1]{
+ The desktop publishing point or PostScript point is $1/72$ of an international inch.
+}
+
+
+\unittable{
+ \printunit[Mass]{_gr}{64.79891*_mg}
+ \printunit[ ]{_lb}{7000*_gr}
+ \printunit[ ]{_oz}{_lb/16}
+ \printunit[ ]{_dr}{_lb/256}
+ \printunit[ ]{_st}{14*_lb}
+ \printunit[ ]{_qtr}{2*_st}
+ \printunit[ ]{_cwt}{4*_qtr}
+ \printunit[ ]{_ton}{20*_cwt}
+ \printunit[ ]{_lb_t}{5760*_gr}
+ \printunit[ ]{_oz_t}{_lb_t/12}
+ \printunit[ ]{_dwt}{24*_gr}
+ \printunit[ ]{_fir}{56*_lb}
+
+ \printunit[Time]{_sen}{7*_d}
+ \printunit[ ]{_ftn}{14*_d}
+
+ \printunit[Temperature\protect\footnotemark]{_degF}{(5/9)*_K}
+
+ \printunit[Force]{_lbf}{_lb*_g_0}
+ \printunit[ ]{_pdl}{_lb*_ft/_s^2}
+
+ \printunit[Mass]{_slug}{_lbf*_s^2/_ft}
+
+ \printunit{_psi}{_lbf/_in^2}
+
+ \printunit{_BTU}{(1897.83047608/1.8)*_J}
+ \printunit{_BTU_it}{1055.05585262 * _J}
+
+ \printunit{_hp}{33000*_ft*_lbf/_min}
+
+}
+
+\footnotetext[1]{
+ The unit \texttt{\_degF} is by default interpreted as a temperature difference.
+}
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{U.S. customary units}
+\label{ch:us customary units}
+
+In the U.S., the length units are bound to the meter differently than in the imperial system. The followin definitions are taken from \url{https://en.wikipedia.org/wiki/United_States_customary_units}.
+
+\unittable{
+ \printunit[Length]{_in_US}{_m/39.37}
+ \printunit[ ]{_hh_US}{4 * _in_US}
+ \printunit[ ]{_ft_US}{3 * _hh_US}
+ \printunit[ ]{_li_US}{0.66 * _ft_US}
+ \printunit[ ]{_yd_US}{3 * _ft_US}
+ \printunit[ ]{_rd_US}{5.5 * _yd_US}
+ \printunit[ ]{_ch_US}{4 * _rd_US}
+ \printunit[ ]{_fur_US}{10 * _ch_US}
+ \printunit[ ]{_mi_US}{8 * _fur_US}
+ \printunit[ ]{_lea_US}{3 * _mi_US}
+ \printunit[ ]{_ftm_US}{72 * _in_US}
+ \printunit[ ]{_cbl_US}{120 * _ftm_US}
+
+ \printunit{_ac_US}{_ch_US * _fur_US}
+
+ \printunit{_gal_US}{231 * _in^3}
+ \printunit[ ]{_qt_US}{_gal_US/4}
+ \printunit[ ]{_pint_US}{_qt_US/2}
+ \printunit[ ]{_cup_US}{_pint_US/2}
+ \printunit[ ]{_gi_US}{_pint_US/4}
+ \printunit[ ]{_fl_oz_US}{_gi_US/4}
+ \printunit[ ]{_Tbsp_US}{_fl_oz_US/2}
+ \printunit[ ]{_tsp_US}{_Tbsp_US/3}
+ \printunit[ ]{_fl_dr_US}{_fl_oz_US/8}
+
+ \printunit[Mass]{_qtr_US}{25 * _lb}
+ \printunit[ ]{_cwt_US}{4 * _qtr_US}
+ \printunit[ ]{_ton_US}{20 * _cwt_US}
+}
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{International Currencies}
+\label{ch:currencies}
+
+International currency units based on exchange rates from 7.3.2019, 21:00 UTC.
+
+\begin{luacode}
+local curr = {"AFN","ALL","AMD","AOA","ARS","AUD","AZN","BAM","BDT","BIF","BOB","BRL","BWP","BYN","CAD","CDF","CHF","CLP","CNY","COP","CRC","CZK","DKK","DOP","DZD","EGP","ETB","FJD","GBP","GEL","GHS","GMD","GNF","GTQ","GYD","HKD","HNL","HRK","HTG","HUF","IDR","ILS","INR","IQD","IRR","ISK","JMD","JPY","KES","KGS","KHR","KPW","KRW","KWD","KZT","LAK","LKR","LRD","LYD","MAD","MDL","MGA","MKD","MMK","MNT","MRU","MUR","MVR","MWK","MXN","MYR","MZN","NGN","NIO","NOK","NZD","PEN","PGK","PHP","PKR","PLN","PYG","QAR","RON","RSD","RUB","RWF","SBD","SCR","SDG","SEK","SGD","SLL","SQS","SOS","SRD","SYP","THB","TJS","TOP","TRY","TTD","TWD","TZS","UAH","UGX","USD","UYU","UZS","VES","VND","WST","YER","ZAR","ZMW"}
+
+-- print variable currency rows
+function printcurrencyrows(start,n)
+ for i,v in ipairs(curr) do
+ if i >= start and i < start+n then
+ printcurrencyrow(v)
+ end
+ end
+
+end
+
+-- print pegged currency row
+function printcurrencyrow(iso,def)
+ q = _G["_"..iso]
+ tex.print(q.unit.name)
+ tex.print("\\index[cur]{"..q.unit.name.." \\texttt{\\_"..iso.."}}&")
+ tex.print("|_"..iso.."|&")
+
+ if def == nil then
+ tex.print("\\texttt{"..q:to(_EUR):__tonumber().." * \\_EUR} \\\\")
+ else
+ tex.print("|"..def.."|\\\\")
+ end
+
+ if _G["_c"..iso] ~= nil then
+ p = _G["_c"..iso]
+ tex.print(p.unit.name)
+ tex.print("\\index[cur]{"..p.unit.name.." \\texttt{\\_c"..iso.."}}&")
+ tex.print("|_c"..iso.."|&")
+ tex.print("\\texttt{"..(p/q):to(_1):__tonumber().." * \\_"..iso.."} \\\\")
+ end
+end
+\end{luacode}
+
+
+\newcommand{\currencytable}[1]{%
+ \begin{table}[H]
+ \centering
+ \begin{tabularx}{\linewidth}{%
+ >{\setlength\hsize{1\hsize}}X%
+ l%
+ >{\setlength\hsize{1\hsize}}X%
+ }
+
+ \thead{Name} & \thead{Symbol} & \thead{Definition} \\\hline
+
+ #1
+
+ \hline
+
+ \end{tabularx}
+ \end{table}
+}
+
+\newcommand{\printcurrency}[2]{%
+ \directlua{printcurrencyrow("#1","#2")}
+}
+
+\newcounter{currency}
+\setcounter{currency}{1}
+
+\newcommand{\printcurrencies}[1]{%
+ \currencytable{\directlua{printcurrencyrows(\thecurrency,#1)}}
+ \addtocounter{currency}{#1}
+}
+
+\printcurrencies{14}
+\printcurrencies{14}
+\printcurrencies{14}
+\printcurrencies{16}
+\printcurrencies{14}
+\printcurrencies{14}
+\printcurrencies{14}
+\printcurrencies{15}
+
+
+%------------------------------------------------------------
+\newpage
+\subsubsection{Pegged International Currencies}
+\label{ch:pegged currencies}
+
+International currency which are pegged to other currencies.
+
+\currencytable{
+ \printcurrency{AED}{(1/3.6725) * _USD}
+ \printcurrency{ANG}{(1/1.79) * _USD}
+ \printcurrency{AWG}{(1/1.79) * _USD}
+ \printcurrency{BBD}{0.5 * _USD}
+ \printcurrency{BGN}{0.51129 * _EUR}
+ \printcurrency{BHD}{(1/0.376) * _USD}
+ \printcurrency{BMD}{1 * _USD}
+ \printcurrency{BND}{1 * _SGD}
+ \printcurrency{BSD}{1 * _USD}
+ \printcurrency{BTN}{1 * _INR}
+ \printcurrency{BZD}{0.5 * _USD}
+ \printcurrency{CUC}{1 * _USD}
+ \printcurrency{CUP}{(1/24) * _CUC}
+
+}
+
+\currencytable{
+ \printcurrency{CVE}{(1/110.265) * _EUR}
+ \printcurrency{DJF}{(1/177.721) * _USD}
+ \printcurrency{ERN}{(1/15) * _USD}
+ \printcurrency{FKP}{1 * _GBP}
+ \printcurrency{GGP}{1 * _GBP}
+ \printcurrency{GIP}{1 * _GBP}
+ \printcurrency{IMP}{1 * _GBP}
+ \printcurrency{JEP}{1 * _GBP}
+ \printcurrency{JOD}{(1/0.708) * _USD}
+ \printcurrency{KID}{1 * _AUD}
+ \printcurrency{KMF}{(1/491.96775) * _EUR}
+ \printcurrency{KYD}{1.2 * _USD}
+ \printcurrency{LBP}{(1/1507.5) * _USD}
+ \printcurrency{MOP}{(1/1.03) * _HKD}
+
+}
+
+\currencytable{
+ \printcurrency{NAD}{1 * _ZAR}
+ \printcurrency{NPR}{(1/1.6) * _INR}
+ \printcurrency{OMR}{(1/2.6008) * _USD}
+ \printcurrency{PAB}{1 * _USD}
+ \printcurrency{PRB}{(1/16.1) * _USD}
+ \printcurrency{SAR}{(1/3.75) * _USD}
+ \printcurrency{SHP}{1 * _GBP}
+ \printcurrency{SSP}{1 * _SDG}
+ \printcurrency{STN}{(1/24.5) * _EUR}
+ \printcurrency{SZL}{1 * _ZAR}
+ \printcurrency{TMT}{(1/3.5) * _USD}
+ \printcurrency{TVD}{1 * _AUD}
+ \printcurrency{XAF}{(1/655.957) * _EUR}
+ \printcurrency{XCD}{(1/2.7) * _USD}
+}
+
+\currencytable{
+ \printcurrency{XOF}{(1/655.957) * _USD}
+ \printcurrency{XPF}{(1000/8.38) * _EUR}
+ \printcurrency{ZWL}{1 * _USD}
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\section{Lua Documentation}
+%------------------------------------------------------------
+
+% shortcut for method definitions
+\newcommand{\method}[2]{\subsection*{|#1.#2|}\index[lua]{\texttt{#1.#2}}}
+
+\newcommand{\subtitle}[1]{\noindent \\\textbf{#1}}
+
+% set listings language to lua
+\lstset{language=Lua}
+
+In this section, the following shortcuts will be used.
+\begin{lstlisting}
+local D = physical.Dimension
+local U = physical.Unit
+local N = physical.Number
+local Q = physical.Quantity
+\end{lstlisting}
+
+The term |number| refers to a lua integer or a lua float number. By |string| a lua string is meant and by |bool| a lua boolean.
+
+
+
+%------------------------------------------------------------
+\subsection{physical.Quantity}
+The quantity class is the main part of the library. Each physical Quantity and all units are represented by an instance of this class.
+
+
+\method{Q}{new(q=nil)}
+\begin{quote}
+ Copy Constuctor
+
+ \begin{description}
+ \item |q| : |Q|, |number|, |object|, |nil|\\
+
+ \item returns : |Q|\\
+ \end{description}
+
+ As an argument it takes |Q|, |number|, |object| or |nil|. If an instance of |Q| is given, a copy is made and returned. If a |number| or an instance |object| of another class is given, the function creates a dimensionless quantity with the |number| or the instance as a value. In the case |nil| is given, a dimensionless quantity with value 1 is returned.
+
+ \begin{lstlisting}
+ print( Q() )
+ 1
+
+ print( Q(42) )
+ 42
+
+ print( Q(73*_m) )
+ 73 * _m
+ \end{lstlisting}
+\end{quote}
+
+
+
+
+\method{Q}{defineBase(symbol,name,dimension)}
+\begin{quote}
+ This function is used to declare base quantities from which all other quantities are derived from.
+
+ \begin{description}
+ \item |symbol| : |string|\\
+ The symbol of the base quantity.
+
+ \item |name| : |string|\\
+ The name of the base quantity.
+
+ \item |dimension| : |D|\\
+ An instance of the |D| class, which represents the dimension of the quantity.
+
+ \item returns : |Q|\\
+ The created |Q| instance.
+ \end{description}
+
+ The function creates a global variable of the created base quantity. The name consist of an underscore concatenated with the |symbol| argument, i.e.\ the symbol |m| becomes the global variable |_m|.
+
+ The |name| is used for example in the siunitx conversion function, e.g |meter| will be converted to |\meter|.
+
+ Each quantity has a dimension associated with it. The argument |dimension| allows any dimension to be associated to base quantities.
+
+ \begin{lstlisting}
+Q.defineBase("m", "meter", L)
+Q.defineBase("kg", "kilogram", M)
+ \end{lstlisting}
+\end{quote}
+
+
+
+
+\method{Q}{define(symbol, name, q)}
+\begin{quote}
+ Creates a new derived quantity from an expression of other quantities. Affine quantities like the absolute temperature in celsius are not supported.
+
+ \begin{description}
+ \item |symbol| : |string|\\
+ Symbol of the base quantity
+
+ \item |name| : |string|, |nil|\\
+ The Name of the derived quantity.
+
+ \item |q| : |physical.Quantity|\\
+ The definition of the derived quantity.
+
+ \item returns : |Q|\\
+ The created quantity.
+ \end{description}
+
+ The function creates a global variable of the created base quantity. The name consist of an underscore concatenated with the |symbol| argument, i.e.\ the symbol |N| becomes the global variable |_N|.
+
+ The |name| is used for example in the siunitx conversion function, e.g |newton| will be converted to |\newton|.
+
+ \begin{lstlisting}
+Q.define("L", "liter", _dm^3)
+Q.define("Pa", "pascal", _N/_m^2)
+Q.define("C", "coulomb", _A*_s)
+
+Q.define("degC", "celsius", _K)
+ \end{lstlisting}
+\end{quote}
+
+
+
+
+\method{Q}{definePrefix(symbol,name,factor)}
+\begin{quote}
+ Defines a new prefix.
+
+ \begin{description}
+ \item |symbol| : |string|\\
+ Symbol of the base quantity
+
+ \item |name| : |string|\\
+ Name of the base quantity
+
+ \item |factor| : |number|\\
+ The factor which corresponds to the prefix
+ \end{description}
+
+
+\begin{lstlisting}
+Q.definePrefix("c", "centi", 1e-2)
+Q.definePrefix("a", "atto", 1e-18)
+\end{lstlisting}
+\end{quote}
+
+
+
+\method{Q}{addPrefix(prefixes, units)}
+\begin{quote}
+ Create several units with prefixes from a given unit.
+
+ \begin{description}
+ \item |prefixes| : |string|\\
+ A list of unit symbols.
+
+ \item |units| : |Q|\\
+ A list of quantities.
+ \end{description}
+
+
+\begin{lstlisting}
+Q.addPrefix({"n","u","m","k","M","G"},{_m,_s,_A})
+\end{lstlisting}
+\end{quote}
+
+
+
+\method{Q}{isclose(self,q,r)}
+\begin{quote}
+ Checks if this quantity is close to another one. The argument |r| is the maximum relative deviation. The function returns |true| if the following condition is fullfilled
+
+ \begin{align*}
+ \frac{abs(\texttt{self} - \texttt{q})}{min(\texttt{self},\texttt{q})} \leq \texttt{r} \quad.
+ \end{align*}
+
+ \begin{description}
+ \item |self| : |Q|, |N|, |number|
+
+ \item |q| : |Q|, |N|, |number|
+
+ \item |r| : |number|\\
+ maximum relative deviation of |self| and |q|
+
+ \item returns : |bool|\\
+ |true| if q is close to |self|, otherwise |false|
+ \end{description}
+
+\begin{lstlisting}
+s_1 = 1.9 * _m
+s_2 = 2.0 * _m
+print( s_1:isclose(s_2,0.1) )
+`
+\begin{luacode}
+s_1 = 1.9 * _m
+s_2 = 2.0 * _m
+tex.write(tostring(s_1:isclose(s_2,10 * _percent)) )
+\end{luacode}
+`
+print( s_1:isclose(s_2,0.01) )
+`
+\begin{luacode}
+s_1 = 1.9 * _m
+s_2 = 2.0 * _m
+tex.write(tostring(s_1:isclose(s_2,1 * _percent)) )
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{Q}{to(self,q=nil)}
+\begin{quote}
+ Converts the quantity |self| to the unit of the quantity |q|. If no |q| is given, the quantity |self| is converted to base units.
+
+ \begin{description}
+ \item |self| : |Q|
+
+ \item |q| : |Q|, |nil|
+ \end{description}
+
+\begin{lstlisting}
+s = 1.9 * _km
+print( s:to(_m) )
+`
+\begin{luacode}
+s = 1.9 * _km
+tex.write(tostring(s:to(_m)) )
+\end{luacode}
+`
+
+T = 10 * _degC
+print( T:to(_K) )
+`
+\begin{luacode}
+T = 10 * _degC
+tex.write(tostring(T:to(_K)) )
+\end{luacode}
+`
+
+print( T:to() )
+`
+\begin{luacode}
+tex.write(tostring(T:to()) )
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{tosiunitx(self,param,mode=Q.siunitx\_SI)}
+\begin{quote}
+ Converts the quantity into a |siunitx| string.
+
+ \begin{description}
+ \item |self| : |Q|
+
+ \item |param| : |string|
+
+ \item |mode| : |number|
+ \end{description}
+
+ If |mode| is equal |Q.siunitx_SI|, which is the default, the quantity is converted to an \textbackslash SI\{\}\{\} macro. If mode is |Q.siunitx_num|, the quantity is converted to \textbackslash num\{\} and if it is |Q.siunitx_si| the macro \textbackslash si\{\} is printed.
+
+\begin{lstlisting}
+s = 1.9 * _km
+
+print( s:tosiunitx() )
+`
+\begin{luacode}
+s = 1.9 * _km
+tex.write(tostring(s:tosiunitx()) )
+\end{luacode}
+`
+
+print( s:tosiunitx(nil,Q.siunitx_num) )
+`
+\begin{luacode}
+tex.write(tostring(s:tosiunitx(nil,Q.siunitx_num)) )
+\end{luacode}
+`
+
+print( s:tosiunitx(nil,Q.siunitx_si) )
+`
+\begin{luacode}
+tex.write(tostring(s:tosiunitx(nil,Q.siunitx_si)) )
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+
+
+
+
+
+
+\method{Q}{min(q1, q2, ...)}
+\begin{quote}
+ Returns the smallest quantity of the given ones. The function returns |q1| if the Quantities are equal.
+
+ \begin{description}
+ \item |q1| : |Q|, |N|, |number|
+
+ \item |q2| : |Q|, |N|, |number|
+
+ \item ...
+
+ \item |qN| : |Q|, |N|, |number|\\
+
+ \item returns : |Q|\\
+ the smallest quantity of |q1|, ... , |qN|
+ \end{description}
+
+
+\begin{lstlisting}
+s_1 = 15 * _m
+s_2 = 5 * _m
+print(s_1:min(s_2))
+`
+\begin{luacode}
+s_1 = 15 * _m
+s_2 = 5 * _m
+tex.write(tostring(s_1:min(s_2)))
+\end{luacode}
+`
+\end{lstlisting}
+\end{quote}
+
+
+\method{Q}{max(q1, q2, ...)}
+\begin{quote}
+ Returns the biggest quantity of several given ones. The function returns |q1| if the Quantities are equal.
+
+ \begin{description}
+ \item |q1| : |Q|, |N|, |number|
+
+ \item |q2| : |Q|, |N|, |number|
+
+ \item ...
+
+ \item |qN| : |Q|, |N|, |number|\\
+
+ \item returns : |Q|\\
+ the biggest quantity of |q1|, ... , |qN|
+ \end{description}
+
+
+\begin{lstlisting}
+s_1 = 15 * _m
+s_2 = 5 * _m
+print(s_1:max(s_2))
+`
+\begin{luacode}
+s_1 = 15 * _m
+s_2 = 5 * _m
+tex.write(tostring(s_1:max(s_2)))
+\end{luacode}
+`
+\end{lstlisting}
+\end{quote}
+
+
+
+
+\method{Q}{abs(q)}
+\begin{quote}
+ Returns the absolute value of the given quantity |q|.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+
+ \item returns : |Q|\\
+ the absolute value of |q|
+ \end{description}
+
+
+\begin{lstlisting}
+U = -5 * _V
+print(U)
+`
+\begin{luacode}
+U = -5 * _V
+tex.write(tostring(U))
+\end{luacode}
+`
+print(U:abs())
+`
+\begin{luacode}
+U = -5 * _V
+tex.write(tostring(U:abs()))
+\end{luacode}
+`
+\end{lstlisting}
+\end{quote}
+
+
+
+
+\method{Q}{sqrt(q)}
+\begin{quote}
+ Returns the square root of the given quantity.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ the square root of |q|
+ \end{description}
+
+
+\begin{lstlisting}
+A = 25 * _m^2
+s = A:sqrt()
+print(s)
+`
+\begin{luacode}
+A = 25 * _m^2
+s = A:sqrt()
+tex.write(tostring(s))
+\end{luacode}
+`
+\end{lstlisting}
+\end{quote}
+
+
+
+\method{Q}{log(q, base=nil)}
+\begin{quote}
+ Returns the logarithm of a given quantitiy to the given base. If no base is given, the natural logarithm is returned.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item |base| : |Q|, |N|, |number|, |nil|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ logarithm of |q| to the |base|
+ \end{description}
+
+
+\begin{lstlisting}
+I = 1 * _W/_m^2
+I_0 = 1e-12 * _W/_m^2
+print(10 * (I/I_0):log(10) * _dB )
+`
+\begin{luacode}
+I = 1 * _W/_m^2
+I_0 = 1e-12 * _W/_m^2
+tex.write(tostring(10 * (I/I_0):log(10.0) *_dB ))
+\end{luacode}
+`
+\end{lstlisting}
+\end{quote}
+
+
+
+
+\method{Q}{exp(q)}
+\begin{quote}
+ Returns the value of the natural exponential function of the given quantitiy.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ natural exponential of |q|
+ \end{description}
+
+\begin{lstlisting}
+x = 2 * _1
+print( x:exp() )
+`
+\begin{luacode}
+x = 2 * _1
+tex.write(tostring(x:exp()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{sin(q)}
+\begin{quote}
+ Returns the value of the sinus function of the given quantitiy.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ sine of |q|
+ \end{description}
+
+\begin{lstlisting}
+alpha = 30 * _deg
+print( alpha:sin() )
+`
+\begin{luacode}
+alpha = 30 * _deg
+tex.write(tostring(alpha:sin()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{cos(q)}
+\begin{quote}
+ Returns the value of the cosinus function of the given quantity. The quantity has to be dimensionless.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ cosine of |q|
+ \end{description}
+
+\begin{lstlisting}
+alpha = 60 * _deg
+print( alpha:cos() )
+`
+\begin{luacode}
+alpha = 60 * _deg
+tex.write(tostring(alpha:cos()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{tan(q)}
+\begin{quote}
+ Returns the value of the tangent function of the given quantity. The quantity has to be dimensionless.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ tangent of |q|
+ \end{description}
+
+\begin{lstlisting}
+alpha = 45 * _deg
+print( alpha:tan() )
+`
+\begin{luacode}
+alpha = 45 * _deg
+tex.write(tostring(alpha:tan()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{asin(q)}
+\begin{quote}
+ Returns the value of the arcus sinus function of the given quantity. The quantity has to be dimensionless.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ inverse sine of |q|
+ \end{description}
+
+\begin{lstlisting}
+x = 0.5 * _1
+print( x:asin():to(_deg) )
+`
+\begin{luacode}
+x = 0.5 * _1
+tex.write(tostring(x:asin():to(_deg)))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{acos(q)}
+\begin{quote}
+ Returns the value of the arcus cosinus function of the given quantity. The quantity has to be dimensionless.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ inverse cosine of |q|
+ \end{description}
+
+\begin{lstlisting}
+x = 0.5 * _1
+print( x:acos():to(_deg) )
+`
+\begin{luacode}
+x = 0.5 * _1
+tex.write(tostring(x:acos():to(_deg)))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+
+\method{Q}{atan(q)}
+\begin{quote}
+ Returns the value of the arcus tangent function of the given quantity. The quantity has to be dimensionless.
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ inverse tangent of |q|
+ \end{description}
+
+\begin{lstlisting}
+x = 1 * _1
+print( x:atan():to(_deg) )
+`
+\begin{luacode}
+x = 1 * _1
+tex.write(tostring(x:atan():to(_deg)))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+
+\method{Q}{sinh(q)}
+\begin{quote}
+ Returns the value of the hyperbolic sine function of the given quantity. The quantity has to be dimensionless. Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ $$
+ \sinh(x) = 0.5 \cdot e^x - 0.5 / e^x \quad.
+ $$
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ hyperbolic sine of |q|
+ \end{description}
+
+
+\begin{lstlisting}
+x = 1 * _1
+print( x:sinh() )
+`
+\begin{luacode}
+x = 1 * _1
+tex.write(tostring(x:sinh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{cosh(q)}
+\begin{quote}
+ Returns the value of the hyperbolic cosine function of the given quantity. The quantity has to be dimensionless. Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ $$
+ \cosh(x) = 0.5 \cdot e^x + 0.5 / e^x \quad.
+ $$
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ hyperbolic cosine of |q|
+ \end{description}
+
+\begin{lstlisting}
+x = 1 * _1
+print( x:cosh() )
+`
+\begin{luacode}
+x = 1 * _1
+tex.write(tostring(x:cosh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{tanh(q)}
+\begin{quote}
+ Returns the value of the hyperbolic tangent function of the given quantity. The quantity has to be dimensionless. Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ $$
+ \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} \quad.
+ $$
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ hyperbolic tangent of |q|
+ \end{description}
+
+\begin{lstlisting}
+x = 1 * _1
+print( x:tanh() )
+`
+\begin{luacode}
+x = 1 * _1
+tex.write(tostring(x:tanh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+
+\method{Q}{asinh(q)}
+\begin{quote}
+ Returns the value of the inverse hyperbolic sine function of the given quantity. The quantity has to be dimensionless. Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ $$
+ \text{asinh}(x) = \ln\left( x + \sqrt{x^2 + 1} \right) \quad.
+ $$
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument
+
+ \item returns : |Q|\\
+ inverse hyperbolic sine of |q|
+ \end{description}
+
+
+\begin{lstlisting}
+x = 1 * _1
+print( x:asinh() )
+`
+\begin{luacode}
+x = 1 * _1
+tex.write(tostring(x:asinh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{acosh(q)}
+\begin{quote}
+ Returns the value of the inverse hyperbolic cosine function of the given quantity. The quantity has to be dimensionless. Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ $$
+ \text{acosh}(x) = \ln\left( x + \sqrt{x^2 - 1} \right) \quad, x > 1 \quad.
+ $$
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument bigger or equal to one
+
+ \item returns : |Q|\\
+ inverse hyperbolic cosine of |q|
+ \end{description}
+
+
+\begin{lstlisting}
+x = 2 * _1
+print( x:acosh() )
+`
+\begin{luacode}
+x = 2 * _1
+tex.write(tostring(x:acosh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{atanh(q)}
+\begin{quote}
+ Returns the value of the inverse hyperbolic tangent function of the given quantity. The quantity has to be dimensionless. Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ $$
+ \text{atanh}(x) = \ln\left( \frac{1 + x}{1 - x} \right) \quad, -1 < x < 1 \quad.
+ $$
+
+ \begin{description}
+ \item |q| : |Q|, |N|, |number|\\
+ dimensionless argument with magnitude smaller than one
+
+ \item returns : |Q|\\
+ inverse hyperbolic tangent of |q|
+ \end{description}
+
+
+
+\begin{lstlisting}
+x = 0.5 * _1
+print( x:atanh() )
+`
+\begin{luacode}
+x = 0.5 * _1
+tex.write(tostring(x:atanh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{physical.Dimension}
+
+ All physical quantities do have a physical dimension. For example the quantity \emph{Area} has the dimension $L^2$ (lenght to the power of two). In the SI-System there are seven base dimensions, from which all other dimensions are derived. Each dimension is represented by an $n$-tuple, where $n$ is the number of base dimensions. Each physical quantity has an associated dimension object. It is used to check equality and if addition or substraction is allowed.
+
+
+\method{D}{new(d)}
+\begin{quote}
+ Constructor of the |Dimension| class.
+
+ \begin{description}
+ \item |d| : |Dimension| or |string|, |nil|\\
+ The name or symbol of the dimension.
+
+ \item returns : |D|\\
+ The created |D| instance
+ \end{description}
+
+ If |d| is a string, a copy of the perviously defined dimension is made. If |d| is a dimension, a copy of it is made. If no argument ist given, a dimension \emph{zero} is created.
+
+
+ \subtitle{Example}
+ \begin{lstlisting}
+ V_1 = D("Velocity")
+ L = D("L")
+ V_2 = D(L/T)
+ \end{lstlisting}
+\end{quote}
+
+
+\method{D}{defineBase(symbol, name)}
+\begin{quote}
+ Defines a base dimension.
+
+ \begin{description}
+ \item |symbol| : |string|\\
+
+ \item |name| : |string|\\
+
+ \item returns : |D|\\
+ The created |D| instance
+ \end{description}
+
+
+ \subtitle{Example}
+ \begin{lstlisting}
+ V_1 = D("Velocity")
+ L = D("L")
+ V_2 = D(L/T)
+ \end{lstlisting}
+\end{quote}
+
+
+
+
+
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{physical.Unit}
+
+The task of this class is keeping track of the unit term. The unit term is a fraction of units. The units in the enumerator and denominator can have an exponent.
+
+
+\method{Unit}{new(u=nil)}
+\begin{quote}
+ Copy Constructor. It copies a given unit object. If nothing is given, an empty unit is created.
+
+ \begin{description}
+ \item |u| : |Unit|\\
+ The unit object which will be copied.
+
+ \item returns : |Unit|\\
+ The created |Unit| object
+ \end{description}
+
+\end{quote}
+
+
+\method{Unit}{new(symbol, name, prefixsymbol=nil, prefixname=nil)}
+\begin{quote}
+ Constructor. A new |Unit| object with symbol is created. The prefixsymbol and prefixname are optional.
+
+ \begin{description}
+ \item |symbol| : |String|\\
+ The symbol of the unit.
+
+ \item |name| : |String|\\
+ The name of the unit.
+
+ \item |prefixsymbol| : |String|\\
+ The optional symbol of the prefix.
+
+ \item |prefixname| : |String|\\
+ The optional name of the prefix.
+
+ \item returns : |Unit|\\
+ The created |Unit| object
+ \end{description}
+
+\end{quote}
+
+
+
+
+\method{Unit}{tosiunitx(self)}
+\begin{quote}
+ The unit term will be compiled into a string, which the LaTeX package siunitx can understand.
+
+ \begin{description}
+ \item returns : |String|\\
+ The siunitx representation of the unit term.
+ \end{description}
+
+\end{quote}
+
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\subsection{physical.Number}
+
+\begin{luacode}
+N.omitUncertainty = false
+N.seperateUncertainty = true
+N.format = N.DECIMAL
+\end{luacode}
+
+It does arithmetics with gaussian error propagation. A number instance has a mean value called |x| and an uncertainty value called |dx|.
+
+\method{N}{new(n=nil)}
+\begin{quote}
+ This is the copy Constructor. It copies a given number object. If |n| is |nil|, an instance representing number zero with uncertainty zero is created.
+
+ \begin{description}
+ \item |n| : |Number|\\
+ The number object to be copied.
+
+ \item returns : |Number|\\
+ The created |Number| instance.
+ \end{description}
+
+\begin{lstlisting}
+n = N(56,0.012)
+m = N(n)
+print(m)
+`
+\begin{luacode}
+n = N(56,0.012)
+m = N(n)
+tex.write(tostring(m))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{new(x, dx=nil)}
+\begin{quote}
+ This constructor, creates a new instance of |N| with mean value |x| and uncertainty |dx|. If |dx| is not given, the uncertainty is zero.
+
+ \begin{description}
+ \item |x| : |number|\\
+ mean value
+
+ \item |dx| : |number|, |nil|\\
+ uncertainty value
+
+ \item returns : |N|\\
+ The created |N| instance.
+ \end{description}
+
+\begin{lstlisting}
+n = N(56,0.012)
+print(n)
+`
+\begin{luacode}
+n = N(56,0.012)
+tex.write(tostring(n))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+\method{N}{new(str)}
+\begin{quote}
+ This constructor creates a new instance of |N| from a string. It can parse strings of the form |"3.4"|, |"3.4e-3"|, |"5.4e-3 +/- 2.4e-6"| and |"5.45(7)e-23"|.
+
+ \begin{description}
+ \item |str| : |string|
+
+ \item returns : |N|
+ \end{description}
+
+\begin{lstlisting}
+n_1 = N("12.3e-3")
+print(n_1)
+`
+\begin{luacode}
+n_1 = N("12.3e-3")
+tex.write(tostring(n_1))
+\end{luacode}
+`
+
+n_2 = N("12 +/- 0.1")
+print(n_2)
+`
+\begin{luacode}
+n_2 = N("12 +/- 0.1")
+tex.write(tostring(n_2))
+\end{luacode}
+`
+
+n_3 = N("12.0(1)")
+print(n_3)
+`
+\begin{luacode}
+n_3 = N("12.0(1)")
+tex.write(tostring(n_3))
+\end{luacode}
+`
+
+n_4 = N("15.0(12)")
+print(n_4)
+`
+\begin{luacode}
+n_4 = N("15.0(12)")
+tex.write(tostring(n_4))
+\end{luacode}
+`
+\end{lstlisting}
+\end{quote}
+
+
+
+\method{N}{mean(n)}
+\begin{quote}
+ Returns the mean value of |n|.
+
+ \subtitle{Parameters / Return}
+ \begin{description}
+ \item returns : |number|
+ \end{description}
+
+\begin{lstlisting}
+n = N(1.25,0.0023)
+print( n:mean() )
+`
+\begin{luacode}
+n = N(1.25,0.0023)
+tex.write(tostring(n:mean()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+\method{N}{uncertainty(n)}
+\begin{quote}
+ Returns the uncertainty value of |n|.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |number|
+ \end{description}
+
+\begin{lstlisting}
+n = N(1.25,0.0023)
+print( n:uncertainty() )
+`
+\begin{luacode}
+n = N(1.25,0.0023)
+tex.write(tostring(n:uncertainty()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{abs(n)}
+\begin{quote}
+ Returns the absolute value of |n|.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \Delta x \quad.
+ \end{align*}
+
+\begin{lstlisting}
+n = N(-10,1)
+print( n:abs() )
+`
+\begin{luacode}
+n = N(-10,1)
+tex.write(tostring(n:abs()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{sqrt(n)}
+\begin{quote}
+ Returns the square root of |n|.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \frac{1}{2 \sqrt{x}} \cdot \Delta x \quad.
+ \end{align*}
+
+\begin{lstlisting}
+n = N(25,1)
+print( n:sqrt() )
+`
+\begin{luacode}
+n = N(25,1)
+tex.write(tostring(n:sqrt()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{log(n,base=nil)}
+\begin{quote}
+ Returns the logarithm of a given number |n| to the given base |base|. If no base is given, the natural logarithm of |n| is returned.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item |base| : |number|, |nil|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \frac{1}{\mid x \cdot \log(b) \mid} \cdot \Delta x \quad.
+ \end{align*}
+
+\begin{lstlisting}
+n = N(25,1)
+print( n:log() )
+`
+\begin{luacode}
+n = N(25,1)
+print( n:log() )
+tex.write(tostring(n:log()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{N}{exp(n)}
+\begin{quote}
+ Returns the value of the natural exponential function of the given number.
+
+ \begin{description}
+ \item |q| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = e^x \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(25,1)
+print( n:sqrt() )
+`
+\begin{luacode}
+n = N(25,1)
+tex.write(tostring(n:sqrt()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{N}{sin(n)}
+\begin{quote}
+ Returns the value of the sine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \mid \cos(x) \mid \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(3,0.1)
+print( n:sin() )
+`
+\begin{luacode}
+n = N(3,0.1)
+tex.write(tostring(n:sin()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{cos(n)}
+\begin{quote}
+ Returns the value of the cosine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \mid \sin(x) \mid \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(0.5,0.01)
+print( n:cos() )
+`
+\begin{luacode}
+n = N(0.5,0.01)
+tex.write(tostring(n:cos()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{tan(n)}
+\begin{quote}
+ Returns the value of the tangent function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \mid \frac{1}{\cos^2(x)} \mid \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(1.5,0.01)
+print( n:tan() )
+`
+\begin{luacode}
+n = N(1.5,0.01)
+tex.write(tostring(n:tan()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{asin(n)}
+\begin{quote}
+ Returns the value of the inverse sine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \frac{1}{\sqrt{1 - x^2}} \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(0.99,0.1)
+print( n:asin() )
+`
+\begin{luacode}
+n = N(0.99,0.1)
+tex.write(tostring(n:asin()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{acos(n)}
+\begin{quote}
+ Returns the value of the inverse cosine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \frac{1}{\sqrt{1 - x^2}} \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(0.99,0.1)
+print( n:acos() )
+`
+\begin{luacode}
+n = N(0.99,0.1)
+tex.write(tostring(n:acos()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{atan(n)}
+\begin{quote}
+ Returns the value of the inverse tangent function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \frac{1}{\sqrt{1 + x^2}} \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(1,0.1)
+print( n:atan() )
+`
+\begin{luacode}
+n = N(1,0.1)
+tex.write(tostring(n:atan()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+\method{N}{sinh(q)}
+\begin{quote}
+ Returns the value of the hyperbolic sine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ \begin{align*}
+ \sinh(x) = 0.5 \cdot e^x - 0.5 / e^x \quad.
+ \end{align*}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \left( 0.5 \cdot e^x + 0.5 / e^x \right) \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(1,0.1)
+print( n:sinh() )
+`
+\begin{luacode}
+n = N(1,0.1)
+tex.write(tostring(n:sinh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{N}{cosh(q)}
+\begin{quote}
+ Returns the value of the hyperbolic cosine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ \begin{align*}
+ \cosh(x) = 0.5 \cdot e^x + 0.5 / e^x \quad.
+ \end{align*}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ %
+ \begin{align*}
+ \Delta y = \left( 0.5 \cdot e^x - 0.5 / e^x \right) \cdot \Delta x \quad.
+ \end{align*}
+
+\begin{lstlisting}
+n = N(1,0.1)
+print( n:cosh() )
+`
+\begin{luacode}
+n = N(1,0.1)
+tex.write(tostring(n:cosh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{N}{tanh(q)}
+\begin{quote}
+ Returns the value of the hyperbolic tangent function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ \begin{align*}
+ \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} \quad.
+ \end{align*}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ \begin{align*}
+ \Delta y = \frac{1}{\left( 0.5 \cdot e^x + 0.5 / e^x \right)^2} \cdot \Delta x \quad.
+ \end{align*}
+
+
+
+\begin{lstlisting}
+n = N(1,0.1)
+print( n:tanh() )
+`
+\begin{luacode}
+n = N(1,0.1)
+tex.write(tostring(n:tanh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+
+\method{Q}{asinh(q)}
+\begin{quote}
+ Returns the value of the inverse hyperbolic sine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ \begin{align*}
+ \text{asinh}(x) = \ln\left( x + \sqrt{x^2 + 1} \right) \quad.
+ \end{align*}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ \begin{align*}
+ \Delta y = \frac{1}{\sqrt{x^2 + 1}} \cdot \Delta x \quad.
+ \end{align*}
+
+
+\begin{lstlisting}
+n = N(1,0.1)
+print( n:asinh() )
+`
+\begin{luacode}
+n = N(1,0.1)
+tex.write(tostring(n:asinh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{acosh(q)}
+\begin{quote}
+ Returns the value of the inverse hyperbolic cosine function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ \begin{align*}
+ \text{acosh}(x) = \ln\left( x + \sqrt{x^2 - 1} \right) \quad, x > 1 \quad.
+ \end{align*}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ \begin{align*}
+ \Delta y = \frac{1}{\sqrt{x^2 - 1}} \cdot \Delta x \quad.
+ \end{align*}
+
+
+
+
+\begin{lstlisting}
+n = N(1,0.1)
+print( n:acosh() )
+`
+\begin{luacode}
+n = N(1,0.1)
+tex.write(tostring(n:acosh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+\method{Q}{atanh(q)}
+\begin{quote}
+ Returns the value of the inverse hyperbolic tangent function of the given number.
+
+ \begin{description}
+ \item |n| : |N|
+
+ \item returns : |N|
+ \end{description}
+
+ Since Lua doesn't implement the hyperbolic functions, the following formula is used
+ \begin{align*}
+ \text{atanh}(x) = \ln\left( \frac{1 + x}{1 - x} \right) \quad, -1 < x < 1 \quad.
+ \end{align*}
+
+ The uncertainty $\Delta y$ is calculated by the following expression
+ \begin{align*}
+ \Delta y = \frac{1}{\mid x^2 - 1 \mid} \cdot \Delta x \quad.
+ \end{align*}
+
+
+
+\begin{lstlisting}
+n = N(1,0.1)
+print( n:atanh() )
+`
+\begin{luacode}
+n = N(1,0.1)
+tex.write(tostring(n:atanh()))
+\end{luacode}
+`
+\end{lstlisting}
+
+\end{quote}
+
+
+
+
+
+
+
+
+
+
+%------------------------------------------------------------
+\newpage
+\section{Change History}
+
+V1.0.1 \quad (2020/09/05) Minor release. Files renamed.
+
+V1.0 \quad (2020/09/03) First official release.
+
+
+
+
+
+\newpage
+\addcontentsline{toc}{section}{Bibliography}
+\bibliographystyle{plain}
+\bibliography{lua-physical}
+
+
+\newpage
+\addcontentsline{toc}{section}{Index of Units}
+\indexprologue{}
+\printindex[unit]
+
+\newpage
+\addcontentsline{toc}{section}{Index of Currencies}
+\indexprologue{}
+\printindex[cur]
+
+\newpage
+\addcontentsline{toc}{section}{Index of Lua Classes and Methods}
+\indexprologue{}
+\printindex[lua]
+
+
+\end{document}
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua
new file mode 100644
index 00000000000..d6e5f90fd2b
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/luaunit.lua
@@ -0,0 +1,2759 @@
+--[[
+ luaunit.lua
+
+Description: A unit testing framework
+Homepage: https://github.com/bluebird75/luaunit
+Development by Philippe Fremy <phil@freehackers.org>
+Based on initial work of Ryu, Gwang (http://www.gpgstudy.com/gpgiki/LuaUnit)
+License: BSD License, see LICENSE.txt
+Version: 3.2
+]]--
+
+require("math")
+local M={}
+
+-- private exported functions (for testing)
+M.private = {}
+
+M.VERSION='3.2'
+M._VERSION=M.VERSION -- For LuaUnit v2 compatibility
+
+--[[ Some people like assertEquals( actual, expected ) and some people prefer
+assertEquals( expected, actual ).
+]]--
+M.ORDER_ACTUAL_EXPECTED = true
+M.PRINT_TABLE_REF_IN_ERROR_MSG = false
+M.TABLE_EQUALS_KEYBYCONTENT = true
+M.LINE_LENGTH = 80
+M.TABLE_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items
+M.LIST_DIFF_ANALYSIS_THRESHOLD = 10 -- display deep analysis for more than 10 items
+
+--[[ M.EPSILON is meant to help with Lua's floating point math in simple corner
+cases like almostEquals(1.1-0.1, 1), which may not work as-is (e.g. on numbers
+with rational binary representation) if the user doesn't provide some explicit
+error margin.
+
+The default margin used by almostEquals() in such cases is M.EPSILON; and since
+Lua may be compiled with different numeric precisions (single vs. double), we
+try to select a useful default for it dynamically. Note: If the initial value
+is not acceptable, it can be changed by the user to better suit specific needs.
+
+See also: https://en.wikipedia.org/wiki/Machine_epsilon
+]]
+M.EPSILON = 2^-52 -- = machine epsilon for "double", ~2.22E-16
+if math.abs(1.1 - 1 - 0.1) > M.EPSILON then
+ -- rounding error is above EPSILON, assume single precision
+ M.EPSILON = 2^-23 -- = machine epsilon for "float", ~1.19E-07
+end
+
+-- set this to false to debug luaunit
+local STRIP_LUAUNIT_FROM_STACKTRACE = true
+
+M.VERBOSITY_DEFAULT = 10
+M.VERBOSITY_LOW = 1
+M.VERBOSITY_QUIET = 0
+M.VERBOSITY_VERBOSE = 20
+M.DEFAULT_DEEP_ANALYSIS = nil
+M.FORCE_DEEP_ANALYSIS = true
+M.DISABLE_DEEP_ANALYSIS = false
+
+-- set EXPORT_ASSERT_TO_GLOBALS to have all asserts visible as global values
+-- EXPORT_ASSERT_TO_GLOBALS = true
+
+-- we need to keep a copy of the script args before it is overriden
+local cmdline_argv = rawget(_G, "arg")
+
+M.FAILURE_PREFIX = 'LuaUnit test FAILURE: ' -- prefix string for failed tests
+
+M.USAGE=[[Usage: lua <your_test_suite.lua> [options] [testname1 [testname2] ... ]
+Options:
+ -h, --help: Print this help
+ --version: Print version information
+ -v, --verbose: Increase verbosity
+ -q, --quiet: Set verbosity to minimum
+ -e, --error: Stop on first error
+ -f, --failure: Stop on first failure or error
+ -r, --random Run tests in random order
+ -o, --output OUTPUT: Set output type to OUTPUT
+ Possible values: text, tap, junit, nil
+ -n, --name NAME: For junit only, mandatory name of xml file
+ -c, --count NUM: Execute all tests NUM times, e.g. to trig the JIT
+ -p, --pattern PATTERN: Execute all test names matching the Lua PATTERN
+ May be repeated to include severals patterns
+ Make sure you escape magic chars like +? with %
+ -x, --exclude PATTERN: Exclude all test names matching the Lua PATTERN
+ May be repeated to include severals patterns
+ Make sure you escape magic chars like +? with %
+ testname1, testname2, ... : tests to run in the form of testFunction,
+ TestClass or TestClass.testMethod
+]]
+
+local is_equal -- defined here to allow calling from mismatchFormattingPureList
+
+----------------------------------------------------------------
+--
+-- general utility functions
+--
+----------------------------------------------------------------
+
+local function pcall_or_abort(func, ...)
+ -- unpack is a global function for Lua 5.1, otherwise use table.unpack
+ local unpack = rawget(_G, "unpack") or table.unpack
+ local result = {pcall(func, ...)}
+ if not result[1] then
+ -- an error occurred
+ print(result[2]) -- error message
+ print()
+ print(M.USAGE)
+ os.exit(-1)
+ end
+ return unpack(result, 2)
+end
+
+local crossTypeOrdering = {
+ number = 1, boolean = 2, string = 3, table = 4, other = 5
+}
+local crossTypeComparison = {
+ number = function(a, b) return a < b end,
+ string = function(a, b) return a < b end,
+ other = function(a, b) return tostring(a) < tostring(b) end,
+}
+
+local function crossTypeSort(a, b)
+ local type_a, type_b = type(a), type(b)
+ if type_a == type_b then
+ local func = crossTypeComparison[type_a] or crossTypeComparison.other
+ return func(a, b)
+ end
+ type_a = crossTypeOrdering[type_a] or crossTypeOrdering.other
+ type_b = crossTypeOrdering[type_b] or crossTypeOrdering.other
+ return type_a < type_b
+end
+
+local function __genSortedIndex( t )
+ -- Returns a sequence consisting of t's keys, sorted.
+ local sortedIndex = {}
+
+ for key,_ in pairs(t) do
+ table.insert(sortedIndex, key)
+ end
+
+ table.sort(sortedIndex, crossTypeSort)
+ return sortedIndex
+end
+M.private.__genSortedIndex = __genSortedIndex
+
+local function sortedNext(state, control)
+ -- Equivalent of the next() function of table iteration, but returns the
+ -- keys in sorted order (see __genSortedIndex and crossTypeSort).
+ -- The state is a temporary variable during iteration and contains the
+ -- sorted key table (state.sortedIdx). It also stores the last index (into
+ -- the keys) used by the iteration, to find the next one quickly.
+ local key
+
+ --print("sortedNext: control = "..tostring(control) )
+ if control == nil then
+ -- start of iteration
+ state.count = #state.sortedIdx
+ state.lastIdx = 1
+ key = state.sortedIdx[1]
+ return key, state.t[key]
+ end
+
+ -- normally, we expect the control variable to match the last key used
+ if control ~= state.sortedIdx[state.lastIdx] then
+ -- strange, we have to find the next value by ourselves
+ -- the key table is sorted in crossTypeSort() order! -> use bisection
+ local lower, upper = 1, state.count
+ repeat
+ state.lastIdx = math.modf((lower + upper) / 2)
+ key = state.sortedIdx[state.lastIdx]
+ if key == control then
+ break -- key found (and thus prev index)
+ end
+ if crossTypeSort(key, control) then
+ -- key < control, continue search "right" (towards upper bound)
+ lower = state.lastIdx + 1
+ else
+ -- key > control, continue search "left" (towards lower bound)
+ upper = state.lastIdx - 1
+ end
+ until lower > upper
+ if lower > upper then -- only true if the key wasn't found, ...
+ state.lastIdx = state.count -- ... so ensure no match in code below
+ end
+ end
+
+ -- proceed by retrieving the next value (or nil) from the sorted keys
+ state.lastIdx = state.lastIdx + 1
+ key = state.sortedIdx[state.lastIdx]
+ if key then
+ return key, state.t[key]
+ end
+
+ -- getting here means returning `nil`, which will end the iteration
+end
+
+local function sortedPairs(tbl)
+ -- Equivalent of the pairs() function on tables. Allows to iterate in
+ -- sorted order. As required by "generic for" loops, this will return the
+ -- iterator (function), an "invariant state", and the initial control value.
+ -- (see http://www.lua.org/pil/7.2.html)
+ return sortedNext, {t = tbl, sortedIdx = __genSortedIndex(tbl)}, nil
+end
+M.private.sortedPairs = sortedPairs
+
+-- seed the random with a strongly varying seed
+math.randomseed(os.clock()*1E11)
+
+local function randomizeTable( t )
+ -- randomize the item orders of the table t
+ for i = #t, 2, -1 do
+ local j = math.random(i)
+ if i ~= j then
+ t[i], t[j] = t[j], t[i]
+ end
+ end
+end
+M.private.randomizeTable = randomizeTable
+
+local function strsplit(delimiter, text)
+-- Split text into a list consisting of the strings in text, separated
+-- by strings matching delimiter (which may _NOT_ be a pattern).
+-- Example: strsplit(", ", "Anna, Bob, Charlie, Dolores")
+ if delimiter == "" then -- this would result in endless loops
+ error("delimiter matches empty string!")
+ end
+ local list, pos, first, last = {}, 1
+ while true do
+ first, last = text:find(delimiter, pos, true)
+ if first then -- found?
+ table.insert(list, text:sub(pos, first - 1))
+ pos = last + 1
+ else
+ table.insert(list, text:sub(pos))
+ break
+ end
+ end
+ return list
+end
+M.private.strsplit = strsplit
+
+local function hasNewLine( s )
+ -- return true if s has a newline
+ return (string.find(s, '\n', 1, true) ~= nil)
+end
+M.private.hasNewLine = hasNewLine
+
+local function prefixString( prefix, s )
+ -- Prefix all the lines of s with prefix
+ return prefix .. string.gsub(s, '\n', '\n' .. prefix)
+end
+M.private.prefixString = prefixString
+
+local function strMatch(s, pattern, start, final )
+ -- return true if s matches completely the pattern from index start to index end
+ -- return false in every other cases
+ -- if start is nil, matches from the beginning of the string
+ -- if final is nil, matches to the end of the string
+ start = start or 1
+ final = final or string.len(s)
+
+ local foundStart, foundEnd = string.find(s, pattern, start, false)
+ return foundStart == start and foundEnd == final
+end
+M.private.strMatch = strMatch
+
+local function patternFilter(patterns, expr, nil_result)
+ -- Check if any of `patterns` is contained in `expr`. If so, return `true`.
+ -- Return `false` if none of the patterns are contained in expr. If patterns
+ -- is `nil` (= unset), return default value passed in `nil_result`.
+ if patterns ~= nil then
+
+ for _, pattern in ipairs(patterns) do
+ if string.find(expr, pattern) then
+ return true
+ end
+ end
+
+ return false -- no match from patterns
+ end
+
+ return nil_result
+end
+M.private.patternFilter = patternFilter
+
+local function xmlEscape( s )
+ -- Return s escaped for XML attributes
+ -- escapes table:
+ -- " &quot;
+ -- ' &apos;
+ -- < &lt;
+ -- > &gt;
+ -- & &amp;
+
+ return string.gsub( s, '.', {
+ ['&'] = "&amp;",
+ ['"'] = "&quot;",
+ ["'"] = "&apos;",
+ ['<'] = "&lt;",
+ ['>'] = "&gt;",
+ } )
+end
+M.private.xmlEscape = xmlEscape
+
+local function xmlCDataEscape( s )
+ -- Return s escaped for CData section, escapes: "]]>"
+ return string.gsub( s, ']]>', ']]&gt;' )
+end
+M.private.xmlCDataEscape = xmlCDataEscape
+
+local function stripLuaunitTrace( stackTrace )
+ --[[
+ -- Example of a traceback:
+ <<stack traceback:
+ example_with_luaunit.lua:130: in function 'test2_withFailure'
+ ./luaunit.lua:1449: in function <./luaunit.lua:1449>
+ [C]: in function 'xpcall'
+ ./luaunit.lua:1449: in function 'protectedCall'
+ ./luaunit.lua:1508: in function 'execOneFunction'
+ ./luaunit.lua:1596: in function 'runSuiteByInstances'
+ ./luaunit.lua:1660: in function 'runSuiteByNames'
+ ./luaunit.lua:1736: in function 'runSuite'
+ example_with_luaunit.lua:140: in main chunk
+ [C]: in ?>>
+
+ Other example:
+ <<stack traceback:
+ ./luaunit.lua:545: in function 'assertEquals'
+ example_with_luaunit.lua:58: in function 'TestToto.test7'
+ ./luaunit.lua:1517: in function <./luaunit.lua:1517>
+ [C]: in function 'xpcall'
+ ./luaunit.lua:1517: in function 'protectedCall'
+ ./luaunit.lua:1578: in function 'execOneFunction'
+ ./luaunit.lua:1677: in function 'runSuiteByInstances'
+ ./luaunit.lua:1730: in function 'runSuiteByNames'
+ ./luaunit.lua:1806: in function 'runSuite'
+ example_with_luaunit.lua:140: in main chunk
+ [C]: in ?>>
+
+ <<stack traceback:
+ luaunit2/example_with_luaunit.lua:124: in function 'test1_withFailure'
+ luaunit2/luaunit.lua:1532: in function <luaunit2/luaunit.lua:1532>
+ [C]: in function 'xpcall'
+ luaunit2/luaunit.lua:1532: in function 'protectedCall'
+ luaunit2/luaunit.lua:1591: in function 'execOneFunction'
+ luaunit2/luaunit.lua:1679: in function 'runSuiteByInstances'
+ luaunit2/luaunit.lua:1743: in function 'runSuiteByNames'
+ luaunit2/luaunit.lua:1819: in function 'runSuite'
+ luaunit2/example_with_luaunit.lua:140: in main chunk
+ [C]: in ?>>
+
+
+ -- first line is "stack traceback": KEEP
+ -- next line may be luaunit line: REMOVE
+ -- next lines are call in the program under testOk: REMOVE
+ -- next lines are calls from luaunit to call the program under test: KEEP
+
+ -- Strategy:
+ -- keep first line
+ -- remove lines that are part of luaunit
+ -- kepp lines until we hit a luaunit line
+ ]]
+
+ local function isLuaunitInternalLine( s )
+ -- return true if line of stack trace comes from inside luaunit
+ return s:find('[/\\]luaunit%.lua:%d+: ') ~= nil
+ end
+
+ -- print( '<<'..stackTrace..'>>' )
+
+ local t = strsplit( '\n', stackTrace )
+ -- print( prettystr(t) )
+
+ local idx = 2
+
+ -- remove lines that are still part of luaunit
+ while t[idx] and isLuaunitInternalLine( t[idx] ) do
+ -- print('Removing : '..t[idx] )
+ table.remove(t, idx)
+ end
+
+ -- keep lines until we hit luaunit again
+ while t[idx] and (not isLuaunitInternalLine(t[idx])) do
+ -- print('Keeping : '..t[idx] )
+ idx = idx + 1
+ end
+
+ -- remove remaining luaunit lines
+ while t[idx] do
+ -- print('Removing : '..t[idx] )
+ table.remove(t, idx)
+ end
+
+ -- print( prettystr(t) )
+ return table.concat( t, '\n')
+
+end
+M.private.stripLuaunitTrace = stripLuaunitTrace
+
+
+local function prettystr_sub(v, indentLevel, keeponeline, printTableRefs, recursionTable )
+ local type_v = type(v)
+ if "string" == type_v then
+ if keeponeline then
+ v = v:gsub("\n", "\\n") -- escape newline(s)
+ end
+
+ -- use clever delimiters according to content:
+ -- enclose with single quotes if string contains ", but no '
+ if v:find('"', 1, true) and not v:find("'", 1, true) then
+ return "'" .. v .. "'"
+ end
+ -- use double quotes otherwise, escape embedded "
+ return '"' .. v:gsub('"', '\\"') .. '"'
+
+ elseif "table" == type_v then
+ --if v.__class__ then
+ -- return string.gsub( tostring(v), 'table', v.__class__ )
+ --end
+ return M.private._table_tostring(v, indentLevel, keeponeline,
+ printTableRefs, recursionTable)
+
+ elseif "number" == type_v then
+ -- eliminate differences in formatting between various Lua versions
+ if v ~= v then
+ return "#NaN" -- "not a number"
+ end
+ if v == math.huge then
+ return "#Inf" -- "infinite"
+ end
+ if v == -math.huge then
+ return "-#Inf"
+ end
+ if _VERSION == "Lua 5.3" then
+ local i = math.tointeger(v)
+ if i then
+ return tostring(i)
+ end
+ end
+ end
+
+ return tostring(v)
+end
+
+local function prettystr( v, keeponeline )
+ --[[ Better string conversion, to display nice variable content:
+ For strings, if keeponeline is set to true, string is displayed on one line, with visible \n
+ * string are enclosed with " by default, or with ' if string contains a "
+ * if table is a class, display class name
+ * tables are expanded
+ ]]--
+ local recursionTable = {}
+ local s = prettystr_sub(v, 1, keeponeline, M.PRINT_TABLE_REF_IN_ERROR_MSG, recursionTable)
+ if recursionTable.recursionDetected and not M.PRINT_TABLE_REF_IN_ERROR_MSG then
+ -- some table contain recursive references,
+ -- so we must recompute the value by including all table references
+ -- else the result looks like crap
+ recursionTable = {}
+ s = prettystr_sub(v, 1, keeponeline, true, recursionTable)
+ end
+ return s
+end
+M.prettystr = prettystr
+
+local function tryMismatchFormatting( table_a, table_b, doDeepAnalysis )
+ --[[
+ Prepares a nice error message when comparing tables, performing a deeper
+ analysis.
+
+ Arguments:
+ * table_a, table_b: tables to be compared
+ * doDeepAnalysis:
+ M.DEFAULT_DEEP_ANALYSIS: (the default if not specified) perform deep analysis only for big lists and big dictionnaries
+ M.FORCE_DEEP_ANALYSIS : always perform deep analysis
+ M.DISABLE_DEEP_ANALYSIS: never perform deep analysis
+
+ Returns: {success, result}
+ * success: false if deep analysis could not be performed
+ in this case, just use standard assertion message
+ * result: if success is true, a multi-line string with deep analysis of the two lists
+ ]]
+
+ -- check if table_a & table_b are suitable for deep analysis
+ if type(table_a) ~= 'table' or type(table_b) ~= 'table' then
+ return false
+ end
+
+ if doDeepAnalysis == M.DISABLE_DEEP_ANALYSIS then
+ return false
+ end
+
+ local len_a, len_b, isPureList = #table_a, #table_b, true
+
+ for k1, v1 in pairs(table_a) do
+ if type(k1) ~= 'number' or k1 > len_a then
+ -- this table a mapping
+ isPureList = false
+ break
+ end
+ end
+
+ if isPureList then
+ for k2, v2 in pairs(table_b) do
+ if type(k2) ~= 'number' or k2 > len_b then
+ -- this table a mapping
+ isPureList = false
+ break
+ end
+ end
+ end
+
+ if isPureList and math.min(len_a, len_b) < M.LIST_DIFF_ANALYSIS_THRESHOLD then
+ if not (doDeepAnalysis == M.FORCE_DEEP_ANALYSIS) then
+ return false
+ end
+ end
+
+ if isPureList then
+ return M.private.mismatchFormattingPureList( table_a, table_b )
+ else
+ -- only work on mapping for the moment
+ -- return M.private.mismatchFormattingMapping( table_a, table_b, doDeepAnalysis )
+ return false
+ end
+end
+M.private.tryMismatchFormatting = tryMismatchFormatting
+
+local function getTaTbDescr()
+ if not M.ORDER_ACTUAL_EXPECTED then
+ return 'expected', 'actual'
+ end
+ return 'actual', 'expected'
+end
+
+local function extendWithStrFmt( res, ... )
+ table.insert( res, string.format( ... ) )
+end
+
+local function mismatchFormattingMapping( table_a, table_b, doDeepAnalysis )
+ --[[
+ Prepares a nice error message when comparing tables which are not pure lists, performing a deeper
+ analysis.
+
+ Returns: {success, result}
+ * success: false if deep analysis could not be performed
+ in this case, just use standard assertion message
+ * result: if success is true, a multi-line string with deep analysis of the two lists
+ ]]
+
+ -- disable for the moment
+ --[[
+ local result = {}
+ local descrTa, descrTb = getTaTbDescr()
+
+ local keysCommon = {}
+ local keysOnlyTa = {}
+ local keysOnlyTb = {}
+ local keysDiffTaTb = {}
+
+ local k, v
+
+ for k,v in pairs( table_a ) do
+ if is_equal( v, table_b[k] ) then
+ table.insert( keysCommon, k )
+ else
+ if table_b[k] == nil then
+ table.insert( keysOnlyTa, k )
+ else
+ table.insert( keysDiffTaTb, k )
+ end
+ end
+ end
+
+ for k,v in pairs( table_b ) do
+ if not is_equal( v, table_a[k] ) and table_a[k] == nil then
+ table.insert( keysOnlyTb, k )
+ end
+ end
+
+ local len_a = #keysCommon + #keysDiffTaTb + #keysOnlyTa
+ local len_b = #keysCommon + #keysDiffTaTb + #keysOnlyTb
+ local limited_display = (len_a < 5 or len_b < 5)
+
+ if math.min(len_a, len_b) < M.TABLE_DIFF_ANALYSIS_THRESHOLD then
+ return false
+ end
+
+ if not limited_display then
+ if len_a == len_b then
+ extendWithStrFmt( result, 'Table A (%s) and B (%s) both have %d items', descrTa, descrTb, len_a )
+ else
+ extendWithStrFmt( result, 'Table A (%s) has %d items and table B (%s) has %d items', descrTa, len_a, descrTb, len_b )
+ end
+
+ if #keysCommon == 0 and #keysDiffTaTb == 0 then
+ table.insert( result, 'Table A and B have no keys in common, they are totally different')
+ else
+ local s_other = 'other '
+ if #keysCommon then
+ extendWithStrFmt( result, 'Table A and B have %d identical items', #keysCommon )
+ else
+ table.insert( result, 'Table A and B have no identical items' )
+ s_other = ''
+ end
+
+ if #keysDiffTaTb ~= 0 then
+ result[#result] = string.format( '%s and %d items differing present in both tables', result[#result], #keysDiffTaTb)
+ else
+ result[#result] = string.format( '%s and no %sitems differing present in both tables', result[#result], s_other, #keysDiffTaTb)
+ end
+ end
+
+ extendWithStrFmt( result, 'Table A has %d keys not present in table B and table B has %d keys not present in table A', #keysOnlyTa, #keysOnlyTb )
+ end
+
+ local function keytostring(k)
+ if "string" == type(k) and k:match("^[_%a][_%w]*$") then
+ return k
+ end
+ return prettystr(k)
+ end
+
+ if #keysDiffTaTb ~= 0 then
+ table.insert( result, 'Items differing in A and B:')
+ for k,v in sortedPairs( keysDiffTaTb ) do
+ extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) )
+ extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) )
+ end
+ end
+
+ if #keysOnlyTa ~= 0 then
+ table.insert( result, 'Items only in table A:' )
+ for k,v in sortedPairs( keysOnlyTa ) do
+ extendWithStrFmt( result, ' - A[%s]: %s', keytostring(v), prettystr(table_a[v]) )
+ end
+ end
+
+ if #keysOnlyTb ~= 0 then
+ table.insert( result, 'Items only in table B:' )
+ for k,v in sortedPairs( keysOnlyTb ) do
+ extendWithStrFmt( result, ' + B[%s]: %s', keytostring(v), prettystr(table_b[v]) )
+ end
+ end
+
+ if #keysCommon ~= 0 then
+ table.insert( result, 'Items common to A and B:')
+ for k,v in sortedPairs( keysCommon ) do
+ extendWithStrFmt( result, ' = A and B [%s]: %s', keytostring(v), prettystr(table_a[v]) )
+ end
+ end
+
+ return true, table.concat( result, '\n')
+ ]]
+end
+M.private.mismatchFormattingMapping = mismatchFormattingMapping
+
+local function mismatchFormattingPureList( table_a, table_b )
+ --[[
+ Prepares a nice error message when comparing tables which are lists, performing a deeper
+ analysis.
+
+ Returns: {success, result}
+ * success: false if deep analysis could not be performed
+ in this case, just use standard assertion message
+ * result: if success is true, a multi-line string with deep analysis of the two lists
+ ]]
+ local result, descrTa, descrTb = {}, getTaTbDescr()
+
+ local len_a, len_b, refa, refb = #table_a, #table_b, '', ''
+ if M.PRINT_TABLE_REF_IN_ERROR_MSG then
+ refa, refb = string.format( '<%s> ', tostring(table_a)), string.format('<%s> ', tostring(table_b) )
+ end
+ local longest, shortest = math.max(len_a, len_b), math.min(len_a, len_b)
+ local deltalv = longest - shortest
+
+ local commonUntil = longest
+ for i = 1, longest do
+ if not is_equal(table_a[i], table_b[i]) then
+ commonUntil = i - 1
+ break
+ end
+ end
+
+ local commonBackTo = shortest - 1
+ for i = 0, shortest - 1 do
+ if not is_equal(table_a[len_a-i], table_b[len_b-i]) then
+ commonBackTo = i - 1
+ break
+ end
+ end
+
+
+ table.insert( result, 'List difference analysis:' )
+ if len_a == len_b then
+ -- TODO: handle expected/actual naming
+ extendWithStrFmt( result, '* lists %sA (%s) and %sB (%s) have the same size', refa, descrTa, refb, descrTb )
+ else
+ extendWithStrFmt( result, '* list sizes differ: list %sA (%s) has %d items, list %sB (%s) has %d items', refa, descrTa, len_a, refb, descrTb, len_b )
+ end
+
+ extendWithStrFmt( result, '* lists A and B start differing at index %d', commonUntil+1 )
+ if commonBackTo >= 0 then
+ if deltalv > 0 then
+ extendWithStrFmt( result, '* lists A and B are equal again from index %d for A, %d for B', len_a-commonBackTo, len_b-commonBackTo )
+ else
+ extendWithStrFmt( result, '* lists A and B are equal again from index %d', len_a-commonBackTo )
+ end
+ end
+
+ local function insertABValue(ai, bi)
+ bi = bi or ai
+ if is_equal( table_a[ai], table_b[bi]) then
+ return extendWithStrFmt( result, ' = A[%d], B[%d]: %s', ai, bi, prettystr(table_a[ai]) )
+ else
+ extendWithStrFmt( result, ' - A[%d]: %s', ai, prettystr(table_a[ai]))
+ extendWithStrFmt( result, ' + B[%d]: %s', bi, prettystr(table_b[bi]))
+ end
+ end
+
+ -- common parts to list A & B, at the beginning
+ if commonUntil > 0 then
+ table.insert( result, '* Common parts:' )
+ for i = 1, commonUntil do
+ insertABValue( i )
+ end
+ end
+
+ -- diffing parts to list A & B
+ if commonUntil < shortest - commonBackTo - 1 then
+ table.insert( result, '* Differing parts:' )
+ for i = commonUntil + 1, shortest - commonBackTo - 1 do
+ insertABValue( i )
+ end
+ end
+
+ -- display indexes of one list, with no match on other list
+ if shortest - commonBackTo <= longest - commonBackTo - 1 then
+ table.insert( result, '* Present only in one list:' )
+ for i = shortest - commonBackTo, longest - commonBackTo - 1 do
+ if len_a > len_b then
+ extendWithStrFmt( result, ' - A[%d]: %s', i, prettystr(table_a[i]) )
+ -- table.insert( result, '+ (no matching B index)')
+ else
+ -- table.insert( result, '- no matching A index')
+ extendWithStrFmt( result, ' + B[%d]: %s', i, prettystr(table_b[i]) )
+ end
+ end
+ end
+
+ -- common parts to list A & B, at the end
+ if commonBackTo >= 0 then
+ table.insert( result, '* Common parts at the end of the lists' )
+ for i = longest - commonBackTo, longest do
+ if len_a > len_b then
+ insertABValue( i, i-deltalv )
+ else
+ insertABValue( i-deltalv, i )
+ end
+ end
+ end
+
+ return true, table.concat( result, '\n')
+end
+M.private.mismatchFormattingPureList = mismatchFormattingPureList
+
+local function prettystrPairs(value1, value2, suffix_a, suffix_b)
+ --[[
+ This function helps with the recurring task of constructing the "expected
+ vs. actual" error messages. It takes two arbitrary values and formats
+ corresponding strings with prettystr().
+
+ To keep the (possibly complex) output more readable in case the resulting
+ strings contain line breaks, they get automatically prefixed with additional
+ newlines. Both suffixes are optional (default to empty strings), and get
+ appended to the "value1" string. "suffix_a" is used if line breaks were
+ encountered, "suffix_b" otherwise.
+
+ Returns the two formatted strings (including padding/newlines).
+ ]]
+ local str1, str2 = prettystr(value1), prettystr(value2)
+ if hasNewLine(str1) or hasNewLine(str2) then
+ -- line break(s) detected, add padding
+ return "\n" .. str1 .. (suffix_a or ""), "\n" .. str2
+ end
+ return str1 .. (suffix_b or ""), str2
+end
+M.private.prettystrPairs = prettystrPairs
+
+local TABLE_TOSTRING_SEP = ", "
+local TABLE_TOSTRING_SEP_LEN = string.len(TABLE_TOSTRING_SEP)
+
+
+local function _table_tostring( tbl, indentLevel, keeponeline, printTableRefs, recursionTable )
+ printTableRefs = printTableRefs or M.PRINT_TABLE_REF_IN_ERROR_MSG
+ recursionTable = recursionTable or {}
+ recursionTable[tbl] = true
+
+ local result, dispOnMultLines = {}, false
+
+ -- like prettystr but do not enclose with "" if the string is just alphanumerical
+ -- this is better for displaying table keys who are often simple strings
+ local function keytostring(k)
+ if "string" == type(k) and k:match("^[_%a][_%w]*$") then
+ return k
+ end
+ return prettystr_sub(k, indentLevel+1, true, printTableRefs, recursionTable)
+ end
+
+ local entry, count, seq_index = nil, 0, 1
+ for k, v in sortedPairs( tbl ) do
+ if k == seq_index then
+ -- for the sequential part of tables, we'll skip the "<key>=" output
+ entry = ''
+ seq_index = seq_index + 1
+ elseif recursionTable[k] then
+ -- recursion in the key detected
+ recursionTable.recursionDetected = true
+ entry = "<"..tostring(k)..">="
+ else
+ entry = keytostring(k) .. "="
+ end
+ if recursionTable[v] then
+ -- recursion in the value detected!
+ recursionTable.recursionDetected = true
+ entry = entry .. "<"..tostring(v)..">"
+ else
+ entry = entry ..
+ prettystr_sub( v, indentLevel+1, keeponeline, printTableRefs, recursionTable )
+ end
+ count = count + 1
+ result[count] = entry
+ end
+
+ if not keeponeline then
+ -- set dispOnMultLines if the maximum LINE_LENGTH would be exceeded
+ local totalLength = 0
+ for k, v in ipairs( result ) do
+ totalLength = totalLength + string.len( v )
+ if totalLength >= M.LINE_LENGTH then
+ dispOnMultLines = true
+ break
+ end
+ end
+
+ if not dispOnMultLines then
+ -- adjust with length of separator(s):
+ -- two items need 1 sep, three items two seps, ... plus len of '{}'
+ if count > 0 then
+ totalLength = totalLength + TABLE_TOSTRING_SEP_LEN * (count - 1)
+ end
+ dispOnMultLines = totalLength + 2 >= M.LINE_LENGTH
+ end
+ end
+
+ -- now reformat the result table (currently holding element strings)
+ if dispOnMultLines then
+ local indentString = string.rep(" ", indentLevel - 1)
+ result = {"{\n ", indentString,
+ table.concat(result, ",\n " .. indentString), "\n",
+ indentString, "}"}
+ else
+ result = {"{", table.concat(result, TABLE_TOSTRING_SEP), "}"}
+ end
+ if printTableRefs then
+ table.insert(result, 1, "<"..tostring(tbl).."> ") -- prepend table ref
+ end
+ return table.concat(result)
+end
+M.private._table_tostring = _table_tostring -- prettystr_sub() needs it
+
+local function _table_contains(t, element)
+ if type(t) == "table" then
+ local type_e = type(element)
+ for _, value in pairs(t) do
+ if type(value) == type_e then
+ if value == element then
+ return true
+ end
+ if type_e == 'table' then
+ -- if we wanted recursive items content comparison, we could use
+ -- _is_table_items_equals(v, expected) but one level of just comparing
+ -- items is sufficient
+ if M.private._is_table_equals( value, element ) then
+ return true
+ end
+ end
+ end
+ end
+ end
+ return false
+end
+
+local function _is_table_items_equals(actual, expected )
+ local type_a, type_e = type(actual), type(expected)
+
+ if (type_a == 'table') and (type_e == 'table') then
+ for k, v in pairs(actual) do
+ if not _table_contains(expected, v) then
+ return false
+ end
+ end
+ for k, v in pairs(expected) do
+ if not _table_contains(actual, v) then
+ return false
+ end
+ end
+ return true
+
+ elseif type_a ~= type_e then
+ return false
+
+ elseif actual ~= expected then
+ return false
+ end
+
+ return true
+end
+
+--[[
+This is a specialized metatable to help with the bookkeeping of recursions
+in _is_table_equals(). It provides an __index table that implements utility
+functions for easier management of the table. The "cached" method queries
+the state of a specific (actual,expected) pair; and the "store" method sets
+this state to the given value. The state of pairs not "seen" / visited is
+assumed to be `nil`.
+]]
+local _recursion_cache_MT = {
+ __index = {
+ -- Return the cached value for an (actual,expected) pair (or `nil`)
+ cached = function(t, actual, expected)
+ local subtable = t[actual] or {}
+ return subtable[expected]
+ end,
+
+ -- Store cached value for a specific (actual,expected) pair.
+ -- Returns the value, so it's easy to use for a "tailcall" (return ...).
+ store = function(t, actual, expected, value, asymmetric)
+ local subtable = t[actual]
+ if not subtable then
+ subtable = {}
+ t[actual] = subtable
+ end
+ subtable[expected] = value
+
+ -- Unless explicitly marked "asymmetric": Consider the recursion
+ -- on (expected,actual) to be equivalent to (actual,expected) by
+ -- default, and thus cache the value for both.
+ if not asymmetric then
+ t:store(expected, actual, value, true)
+ end
+
+ return value
+ end
+ }
+}
+
+local function _is_table_equals(actual, expected, recursions)
+ local type_a, type_e = type(actual), type(expected)
+ recursions = recursions or setmetatable({}, _recursion_cache_MT)
+
+ if type_a ~= type_e then
+ return false -- different types won't match
+ end
+
+ if (type_a == 'table') --[[ and (type_e == 'table') ]] then
+ if actual == expected then
+ -- Both reference the same table, so they are actually identical
+ return recursions:store(actual, expected, true)
+ end
+
+ -- If we've tested this (actual,expected) pair before: return cached value
+ local previous = recursions:cached(actual, expected)
+ if previous ~= nil then
+ return previous
+ end
+
+ -- Mark this (actual,expected) pair, so we won't recurse it again. For
+ -- now, assume a "false" result, which we might adjust later if needed.
+ recursions:store(actual, expected, false)
+
+ -- Tables must have identical element count, or they can't match.
+ if (#actual ~= #expected) then
+ return false
+ end
+
+ local actualKeysMatched, actualTableKeys = {}, {}
+
+ for k, v in pairs(actual) do
+ if M.TABLE_EQUALS_KEYBYCONTENT and type(k) == "table" then
+ -- If the keys are tables, things get a bit tricky here as we
+ -- can have _is_table_equals(t[k1], t[k2]) despite k1 ~= k2. So
+ -- we first collect table keys from "actual", and then later try
+ -- to match each table key from "expected" to actualTableKeys.
+ table.insert(actualTableKeys, k)
+ else
+ if not _is_table_equals(v, expected[k], recursions) then
+ return false -- Mismatch on value, tables can't be equal
+ end
+ actualKeysMatched[k] = true -- Keep track of matched keys
+ end
+ end
+
+ for k, v in pairs(expected) do
+ if M.TABLE_EQUALS_KEYBYCONTENT and type(k) == "table" then
+ local found = false
+ -- Note: DON'T use ipairs() here, table may be non-sequential!
+ for i, candidate in pairs(actualTableKeys) do
+ if _is_table_equals(candidate, k, recursions) then
+ if _is_table_equals(actual[candidate], v, recursions) then
+ found = true
+ -- Remove the candidate we matched against from the list
+ -- of table keys, so each key in actual can only match
+ -- one key in expected.
+ actualTableKeys[i] = nil
+ break
+ end
+ -- keys match but values don't, keep searching
+ end
+ end
+ if not found then
+ return false -- no matching (key,value) pair
+ end
+ else
+ if not actualKeysMatched[k] then
+ -- Found a key that we did not see in "actual" -> mismatch
+ return false
+ end
+ -- Otherwise actual[k] was already matched against v = expected[k].
+ end
+ end
+
+ if next(actualTableKeys) then
+ -- If there is any key left in actualTableKeys, then that is
+ -- a table-type key in actual with no matching counterpart
+ -- (in expected), and so the tables aren't equal.
+ return false
+ end
+
+ -- The tables are actually considered equal, update cache and return result
+ return recursions:store(actual, expected, true)
+
+ elseif actual ~= expected then
+ return false
+ end
+
+ return true
+end
+M.private._is_table_equals = _is_table_equals
+is_equal = _is_table_equals
+
+local function failure(msg, level)
+ -- raise an error indicating a test failure
+ -- for error() compatibility we adjust "level" here (by +1), to report the
+ -- calling context
+ error(M.FAILURE_PREFIX .. msg, (level or 1) + 1)
+end
+
+local function fail_fmt(level, ...)
+ -- failure with printf-style formatted message and given error level
+ failure(string.format(...), (level or 1) + 1)
+end
+M.private.fail_fmt = fail_fmt
+
+local function error_fmt(level, ...)
+ -- printf-style error()
+ error(string.format(...), (level or 1) + 1)
+end
+
+----------------------------------------------------------------
+--
+-- assertions
+--
+----------------------------------------------------------------
+
+local function errorMsgEquality(actual, expected, doDeepAnalysis)
+
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected, actual = actual, expected
+ end
+ if type(expected) == 'string' or type(expected) == 'table' then
+ local strExpected, strActual = prettystrPairs(expected, actual)
+ local result = string.format("expected: %s\nactual: %s", strExpected, strActual)
+
+ -- extend with mismatch analysis if possible:
+ local success, mismatchResult
+ success, mismatchResult = tryMismatchFormatting( actual, expected, doDeepAnalysis )
+ if success then
+ result = table.concat( { result, mismatchResult }, '\n' )
+ end
+ return result
+ end
+ return string.format("expected: %s, actual: %s",
+ prettystr(expected), prettystr(actual))
+end
+
+function M.assertError(f, ...)
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ if pcall( f, ... ) then
+ failure( "Expected an error when calling function but no error generated", 2 )
+ end
+end
+
+function M.assertEvalToTrue(value)
+ if not value then
+ failure("expected: a value evaluating to true, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertEvalToFalse(value)
+ if value then
+ failure("expected: false or nil, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertIsTrue(value)
+ if value ~= true then
+ failure("expected: true, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsTrue(value)
+ if value == true then
+ failure("expected: anything but true, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertIsFalse(value)
+ if value ~= false then
+ failure("expected: false, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsFalse(value)
+ if value == false then
+ failure("expected: anything but false, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertIsNil(value)
+ if value ~= nil then
+ failure("expected: nil, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsNil(value)
+ if value == nil then
+ failure("expected non nil value, received nil", 2)
+ end
+end
+
+function M.assertIsNaN(value)
+ if type(value) ~= "number" or value == value then
+ failure("expected: nan, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsNaN(value)
+ if type(value) == "number" and value ~= value then
+ failure("expected non nan value, received nan", 2)
+ end
+end
+
+function M.assertIsInf(value)
+ if type(value) ~= "number" or math.abs(value) ~= math.huge then
+ failure("expected: inf, actual: " ..prettystr(value), 2)
+ end
+end
+
+function M.assertNotIsInf(value)
+ if type(value) == "number" and math.abs(value) == math.huge then
+ failure("expected non inf value, received ±inf", 2)
+ end
+end
+
+function M.assertEquals(actual, expected, doDeepAnalysis)
+ if type(actual) == 'table' and type(expected) == 'table' then
+ if not _is_table_equals(actual, expected) then
+ failure( errorMsgEquality(actual, expected, doDeepAnalysis), 2 )
+ end
+ elseif type(actual) ~= type(expected) then
+ failure( errorMsgEquality(actual, expected), 2 )
+ elseif actual ~= expected then
+ failure( errorMsgEquality(actual, expected), 2 )
+ end
+end
+
+function M.almostEquals( actual, expected, margin, margin_boost )
+ if type(actual) ~= 'number' or type(expected) ~= 'number' or type(margin) ~= 'number' then
+ error_fmt(3, 'almostEquals: must supply only number arguments.\nArguments supplied: %s, %s, %s',
+ prettystr(actual), prettystr(expected), prettystr(margin))
+ end
+ if margin < 0 then
+ error('almostEquals: margin must not be negative, current value is ' .. margin, 3)
+ end
+ local realmargin = margin + (margin_boost or M.EPSILON)
+ return math.abs(expected - actual) <= realmargin
+end
+
+function M.assertAlmostEquals( actual, expected, margin )
+ -- check that two floats are close by margin
+ if not M.almostEquals(actual, expected, margin) then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected, actual = actual, expected
+ end
+ fail_fmt(2, 'Values are not almost equal\nExpected: %s with margin of %s, received: %s',
+ expected, margin, actual)
+ end
+end
+
+function M.assertNotEquals(actual, expected)
+ if type(actual) ~= type(expected) then
+ return
+ end
+
+ if type(actual) == 'table' and type(expected) == 'table' then
+ if not _is_table_equals(actual, expected) then
+ return
+ end
+ elseif actual ~= expected then
+ return
+ end
+ fail_fmt(2, 'Received the not expected value: %s', prettystr(actual))
+end
+
+function M.assertNotAlmostEquals( actual, expected, margin )
+ -- check that two floats are not close by margin
+ if M.almostEquals(actual, expected, margin) then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected, actual = actual, expected
+ end
+ fail_fmt(2, 'Values are almost equal\nExpected: %s with a difference above margin of %s, received: %s',
+ expected, margin, actual)
+ end
+end
+
+function M.assertStrContains( str, sub, useRe )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if not string.find(str, sub, 1, not useRe) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, %s %s was not found in string %s',
+ useRe and 'regexp' or 'substring', sub, str)
+ end
+end
+
+function M.assertStrIContains( str, sub )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if not string.find(str:lower(), sub:lower(), 1, true) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, substring %s was not found (case insensitively) in string %s',
+ sub, str)
+ end
+end
+
+function M.assertNotStrContains( str, sub, useRe )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if string.find(str, sub, 1, not useRe) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, %s %s was found in string %s',
+ useRe and 'regexp' or 'substring', sub, str)
+ end
+end
+
+function M.assertNotStrIContains( str, sub )
+ -- this relies on lua string.find function
+ -- a string always contains the empty string
+ if string.find(str:lower(), sub:lower(), 1, true) then
+ sub, str = prettystrPairs(sub, str, '\n')
+ fail_fmt(2, 'Error, substring %s was found (case insensitively) in string %s',
+ sub, str)
+ end
+end
+
+function M.assertStrMatches( str, pattern, start, final )
+ -- Verify a full match for the string
+ -- for a partial match, simply use assertStrContains with useRe set to true
+ if not strMatch( str, pattern, start, final ) then
+ pattern, str = prettystrPairs(pattern, str, '\n')
+ fail_fmt(2, 'Error, pattern %s was not matched by string %s',
+ pattern, str)
+ end
+end
+
+function M.assertErrorMsgEquals( expectedMsg, func, ... )
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ local no_error, error_msg = pcall( func, ... )
+ if no_error then
+ failure( 'No error generated when calling function but expected error: "'..expectedMsg..'"', 2 )
+ end
+
+ if error_msg ~= expectedMsg then
+ error_msg, expectedMsg = prettystrPairs(error_msg, expectedMsg)
+ fail_fmt(2, 'Exact error message expected: %s\nError message received: %s\n',
+ expectedMsg, error_msg)
+ end
+end
+
+function M.assertErrorMsgContains( partialMsg, func, ... )
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ local no_error, error_msg = pcall( func, ... )
+ if no_error then
+ failure( 'No error generated when calling function but expected error containing: '..prettystr(partialMsg), 2 )
+ end
+ if not string.find( error_msg, partialMsg, nil, true ) then
+ error_msg, partialMsg = prettystrPairs(error_msg, partialMsg)
+ fail_fmt(2, 'Error message does not contain: %s\nError message received: %s\n',
+ partialMsg, error_msg)
+ end
+end
+
+function M.assertErrorMsgMatches( expectedMsg, func, ... )
+ -- assert that calling f with the arguments will raise an error
+ -- example: assertError( f, 1, 2 ) => f(1,2) should generate an error
+ local no_error, error_msg = pcall( func, ... )
+ if no_error then
+ failure( 'No error generated when calling function but expected error matching: "'..expectedMsg..'"', 2 )
+ end
+ if not strMatch( error_msg, expectedMsg ) then
+ expectedMsg, error_msg = prettystrPairs(expectedMsg, error_msg)
+ fail_fmt(2, 'Error message does not match: %s\nError message received: %s\n',
+ expectedMsg, error_msg)
+ end
+end
+
+--[[
+Add type assertion functions to the module table M. Each of these functions
+takes a single parameter "value", and checks that its Lua type matches the
+expected string (derived from the function name):
+
+M.assertIsXxx(value) -> ensure that type(value) conforms to "xxx"
+]]
+for _, funcName in ipairs(
+ {'assertIsNumber', 'assertIsString', 'assertIsTable', 'assertIsBoolean',
+ 'assertIsFunction', 'assertIsUserdata', 'assertIsThread'}
+) do
+ local typeExpected = funcName:match("^assertIs([A-Z]%a*)$")
+ -- Lua type() always returns lowercase, also make sure the match() succeeded
+ typeExpected = typeExpected and typeExpected:lower()
+ or error("bad function name '"..funcName.."' for type assertion")
+
+ M[funcName] = function(value)
+ if type(value) ~= typeExpected then
+ fail_fmt(2, 'Expected: a %s value, actual: type %s, value %s',
+ typeExpected, type(value), prettystrPairs(value))
+ end
+ end
+end
+
+--[[
+Add shortcuts for verifying type of a variable, without failure (luaunit v2 compatibility)
+M.isXxx(value) -> returns true if type(value) conforms to "xxx"
+]]
+for _, typeExpected in ipairs(
+ {'Number', 'String', 'Table', 'Boolean',
+ 'Function', 'Userdata', 'Thread', 'Nil' }
+) do
+ local typeExpectedLower = typeExpected:lower()
+ local isType = function(value)
+ return (type(value) == typeExpectedLower)
+ end
+ M['is'..typeExpected] = isType
+ M['is_'..typeExpectedLower] = isType
+end
+
+--[[
+Add non-type assertion functions to the module table M. Each of these functions
+takes a single parameter "value", and checks that its Lua type differs from the
+expected string (derived from the function name):
+
+M.assertNotIsXxx(value) -> ensure that type(value) is not "xxx"
+]]
+for _, funcName in ipairs(
+ {'assertNotIsNumber', 'assertNotIsString', 'assertNotIsTable', 'assertNotIsBoolean',
+ 'assertNotIsFunction', 'assertNotIsUserdata', 'assertNotIsThread'}
+) do
+ local typeUnexpected = funcName:match("^assertNotIs([A-Z]%a*)$")
+ -- Lua type() always returns lowercase, also make sure the match() succeeded
+ typeUnexpected = typeUnexpected and typeUnexpected:lower()
+ or error("bad function name '"..funcName.."' for type assertion")
+
+ M[funcName] = function(value)
+ if type(value) == typeUnexpected then
+ fail_fmt(2, 'Not expected: a %s type, actual: value %s',
+ typeUnexpected, prettystrPairs(value))
+ end
+ end
+end
+
+function M.assertIs(actual, expected)
+ if actual ~= expected then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ actual, expected = expected, actual
+ end
+ expected, actual = prettystrPairs(expected, actual, '\n', ', ')
+ fail_fmt(2, 'Expected object and actual object are not the same\nExpected: %sactual: %s',
+ expected, actual)
+ end
+end
+
+function M.assertNotIs(actual, expected)
+ if actual == expected then
+ if not M.ORDER_ACTUAL_EXPECTED then
+ expected = actual
+ end
+ fail_fmt(2, 'Expected object and actual object are the same object: %s',
+ prettystrPairs(expected))
+ end
+end
+
+function M.assertItemsEquals(actual, expected)
+ -- checks that the items of table expected
+ -- are contained in table actual. Warning, this function
+ -- is at least O(n^2)
+ if not _is_table_items_equals(actual, expected ) then
+ expected, actual = prettystrPairs(expected, actual)
+ fail_fmt(2, 'Contents of the tables are not identical:\nExpected: %s\nActual: %s',
+ expected, actual)
+ end
+end
+
+----------------------------------------------------------------
+-- Compatibility layer
+----------------------------------------------------------------
+
+-- for compatibility with LuaUnit v2.x
+function M.wrapFunctions()
+ -- In LuaUnit version <= 2.1 , this function was necessary to include
+ -- a test function inside the global test suite. Nowadays, the functions
+ -- are simply run directly as part of the test discovery process.
+ -- so just do nothing !
+ io.stderr:write[[Use of WrapFunctions() is no longer needed.
+Just prefix your test function names with "test" or "Test" and they
+will be picked up and run by LuaUnit.
+]]
+end
+
+local list_of_funcs = {
+ -- { official function name , alias }
+
+ -- general assertions
+ { 'assertEquals' , 'assert_equals' },
+ { 'assertItemsEquals' , 'assert_items_equals' },
+ { 'assertNotEquals' , 'assert_not_equals' },
+ { 'assertAlmostEquals' , 'assert_almost_equals' },
+ { 'assertNotAlmostEquals' , 'assert_not_almost_equals' },
+ { 'assertEvalToTrue' , 'assert_eval_to_true' },
+ { 'assertEvalToFalse' , 'assert_eval_to_false' },
+ { 'assertStrContains' , 'assert_str_contains' },
+ { 'assertStrIContains' , 'assert_str_icontains' },
+ { 'assertNotStrContains' , 'assert_not_str_contains' },
+ { 'assertNotStrIContains' , 'assert_not_str_icontains' },
+ { 'assertStrMatches' , 'assert_str_matches' },
+ { 'assertError' , 'assert_error' },
+ { 'assertErrorMsgEquals' , 'assert_error_msg_equals' },
+ { 'assertErrorMsgContains' , 'assert_error_msg_contains' },
+ { 'assertErrorMsgMatches' , 'assert_error_msg_matches' },
+ { 'assertIs' , 'assert_is' },
+ { 'assertNotIs' , 'assert_not_is' },
+ { 'wrapFunctions' , 'WrapFunctions' },
+ { 'wrapFunctions' , 'wrap_functions' },
+
+ -- type assertions: assertIsXXX -> assert_is_xxx
+ { 'assertIsNumber' , 'assert_is_number' },
+ { 'assertIsString' , 'assert_is_string' },
+ { 'assertIsTable' , 'assert_is_table' },
+ { 'assertIsBoolean' , 'assert_is_boolean' },
+ { 'assertIsNil' , 'assert_is_nil' },
+ { 'assertIsTrue' , 'assert_is_true' },
+ { 'assertIsFalse' , 'assert_is_false' },
+ { 'assertIsNaN' , 'assert_is_nan' },
+ { 'assertIsInf' , 'assert_is_inf' },
+ { 'assertIsFunction' , 'assert_is_function' },
+ { 'assertIsThread' , 'assert_is_thread' },
+ { 'assertIsUserdata' , 'assert_is_userdata' },
+
+ -- type assertions: assertIsXXX -> assertXxx
+ { 'assertIsNumber' , 'assertNumber' },
+ { 'assertIsString' , 'assertString' },
+ { 'assertIsTable' , 'assertTable' },
+ { 'assertIsBoolean' , 'assertBoolean' },
+ { 'assertIsNil' , 'assertNil' },
+ { 'assertIsTrue' , 'assertTrue' },
+ { 'assertIsFalse' , 'assertFalse' },
+ { 'assertIsNaN' , 'assertNaN' },
+ { 'assertIsInf' , 'assertInf' },
+ { 'assertIsFunction' , 'assertFunction' },
+ { 'assertIsThread' , 'assertThread' },
+ { 'assertIsUserdata' , 'assertUserdata' },
+
+ -- type assertions: assertIsXXX -> assert_xxx (luaunit v2 compat)
+ { 'assertIsNumber' , 'assert_number' },
+ { 'assertIsString' , 'assert_string' },
+ { 'assertIsTable' , 'assert_table' },
+ { 'assertIsBoolean' , 'assert_boolean' },
+ { 'assertIsNil' , 'assert_nil' },
+ { 'assertIsTrue' , 'assert_true' },
+ { 'assertIsFalse' , 'assert_false' },
+ { 'assertIsNaN' , 'assert_nan' },
+ { 'assertIsInf' , 'assert_inf' },
+ { 'assertIsFunction' , 'assert_function' },
+ { 'assertIsThread' , 'assert_thread' },
+ { 'assertIsUserdata' , 'assert_userdata' },
+
+ -- type assertions: assertNotIsXXX -> assert_not_is_xxx
+ { 'assertNotIsNumber' , 'assert_not_is_number' },
+ { 'assertNotIsString' , 'assert_not_is_string' },
+ { 'assertNotIsTable' , 'assert_not_is_table' },
+ { 'assertNotIsBoolean' , 'assert_not_is_boolean' },
+ { 'assertNotIsNil' , 'assert_not_is_nil' },
+ { 'assertNotIsTrue' , 'assert_not_is_true' },
+ { 'assertNotIsFalse' , 'assert_not_is_false' },
+ { 'assertNotIsNaN' , 'assert_not_is_nan' },
+ { 'assertNotIsInf' , 'assert_not_is_inf' },
+ { 'assertNotIsFunction' , 'assert_not_is_function' },
+ { 'assertNotIsThread' , 'assert_not_is_thread' },
+ { 'assertNotIsUserdata' , 'assert_not_is_userdata' },
+
+ -- type assertions: assertNotIsXXX -> assertNotXxx (luaunit v2 compat)
+ { 'assertNotIsNumber' , 'assertNotNumber' },
+ { 'assertNotIsString' , 'assertNotString' },
+ { 'assertNotIsTable' , 'assertNotTable' },
+ { 'assertNotIsBoolean' , 'assertNotBoolean' },
+ { 'assertNotIsNil' , 'assertNotNil' },
+ { 'assertNotIsTrue' , 'assertNotTrue' },
+ { 'assertNotIsFalse' , 'assertNotFalse' },
+ { 'assertNotIsNaN' , 'assertNotNaN' },
+ { 'assertNotIsInf' , 'assertNotInf' },
+ { 'assertNotIsFunction' , 'assertNotFunction' },
+ { 'assertNotIsThread' , 'assertNotThread' },
+ { 'assertNotIsUserdata' , 'assertNotUserdata' },
+
+ -- type assertions: assertNotIsXXX -> assert_not_xxx
+ { 'assertNotIsNumber' , 'assert_not_number' },
+ { 'assertNotIsString' , 'assert_not_string' },
+ { 'assertNotIsTable' , 'assert_not_table' },
+ { 'assertNotIsBoolean' , 'assert_not_boolean' },
+ { 'assertNotIsNil' , 'assert_not_nil' },
+ { 'assertNotIsTrue' , 'assert_not_true' },
+ { 'assertNotIsFalse' , 'assert_not_false' },
+ { 'assertNotIsNaN' , 'assert_not_nan' },
+ { 'assertNotIsInf' , 'assert_not_inf' },
+ { 'assertNotIsFunction' , 'assert_not_function' },
+ { 'assertNotIsThread' , 'assert_not_thread' },
+ { 'assertNotIsUserdata' , 'assert_not_userdata' },
+
+ -- all assertions with Coroutine duplicate Thread assertions
+ { 'assertIsThread' , 'assertIsCoroutine' },
+ { 'assertIsThread' , 'assertCoroutine' },
+ { 'assertIsThread' , 'assert_is_coroutine' },
+ { 'assertIsThread' , 'assert_coroutine' },
+ { 'assertNotIsThread' , 'assertNotIsCoroutine' },
+ { 'assertNotIsThread' , 'assertNotCoroutine' },
+ { 'assertNotIsThread' , 'assert_not_is_coroutine' },
+ { 'assertNotIsThread' , 'assert_not_coroutine' },
+}
+
+-- Create all aliases in M
+for _,v in ipairs( list_of_funcs ) do
+ local funcname, alias = v[1], v[2]
+ M[alias] = M[funcname]
+
+ if EXPORT_ASSERT_TO_GLOBALS then
+ _G[funcname] = M[funcname]
+ _G[alias] = M[funcname]
+ end
+end
+
+----------------------------------------------------------------
+--
+-- Outputters
+--
+----------------------------------------------------------------
+
+-- A common "base" class for outputters
+-- For concepts involved (class inheritance) see http://www.lua.org/pil/16.2.html
+
+local genericOutput = { __class__ = 'genericOutput' } -- class
+local genericOutput_MT = { __index = genericOutput } -- metatable
+M.genericOutput = genericOutput -- publish, so that custom classes may derive from it
+
+function genericOutput.new(runner, default_verbosity)
+ -- runner is the "parent" object controlling the output, usually a LuaUnit instance
+ local t = { runner = runner }
+ if runner then
+ t.result = runner.result
+ t.verbosity = runner.verbosity or default_verbosity
+ t.fname = runner.fname
+ else
+ t.verbosity = default_verbosity
+ end
+ return setmetatable( t, genericOutput_MT)
+end
+
+-- abstract ("empty") methods
+function genericOutput:startSuite() end
+function genericOutput:startClass(className) end
+function genericOutput:startTest(testName) end
+function genericOutput:addStatus(node) end
+function genericOutput:endTest(node) end
+function genericOutput:endClass() end
+function genericOutput:endSuite() end
+
+
+----------------------------------------------------------------
+-- class TapOutput
+----------------------------------------------------------------
+
+local TapOutput = genericOutput.new() -- derived class
+local TapOutput_MT = { __index = TapOutput } -- metatable
+TapOutput.__class__ = 'TapOutput'
+
+ -- For a good reference for TAP format, check: http://testanything.org/tap-specification.html
+
+ function TapOutput.new(runner)
+ local t = genericOutput.new(runner, M.VERBOSITY_LOW)
+ return setmetatable( t, TapOutput_MT)
+ end
+ function TapOutput:startSuite()
+ print("1.."..self.result.testCount)
+ print('# Started on '..self.result.startDate)
+ end
+ function TapOutput:startClass(className)
+ if className ~= '[TestFunctions]' then
+ print('# Starting class: '..className)
+ end
+ end
+
+ function TapOutput:addStatus( node )
+ io.stdout:write("not ok ", self.result.currentTestNumber, "\t", node.testName, "\n")
+ if self.verbosity > M.VERBOSITY_LOW then
+ print( prefixString( ' ', node.msg ) )
+ end
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( prefixString( ' ', node.stackTrace ) )
+ end
+ end
+
+ function TapOutput:endTest( node )
+ if node:isPassed() then
+ io.stdout:write("ok ", self.result.currentTestNumber, "\t", node.testName, "\n")
+ end
+ end
+
+ function TapOutput:endSuite()
+ print( '# '..M.LuaUnit.statusLine( self.result ) )
+ return self.result.notPassedCount
+ end
+
+
+-- class TapOutput end
+
+----------------------------------------------------------------
+-- class JUnitOutput
+----------------------------------------------------------------
+
+-- See directory junitxml for more information about the junit format
+local JUnitOutput = genericOutput.new() -- derived class
+local JUnitOutput_MT = { __index = JUnitOutput } -- metatable
+JUnitOutput.__class__ = 'JUnitOutput'
+
+ function JUnitOutput.new(runner)
+ local t = genericOutput.new(runner, M.VERBOSITY_LOW)
+ t.testList = {}
+ return setmetatable( t, JUnitOutput_MT )
+ end
+
+ function JUnitOutput:startSuite()
+ -- open xml file early to deal with errors
+ if self.fname == nil then
+ error('With Junit, an output filename must be supplied with --name!')
+ end
+ if string.sub(self.fname,-4) ~= '.xml' then
+ self.fname = self.fname..'.xml'
+ end
+ self.fd = io.open(self.fname, "w")
+ if self.fd == nil then
+ error("Could not open file for writing: "..self.fname)
+ end
+
+ print('# XML output to '..self.fname)
+ print('# Started on '..self.result.startDate)
+ end
+ function JUnitOutput:startClass(className)
+ if className ~= '[TestFunctions]' then
+ print('# Starting class: '..className)
+ end
+ end
+ function JUnitOutput:startTest(testName)
+ print('# Starting test: '..testName)
+ end
+
+ function JUnitOutput:addStatus( node )
+ if node:isFailure() then
+ print('# Failure: ' .. node.msg)
+ -- print('# ' .. node.stackTrace)
+ elseif node:isError() then
+ print('# Error: ' .. node.msg)
+ -- print('# ' .. node.stackTrace)
+ end
+ end
+
+ function JUnitOutput:endSuite()
+ print( '# '..M.LuaUnit.statusLine(self.result))
+
+ -- XML file writing
+ self.fd:write('<?xml version="1.0" encoding="UTF-8" ?>\n')
+ self.fd:write('<testsuites>\n')
+ self.fd:write(string.format(
+ ' <testsuite name="LuaUnit" id="00001" package="" hostname="localhost" tests="%d" timestamp="%s" time="%0.3f" errors="%d" failures="%d">\n',
+ self.result.runCount, self.result.startIsodate, self.result.duration, self.result.errorCount, self.result.failureCount ))
+ self.fd:write(" <properties>\n")
+ self.fd:write(string.format(' <property name="Lua Version" value="%s"/>\n', _VERSION ) )
+ self.fd:write(string.format(' <property name="LuaUnit Version" value="%s"/>\n', M.VERSION) )
+ -- XXX please include system name and version if possible
+ self.fd:write(" </properties>\n")
+
+ for i,node in ipairs(self.result.tests) do
+ self.fd:write(string.format(' <testcase classname="%s" name="%s" time="%0.3f">\n',
+ node.className, node.testName, node.duration ) )
+ if node:isNotPassed() then
+ self.fd:write(node:statusXML())
+ end
+ self.fd:write(' </testcase>\n')
+ end
+
+ -- Next two lines are needed to validate junit ANT xsd, but really not useful in general:
+ self.fd:write(' <system-out/>\n')
+ self.fd:write(' <system-err/>\n')
+
+ self.fd:write(' </testsuite>\n')
+ self.fd:write('</testsuites>\n')
+ self.fd:close()
+ return self.result.notPassedCount
+ end
+
+
+-- class TapOutput end
+
+----------------------------------------------------------------
+-- class TextOutput
+----------------------------------------------------------------
+
+--[[
+
+-- Python Non verbose:
+
+For each test: . or F or E
+
+If some failed tests:
+ ==============
+ ERROR / FAILURE: TestName (testfile.testclass)
+ ---------
+ Stack trace
+
+
+then --------------
+then "Ran x tests in 0.000s"
+then OK or FAILED (failures=1, error=1)
+
+-- Python Verbose:
+testname (filename.classname) ... ok
+testname (filename.classname) ... FAIL
+testname (filename.classname) ... ERROR
+
+then --------------
+then "Ran x tests in 0.000s"
+then OK or FAILED (failures=1, error=1)
+
+-- Ruby:
+Started
+ .
+ Finished in 0.002695 seconds.
+
+ 1 tests, 2 assertions, 0 failures, 0 errors
+
+-- Ruby:
+>> ruby tc_simple_number2.rb
+Loaded suite tc_simple_number2
+Started
+F..
+Finished in 0.038617 seconds.
+
+ 1) Failure:
+test_failure(TestSimpleNumber) [tc_simple_number2.rb:16]:
+Adding doesn't work.
+<3> expected but was
+<4>.
+
+3 tests, 4 assertions, 1 failures, 0 errors
+
+-- Java Junit
+.......F.
+Time: 0,003
+There was 1 failure:
+1) testCapacity(junit.samples.VectorTest)junit.framework.AssertionFailedError
+ at junit.samples.VectorTest.testCapacity(VectorTest.java:87)
+ at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
+ at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
+ at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
+
+FAILURES!!!
+Tests run: 8, Failures: 1, Errors: 0
+
+
+-- Maven
+
+# mvn test
+-------------------------------------------------------
+ T E S T S
+-------------------------------------------------------
+Running math.AdditionTest
+Tests run: 2, Failures: 1, Errors: 0, Skipped: 0, Time elapsed:
+0.03 sec <<< FAILURE!
+
+Results :
+
+Failed tests:
+ testLireSymbole(math.AdditionTest)
+
+Tests run: 2, Failures: 1, Errors: 0, Skipped: 0
+
+
+-- LuaUnit
+---- non verbose
+* display . or F or E when running tests
+---- verbose
+* display test name + ok/fail
+----
+* blank line
+* number) ERROR or FAILURE: TestName
+ Stack trace
+* blank line
+* number) ERROR or FAILURE: TestName
+ Stack trace
+
+then --------------
+then "Ran x tests in 0.000s (%d not selected, %d skipped)"
+then OK or FAILED (failures=1, error=1)
+
+
+]]
+
+local TextOutput = genericOutput.new() -- derived class
+local TextOutput_MT = { __index = TextOutput } -- metatable
+TextOutput.__class__ = 'TextOutput'
+
+ function TextOutput.new(runner)
+ local t = genericOutput.new(runner, M.VERBOSITY_DEFAULT)
+ t.errorList = {}
+ return setmetatable( t, TextOutput_MT )
+ end
+
+ function TextOutput:startSuite()
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( 'Started on '.. self.result.startDate )
+ end
+ end
+
+ function TextOutput:startTest(testName)
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ io.stdout:write( " ", self.result.currentNode.testName, " ... " )
+ end
+ end
+
+ function TextOutput:endTest( node )
+ if node:isPassed() then
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ io.stdout:write("Ok\n")
+ else
+ io.stdout:write(".")
+ end
+ else
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( node.status )
+ print( node.msg )
+ --[[
+ -- find out when to do this:
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print( node.stackTrace )
+ end
+ ]]
+ else
+ -- write only the first character of status
+ io.stdout:write(string.sub(node.status, 1, 1))
+ end
+ end
+ end
+
+ function TextOutput:displayOneFailedTest( index, fail )
+ print(index..") "..fail.testName )
+ print( fail.msg )
+ print( fail.stackTrace )
+ print()
+ end
+
+ function TextOutput:displayFailedTests()
+ if self.result.notPassedCount ~= 0 then
+ print("Failed tests:")
+ print("-------------")
+ for i, v in ipairs(self.result.notPassed) do
+ self:displayOneFailedTest(i, v)
+ end
+ end
+ end
+
+ function TextOutput:endSuite()
+ if self.verbosity > M.VERBOSITY_DEFAULT then
+ print("=========================================================")
+ else
+ print()
+ end
+ self:displayFailedTests()
+ print( M.LuaUnit.statusLine( self.result ) )
+ if self.result.notPassedCount == 0 then
+ print('OK')
+ end
+ end
+
+-- class TextOutput end
+
+
+----------------------------------------------------------------
+-- class NilOutput
+----------------------------------------------------------------
+
+local function nopCallable()
+ --print(42)
+ return nopCallable
+end
+
+local NilOutput = { __class__ = 'NilOuptut' } -- class
+local NilOutput_MT = { __index = nopCallable } -- metatable
+
+function NilOutput.new(runner)
+ return setmetatable( { __class__ = 'NilOutput' }, NilOutput_MT )
+end
+
+----------------------------------------------------------------
+--
+-- class LuaUnit
+--
+----------------------------------------------------------------
+
+M.LuaUnit = {
+ outputType = TextOutput,
+ verbosity = M.VERBOSITY_DEFAULT,
+ __class__ = 'LuaUnit'
+}
+local LuaUnit_MT = { __index = M.LuaUnit }
+
+if EXPORT_ASSERT_TO_GLOBALS then
+ LuaUnit = M.LuaUnit
+end
+
+ function M.LuaUnit.new()
+ return setmetatable( {}, LuaUnit_MT )
+ end
+
+ -----------------[[ Utility methods ]]---------------------
+
+ function M.LuaUnit.asFunction(aObject)
+ -- return "aObject" if it is a function, and nil otherwise
+ if 'function' == type(aObject) then
+ return aObject
+ end
+ end
+
+ function M.LuaUnit.splitClassMethod(someName)
+ --[[
+ Return a pair of className, methodName strings for a name in the form
+ "class.method". If no class part (or separator) is found, will return
+ nil, someName instead (the latter being unchanged).
+
+ This convention thus also replaces the older isClassMethod() test:
+ You just have to check for a non-nil className (return) value.
+ ]]
+ local separator = string.find(someName, '.', 1, true)
+ if separator then
+ return someName:sub(1, separator - 1), someName:sub(separator + 1)
+ end
+ return nil, someName
+ end
+
+ function M.LuaUnit.isMethodTestName( s )
+ -- return true is the name matches the name of a test method
+ -- default rule is that is starts with 'Test' or with 'test'
+ return string.sub(s, 1, 4):lower() == 'test'
+ end
+
+ function M.LuaUnit.isTestName( s )
+ -- return true is the name matches the name of a test
+ -- default rule is that is starts with 'Test' or with 'test'
+ return string.sub(s, 1, 4):lower() == 'test'
+ end
+
+ function M.LuaUnit.collectTests()
+ -- return a list of all test names in the global namespace
+ -- that match LuaUnit.isTestName
+
+ local testNames = {}
+ for k, _ in pairs(_G) do
+ if type(k) == "string" and M.LuaUnit.isTestName( k ) then
+ table.insert( testNames , k )
+ end
+ end
+ table.sort( testNames )
+ return testNames
+ end
+
+ function M.LuaUnit.parseCmdLine( cmdLine )
+ -- parse the command line
+ -- Supported command line parameters:
+ -- --verbose, -v: increase verbosity
+ -- --quiet, -q: silence output
+ -- --error, -e: treat errors as fatal (quit program)
+ -- --output, -o, + name: select output type
+ -- --pattern, -p, + pattern: run test matching pattern, may be repeated
+ -- --exclude, -x, + pattern: run test not matching pattern, may be repeated
+ -- --random, -r, : run tests in random order
+ -- --name, -n, + fname: name of output file for junit, default to stdout
+ -- --count, -c, + num: number of times to execute each test
+ -- [testnames, ...]: run selected test names
+ --
+ -- Returns a table with the following fields:
+ -- verbosity: nil, M.VERBOSITY_DEFAULT, M.VERBOSITY_QUIET, M.VERBOSITY_VERBOSE
+ -- output: nil, 'tap', 'junit', 'text', 'nil'
+ -- testNames: nil or a list of test names to run
+ -- exeCount: num or 1
+ -- pattern: nil or a list of patterns
+ -- exclude: nil or a list of patterns
+
+ local result, state = {}, nil
+ local SET_OUTPUT = 1
+ local SET_PATTERN = 2
+ local SET_EXCLUDE = 3
+ local SET_FNAME = 4
+ local SET_XCOUNT = 5
+
+ if cmdLine == nil then
+ return result
+ end
+
+ local function parseOption( option )
+ if option == '--help' or option == '-h' then
+ result['help'] = true
+ return
+ elseif option == '--version' then
+ result['version'] = true
+ return
+ elseif option == '--verbose' or option == '-v' then
+ result['verbosity'] = M.VERBOSITY_VERBOSE
+ return
+ elseif option == '--quiet' or option == '-q' then
+ result['verbosity'] = M.VERBOSITY_QUIET
+ return
+ elseif option == '--error' or option == '-e' then
+ result['quitOnError'] = true
+ return
+ elseif option == '--failure' or option == '-f' then
+ result['quitOnFailure'] = true
+ return
+ elseif option == '--random' or option == '-r' then
+ result['randomize'] = true
+ return
+ elseif option == '--output' or option == '-o' then
+ state = SET_OUTPUT
+ return state
+ elseif option == '--name' or option == '-n' then
+ state = SET_FNAME
+ return state
+ elseif option == '--count' or option == '-c' then
+ state = SET_XCOUNT
+ return state
+ elseif option == '--pattern' or option == '-p' then
+ state = SET_PATTERN
+ return state
+ elseif option == '--exclude' or option == '-x' then
+ state = SET_EXCLUDE
+ return state
+ end
+ error('Unknown option: '..option,3)
+ end
+
+ local function setArg( cmdArg, state )
+ if state == SET_OUTPUT then
+ result['output'] = cmdArg
+ return
+ elseif state == SET_FNAME then
+ result['fname'] = cmdArg
+ return
+ elseif state == SET_XCOUNT then
+ result['exeCount'] = tonumber(cmdArg)
+ or error('Malformed -c argument: '..cmdArg)
+ return
+ elseif state == SET_PATTERN then
+ if result['pattern'] then
+ table.insert( result['pattern'], cmdArg )
+ else
+ result['pattern'] = { cmdArg }
+ end
+ return
+ elseif state == SET_EXCLUDE then
+ if result['exclude'] then
+ table.insert( result['exclude'], cmdArg )
+ else
+ result['exclude'] = { cmdArg }
+ end
+ return
+ end
+ error('Unknown parse state: '.. state)
+ end
+
+
+ for i, cmdArg in ipairs(cmdLine) do
+ if state ~= nil then
+ setArg( cmdArg, state, result )
+ state = nil
+ else
+ if cmdArg:sub(1,1) == '-' then
+ state = parseOption( cmdArg )
+ else
+ if result['testNames'] then
+ table.insert( result['testNames'], cmdArg )
+ else
+ result['testNames'] = { cmdArg }
+ end
+ end
+ end
+ end
+
+ if result['help'] then
+ M.LuaUnit.help()
+ end
+
+ if result['version'] then
+ M.LuaUnit.version()
+ end
+
+ if state ~= nil then
+ error('Missing argument after '..cmdLine[ #cmdLine ],2 )
+ end
+
+ return result
+ end
+
+ function M.LuaUnit.help()
+ print(M.USAGE)
+ os.exit(0)
+ end
+
+ function M.LuaUnit.version()
+ print('LuaUnit v'..M.VERSION..' by Philippe Fremy <phil@freehackers.org>')
+ os.exit(0)
+ end
+
+----------------------------------------------------------------
+-- class NodeStatus
+----------------------------------------------------------------
+
+ local NodeStatus = { __class__ = 'NodeStatus' } -- class
+ local NodeStatus_MT = { __index = NodeStatus } -- metatable
+ M.NodeStatus = NodeStatus
+
+ -- values of status
+ NodeStatus.PASS = 'PASS'
+ NodeStatus.FAIL = 'FAIL'
+ NodeStatus.ERROR = 'ERROR'
+
+ function NodeStatus.new( number, testName, className )
+ local t = { number = number, testName = testName, className = className }
+ setmetatable( t, NodeStatus_MT )
+ t:pass()
+ return t
+ end
+
+ function NodeStatus:pass()
+ self.status = self.PASS
+ -- useless but we know it's the field we want to use
+ self.msg = nil
+ self.stackTrace = nil
+ end
+
+ function NodeStatus:fail(msg, stackTrace)
+ self.status = self.FAIL
+ self.msg = msg
+ self.stackTrace = stackTrace
+ end
+
+ function NodeStatus:error(msg, stackTrace)
+ self.status = self.ERROR
+ self.msg = msg
+ self.stackTrace = stackTrace
+ end
+
+ function NodeStatus:isPassed()
+ return self.status == NodeStatus.PASS
+ end
+
+ function NodeStatus:isNotPassed()
+ -- print('hasFailure: '..prettystr(self))
+ return self.status ~= NodeStatus.PASS
+ end
+
+ function NodeStatus:isFailure()
+ return self.status == NodeStatus.FAIL
+ end
+
+ function NodeStatus:isError()
+ return self.status == NodeStatus.ERROR
+ end
+
+ function NodeStatus:statusXML()
+ if self:isError() then
+ return table.concat(
+ {' <error type="', xmlEscape(self.msg), '">\n',
+ ' <![CDATA[', xmlCDataEscape(self.stackTrace),
+ ']]></error>\n'})
+ elseif self:isFailure() then
+ return table.concat(
+ {' <failure type="', xmlEscape(self.msg), '">\n',
+ ' <![CDATA[', xmlCDataEscape(self.stackTrace),
+ ']]></failure>\n'})
+ end
+ return ' <passed/>\n' -- (not XSD-compliant! normally shouldn't get here)
+ end
+
+ --------------[[ Output methods ]]-------------------------
+
+ local function conditional_plural(number, singular)
+ -- returns a grammatically well-formed string "%d <singular/plural>"
+ local suffix = ''
+ if number ~= 1 then -- use plural
+ suffix = (singular:sub(-2) == 'ss') and 'es' or 's'
+ end
+ return string.format('%d %s%s', number, singular, suffix)
+ end
+
+ function M.LuaUnit.statusLine(result)
+ -- return status line string according to results
+ local s = {
+ string.format('Ran %d tests in %0.3f seconds',
+ result.runCount, result.duration),
+ conditional_plural(result.passedCount, 'success'),
+ }
+ if result.notPassedCount > 0 then
+ if result.failureCount > 0 then
+ table.insert(s, conditional_plural(result.failureCount, 'failure'))
+ end
+ if result.errorCount > 0 then
+ table.insert(s, conditional_plural(result.errorCount, 'error'))
+ end
+ else
+ table.insert(s, '0 failures')
+ end
+ if result.nonSelectedCount > 0 then
+ table.insert(s, string.format("%d non-selected", result.nonSelectedCount))
+ end
+ return table.concat(s, ', ')
+ end
+
+ function M.LuaUnit:startSuite(testCount, nonSelectedCount)
+ self.result = {
+ testCount = testCount,
+ nonSelectedCount = nonSelectedCount,
+ passedCount = 0,
+ runCount = 0,
+ currentTestNumber = 0,
+ currentClassName = "",
+ currentNode = nil,
+ suiteStarted = true,
+ startTime = os.clock(),
+ startDate = os.date(os.getenv('LUAUNIT_DATEFMT')),
+ startIsodate = os.date('%Y-%m-%dT%H:%M:%S'),
+ patternIncludeFilter = self.patternIncludeFilter,
+ patternExcludeFilter = self.patternExcludeFilter,
+ tests = {},
+ failures = {},
+ errors = {},
+ notPassed = {},
+ }
+
+ self.outputType = self.outputType or TextOutput
+ self.output = self.outputType.new(self)
+ self.output:startSuite()
+ end
+
+ function M.LuaUnit:startClass( className )
+ self.result.currentClassName = className
+ self.output:startClass( className )
+ end
+
+ function M.LuaUnit:startTest( testName )
+ self.result.currentTestNumber = self.result.currentTestNumber + 1
+ self.result.runCount = self.result.runCount + 1
+ self.result.currentNode = NodeStatus.new(
+ self.result.currentTestNumber,
+ testName,
+ self.result.currentClassName
+ )
+ self.result.currentNode.startTime = os.clock()
+ table.insert( self.result.tests, self.result.currentNode )
+ self.output:startTest( testName )
+ end
+
+ function M.LuaUnit:addStatus( err )
+ -- "err" is expected to be a table / result from protectedCall()
+ if err.status == NodeStatus.PASS then
+ return
+ end
+
+ local node = self.result.currentNode
+
+ --[[ As a first approach, we will report only one error or one failure for one test.
+
+ However, we can have the case where the test is in failure, and the teardown is in error.
+ In such case, it's a good idea to report both a failure and an error in the test suite. This is
+ what Python unittest does for example. However, it mixes up counts so need to be handled carefully: for
+ example, there could be more (failures + errors) count that tests. What happens to the current node ?
+
+ We will do this more intelligent version later.
+ ]]
+
+ -- if the node is already in failure/error, just don't report the new error (see above)
+ if node.status ~= NodeStatus.PASS then
+ return
+ end
+
+ if err.status == NodeStatus.FAIL then
+ node:fail( err.msg, err.trace )
+ table.insert( self.result.failures, node )
+ elseif err.status == NodeStatus.ERROR then
+ node:error( err.msg, err.trace )
+ table.insert( self.result.errors, node )
+ end
+
+ if node:isFailure() or node:isError() then
+ -- add to the list of failed tests (gets printed separately)
+ table.insert( self.result.notPassed, node )
+ end
+ self.output:addStatus( node )
+ end
+
+ function M.LuaUnit:endTest()
+ local node = self.result.currentNode
+ -- print( 'endTest() '..prettystr(node))
+ -- print( 'endTest() '..prettystr(node:isNotPassed()))
+ node.duration = os.clock() - node.startTime
+ node.startTime = nil
+ self.output:endTest( node )
+
+ if node:isPassed() then
+ self.result.passedCount = self.result.passedCount + 1
+ elseif node:isError() then
+ if self.quitOnError or self.quitOnFailure then
+ -- Runtime error - abort test execution as requested by
+ -- "--error" option. This is done by setting a special
+ -- flag that gets handled in runSuiteByInstances().
+ print("\nERROR during LuaUnit test execution:\n" .. node.msg)
+ self.result.aborted = true
+ end
+ elseif node:isFailure() then
+ if self.quitOnFailure then
+ -- Failure - abort test execution as requested by
+ -- "--failure" option. This is done by setting a special
+ -- flag that gets handled in runSuiteByInstances().
+ print("\nFailure during LuaUnit test execution:\n" .. node.msg)
+ self.result.aborted = true
+ end
+ end
+ self.result.currentNode = nil
+ end
+
+ function M.LuaUnit:endClass()
+ self.output:endClass()
+ end
+
+ function M.LuaUnit:endSuite()
+ if self.result.suiteStarted == false then
+ error('LuaUnit:endSuite() -- suite was already ended' )
+ end
+ self.result.duration = os.clock()-self.result.startTime
+ self.result.suiteStarted = false
+
+ -- Expose test counts for outputter's endSuite(). This could be managed
+ -- internally instead, but unit tests (and existing use cases) might
+ -- rely on these fields being present.
+ self.result.notPassedCount = #self.result.notPassed
+ self.result.failureCount = #self.result.failures
+ self.result.errorCount = #self.result.errors
+
+ self.output:endSuite()
+ end
+
+ function M.LuaUnit:setOutputType(outputType)
+ -- default to text
+ -- tap produces results according to TAP format
+ if outputType:upper() == "NIL" then
+ self.outputType = NilOutput
+ return
+ end
+ if outputType:upper() == "TAP" then
+ self.outputType = TapOutput
+ return
+ end
+ if outputType:upper() == "JUNIT" then
+ self.outputType = JUnitOutput
+ return
+ end
+ if outputType:upper() == "TEXT" then
+ self.outputType = TextOutput
+ return
+ end
+ error( 'No such format: '..outputType,2)
+ end
+
+ --------------[[ Runner ]]-----------------
+
+ function M.LuaUnit:protectedCall(classInstance, methodInstance, prettyFuncName)
+ -- if classInstance is nil, this is just a function call
+ -- else, it's method of a class being called.
+
+ local function err_handler(e)
+ -- transform error into a table, adding the traceback information
+ return {
+ status = NodeStatus.ERROR,
+ msg = e,
+ trace = string.sub(debug.traceback("", 3), 2)
+ }
+ end
+
+ local ok, err
+ if classInstance then
+ -- stupid Lua < 5.2 does not allow xpcall with arguments so let's use a workaround
+ ok, err = xpcall( function () methodInstance(classInstance) end, err_handler )
+ else
+ ok, err = xpcall( function () methodInstance() end, err_handler )
+ end
+ if ok then
+ return {status = NodeStatus.PASS}
+ end
+
+ -- determine if the error was a failed test:
+ -- We do this by stripping the failure prefix from the error message,
+ -- while keeping track of the gsub() count. A non-zero value -> failure
+ local failed, iter_msg
+ iter_msg = self.exeCount and 'iteration: '..self.currentCount..', '
+ err.msg, failed = err.msg:gsub(M.FAILURE_PREFIX, iter_msg or '', 1)
+ if failed > 0 then
+ err.status = NodeStatus.FAIL
+ end
+
+ -- reformat / improve the stack trace
+ if prettyFuncName then -- we do have the real method name
+ err.trace = err.trace:gsub("in (%a+) 'methodInstance'", "in %1 '"..prettyFuncName.."'")
+ end
+ if STRIP_LUAUNIT_FROM_STACKTRACE then
+ err.trace = stripLuaunitTrace(err.trace)
+ end
+
+ return err -- return the error "object" (table)
+ end
+
+
+ function M.LuaUnit:execOneFunction(className, methodName, classInstance, methodInstance)
+ -- When executing a test function, className and classInstance must be nil
+ -- When executing a class method, all parameters must be set
+
+ if type(methodInstance) ~= 'function' then
+ error( tostring(methodName)..' must be a function, not '..type(methodInstance))
+ end
+
+ local prettyFuncName
+ if className == nil then
+ className = '[TestFunctions]'
+ prettyFuncName = methodName
+ else
+ prettyFuncName = className..'.'..methodName
+ end
+
+ if self.lastClassName ~= className then
+ if self.lastClassName ~= nil then
+ self:endClass()
+ end
+ self:startClass( className )
+ self.lastClassName = className
+ end
+
+ self:startTest(prettyFuncName)
+
+ local node = self.result.currentNode
+ for iter_n = 1, self.exeCount or 1 do
+ if node:isNotPassed() then
+ break
+ end
+ self.currentCount = iter_n
+
+ -- run setUp first (if any)
+ if classInstance then
+ local func = self.asFunction( classInstance.setUp ) or
+ self.asFunction( classInstance.Setup ) or
+ self.asFunction( classInstance.setup ) or
+ self.asFunction( classInstance.SetUp )
+ if func then
+ self:addStatus(self:protectedCall(classInstance, func, className..'.setUp'))
+ end
+ end
+
+ -- run testMethod()
+ if node:isPassed() then
+ self:addStatus(self:protectedCall(classInstance, methodInstance, prettyFuncName))
+ end
+
+ -- lastly, run tearDown (if any)
+ if classInstance then
+ local func = self.asFunction( classInstance.tearDown ) or
+ self.asFunction( classInstance.TearDown ) or
+ self.asFunction( classInstance.teardown ) or
+ self.asFunction( classInstance.Teardown )
+ if func then
+ self:addStatus(self:protectedCall(classInstance, func, className..'.tearDown'))
+ end
+ end
+ end
+
+ self:endTest()
+ end
+
+ function M.LuaUnit.expandOneClass( result, className, classInstance )
+ --[[
+ Input: a list of { name, instance }, a class name, a class instance
+ Ouptut: modify result to add all test method instance in the form:
+ { className.methodName, classInstance }
+ ]]
+ for methodName, methodInstance in sortedPairs(classInstance) do
+ if M.LuaUnit.asFunction(methodInstance) and M.LuaUnit.isMethodTestName( methodName ) then
+ table.insert( result, { className..'.'..methodName, classInstance } )
+ end
+ end
+ end
+
+ function M.LuaUnit.expandClasses( listOfNameAndInst )
+ --[[
+ -- expand all classes (provided as {className, classInstance}) to a list of {className.methodName, classInstance}
+ -- functions and methods remain untouched
+
+ Input: a list of { name, instance }
+
+ Output:
+ * { function name, function instance } : do nothing
+ * { class.method name, class instance }: do nothing
+ * { class name, class instance } : add all method names in the form of (className.methodName, classInstance)
+ ]]
+ local result = {}
+
+ for i,v in ipairs( listOfNameAndInst ) do
+ local name, instance = v[1], v[2]
+ if M.LuaUnit.asFunction(instance) then
+ table.insert( result, { name, instance } )
+ else
+ if type(instance) ~= 'table' then
+ error( 'Instance must be a table or a function, not a '..type(instance)..', value '..prettystr(instance))
+ end
+ local className, methodName = M.LuaUnit.splitClassMethod( name )
+ if className then
+ local methodInstance = instance[methodName]
+ if methodInstance == nil then
+ error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) )
+ end
+ table.insert( result, { name, instance } )
+ else
+ M.LuaUnit.expandOneClass( result, name, instance )
+ end
+ end
+ end
+
+ return result
+ end
+
+ function M.LuaUnit.applyPatternFilter( patternIncFilter, patternExcFilter, listOfNameAndInst )
+ local included, excluded = {}, {}
+ for i, v in ipairs( listOfNameAndInst ) do
+ -- local name, instance = v[1], v[2]
+ if patternFilter( patternIncFilter, v[1], true ) and
+ not patternFilter( patternExcFilter, v[1], false ) then
+ table.insert( included, v )
+ else
+ table.insert( excluded, v )
+ end
+ end
+ return included, excluded
+ end
+
+ function M.LuaUnit:runSuiteByInstances( listOfNameAndInst )
+ --[[ Run an explicit list of tests. All test instances and names must be supplied.
+ each test must be one of:
+ * { function name, function instance }
+ * { class name, class instance }
+ * { class.method name, class instance }
+ ]]
+
+ local expandedList = self.expandClasses( listOfNameAndInst )
+ if self.randomize then
+ randomizeTable( expandedList )
+ end
+ local filteredList, filteredOutList = self.applyPatternFilter(
+ self.patternIncludeFilter, self.patternExcludeFilter, expandedList )
+
+ self:startSuite( #filteredList, #filteredOutList )
+
+ for i,v in ipairs( filteredList ) do
+ local name, instance = v[1], v[2]
+ if M.LuaUnit.asFunction(instance) then
+ self:execOneFunction( nil, name, nil, instance )
+ else
+ -- expandClasses() should have already taken care of sanitizing the input
+ assert( type(instance) == 'table' )
+ local className, methodName = M.LuaUnit.splitClassMethod( name )
+ assert( className ~= nil )
+ local methodInstance = instance[methodName]
+ assert(methodInstance ~= nil)
+ self:execOneFunction( className, methodName, instance, methodInstance )
+ end
+ if self.result.aborted then
+ break -- "--error" or "--failure" option triggered
+ end
+ end
+
+ if self.lastClassName ~= nil then
+ self:endClass()
+ end
+
+ self:endSuite()
+
+ if self.result.aborted then
+ print("LuaUnit ABORTED (as requested by --error or --failure option)")
+ os.exit(-2)
+ end
+ end
+
+ function M.LuaUnit:runSuiteByNames( listOfName )
+ --[[ Run LuaUnit with a list of generic names, coming either from command-line or from global
+ namespace analysis. Convert the list into a list of (name, valid instances (table or function))
+ and calls runSuiteByInstances.
+ ]]
+
+ local instanceName, instance
+ local listOfNameAndInst = {}
+
+ for i,name in ipairs( listOfName ) do
+ local className, methodName = M.LuaUnit.splitClassMethod( name )
+ if className then
+ instanceName = className
+ instance = _G[instanceName]
+
+ if instance == nil then
+ error( "No such name in global space: "..instanceName )
+ end
+
+ if type(instance) ~= 'table' then
+ error( 'Instance of '..instanceName..' must be a table, not '..type(instance))
+ end
+
+ local methodInstance = instance[methodName]
+ if methodInstance == nil then
+ error( "Could not find method in class "..tostring(className).." for method "..tostring(methodName) )
+ end
+
+ else
+ -- for functions and classes
+ instanceName = name
+ instance = _G[instanceName]
+ end
+
+ if instance == nil then
+ error( "No such name in global space: "..instanceName )
+ end
+
+ if (type(instance) ~= 'table' and type(instance) ~= 'function') then
+ error( 'Name must match a function or a table: '..instanceName )
+ end
+
+ table.insert( listOfNameAndInst, { name, instance } )
+ end
+
+ self:runSuiteByInstances( listOfNameAndInst )
+ end
+
+ function M.LuaUnit.run(...)
+ -- Run some specific test classes.
+ -- If no arguments are passed, run the class names specified on the
+ -- command line. If no class name is specified on the command line
+ -- run all classes whose name starts with 'Test'
+ --
+ -- If arguments are passed, they must be strings of the class names
+ -- that you want to run or generic command line arguments (-o, -p, -v, ...)
+
+ local runner = M.LuaUnit.new()
+ return runner:runSuite(...)
+ end
+
+ function M.LuaUnit:runSuite( ... )
+
+ local args = {...}
+ if type(args[1]) == 'table' and args[1].__class__ == 'LuaUnit' then
+ -- run was called with the syntax M.LuaUnit:runSuite()
+ -- we support both M.LuaUnit.run() and M.LuaUnit:run()
+ -- strip out the first argument
+ table.remove(args,1)
+ end
+
+ if #args == 0 then
+ args = cmdline_argv
+ end
+
+ local options = pcall_or_abort( M.LuaUnit.parseCmdLine, args )
+
+ -- We expect these option fields to be either `nil` or contain
+ -- valid values, so it's safe to always copy them directly.
+ self.verbosity = options.verbosity
+ self.quitOnError = options.quitOnError
+ self.quitOnFailure = options.quitOnFailure
+ self.fname = options.fname
+
+ self.exeCount = options.exeCount
+ self.patternIncludeFilter = options.pattern
+ self.patternExcludeFilter = options.exclude
+ self.randomize = options.randomize
+
+ if options.output then
+ if options.output:lower() == 'junit' and options.fname == nil then
+ print('With junit output, a filename must be supplied with -n or --name')
+ os.exit(-1)
+ end
+ pcall_or_abort(self.setOutputType, self, options.output)
+ end
+
+ self:runSuiteByNames( options.testNames or M.LuaUnit.collectTests() )
+
+ return self.result.notPassedCount
+ end
+-- class LuaUnit
+
+-- For compatbility with LuaUnit v2
+M.run = M.LuaUnit.run
+M.Run = M.LuaUnit.run
+
+function M:setVerbosity( verbosity )
+ M.LuaUnit.verbosity = verbosity
+end
+M.set_verbosity = M.setVerbosity
+M.SetVerbosity = M.setVerbosity
+
+
+return M
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua
new file mode 100644
index 00000000000..001b97231b9
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/test.lua
@@ -0,0 +1,36 @@
+--[[
+This file contains the unit tests for the physical module.
+
+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")
+
+TestDimension = require("testDimension")
+TestUnit = require("testUnit")
+TestQuantity = require("testQuantity")
+TestNumber = require("testNumber")
+TestDefinition = require("testDefinition")
+TestData = require("testData")
+
+
+lu.LuaUnit.verbosity = 2
+os.exit( lu.LuaUnit.run() )
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua
new file mode 100644
index 00000000000..96d6201aa45
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testData.lua
@@ -0,0 +1,238 @@
+--[[
+This file contains the unit tests for the physical.Data object.
+
+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 = "../?.lua;" .. package.path
+
+local physical = require("physical")
+
+local Data = physical.Data
+local D = physical.Dimension
+local Q = physical.Quantitiy
+local N = physical.Number
+
+local N = physical.Number
+N.compact = true
+
+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..'] = ' .. tostring(v) .. ','
+ else
+ s = s .. '['..k..'] = ' .. dump(v) .. ','
+ end
+ end
+ return s .. '}\n'
+ else
+ return tostring(o)
+ end
+end
+
+
+local function contains(table, val)
+ for i=1,#table do
+ if table[i] == val then
+ return true
+ end
+ end
+ return false
+end
+
+local function count(table)
+ local n = 0
+ for k,v in pairs(table) do
+ n = n + 1
+ end
+ return n
+end
+
+
+TestData = {}
+
+
+-- Test astronomical data
+
+function TestData:testAstronomicalBodies()
+ local bodies = Data.Astronomical()
+ lu.assertTrue( contains(bodies,"Sun") )
+ lu.assertTrue( contains(bodies,"Earth") )
+ lu.assertTrue( contains(bodies,"Ceres") )
+end
+
+function TestData:testAstronomicalKeys()
+ local keys = Data.Astronomical("Earth")
+
+ lu.assertTrue( keys["Mass"] ~= nil )
+ lu.assertTrue( keys["EquatorialRadius"] ~= nil )
+end
+
+function TestData:testSun()
+ local m_s = N(1.9884e30, 2e26) * _kg
+ lu.assertEquals( tostring(Data.Astronomical("Sun","Mass")), tostring(m_s) )
+end
+
+function TestData:testMercury()
+ local m = (N(2.2031870799e13, 8.6e5) * _m^3 * _s^(-2) / _Gc ):to()
+
+ lu.assertEquals( tostring(Data.Astronomical("Mercury","Mass")), tostring(m) )
+end
+
+function TestData:testVenus()
+ local m = Data.Astronomical("Sun","Mass") / N(4.08523719e5,8e-3)
+ lu.assertEquals( tostring(Data.Astronomical("Venus","Mass")), tostring(m) )
+end
+
+function TestData:testEarth()
+ local m = N("5.97220(60)e24") * _kg
+ lu.assertEquals( tostring(Data.Astronomical("Earth","Mass")), tostring(m) )
+end
+
+function TestData:testMoon()
+ local m = Data.Astronomical("Earth","Mass") * N(1.23000371e-2,4e-10)
+ lu.assertEquals( tostring(Data.Astronomical("Moon","Mass")), tostring(m) )
+end
+
+function TestData:testMars()
+ local m = Data.Astronomical("Sun","Mass") / N(3.09870359e6,2e-2)
+
+ lu.assertEquals( tostring(Data.Astronomical("Mars","Mass")), tostring(m) )
+end
+
+function TestData:testJupiter()
+ local m = Data.Astronomical("Sun","Mass") / N(1.047348644e3,1.7e-5)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Jupiter","Mass")) )
+end
+
+function TestData:testSaturn()
+ local m = Data.Astronomical("Sun","Mass") / N(3.4979018e3,1e-4)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Saturn","Mass")) )
+end
+
+function TestData:testUranus()
+ local m = Data.Astronomical("Sun","Mass") / N(2.290298e4,3e-2)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Uranus","Mass")) )
+end
+
+function TestData:testNeptune()
+ local m = Data.Astronomical("Sun","Mass") / N(1.941226e4,3e-2)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Neptune","Mass")) )
+end
+
+function TestData:testPluto()
+ local m = Data.Astronomical("Sun","Mass") / N(1.36566e8,2.8e4)
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Pluto","Mass")) )
+end
+
+function TestData:testEris()
+ local m = Data.Astronomical("Sun","Mass") / N(1.191e8,1.4e6)
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Eris","Mass")) )
+end
+
+function TestData:testCeres()
+ local m = N(4.72e-10,3e-12) * Data.Astronomical("Sun","Mass")
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Ceres","Mass")) )
+end
+
+function TestData:testPallas()
+ local m = N(1.03e-10,3e-12) * Data.Astronomical("Sun","Mass")
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Pallas","Mass")) )
+end
+
+function TestData:testVesta()
+ local m = N(1.35e-10,3e-12) * Data.Astronomical("Sun","Mass")
+
+ lu.assertEquals(tostring(m), tostring(Data.Astronomical("Vesta","Mass")) )
+end
+
+
+
+-- Test isotope data
+function TestQuantity.isotopeGetByAZError()
+ local Z = Data.Isotope({56,55},"Z")
+end
+function TestData:testIsotopeGetByAZ()
+ lu.assertEquals( Data.Isotope({4,2},"A"), 4 )
+ lu.assertEquals( Data.Isotope({4,2},"Z"), 2 )
+
+ lu.assertTrue( (28667 * _keV):isclose(Data.Isotope({3,3},"MassExcess"),1e-9) )
+
+ lu.assertEquals( Data.Isotope({56,25},"A"), 56 )
+ lu.assertEquals( Data.Isotope({56,25},"Z"), 25 )
+
+ lu.assertTrue( (-2267 * _keV):isclose(Data.Isotope({3,3},"BindingEnergyPerNucleon"),1e-9) )
+
+ lu.assertError( TestQuantity.isotopeGetByAZError )
+end
+
+function TestData:testIsotopeGetAllKeys()
+ local row = Data.Isotope({4,2})
+ lu.assertTrue( row["MassExcess"] ~= nil )
+end
+
+
+function TestData:testIsotopeGetByIsotopeName()
+ lu.assertEquals( Data.Isotope("Helium-5","A"), 5 )
+
+
+ lu.assertEquals( Data.Isotope("Helium-5","Z"), 2 )
+
+ lu.assertEquals( Data.Isotope("Lithium5","A"), 5 )
+ lu.assertEquals( Data.Isotope("Lithium5","Z"), 3 )
+
+ lu.assertEquals( Data.Isotope("5He","A"), 5 )
+ lu.assertEquals( Data.Isotope("5He","Z"), 2 )
+end
+
+function TestData:testIsotopeGetByElementName()
+ local list = Data.Isotope("Fe","symbol")
+
+ lu.assertEquals( count(list), 31 )
+ lu.assertTrue( list["47Fe"] ~= nil )
+ lu.assertTrue( list["56Fe"] ~= nil )
+ lu.assertTrue( list["66Fe"] ~= nil )
+
+ --print(dump(Data.Isotope("He",{"DecayModes","DecayModesIntensity"})))
+end
+
+function TestData:testIsotopeGetAll()
+ local isotopes = Data.Isotope()
+
+ lu.assertTrue( contains(isotopes,"6He") )
+ lu.assertTrue( contains(isotopes,"55K") )
+ lu.assertTrue( contains(isotopes,"209Rn") )
+ lu.assertTrue( contains(isotopes,"295Ei") )
+end
+
+
+return TestData
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua
new file mode 100644
index 00000000000..08efc8bb9df
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDefinition.lua
@@ -0,0 +1,964 @@
+--[[
+This file contains the unit tests for the physical units defined in "defition.lua".
+
+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 = "../?.lua;" .. package.path
+local physical = require("physical")
+local N = physical.Number
+
+
+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
+
+
+TestDefinition = {}
+
+-- SI Derived Units
+function TestDefinition:testGram()
+ lu.assertTrue( 5000 * _g == 5 * _kg)
+ lu.assertTrue( 5000 * _mg == 5 * _g)
+ lu.assertTrue( (1200 * _ng):isclose(1.2 * _ug,1e-10))
+end
+
+function TestDefinition:testHertz()
+ local f = (1.24e6*_Hz):to(_kHz)
+ lu.assertTrue(f == 1.24e3 * _kHz)
+
+ local f = (30/_s):to(_Hz)
+ lu.assertTrue(f == 30*_Hz)
+end
+
+function TestDefinition:testNewton()
+ local F = (30*_kg*5*_m/(2 * _s^2)):to(_N)
+ lu.assertTrue(F == 75 * _N)
+end
+
+function TestDefinition:testPascal()
+ local F = 400000 * _N
+ local A = 2 * _m^2
+ local P = (F/A):to(_Pa)
+ lu.assertTrue(P == 200000 * _Pa)
+end
+
+function TestDefinition:testJoule()
+ local F = 2 * _kN
+ local s = 5 * _mm
+ local W = (F*s):to(_J)
+ lu.assertTrue(W == 10*_J)
+end
+
+function TestDefinition:testWatt()
+ local W = 2 * _MJ
+ local t = 2 * _h
+ local P = (W/t):to(_W)
+ lu.assertTrue(P:isclose(277.777777777777778 * _W, 1e-10))
+end
+
+
+function TestDefinition:testCoulomb()
+ local I = 3.4 * _pA
+ local t = 1 * _min
+ local Q = (I*t):to(_C)
+ lu.assertTrue(Q:isclose(2.04e-10 * _C, 1e-10))
+end
+
+function TestDefinition:testVolt()
+ local E = 45 * _J
+ local Q = 4 * _C
+ local U = (E/Q):to(_V)
+ lu.assertTrue(U:isclose(11.25 * _V, 1e-10))
+end
+
+
+function TestDefinition:testFarad()
+ local Q = 40 * _mC
+ local U = 12 * _V
+ local C = (Q/U):to(_F)
+ lu.assertTrue(C:isclose(0.003333333333333 * _F, 1e-10))
+end
+
+function TestDefinition:testOhm()
+ local I = 30 * _mA
+ local U = 5 * _V
+ local R = (U/I):to(_Ohm)
+ lu.assertTrue(R:isclose(166.666666666666667 * _Ohm, 1e-10))
+end
+
+function TestDefinition:testSiemens()
+ local R = 5 * _kOhm
+ local G = (1/R):to(_S)
+ lu.assertTrue(G:isclose(0.0002 * _S, 1e-10))
+end
+
+function TestDefinition:testWeber()
+ local B = 2.3 * _mT
+ local A = 23 * _cm^2
+ local Phi = (B*A):to(_Wb)
+ lu.assertTrue(Phi:isclose(0.00000529 * _Wb, 1e-10))
+end
+
+function TestDefinition:testTesla()
+ local Phi = 40 * _uWb
+ local A = 78 * _mm^2
+ local B = (Phi/A):to(_T)
+ lu.assertTrue(B:isclose(0.512820512820513 * _T, 1e-10))
+end
+
+function TestDefinition:testHenry()
+ local U = 45 * _mV
+ local I = 20 * _mA
+ local t = 100 * _ms
+ local L = (U/(I/t)):to(_H)
+ lu.assertTrue(L:isclose(0.225*_H, 1e-10))
+end
+
+function TestDefinition:testLumen()
+ local I = 400 * _cd
+ local r = 2.4 * _m
+ local A = 1.2 * _m^2
+ local F = (I*A/r^2):to(_lm)
+ lu.assertTrue(F:isclose(83.333333333333*_lm, 1e-10))
+end
+
+function TestDefinition:testLux()
+ local I = 400 * _cd
+ local r = 2.4 * _m
+ local A = 1.2 * _m^2
+ local E = (I/r^2):to(_lx)
+ lu.assertTrue(E:isclose(69.444444444444*_lx, 1e-10))
+end
+
+function TestDefinition:testBecquerel()
+ local N = 12000
+ local t = 6 * _s
+ local A = (N/t):to(_Bq)
+
+ lu.assertTrue(A:isclose(2000*_Bq, 1e-10))
+end
+
+function TestDefinition:testGray()
+ local E = 12 * _mJ
+ local m = 60 * _kg
+ local D = (E/m):to(_Gy)
+ lu.assertTrue(D:isclose(0.0002*_Gy, 1e-10))
+end
+
+function TestDefinition:testSievert()
+ local E = 20 * _mJ
+ local m = 50 * _kg
+ local H = (E/m):to(_Sv)
+ lu.assertTrue(H:isclose(0.0004*_Sv, 1e-10))
+end
+
+function TestDefinition:testKatal()
+ local n = 20 * _mol
+ local t = 34 * _min
+ local a = (n/t):to(_kat)
+ lu.assertTrue(a:isclose(0.0098039215686275 * _kat, 1e-10))
+end
+
+function TestDefinition:testDegreeCelsius()
+ local theta = 0 * _degC
+ lu.assertTrue( (theta + _degC_0):to(_K) == 273.15 * _K )
+
+ theta = 5*_degC
+ lu.assertTrue( (theta + _degC_0):to(_K) == 278.15*_K )
+
+ theta = _degC*5
+ lu.assertTrue( (theta + _degC_0):to(_K) == 278.15*_K )
+
+ theta = 5*_degC + 3*_degC
+ lu.assertTrue( (theta + _degC_0):to(_K,true) == 281.15*_K )
+
+ local dT = (5 * _degC) / _cm
+ lu.assertTrue( dT == 5 * _K / _cm )
+
+
+ local T_1 = 0 * _degC
+ local T_2 = 1 * _K
+
+ local r = ((T_1 + _degC_0):to(_K)-T_2)/(T_1 + _degC_0)
+
+ r = r:to(_percent)
+ lu.assertTrue( r:isclose(99.63*_percent, 0.1) )
+
+ local c = 1000 * _J/(_kg*_degC)
+ local m = 1 * _g
+ local dT = 20 * _degC
+
+ local Q = ( c * m * dT ):to(_J)
+ lu.assertTrue( Q == 20 * _J )
+
+
+ theta_1 = 110 * _degC
+ T_1 = ( theta_1 + _degC_0 ):to(_K)
+ theta_1 = T_1:to(_degC) - _degC_0
+
+ lu.assertTrue( T_1 == 383.15 * _K )
+ lu.assertTrue( theta_1 == 110 * _degC )
+end
+
+
+-- PHYSICAL CONSTANTS
+function TestDefinition:testSpeedOfLight()
+ local lns = _c * 1 * _ns
+ local l = lns:to(_cm)
+ lu.assertTrue(l:isclose(29.9792458 * _cm, 1e-10))
+end
+
+function TestDefinition:testGravitationalConstant()
+ local m_1 = 20 * _kg
+ local m_2 = 40 * _kg
+ local r = 2 * _m
+ local F_G = (_Gc*m_1*m_2/r^2):to(_N)
+ lu.assertTrue(F_G:isclose(0.0000000133482 * _N, 1e-5))
+end
+
+function TestDefinition:testGravitationalConstant()
+ local m_1 = 20 * _kg
+ local m_2 = 40 * _kg
+ local r = 2 * _m
+ local F_G = (_Gc*m_1*m_2/r^2):to(_N)
+ lu.assertTrue(F_G:isclose(0.0000000133482 * _N, 1e-5))
+end
+
+function TestDefinition:testPlanckConstant()
+ local p = 4.619e-23 * _kg * _m / _s
+ local lambda = (_h_P/p):to(_m)
+ lu.assertTrue(lambda:isclose(0.01434 * _nm, 1e-3))
+end
+
+function TestDefinition:testReducedPlanckConstant()
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+ local hbar = _h_P/(2*Pi)
+ lu.assertTrue(hbar:isclose(_h_Pbar, 1e-3))
+end
+
+function TestDefinition:testElementaryCharge()
+ local N = (150 * _C / _e):to(_1)
+ lu.assertTrue(N:isclose(9.3622e20*_1, 1e-4))
+end
+
+function TestDefinition:testVacuumPermeability()
+ local I = 1.3 * _A
+ local r = 20 * _cm
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+
+ local B = (_u_0*I/(2*Pi*r)):to(_T)
+ lu.assertTrue(B:isclose(1.3*_uT, 1e-4))
+end
+
+function TestDefinition:testVacuumPermitivity()
+ local Q_1 = 3 * _nC
+ local Q_2 = 5.5 * _uC
+ local r = 20 * _cm
+
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+
+ local F_C = (1/(4*Pi*_e_0)) * Q_1 * Q_2 / r^2
+ F_C = F_C:to(_N)
+ lu.assertTrue(F_C:isclose(0.003707365112549*_N, 1e-4))
+end
+
+function TestDefinition:testAtomicMass()
+ local m = (12*_u):to(_yg)
+ lu.assertTrue(m:isclose(19.926468 * _yg, 1e-4))
+end
+
+function TestDefinition:testElectronMass()
+ local k = (_u/_m_e):to(_1)
+ lu.assertTrue(k:isclose(1822.888 * _1, 1e-4))
+end
+
+function TestDefinition:testProtonMass()
+ local k = (_m_p/_u):to(_1)
+ lu.assertTrue(k:isclose(1.007276 * _1, 1e-4))
+end
+
+function TestDefinition:testNeutronMass()
+ local k = (_m_n/_u):to(_1)
+ lu.assertTrue(k:isclose(1.008665 * _1, 1e-4))
+end
+
+function TestDefinition:testBohrMagneton()
+ local dE = (0.5*2*_u_B*1.2*_T):to(_eV)
+ lu.assertTrue(dE:isclose(6.95e-5 * _eV, 1e-3))
+end
+
+function TestDefinition:testNuclearMagneton()
+ local m_p = 0.5*_e*_h_Pbar/_u_N
+ lu.assertTrue(m_p:isclose(_m_p, 1e-10))
+end
+
+function TestDefinition:testProtonmagneticmoment()
+ lu.assertTrue((2.7928473508*_u_N):isclose(_u_p, 1e-6))
+end
+
+function TestDefinition:testNeutronMagneticMoment()
+ lu.assertTrue((-1.91304272*_u_N):isclose(_u_n, 1e-6))
+end
+
+function TestDefinition:testFineStructureConstant()
+ lu.assertTrue(_alpha:isclose(N(7.2973525664e-3,0.0000000017e-3)*_1, 1e-9))
+end
+
+function TestDefinition:testRydbergConstant()
+ lu.assertTrue(_Ry:isclose(N(1.097373139e7,0.000065e7)/_m, 1e-9))
+end
+
+function TestDefinition:testAvogadrosNumber()
+ lu.assertTrue(_N_A:isclose(N(6.02214076e23,0.00000001e23)/_mol, 1e-9))
+end
+
+function TestDefinition:testBoltzmannConstant()
+ lu.assertTrue(_k_B:isclose(N(1.380649e-23,0.000001e-23)*_J/_K, 1e-9))
+end
+
+function TestDefinition:testGasConstant()
+ lu.assertTrue(_R:isclose(N(8.3144598,0.0000048)*_J/(_K*_mol), 1e-9))
+end
+
+function TestDefinition:testStefanBoltzmannConstant()
+ lu.assertTrue(_sigma:isclose(N(5.6703744191844e-8,0.000013e-8)*_W/(_m^2*_K^4), 1e-6))
+end
+
+function TestDefinition:testStandardGravity()
+ lu.assertTrue(_g_0:isclose(9.80665*_m/_s^2, 1e-6))
+end
+
+function TestDefinition:testNominalSolarRadius()
+ lu.assertTrue(_R_S_nom:isclose(695700*_km, 1e-6))
+end
+
+
+
+-- NON-SI UNITS BUT ACCEPTED FOR USE WITH THE SI
+
+-- Length
+function TestDefinition:testAngstrom()
+ lu.assertTrue( (1e10*_angstrom):isclose(1*_m,1e-6) )
+end
+
+function TestDefinition:testFermi()
+ lu.assertTrue( (23444 * _fermi):isclose(0.23444*_angstrom,1e-6) )
+end
+
+-- Area
+function TestDefinition:testBarn()
+ lu.assertTrue( (38940*_am^2):isclose(0.3894*_mbarn,1e-6) )
+end
+
+function TestDefinition:testAre()
+ lu.assertTrue( (200*_m^2):isclose(2*_are,1e-6) )
+end
+
+function TestDefinition:testHectare()
+ lu.assertTrue( (56000*_m^2):isclose(5.6*_hectare,1e-6) )
+end
+
+-- Volume
+function TestDefinition:testLiter()
+ lu.assertTrue((1000*_L):isclose(1*_m^3, 1e-6))
+ lu.assertTrue((1*_mL):isclose(1*_cm^3, 1e-6))
+end
+
+function TestDefinition:testMetricTeaspoon()
+ lu.assertTrue((_L):isclose(200*_tsp, 1e-6))
+end
+
+function TestDefinition:testMetricTablespoon()
+ lu.assertTrue((3*_L):isclose(200*_Tbsp, 1e-6))
+end
+
+-- Time
+function TestDefinition:testSvedberg()
+ lu.assertTrue((0.56*_ns):isclose(5600*_svedberg, 1e-6))
+end
+function TestDefinition:testMinute()
+ lu.assertTrue((60*_s):isclose(_min, 1e-6))
+end
+
+function TestDefinition:testHour()
+ lu.assertTrue((60*_min):isclose(_h, 1e-6))
+end
+
+function TestDefinition:testDay()
+ lu.assertTrue((24*_h):isclose(_d, 1e-6))
+end
+
+function TestDefinition:testWeek()
+ lu.assertTrue((7*_d):isclose(_wk, 1e-6))
+end
+
+function TestDefinition:testYear()
+ lu.assertTrue((_a):isclose(365*_d+6*_h, 1e-6))
+end
+
+-- Angular
+
+function TestDefinition:testRadian()
+ lu.assertTrue((_rad):isclose(57.295779513082321*_deg, 1e-6))
+end
+
+function TestDefinition:testSteradian()
+ lu.assertTrue((_sr):isclose(_rad^2, 1e-6))
+end
+
+function TestDefinition:testGrad()
+ lu.assertTrue(((5*_deg):to()):isclose(0.087266462599716*_rad, 1e-6))
+end
+
+function TestDefinition:testArcMinute()
+ lu.assertTrue(((2*_deg):to(_arcmin)):isclose(120*_arcmin, 1e-6))
+end
+
+function TestDefinition:testArcSecond()
+ lu.assertTrue(((2*_deg):to(_arcmin)):isclose(120*60*_arcsec, 1e-6))
+end
+
+function TestDefinition:testGon()
+ lu.assertTrue((34.3*_deg):isclose(38.111111111111*_gon, 1e-6))
+end
+
+function TestDefinition:testTurn()
+ lu.assertTrue((720*_deg):isclose(2*_tr, 1e-6))
+end
+
+function TestDefinition:testSpat()
+ lu.assertTrue((_sp):isclose(12.566370614359173*_sr, 1e-6))
+end
+
+
+
+-- Astronomical
+function TestDefinition:testAstronomicalUnit()
+ lu.assertTrue((34.3*_au):isclose(5.131e9*_km, 1e-4))
+end
+
+function TestDefinition:testLightYear()
+ lu.assertTrue(_ly:isclose(63241*_au, 1e-4))
+end
+
+function TestDefinition:testParsec()
+ lu.assertTrue(_pc:isclose(3.262*_ly, 1e-3))
+end
+
+
+--force
+function TestDefinition:testKiloPond()
+ lu.assertTrue(_kp:isclose(9.8*_kg*_m/_s^2, 1e-2))
+end
+
+
+-- Pressure
+function TestDefinition:testBar()
+ lu.assertTrue((1013*_hPa):isclose(1.013*_bar, 1e-6))
+end
+
+function TestDefinition:testStandardAtmosphere()
+ lu.assertTrue((2*_atm):isclose(2.0265*_bar, 1e-6))
+end
+
+function TestDefinition:testTechnicalAtmosphere()
+ lu.assertTrue((2*_at):isclose(1.96*_bar, 1e-3))
+end
+
+function TestDefinition:testMillimeterOfMercury()
+ lu.assertTrue((120*_mmHg):isclose(15999*_Pa, 1e-4))
+end
+
+function TestDefinition:testTorr()
+ lu.assertTrue((120*_mmHg):isclose(120*_Torr, 1e-4))
+end
+
+
+-- Heat
+function TestDefinition:testCalorie()
+ lu.assertTrue((120*_cal):isclose(502.08*_J, 1e-6))
+end
+
+function TestDefinition:testCalorieInternational()
+ lu.assertTrue((120*_cal_IT):isclose(502.416*_J, 1e-6))
+end
+
+function TestDefinition:testGramOfTNT()
+ lu.assertTrue((5*_kg_TNT):isclose(2.092e7*_J, 1e-3))
+end
+
+function TestDefinition:testTonsOfTNT()
+ lu.assertTrue((2*_Mt_TNT):isclose(8.368e15*_J, 1e-3))
+end
+
+
+-- Electrical
+
+function TestDefinition:testVoltAmpere()
+ lu.assertTrue((23*_VA):isclose(23*_W, 1e-3))
+end
+
+function TestDefinition:testAmpereHour()
+ lu.assertTrue((850*_mAh):isclose(3060*_C, 1e-3))
+end
+
+
+-- Information units
+
+function TestDefinition:testBit()
+ lu.assertTrue( (100e12 * _bit):isclose(12500000000*_kB,1e-6) )
+ lu.assertTrue( (_KiB/_s):isclose(8192*_bit/_s,1e-6) )
+end
+
+function TestDefinition:testBitsPerSecond()
+ lu.assertTrue( (_MiB/_s):isclose(8388608*_bps,1e-6) )
+end
+
+function TestDefinition:testByte()
+ local d = 12500000000 * _kB
+ lu.assertTrue( d:isclose(12207031250*_KiB,1e-6) )
+ lu.assertTrue( d:isclose(100000000000*_kbit,1e-6) )
+end
+
+
+-- Others
+
+function TestDefinition:testPercent()
+ local k = (80 * _percent):to()
+ lu.assertEquals( k.value, 0.8)
+end
+
+function TestDefinition:testPermille()
+ local k = (80 * _permille):to()
+ lu.assertEquals( k.value, 0.08)
+end
+
+function TestDefinition:testTonne()
+ lu.assertTrue( (2.3*_t):isclose(2.3e6*_g,1e-6) )
+end
+
+function TestDefinition:testElectronVolt()
+ lu.assertTrue( (200 * _MeV):isclose(3.20435466e-11*_J,1e-6) )
+end
+
+function TestDefinition:testWattHour()
+ lu.assertTrue( (20 * _mWh):isclose(20*3.6*_J,1e-6) )
+end
+
+function TestDefinition:testMetricHorsePower()
+ lu.assertTrue( (200 * _PS):isclose(147100 * _W,1e-4) )
+end
+
+function TestDefinition:testCurie()
+ lu.assertTrue( (3e9 * _Bq):isclose(0.081081081 * _Ci,1e-4) )
+end
+
+function TestDefinition:testRad()
+ lu.assertTrue( (100 * _Rad):isclose(1 * _Gy,1e-4) )
+end
+
+function TestDefinition:testRad()
+ lu.assertTrue( (100 * _rem):isclose(1 * _Sv,1e-4) )
+end
+
+function TestDefinition:testPoiseuille()
+ local r = 2 * _mm
+ local l = 10 * _cm
+ local p = 400 * _Pa
+ local eta = 0.0027 * _Pl
+
+ local Pi = 3.1415926535897932384626433832795028841971693993751
+
+ local Q = Pi*p*r^4/(8*eta*l)
+ lu.assertTrue( Q:isclose(9.3084226773031 * _mL/_s,1e-4) )
+end
+
+
+
+
+
+-- IMPERIAL UNITS
+
+-- Length
+function TestDefinition:testInch()
+ lu.assertTrue( (2.2*_m):isclose(86.6 * _in,1e-3) )
+end
+
+function TestDefinition:testThou()
+ lu.assertTrue( (4.3*_th):isclose(0.0043 * _in,1e-3) )
+end
+
+function TestDefinition:testPica()
+ lu.assertTrue( (5*_pica):isclose(0.8333 * _in,1e-3) )
+end
+
+function TestDefinition:testPoint()
+ lu.assertTrue( (72*_pt):isclose(25.4 * _mm,1e-3) )
+end
+
+function TestDefinition:testHand()
+ lu.assertTrue( (3*_hh):isclose(12 * _in,1e-3) )
+end
+
+function TestDefinition:testFoot()
+ lu.assertTrue( (2*_ft):isclose(60.96 * _cm,1e-3) )
+end
+
+function TestDefinition:testYard()
+ lu.assertTrue( (1.5*_yd):isclose(1.3716 * _m,1e-3) )
+end
+
+function TestDefinition:testRod()
+ lu.assertTrue( (22*_rd):isclose(1.106 * _hm,1e-3) )
+end
+
+function TestDefinition:testChain()
+ lu.assertTrue( (0.5*_ch):isclose(0.0100584 * _km,1e-3) )
+end
+
+function TestDefinition:testFurlong()
+ lu.assertTrue( (69*_fur):isclose(13.88 * _km,1e-3) )
+end
+
+function TestDefinition:testMile()
+ lu.assertTrue( (2*_mi):isclose(3.219 * _km,1e-3) )
+end
+
+function TestDefinition:testLeague()
+ lu.assertTrue( (2.2*_lea):isclose(6.6 * _mi,1e-3) )
+end
+
+
+-- International Nautical Units
+function TestDefinition:testNauticalMile()
+ lu.assertTrue( (200*_nmi):isclose(370.4 * _km,1e-4) )
+end
+
+function TestDefinition:testNauticalLeague()
+ lu.assertTrue( (200*_nlea):isclose(1111 * _km,1e-3) )
+end
+
+function TestDefinition:testCables()
+ lu.assertTrue( (23*_cbl):isclose(4262 * _m,1e-3) )
+end
+
+function TestDefinition:testFathom()
+ lu.assertTrue( (12*_ftm):isclose(21.95 * _m,1e-3) )
+end
+
+function TestDefinition:testKnot()
+ lu.assertTrue( (24*_kn):isclose(44.45 * _km/_h,1e-3) )
+end
+
+
+-- Area
+
+function TestDefinition:testAcre()
+ lu.assertTrue( (300*_ac):isclose(1.214e6 * _m^2,1e-3) )
+end
+
+
+
+
+-- Volume
+
+function TestDefinition:testGallon()
+ lu.assertTrue( (23*_L):isclose(5.059 * _gal,1e-3) )
+end
+
+function TestDefinition:testQuart()
+ lu.assertTrue( (3*_hL):isclose(264 * _qt,1e-3) )
+end
+
+function TestDefinition:testPint()
+ lu.assertTrue( (2*_daL):isclose(35.2 * _pint,1e-3) )
+end
+
+function TestDefinition:testCup()
+ lu.assertTrue( (5.5*_cL):isclose(0.194 * _cup,1e-2) )
+end
+
+function TestDefinition:testGill()
+ lu.assertTrue( (3.4*_cL):isclose(0.239 * _gi,1e-2) )
+end
+
+function TestDefinition:testFluidOunce()
+ lu.assertTrue( (23*_mL):isclose(0.8095 * _fl_oz,1e-2) )
+end
+
+function TestDefinition:testFluidDram()
+ lu.assertTrue( (800*_uL):isclose(0.2252 * _fl_dr,1e-2) )
+end
+
+
+
+
+-- Mass (Avoirdupois)
+
+function TestDefinition:testGrain()
+ lu.assertTrue( (34*_g):isclose(524.7 * _gr,1e-3) )
+end
+
+function TestDefinition:testPound()
+ lu.assertTrue( (0.5*_kg):isclose(1.102311310925 * _lb,1e-6) )
+end
+
+function TestDefinition:testOunce()
+ lu.assertTrue( (0.3*_kg):isclose(10.582188584879999 * _oz,1e-6) )
+end
+
+function TestDefinition:testDram()
+ lu.assertTrue( (45*_g):isclose(25.397252603684997 * _dr,1e-6) )
+end
+
+function TestDefinition:testStone()
+ lu.assertTrue( (34*_kg):isclose(5.354083510212 * _st,1e-6) )
+end
+
+function TestDefinition:testQuarter()
+ lu.assertTrue( (300*_kg):isclose(23.62095666267 * _qtr,1e-6) )
+end
+
+function TestDefinition:testHundredWeight()
+ lu.assertTrue( (0.5*_t):isclose(9.8420652761 * _cwt,1e-6) )
+end
+
+function TestDefinition:testLongTon()
+ lu.assertTrue( (45*_t):isclose(44.289293742495* _ton,1e-6) )
+end
+
+
+-- Mass (Troy)
+
+function TestDefinition:testTroyPound()
+ lu.assertTrue( (0.6*_kg):isclose(1.607537328432 * _lb_t,1e-6) )
+end
+
+function TestDefinition:testTroyOunce()
+ lu.assertTrue( (0.2*_kg):isclose(6.43014931372 * _oz_t,1e-6) )
+end
+
+function TestDefinition:testPennyWeight()
+ lu.assertTrue( (78*_g):isclose(50.155164647094004 * _dwt,1e-6) )
+end
+
+
+-- Mass (Others)
+
+function TestDefinition:testFirkin()
+ lu.assertTrue( (3*_kg):isclose(0.11810478331319998 * _fir,1e-6) )
+end
+
+
+-- Time
+function TestDefinition:testSennight()
+ lu.assertTrue( (3*_sen):isclose(21 * _d,1e-6) )
+end
+
+function TestDefinition:testFortnight()
+ lu.assertTrue( (2*_ftn):isclose(28 * _d,1e-6) )
+end
+
+-- Temperature
+function TestDefinition:testFahrenheit()
+ local theta = -457.87*_degF
+ lu.assertTrue( (theta + _degF_0):to(_K):isclose(1*_K, 0.001) )
+
+ -- the isclose function treats temperature units as differences
+ local theta_1 = 32 * _degF
+ local theta_2 = 0 * _degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+
+ local theta_1 = 69.8*_degF
+ local theta_2 = 21*_degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+
+ local theta_1 = 98.6*_degF
+ local theta_2 = 37*_degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+
+
+ local theta_1 = 212.0*_degF
+ local theta_2 = 100*_degC
+ lu.assertTrue( ((theta_1 + _degF_0):to(_K)):isclose((theta_2 + _degC_0):to(_K,true), 0.001) )
+end
+
+
+-- Others
+
+function TestDefinition:testPoundForce()
+ lu.assertTrue( (12*_lbf):isclose(53.3787 * _N,1e-4) )
+end
+
+function TestDefinition:testPoundal()
+ lu.assertTrue( (99*_pdl):isclose(13.6872 * _N,1e-4) )
+end
+
+function TestDefinition:testSlug()
+ lu.assertTrue( (0.5*_slug):isclose(7296.95 * _g,1e-4) )
+end
+
+function TestDefinition:testPsi()
+ lu.assertTrue( (29.0075*_psi):isclose(2 * _bar,1e-4) )
+end
+
+function TestDefinition:testThermochemicalBritishThermalUnit()
+ lu.assertTrue( (_BTU):isclose( _kcal*_lb*_degF/(_kg*_K) ,1e-8) )
+end
+
+function TestDefinition:testInternationalBritishThermalUnit()
+ lu.assertTrue( (_BTU_it):isclose( _kcal_IT*_lb*_degF/(_kg*_K) ,1e-8) )
+end
+
+function TestDefinition:testHorsepower()
+ lu.assertTrue( (700*_hp):isclose( 521990*_W ,1e-4) )
+end
+
+
+-- US CUSTOMARY UNITS
+-- ******************
+
+-- Length
+function TestDefinition:testUSinch()
+ lu.assertTrue( (12333*_in_US):isclose( 12333.024666*_in ,1e-6) )
+end
+
+function TestDefinition:testUShand()
+ lu.assertTrue( (3*_hh_US):isclose( 12.00002400005*_in ,1e-9) )
+end
+
+function TestDefinition:testUSFoot()
+ lu.assertTrue( (12*_ft_US):isclose( 144.0002880006*_in ,1e-9) )
+end
+
+function TestDefinition:testUSLink()
+ lu.assertTrue( (56*_li_US):isclose( 11.265430530872*_m ,1e-7) )
+end
+
+function TestDefinition:testUSYard()
+ lu.assertTrue( (3937*_yd_US):isclose( 3600*_m ,1e-9) )
+end
+
+function TestDefinition:testUSRod()
+ lu.assertTrue( (74*_rd_US):isclose( 372.16154432*_m ,1e-10) )
+end
+
+function TestDefinition:testUSChain()
+ lu.assertTrue( (0.5*_ch_US):isclose( 100.58420117*_dm ,1e-10) )
+end
+
+function TestDefinition:testUSFurlong()
+ lu.assertTrue( (56*_fur_US):isclose( 11.265430531*_km ,1e-10) )
+end
+
+function TestDefinition:testUSMile()
+ lu.assertTrue( (2.4*_mi_US):isclose( 3.8624333249*_km ,1e-10) )
+end
+
+function TestDefinition:testUSLeague()
+ lu.assertTrue( (5.5*_lea_US):isclose( 26.554229108*_km ,1e-10) )
+end
+
+function TestDefinition:testUSFathom()
+ lu.assertTrue( (5.5*_ftm_US):isclose( 100.58420117*_dm ,1e-10) )
+end
+
+function TestDefinition:testUSCable()
+ lu.assertTrue( (45*_cbl_US):isclose( 987.55*_dam ,1e-5) )
+end
+
+-- Area
+function TestDefinition:testUSAcre()
+ lu.assertTrue( (45*_ac_US):isclose( 182109.26744*_m^2 ,1e-9) )
+end
+
+-- Volume
+function TestDefinition:testUSGallon()
+ lu.assertTrue( (2*_gal_US):isclose( 1.665348369257978 * _gal ,1e-9) )
+ lu.assertTrue( (2*_gal_US):isclose( 7.570823568*_L ,1e-9) )
+end
+
+function TestDefinition:testUSQuart()
+ lu.assertTrue( (78*_qt_US):isclose( 738.15529788 * _dL ,1e-9) )
+end
+
+function TestDefinition:testUSPint()
+ lu.assertTrue( (46*_pint_US):isclose( 2.1766117758 * _daL ,1e-9) )
+end
+
+function TestDefinition:testUSCup()
+ lu.assertTrue( (2*_cup_US):isclose( 31.5450982 * _Tbsp ,1e-9) )
+end
+
+function TestDefinition:testUSGill()
+ lu.assertTrue( (45*_gi_US):isclose( 5.3232353437499995 * _L ,1e-7) )
+end
+
+function TestDefinition:testUSFluidOunce()
+ lu.assertTrue( (22*_fl_oz_US):isclose( 650.617653125 * _mL ,1e-7) )
+end
+
+function TestDefinition:testUSTablespoon()
+ lu.assertTrue( (10*_Tbsp_US):isclose( 147.867648438 * _mL ,1e-7) )
+end
+
+function TestDefinition:testUSTeaspoon()
+ lu.assertTrue( (0.5*_tsp_US):isclose( 2464.46080729 * _uL ,1e-7) )
+end
+
+function TestDefinition:testUSFluidDram()
+ lu.assertTrue( (1*_fl_dr_US):isclose( 3.6966911953125 * _mL ,1e-7) )
+end
+
+-- Mass
+
+function TestDefinition:testUSQuarter()
+ lu.assertTrue( (23*_qtr_US):isclose( 260.81561275 * _kg ,1e-7) )
+end
+
+function TestDefinition:testUSHunderdWeight()
+ lu.assertTrue( (1.4*_cwt_US):isclose( 63.5029318 * _kg ,1e-7) )
+end
+
+function TestDefinition:testUSTon()
+ lu.assertTrue( (2*_ton_US):isclose( 1.81436948 * _t ,1e-7) )
+end
+
+return TestDefinition
+
+
+
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua
new file mode 100644
index 00000000000..44d2840322e
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testDimension.lua
@@ -0,0 +1,314 @@
+--[[
+This file contains the unit tests for the physical.Dimension 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 = "../?.lua;" .. package.path
+local physical = require("physical")
+
+local D = physical.Dimension
+
+local L = D("L")
+local M = D("M")
+local T = D("T")
+local I = D("I")
+local K = D("K")
+local N = D("N")
+local J = D("J")
+
+
+TestDimension = {} --class
+
+
+-- Dimension.new(o=nil)
+function TestDimension:testEmptyConstructor()
+ local d = D()
+ lu.assertTrue( d:iszero() )
+end
+
+function TestDimension:testConstructorByString()
+ local d = D("Dimensionless")
+ lu.assertTrue( d:iszero() )
+end
+
+function TestDimension:testCopyConstructor()
+ local d1, d2
+
+ d1 = D("Energy")
+ lu.assertEquals( d1, M*L^2/T^2 )
+
+ d2 = D(d1)
+ lu.assertEquals( d2, M*L^2/T^2 )
+end
+
+
+
+
+function TestDimension:testDefine()
+ local i1 = D("Force")^4
+ local i2 = D.define("Insanity",i1)
+ local i3 = D("Insanity")
+ lu.assertEquals( i1, L^4 * M^4 * T^-8)
+ lu.assertEquals( i2, L^4 * M^4 * T^-8)
+ lu.assertEquals( i3, L^4 * M^4 * T^-8)
+end
+
+function TestDimension:testToString()
+ local d = D("Force")
+ lu.assertEquals( tostring(d), "[Force]" )
+
+ local d = D(L^-1 * T * I^2 * K^3 * N^4 * J^5)
+ lu.assertEquals( tostring(d), "[L]^-1 [T] [I]^2 [K]^3 [N]^4 [J]^5" )
+end
+
+function TestDimension:testEqual()
+ local d1 = D("Energy")
+ local d2 = D("Force")
+ local d3 = D("Torque")
+
+ lu.assertEquals( d1==d2, false)
+ lu.assertEquals( d1==d3, true)
+end
+
+function TestDimension:testMultiply()
+ local d1 = D("Force")
+ local d2 = D("Energy")
+
+ local d3 = d1 * d2
+ local d4 = d2 * d1
+
+ lu.assertEquals( d3, L^3 * M^2 * T^-4)
+ lu.assertEquals( d3, d4)
+end
+
+function TestDimension:testDivide()
+ local d1 = D("Force")
+ local d2 = D("Energy")
+
+ lu.assertEquals( d1, {1,1,-2,0,0,0,0,0,0})
+ lu.assertEquals( d2, {2,1,-2,0,0,0,0,0,0})
+
+ local d3 = d1 / d2
+ local d4 = d2 / d1
+
+ lu.assertEquals( d3, {-1,0,0,0,0,0,0,0,0})
+ lu.assertNotEquals( d3, d4)
+end
+
+function TestDimension:testPow()
+ local d = D("Length")^3
+ lu.assertEquals( d, L^3)
+end
+
+function TestDimension:isequal()
+ local d = D("Length")^3
+ lu.assertTrue( d == L^3)
+
+ local d = D("Force")
+ lu.assertTrue( d == L * M / T^2)
+end
+
+
+-- Test Dimension Definitions
+
+function TestDimension:testLength()
+ lu.assertEquals(D("Length"), L)
+ lu.assertEquals(D("L"), L)
+end
+
+function TestDimension:testMass()
+ lu.assertEquals(D("Mass"), M)
+ lu.assertEquals(D("M"), M)
+end
+
+function TestDimension:testTime()
+ lu.assertEquals(D("Time"), T)
+ lu.assertEquals(D("T"), T)
+end
+
+function TestDimension:testCreateByNameForce()
+ lu.assertTrue( D("Force") == M*L/T^2 )
+end
+
+function TestDimension:testArea()
+ lu.assertEquals(D("Area"), L^2)
+end
+
+function TestDimension:testVolume()
+ lu.assertEquals(D("Volume"), L^3)
+end
+
+function TestDimension:testFrequency()
+ lu.assertEquals(D("Frequency"), T^-1)
+end
+
+function TestDimension:testFrequency()
+ lu.assertEquals(D("Frequency"), T^-1)
+end
+
+function TestDimension:testDensity()
+ lu.assertEquals(D("Density"), M / D("Volume"))
+end
+
+function TestDimension:testVelocity()
+ lu.assertEquals(D("Velocity"), L / T)
+end
+
+function TestDimension:testAcceleration()
+ lu.assertEquals(D("Acceleration"), D("Velocity") / T)
+end
+
+function TestDimension:testForce()
+ lu.assertEquals(D("Force"), M * D("Acceleration"))
+end
+
+function TestDimension:testEnergy()
+ lu.assertEquals(D("Energy"), D("Force") * L)
+end
+
+function TestDimension:testPower()
+ lu.assertEquals(D("Power"), D("Energy") / T)
+end
+
+function TestDimension:testPower()
+ lu.assertEquals(D("Power"), D("Energy") / T)
+end
+
+function TestDimension:testTorque()
+ lu.assertEquals(D("Energy"), D("Torque"))
+end
+
+function TestDimension:testTorque()
+ lu.assertEquals(D("Pressure"), D("Force") / D("Area"))
+end
+
+function TestDimension:testImpulse()
+ lu.assertEquals(D("Impulse"), M * D("Velocity"))
+end
+
+function TestDimension:testSpecificAbsorbedDose()
+ lu.assertEquals(D("Absorbed Dose"), D("Energy") / M)
+end
+
+function TestDimension:testHeatCapacity()
+ lu.assertEquals(D("Heat Capacity"), D("Energy") / K)
+end
+
+function TestDimension:testSpecificHeatCapacity()
+ lu.assertEquals(D("Specific Heat Capacity"), D("Energy") / (M * K) )
+end
+
+function TestDimension:testAngularMomentum()
+ lu.assertEquals(D("Angular Momentum"), L * D("Impulse") )
+end
+
+function TestDimension:testAngularMomentofInertia()
+ lu.assertEquals(D("Moment of Inertia"), D("Torque") * T^2 )
+end
+
+function TestDimension:testEntropy()
+ lu.assertEquals(D("Entropy"), D("Energy") / K )
+end
+
+function TestDimension:testThermalConductivity()
+ lu.assertEquals(D("Thermal Conductivity"), D("Power") / (L*K) )
+end
+
+function TestDimension:testElectricCharge()
+ lu.assertEquals(D("Electric Charge"), D("Electric Current") * T )
+end
+
+function TestDimension:testElectricPermittivity()
+ lu.assertEquals(D("Electric Permittivity"), D("Electric Charge")^2 / ( D("Force") * D("Area") ) )
+end
+
+function TestDimension:testElectricFieldStrength()
+ lu.assertEquals(D("Electric Field Strength"), D("Force") / D("Electric Charge") )
+end
+
+function TestDimension:testElectricPotential()
+ lu.assertEquals(D("Electric Potential"), D("Energy") / D("Electric Charge") )
+end
+
+function TestDimension:testElectricResistance()
+ lu.assertEquals(D("Electric Resistance"), D("Electric Potential") / D("Electric Current") )
+end
+
+function TestDimension:testElectricConductance()
+ lu.assertEquals(D("Electric Conductance"), 1 / D("Electric Resistance") )
+end
+
+function TestDimension:testElectricCapacitance()
+ lu.assertEquals(D("Electric Capacitance"), D("Electric Charge") / D("Electric Potential") )
+end
+
+function TestDimension:testElectricInductance()
+ lu.assertEquals(D("Inductance"), D("Electric Potential") * T / D("Electric Current") )
+end
+
+function TestDimension:testMagneticFluxDensity()
+ lu.assertEquals(D("Magnetic Flux Density"), D("Force") / (D("Electric Charge") * D("Velocity")) )
+end
+
+function TestDimension:testMagneticFlux()
+ lu.assertEquals(D("Magnetic Flux"), D("Magnetic Flux Density") * D("Area") )
+end
+
+function TestDimension:testMagneticPermeability()
+ lu.assertEquals(D("Magnetic Permeability"), D("Magnetic Flux Density") * L / D("Electric Current") )
+end
+
+function TestDimension:testMagneticFieldStrength()
+ lu.assertEquals(D("Magnetic Field Strength"), D("Magnetic Flux Density") / D("Magnetic Permeability") )
+end
+
+function TestDimension:testIntensity()
+ lu.assertEquals(D("Intensity"), D("Power") / D("Area") )
+end
+
+function TestDimension:testReactionRate()
+ lu.assertEquals(D("Reaction Rate"), N / (T * D("Volume")) )
+end
+
+function TestDimension:testCatalyticActivity()
+ lu.assertEquals(D("Catalytic Activity"), N / T )
+end
+
+function TestDimension:testChemicalPotential()
+ lu.assertEquals(D("Chemical Potential"), D("Energy") / N )
+end
+
+function TestDimension:testMolarConcentration()
+ lu.assertEquals(D("Molar Concentration"), N / D("Volume") )
+end
+
+function TestDimension:testMolarHeatCapacity()
+ lu.assertEquals(D("Molar Heat Capacity"), D("Energy") / (K * N) )
+end
+
+function TestDimension:testIlluminance()
+ lu.assertEquals(D("Illuminance"), J / D("Area") )
+end
+
+return TestDimension
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua
new file mode 100644
index 00000000000..1a5ec0791d3
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testNumber.lua
@@ -0,0 +1,515 @@
+--[[
+This file contains the unit tests for the physical.Number 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 = "../?.lua;" .. package.path
+local physical = require("physical")
+
+local N = physical.Number
+
+
+function defaultformat()
+ N.seperateUncertainty = true
+ N.omitUncertainty = false
+ N.format = N.DECIMAL
+end
+
+
+TestNumber = {}
+
+
+
+-- test the default constructor
+function TestNumber:testNewDefault()
+ local n = N()
+ lu.assertEquals( n._x, 0 )
+ lu.assertEquals( n._dx, 0 )
+end
+
+-- test if the constructor works with one or two numbers as arguments
+function TestNumber:testNewByNumber()
+ local n = N(122,0.022)
+ lu.assertEquals( n._x, 122 )
+ lu.assertEquals( n._dx, 0.022 )
+
+ local n = N(122,0)
+ lu.assertEquals( n._x, 122 )
+ lu.assertEquals( n._dx, 0 )
+
+ local n = N(0, 0.01)
+ lu.assertEquals( n._x, 0 )
+ lu.assertEquals( n._dx, 0.01 )
+end
+
+-- test the copy constructor
+function TestNumber:testNewCopyConstructor()
+ local n = N(122,0.022)
+ local m = N(n)
+ lu.assertEquals( m._x, 122 )
+ lu.assertEquals( m._dx, 0.022 )
+end
+
+-- test construction by string in plus minus format
+function TestNumber:testNewByStringPlusMinusNotation()
+ local n = N("2.2 +/- 0.3")
+ lu.assertEquals( n._x, 2.2 )
+ lu.assertEquals( n._dx, 0.3 )
+
+ local n = N("2e-3 +/- 0.0003")
+ lu.assertEquals( n._x, 0.002 )
+ lu.assertEquals( n._dx, 0.0003 )
+
+ local n = N("67 +/- 2e-2")
+ lu.assertEquals( n._x, 67 )
+ lu.assertEquals( n._dx, 0.02 )
+
+ local n = N("15.2e-3 +/- 10.4e-6")
+ lu.assertEquals( n._x, 0.0152 )
+ lu.assertEquals( n._dx, 0.0000104 )
+end
+
+-- test construction by string in compact format
+function TestNumber:testNewByString()
+ local n = N("2.32(5)")
+ lu.assertEquals( n._x, 2.32 )
+ lu.assertEquals( n._dx, 0.05 )
+
+ local n = N("2.32(51)")
+ lu.assertEquals( n._x, 2.32 )
+ lu.assertEquals( n._dx, 0.51 )
+
+ local n = N("4.566(5)e2")
+ lu.assertAlmostEquals( n._x, 456.6, 0.01 )
+ lu.assertEquals( n._dx, 0.5 )
+
+ local n = N("2.30(55)e3")
+ lu.assertAlmostEquals( n._x, 2300, 0.1)
+ lu.assertEquals( n._dx, 550 )
+
+ local n = N("255.30(55)e6")
+ lu.assertAlmostEquals( n._x, 255300000, 0.1)
+ lu.assertEquals( n._dx, 550000 )
+end
+
+-- test construction by string in compact format
+function TestNumber:testNewByStringNumber()
+ local n = N("1")
+ lu.assertEquals( n._x, 1 )
+ lu.assertEquals( n._dx, 0.5 )
+
+ local n = N("2.3")
+ lu.assertEquals( n._x, 2.3 )
+ lu.assertEquals( n._dx, 0.05 )
+
+ local n = N("2.3e-2")
+ lu.assertEquals( n._x, 2.3e-2 )
+ lu.assertEquals( n._dx, 0.05e-2 )
+
+ local n = N("123")
+ lu.assertEquals( n._x, 123 )
+ lu.assertEquals( n._dx, 0.5 )
+
+ local n = N("123.556")
+ lu.assertEquals( n._x, 123.556 )
+ lu.assertEquals( n._dx, 0.0005 )
+end
+
+
+
+-- test string conversion to plus-minus format
+function TestNumber:testToPlusMinusNotation()
+
+ N.seperateUncertainty = true
+
+ N.format = N.DECIMAL
+ lu.assertEquals( tostring(N(1,0.5)), "(1.0 +/- 0.5)" )
+ lu.assertEquals( tostring(N(7,1)), "(7.0 +/- 1.0)" )
+ lu.assertEquals( tostring(N(0.005,0.0001)), "(0.00500 +/- 0.00010)" )
+ lu.assertEquals( tostring(N(500,2)), "(500 +/- 2)" )
+ lu.assertEquals( tostring(N(1023453838.0039,0.06)), "(1023453838.00 +/- 0.06)" )
+ lu.assertEquals( tostring(N(10234.0039, 0.00000000012)), "(10234.00390000000 +/- 0.00000000012)" )
+ lu.assertEquals( tostring(N(10234.0039e12, 0.00000000012e12)), "(10234003900000000 +/- 120)" )
+ lu.assertEquals( tostring(N(0, 0)), "0" )
+ lu.assertEquals( tostring(N(0, 0.01)), "(0.000 +/- 0.010)" )
+
+ N.format = N.SCIENTIFIC
+ lu.assertEquals( tostring(N(7,1)), "(7 +/- 1)" )
+ lu.assertEquals( tostring(N(80,22)), "(8 +/- 2)e1" )
+ lu.assertEquals( tostring(N(40,100)), "(4 +/- 10)e1" )
+ lu.assertEquals( tostring(N(0.005,0.0001)), "(5.00 +/- 0.10)e-3" )
+ lu.assertEquals( tostring(N(500,2)), "(5.00 +/- 0.02)e2" )
+
+end
+
+-- test string conversion to plus-minus format
+function TestNumber:testToParenthesesNotation()
+
+ N.seperateUncertainty = false
+ N.omitUncertainty = false
+
+ N.format = N.DECIMAL
+ lu.assertEquals( tostring(N(1,0.5)), "1.0(5)" )
+ lu.assertEquals( tostring(N(1.25,0.5)), "1.3(5)" )
+ lu.assertEquals( tostring(N(100,13)), "100(13)" )
+ lu.assertEquals( tostring(N(26076,45)), "26076(45)" )
+ lu.assertEquals( tostring(N(26076,0.01)), "26076.000(10)" )
+ lu.assertEquals( tostring(N(1234.56789, 0.00011)), "1234.56789(11)" )
+ lu.assertEquals( tostring(N(15200000, 23000)), "15200000(23000)" )
+ lu.assertEquals( tostring(N(5, 0.01)), "5.000(10)" )
+ lu.assertEquals( tostring(N(100, 5)), "100(5)" )
+
+ N.format = N.SCIENTIFIC
+ lu.assertEquals( tostring(N(15.2e-6, 2.3e-8)), "1.520(2)e-5" )
+ lu.assertEquals( tostring(N(15.2e-6, 1.2e-8)), "1.5200(12)e-5" )
+ lu.assertEquals( tostring(N(5, 0.01)), "5.000(10)" )
+ lu.assertEquals( tostring(N(15.2e-6, 0)), "1.52e-05" )
+ lu.assertEquals( tostring(N(16.25e-6, 5e-7)), "1.62(5)e-5" )
+
+ lu.assertEquals( tostring(N(1.9884e30, 2e26)/N(1.191e8,1.4e6)), "1.67(2)e22" )
+end
+
+
+
+-- test string conversion to compact format
+function TestNumber:testToOmitUncertaintyNotation()
+ N.seperateUncertainty = false
+ N.omitUncertainty = true
+
+ N.format = N.DECIMAL
+ lu.assertEquals( tostring(N(1, 0.5)), "1" )
+ lu.assertEquals( tostring(N(1.2, 0.05)), "1.2" )
+ lu.assertEquals( tostring(N(1.2, 0.005)), "1.20" )
+ lu.assertEquals( tostring(N(1.25, 0.05)), "1.3" )
+
+
+
+ N.format = N.SCIENTIFIC
+ lu.assertEquals( tostring(N(1.2e27, 0.05e27)), "1.2e27" )
+
+end
+
+
+-- test string conversion from compact to plus-minus format
+function TestNumber:testParseParenthesesNotation()
+ defaultformat()
+
+ local n = N("2.32(5)")
+ lu.assertEquals( tostring(n), "(2.32 +/- 0.05)" )
+
+ local n = N("2.32(51)")
+ lu.assertEquals( tostring(n), "(2.3 +/- 0.5)" )
+
+ local n = N("4.566(5)e2")
+ lu.assertEquals( tostring(n), "(456.6 +/- 0.5)" )
+
+ local n = N("2.30(55)e3")
+ lu.assertEquals( tostring(n), "(2300 +/- 550)" )
+end
+
+-- test string conversion from and to plus-minus format
+function TestNumber:testParsePlusMinusNotation()
+ defaultformat()
+
+ local n = N("2.2 +/- 0.3")
+ lu.assertEquals( tostring(n), "(2.2 +/- 0.3)" )
+
+ local n = N("2.2+/-0.3")
+ lu.assertEquals( tostring(n), "(2.2 +/- 0.3)" )
+
+ local n = N("2e-3 +/- 0.0003")
+ lu.assertEquals( tostring(n), "(0.0020 +/- 0.0003)" )
+
+ local n = N("67 +/- 2e-2")
+ lu.assertEquals( tostring(n), "(67.00 +/- 0.02)" )
+end
+
+-- test the frexp function
+function TestNumber:testfrexp()
+ local m,e = N._frexp(123)
+
+ lu.assertEquals( m, 1.23 )
+ lu.assertEquals( e, 2 )
+end
+
+
+
+
+
+
+-- test widget
+-- http://www.wolframalpha.com/widgets/gallery/view.jsp?id=ff2d5fc3ab1932df3c00308bead36006
+
+-- article on
+-- https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3387884/
+
+-- test addition of numbers and other physical numbers
+function TestNumber:testAdd()
+ defaultformat()
+
+ local n1 = N(5, 0.5)
+ local n2 = N(10, 0.2)
+
+ lu.assertEquals( tostring(2+n2), "(12.0 +/- 0.2)" )
+ lu.assertEquals( tostring(n1+3), "(8.0 +/- 0.5)" )
+ lu.assertEquals( tostring(n1+n2), "(15.0 +/- 0.5)" )
+end
+
+-- test subtraction of numbers and other physical numbers
+function TestNumber:testSubtract()
+ defaultformat()
+
+ local n1 = N(5, 0.5)
+ local n2 = N(10, 0.2)
+
+ lu.assertEquals( tostring(2-n2), "(-8.0 +/- 0.2)" )
+ lu.assertEquals( tostring(n1-3), "(2.0 +/- 0.5)" )
+ lu.assertEquals( tostring(n1-n2), "(-5.0 +/- 0.5)" )
+end
+
+-- test mixed operations
+function TestNumber:testMixed()
+ defaultformat()
+
+ local d = N(5, 0.5)
+
+ l = d - d
+ m = l / d
+
+ lu.assertEquals( tostring(m), "0.0" )
+end
+
+-- test unary minus operation
+function TestNumber:testUnaryMinus()
+ defaultformat()
+
+ local n = N(5.68, 0.2)
+
+ lu.assertEquals( tostring(-n), "(-5.7 +/- 0.2)" )
+ lu.assertEquals( tostring(-(-n)), "(5.7 +/- 0.2)" )
+end
+
+-- test multiplication with numbers and other physical numbers
+function TestNumber:testMultiplication()
+ defaultformat()
+
+ local n1 = N(4.52, 0.02)
+ local n2 = N(2.0, 0.2)
+
+ lu.assertEquals( tostring(4*n2), "(8.0 +/- 0.8)" )
+ lu.assertEquals( tostring(n1*5), "(22.60 +/- 0.10)" )
+ lu.assertEquals( tostring(n1*n2), "(9.0 +/- 0.9)" )
+end
+
+
+-- test division with numbers and other physical numbers
+function TestNumber:testDivision()
+ defaultformat()
+
+ local n1 = N(2.0, 0.2)
+ local n2 = N(3.0, 0.6)
+
+ lu.assertEquals( tostring(5/n2), "(1.7 +/- 0.3)" )
+ lu.assertEquals( tostring(n1/6), "(0.33 +/- 0.03)" )
+ lu.assertEquals( tostring(n1/n2), "(0.67 +/- 0.15)" )
+end
+
+
+-- uncertainty calculator physics
+-- http://ollyfg.github.io/Uncertainty-Calculator/
+function TestNumber:testPower()
+ defaultformat()
+
+ local n1 = N(3.0, 0.2)
+ local n2 = N(2.5, 0.01)
+
+ lu.assertEquals( tostring(n1^2), "(9.0 +/- 1.2)" )
+ lu.assertEquals( tostring(3^n2), "(15.59 +/- 0.17)" )
+ lu.assertEquals( tostring(n1^n2), "(16 +/- 3)" )
+end
+
+-- test the absolute value function
+function TestNumber:testAbs()
+ defaultformat()
+
+ local n = N(-5.0, 0.2)
+ lu.assertEquals( tostring(n:abs()), "(5.0 +/- 0.2)" )
+
+ local n = N(100, 50)
+ lu.assertEquals( tostring(n:abs()), "(100 +/- 50)" )
+end
+
+-- test the logarithm function
+function TestNumber:testLog()
+ defaultformat()
+
+ local n = N(5.0, 0.2)
+ lu.assertEquals( tostring(n:log()), "(1.61 +/- 0.04)" )
+
+ local n = N(0.03, 0.003)
+ lu.assertEquals( tostring(n:log()), "(-3.51 +/- 0.10)" )
+
+ local n = N(0.03, 0.003)
+ lu.assertEquals( tostring(n:log(4)), "(-2.53 +/- 0.07)" )
+
+ local n = N(5.0, 0.2)
+ lu.assertEquals( tostring(n:log(10)), "(0.699 +/- 0.017)" )
+
+ local n = N(0.03, 0.003)
+ lu.assertEquals( tostring(n:log(10)), "(-1.52 +/- 0.04)" )
+end
+
+-- test the exponential function
+function TestNumber:testExp()
+ defaultformat()
+
+ local n = N(7.0, 0.06)
+ lu.assertEquals( tostring(n:exp()), "(1097 +/- 66)" )
+
+ local n = N(0.2, 0.01)
+ lu.assertEquals( tostring(n:exp()), "(1.221 +/- 0.012)" )
+end
+
+-- test the square root function
+function TestNumber:testSqrt()
+ defaultformat()
+
+ local n = N(104.2, 0.06)
+ lu.assertEquals( tostring(n:sqrt()), "(10.208 +/- 0.003)" )
+
+ local n = N(0.0004, 0.000005)
+ lu.assertEquals( tostring(n:sqrt()), "(0.02000 +/- 0.00013)" )
+end
+
+
+
+-- TRIGONOMETRIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Trigonometric_functions
+
+function TestNumber:testSin()
+ defaultformat()
+
+ local n = N(-math.pi/6, 0.02)
+ lu.assertEquals( tostring(n:sin()), "(-0.500 +/- 0.017)" )
+end
+
+
+function TestNumber:testCos()
+ defaultformat()
+
+ local n = N(math.pi/3, 0.01)
+ lu.assertEquals( tostring(n:cos()), "(0.500 +/- 0.009)" )
+
+ local n = N(math.pi/4, 0.1)
+ lu.assertEquals( tostring(n:cos()), "(0.71 +/- 0.07)" )
+
+ local n = N(-math.pi/6, 0.02)
+ lu.assertEquals( tostring(n:cos()), "(0.87 +/- 0.01)" )
+end
+
+function TestNumber:testTan()
+ defaultformat()
+
+ local n = N(0, 0.01)
+ lu.assertEquals( tostring(n:tan()), "(0.000 +/- 0.010)" )
+
+ local n = N(math.pi/3, 0.1)
+ lu.assertEquals( tostring(n:tan()), "(1.7 +/- 0.4)" )
+
+ local n = N(-math.pi/3, 0.02)
+ lu.assertEquals( tostring(n:tan()), "(-1.73 +/- 0.08)" )
+end
+
+
+-- INVERS TRIGONOMETRIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#arctan
+
+function TestNumber:testArcsin()
+ defaultformat()
+
+ local n = N(0.5, 0.01)
+ lu.assertEquals( tostring(n:asin()), "(0.524 +/- 0.012)" )
+end
+
+
+function TestNumber:testArccos()
+ defaultformat()
+
+ local n = N(0.5, 0.01)
+ lu.assertEquals( tostring(n:acos()), "(1.047 +/- 0.012)" )
+end
+
+function TestNumber:testArctan()
+ defaultformat()
+
+ local n = N(0.5, 0.01)
+ lu.assertEquals( tostring(n:atan()), "(0.464 +/- 0.008)" )
+end
+
+
+-- HYPERBOLIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Hyperbolic_function
+
+function TestNumber:testSinh()
+ defaultformat()
+
+ local n = N(10, 0.003)
+ lu.assertEquals( tostring(n:sinh()), "(11013 +/- 33)" )
+end
+
+function TestNumber:testCosh()
+ defaultformat()
+
+ local n = N(10, 0.003)
+ lu.assertEquals( tostring(n:cosh()), "(11013 +/- 33)" )
+end
+
+function TestNumber:testTanh()
+ defaultformat()
+
+ local n = N(1, 0.003)
+ lu.assertEquals( tostring(n:tanh()), "(0.7616 +/- 0.0013)" )
+end
+
+
+-- INVERS HYPERBOLIC FUNCTIONS
+-- https://en.wikipedia.org/wiki/Inverse_hyperbolic_function
+
+function TestNumber:testArcsinh()
+ local n = N(1000, 5)
+ lu.assertEquals( tostring(n:asinh()), "(7.601 +/- 0.005)" )
+end
+
+function TestNumber:testArccosh()
+ local n = N(1000, 5)
+ lu.assertEquals( tostring(n:acosh()), "(7.601 +/- 0.005)" )
+end
+
+function TestNumber:testArctanh()
+ local n = N(0.2, 0.01)
+ lu.assertEquals( tostring(n:atanh()), "(0.203 +/- 0.010)" )
+end
+
+
+return TestNumber
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua
new file mode 100644
index 00000000000..b27c853e7cc
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testQuantity.lua
@@ -0,0 +1,609 @@
+--[[
+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 = "../?.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
diff --git a/Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua b/Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua
new file mode 100644
index 00000000000..eb0d67402be
--- /dev/null
+++ b/Master/texmf-dist/doc/lualatex/lua-physical/test/testUnit.lua
@@ -0,0 +1,95 @@
+--[[
+This file contains the unit tests for the physical.Unit 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 = "../?.lua;" .. package.path
+local physical = require("physical")
+
+local D = physical.Dimension
+local U = physical.Unit
+
+
+TestUnit = {}
+
+function TestUnit:testNewDefault()
+ local u = U.new()
+ lu.assertEquals(u._term, {{},{}})
+end
+
+function TestUnit:testNewBaseUnit()
+ local _m = U.new("m","meter")
+ lu.assertEquals(_m.symbol, "m")
+ lu.assertEquals(_m.name, "meter")
+end
+
+function TestUnit:testMultiply()
+ local _m = U.new("m","meter")
+
+ local _m2 = _m * _m
+
+ lu.assertEquals(_m2.symbol, nil)
+ lu.assertEquals(_m2.name, nil)
+ lu.assertEquals(_m2._term[1][1][1], _m)
+ lu.assertEquals(_m2._term[1][1][2], 1)
+ lu.assertEquals(_m2._term[1][2][1], _m)
+ lu.assertEquals(_m2._term[1][2][2], 1)
+end
+
+function TestUnit:testDivide()
+ local _m = U.new("m","meter")
+
+ local _m2 = _m / _m
+
+ lu.assertEquals(_m2.symbol, nil)
+ lu.assertEquals(_m2.name, nil)
+ lu.assertEquals(_m2._term[1][1][1], _m)
+ lu.assertEquals(_m2._term[1][1][2], 1)
+ lu.assertEquals(_m2._term[2][1][1], _m)
+ lu.assertEquals(_m2._term[2][1][2], 1)
+end
+
+function TestUnit:testPower()
+ local _m = U.new("m","meter")
+
+ local _m2 = _m^4
+
+ lu.assertEquals(_m2.symbol, nil)
+ lu.assertEquals(_m2.name, nil)
+ lu.assertEquals(_m2._term[1][1][1], _m)
+ lu.assertEquals(_m2._term[1][1][2], 4)
+end
+
+function TestUnit:testNewDerivedUnit()
+ local _m = U.new("m","meter")
+ local _m2 = U.new("m2","squaremeter")
+
+ lu.assertEquals(_m2.symbol, "m2")
+ lu.assertEquals(_m2.name, "squaremeter")
+ lu.assertEquals(_m2._term[1][1][1], _m2)
+ lu.assertEquals(_m2._term[1][1][2], 1)
+end
+
+
+return TestUnit