Supplying Your Own Limb Properties

Some analysis functions calculate their results from a series of properties per limb profile point. Usually, when the underlying model permits some sort of latitude, like accounting for the cross-section the limb has at each point, the analysis function will accept another function to calculate these properties for each profile point. (The beckhoff1964() analysis function, for example, accepts this in its shape keyword argument.)

The downside to that is that one cross-sectional shape gets assumed for each profile point. For many bows, this is a good enough approximation. However, in some scenarios or for some bows, more accuracy is required and properties have to be calculated on a per-profile basis.

There are two ways to go about this:

The Completely Manual Approach

Obviously, you get the most direct control when you look up the properties an analysis function calculates, and supply them manually. So, for beckhoff1964(), you would be required to provide second moment of area in column I and the distance from neutral plane to belly in column e_belly:

>>> my_df # the DataFrame with just the bare necessities
       l  width  thickness
0    0.0    1.2        0.9
1   10.0    1.6        1.0
...
10  80.0    3.6        2.1
>>> my_df['I'] = [0.05, 0.1, ..., 1.9]
>>> my_df
       l  width  thickness     I
0    0.0    1.2        0.9  0.05
1   10.0    1.6        1.0  0.10
...
10  80.0    3.6        2.1  1.90

…and so on, until all required columns are filled with values. If you then do:

>>> results, _ = pb.analysis.beckhoff1964(my_df, some_moe, some_mor)

The Beckhoff analysis will use your supplied values.

Pros:

  • Complete control
  • Derive properties by whichever method you desire

Cons:

  • Manual filling is a lot of work
  • Need to derive all the properties manually

The Function-aided Approach

If using geometrical shapes makes a good enough approximation, you can leverage some automation by using the provided property functions, or write your own. Say your bow has a profile change somewhere in the limb, tapering from a rounded-back/flat-belly shape in the inner limb to a narrowed rectangular shape, you can fill them in this way:

>>> my_df # note the ‘shoulder’ between rows 3 and 4
      l  width  thickness
0   0.0    0.9       1.30
1   7.0    1.0       1.40
2  12.0    1.0       1.50
3  19.0    1.1       1.50
4  22.0    2.7       1.10
5  33.0    3.1       1.15
6  45.0    3.4       1.20
7  57.0    3.5       1.40
8  55.0    2.1       2.50
9  60.0    2.1       2.60
>>> # step 1: rectangular I for the outer limb
>>> my_df.loc[0:3, 'I'] = pb.rect_I(df['width'],
...                                 df['thickness'])
>>> my_df
      l  width  thickness         I
0   0.0    0.9       1.30  0.164775
1   7.0    1.0       1.40  0.228667
2  12.0    1.0       1.50  0.281250
3  19.0    1.1       1.50  0.309375
4  22.0    2.7       1.10       NaN
5  33.0    3.1       1.15       NaN
6  45.0    3.4       1.20       NaN
7  57.0    3.5       1.40       NaN
8  55.0    2.1       2.50       NaN
9  60.0    2.1       2.60       NaN
>>> # step 2: rectangular I for the handle
>>> my_df.loc[7:9, 'I'] = pb.rect_I(my_df['width'],
...                                 my_df['thickness'])
>>> my_df
      l  width  thickness         I
0   0.0    0.9       1.30  0.164775
1   7.0    1.0       1.40  0.228667
2  12.0    1.0       1.50  0.281250
3  19.0    1.1       1.50  0.309375
4  22.0    2.7       1.10       NaN
5  33.0    3.1       1.15       NaN
6  45.0    3.4       1.20       NaN
7  57.0    3.5       1.40  0.800333
8  55.0    2.1       2.50  2.734375
9  60.0    2.1       2.60  3.075800
>>> # step 3: half-round I for the remainder
>>> my_df.loc[4:6, 'I'] = pb.halfround_I(my_df['width'],
...                                      my_df['thickness'])
>>> my_df
      l  width  thickness         I
0   0.0    0.9       1.30  0.164775
1   7.0    1.0       1.40  0.228667
2  12.0    1.0       1.50  0.281250
3  19.0    1.1       1.50  0.309375
4  22.0    2.7       1.10  0.197217
5  33.0    3.1       1.15  0.258736
6  45.0    3.4       1.20  0.322422
7  57.0    3.5       1.40  0.800333
8  55.0    2.1       2.50  2.734375
9  60.0    2.1       2.60  3.075800

…and so on until you filled all the columns you need to fill.

Pros:

  • No calculating by hand
  • More accurate representation than one cross-sectional shape for every profile point

Cons:

  • Manually determining ‘areas of effect’ for different cross-section functions can still be too much work to apply on a large corpus
  • Only as accurate as the geometric shape approximates actual limb cross-section

Bonus: Bundle Functions

In addition to the simple limb property functions, there are also bundle functions that may make your life easier. Instead of filling column by column, first column I with a function rect_I() and then column e_belly with function rect_e_belly(), for example, you can fill multiple columns by using the bundle function rect() that takes a list of properties as an additional argument:

>>> my_df['I'], my_df['e_belly'] = pb.rect(my_df['width'],
...                                        my_df['thickness'],
...                                        ['I', 'e_belly'])

Syntax can become a bit convoluted when you do multiple things at once like this, but the option might come in handy.