From ec131f43566d73ee302a0b6195f3fef6962896ed Mon Sep 17 00:00:00 2001 From: Cobord Date: Tue, 10 Sep 2024 15:00:07 -0400 Subject: [PATCH 1/3] multidimensional test, docstring --- test/test_dimension.py | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/test_dimension.py b/test/test_dimension.py index 3000d0b..a922b07 100644 --- a/test/test_dimension.py +++ b/test/test_dimension.py @@ -37,6 +37,9 @@ def _all_dimensions() -> Iterator[type[core.ValueWithDimension]]: @pytest.mark.parametrize('dimension', _ALL_DIMENSIONS) def test_arithmetic_ops_preserve_type(dimension: type[core.ValueWithDimension]) -> None: + """ + addition, subtraction and multiplication by scalars preserve the dimensions + """ u = dimension(dimension.valid_base_units()[0]) a = u * 2 b = u * 3 @@ -61,6 +64,10 @@ def test_arithmetic_ops_preserve_type(dimension: type[core.ValueWithDimension]) def test_arithmetic_ops_preserve_type_array( dimension: type[core.ValueWithDimension], ) -> None: + """ + addition, subtraction and multiplication by scalars preserve the dimensions + even when it is an array of multiple such elements + """ u = dimension(dimension.valid_base_units()[0]) a = u * [2, 3] b = u * [5, 7] @@ -79,3 +86,35 @@ def test_arithmetic_ops_preserve_type_array( c = a / 11 assert all(c == u * [2 / 11, 3 / 11]) + + +@pytest.mark.parametrize('dimension', _ALL_DIMENSIONS) +def test_arithmetic_ops_preserve_type_multi_array( + dimension: type[core.ValueWithDimension], +) -> None: + """ + addition, subtraction and multiplication by scalars preserve the dimensions + even when it is a multidimensional array of multiple such elements + """ + u = dimension(dimension.valid_base_units()[0]) + a = u * [[2, 3],[4,9]] + b = u * [[5, 7],[8,5]] + + # Declare with correct type so that mypy knows the type to compare to. + c = u * [[1.0, 1.0],[1.0,1.0]] + + c = a + b + should_all_true = c == u * [[7.0, 10.0],[12.0,14.0]] + assert should_all_true.all().all() + + c = a - b + should_all_true = c == u * [[-3.0, -4.0],[-4.0,4.0]] + assert should_all_true.all().all() + + c = a * 7 + should_all_true = c == u * [[14.0, 21.0],[28.0,63.0]] + assert should_all_true.all().all() + + c = a / 11 + should_all_true = c == u * [[2/11, 3/11],[4/11,9/11]] + assert should_all_true.all().all() From 5b9c0c2c547feea51ae11dae6caac25ef9db6d5d Mon Sep 17 00:00:00 2001 From: Cobord Date: Tue, 10 Sep 2024 16:31:04 -0400 Subject: [PATCH 2/3] docstrings, further cases in some tests --- test/test_dimension.py | 14 ++++++------ test/test_protos.py | 26 ++++++++++++++++++--- test/test_unit_data.py | 51 ++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/test/test_dimension.py b/test/test_dimension.py index a922b07..3066af0 100644 --- a/test/test_dimension.py +++ b/test/test_dimension.py @@ -97,24 +97,24 @@ def test_arithmetic_ops_preserve_type_multi_array( even when it is a multidimensional array of multiple such elements """ u = dimension(dimension.valid_base_units()[0]) - a = u * [[2, 3],[4,9]] - b = u * [[5, 7],[8,5]] + a = u * [[2, 3], [4, 9]] + b = u * [[5, 7], [8, 5]] # Declare with correct type so that mypy knows the type to compare to. - c = u * [[1.0, 1.0],[1.0,1.0]] + c = u * [[1.0, 1.0], [1.0, 1.0]] c = a + b - should_all_true = c == u * [[7.0, 10.0],[12.0,14.0]] + should_all_true = c == u * [[7.0, 10.0], [12.0, 14.0]] assert should_all_true.all().all() c = a - b - should_all_true = c == u * [[-3.0, -4.0],[-4.0,4.0]] + should_all_true = c == u * [[-3.0, -4.0], [-4.0, 4.0]] assert should_all_true.all().all() c = a * 7 - should_all_true = c == u * [[14.0, 21.0],[28.0,63.0]] + should_all_true = c == u * [[14.0, 21.0], [28.0, 63.0]] assert should_all_true.all().all() c = a / 11 - should_all_true = c == u * [[2/11, 3/11],[4/11,9/11]] + should_all_true = c == u * [[2 / 11, 3 / 11], [4 / 11, 9 / 11]] assert should_all_true.all().all() diff --git a/test/test_protos.py b/test/test_protos.py index d41496c..cad82bc 100644 --- a/test/test_protos.py +++ b/test/test_protos.py @@ -47,6 +47,11 @@ @pytest.mark.parametrize('unit', _ONE_UNIT + _TWO_UNITS) def test_value_conversion_trip(unit: Value) -> None: + """ + when making a unitful value + we can convert it back and forth to the proto of that unit + and get the same value + """ rs = np.random.RandomState(0) for value in rs.random(10): v = value * unit @@ -55,6 +60,10 @@ def test_value_conversion_trip(unit: Value) -> None: @pytest.mark.parametrize('unit', _ONE_UNIT + _TWO_UNITS) def test_complex_conversion_trip(unit: Value) -> None: + """ + the roundtrip of conversion back and forth + still works the same way even if the value is complex valued + """ rs = np.random.RandomState(0) for value in rs.random(10): v = 1j * value * unit @@ -63,6 +72,10 @@ def test_complex_conversion_trip(unit: Value) -> None: @pytest.mark.parametrize('unit', _ONE_UNIT + _TWO_UNITS) def test_valuearray_conversion_trip(unit: Value) -> None: + """ + the roundtrip of conversion back and forth + still works the same way even if the value is array + """ rs = np.random.RandomState(0) for value in rs.random((4, 2, 4, 3)): v = value * unit @@ -73,6 +86,10 @@ def test_valuearray_conversion_trip(unit: Value) -> None: @pytest.mark.parametrize('unit', _ONE_UNIT + _TWO_UNITS) def test_complex_valuearray_conversion_trip(unit: Value) -> None: + """ + the roundtrip of conversion back and forth + still works the same way even if the value is complex and array + """ rs = np.random.RandomState(0) for real, imag in zip(rs.random((4, 2, 4, 3)), rs.random((4, 2, 4, 3))): v = (real + 1j * imag) * unit @@ -128,9 +145,12 @@ def test_unit_exponent_with_zero_denominator_raises() -> None: def test_scale_values_are_correct() -> None: - assert len(SCALE_PREFIXES) == len( - tunits_pb2.Scale.items() - ), f'differing number of scales in proto and SCALE_PREFIXES. If you are adding new scales please update the SCALE_PREFIXES map' + assert len(SCALE_PREFIXES) == len(tunits_pb2.Scale.items()), " ".join( + [ + "differing number of scales in proto and SCALE_PREFIXES.", + "If you are adding new scales please update the SCALE_PREFIXES map", + ] + ) scale_to_prefix = { 'YOTTA': 'Y', diff --git a/test/test_unit_data.py b/test/test_unit_data.py index e00f8c2..f38544a 100644 --- a/test/test_unit_data.py +++ b/test/test_unit_data.py @@ -22,14 +22,22 @@ def test_all_default_units_and_simple_variations_thereof_are_parseable() -> None: + """ + parsing unit formulas recovers that same default unit + even after doing basic arithmetic, the printed expression + remains parseable and parses to the given value + """ db = core.default_unit_database for k, u in db.known_units.items(): assert db.parse_unit_formula(k) == u - for v in [u, 1 / u, 5 * u, 1.1 * u, u**2]: + for v in [u, 1 / u, 5 * u, 1.1 * u, u**2, 1e-20 * u]: assert db.parse_unit_formula(str(v)) == v def test_unit_relationship_energy_stored_in_capacity() -> None: + """ + unit relationship of volts, farads and joules + """ capacitance = 2 * units.uF voltage = 5 * units.V stored = capacitance * voltage**2 / 2 @@ -37,6 +45,12 @@ def test_unit_relationship_energy_stored_in_capacity() -> None: def test_durations() -> None: + """ + time units are compatible + - can be added to give another time + - explicit conversion factors for some of the specified durations + - can divide times and get something unitless + """ a = units.week + units.year + units.day + units.hour + units.minute assert a.is_compatible(units.second) assert round(units.year / units.week) == 52 @@ -44,6 +58,12 @@ def test_durations() -> None: def test_lengths() -> None: + """ + length units are compatible + - can be added to give another length + - explicit conversion factors for some of the specified lengths (mix of imperial and SI) + - can divide lengths and get something unitless + """ a = ( units.inch + units.foot @@ -60,6 +80,11 @@ def test_lengths() -> None: def test_areas() -> None: + """ + area units are compatible + - can be added to give another area + - can compare areas even if they have different units + """ assert (units.hectare + units.barn).is_compatible(units.meter**2) # *Obviously* a hectare of land can hold a couple barns. @@ -69,12 +94,23 @@ def test_areas() -> None: def test_angles() -> None: + """ + angle units are compatible + - can be added to give another angle + - explicit conversion factors for the different units + """ assert (units.deg + units.cyc).is_compatible(units.rad) assert np.isclose((math.pi * units.rad)[units.deg], 180) assert np.isclose((math.pi * units.rad)[units.cyc], 0.5) def test_volumes() -> None: + """ + volume units are compatible + - can be added to give another volume + - explicit conversion factors for some of the specified volumes (mix of imperial and SI) + - can divide volumes and get something unitless + """ a = ( units.teaspoon + units.tablespoon @@ -92,6 +128,12 @@ def test_volumes() -> None: def test_masses() -> None: + """ + mass units are compatible + - can be added to give another mass + - explicit conversion factors for some of the specified masses (mix of imperial and SI) + - can divide masses and get something unitless + """ assert np.isclose((units.ounce + units.pound + units.ton) / units.megagram, 0.9077, atol=1e-4) @@ -100,7 +142,9 @@ def test_pressures() -> None: def test_basic_constants() -> None: - # Just some random products compared against results from Wolfram Alpha. + """ + Just some random products compared against results from Wolfram Alpha. + """ u = units.c * units.mu0 * units.eps0 * units.G * units.hplanck v = 1.475e-52 * units.m**4 / units.s**2 @@ -127,5 +171,8 @@ def test_basic_constants() -> None: ], ) def test_other_constants(lhs: core.Value, rhs: core.Value, ratio: float) -> None: + """ + more physically relevant unitful constants with parameterized pytest + """ r = lhs / rhs assert np.isclose(r, ratio, atol=1e-3) From 458c12f12d9e9f8abedc8c5eca8e1c2bb5bc3785 Mon Sep 17 00:00:00 2001 From: Cobord Date: Wed, 11 Sep 2024 13:39:19 -0400 Subject: [PATCH 3/3] docstrings, further cases in some tests --- test/test_unit_database.py | 56 ++++++++++++++++++++++++++ test/test_value.py | 80 +++++++++++++++++++++++++++++++++++++- test/test_value_array.py | 47 ++++++++++++++++++++++ 3 files changed, 182 insertions(+), 1 deletion(-) diff --git a/test/test_unit_database.py b/test/test_unit_database.py index c254aa8..113b269 100644 --- a/test/test_unit_database.py +++ b/test/test_unit_database.py @@ -25,15 +25,26 @@ def test_auto_create() -> None: + """ + will automatically create a unit depending on auto_create_units parameter of __init__ + """ with pytest.raises(KeyError): UnitDatabase(auto_create_units=False).parse_unit_formula('tests') db = UnitDatabase(auto_create_units=True) u = db.parse_unit_formula('tests') + assert str(u) == 'tests' + u_again = db.get_unit('tests') + assert str(u_again) == 'tests' assert 5 == 5 * u / u def test_auto_create_disabled_when_purposefully_adding_units() -> None: + """ + even when auto_create_units is True + the auto creation does not happen + with these methods + """ db = UnitDatabase(auto_create_units=True) with pytest.raises(KeyError): @@ -47,6 +58,10 @@ def test_auto_create_disabled_when_purposefully_adding_units() -> None: def test_get_unit_with_auto_create_override() -> None: + """ + can override the instance level auto_create_units + with auto_create in method call + """ db_auto = UnitDatabase(auto_create_units=True) db_manual = UnitDatabase(auto_create_units=False) @@ -67,6 +82,9 @@ def test_get_unit_with_auto_create_override() -> None: def test_add_root_unit() -> None: + """ + adding a root unit not derived from anything else + """ db = UnitDatabase(auto_create_units=False) db.add_root_unit('cats') @@ -85,6 +103,10 @@ def test_add_root_unit() -> None: def test_add_base_unit_with_prefixes() -> None: + """ + adding a root unit not derived from anything else + and prefixes for orders of magnitude + """ db = UnitDatabase(auto_create_units=False) db.add_base_unit_data( BaseUnitData('b', 'base', True), @@ -120,6 +142,9 @@ def test_add_base_unit_with_prefixes() -> None: def test_add_base_unit_without_prefixes() -> None: + """ + can disallow prefixes with False in use_prefixes argument to BaseUnitData + """ db = UnitDatabase(auto_create_units=False) db.add_base_unit_data( BaseUnitData('b', 'base', False), @@ -157,6 +182,9 @@ def test_add_base_unit_without_prefixes() -> None: def test_add_derived_unit_with_prefixes() -> None: + """ + can add a derived unit that can be prefixed + """ db = UnitDatabase(auto_create_units=False) with pytest.raises(KeyError): db.add_derived_unit_data(DerivedUnitData('tails', 't', 'shirts'), []) @@ -187,6 +215,14 @@ def test_add_derived_unit_with_prefixes() -> None: assert db.get_unit('super_tails') == v * 10 assert db.get_unit('d_t') == v * 100 assert db.get_unit('duper_tails') == v * 100 + with pytest.raises(KeyError): + db.get_unit('super_shirts') + with pytest.raises(KeyError): + db.get_unit('s_shirts') + with pytest.raises(KeyError): + db.get_unit('d_shirts') + with pytest.raises(KeyError): + db.get_unit('duper_shirts') with pytest.raises(KeyError): db.get_unit('s_tails') with pytest.raises(KeyError): @@ -194,6 +230,9 @@ def test_add_derived_unit_with_prefixes() -> None: def test_add_derived_unit_without_prefixes() -> None: + """ + can add a derived unit that cannot be prefixed + """ db = UnitDatabase(auto_create_units=False) db.add_root_unit('shirts') @@ -226,6 +265,10 @@ def test_add_derived_unit_without_prefixes() -> None: def test_kilogram_special_case() -> None: + """ + kilogram example + unusual in that the base unit has a prefix + """ db = UnitDatabase(auto_create_units=False) db.add_base_unit_data(BaseUnitData('kg', 'kilogram'), SI_PREFIXES) assert db.get_unit('g').base_units == raw_UnitArray([('kg', 1, 1)]) @@ -234,6 +277,11 @@ def test_kilogram_special_case() -> None: def test_parse_unit_formula() -> None: + """ + with custom root units + usual dimensional arithmetic with those units holds + when not using any float numerical factors + """ db = UnitDatabase(auto_create_units=False) db.add_root_unit('cats') db.add_root_unit('dogs') @@ -256,6 +304,10 @@ def test_parse_unit_formula() -> None: def test_parse_float_formula() -> None: + """ + with custom root units + usual dimensional arithmetic with those units and floats holds + """ db = UnitDatabase(auto_create_units=False) db.add_root_unit('J') db.add_root_unit('s') @@ -268,6 +320,9 @@ def test_parse_float_formula() -> None: def test_is_consistent_with_database() -> None: + """ + are conversions consistent with what is known in db + """ db = UnitDatabase(auto_create_units=True) # Empty. @@ -282,6 +337,7 @@ def test_is_consistent_with_database() -> None: # Self-contradictory conversion. assert not db.is_value_consistent_with_database(val(6, conv=conv(3), units=unit('theorems'))) + assert db.is_value_consistent_with_database(val(6, conv=conv(1), units=unit('theorems'))) # Inconsistent conversion. db.add_scaled_unit('kilo_theorems', 'theorems', exp10=3) diff --git a/test/test_value.py b/test/test_value.py index 84f73df..df5fbc1 100644 --- a/test/test_value.py +++ b/test/test_value.py @@ -21,6 +21,9 @@ def test_construction() -> None: + """ + can construct values with or without dimensions + """ x = 2 * Value(1, '') y = Value(5, 'ns') assert isinstance(x, Value) @@ -40,6 +43,9 @@ def test_dimensionless() -> None: def test_addition() -> None: + """ + addition and subtraction only make sense when units can be made to match + """ from tunits.units import kilometer n = Value(2, '') @@ -60,6 +66,11 @@ def test_addition() -> None: def test_multiplication() -> None: + """ + multiplication/division makes sense regardless + the units become the product/quotient unit + cancellation to give something dimensionless where relevant + """ from tunits.units import meter, mm, second x = Value(1.0 + 2j, meter) @@ -70,6 +81,9 @@ def test_multiplication() -> None: def test_power() -> None: + """ + can raise to numeric powers + """ from tunits.units import km, m, minute, s, um, mm _ = mm * np.complex128(3) @@ -88,6 +102,9 @@ def test_power() -> None: def test_repr() -> None: + """ + the repr for a Value has the numeric value and the unit information + """ from tunits.units import km, kg, mm assert repr(Value(1, mm)) == "Value(1, 'mm')" @@ -96,6 +113,10 @@ def test_repr() -> None: def test_str() -> None: + """ + the displayable str uses the + appropriate string representation for the units + """ from tunits.units import mm, meter, kilometer, rad, cyc assert str(Value(1, mm)) == 'mm' @@ -108,7 +129,13 @@ def test_str() -> None: def test_div_mod() -> None: - from tunits.units import us, ns + """ + when the operands match units + truncated division and remainder + the division is like normal division with dimensionless answer + the remainder retains the unit + """ + from tunits.units import us, ns, m x = 4.0009765625 * us assert x // (4 * ns) == 1000 @@ -116,9 +143,16 @@ def test_div_mod() -> None: q, r = divmod(x, 2 * ns) assert q == 2000 assert r == x - 4 * us + with pytest.raises(UnitMismatchError): + _ = x // (4 * m) def test_conversion() -> None: + """ + can convert Values to other units with dictionary syntax + or explicit in_units_of + if the desired units of does not match, then a UnitMismatchError + """ from tunits.units import mm x = Value(3, 'm') @@ -132,10 +166,18 @@ def test_conversion() -> None: def test_parsing_by_comparison() -> None: + """ + with potential conversion factors required + we can compare two values with matching dimensional analysis + with inequalities and equalities + UnitMismatchError if they are not + """ assert Value(1, 'in') < Value(1, 'm') assert Value(1, 'cm') < Value(1, 'in') assert Value(1, 'gauss') < Value(1, 'mT') assert Value(1, 'minute') < Value(100, 's') + with pytest.raises(UnitMismatchError): + _ = Value(1, 'minute') < Value(100, 'm') assert Value(10, 'hertz') == Value(10, 'Hz') assert Value(10, 'Mg') == Value(10000, 'kg') @@ -147,6 +189,9 @@ def test_parsing_by_comparison() -> None: def test_radians_vs_steradians() -> None: + """ + specific equalities for solid angle measurements + """ assert Value(1, 'rad') != Value(1, 'sr') assert Value(2, 'rad') ** 2 == Value(4, 'sr') assert Value(16, 'rad') == Value(256, 'sr') ** 0.5 @@ -155,6 +200,15 @@ def test_radians_vs_steradians() -> None: def test_division() -> None: + """ + division makes sense regardless of units + the units become the quotient unit + cancellation to give something dimensionless where relevant + when the operands match units + can also perform truncated division and remainder + the division is like normal division with dimensionless answer + the remainder retains the unit + """ from tunits.units import km, s, m assert 5 * km / (2 * s) == Value(2500, 'm/s') @@ -168,13 +222,22 @@ def test_division() -> None: assert (5 * km) // (64 * m) == 78 assert (5 * km).__truediv__(64 * m) == 78.125 assert (5 * km).__floordiv__(64 * m) == 78 + assert (5 * km).__mod__(64 * m) == 8 * m def test_get_item() -> None: + """ + in certain cases can use dictionary syntax for conversion + """ from tunits.units import ns, s with pytest.raises(TypeError): _ = (ns / s)[2 * s / ns] + two_thousand_unitless = Value(2, "s / ns") + with pytest.raises(TypeError): + _ = (ns / s)[two_thousand_unitless] + one_thousandth_unitless = Value(1, "ns / s") + assert (ns / s)[one_thousandth_unitless] == 1 with pytest.raises(TypeError): _ = (ns / s)[Value(3, '')] assert Value(1, '')[Value(1, '')] == 1 @@ -182,6 +245,9 @@ def test_get_item() -> None: def test_cycles() -> None: + """ + explicit angle conversions + """ from tunits.units import cyc, rad assert np.isclose((3.14159265 * rad)[cyc], 0.5) @@ -197,6 +263,12 @@ def test_decibels_vs_decibel_milliwatts() -> None: def test_hash() -> None: + """ + hash does not depend on how the value is presented + they can be equal but presented differently because of conversion + and still give the same hash + dimensionless quantities behave just like underlying numbers + """ x = Value(3, 'ks') y = Value(3000, 's') assert hash(x) == hash(y) @@ -206,11 +278,17 @@ def test_hash() -> None: def test_numpy_sqrt() -> None: + """ + like in test_power, can raise to the power 1/2 + but this time with np.sqrt instead of ** 0.5 + """ from tunits.units import m, km, cm u = np.sqrt(8 * km * m) - cm v = 8943.27191 * cm assert np.isclose(u / v, 1) + u_prime = (8 * km * m) ** 0.5 - cm + assert np.isclose(u / u_prime, 1) u = np.sqrt(8 * km / m) assert np.isclose(u, 89.4427191) diff --git a/test/test_value_array.py b/test/test_value_array.py index bcf7dcd..2593a58 100644 --- a/test/test_value_array.py +++ b/test/test_value_array.py @@ -20,6 +20,9 @@ def test_construction() -> None: + """ + ValueArray construction from unit and numerical array + """ from tunits.units import ns, ps assert isinstance(ns * [1, 2, 3], ValueArray) @@ -27,6 +30,9 @@ def test_construction() -> None: def test_slicing() -> None: + """ + ValueArray can be sliced + """ from tunits.units import ms, ns assert np.array_equal((ms * [0, 1, 2, 3, 4])[3:], ms * [3, 4]) @@ -34,6 +40,10 @@ def test_slicing() -> None: def test_set_item() -> None: + """ + ValueArray makes sure all the entries match dimensions + but can set with different units that have the correct dimensional analysis + """ from tunits.units import km, m, s v = m * [1, 2, 3] @@ -48,6 +58,10 @@ def test_set_item() -> None: def test_addition() -> None: + """ + entrywise addition + addition requiring matching + """ from tunits.units import km, m assert np.array_equal(km * [1, 2, 3] + m * [2, 3, 5], m * [1002, 2003, 3005]) @@ -59,6 +73,10 @@ def test_addition() -> None: def test_multiplication() -> None: + """ + entrywise addition + units multiply with dimensional arithmetic + """ from tunits.units import km, m assert np.array_equal((km * [1, 2, 3]) * (m * [2, 3, 5]), (km * m) * [2, 6, 15]) @@ -77,12 +95,20 @@ def test_multiplication() -> None: def test_power() -> None: + """ + entrywise power + units go to appropriate powers thereof + """ from tunits.units import s assert np.array_equal((s * [1, 2, 3]) ** 2, s * s * [1, 4, 9]) def test_repr() -> None: + """ + repr of ValueArray has the underlying numeric array + and the string for the common unit for all the entries + """ from tunits.units import km, kg, s assert repr(s * []) == "TimeArray(array([], dtype=float64), 's')" @@ -123,6 +149,9 @@ def test_repr() -> None: def test_str() -> None: + """ + contrast repr above with displayed str + """ from tunits.units import mm assert str(mm**3 * []) == '[] mm^3' @@ -130,6 +159,9 @@ def test_str() -> None: def test_array_dtype() -> None: + """ + depending on dtype, complex arithmetic may or may not be allowed + """ from tunits.units import dekahertz, s a = np.array(s * [1, 2, 3] * dekahertz, dtype=complex) @@ -146,6 +178,10 @@ def test_array_dtype() -> None: def test_multi_index() -> None: + """ + multidimensional arrays + indexing and slicing + """ from tunits.units import m assert (m * [[2, 3], [4, 5], [6, 7]])[0, 0] == m * 2 @@ -157,6 +193,10 @@ def test_multi_index() -> None: def test_predicate_index() -> None: + """ + when using a predicate index + the relevant entries are selected and flattened + """ from tunits.units import m v = m * [[2, 3, 4], [5, 6, 7], [8, 9, 10]] @@ -164,6 +204,9 @@ def test_predicate_index() -> None: def test_extract_unit() -> None: + """ + can pull the common unit out as a common factor + """ from tunits.units import m # Singleton. @@ -183,6 +226,10 @@ def test_extract_unit() -> None: def test_numpy_kron() -> None: + """ + kronecker product on the arrays multiplies all the values + as well as having the product unit as the common unit for all entries + """ from tunits.units import km, ns u = km * [2, 3, 5]