Skip to content

Commit

Permalink
DOC: detail Function features and computations.
Browse files Browse the repository at this point in the history
  • Loading branch information
phmbressan committed Nov 17, 2023
1 parent d2b7eae commit ba0a513
Showing 1 changed file with 220 additions and 22 deletions.
242 changes: 220 additions & 22 deletions docs/user/function.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
Function Class Usage
====================

The :class:`rocketpy.mathutils.Function` class in RocketPy is an auxiliary module that allows for easy manipulation of datasets and Python functions. Some of the class features are data interpolation, extrapolation, algebra and plotting.
The :class:`rocketpy.Function` class in RocketPy is an auxiliary module that allows for easy manipulation of datasets and Python functions. Some of the class features are data interpolation, extrapolation, algebra and plotting.

The basic steps to create a ``Function`` are as follows:

1. Define a data source: a dataset (e.g. x,y coordinates) or a function that maps a dataset to a value (e.g. f(x) = x^2);
1. Define a data source: a dataset (e.g. x,y coordinates) or a function that maps a dataset to a value (e.g. :math:`f(x) = x^2`);
2. Construct a ``Function`` object with this dataset as ``source``;
3. Use the ``Function`` features as needed: add datasets, integrate at a point, make a scatter plot and much more.

Expand All @@ -16,15 +16,15 @@ These basic steps are detailed in this guide.
1. Define a Data Source
-----------------------

Datasets
~~~~~~~~
a. Datasets
~~~~~~~~~~~

The ``Function`` class supports a wide variety of dataset sources:

List or Numpy Array
^^^^^^^^^^^^^^^^^^^
- List or Numpy Array
^^^^^^^^^^^^^^^^^^^^^

A ``list`` or ``numpy.ndarray`` of datapoints that maps input values to an output can be used as a ``Function`` source. For instance, we can define a dataset that follows the function f(x) = x^2:
A ``list`` or ``numpy.ndarray`` of datapoints that maps input values to an output can be used as a ``Function`` source. For instance, we can define a dataset that follows the function :math:`f(x) = x^2`:

.. jupyter-execute::

Expand Down Expand Up @@ -56,7 +56,7 @@ Furthermore, in order to visualize the dataset, one may use the ``plot`` method

|
The dataset can be defined as a *multidimensional* array (more than one input maps to an output), where each row is a datapoint. For example, let us define a dataset that follows the plane z = x + y:
The dataset can be defined as a *multidimensional* array (more than one input maps to an output), where each row is a datapoint. For example, let us define a dataset that follows the plane :math:`z = x + y`:

.. jupyter-execute::

