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.
This tutorial explains how to extract parts of a field, converted to a specified unit, normalized, and transformed to the Fourier domain.
Once the data has been stored into Data
objects, 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.
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:
# import SciDataTool objects
from SciDataTool import Data1D, DataLinspace, DataTime, DataFreq, VectorField
import numpy as np
import plotly.graph_objects as go
import plotly.io as pio
pio.renderers.default = 'notebook_connected'
f = 50
Time = DataLinspace(
name="time",
unit="s",
initial=0,
final=1/f,
number=10,
include_endpoint=False,
)
Angle = DataLinspace(
name="angle",
unit="rad",
initial=0,
final=2*np.pi,
number=20,
include_endpoint=False,
)
ta, at = np.meshgrid(Time.get_values(), Angle.get_values())
field = 5 * np.cos(2*np.pi*f*ta + 3*at)
Field = DataTime(
name="Example field",
symbol="X",
axes=[Angle, Time],
values=field,
)
#---------------------------------------------------------------
# Extract a slice at t=0.01s
results = Field.get_along("time=0.01", "angle{°}")
#---------------------------------------------------------------
print(results)
{'time': array([0.01]), 'angle': array([ 0., 18., 36., 54., 72., 90., 108., 126., 144., 162., 180., 198., 216., 234., 252., 270., 288., 306., 324., 342.]), 'X': array([-5.00000000e+00, -2.93892626e+00, 1.54508497e+00, 4.75528258e+00, 4.04508497e+00, 1.53080850e-15, -4.04508497e+00, -4.75528258e+00, -1.54508497e+00, 2.93892626e+00, 5.00000000e+00, 2.93892626e+00, -1.54508497e+00, -4.75528258e+00, -4.04508497e+00, -1.22495629e-14, 4.04508497e+00, 4.75528258e+00, 1.54508497e+00, -2.93892626e+00]), 'axes_list': [<SciDataTool.Classes.RequestedAxis.RequestedAxis object at 0x00000216F0130EB0>, <SciDataTool.Classes.RequestedAxis.RequestedAxis object at 0x00000216F0130E20>], 'axes_dict_other': {}}
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.
# Plot
fig = go.Figure()
fig.add_trace(go.Scatter(
x=results["angle"],
y=results["X"],
))
fig.update_layout(
title={
'text': Field.name + " as a function of space",
'y':0.9,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'},
xaxis_title="Angle [°]",
yaxis_title=r"$"+Field.symbol+" ["+Field.unit+"]$",
)
fig.show(config = {"displaylogo":False})
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.
The syntax for requesting axes has been designed to be as intuitive as possible. Hereafter is the list of the available syntaxes:
"time{ms}"
"time[0]"
, "time[-1]"
, "time[0,3,6]"
, "time[0:5]"
"time=5"
, "angle=[0,pi/2]"
, "angle>pi"
"time=sum"
"time=rss"
"time=mean"
"time=rms"
"time=integrate"
"time[oneperiod]"
, "time[antiperiod]"
, "time[smallestperiod]"
"time=axis_data", axis_data={"time": np.array([0,0.5,1,1.5,2])}
"freqs->elec_order"
The unit of the axis can be added to any of the previous strings as {unit}
(e.g. "angle[-1]{°}"
).
If nothing is specified regarding an axis from the axes
list, a slice at its first value will be extracted.
The following examples show different syntaxes:
angle_data = np.linspace(0, 360, 100, endpoint=False)
#---------------------------------------------------------------
# Extract slices
results2 = Field.get_along("angle{°}")
results3 = Field.get_along("time[-1]", "angle=axis_data{°}", axis_data={"angle":angle_data})
#---------------------------------------------------------------
# Plot
fig = go.Figure()
fig.add_trace(go.Scatter(
x=results["angle"],
y=results["X"],
name="t=0.01s"
))
fig.add_trace(go.Scatter(
x=results2["angle"],
y=results2["X"],
name="t[0]"
))
fig.add_trace(go.Scatter(
x=results3["angle"],
y=results3["X"],
name="t[-1]"
))
fig.update_layout(
title={
'text': Field.name + " as a function of space at three instants",
'y':0.9,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'},
xaxis_title="Angle [°]",
yaxis_title=r"$"+Field.symbol+" ["+Field.unit+"]$",
)
fig.show(config = {"displaylogo":False})
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:
"time"
↔ "freqs"
"angle"
↔ "wavenumber"
Other correspondances may be defined in the axes_dict
and rev_axes_dict
located in the __init__
file of the Functions
folder.
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.
The frequencies can be converted to orders (electrical orders, space orders, mechanical orders, etc.) using the normalizations
dictionary (see example below).
#---------------------------------------------------------------
# Extract slices
results4 = Field.get_magnitude_along("freqs>0")
#---------------------------------------------------------------
fig = go.Figure()
fig.add_trace(go.Bar(x=results4["freqs"],
y=results4["X"],
))
fig.update_layout(
title={
'text': "FFT of " + Field.name,
'y':0.9,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'},
xaxis_title="Frequency [Hz]",
yaxis_title=r"$|\hat{"+Field.symbol+"}| ["+Field.unit+"]$",
)
fig.show(config = {"displaylogo":False})
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).
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.
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:
# Add normalization values
Field.normalizations["ref"] = 0.8
Time.normalizations["elec_order"] = 50
#---------------------------------------------------------------
# Extract slices
results5 = Field.get_magnitude_along("freqs->elec_order=[0,10]", is_norm=True)
#---------------------------------------------------------------
# Plot
fig = go.Figure()
fig.add_trace(go.Bar(x=results5["freqs"],
y=results5["X"],
))
fig.update_layout(
title={
'text': "FFT of " + Field.name,
'y':0.9,
'x':0.5,
'xanchor': 'center',
'yanchor': 'top'},
xaxis_title="Electrical orders []",
yaxis_title=r"$|\hat{"+Field.symbol+"}| ["+Field.unit+"]$",
)
fig.show(config = {"displaylogo":False})
get_along
methods¶The get_along
methods encapsulate several important steps. Hereafter is a summarized description of each of these steps:
Read the axes requested by the user
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
).
Get the field (reconstruct anti-period at this point if a fft is requested).
Get the inverse Fourier transform if requested.
The slices of the field are extracted (single index or values, or interval of indices) along time/space axes.
Get the Fourier transform if requested.
The slices of the field are extracted (single index or values, or interval of indices) along Fourier axes.
The full field is reconstructed according to the axes symmetries.
The field is interpolated over the specified axis values.
The field is converted (unit and normalizations).
The return dict is built.