{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# How to create Data objects\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 the **structure** of the `Data` classes, then shows **how to create axes and fields objects**.\n", "\n", "The following example demonstrates the syntax to **quickly create a 1D data field** depending on time:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0. 1. 2. 3. 4. 5. 6. 7. 8. 9.]\n" ] } ], "source": [ "# import SciDataTool objects\n", "from SciDataTool import Data1D, DataLinspace, DataPattern, DataTime, DataFreq, VectorField\n", "\n", "import numpy as np\n", "\n", "time = np.linspace(0,10,10,endpoint=False)\n", "Time = Data1D(\n", " name=\"time\",\n", " unit=\"s\",\n", " values=time,\n", ")\n", "print(Time.get_values())\n", "\n", "field = np.ones(10)\n", "Field = DataTime(\n", " name=\"Example field\",\n", " symbol=\"X\",\n", " unit=\"dimless\",\n", " axes=[Time],\n", " values=field,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Your `Data`objects have been successfully created. Other features of the `SciDataTool` package are also available:\n", "- reduce storage if an axis is regularly spaced\n", "- reduce storage if the field presents a symmetry along one of its axes\n", "- store a field in the frequency domain\n", "- specifiy normalizations\n", "\n", "These functionalities are described in the following sections.\n", "\n", "## 1. Data class structure\n", "The `Data` class is composed of:\n", "- classes describing **axes**: `Data1D`, or `DataLinspace` if the axis is regularly spaced (see [section 2](#How-to-reduce-storage-if-an-axis-is-regularly-spaced))\n", "- classes describing **fields** stored in the time/space domain (`DataTime`) or in the frequential domain (`DataFreq`)\n", "\n", "The following UML summarizes this structure:\n", "\n", "
\n", "\n", "
\n", "\n", "The attributes in red are **mandatory**, those in gray are **optional**. To correctly fill the mandatory attributes, it is advised to follow these principles:\n", "- `values` is a **numpy array**\n", "- `axes` is a **list** of `Data1D` or `DataLinspace`\n", "- `name` is **string** corresponding to a short description of the field, or the \n", "- `symbol` is a **string** giving the symbol of the field in LaTeX format\n", "- `unit` is a **string** among the list: `[dimless, m, rad, °, g, s, min, h, Hz, rpm, degC, A, J, W, N, C, T, G, V, F, H, Ohm, At, Wb, Mx]`, with a prefix `[k, h, da, d, c, m, etc.]`. Composed units are also available (e.g. `mm/s^2`). It is best to use such a LaTeX formatting for axis labelling. Other units can be added in [conversions.py](https://github.com/Eomys/SciDataTool/blob/master/Functions/conversions.py).\n", "- for `Data1D` and `DataLinspace`, `name` + `[unit]` can be used to label axes\n", "- for `DataTime` and `DataFreq`, `name` can be used as plot title, and `symbol` + `[unit]` as label\n", "\n", "When a `Data1D` is created, the array `values` is **squeezed** to avoid dimension problems. When a `DataTime` or `DataFreq` is created, `values` is also squeezed, and a `CheckDimError` is raised if **dimensions** of `axes` and `values` do not match.\n", "\n", "The following sections explain how to use the optional attributes to optimize storage." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 2. How to reduce storage if an axis is regularly spaced\n", "Axes often have a **regular distribution**, so that the use of `DataLinspace` allows to reduce the storage.\n", "\n", "A `DataLinspace` object has five properties instead of the `values` array: `initial`, `final`, `step` and `number` allow to define the linspace vector (3 out of these 4 suffice), and `include_endpoint` is a boolean used to indicate whether the final point should be included or not (default `False`).\n", "\n", "In the following example, the angle vector is defined as a linspace:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0. 0.31415927 0.62831853 0.9424778 1.25663706 1.57079633\n", " 1.88495559 2.19911486 2.51327412 2.82743339 3.14159265 3.45575192\n", " 3.76991118 4.08407045 4.39822972 4.71238898 5.02654825 5.34070751\n", " 5.65486678 5.96902604]\n" ] } ], "source": [ "#---------------------------------------------------------------\n", "# Create Data objects\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", "print(Angle.get_values())\n", "#---------------------------------------------------------------" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 3. How to reduce storage if a field presents a symmetry/periodicity\n", "If a signal shows a **symmetry** or a **periodicity** along one or several of its axes, it is possible to store only the relevant part of the signal, and save the information necessary to rebuild it within the optional attribute `symmetries`. A repeting signal can either be periodic: $f(t+T)=f(t)$, or antiperiodic: $f(t+T)=-f(t)$. Indeed, we can consider that a symmetric signal is a periodic signal of period $T=N/2$.\n", "\n", "`symmetries` is a dictionary containing the symmetry of the axis (`{\"period\": n}` or `{\"antiperiod\": n}`, with *n* the number of periods in the complete signal.\n", "\n", "In the following example, the time vector and the field are reduced using the built-in method `get_axis_periodic`. To access the reconstructed axis values, the `get_values` method is available, with options to extract a single period or antiperiod:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0. 1. 2. 3. 4.]\n", "[ 0. 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17.\n", " 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29.]\n" ] } ], "source": [ "Time_periodic = DataLinspace(\n", " name=\"time\",\n", " unit=\"s\",\n", " initial=0,\n", " final=5,\n", " number=5,\n", " include_endpoint=False,\n", " symmetries={\"period\": 6},\n", ")\n", "print(Time_periodic.get_values(is_oneperiod=True))\n", "print(Time_periodic.get_values())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A special case can occur: when a single sample is periodic or antiperiodic. In this case, the distance between the points must be provided by the user:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[0.]\n", "[ 0. 3. 6. 9. 12. 15.]\n" ] } ], "source": [ "Time_periodic = DataLinspace(\n", " name=\"time\",\n", " unit=\"s\",\n", " initial=0,\n", " final=0,\n", " number=1,\n", " include_endpoint=False,\n", " symmetries={\"period\": 6, \"delta\": 3},\n", ")\n", "print(Time_periodic.get_values(is_oneperiod=True))\n", "print(Time_periodic.get_values())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 4. How to reduce storage if a field presents a pattern\n", "If a signal shows a **pattern** (a repetition of certain slices) along one or several of its axes, it is also possible to reduce storage by storing only the **unique** slices. To do so, the DataPattern object can be used, with a `rebuild_indices` attribute which allows to reconstruct the whole field.\n", "\n", "The whole axis (`values_whole`) must however also be provided since the rebuild indices are different between axis and field. It is also possible to store the indices which have been used to extract the unique slices in `unique_indices`; it is not used in SciDataTool but can be useful outside.\n", "\n", "The slices can be either continuuous or by step, so that an `is_step` attribute has also been added (useful for field interpolations, integrations, etc)." ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[-5 -3 -1 0]\n", "[-5 -3 -3 -1 -1 0 1 1 3 3 5]\n" ] } ], "source": [ "Slices = DataPattern(\n", " name=\"z\",\n", " unit=\"m\",\n", " values=np.array([-5, -3, -1, 0]),\n", " values_whole=np.array([-5, -3, -3, -1, -1, 0, 1, 1, 3, 3, 5]),\n", " unique_indices=[0, 1, 3, 5],\n", " rebuild_indices=[0, 1, 1, 2, 2, 3, 2, 2, 1, 1, 0],\n", " is_step=True,\n", ")\n", "print(Slices.get_values(is_pattern=True))\n", "print(Slices.get_values())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 5. How to store a field in the frequency domain\n", "If one prefers to store data in the frequency domain, for example because most postprocessings will handle spectra, or because a small number of harmonics allow to reduce storage, the `DataFreq` class can be used.\n", "\n", "The definition is similar to the `DataTime` one, with the difference that the axes now have to be **frequencies** or **wavenumbers** and a `DataFreq` object is created.\n", "\n", "Since we want to be able to go back to the time/space domain, there must exist a corresponding axis name. For the time being, the existing **correspondances** are:\n", " + `\"time\"` ↔ `\"freqs\"`\n", " + `\"angle\"` ↔ `\"wavenumber\"`\n", "\n", "This list is to be expanded, and a possibility to manually add a correspondance will be implemented soon.\n", "\n", "In the following example, a field is stored in a `DataFreq` object." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "f = 50\n", "freqs = np.array([-100, -50, 0, 50, 100])\n", "Freqs = Data1D(name=\"freqs\", unit=\"Hz\", values=freqs)\n", "field_ft = np.array(\n", " [\n", " 0,\n", " 3 + 5 * 1j,\n", " 0,\n", " 3 - 5 * 1j,\n", " 0,\n", " ]\n", ")\n", "Field_FT = DataFreq(\n", " name=\"Example fft field\",\n", " symbol=\"X_FT\",\n", " axes=[Freqs],\n", " values=field_ft,\n", " unit=\"m\",\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A field can easily be transformed from time/space into Fourier domain, and vice-versa, using built-in methods:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "Field = Field_FT.freq_to_time()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 6. How to specify normalizations (axes or field)\n", "If you plan to **normalize** your field or its axes during certain postprocessings (but not all), you might want to store the normalizations values. To do so, you can use the `normalizations` attribute, which is a dictionaray:\n", "- for a normalization of the **field**, use `\"ref\"` (e.g. `{\"ref\": 0.8}`)\n", "- for a normalization of an **axis**, use the name of the normalized axis unit (e.g. `{\"elec_order\": 60}`) in the axis dict. There is no list of predefined normalized axis units, you simply must make sure to request it when you extract data (see [How to extract slices](https://github.com/Eomys/SciDataTool/tree/master/Tutorials/tuto_Slices.ipynb))\n", "- to **convert** to a unit which does not exist in the predefined units, and if there exists a proportionality relation, it is also possible to add it in the `normalizations` dictionary (e.g. `{\"nameofmyunit\": 154}`)\n", "\n", "This dictionary can also be updated later.\n", "\n", "See below examples of use of `normalizations`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [], "source": [ "Time = Data1D(\n", " name=\"time\",\n", " unit=\"s\",\n", " values=time,\n", " normalizations={\"elec_order\": 3}\n", ")\n", "\n", "field = np.ones(10)\n", "Field = DataTime(\n", " name=\"Example field\",\n", " symbol=\"X\",\n", " axes=[Time],\n", " values=field,\n", " normalizations={\"ref\": 10, \"my_norm\": 0.5}\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`normalizations` can also contain an array, which should be the same size as the axis or the field, for non-linear normalizations. Normalization by a function is to be developed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 7. How to store a field with multiple components\n", "It is more efficient to store all the **components** of a same field (e.g. $x$, $y$, $z$ components of a vector field, phases of a signal, etc.) in the same `Data` object. To do so, the `is_components` key can be used to easily recognize it, and strings can be used as values. In particular, using `is_components` ensures that no mathematical operation will be made on the axis values." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [], "source": [ "fieldA = np.ones(10)\n", "fieldB = np.ones(10) * 5\n", "fieldC = np.ones(10) * 10\n", "new_field = np.array([field, fieldB, fieldC])\n", "\n", "Phases = Data1D(name=\"phases\", unit=\"\", values=[\"Phase A\",\"Phase B\",\"Phase C\"], is_components=True)\n", "Field = DataTime(\n", " name=\"Example phase field\",\n", " symbol=\"X\",\n", " axes=[Phases, Time],\n", " values=new_field,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## 8. How to store a vector field\n", "The `VectorField` class allows to store the vector components of a field (for example Fx, Fy, Fz for a force) into a single object. A `VectorField` object has a `components` attribute which is a dictionary of `DataND` objects. It has built-in methods to extract the components in cartesian or in polar coordinates." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [], "source": [ "Field_x = DataTime(\n", " name=\"Example field x\",\n", " symbol=\"X_x\",\n", " axes=[Time],\n", " values=fieldA,\n", ")\n", "Field_y = DataTime(\n", " name=\"Example field y\",\n", " symbol=\"X_y\",\n", " axes=[Time],\n", " values=fieldB,\n", ")\n", "Field_z = DataTime(\n", " name=\"Example field z\",\n", " symbol=\"X_z\",\n", " axes=[Time],\n", " values=fieldC,\n", ")\n", "VectField = VectorField(\n", " name=\"Example vector field\",\n", " symbol=\"X\",\n", " components={\"comp_x\": Field_x, \"comp_y\": Field_y, \"comp_z\": Field_z}\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For cylindrical coordinates, the keys must be \"radial\", \"tangential\" and \"axial\"." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now that the `Data` objects have been created, we can:\n", "- [extract slices](https://nbviewer.jupyter.org/github/Eomys/SciDataTool/blob/master/Tutorials/tuto2_Slices.ipynb)\n", "- [plot data](https://nbviewer.jupyter.org/github/Eomys/SciDataTool/blob/master/Tutorials/tuto3_Plot.ipynb)" ] } ], "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" }, "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": "311.6px" }, "toc_section_display": false, "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }