Repository: chrisguidry/measured (language: python)

Issue:
There is a cubic ton of odd and wonderful measurements to incorporate on Wikipedia's List of unusual units of measurement
https://en.wikipedia.org/wiki/List_of_unusual_units_of_measurement


Discussion / hints:

Fix patch:
diff --git a/src/measured/astronomical.py b/src/measured/astronomical.py
index 6e7c935..3975bdb 100644
--- a/src/measured/astronomical.py
+++ b/src/measured/astronomical.py
@@ -38,12 +38,15 @@
 from measured import Length, Mass, Time, Unit, si
 from measured.geometry import π
 from measured.physics import c
-from measured.si import Kilogram, Meter
+from measured.si import Kilogram, Meter, Second
 
 # Measures of Time
 
 Day = Unit.derive(si.Day, name="day", symbol="D")
 
+SiderealDay = Time.unit("sidereal day", "SD")
+SiderealDay.equals(86164.0905 * Second)
+
 JulianYear = Time.unit("Julian year", "a")
 JulianYear.equals(365.25 * Day)
 
@@ -60,6 +63,10 @@
 Parsec = Length.unit("parsec", "pc")
 Parsec.equals(96939420213600600 / π * Meter)
 
+# https://en.wikipedia.org/wiki/Siriometer
+Siriometer = Length.unit("seriometer", "sir")
+Siriometer.equals(1000000 * AstronomicalUnit)
+
 # Measures of Mass
 
 # https://en.wikipedia.org/wiki/Solar_mass
diff --git a/src/measured/computing.py b/src/measured/computing.py
new file mode 100644
index 0000000..1135798
--- /dev/null
+++ b/src/measured/computing.py
@@ -0,0 +1,8 @@
+from measured import PlaneAngle
+from measured.geometry import π
+from measured.si import Radian
+
+# https://en.wikipedia.org/wiki/List_of_unusual_units_of_measurement
+# The Furman is a unit of angular measure equal to 1/65,536 of a circle
+Furman = PlaneAngle.unit("Furman", "furman")
+Furman.equals(1 / 65536 * 2 * π * Radian)
diff --git a/src/measured/eu.py b/src/measured/eu.py
new file mode 100644
index 0000000..1fde5e7
--- /dev/null
+++ b/src/measured/eu.py
@@ -0,0 +1,6 @@
+from measured import PlaneAngle
+from measured.geometry import π
+from measured.si import Radian
+
+Gradian = PlaneAngle.unit("gradian", "gon")
+Gradian.equals(π / 200 * Radian)
diff --git a/src/measured/iec.py b/src/measured/iec.py
index e882e84..929c49f 100644
--- a/src/measured/iec.py
+++ b/src/measured/iec.py
@@ -10,6 +10,8 @@
 
     Bit (Unit):
 
+    Rack Unit (Unit):  The height unit of a standard electronics rack
+
 Attributes: Derived Units
 
     Byte (Unit): 8 bits
@@ -29,7 +31,10 @@
 
 # https://en.wikipedia.org/wiki/Binary_prefix#Adoption_by_IEC,_NIST_and_ISO
 
-from . import Information, Prefix, Unit
+from measured.si import Meter
+from measured.us import Inch
+
+from . import Information, Length, Prefix, Unit
 
 Kibi = Prefix(2, 10, name="kibi", symbol="Ki")
 Mebi = Prefix(2, 20, name="mebi", symbol="Mi")
@@ -44,3 +49,10 @@
 Bit = Information.unit(name="bit", symbol="b")
 Bit.equals(1 * Shannon)
 Byte = Unit.derive(Bit.scale(Prefix(2, 3)), name="byte", symbol="B")
