From 69ffc01cc0612f4203ed7c1a2ae84afef8d54181 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 14:38:24 -0300 Subject: [PATCH 01/15] ENH: implement setDiscreteBasedOnModel --- rocketpy/Function.py | 48 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 8602436ce..04de7b1aa 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -473,6 +473,54 @@ def setDiscrete( self.__interpolation__ = "shepard" return self + def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): + """This method transforms function defined Functions into list + defined Functions. It evaluates the function at certain points + (sampling range) and stores the results in a list, which is converted + into a Function and then returned. The original Function object is + replaced by the new one. + + Parameters + ---------- + modelFunction : Function + Function object that will be used to define the sampling points, + interpolation method and extrapolation method. + Must be a Function whose source attribute is a list (i.e. a list based + Function instance). + Must have the same domain dimension as the Function to be discretized. + + oneByOne : boolean, optional + If True, evaluate Function in each sample point separately. If + False, evaluates Function in vectorized form. Default is True. + + Returns + ------- + self : Function + """ + if not isinstance(modelFunction.source, np.ndarray): + raise TypeError("modelFunction must be a list based Function.") + if modelFunction.__domDim__ != self.__domDim__: + raise ValueError("modelFunction must have the same domain dimension.") + + if self.__domDim__ == 1: + Xs = modelFunction.source[:, 0] + Ys = self.getValue(Xs.tolist()) if oneByOne else self.getValue(Xs) + self.source = np.concatenate(([Xs], [Ys])).transpose() + elif self.__domDim__ == 2: + # Create nodes to evaluate function + Xs = modelFunction.source[:, 0] + Ys = modelFunction.source[:, 1] + Xs, Ys = np.meshgrid(Xs, Ys) + Xs, Ys = Xs.flatten(), Ys.flatten() + mesh = [[Xs[i], Ys[i]] for i in range(len(Xs))] + # Evaluate function at all mesh nodes and convert it to matrix + Zs = np.array(self.getValue(mesh)) + self.source = np.concatenate(([Xs], [Ys], [Zs])).transpose() + + self.setInterpolation(modelFunction.__interpolation__) + self.setExtrapolation(modelFunction.__extrapolation__) + return self + # Define all get methods def getInputs(self): "Return tuple of inputs of the function." From 64079637172171df397fc3a3389173243c80ec5e Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 14:38:50 -0300 Subject: [PATCH 02/15] ENH: implement Function.reset method --- rocketpy/Function.py | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 04de7b1aa..f92c8cfaf 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -521,6 +521,44 @@ def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): self.setExtrapolation(modelFunction.__extrapolation__) return self + def reset( + self, + inputs=None, + outputs=None, + interpolation=None, + extrapolation=None, + ): + """This method resets the Function object to its initial state, with the + possibility of resetting all of its initialization arguments besides the + source. + + Parameters + ---------- + inputs : string, sequence of strings, optional + List of input variable names. If None, the original inputs are kept. + outputs : string, sequence of strings, optional + List of output variable names. If None, the original outputs are kept. + interpolation : string, optional + Interpolation method to be used if source type is ndarray. + For 1-D functions, linear, polynomial, akima and spline is + supported. For N-D functions, only shepard is supported. + If None, the original interpolation method is kept. + extrapolation : string, optional + Extrapolation method to be used if source type is ndarray. + Options are 'natural', which keeps interpolation, 'constant', + which returns the value of the function at the edge of the interval, + and 'zero', which returns zero for all points outside of source + range. If None, the original extrapolation method is kept. + """ + if inputs is not None: + self.setInputs(inputs) + if outputs is not None: + self.setOutputs(outputs) + if interpolation is not None and interpolation != self.__interpolation__: + self.setInterpolation(interpolation) + if extrapolation is not None and extrapolation != self.__extrapolation__: + self.setExtrapolation(extrapolation) + # Define all get methods def getInputs(self): "Return tuple of inputs of the function." From a7b94a7c2e9f6e36f0489446f712381ef78d0d57 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 14:39:35 -0300 Subject: [PATCH 03/15] ENH: create funcify_method decorator factory --- rocketpy/Function.py | 98 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index f92c8cfaf..3987d3038 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -2116,3 +2116,101 @@ def differentiate(self, x, dx=1e-6): # h = (10)**-300 # z = x + h*1j # return self(z).imag/h + + +def funcify_method(*args, **kwargs): + """Decorator factory to wrap methods as Function objects and save them as cached + properties. + + Parameters + ---------- + *args : list + Positional arguments to be passed to rocketpy.Function. + **kwargs : dict + Keyword arguments to be passed to rocketpy.Function. + + Returns + ------- + decorator : function + Decorator function to wrap callables as Function objects. + + Examples + -------- + >>> class Test(): + ... @Funcify(inputs=['x'], outputs=['y']) + ... def func(x): + ... return x**2 + >>> t = Test() + ... t.func + Function from R1 to R1 : (x) → (y) + >>> g = 2*t.func + 3 + >>> g(2) + 11 + + Can also be used without any arguments: + + >>> class Test(): + ... @Funcify + ... def func(x): + ... return x**2 + >>> t = Test() + ... t.func + Function from R1 to R1 : (Scalar) → (Scalar) + + Can also be used when the method already returns a Function instance. In such case + it is interesting to use the `inputs` and `outputs` arguments to overwrite the + inputs and outputs of the method. + + >>> class Test(): + ... @Funcify(inputs='x', outputs='y') + ... def func(x): + ... return Function(lambda x: x**2) + >>> t = Test() + ... t.func + Function from R1 to R1 : (x) → (y) + + In order to reset the cache, just delete de attribute from the instance: + + >>> del t.func + + Once it is requested again, it will be re-created as a new Function object: + + >>> t.func + Function from R1 to R1 : (Scalar) → (Scalar) + """ + func = None + if len(args) == 1 and callable(args[0]): + func = args[0] + args = [] + + class funcify_method_decorator: + def __init__(self, func): + self.func = func + self.attrname = None + self.__doc__ = func.__doc__ + + def __set_name__(self, owner, name): + self.attrname = name + + def __get__(self, instance, owner=None): + if instance is None: + return self + cache = instance.__dict__ + try: + val = cache[self.attrname] + except KeyError: + source = self.func(instance) + if isinstance(source, Function): + # Avoid creating a new Function object if the source is already one + val = source + val.reset(*args, **kwargs) + else: + val = Function(source, *args, **kwargs) + val.__doc__ = self.__doc__ + cache[self.attrname] = val + return val + + if func: + return funcify_method_decorator(func) + else: + return funcify_method_decorator From 8bfd7973b6b846461309d7b334351dcfe560e195 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 14:40:31 -0300 Subject: [PATCH 04/15] MAINT: fix except blocks without defined exceptions --- rocketpy/Function.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 3987d3038..8f35437c4 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -1556,7 +1556,7 @@ def __truediv__(self, other): else: return Function(lambda x: (self.getValueOpt2(x) / other(x))) # If other is Float except... - except: + except AttributeError: if isinstance(other, (float, int, complex)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): @@ -1658,7 +1658,7 @@ def __pow__(self, other): else: return Function(lambda x: (self.getValueOpt2(x) ** other(x))) # If other is Float except... - except: + except AttributeError: if isinstance(other, (float, int, complex)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): @@ -1760,7 +1760,7 @@ def __mul__(self, other): else: return Function(lambda x: (self.getValue(x) * other(x))) # If other is Float except... - except: + except AttributeError: if isinstance(other, (float, int, complex)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): @@ -1862,7 +1862,7 @@ def __add__(self, other): else: return Function(lambda x: (self.getValue(x) + other(x))) # If other is Float except... - except: + except AttributeError: if isinstance(other, (float, int, complex)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): @@ -1964,7 +1964,7 @@ def __sub__(self, other): else: return Function(lambda x: (self.getValue(x) * other(x))) # If other is Float except... - except: + except AttributeError: if isinstance(other, (float, int, complex)): # Check if Function object source is array or callable if isinstance(self.source, np.ndarray): From 61a5a12765c3e8459bdb4ee647ba717730685b34 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 14:42:11 -0300 Subject: [PATCH 05/15] MAINT: avoid NaNs in Function.__truediv__ --- rocketpy/Function.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 8f35437c4..11dab1190 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -1511,7 +1511,7 @@ def __interpolateAkima__(self): # Define all possible algebraic operations def __truediv__(self, other): - """Devides a Function object and returns a new Function object + """Divides a Function object and returns a new Function object which gives the result of the division. Only implemented for 1D domains. @@ -1543,7 +1543,9 @@ def __truediv__(self, other): and np.any(self.source[:, 0] - other.source[:, 0]) == False ): # Operate on grid values - Ys = self.source[:, 1] / other.source[:, 1] + with np.errstate(divide="ignore"): + Ys = self.source[:, 1] / other.source[:, 1] + Ys = np.nan_to_num(Ys) Xs = self.source[:, 0] source = np.concatenate(([Xs], [Ys])).transpose() # Retrieve inputs, outputs and interpolation From 42fb8b19666158b85bea9ce8c51b652706678d98 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 23:17:12 -0300 Subject: [PATCH 06/15] DOC: improve doc strings for Function.reset method --- rocketpy/Function.py | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 11dab1190..d31038571 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -528,27 +528,45 @@ def reset( interpolation=None, extrapolation=None, ): - """This method resets the Function object to its initial state, with the - possibility of resetting all of its initialization arguments besides the - source. + """This method allows the user to reset the inputs, outputs, interpolation + and extrapolation settings of a Function object, all at once, without + having to call each of the corresponding methods. Parameters ---------- inputs : string, sequence of strings, optional List of input variable names. If None, the original inputs are kept. + See Function.setInputs for more information. outputs : string, sequence of strings, optional List of output variable names. If None, the original outputs are kept. + See Function.setOutputs for more information. interpolation : string, optional Interpolation method to be used if source type is ndarray. - For 1-D functions, linear, polynomial, akima and spline is - supported. For N-D functions, only shepard is supported. - If None, the original interpolation method is kept. + See Function.setInterpolation for more information. extrapolation : string, optional Extrapolation method to be used if source type is ndarray. - Options are 'natural', which keeps interpolation, 'constant', - which returns the value of the function at the edge of the interval, - and 'zero', which returns zero for all points outside of source - range. If None, the original extrapolation method is kept. + See Function.setExtrapolation for more information. + + Examples + -------- + A simple use case is to reset the inputs and outputs of a Function object + that has been defined by algebraic manipulation of other Function objects. + + >>> from rocketpy import Function + >>> v = Function(lambda t: t**2, inputs='t', outputs='v') + >>> mass = 10 # Mass + >>> kinetic_energy = mass * v**2 / 2 + >>> v.getInputs(), v.getOutputs() + (['t'], ['v']) + >>> kinetic_energy.getInputs(), kinetic_energy.getOutputs() + (['x'], ['Scalar']) + >>> kinetic_energy.reset(inputs='t', outputs='Kinetic Energy') + >>> kinetic_energy.getInputs(), kinetic_energy.getOutputs() + (['t'], ['Kinetic Energy']) + + Returns + ------- + self : Function """ if inputs is not None: self.setInputs(inputs) @@ -559,6 +577,8 @@ def reset( if extrapolation is not None and extrapolation != self.__extrapolation__: self.setExtrapolation(extrapolation) + return self + # Define all get methods def getInputs(self): "Return tuple of inputs of the function." From f8ed237d7717d3a2d096cd80eae74a3803a0deb1 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 23:45:13 -0300 Subject: [PATCH 07/15] DOC: improve Function.setDiscreteBasedOnModel doc strings with notes and example --- rocketpy/Function.py | 58 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index d31038571..0f012374f 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -474,11 +474,13 @@ def setDiscrete( return self def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): - """This method transforms function defined Functions into list - defined Functions. It evaluates the function at certain points - (sampling range) and stores the results in a list, which is converted - into a Function and then returned. The original Function object is - replaced by the new one. + """This method transform a Function instance defined from callables into a + Function instance defined by a list of discrete points. + It does so based on a model Function, from which it retrieves the domain, + domain name, interpolation method and extrapolation method. + It then evaluates the original Function instance in all points of the retrieved + domain to generate the list of discrete points that will be used for + interpolation when this Function is called. Parameters ---------- @@ -496,6 +498,52 @@ def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): Returns ------- self : Function + + See also + -------- + Function.setDiscrete + + Examples + -------- + This method is particularly useful when algebraic operations is carried out + using Function instances defined by different discretized domains (same range, + but different mesh size). Once an algebraic operation is done, it will not + directly be applied between the list of discrete points of the two Function + instances. Instead, the result will be a Function instance defined by a callable + that calls both Function instances and performs the operation. This makes the + evaluation of the resulting Function inefficient, due to extra function calling + overhead and multiple interpolations being carried out. + + >>> from rocketpy import Function + >>> f = Function([(0, 0), (1, 1), (2, 4), (3, 9), (4, 16)]) + >>> g = Function([(0, 0), (2, 2), (4, 4)]) + >>> h = f * g + >>> h.source + .(x)> + + Therefore, it is good practice to make sure both Function instances are defined + by the same domain, i.e. by the same list of mesh points. This way, the + algebraic operation will be carried out directly between the lists of discrete + points, generating a new Function instance defined by this result. When it is + evaluated, there are no extra function calling overheads neither multiple + interpolations. + + >>> g.setDiscreteBasedOnModel(f) + >>> h = f * g + >>> h.source + array([[ 0., 0.], + [ 1., 1.], + [ 2., 8.], + [ 3., 27.], + [ 4., 64.]]) + + Notes + ----- + 1. This method performs in place replacement of the original Function object + source. + + 2. This method is similar to setDiscrete, but it uses the domain of a model + Function to define the domain of the new Function instance. """ if not isinstance(modelFunction.source, np.ndarray): raise TypeError("modelFunction must be a list based Function.") From 24178686ed578e06ffc3c500fbfed47b1d66dc5c Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Sun, 13 Nov 2022 23:46:15 -0300 Subject: [PATCH 08/15] MAINT: replace same domain check with np.array_equal --- rocketpy/Function.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 0f012374f..3aaa2d52d 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -1608,7 +1608,7 @@ def __truediv__(self, other): and isinstance(self.source, np.ndarray) and self.__interpolation__ == other.__interpolation__ and self.__inputs__ == other.__inputs__ - and np.any(self.source[:, 0] - other.source[:, 0]) == False + and np.array_equal(self.source[:, 0], other.source[:, 0]) ): # Operate on grid values with np.errstate(divide="ignore"): @@ -1712,7 +1712,7 @@ def __pow__(self, other): and isinstance(self.source, np.ndarray) and self.__interpolation__ == other.__interpolation__ and self.__inputs__ == other.__inputs__ - and np.any(self.source[:, 0] - other.source[:, 0]) == False + and np.array_equal(self.source[:, 0], other.source[:, 0]) ): # Operate on grid values Ys = self.source[:, 1] ** other.source[:, 1] @@ -1814,7 +1814,7 @@ def __mul__(self, other): and isinstance(self.source, np.ndarray) and self.__interpolation__ == other.__interpolation__ and self.__inputs__ == other.__inputs__ - and np.any(self.source[:, 0] - other.source[:, 0]) == False + and np.array_equal(self.source[:, 0], other.source[:, 0]) ): # Operate on grid values Ys = self.source[:, 1] * other.source[:, 1] @@ -1916,7 +1916,7 @@ def __add__(self, other): and isinstance(self.source, np.ndarray) and self.__interpolation__ == other.__interpolation__ and self.__inputs__ == other.__inputs__ - and np.any(self.source[:, 0] - other.source[:, 0]) == False + and np.array_equal(self.source[:, 0], other.source[:, 0]) ): # Operate on grid values Ys = self.source[:, 1] + other.source[:, 1] @@ -2018,7 +2018,7 @@ def __sub__(self, other): and isinstance(self.source, np.ndarray) and self.__interpolation__ == other.__interpolation__ and self.__inputs__ == other.__inputs__ - and np.any(self.source[:, 0] - other.source[:, 0]) == False + and np.array_equal(self.source[:, 0], other.source[:, 0]) ): # Operate on grid values Ys = self.source[:, 1] - other.source[:, 1] From 0f848e9f5ce76fc77eaf9334c680f594e4c1b48e Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Mon, 14 Nov 2022 01:42:54 -0300 Subject: [PATCH 09/15] ENH: add support for methods with arguments in funcify decorator --- rocketpy/Function.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 3aaa2d52d..4c492a52a 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -2267,15 +2267,27 @@ def __get__(self, instance, owner=None): return self cache = instance.__dict__ try: + # If cache is ready, return it val = cache[self.attrname] except KeyError: - source = self.func(instance) - if isinstance(source, Function): - # Avoid creating a new Function object if the source is already one - val = source - val.reset(*args, **kwargs) - else: + # If cache is not ready, create it + try: + # Handle methods which return Function instances + val = self.func(instance).reset(*args, **kwargs) + except AttributeError: + # Handle methods which return a valid source + source = self.func(instance) + val = Function(source, *args, **kwargs) + except TypeError: + # Handle methods which are the source themselves + source = lambda *_: self.func(instance, *_) val = Function(source, *args, **kwargs) + except Exception: + raise Exception( + "Could not create Function object from method " + f"{self.func.__name__}." + ) + val.__doc__ = self.__doc__ cache[self.attrname] = val return val From b1f5e37f9dba8b522b2bf8ad3912ac199055f2a0 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Mon, 14 Nov 2022 01:43:28 -0300 Subject: [PATCH 10/15] DOC: add more complete examples to docs for funcify_method --- rocketpy/Function.py | 67 +++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 4c492a52a..85e9ea121 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -2206,47 +2206,56 @@ def funcify_method(*args, **kwargs): Examples -------- - >>> class Test(): - ... @Funcify(inputs=['x'], outputs=['y']) - ... def func(x): - ... return x**2 - >>> t = Test() - ... t.func + There are 3 types of methods that this decorator supports: + + 1. Method which returns a valid rocketpy.Function source argument. + + >>> from rocketpy.Function import funcify_method + >>> class Example(): + ... @funcify_method(inputs=['x'], outputs=['y']) + ... def f(self): + ... return lambda x: x**2 + >>> example = Example() + >>> example.f Function from R1 to R1 : (x) → (y) - >>> g = 2*t.func + 3 + + Normal algebra can be performed afterwards: + + >>> g = 2*example.f + 3 >>> g(2) 11 - Can also be used without any arguments: + 2. Method which returns a rocketpy.Function instance. An interesting use is to reset + input and output names after algebraic operations. + + >>> class Example(): + ... @funcify_method(inputs=['x'], outputs=['x**3']) + ... def cube(self): + ... f = Function(lambda x: x**2) + ... g = Function(lambda x: x**5) + ... return g / f + >>> example = Example() + >>> example.cube + Function from R1 to R1 : (x) → (x**3) - >>> class Test(): - ... @Funcify - ... def func(x): + 3. Method which is itself a valid rocketpy.Function source argument. + + >>> class Example(): + ... @funcify_method('x', 'f(x)') + ... def f(self, x): ... return x**2 - >>> t = Test() - ... t.func - Function from R1 to R1 : (Scalar) → (Scalar) - - Can also be used when the method already returns a Function instance. In such case - it is interesting to use the `inputs` and `outputs` arguments to overwrite the - inputs and outputs of the method. - - >>> class Test(): - ... @Funcify(inputs='x', outputs='y') - ... def func(x): - ... return Function(lambda x: x**2) - >>> t = Test() - ... t.func - Function from R1 to R1 : (x) → (y) + >>> example = Example() + >>> example.f + Function from R1 to R1 : (x) → (f(x)) In order to reset the cache, just delete de attribute from the instance: - >>> del t.func + >>> del example.f Once it is requested again, it will be re-created as a new Function object: - >>> t.func - Function from R1 to R1 : (Scalar) → (Scalar) + >>> example.f + Function from R1 to R1 : (x) → (f(x)) """ func = None if len(args) == 1 and callable(args[0]): From d6159fd89d99ebad220a91ef0b1bd28019727cf6 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Mon, 14 Nov 2022 01:44:11 -0300 Subject: [PATCH 11/15] TST: add doctest to Function module --- rocketpy/Function.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 85e9ea121..8f1c8bc46 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -518,8 +518,8 @@ def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): >>> f = Function([(0, 0), (1, 1), (2, 4), (3, 9), (4, 16)]) >>> g = Function([(0, 0), (2, 2), (4, 4)]) >>> h = f * g - >>> h.source - .(x)> + >>> type(h.source) + Therefore, it is good practice to make sure both Function instances are defined by the same domain, i.e. by the same list of mesh points. This way, the @@ -529,6 +529,7 @@ def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): interpolations. >>> g.setDiscreteBasedOnModel(f) + Function from R1 to R1 : (Scalar) → (Scalar) >>> h = f * g >>> h.source array([[ 0., 0.], @@ -601,16 +602,15 @@ def reset( that has been defined by algebraic manipulation of other Function objects. >>> from rocketpy import Function - >>> v = Function(lambda t: t**2, inputs='t', outputs='v') + >>> v = Function(lambda t: (9.8*t**2)/2, inputs='t', outputs='v') >>> mass = 10 # Mass >>> kinetic_energy = mass * v**2 / 2 >>> v.getInputs(), v.getOutputs() (['t'], ['v']) - >>> kinetic_energy.getInputs(), kinetic_energy.getOutputs() - (['x'], ['Scalar']) - >>> kinetic_energy.reset(inputs='t', outputs='Kinetic Energy') - >>> kinetic_energy.getInputs(), kinetic_energy.getOutputs() - (['t'], ['Kinetic Energy']) + >>> kinetic_energy + Function from R1 to R1 : (x) → (Scalar) + >>> kinetic_energy.reset(inputs='t', outputs='Kinetic Energy'); + Function from R1 to R1 : (t) → (Kinetic Energy) Returns ------- @@ -2305,3 +2305,9 @@ def __get__(self, instance, owner=None): return funcify_method_decorator(func) else: return funcify_method_decorator + + +if __name__ == "__main__": + import doctest + + doctest.testmod() From 71ca519f6486db19116b1dd7f4bafa9adf67170b Mon Sep 17 00:00:00 2001 From: Lint Action Date: Mon, 14 Nov 2022 04:44:56 +0000 Subject: [PATCH 12/15] Fix code style issues with Black --- rocketpy/Function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 8f1c8bc46..b3ddc066d 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -2238,7 +2238,7 @@ def funcify_method(*args, **kwargs): >>> example.cube Function from R1 to R1 : (x) → (x**3) - 3. Method which is itself a valid rocketpy.Function source argument. + 3. Method which is itself a valid rocketpy.Function source argument. >>> class Example(): ... @funcify_method('x', 'f(x)') From 68625b2ce9a049c11acfc95c56edc6859a619d15 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Mon, 14 Nov 2022 01:50:05 -0300 Subject: [PATCH 13/15] DOC: minor typo fix --- rocketpy/Function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 8f1c8bc46..5dc8f32b1 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -474,7 +474,7 @@ def setDiscrete( return self def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): - """This method transform a Function instance defined from callables into a + """This method transforms a Function instance defined from callables into a Function instance defined by a list of discrete points. It does so based on a model Function, from which it retrieves the domain, domain name, interpolation method and extrapolation method. @@ -2238,7 +2238,7 @@ def funcify_method(*args, **kwargs): >>> example.cube Function from R1 to R1 : (x) → (x**3) - 3. Method which is itself a valid rocketpy.Function source argument. + 3. Method which is itself a valid rocketpy.Function source argument. >>> class Example(): ... @funcify_method('x', 'f(x)') From 0e253a5c1cb1935f5831904125c57a432cbc61f8 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Mon, 14 Nov 2022 01:55:40 -0300 Subject: [PATCH 14/15] TST: add pytest doctest modules to GH actions --- .github/workflows/test_pytest.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test_pytest.yaml b/.github/workflows/test_pytest.yaml index 8bb5bfc03..b775a3dd3 100644 --- a/.github/workflows/test_pytest.yaml +++ b/.github/workflows/test_pytest.yaml @@ -29,4 +29,6 @@ jobs: python setup.py install - name: Test with pytest run: | - pytest \ No newline at end of file + pytest + cd rocketpy + pytest --doctest-modules \ No newline at end of file From 634d9e68c2e8af3515d58df11eb545ec903b3705 Mon Sep 17 00:00:00 2001 From: Giovani Hidalgo Ceotto Date: Tue, 15 Nov 2022 11:26:09 -0300 Subject: [PATCH 15/15] DOC: improvements to setDiscreteBasedOnModel docstring as suggest by @MateusStano --- rocketpy/Function.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/rocketpy/Function.py b/rocketpy/Function.py index 5dc8f32b1..d369c86f6 100644 --- a/rocketpy/Function.py +++ b/rocketpy/Function.py @@ -474,13 +474,12 @@ def setDiscrete( return self def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): - """This method transforms a Function instance defined from callables into a - Function instance defined by a list of discrete points. - It does so based on a model Function, from which it retrieves the domain, - domain name, interpolation method and extrapolation method. - It then evaluates the original Function instance in all points of the retrieved - domain to generate the list of discrete points that will be used for - interpolation when this Function is called. + """This method transforms the domain of Function instance into a list of + discrete points based on the domain of a model Function instance. It does so by + retrieving the domain, domain name, interpolation method and extrapolation + method of the model Function instance. It then evaluates the original Function + instance in all points of the retrieved domain to generate the list of discrete + points that will be used for interpolation when this Function is called. Parameters ---------- @@ -505,7 +504,7 @@ def setDiscreteBasedOnModel(self, modelFunction, oneByOne=True): Examples -------- - This method is particularly useful when algebraic operations is carried out + This method is particularly useful when algebraic operations are carried out using Function instances defined by different discretized domains (same range, but different mesh size). Once an algebraic operation is done, it will not directly be applied between the list of discrete points of the two Function