ckunte.net

Unconditional

While fixing an UnboundLocalError in my code today, I realized, I tint my scripts unconsciously with cyclomatic complexity — an avoidable habit I picked up as a young engineer. Back then, time-sharing was still very much a necessity, and so I’d spend much of my time buckling down and performing hand-calculations instead. Conditional problems worsened this, like for instance, referring to a table to pick an option, and then use its data to perform specific calculations. Also, results for ten options, when I needed for one or two, at the time felt both repetitive as well as unnecessary.

Scripts produced from thinking linearly tend to grow verbose is what I’ve come to realize. Whereas, knowing results of cases — I may not readily be interested-in — offers new insights, all sans extra labor. And so, running with an entire dataset, instead of one value contained within, gives me a high now.

Here is an example, in which I am trying to calculate energy absorbing capacity of a non-dented member for a number of end-conditions:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
impactcheck.py -- tube impact check
2013 ckunte
"""
from math import *
# Mechanical properties E and G (both in MPa)
E = 2.1E+5
G = 8000.0
def impactcheck():
    print "Impact check"
    print "D - Tube diameter (mm)"
    print "t - Tube thickness (mm)"
    print "L - Tube span (mm)"
    print "Fy - Yield strength of steel (MPa)"
    D, t, L, Fy = input("Enter D, t, L, Fy: ")
    D, t, L, Fy = float(D), float(t), float(L), float(Fy)
    # Section properties (A, I):
    A_tube = (pi / 4) * (D**2 - (D - 2 * t)**2)
    I_tube = (pi / 64) * (D**4 - (D - 2 * t)**4)
    print "From Design of Welded Structures O. Blodgett, Table 2: Impact formulas"
    print "Case 1: Simply supported concentrated load uniform section."
    print "Case 2: Fixed-ends concentrated load uniform section."
    print "Case 3: Cantilever concentrated load uniform section."
    print "Case 4: Axial tension uniform section."
    print "Case 5: Torsion: Rounded shaft."
    print "Case 6: Simply supported uniform load uniform section."
    print "Case 7: Fixed ends uniform load uniform section."
    print "Case 8: Cantilever uniform load uniform section."
    print "Case 9: Simply supported concentrated load variable section."
    case = input("Enter applicable case number: ")
    # All c values below are in mm (In Blodgett they are in inch)
    c = [4.2342, 6.7742, 2.54, 8.4582]
            if case == 1:
        U = (Fy**2 * I_tube * L) / (6 * E * c[0]**2)
    elif case == 2:
        U = (Fy**2 * I_tube * L) / (6 * E * c[0]**2)
    elif case == 3:
        U = (Fy**2 * I_tube * L) / (6 * E * c[0]**2)
    elif case == 4:
        U = (Fy**2 * A_tube * L) / (2 * E)
    elif case == 5:
        U = (Fy**2 * (D**2 + (D - 2 * t)**2) * A_tube * L) / (4 * G * D**2)
    elif case == 6:
        U = (4 * Fy**2 * I_tube * L) / (15 * E * c[1]**2)
    elif case == 7:
        U = (Fy**2 * I_tube * L) / (10 * E * c[2]**2)
    elif case == 8:
        U = (Fy**2 * I_tube * L) / (10 * E * c[2]**2)
    elif case == 9:
        U = (Fy**2 * I_tube * L) / (3 * E * c[3]**2)
    else:
        print "No available option was selected."
    print "Energy absorbing capacity -- nondented tube: ", U
if __name__ == '__main__':
    impactcheck()

As you can see, most visible parts of this code are all those waffling conditional checks, all for just one user-selected case. Here’s a simpler way, minimum overhead notwithstanding:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
impactcheck.py -- tube impact check
2015 ckunte
"""
from math import *
# Mechanical properties E and G (both in MPa)
E = 2.1E+5
G = 8000.0
def impactcheck():
    print "Impact check"
    print "D - Tube diameter (mm)"
    print "t - Tube thickness (mm)"
    print "L - Tube span (mm)"
    print "Fy - Yield strength of steel (MPa)"
    D, t, L, Fy = input("Enter D, t, L, Fy: ")
    D, t, L, Fy = float(D), float(t), float(L), float(Fy)
    # Section properties (A, I):
    A_tube = (pi / 4) * (D**2 - (D - 2 * t)**2)
    I_tube = (pi / 64) * (D**4 - (D - 2 * t)**4)
    print "From Design of Welded Structures O. Blodgett, Table 2: Impact formulas"
    print "Case 1: Simply supported concentrated load uniform section."
    print "Case 2: Fixed-ends concentrated load uniform section."
    print "Case 3: Cantilever concentrated load uniform section."
    print "Case 4: Axial tension uniform section."
    print "Case 5: Torsion: Rounded shaft."
    print "Case 6: Simply supported uniform load uniform section."
    print "Case 7: Fixed ends uniform load uniform section."
    print "Case 8: Cantilever uniform load uniform section."
    print "Case 9: Simply supported concentrated load variable section."
    # All c values below are in mm (In Blodgett they are in inch)
    c = [4.2342, 6.7742, 2.54, 8.4582]
    U1 = (Fy**2 * I_tube * L) / (6 * E * c[0]**2)
    U4 = (Fy**2 * A_tube * L) / (2 * E)
    U5 = (Fy**2 * (D**2 + (D - 2 * t)**2) * A_tube * L) / (4 * G * D**2)
    U6 = (4 * Fy**2 * I_tube * L) / (15 * E * c[1]**2)
    U7 = (Fy**2 * I_tube * L) / (10 * E * c[2]**2)
    U9 = (Fy**2 * I_tube * L) / (3 * E * c[3]**2)
    U = [U1, U1, U1, U4, U5, U6, U7, U7, U9]
    case = input("Enter applicable case number: ")
    # Data structure index starts with 0 in python, and so:
    i = case - 1
    # Energy for the user-selected case:
    print "Energy absorbing capacity -- nondented tube: ", U[i]
    print "Results for all cases:"
    print U
    pass
