The Signal Broadcaster
Motivation
pyLife tries to provide a flexible API for its functionality with respect to sizes of the datasets involved. No matter if you want to perform some calculation on just a single value or on a whole FEM-mesh. No matter if you want to calculate the damage that a certain load amplitude does on a certain material, or if you have a FEM-mesh with different materials associated with to element and every node has its own rainflow matrix.
Example
Take for example the function
cycles()
. Imagine you have a
single Wöhler curve dataset like
import pandas as pd
from pylife.materiallaws import WoehlerCurve
woehler_curve_data = pd.Series({
'k_1': 7.0,
'ND': 2e5,
'SD': 320.0,
'TN': 2.3,
'TS': 1.25
})
woehler_curve_data
k_1 7.00
ND 200000.00
SD 320.00
TN 2.30
TS 1.25
dtype: float64
Now you can calculate the cycles along the Basquin equation for a single load value:
woehler_curve_data.woehler.cycles(load=350.)
array(106807.93865297)
Now let’s say, you have different loads for each element_id if your FEM-mesh:
amplitude = pd.Series([320., 340., 330., 320.], index=pd.Index([1, 2, 3, 4], name='element_id'))
amplitude
element_id
1 320.0
2 340.0
3 330.0
4 320.0
dtype: float64
cycles()
now gives you a result
for every element_id.
woehler_curve_data.woehler.cycles(load=amplitude)
element_id
1 200000.000000
2 130836.050152
3 161243.517913
4 200000.000000
dtype: float64
In the next step, even the Wöhler curve data is different for every element, like for example for a hardness gradient in your component:
woehler_curve_data = pd.DataFrame({
'k_1': 7.0,
'ND': 2e5,
'SD': [370., 320., 280, 280],
'TN': 2.3,
'TS': 1.25
}, index=pd.Index([1, 2, 3, 4], name='element_id'))
woehler_curve_data
k_1 | ND | SD | TN | TS | |
---|---|---|---|---|---|
element_id | |||||
1 | 7.0 | 200000.0 | 370.0 | 2.3 | 1.25 |
2 | 7.0 | 200000.0 | 320.0 | 2.3 | 1.25 |
3 | 7.0 | 200000.0 | 280.0 | 2.3 | 1.25 |
4 | 7.0 | 200000.0 | 280.0 | 2.3 | 1.25 |
In this case the broadcaster determines from the identical index name element_id that the two structures can be aligned, so every element is associated with its load and with its Wöhler curve:
woehler_curve_data.woehler.cycles(load=amplitude)
element_id
1 inf
2 1.308361e+05
3 6.331967e+04
4 7.853918e+04
dtype: float64
In another case we assume that you have a Wöhler curve associated to every element, and the loads are constant throughout the component but different for different load scenarios.
amplitude_scenarios = pd.Series([320., 340., 330., 320.], index=pd.Index([1, 2, 3, 4], name='scenario'))
amplitude_scenarios
scenario
1 320.0
2 340.0
3 330.0
4 320.0
dtype: float64
In this case the broadcaster makes a cross product of load scenario and element_id, i.e. for every element_id for every load scenario the allowable cycles are calculated:
woehler_curve_data.woehler.cycles(load=amplitude_scenarios)
element_id scenario
1 1 inf
2 inf
3 inf
4 inf
2 1 2.000000e+05
2 1.308361e+05
3 1.612435e+05
4 2.000000e+05
3 1 7.853918e+04
2 5.137878e+04
3 6.331967e+04
4 7.853918e+04
4 1 7.853918e+04
2 5.137878e+04
3 6.331967e+04
4 7.853918e+04
dtype: float64
As is very uncommon that the load is constant all over the component like in the previous example we now consider an even more complex one. Let’s say we have a different load scenarios, which give us for every element_id multiple load scenarios:
amplitude_scenarios = pd.Series(
[320., 340., 330., 320, 220., 240., 230., 220, 420., 440., 430., 420],
index=pd.MultiIndex.from_tuples([
(1, 1), (1, 2), (1, 3), (1, 4),
(2, 1), (2, 2), (2, 3), (2, 4),
(3, 1), (3, 2), (3, 3), (3, 4)
], names=['scenario', 'element_id']))
amplitude_scenarios
scenario element_id
1 1 320.0
2 340.0
3 330.0
4 320.0
2 1 220.0
2 240.0
3 230.0
4 220.0
3 1 420.0
2 440.0
3 430.0
4 420.0
dtype: float64
Now the broadcaster still aligns the element_id:
woehler_curve_data.woehler.cycles(load=amplitude_scenarios)
scenario element_id
1 1 inf
2 1.308361e+05
3 6.331967e+04
4 7.853918e+04
2 1 inf
2 inf
3 inf
4 inf
3 1 8.235634e+04
2 2.152341e+04
3 9.927892e+03
4 1.170553e+04
dtype: float64
Note that in the above examples the call was always identical
woehler_curve_data.woehler.cycles(load=...)
That means that when you write a module for a certain functionality you don’t need to know if your code later on receives a single value parameter or a whole FEM-mesh. Your code will take both and handle them.
Usage
As you might have seen, we did not call the pylife.Broadcaster
in the
above code snippets directly. And that’s the way it’s meant to be. When you
are on the level that you simply want to use pyLife’s functionality to perform
calculations, you should not be required to think about how to broadcast your
datasets to one another. It should simply happen automatically. In our
example the the calls to the pylife.Broadcaster
are done inside
cycles()
.
You do need to deal with the pylife.Broadcaster
when you implement new
calculation methods. Let’s go through an example.
Todo
Sorry, this is still to be written.