diff --git a/sdRDM/base/datatypes/test_quantity.ipynb b/sdRDM/base/datatypes/test_quantity.ipynb index 70f4dec..b1313a1 100644 --- a/sdRDM/base/datatypes/test_quantity.ipynb +++ b/sdRDM/base/datatypes/test_quantity.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 3, + "execution_count": 49, "metadata": {}, "outputs": [], "source": [ @@ -11,7 +11,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 50, "metadata": {}, "outputs": [], "source": [ @@ -28,10 +28,10 @@ "output_type": "stream", "text": [ "\u001b[4mUnitStuff\u001b[0m\n", - "├── \u001b[94mid\u001b[0m = 1b363563-32df-41b2-a4ae-4335cb0f9f6a\n", + "├── \u001b[94mid\u001b[0m = 8fa1134e-a6e4-41fe-8442-022cdee6e6f2\n", "└── \u001b[94monly_unit\u001b[0m\n", " └── \u001b[4mUnit\u001b[0m\n", - " ├── \u001b[94mid\u001b[0m = c996f871-1563-4f3e-a32a-ff01669bf504\n", + " ├── \u001b[94mid\u001b[0m = 8434a947-65a6-4b23-bdb5-7fda5656051e\n", " ├── \u001b[94mname\u001b[0m = mmol / l\n", " └── \u001b[94mbases\u001b[0m\n", " ├── 0\n", @@ -55,7 +55,7 @@ }, { "cell_type": "code", - "execution_count": 102, + "execution_count": 52, "metadata": {}, "outputs": [], "source": [ @@ -66,7 +66,7 @@ }, { "cell_type": "code", - "execution_count": 103, + "execution_count": 53, "metadata": {}, "outputs": [], "source": [ @@ -76,16 +76,16 @@ }, { "cell_type": "code", - "execution_count": 118, + "execution_count": 54, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Quantity(id='e3f7c36b-0e73-43af-9f98-f3f36749bca4', value=0.5, unit=Unit(id='8914dfab-52ef-48aa-9abc-a9bf520f8035', name='mmol / l', bases=[BaseUnit(scale=0.001, kind=Unit(\"mol\"), exponent=1.0), BaseUnit(scale=1.0, kind=Unit(\"l\"), exponent=-1.0)]))" + "Quantity(id='a476d64d-ac25-4285-a4bc-06ed90087944', value=0.5, unit=Unit(id='2dbbfb17-98cf-41bb-85a1-eab68d4ae608', name='mmol / l', bases=[BaseUnit(scale=0.001, kind=Unit(\"mol\"), exponent=1.0), BaseUnit(scale=1.0, kind=Unit(\"l\"), exponent=-1.0)]))" ] }, - "execution_count": 118, + "execution_count": 54, "metadata": {}, "output_type": "execute_result" } @@ -97,16 +97,16 @@ }, { "cell_type": "code", - "execution_count": 119, + "execution_count": 55, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Quantity(id='fc034cf1-e868-4de7-85e2-4359a4d78a8e', value=4.0, unit=Unit(id='699e2af1-865b-4351-9634-a8ead9e15e22', name='l', bases=[BaseUnit(scale=1.0, kind=Unit(\"l\"), exponent=1.0)]))" + "Quantity(id='0170149a-ae7a-48a5-8cff-100c4e863497', value=4, unit=Unit(id='877589fc-26c3-4337-8e84-44fa142f3171', name='l', bases=[BaseUnit(scale=1.0, kind=Unit(\"l\"), exponent=1.0)]))" ] }, - "execution_count": 119, + "execution_count": 55, "metadata": {}, "output_type": "execute_result" } @@ -117,16 +117,16 @@ }, { "cell_type": "code", - "execution_count": 121, + "execution_count": 56, "metadata": {}, "outputs": [ { "data": { "text/plain": [ - "Quantity(id='3dab6ffb-04e6-4131-a2d6-00ca32181d19', value=5.0, unit=Unit(id='6fda2341-d0b5-4991-a93c-d0d4850be84e', name='mmol', bases=[BaseUnit(scale=0.001, kind=Unit(\"mol\"), exponent=1.0)]))" + "Quantity(id='5ee41bd8-a9f1-4f68-80f8-211e68ea2f1d', value=5.0, unit=Unit(id='9b126978-bc26-4db3-b38d-ce73d7cdf72e', name='mmol', bases=[BaseUnit(scale=0.001, kind=Unit(\"mol\"), exponent=1.0)]))" ] }, - "execution_count": 121, + "execution_count": 56, "metadata": {}, "output_type": "execute_result" } @@ -134,6 +134,127 @@ "source": [ "ammount + 3" ] + }, + { + "cell_type": "code", + "execution_count": 57, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[4mQuantity\u001b[0m\n", + "├── \u001b[94mid\u001b[0m = 36f9202f-1825-4d76-b48e-814d687bd440\n", + "├── \u001b[94mvalue\u001b[0m = 2.5\n", + "└── \u001b[94munit\u001b[0m\n", + " └── \u001b[4mUnit\u001b[0m\n", + " ├── \u001b[94mid\u001b[0m = a3f6a2a4-46d6-441e-b4be-b84aa792b8e4\n", + " ├── \u001b[94mname\u001b[0m = mmol / l\n", + " └── \u001b[94mbases\u001b[0m\n", + " ├── 0\n", + " │ └── \u001b[4mBaseUnit\u001b[0m\n", + " │ ├── \u001b[94mscale\u001b[0m = 0.001\n", + " │ ├── \u001b[94mkind\u001b[0m = mol\n", + " │ └── \u001b[94mexponent\u001b[0m = 1.0\n", + " └── 1\n", + " └── \u001b[4mBaseUnit\u001b[0m\n", + " ├── \u001b[94mscale\u001b[0m = 1.0\n", + " ├── \u001b[94mkind\u001b[0m = l\n", + " └── \u001b[94mexponent\u001b[0m = -1.0\n", + "\n" + ] + } + ], + "source": [ + "print(conc * 5)" + ] + }, + { + "cell_type": "code", + "execution_count": 58, + "metadata": {}, + "outputs": [ + { + "data": { + "text/latex": [ + "$\\mathrm{\\frac{mmol}{\\mathcal{l}}}$" + ], + "text/plain": [ + "Unit(\"mmol / l\")" + ] + }, + "execution_count": 58, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "conc.unit._unit" + ] + }, + { + "cell_type": "code", + "execution_count": 61, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 61, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Quantity(value=1, unit=\"mm\") == Quantity(value=1, unit=\"mm\")" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 68, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "q1 = Quantity(value=1, unit=\"mm\")\n", + "q2 = Quantity(value=1, unit=\"mm\")\n", + "\n", + "q1.__eq__(q2) is True" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "False" + ] + }, + "execution_count": 73, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "Quantity(value=1000, unit=\"mm\") == \"abx\"" + ] } ], "metadata": { diff --git a/tests/unit/datatypes/__init__.py b/tests/unit/datatypes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/unit/datatypes/test_quantity.py b/tests/unit/datatypes/test_quantity.py new file mode 100644 index 0000000..92f2632 --- /dev/null +++ b/tests/unit/datatypes/test_quantity.py @@ -0,0 +1,376 @@ +from pydantic import ValidationError +import pytest + +from sdRDM.base.datatypes.quantity import Quantity +from astropy.units import Unit as AstroUnit +from astropy.units import Quantity as AstroQuantity + + +class TestQuantity: + + # Creating a Quantity object with valid value and unit should set the value and unit attributes correctly. + @pytest.mark.unit + def test_valid_value_and_unit(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + + assert quantity.value == value + assert quantity.unit.name == unit + + # The __mul__ method should correctly multiply two Quantity objects and return a new Quantity object with the correct value and unit. + @pytest.mark.unit + def test_multiply_two_quantity_objects(self): + value1 = 10 + unit1 = "m" + quantity1 = Quantity(value1, unit1) + + value2 = 5 + unit2 = "s" + quantity2 = Quantity(value2, unit2) + + result = quantity1 * quantity2 + + assert result.value == value1 * value2 + assert result.unit._unit == AstroUnit(unit1) * AstroUnit(unit2) + + # The __mul__ method should correctly multiply a Quantity object with a scalar and return a new Quantity object with the correct value and unit. + @pytest.mark.unit + def test_multiply_quantity_with_scalar(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + + scalar = 5 + + result = quantity * scalar + + assert result.value == value * scalar + assert result.unit.name == unit + + # Creating a Quantity object with a non-numeric value should raise a validation error. + @pytest.mark.unit + def test_non_numeric_value_validation_error(self): + value = "invalid" + unit = "m" + + with pytest.raises(ValidationError): + Quantity(value, unit) + + # Creating a Quantity object with an invalid unit should raise a validation error. + @pytest.mark.unit + def test_invalid_unit_validation_error(self): + value = 10 + unit = "invalid" + + with pytest.raises(ValueError): + Quantity(value, unit) + + # The __mul__ method should raise a TypeError if the argument is not a Quantity object or a scalar. + @pytest.mark.unit + def test_multiply_invalid_argument_type_error(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + + invalid_argument = "invalid" + + with pytest.raises(TypeError): + quantity * invalid_argument + + @pytest.mark.unit + def test_divide_invlid_type_error(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + + invalid_argument = "invalid" + + with pytest.raises(TypeError): + quantity / invalid_argument + + @pytest.mark.unit + def test_add_invlid_type_error(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + + invalid_argument = "invalid" + + with pytest.raises(TypeError): + quantity + invalid_argument + + @pytest.mark.unit + def test_subtract_invlid_type_error(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + + invalid_argument = "invalid" + + with pytest.raises(TypeError): + quantity - invalid_argument + + def test_divide_two_quantity_objects(self): + # Create two Quantity objects + q1 = Quantity(value=10, unit="m") + q2 = Quantity(value=2, unit="s") + + # Divide q1 by q2 + result = q1 / q2 + + # Check the value and unit of the result + assert result.value == 5 + assert result.unit._unit == AstroUnit("m") / AstroUnit("s") + + def test_divide_quantity_by_scalar(self): + # Create a Quantity object + quantity = Quantity(value=10, unit="m") + + # Divide the quantity by a scalar + result = quantity.__truediv__(2) + + # Check that the result is a Quantity object + assert isinstance(result, Quantity) + + # Check that the value and unit of the result are correct + assert result.value == 5 + assert result.unit.bases == quantity.unit.bases + + # The __add__ method should correctly add two Quantity objects with the same unit and return a new Quantity object with the correct value and unit. + def test_add_method_with_same_unit(self): + # Create two Quantity objects with the same unit + q1 = Quantity(value=2, unit="m") + q2 = Quantity(value=3, unit="m") + + # Add the two Quantity objects + result = q1 + q2 + + # Check that the result is a new Quantity object with the correct value and unit + assert isinstance(result, Quantity) + assert result.value == 5 + assert result.unit.bases == q1.unit.bases + + def test_add_scalar_to_quantity(self): + # Create a Quantity object + quantity = Quantity(value=10, unit="m") + + # Add a scalar to the Quantity object + result = quantity + 5 + + # Check that the result is a Quantity object + assert isinstance(result, Quantity) + + # Check that the value and unit of the result are correct + assert result.value == 15 + assert result.unit.bases == quantity.unit.bases + + def test_subtract_two_quantity_objects_with_same_unit(self): + # Create two Quantity objects with the same unit + q1 = Quantity(value=10, unit="m") + q2 = Quantity(value=5, unit="m") + + # Subtract q2 from q1 + result = q1 - q2 + + # Check that the result is a Quantity object + assert isinstance(result, Quantity) + + # Check that the value and unit of the result are correct + assert result.value == 5 + assert result.unit.bases == q1.unit.bases + + def test_subtract_scalar_from_quantity(self): + # Create a Quantity object + quantity = Quantity(value=10, unit="m") + + # Subtract a scalar from the Quantity object + result = quantity - 5 + + # Check that the result is a Quantity object + assert isinstance(result, Quantity) + + # Check that the value and unit of the result are correct + assert result.value == 5 + assert result.unit.bases == quantity.unit.bases + + # subtracting a quantity object from another quantity object with different units should raise a ValueError + @pytest.mark.unit + def test_subtract_quantity_objects_with_different_units(self): + value1 = 10 + unit1 = "m" + quantity1 = Quantity(value1, unit1) + + value2 = 5 + unit2 = "s" + quantity2 = Quantity(value2, unit2) + + with pytest.raises(TypeError): + quantity1 - quantity2 + + # adding a quantity object to another quantity object with different units should raise a ValueError + @pytest.mark.unit + def test_add_quantity_objects_with_different_units(self): + value1 = 10 + unit1 = "m" + quantity1 = Quantity(value1, unit1) + + value2 = 5 + unit2 = "s" + quantity2 = Quantity(value2, unit2) + + with pytest.raises(TypeError): + quantity1 + quantity2 + + # _set_quantity sets the _quantity attribute of Quantity instance with an AstroQuantity object created from the value and unit attributes + def test_set_quantity_sets_quantity_attribute(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + quantity._set_quantity() + assert isinstance(quantity._quantity, AstroQuantity) + + # test division by zero scalar + @pytest.mark.unit + def test_division_by_zero_scalar(self): + value = 10 + unit = "m" + quantity = Quantity(value, unit) + with pytest.raises(ZeroDivisionError): + quantity / 0 + + # test division by zero quantity + @pytest.mark.unit + def test_division_by_zero_quantity(self): + value1 = 10 + unit1 = "m" + quantity1 = Quantity(value1, unit1) + + value2 = 0 + unit2 = "s" + quantity2 = Quantity(value2, unit2) + + with pytest.raises(ZeroDivisionError): + quantity1 / quantity2 + + @pytest.mark.unit + def test_multiplication_with_different_units(self): + value1 = 10 + unit1 = "m" + quantity1 = Quantity(value1, unit1) + + value2 = 5 + unit2 = "s" + quantity2 = Quantity(value2, unit2) + + new_quantity = quantity1 * quantity2 + assert new_quantity.value == value1 * value2 + assert new_quantity.unit._unit == AstroUnit(unit1) * AstroUnit(unit2) + + # test division with different units + @pytest.mark.unit + def test_division_with_different_units(self): + value1 = 10 + unit1 = "mmol / l" + quantity1 = Quantity(value1, unit1) + + value2 = 5 + unit2 = "l" + quantity2 = Quantity(value2, unit2) + + new_quantity = quantity1 / quantity2 + assert new_quantity.value == value1 / value2 + assert new_quantity.unit._unit == AstroUnit(unit1) / AstroUnit(unit2) + + # test raise error bitwise operation + @pytest.mark.unit + def test_bitwise_operation_error(self): + value1 = 10 + unit1 = "m" + quantity1 = Quantity(value1, unit1) + + value2 = 5 + unit2 = "s" + quantity2 = Quantity(value2, unit2) + + with pytest.raises(TypeError): + quantity1 & quantity2 + + # test raise error module operation + @pytest.mark.unit + def test_module_operation_error(self): + value1 = 10 + unit1 = "m" + quantity1 = Quantity(value1, unit1) + + value2 = 5 + unit2 = "s" + quantity2 = Quantity(value2, unit2) + + with pytest.raises(TypeError): + quantity1 % quantity2 + + # Two Quantity objects with the same value and unit should be equal. + @pytest.mark.unit + def test_same_value_and_unit(self): + quantity1 = Quantity(value=10, unit="m") + quantity2 = Quantity(value=10, unit="m") + assert quantity1 == quantity2 + + @pytest.mark.unit + def test_compare_to_self(self): + quantity = Quantity(value=10, unit="m") + assert quantity == quantity + + @pytest.mark.unit + def test_compare_to_scalar_with_same_value(self): + quantity = Quantity(value=10, unit="m") + scalar = 10 + assert quantity == scalar + + @pytest.mark.unit + def test_compare_to_scalar_with_nan_value(self): + quantity = Quantity(value=float("nan"), unit="m") + scalar = float("nan") + assert quantity != scalar + + @pytest.mark.unit + def test_compare_to_scalar_with_infinity_value(self): + quantity = Quantity(value=float("inf"), unit="m") + scalar = float("inf") + assert quantity == scalar + + @pytest.mark.unit + def test_comparing_quantity_to_scalar_with_different_value_should_return_false( + self, + ): + quantity = Quantity(value=10, unit="m") + scalar = 5 + assert quantity != scalar + + @pytest.mark.unit + def test_comparing_quantity_to_none(self): + quantity = Quantity(value=10, unit="m") + with pytest.raises(TypeError): + quantity == None + + @pytest.mark.unit + def test_different_values(self): + quantity1 = Quantity(value=10, unit="m") + quantity2 = Quantity(value=20, unit="m") + assert quantity1.__eq__(quantity2) == False + + @pytest.mark.unit + def test_different_units(self): + quantity1 = Quantity(value=10, unit="m") + quantity2 = Quantity(value=10, unit="s") + assert quantity1.__eq__(quantity2) == False + + @pytest.mark.unit + def test_comparing_quantity_to_scalar_with_non_float_value_should_raise_type_error( + self, + ): + quantity = Quantity(value=10, unit="m") + scalar = "abc" + with pytest.raises(TypeError): + quantity.__eq__(scalar)