Expand All @@ -70,13 +70,13 @@ The dataset can be defined as a *multidimensional* array (more than one input ma
# Create a Function object with this dataset
f = Function(source, ["x", "y"], "z")

One may print the source attribute from the ``Function`` object to check the inputed dataset.
One may print the source attribute from the ``Function`` object to check the input dataset.

.. jupyter-execute::

print(f.source)

Two dimensional plots are also supported, therefore this datasource can be plotted as follows:
Two dimensional plots are also supported, therefore this data source can be plotted as follows:

.. jupyter-execute::

Expand All @@ -86,8 +86,8 @@ Two dimensional plots are also supported, therefore this datasource can be plott
.. important::
The ``Function`` class only supports interpolation ``shepard`` and extrapolation ``natural`` for datasets higher than one dimension (more than one input).

CSV File
^^^^^^^^
- CSV File
^^^^^^^^^^

A CSV file path can be passed as ``string`` to the ``Function`` source. The file must contain a dataset structured so that each line is a datapoint: the last column is the output and the previous columns are the inputs.

Expand Down Expand Up @@ -122,10 +122,10 @@ Having the csv file, we can define a ``Function`` object with it:
.. note::
A header in the csv file is optional, but if present must be in a string like format, i.e. beginning and ending with quotation marks.

Function Map
~~~~~~~~~~~~
b. Function Map
~~~~~~~~~~~~~~~

A Python function that maps a set of parameters to a result can be used as a ``Function`` source. For instance, we can define a function that maps x to f(x) = sin(x):
A Python function that maps a set of parameters to a result can be used as a ``Function`` source. For instance, we can define a function that maps x to :math:`f(x) = \sin(x)`:

.. jupyter-execute::

Expand All @@ -141,8 +141,8 @@ A Python function that maps a set of parameters to a result can be used as a ``F

The result of this operation is a ``Function`` object that wraps the source function and features many functionalities, such as plotting.

Constant Functions
^^^^^^^^^^^^^^^^^^
- Constant Functions
^^^^^^^^^^^^^^^^^^^^

A special case of the python function source is the definition of a constant ``Function``. The class supports a convenient shortcut to ease the definition of a constant source:

Expand All @@ -158,6 +158,7 @@ A special case of the python function source is the definition of a constant ``F
This shortcut is completely equivalent to defining a Python constant function as the source:

.. jupyter-input::

def const_source(_):
return 1.5

Expand All @@ -176,12 +177,209 @@ In this section we are going to delve deeper on ``Function`` creation and its pa
- extrapolation: a string that is the extrapolation method to be used if the source is a dataset. Defaults to ``constant``;
- title: the title to be shown in the plots.

With these in mind, let us create a more concrete example so that each of these parameters usefulness is explored.::
.. seealso::
Check out more about the constructor parameters and other functionalities in the :class:`rocketpy.Function` documentation.

Suppose we have a particle named Bob
With these in mind, let us create a more concrete example so that each of these parameters usefulness is explored.

.. seealso::
Check out more about the constructor parameters and other functionalities in the :class:`rocketpy.mathutils.Function` documentation.
Suppose we have a dataset containing the data from a static fire test of a rocket engine in testing phase. The dataset contain has a column for time (s) and thrust (N). We want to create a ``Function`` object that represents the thrust curve of this engine.

.. jupyter-execute::

from rocketpy.mathutils import Function

# Static fire data
motor_thrust = [
(0, 0), (0.5, 1500), (1, 2000),
(1.5, 2100), (2, 1900), (2.5, 800),
(3, 0)
]

# Create a Function object with this dataset
thrust = Function(
source=motor_thrust,
inputs="time (s)",
outputs="thrust (N)",
interpolation="spline",
extrapolation="zero",
title="Static Fire Thrust Curve"
)

The parameters ``interpolation`` and ``extrapolation`` are of particular importance in this example:

- Due the fact the data is quite sparse, we want to use a ``spline`` interpolation to smooth the curve.
- The extrapolation method is set to ``zero`` because we know that the thrust is zero before and after the test.

Let's plot this curve to visualize the effect of these options in action:

.. jupyter-execute::

# Plotting from 0 to 5 seconds
thrust.plot(0, 5)

.. note::
The ``Function`` class plots only supports one or two dimensional inputs.
Compare the interpolation and extrapolation effects by changing their methods. Check out this plot results with ``linear`` interpolation and ``constant`` extrapolation and see their difference.

3. Function Features
--------------------

The ``Function`` class has many features that can be used to manipulate the source data. In this section we are going to explore some of these features, such as Function call, Function arithmetic, discretization, differentiation and integration.

a. Function Call
~~~~~~~~~~~~~~~~

A ``Function`` objects maps input data to an output, therefore should you want to get an output value from a given input, this can be accomplished by the method :meth:`rocketpy.Function.get_value`:

.. jupyter-execute::

from rocketpy.mathutils import Function

f = Function(lambda x: x**0.5)

print(f.get_value(9))

Equivalently, the same operation is defined by the Python dunder method ``__call__`` so that the object can be used like a common function. For instance:

.. jupyter-execute::

print(f(9), f(25))

Furthermore, the :meth:`rocketpy.Function.get_value` method can be used to get a list of outputs from a list of inputs:

.. jupyter-execute::

print(f.get_value([1, 4, 9, 16, 25]))

b. Function Arithmetic
~~~~~~~~~~~~~~~~~~~~~~

An important feature of the ``Function`` class is the ability to perform arithmetic operations between real values or even other ``Function`` objects.

.. jupyter-execute::

import numpy as np

f = Function(lambda x: np.sin(x))

g = f/4 + 1

Function.compare_plots([f, g], lower=0, upper=4*np.pi)

.. note::
This is an example of the static method :meth:`rocketpy.Function.compare_plots`, it is used to plot Functions in the same graph for comparison.

Arithmetic can also be performed on sets of data of the same length and same domain discretization (i.e. equal x values):

.. jupyter-execute::

source1 = [(0, 0), (0.5, 0.25), (1, 1), (1.5, 2.25), (2, 4)]
source2 = [(0, 0), (0.5, 0.5), (1, 1), (1.5, 1.5), (2, 2)]

f = Function(source1)
g = Function(source2)

h = (f + g) / 2

Function.compare_plots([f, g, h], lower=0, upper=2)

c. Discretization
~~~~~~~~~~~~~~~~~

The ``Function`` class can also convert from function sourced to a discretized dataset produced from it. This is accomplished by the method :meth:`rocketpy.Function.set_discrete` and allows for a great computational speed up if the function source is complex.

The accuracy of the discretization depends on the number of datapoints and the chosen interpolation method.

Let's compare the discretization of a sine function:

.. jupyter-execute::

import numpy as np
from copy import copy

# Function from sine
f = Function(lambda x: np.sin(x))

# Discretization
f_continuous = copy(f)
f_discrete = f.set_discrete(
lower=0,
upper=4*np.pi,
samples=20,
interpolation="linear"
)

Function.compare_plots([f_continuous, f_discrete], lower=0, upper=4*np.pi)

.. important::

A `copy` of the original continuous function was necessary in this example, since the method :meth:`rocketpy.Function.set_discrete` mutates the original ``Function``.

d. Differentiation and Integration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

One of the most useful ``Function`` features for data analysis is easily differentiating and integrating the data source. These methods are divided as follow:

- :meth:`rocketpy.Function.differentiate`: differentiate the ``Function`` at a given point, returning the derivative value as the result;
- :meth:`rocketpy.Function.integral`: performs a definite integral over specified limits, returns the integral value (area under ``Function``);
- :meth:`rocketpy.Function.derivative_function`: computes the derivative of the given `Function`, returning another `Function` that is the derivative of the original at each point;
- :meth:`rocketpy.Function.integral_function`: calculates the definite integral of the function from a given point up to a variable, returns a ``Function``.

Derivatives
^^^^^^^^^^^

Let's make a familiar example of differentiation: the derivative of the function :math:`f(x) = x^2` is :math:`f'(x) = 2x`. We can use the ``Function`` class to compute those:

.. jupyter-execute::

# Define the function x^2
f = Function(lambda x: x**2)

# Differentiate it at x = 3
print(f.differentiate(3))

Also one may compute the derivative function:

.. jupyter-execute::

# Define the function x^2 and its derivative
f = Function(lambda x: x**2)
f_dot = f.derivative_function()

# Compare their plots
Function.compare_plots([f, f_dot], lower=-2, upper=2)

Integrals
^^^^^^^^^

Now, to illustrate the power of the ``Function`` class in making it easy to make plots of complex functions, let's plot the integral of the gaussian function:

.. math::
f(x) = \frac{1}{\sqrt{2\pi}} \cdot e^{-\frac{x^2}{2}}
Which is non-elementary so it cannot be expressed in terms of common functions.

.. jupyter-execute::

# Define the gaussian function
def gaussian(x):
return 1 / np.sqrt(2*np.pi) * np.exp(-x**2/2)

f = Function(gaussian)

# Integrate from 0 to 1
print(f.integral(0,1))

Here we have shown that we can integrate the gaussian function over a defined interval, let's compute its integral function.

.. jupyter-execute::

# Compute the integral function from -4
f_int = f.integral_function(-4, 4, 1000)

# Compare the function with the integral
Function.compare_plots([f, f_int], lower=-4, upper=4)

........

This guide shows some of the capabilities of the ``Function`` class, but there are many other functionalities to enhance your analysis. Do not hesitate in tanking a look at the documentation :class:`rocketpy.Function`.

0 comments on commit ba0a513

Please sign in to comment.