{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# How to extract slices\n", "\n", "The SciDataTool python module has been created to **ease the handling of scientific data**, and considerately simplify plot commands. It unifies the extraction of relevant data (e.g. slices), whether they are stored in the time/space or in the frequency domain. The call to Fourier Transform functions is **transparent**, although it still can be parameterized through the use of a dictionary.\n", "\n", "This tutorial explains how to **extract** parts of a field, converted to a specified unit, normalized, and transformed to the Fourier domain.\n", "\n", "[Once the data has been stored into `Data` objects](https://nbviewer.jupyter.org/github/Eomys/SciDataTool/blob/master/Tutorials/tuto1_Create.ipynb), you will want to plot evolutions, compute postprocessings, etc. You therefore need to **extract** the totality or a part of the data. In the case of an nD field, this corresponds to extracting **slices** of your data. The methods described hereafter are of course also valid for 1D data.\n", "\n", "The `DataTime` and `DataFreq` classes have built-in methods to extract slices of the field: the `get_along` methods. One interesting feature is that, no matter whether the data was stored in the **time/space** or the **frequency** domain, the syntax will be **identical**. The `get_along` methods return a **tuple** containing the axes and the field arrays. These methods allow to **intuitively** extract nD slices, as can be seen in the example below:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{'time': array([0.01]), 'angle': array([ 0., 18., 36., 54., 72., 90., 108., 126., 144., 162., 180.,\n", " 198., 216., 234., 252., 270., 288., 306., 324., 342.]), 'X': array([-5.00000000e+00, -2.93892626e+00, 1.54508497e+00, 4.75528258e+00,\n", " 4.04508497e+00, 1.53080850e-15, -4.04508497e+00, -4.75528258e+00,\n", " -1.54508497e+00, 2.93892626e+00, 5.00000000e+00, 2.93892626e+00,\n", " -1.54508497e+00, -4.75528258e+00, -4.04508497e+00, -1.22495629e-14,\n", " 4.04508497e+00, 4.75528258e+00, 1.54508497e+00, -2.93892626e+00]), 'axes_list': [, ], 'axes_dict_other': {}}\n" ] } ], "source": [ "# import SciDataTool objects\n", "from SciDataTool import Data1D, DataLinspace, DataTime, DataFreq, VectorField\n", "\n", "import numpy as np\n", "import plotly.graph_objects as go\n", "import plotly.io as pio\n", "pio.renderers.default = 'notebook_connected'\n", "\n", "f = 50\n", "Time = DataLinspace(\n", " name=\"time\",\n", " unit=\"s\",\n", " initial=0,\n", " final=1/f,\n", " number=10,\n", " include_endpoint=False,\n", ")\n", "Angle = DataLinspace(\n", " name=\"angle\",\n", " unit=\"rad\",\n", " initial=0,\n", " final=2*np.pi,\n", " number=20,\n", " include_endpoint=False,\n", ")\n", "ta, at = np.meshgrid(Time.get_values(), Angle.get_values())\n", "field = 5 * np.cos(2*np.pi*f*ta + 3*at)\n", "Field = DataTime(\n", " name=\"Example field\",\n", " symbol=\"X\",\n", " axes=[Angle, Time],\n", " values=field,\n", ")\n", "\n", "#---------------------------------------------------------------\n", "# Extract a slice at t=0.01s\n", "results = Field.get_along(\"time=0.01\", \"angle{°}\")\n", "#---------------------------------------------------------------\n", "print(results)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `get_along` methods return a dict containing the axes values, the field values, and meta data (`axes_list` and `axes_dict_other`) which can be used to build plots." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ " \n", " " ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Plot\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(\n", " x=results[\"angle\"],\n", " y=results[\"X\"],\n", "))\n", "fig.update_layout(\n", " title={\n", " 'text': Field.name + \" as a function of space\",\n", " 'y':0.9,\n", " 'x':0.5,\n", " 'xanchor': 'center',\n", " 'yanchor': 'top'},\n", " xaxis_title=\"Angle [°]\",\n", " yaxis_title=r\"$\"+Field.symbol+\" [\"+Field.unit+\"]$\",\n", ")\n", "fig.show(config = {\"displaylogo\":False})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The **syntax** for the requested axes string is described in the following section. More **advanced functionalities** are presented in the other sections (how to normalize the field or an axis, how to extract a slice in the frequential domain). Finally, for development purposes, the **synopsis** of the `get_along` methods is given in the last section." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 1. Possible syntaxes for requesting axes\n", "The **syntax** for requesting axes has been designed to be as intuitive as possible. Hereafter is the list of the available syntaxes:\n", "- units: `\"time{ms}\"`\n", "- indices: `\"time[0]\"`, `\"time[-1]\"`, `\"time[0,3,6]\"`, `\"time[0:5]\"`\n", "- values: `\"time=5\"`, `\"angle=[0,pi/2]\"`, `\"angle>pi\"`\n", "- sums: `\"time=sum\"`\n", "- root sum square value: `\"time=rss\"`\n", "- mean value: `\"time=mean\"`\n", "- rms value: `\"time=rms\"`\n", "- integrals: `\"time=integrate\"`\n", "- periods: `\"time[oneperiod]\"`, `\"time[antiperiod]\"`, `\"time[smallestperiod]\"`\n", "- interpolations: `\"time=axis_data\", axis_data={\"time\": np.array([0,0.5,1,1.5,2])}`\n", "- normalizations: `\"freqs->elec_order\"`\n", "\n", "The **unit** of the axis can be added to any of the previous strings as `{unit}` (e.g. `\"angle[-1]{°}\"`).\n", "\n", "If nothing is specified regarding an axis from the `axes` list, a slice at its **first value** will be extracted.\n", "\n", "The following examples show different syntaxes:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "angle_data = np.linspace(0, 360, 100, endpoint=False)\n", "\n", "#---------------------------------------------------------------\n", "# Extract slices\n", "results2 = Field.get_along(\"angle{°}\")\n", "results3 = Field.get_along(\"time[-1]\", \"angle=axis_data{°}\", axis_data={\"angle\":angle_data})\n", "#---------------------------------------------------------------\n", "\n", "# Plot\n", "fig = go.Figure()\n", "fig.add_trace(go.Scatter(\n", " x=results[\"angle\"],\n", " y=results[\"X\"],\n", " name=\"t=0.01s\"\n", "))\n", "fig.add_trace(go.Scatter(\n", " x=results2[\"angle\"],\n", " y=results2[\"X\"],\n", " name=\"t[0]\"\n", "))\n", "fig.add_trace(go.Scatter(\n", " x=results3[\"angle\"],\n", " y=results3[\"X\"],\n", " name=\"t[-1]\"\n", "))\n", "fig.update_layout(\n", " title={\n", " 'text': Field.name + \" as a function of space at three instants\",\n", " 'y':0.9,\n", " 'x':0.5,\n", " 'xanchor': 'center',\n", " 'yanchor': 'top'},\n", " xaxis_title=\"Angle [°]\",\n", " yaxis_title=r\"$\"+Field.symbol+\" [\"+Field.unit+\"]$\",\n", ")\n", "fig.show(config = {\"displaylogo\":False})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. How to extract slices in the frequential domain\n", "\n", "To extract slices in the frequential domain, i.e. to extract **complex Fourier Transform**, **magnitude** or **phase**, use the methods: `get_FT_along`, `get_magnitude_along` or `get_phase_along`. The principle is the same as in the `get_along` methods, with the addition of the Fourier Transform. Note that the name of the Fourier axes must be part of the predefined correspondances:\n", " + `\"time\"` ↔ `\"freqs\"`\n", " + `\"angle\"` ↔ `\"wavenumber\"`\n", "\n", "Other correspondances may be defined in the `axes_dict` and `rev_axes_dict` located in the `__init__` file of the `Functions` folder.\n", "\n", "The default method for the Fourier Transform is the numpy fft. See section 6. How to customize Fourier Transform parameters, for other Fourier Transform methods.\n", "\n", "The frequencies can be converted to orders (electrical orders, space orders, mechanical orders, etc.) using the `normalizations` dictionary (see example below)." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "#---------------------------------------------------------------\n", "# Extract slices\n", "results4 = Field.get_magnitude_along(\"freqs>0\")\n", "#---------------------------------------------------------------\n", "\n", "fig = go.Figure()\n", "fig.add_trace(go.Bar(x=results4[\"freqs\"],\n", " y=results4[\"X\"],\n", " ))\n", "fig.update_layout(\n", " title={\n", " 'text': \"FFT of \" + Field.name,\n", " 'y':0.9,\n", " 'x':0.5,\n", " 'xanchor': 'center',\n", " 'yanchor': 'top'},\n", " xaxis_title=\"Frequency [Hz]\",\n", " yaxis_title=r\"$|\\hat{\"+Field.symbol+\"}| [\"+Field.unit+\"]$\",\n", ")\n", "fig.show(config = {\"displaylogo\":False})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. How to normalize the field or an axis\n", "\n", "To normalize a field or an axis, the `normalizations` attribute can be called. If it was not defined at the `Data` creation step, it is possible to add entries before extracting a slice (see example below).\n", "\n", "The normalization of the **field** is activated by the key `is_norm=True`. In this case, the field values will be divided by the reference value stored in `{\"ref\": ref_value}`. If **dB** or **dBA** are requested in a `get_magnitude_along` method, the reference value will also be the one specified in `normalizations`. If none was provided, the default reference value will be 1.0. It is also possible to add a new unit with its normalization value here.\n", "\n", "To normalize an **axis**, for example express frequencies in electrical orders, knowing that one order corresponds to 60Hz, it is also possible to use `normalizations`. The example below demonstrates this feature:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "# Add normalization values\n", "Field.normalizations[\"ref\"] = 0.8\n", "Time.normalizations[\"elec_order\"] = 50\n", "\n", "#---------------------------------------------------------------\n", "# Extract slices\n", "results5 = Field.get_magnitude_along(\"freqs->elec_order=[0,10]\", is_norm=True)\n", "#---------------------------------------------------------------\n", "\n", "# Plot\n", "fig = go.Figure()\n", "fig.add_trace(go.Bar(x=results5[\"freqs\"],\n", " y=results5[\"X\"],\n", " ))\n", "fig.update_layout(\n", " title={\n", " 'text': \"FFT of \" + Field.name,\n", " 'y':0.9,\n", " 'x':0.5,\n", " 'xanchor': 'center',\n", " 'yanchor': 'top'},\n", " xaxis_title=\"Electrical orders []\",\n", " yaxis_title=r\"$|\\hat{\"+Field.symbol+\"}| [\"+Field.unit+\"]$\",\n", ")\n", "fig.show(config = {\"displaylogo\":False})" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. Synopsis of the `get_along` methods\n", "The `get_along` methods encapsulate several important steps. Hereafter is a summarized description of each of these steps:\n", "\n", "- **Read the axes requested** by the user\n", "\n", "- **Extract the requested axes** by calling the `comp_axes` method. The method will first rebuild the complete axis if a symmetry was used, or compute the time/space axes if the field was stored in the frequency domain, then convert to the requested unit or normalization. If necessary, the axis is then interpolated on the prescribed values (interval or `axis_data`).\n", "\n", "- Get the **field** (reconstruct anti-period at this point if a fft is requested).\n", "\n", "- Get the **inverse Fourier transform** if requested.\n", "\n", "- The **slices** of the field are extracted (single index or values, or interval of indices) along time/space axes.\n", "\n", "- Get the **Fourier transform** if requested.\n", "\n", "- The **slices** of the field are extracted (single index or values, or interval of indices) along Fourier axes.\n", "\n", "- The full field is reconstructed according to the axes **symmetries**.\n", "\n", "- The field is **interpolated** over the specified axis values.\n", "\n", "- The field is **converted** (unit and normalizations).\n", "\n", "- The **return dict** is built." ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.8" }, "latex_envs": { "LaTeX_envs_menu_present": true, "autoclose": false, "autocomplete": true, "bibliofile": "biblio.bib", "cite_by": "apalike", "current_citInitial": 1, "eqLabelWithNumbers": true, "eqNumInitial": 1, "hotkeys": { "equation": "Ctrl-E", "itemize": "Ctrl-I" }, "labels_anchors": false, "latex_user_defs": false, "report_style_numbering": false, "user_envs_cfg": false }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": false, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": { "height": "calc(100% - 180px)", "left": "10px", "top": "150px", "width": "307.2px" }, "toc_section_display": false, "toc_window_display": false }, "widgets": { "application/vnd.jupyter.widget-state+json": { "state": {}, "version_major": 2, "version_minor": 0 } } }, "nbformat": 4, "nbformat_minor": 4 }