+
+# https://en.wikipedia.org/wiki/Rack_unit
+# IEC 60297 Mechanical structures for electronic equipment
+
+RackUnit = Length.unit("rack unit", "U")
+RackUnit.equals(1.75 * Inch)
+RackUnit.equals(0.04445 * Meter)
diff --git a/src/measured/si.py b/src/measured/si.py
index e9992b2..b743417 100644
--- a/src/measured/si.py
+++ b/src/measured/si.py
@@ -125,7 +125,9 @@
 
 from . import (
     AmountOfSubstance,
+    Area,
     Charge,
+    Energy,
     Length,
     LuminousIntensity,
     Mass,
@@ -235,6 +237,9 @@
 Hour.equals(3600 * Second)
 Hour.equals(60 * Minute)
 
+Dalton = Mass.unit("dalton", "Da")
+Dalton.equals(1.66053906660e-27 * Kilogram)
+
 # This is the SI-compatible "day" as a unit of measure, defined as 86,400 seconds.
 # https://en.wikipedia.org/wiki/Day#International_System_of_Units_(SI)
 Day = Time.unit("day", "d")
@@ -254,3 +259,25 @@
 Arcsecond.equals(π / (180 * 3600) * Radian)
 Arcsecond.equals(1 / 3600 * Degree)
 Arcsecond.equals(1 / 60 * Arcminute)
+
+
+# Specialized units compatible with SI
+
+# From the domain of High-energy physics
+# https://en.wikipedia.org/wiki/Barn_(unit)
+# https://en.wikipedia.org/wiki/Shake_(unit)
+
+Barn = Area.unit("barn", "barn")
+Barn.equals(1e-28 * Meter**2)
+
+Outhouse = Area.unit("outhouse", "outhouse")
+Outhouse.equals(1e-34 * Meter**2)
+
+Shed = Area.unit("shed", "shed")
+Shed.equals(1e-52 * Meter**2)
+
+Shake = Time.unit("shake", "shake")
+Shake.equals(1e-8 * Second)
+
+ElectronVolt = Energy.unit("electron-volt", "eV")
+ElectronVolt.equals(1.602176634e-19 * Joule)
diff --git a/src/measured/systems.py b/src/measured/systems.py
index 95eff90..ca81c81 100644
--- a/src/measured/systems.py
+++ b/src/measured/systems.py
@@ -1,1 +1,12 @@
-from measured import astronomical, avoirdupois, iec, natural, si, troy, us  # noqa: F401
+from measured import (  # noqa: F401
+    astronomical,
+    avoirdupois,
+    computing,
+    eu,
+    fff,
+    iec,
+    natural,
+    si,
+    troy,
+    us,
+)
diff --git a/src/measured/us.py b/src/measured/us.py
index 3aa12c5..ca851bc 100644
--- a/src/measured/us.py
+++ b/src/measured/us.py
@@ -66,6 +66,10 @@
 Inch.equals(6 * Pica)
 Inch.equals(25.4 * Milli * Meter)
 
+# https://en.wikipedia.org/wiki/Hand_(unit)
+Hand = Length.unit(name="hand", symbol="hh")
+Hand.equals(4 * Inch)
+
 Foot = Length.unit(name="foot", symbol="ft.")
 Foot.equals(12 * Inch)
 Foot.equals(0.3048 * Meter)
@@ -147,10 +151,19 @@
 # Volume
 # https://en.wikipedia.org/wiki/United_States_customary_units#Volume
 
+BoardFoot = Volume.unit(name="boardfoot", symbol="FBM")
+BoardFoot.equals(1 * Foot * 1 * Foot * 1 * Inch)
+
+Rick = Volume.unit(name="rick", symbol="rick")
+Rick.equals(4 * Foot * 8 * Foot * 16 * Inch)
+
+Cord = Volume.unit(name="cord", symbol="cord")
+Cord.equals(4 * Foot * 8 * Foot * 4 * Foot)
+
 AcreFoot = Volume.unit(name="acre-foot", symbol="acre-ft.")
 AcreFoot.equals(43560 * Foot**3)
 
-# Fluid Volumes
+# Fluid Volume
 # https://en.wikipedia.org/wiki/United_States_customary_units#Fluid_volume
 
 Minim = Volume.unit(name="minim", symbol="min.")


Test patch:
diff --git a/tests/iec/test_lengths.py b/tests/iec/test_lengths.py
new file mode 100644
index 0000000..4cb4745
--- /dev/null
+++ b/tests/iec/test_lengths.py
@@ -0,0 +1,10 @@
+from measured import Length
+from measured.iec import RackUnit
+from measured.us import Inch
+
+
+def test_rack_unit() -> None:
+    assert RackUnit.dimension is Length
+    assert RackUnit.name == "rack unit"
+    assert RackUnit.symbol == "U"
+    assert 1 * RackUnit == 1.75 * Inch
diff --git a/tests/si/test_length_area_volume.py b/tests/si/test_length_area_volume.py
index 0d8118b..b608019 100644
--- a/tests/si/test_length_area_volume.py
+++ b/tests/si/test_length_area_volume.py
@@ -1,4 +1,17 @@
-from measured.si import Centi, Deci, Kilo, Liter, Meter, Milli
+from measured import Area
+from measured.si import (
+    Barn,
+    Centi,
+    Deci,
+    Kilo,
+    Liter,
+    Meter,
+    Micro,
+    Milli,
+    Outhouse,
+    Shed,
+    Yocto,
+)
 
 cm = Centi * Meter
 km = Kilo * Meter
@@ -30,6 +43,29 @@ def test_area_conversions() -> None:
     assert (1 * Meter**2).in_unit(km**2).magnitude == 0.000001
 
 
+def test_barn() -> None:
+    assert Barn.dimension is Area
+    assert Barn.name == "barn"
+    assert Barn.symbol == "barn"  # we already have a 'b' symbol at measured.iec.Bit
+    (1 * Barn).assert_approximates(1e-28 * Meter**2)
+
+
+def test_outhouse() -> None:
+    assert Outhouse.dimension is Area
+    assert Outhouse.name == "outhouse"
+    assert Outhouse.symbol == "outhouse"
+    (1 * Outhouse).assert_approximates(1 * Micro * Barn)
+    (1 * Outhouse).assert_approximates(1e-34 * Meter**2)
+
+
+def test_shed() -> None:
+    assert Shed.dimension is Area
+    assert Shed.name == "shed"
+    assert Shed.symbol == "shed"
+    (1 * Shed).assert_approximates(1 * Yocto * Barn)
+    (1 * Shed).assert_approximates(1e-52 * Meter**2)
+
+
 def test_volume_equivalence() -> None:
     assert 1 * Liter == 0.001 * Meter**3
     assert 1 * Liter == 1 * (Deci * Meter) ** 3
diff --git a/tests/si/test_time.py b/tests/si/test_time.py
index 9d89dbb..5af633e 100644
--- a/tests/si/test_time.py
+++ b/tests/si/test_time.py
@@ -1,7 +1,7 @@
 import pytest
 
 from measured import Quantity
-from measured.si import Day, Hour, Minute, Second
+from measured.si import Day, Hour, Minute, Nano, Second, Shake
 
 
 @pytest.mark.parametrize(
@@ -32,3 +32,7 @@ def test_time_conversions(
     seconds.assert_approximates(days)
     assert seconds == hours
     assert seconds == minutes
+
+
+def test_shake() -> None:
+    assert 1 * Shake == 10 * Nano * Second
diff --git a/tests/test_astronomical.py b/tests/test_astronomical.py
index 4efda50..61ba808 100644
--- a/tests/test_astronomical.py
+++ b/tests/test_astronomical.py
@@ -5,11 +5,18 @@
     JupiterMass,
     LightYear,
     Parsec,
+    SiderealDay,
+    Siriometer,
     SolarMass,
 )
 from measured.geometry import π
 from measured.physics import G
-from measured.si import Meter
+from measured.si import Hour, Meter
+
+
+def test_astronomical_unit() -> None:
+    (1 * AstronomicalUnit).assert_approximates(1.495978707e11 * Meter)
+    (1 * Siriometer).assert_approximates(1e6 * AstronomicalUnit)
 
 
 def test_parsec() -> None:
@@ -36,3 +43,7 @@ def test_solar_system_masses() -> None:
     (1 * SolarMass).assert_approximates(1047.348644 * JupiterMass, within=3e-4)
     (1 * SolarMass).assert_approximates(332946.0487 * EarthMass, within=3e-5)
     (1 * JupiterMass).assert_approximates(317.82838 * EarthMass, within=3e-6)
+
+
+def test_sidereal_day() -> None:
+    (1 * SiderealDay).assert_approximates(23.9344696 * Hour, within=7e-10)
diff --git a/tests/test_computing.py b/tests/test_computing.py
new file mode 100644
index 0000000..cd3129c
--- /dev/null
+++ b/tests/test_computing.py
@@ -0,0 +1,9 @@
+from measured.computing import Furman
+from measured.geometry import π
+from measured.si import Arcsecond, Degree, Radian
+
+
+def test_furman() -> None:
+    assert 65536 * Furman == 2 * π * Radian
+    assert 65536 * Furman == 360 * Degree
+    assert 19.77 * Arcsecond < 1 * Furman < 20 * Arcsecond
diff --git a/tests/test_eu.py b/tests/test_eu.py
new file mode 100644
index 0000000..d300417
--- /dev/null
+++ b/tests/test_eu.py
@@ -0,0 +1,9 @@
+from measured.eu import Gradian
+from measured.geometry import π
+from measured.si import Arcminute, Degree, Radian
+
+
+def test_gradian() -> None:
+    assert (1 * Gradian) == (π / 200 * Radian)
+    assert (1 * Gradian) == (9 / 10 * Degree)
+    (1 * Gradian).assert_approximates((54 * Arcminute))
diff --git a/tests/test_physics.py b/tests/test_physics.py
index e996004..89dde85 100644
--- a/tests/test_physics.py
+++ b/tests/test_physics.py
@@ -19,7 +19,19 @@
     Volume,
     physics,
 )
-from measured.si import Coulomb, Farad, Joule, Kelvin, Kilogram, Meter, Second
+from measured.si import (
+    Coulomb,
+    Dalton,
+    ElectronVolt,
+    Farad,
+    Joule,
+    Kelvin,
+    Kilogram,
+    Mega,
+    Meter,
+    Second,
+    Volt,
+)
 from measured.us import Foot
 
 
@@ -60,6 +72,7 @@ def test_constants_have_expected_dimension(
         (physics.e, 1.602176634e-19 * Coulomb),
         (physics.ε0, 8.8541878128e-12 * Farad / Meter),
         (physics.mₑ, 9.1093837015e-31 * Kilogram),
+        (physics.mₑ, 0.0005485799090649074 * Dalton),
         (physics.e, 1.602176634e-19 * Coulomb),
         (
             physics.kₑ,
@@ -74,3 +87,12 @@ def test_constants_have_expected_si_values(
     constant: Quantity, expected: Quantity
 ) -> None:
     constant.assert_approximates(expected)
+
+
+def test_electronvolt() -> None:
+    c = physics.c
+    e = physics.e
+
+    assert (1 * ElectronVolt) == e * Volt
+    (1 * ElectronVolt / c**2).assert_approximates(1.78266192e-36 * Kilogram)
+    (1 * (Mega * ElectronVolt) / c**2).assert_approximates(0.0010735441 * Dalton)
diff --git a/tests/us/test_fda_nutrition_labeling.py b/tests/us/test_fda_nutrition_labeling.py
new file mode 100644
index 0000000..77efeef
--- /dev/null
+++ b/tests/us/test_fda_nutrition_labeling.py
@@ -0,0 +1,29 @@
+# https://web.archive.org/web/20120204081321/http://edocket.access.gpo.gov/cfr_2004/aprqtr/21cfr101.9.htm
+#
+# (viii) For nutrition labeling purposes, a teaspoon means 5
+# milliliters (mL), a tablespoon means 15 mL, a cup means 240 mL, 1 fl oz
+# means 30 mL, and 1 oz in weight means 28 g.
+# [pp 23]
+
+from measured.si import Gram, Liter, Milli
+from measured.us import Cup, FluidOunce, Ounce, Tablespoon, Teaspoon
+
+
+def test_teaspoon_is_about_5mL() -> None:
+    (1 * Teaspoon).assert_approximates(5 * Milli * Liter, within=0.015)
+
+
+def test_tablespoon_is_about_15mL() -> None:
+    (1 * Tablespoon).assert_approximates(15 * Milli * Liter, within=0.015)
+
+
+def test_cup_is_about_240mL() -> None:
+    (1 * Cup).assert_approximates(240 * Milli * Liter, within=0.015)
+
+
+def test_fluid_ounce_is_about_30mL() -> None:
+    (1 * FluidOunce).assert_approximates(30 * Milli * Liter, within=0.015)
+
+
+def test_dry_ounce_is_about_28g() -> None:
+    (1 * Ounce).assert_approximates(28 * Gram, within=0.013)
diff --git a/tests/us/test_volumes.py b/tests/us/test_volumes.py
index 73ed1cc..8d875a4 100644
--- a/tests/us/test_volumes.py
+++ b/tests/us/test_volumes.py
@@ -1,8 +1,12 @@
 import pytest
 
-from measured import Quantity
-from measured.si import Kilo, Liter, Meter, Milli
+from measured import Quantity, Volume
+from measured.si import Centi, Kilo, Liter, Meter, Milli
 from measured.us import (
+    Acre,
+    AcreFoot,
+    BoardFoot,
+    Cord,
     Cup,
     FluidOunce,
     Foot,
@@ -10,6 +14,7 @@
     Hogshead,
     Inch,
     Mile,
+    Rick,
     Rod,
     Tablespoon,
     Teaspoon,
@@ -50,3 +55,27 @@ def test_abe() -> None:
 
     abes_car.assert_approximates((0.125 / 63) * Mile / Gallon, 0)
     assert 0.00198 * Mile / Gallon <= abes_car <= 0.00199 * Mile / Gallon
+
+
+def test_boardfoot() -> None:
+    assert BoardFoot.dimension is Volume
+    assert BoardFoot.name == "boardfoot"
+    assert BoardFoot.symbol == "FBM"
+    assert 1 * BoardFoot == 12 * 12 * 1 * Inch**3
+    (1 * BoardFoot).assert_approximates(2359.737 * (Centi * Meter) ** 3)
+    (1 * BoardFoot).assert_approximates(0.002359737 * Meter**3)
+    (1 * BoardFoot).assert_approximates(2.359737 * Liter)
+
+
+def test_cord_and_rick() -> None:
+    assert (1 * Cord) == (3 * Rick)
+    (1 * Cord).assert_approximates(3.623 * Meter**3, within=5e-4)
+
+
+def test_acrefoot() -> None:
+    assert AcreFoot.dimension is Volume
+    assert AcreFoot.name == "acre-foot"
+    assert AcreFoot.symbol == "acre-ft."
+    assert 1 * AcreFoot == 1 * Acre * Foot
+    (1 * AcreFoot).assert_approximates(325851 * Gallon, within=2e-6)
+    (1 * AcreFoot).assert_approximates(1233 * Meter**3, within=4e-4)