if __name__ == '__main__':
    impactcheck()

In the above, instead of if-then-else, I put all cases in to one basket, U, and then produce both user-selected case result, U[i], as well as for the entire list with print U. The fact that this also results in fewer lines of code is the icing. Here are the results:

$ python impactcheck.py                                                 
Impact check
D - Tube diameter (mm)
t - Tube thickness (mm)
L - Tube span (mm)
Fy - Yield strength of steel (MPa)
Enter D, t, L, Fy: 406.4, 15.9, 5000, 248
From Design of Welded Structures O. Blodgett, Table 2: Impact formulas
Case 1: Simply supported concentrated load uniform section.
Case 2: Fixed-ends concentrated load uniform section.
Case 3: Cantilever concentrated load uniform section.
Case 4: Axial tension uniform section.
Case 5: Torsion: Rounded shaft.
Case 6: Simply supported uniform load uniform section.
Case 7: Fixed ends uniform load uniform section.
Case 8: Cantilever uniform load uniform section.
Case 9: Simply supported concentrated load variable section.
Enter applicable case number: 7
Energy absorbing capacity -- nondented tube:  8453297881.03
Results for all cases:
[5069902650.473422, 5069902650.473422, 5069902650.473422, 14282101.356154371, 346717295.90146893, 3169175643.9842114, 8453297881.026284, 8453297881.026284, 2541068961.1612463]

The big picture

Given the number of variables, I could easily keep generating energy absorption capacities, considering each case, one after the other, and I would still not have a useful realization to stand back and see what causes increase in energy absorption capacity. Instead, here’s what I did: I nailed down Fy (to 248MPa, mild steel) and length (by setting it at 5.0m, which is typical for a boat landing king post, where operational or accidental impact is typically expected), and plot all cases per D/t (slenderness) ratio (from 20 to 120).

I’ve omitted (non-bending) cases 4 & 5 in the above, because they are not good candidates for comparison with the others.

Some conclusions

The plots all look the same, except for Energy (MJ) in ordinates, from which the following can be observed:

  1. Capacity increases when D/t (slenderness ratio) is lower, and
  2. Capacity increases when impact load is uniform over tube length
  3. Fixed and cantilever end-conditions (Case 7 & 8) perform better than simply-supported (Case 6).

From item 1 above, it’s pretty obvious that section compactness matters when it comes to resisting impact. Item 2 indicates that the more contact area there is during impact, the more the tube has the ability to absorb energy. More realistic of impact, however are the first three cases (for concentrated load), represented by the blue line, when there is less control over contact area.

Here is the code used to generate each plot above.

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
"""
ecap.py: Energy absorption capacity of non-dented tube
2015 ckunte 
"""
import numpy as np
import matplotlib.pyplot as plt
def main():
    # Mechanical properties (steel) in N/m^2:
    E = 2.1E+11
    G = 8.0E+9
    # Yield strength (N/m^2)
    Fy = [248.0E+6, 345.0E+6]
    Fy = Fy[0]
    # x corresponds to tube diameter, which we are going to 
    # vary with a constant D / t
    x = np.linspace(0.2, 0.9)
    r = [20.0, 50.0, 80.0, 120.0]
    t = (x / r[0])
    L = 5.0
    # Section properties
    A = np.pi * (x - t) * t
    I = (np.pi / 64.0) * ((x**4 - (x - (2 * t))**4))
    # Energy absorption capacity of non-dented tube (J)
    # All c values below are in m (In Blodgett they are in inch)
    c = [0.0042, 0.0068, 0.0025, 0.0085]
    U1 = (Fy**2 * I * L) / (6 * E * c[0]**2)
    U6 = (4 * Fy**2 * I * L) / (15 * E * c[1]**2)
    U7 = (Fy**2 * I * L) / (10 * E * c[2]**2)
    U9 = (Fy**2 * I * L) / (3 * E * c[3]**2)
    U = [U1, U6, U7, U9]
    fig, ax = plt.subplots()
    ax.plot(x, U[0] / 1E6, label="Cases 1-3", linewidth=2)
    ax.plot(x, U[1] / 1E6, label="Case 6", linewidth=2)
    ax.plot(x, U[2] / 1E6, label="Case 7 & 8", linewidth=2)
    ax.plot(x, U[3] / 1E6, label="Case 9", linewidth=2)
    ax.legend(loc=0)
    ax.set_xlabel('Tube diameter, D (m) with D/t = %s'%r[0])
    ax.set_ylabel('Energy (MJ)')
    plt.show()
if __name__ == '__main__':
    main()