From 657ae95a3ff461029cb904abec25d49c86fb2f54 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Mon, 27 Nov 2023 22:45:42 -0300 Subject: [PATCH 01/15] ENH: set Function operation prioritary with ufunc. --- rocketpy/mathutils/function.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index e52fbb2fb..2dab353de 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -24,6 +24,9 @@ class Function: extrapolation, plotting and algebra. """ + # Arithmetic priority + __array_ufunc__ = None + def __init__( self, source, @@ -1837,7 +1840,7 @@ def __add__(self, other): return Function(lambda x: (self.get_value(x) + other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex)): + if isinstance(other, (float, int, complex, np.ndarray)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -1967,7 +1970,7 @@ def __mul__(self, other): return Function(lambda x: (self.get_value(x) * other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex)): + if isinstance(other, (float, int, complex, np.ndarray)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -2056,7 +2059,7 @@ def __truediv__(self, other): return Function(lambda x: (self.get_value_opt(x) / other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex)): + if isinstance(other, (float, int, complex, np.ndarray)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -2095,7 +2098,7 @@ def __rtruediv__(self, other): A Function object which gives the result of other(x)/self(x). """ # Check if Function object source is array and other is float - if isinstance(other, (float, int, complex)): + if isinstance(other, (float, int, complex, np.ndarray)): if isinstance(self.source, np.ndarray): # Operate on grid values ys = other / self.y_array @@ -2163,7 +2166,7 @@ def __pow__(self, other): return Function(lambda x: (self.get_value_opt(x) ** other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex)): + if isinstance(other, (float, int, complex, np.ndarray)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -2202,7 +2205,7 @@ def __rpow__(self, other): A Function object which gives the result of other(x)**self(x). """ # Check if Function object source is array and other is float - if isinstance(other, (float, int, complex)): + if isinstance(other, (float, int, complex, np.ndarray)): if isinstance(self.source, np.ndarray): # Operate on grid values ys = other**self.y_array From b82810ff9275d0170a9e3b92b90fa90351eec89b Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Mon, 27 Nov 2023 22:46:17 -0300 Subject: [PATCH 02/15] TST: test result type of reverse Function arithmetic. --- tests/test_function.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tests/test_function.py b/tests/test_function.py index c67a21b30..fdaf092e3 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -390,3 +390,19 @@ def test_shepard_interpolation(x, y, z_expected): func = Function(source=source, inputs=["x", "y"], outputs=["z"]) z = func(x, y) assert np.isclose(z, z_expected, atol=1e-8).all() + + +def test_arithmetic_priority(): + """Test the arithmetic priority of the Function class, specially + comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + array = np.array([1]) + + assert isinstance(func_lambda + func_array, Function) + assert isinstance(func_array + func_lambda, Function) + assert isinstance(func_lambda + array, Function) + assert isinstance(array + func_lambda, Function) + assert isinstance(func_array + array, Function) + assert isinstance(array + func_array, Function) From ac6f3b32d65887e77dedeeb57dc56b412195e548 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Tue, 28 Nov 2023 00:57:55 -0300 Subject: [PATCH 03/15] ENH: lowercase trigger comparison in parachute.py --- rocketpy/rocket/parachute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index 07c44e158..18a90c83d 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -184,7 +184,7 @@ def triggerfunc(p, h, y): self.triggerfunc = triggerfunc - elif trigger == "apogee": + elif trigger.lower() == "apogee": # trigger for apogee def triggerfunc(p, h, y): # p = pressure considering parachute noise signal From 990df21752473a8dc45955dfeea4a9139478fc21 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Tue, 28 Nov 2023 01:07:03 -0300 Subject: [PATCH 04/15] MNT: Refactor Parachute trigger function handling --- rocketpy/rocket/parachute.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index 18a90c83d..d50de92c4 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -171,11 +171,17 @@ def __init__( self.prints = _ParachutePrints(self) - # evaluate the trigger + self.__evaluate_trigger_function(trigger) + + def __evaluate_trigger_function(self, trigger): + """This is used to set the triggerfunc attribute that will be used to + interact with the Flight class. + """ if callable(trigger): self.triggerfunc = trigger + elif isinstance(trigger, (int, float)): - # trigger is interpreted as the absolute height at which the parachute will be ejected + # The parachute is deployed at a given height def triggerfunc(p, h, y): # p = pressure considering parachute noise signal # h = height above ground level considering parachute noise signal @@ -185,7 +191,7 @@ def triggerfunc(p, h, y): self.triggerfunc = triggerfunc elif trigger.lower() == "apogee": - # trigger for apogee + # The parachute is deployed at apogee def triggerfunc(p, h, y): # p = pressure considering parachute noise signal # h = height above ground level considering parachute noise signal @@ -194,7 +200,12 @@ def triggerfunc(p, h, y): self.triggerfunc = triggerfunc - return None + else: + raise ValueError( + f"Unable to set the trigger function for parachute '{self.name}'. " + + "Trigger must be a callable, a float value or the string 'apogee'. " + + "See the Parachute class documentation for mor information." + ) def __str__(self): """Returns a string representation of the Parachute class. From 83bcd02bca1790e4d4f9ca15ab938028d5e2df51 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Tue, 28 Nov 2023 01:09:30 -0300 Subject: [PATCH 05/15] MNT: update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 311189757..9bcb06322 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,7 +40,7 @@ straightforward as possible. ### Fixed -- +- ENH: Parachute trigger doesn't work if "Apogee" is used instead of "apogee" [#489](https://github.com/RocketPy-Team/RocketPy/pull/489) ## [v1.1.2] - 2023-11-25 From c1e43ece65f31c1811e37f48e52a6c353036d53d Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Tue, 28 Nov 2023 01:14:57 -0300 Subject: [PATCH 06/15] MNT: Fix typo in parachute trigger error message --- rocketpy/rocket/parachute.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index d50de92c4..0bfd9f9b9 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -204,7 +204,7 @@ def triggerfunc(p, h, y): raise ValueError( f"Unable to set the trigger function for parachute '{self.name}'. " + "Trigger must be a callable, a float value or the string 'apogee'. " - + "See the Parachute class documentation for mor information." + + "See the Parachute class documentation for more information." ) def __str__(self): From f4437d3c2b285165140262d69e8430e853c29d7d Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Tue, 28 Nov 2023 01:31:15 -0300 Subject: [PATCH 07/15] MNT: Add __repr__ method to Parachute class --- rocketpy/rocket/parachute.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index 0bfd9f9b9..8e3c06757 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -220,6 +220,13 @@ def __str__(self): self.cd_s, ) + def __repr__(self): + """Representation method for the class, useful when debugging.""" + return ( + f"'{self.name}' parachute " + + f"(cd_s = {self.cd_s:.4f} m2, trigger = {self.trigger})" + ) + def info(self): """Prints information about the Parachute class.""" self.prints.all() From 622439b4878882a27cb4f2c08448e200bf5634de Mon Sep 17 00:00:00 2001 From: Lucas <69172945+lucasfourier@users.noreply.github.com> Date: Tue, 28 Nov 2023 09:33:14 -0300 Subject: [PATCH 08/15] DOC: add tests to git style Adding tests (tst) to the branch naming conventions in the git style page. --- docs/development/style_guide.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/development/style_guide.rst b/docs/development/style_guide.rst index b77e4e06b..fd2f12365 100644 --- a/docs/development/style_guide.rst +++ b/docs/development/style_guide.rst @@ -69,6 +69,7 @@ So here are a couple of **guidelines** to help you when creating new branches to #. ``enh``: when you add new features and enhancements #. ``maint``: when your branch is all about refactoring, fixing typos, etc. #. ``rel``: when your branch makes changes related to creating new releases + #. ``tst``: when your branch makes changes related to tests * Use ``-`` instead of spaces for the description text. * Keep branch names with lowercase letters. @@ -80,6 +81,7 @@ Here are a couple of example branch names: - ``bug/issue-98-upside-down-rockets`` - ``enh/hybrid-motor-feature`` - ``maint/typos-flight-class`` +- ``tst/refactor-tests-flight-class`` Once you are ready to create a Pull Request for your branch, we advise you to merge with the ``develop`` branch instead of the default ``master`` branch. This way, we keep the ``master`` branch stable and use the ``develop`` branch to test out new features! From 63881c4df73c0a6029464eeb0eff63974e9cb750 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Tue, 28 Nov 2023 13:50:02 -0300 Subject: [PATCH 09/15] FIX: include numpy numeric types in Function arithmetic. --- rocketpy/mathutils/function.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/rocketpy/mathutils/function.py b/rocketpy/mathutils/function.py index 2dab353de..8bdad9d2b 100644 --- a/rocketpy/mathutils/function.py +++ b/rocketpy/mathutils/function.py @@ -1840,7 +1840,9 @@ def __add__(self, other): return Function(lambda x: (self.get_value(x) + other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex, np.ndarray)): + if isinstance( + other, (float, int, complex, np.ndarray, np.integer, np.floating) + ): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -1970,7 +1972,9 @@ def __mul__(self, other): return Function(lambda x: (self.get_value(x) * other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex, np.ndarray)): + if isinstance( + other, (float, int, complex, np.ndarray, np.integer, np.floating) + ): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -2059,7 +2063,9 @@ def __truediv__(self, other): return Function(lambda x: (self.get_value_opt(x) / other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex, np.ndarray)): + if isinstance( + other, (float, int, complex, np.ndarray, np.integer, np.floating) + ): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -2098,7 +2104,9 @@ def __rtruediv__(self, other): A Function object which gives the result of other(x)/self(x). """ # Check if Function object source is array and other is float - if isinstance(other, (float, int, complex, np.ndarray)): + if isinstance( + other, (float, int, complex, np.ndarray, np.integer, np.floating) + ): if isinstance(self.source, np.ndarray): # Operate on grid values ys = other / self.y_array @@ -2166,7 +2174,9 @@ def __pow__(self, other): return Function(lambda x: (self.get_value_opt(x) ** other(x))) # If other is Float except... except AttributeError: - if isinstance(other, (float, int, complex, np.ndarray)): + if isinstance( + other, (float, int, complex, np.ndarray, np.integer, np.floating) + ): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): # Operate on grid values @@ -2205,7 +2215,9 @@ def __rpow__(self, other): A Function object which gives the result of other(x)**self(x). """ # Check if Function object source is array and other is float - if isinstance(other, (float, int, complex, np.ndarray)): + if isinstance( + other, (float, int, complex, np.ndarray, np.integer, np.floating) + ): if isinstance(self.source, np.ndarray): # Operate on grid values ys = other**self.y_array From 9162921f9d4ffae580d2e48b002ff7b555d13612 Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Tue, 28 Nov 2023 13:54:08 -0300 Subject: [PATCH 10/15] TST: expand testing of data type priorities. --- tests/test_function.py | 80 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 8 deletions(-) diff --git a/tests/test_function.py b/tests/test_function.py index fdaf092e3..5362b0486 100644 --- a/tests/test_function.py +++ b/tests/test_function.py @@ -392,17 +392,81 @@ def test_shepard_interpolation(x, y, z_expected): assert np.isclose(z, z_expected, atol=1e-8).all() -def test_arithmetic_priority(): - """Test the arithmetic priority of the Function class, specially - comparing to the numpy array operations. +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float_(0.1), np.array([1])]) +def test_sum_arithmetic_priority(other): + """Test the arithmetic priority of the add operation of the Function class, + specially comparing to the numpy array operations. """ func_lambda = Function(lambda x: x**2) func_array = Function([(0, 0), (1, 1), (2, 4)]) - array = np.array([1]) assert isinstance(func_lambda + func_array, Function) assert isinstance(func_array + func_lambda, Function) - assert isinstance(func_lambda + array, Function) - assert isinstance(array + func_lambda, Function) - assert isinstance(func_array + array, Function) - assert isinstance(array + func_array, Function) + assert isinstance(func_lambda + other, Function) + assert isinstance(other + func_lambda, Function) + assert isinstance(func_array + other, Function) + assert isinstance(other + func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float_(0.1), np.array([1])]) +def test_sub_arithmetic_priority(other): + """Test the arithmetic priority of the sub operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + + assert isinstance(func_lambda - func_array, Function) + assert isinstance(func_array - func_lambda, Function) + assert isinstance(func_lambda - other, Function) + assert isinstance(other - func_lambda, Function) + assert isinstance(func_array - other, Function) + assert isinstance(other - func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float_(0.1), np.array([1])]) +def test_mul_arithmetic_priority(other): + """Test the arithmetic priority of the mul operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + + assert isinstance(func_lambda * func_array, Function) + assert isinstance(func_array * func_lambda, Function) + assert isinstance(func_lambda * other, Function) + assert isinstance(other * func_lambda, Function) + assert isinstance(func_array * other, Function) + assert isinstance(other * func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float_(0.1), np.array([1])]) +def test_truediv_arithmetic_priority(other): + """Test the arithmetic priority of the truediv operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(1, 1), (2, 4)]) + + assert isinstance(func_lambda / func_array, Function) + assert isinstance(func_array / func_lambda, Function) + assert isinstance(func_lambda / other, Function) + assert isinstance(other / func_lambda, Function) + assert isinstance(func_array / other, Function) + assert isinstance(other / func_array, Function) + + +@pytest.mark.parametrize("other", [1, 0.1, np.int_(1), np.float_(0.1), np.array([1])]) +def test_pow_arithmetic_priority(other): + """Test the arithmetic priority of the pow operation of the Function class, + specially comparing to the numpy array operations. + """ + func_lambda = Function(lambda x: x**2) + func_array = Function([(0, 0), (1, 1), (2, 4)]) + + assert isinstance(func_lambda**func_array, Function) + assert isinstance(func_array**func_lambda, Function) + assert isinstance(func_lambda**other, Function) + assert isinstance(other**func_lambda, Function) + assert isinstance(func_array**other, Function) + assert isinstance(other**func_array, Function) From beb22ab4de0d78ea998150d598413a940eceb0aa Mon Sep 17 00:00:00 2001 From: Pedro Bressan Date: Tue, 28 Nov 2023 14:00:57 -0300 Subject: [PATCH 11/15] MNT: update CHANGELOG. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 311189757..2abd6ed91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ straightforward as possible. ### Changed -- +- ENH: Function Reverse Arithmetic Priority [#488](https://github.com/RocketPy-Team/RocketPy/pull/488) ### Fixed From fcea30a8b16931fa57f81a9042bbb570bd97c943 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Wed, 29 Nov 2023 00:21:15 -0300 Subject: [PATCH 12/15] GIT: Set code owners as @RocketPy-Team/code-owners --- CODEOWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 177b566ec..912580dc7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,3 +1,4 @@ # These owners will be the default owners for everything in # the repo. -* @giovaniceotto +* @RocketPy-Team/code-owners + From a6dfea37e51e52d846e2a84d6d0502641041d80a Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 30 Nov 2023 02:22:45 -0300 Subject: [PATCH 13/15] MNT: Fix typo in parachute trigger string --- rocketpy/rocket/parachute.py | 4 ++-- rocketpy/rocket/rocket.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index 0bfd9f9b9..3abd66ac2 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -38,7 +38,7 @@ class Parachute: case, the parachute will be ejected when the rocket reaches this height above ground level. - - The string "apogee," which triggers the parachute at apogee, i.e., + - The string "apogee" which triggers the parachute at apogee, i.e., when the rocket reaches its highest point and starts descending. Note: The function will be called according to the sampling rate @@ -126,7 +126,7 @@ def __init__( case, the parachute will be ejected when the rocket reaches this height above ground level. - - The string "apogee," which triggers the parachute at apogee, i.e., + - The string "apogee" which triggers the parachute at apogee, i.e., when the rocket reaches its highest point and starts descending. Note: The function will be called according to the sampling rate diff --git a/rocketpy/rocket/rocket.py b/rocketpy/rocket/rocket.py index 947c82acd..62f61d99b 100644 --- a/rocketpy/rocket/rocket.py +++ b/rocketpy/rocket/rocket.py @@ -1092,7 +1092,7 @@ def add_parachute( case, the parachute will be ejected when the rocket reaches this height above ground level. - - The string "apogee," which triggers the parachute at apogee, i.e., + - The string "apogee" which triggers the parachute at apogee, i.e., when the rocket reaches its highest point and starts descending. Note: The function will be called according to the sampling rate From daa2dd66261df0db7cf4f649143a6e4fb84d0be9 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 30 Nov 2023 02:27:49 -0300 Subject: [PATCH 14/15] MNT: Update parachute __repr__ method 2 convention --- rocketpy/rocket/parachute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/rocket/parachute.py b/rocketpy/rocket/parachute.py index 8e3c06757..efc274985 100644 --- a/rocketpy/rocket/parachute.py +++ b/rocketpy/rocket/parachute.py @@ -223,8 +223,8 @@ def __str__(self): def __repr__(self): """Representation method for the class, useful when debugging.""" return ( - f"'{self.name}' parachute " - + f"(cd_s = {self.cd_s:.4f} m2, trigger = {self.trigger})" + f"" ) def info(self): From 72ca940be1f7107c7fbcfaa82a589855adda4039 Mon Sep 17 00:00:00 2001 From: Gui-FernandesBR Date: Thu, 30 Nov 2023 02:29:23 -0300 Subject: [PATCH 15/15] MNT: Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bcb06322..63109ce65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,7 +36,7 @@ straightforward as possible. ### Changed -- +- MNT: Add repr method to Parachute class [#490](https://github.com/RocketPy-Team/RocketPy/pull/490) ### Fixed