Bridge Analysis#

This notebook demonstrates the use of PyCBA in conducting moving load and other analyses relevant to bridge analysis.

[1]:
import pycba as cba
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from IPython import display

Example 1 - Moving Load Envelope#

This example shows the basic interface for moving a vehicle across the bridge.

Consider a two-span continuous bridge, 50 m long:

[2]:
L = [25, 25]
EI = 30 * 1e11 * np.ones(len(L)) * 1e-6
R = [-1, 0, -1, 0, -1, 0]

and a three-axle vehicle with a 6 t steer axle, 6 m spacing back to the tandem of 12 t axles each with spacing 1.2 m:

[3]:
axle_spacings = np.array([6, 1.2])
axle_weights = np.array([6, 12, 12]) * 9.81  # t to kN

Now define the bridge_analysis object and add the bridge definition and vehicle definitions:

[4]:
bridge_analysis = cba.BridgeAnalysis()
bridge = bridge_analysis.add_bridge(L, EI, R)
vehicle = bridge_analysis.add_vehicle(axle_spacings, axle_weights)

Examine the vehicle at a single position, when the front axle is at 30.0 m say:

[5]:
bridge_analysis.static_vehicle(30.0, True);
../_images/notebooks_bridge_9_0.png

Now we run the vehicle over the bridge, returning the envelope of results. When run as a python script, the matplotlib figure will animate each result when plot_all=True.

[6]:
bridge_analysis.run_vehicle(0.5, plot_env=True, plot_all=False);
../_images/notebooks_bridge_11_0.png

Alternatively, using the reverse() method, we can do an analysis for the vehicle travelling in the reverse direction.

[7]:
vehicle.reverse()
bridge_analysis.static_vehicle(30.0, True)
[7]:
<pycba.results.BeamResults at 0x7fdac318a430>
../_images/notebooks_bridge_13_1.png

Example 2 - Critical Values and Positions#

[8]:
L = [37]
EI = 30 * 1e11 * np.ones(len(L)) * 1e-6
R = [-1, 0, -1, 0]
[9]:
bridge = cba.BeamAnalysis(L, EI, R)
bridge.npts = 500  # Use more points along the beam members
vehicle = cba.VehicleLibrary.get_m1600(6.25)
bridge_analysis = cba.BridgeAnalysis(bridge, vehicle)
env = bridge_analysis.run_vehicle(0.1)

From the envelope, we can extract the critical values of load effects, where they are located, and the vehicle position that caused it:

[10]:
cvals = bridge_analysis.critical_values(env)

and so if we are interested in the maximum bending moment in particular, we can interogate the results as follows:

[11]:
pos = cvals["Mmax"]["pos"][0]
at = cvals["Mmax"]["at"]
val = cvals["Mmax"]["val"]
print(f"Max moment is {val} kNm at {at:.2f} m when front axle position is {pos} m")
Max moment is 7809.3 kNm at 20.35 m when front axle position is 29.1 m

and confirm the results with a static analysis with the vehicle at that position:

[12]:
bridge_analysis.static_vehicle(pos, True);
../_images/notebooks_bridge_22_0.png

Example 3 - Critical M1600 Positions#

Here we consider the positioning of the M1600 vehicle to obtain maximum bending moment in simply-supported bridges of spans 15 to 40~m

First create the array to store the results and the span lengths.

[13]:
critical_axle_positions = []
critical_beam_location = []
spans = np.arange(15,40.5,0.5)

Next, create unchanging variables outside the loop, and then loop to find the critical positions:

[14]:
vehicle = cba.VehicleLibrary.get_m1600(6.25)
for L in spans:
    EI = 30 * 1e11 * 1e-6
    R = [-1, 0, -1, 0]
    bridge = cba.BeamAnalysis([L], EI, R)
    bridge.npts = 500  # Use more points along the beam members
    bridge_analysis = cba.BridgeAnalysis(bridge, vehicle)
    env = bridge_analysis.run_vehicle(0.05)
    cvals = bridge_analysis.critical_values(env)
    critical_axle_positions.append(cvals["Mmax"]["pos"][0])
    critical_beam_location.append(cvals["Mmax"]["at"])

And plot the results

[15]:
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(9,6), sharex=True)
ax1.plot(spans,critical_axle_positions,'k.:')
ax1.grid(which="both")
ax1.set_ylabel("Front axle position (m)")
ax1.set_title("Position of M1600 Vehicle for Maximum Moment");

ax2.plot(spans,spans/2 - critical_beam_location,'k.:')
ax2.grid(which="both")
ax2.set_title("Location of Maximum Moment")
ax2.set_ylabel("Distance from midspan (m)")
ax2.locator_params(axis='x', nbins=12)
ax2.set_xlabel("Bridge span (m)");
../_images/notebooks_bridge_28_0.png

Example 4 - Access Assessment for Single Vehicles#

This example considers the relative load effects between a reference vehicle and another vehicle. This type of analysis is commonly done to assess whether a new vehicle type would impose more onerous load effects on a bridge than some existing reference vehicle.

[16]:
L = [25, 25]
EI = 30 * 1e11 * np.ones(len(L)) * 1e-6
R = [-1, 0, -1, 0, -1, 0]
bridge = cba.BeamAnalysis(L, EI, R)

Here we use a suite of reference vehicles (the Australian ABAG B-doubles) to create a “super-envelope”: an envelope of the load effect envelopes from each of the 3 reference vehicles.

Firstly, obtain the vehicle from the VehicleLibrary and analyze for the envelope, appending it to the list of envelopes.

[17]:
envs = []
for i in range(3):
    vehicle = cba.VehicleLibrary.get_abag_bdouble(i)
    bridge_analysis = cba.BridgeAnalysis(bridge, vehicle)
    envs.append(bridge_analysis.run_vehicle(0.5))

Next, create a new zero-like envelope and augment it with the stored envelopes, such that the result is the envelope of envelopes:

[18]:
envenv = cba.Envelopes.zero_like(envs[0])
for e in envs:
    envenv.augment(e)
bridge_analysis.plot_envelopes(envenv)
../_images/notebooks_bridge_34_0.png

Now analyze the permit application vehicle; here just taking an example vehicle from the VehicleLibrary:

[19]:
vehicle = cba.VehicleLibrary.get_example_permit()
bridge_analysis = cba.BridgeAnalysis(bridge, vehicle)
trial_env = bridge_analysis.run_vehicle(0.5, True)
../_images/notebooks_bridge_36_0.png

And we can plot the ratios of trial to reference envelopes

[20]:
envr = bridge_analysis.envelopes_ratios(trial_env, envenv)
bridge_analysis.plot_ratios(envr)
../_images/notebooks_bridge_38_0.png

As can be seen in this case, the central support reaction is greater than 1.0, as is the hogging moment (by about 7%). This vehicle is unlikely to be granted a permit as a result.

Example 5 - Access Assessment for Vehicle Spacings#

Using the make_train method we create a Vehicle object representing a sequence of vehicles at different spacings. Here, we will explore different prime mover and platform trailer combinations commonly adopted in “superload” transport convoys.

First set up the bridge as per Example 3:

[21]:
L = [25, 25]
EI = 30 * 1e11 * np.ones(len(L)) * 1e-6
R = [-1, 0, -1, 0, -1, 0]
bridge = cba.BeamAnalysis(L, EI, R)

And now define the components of the superload:

[22]:
prime_mover = cba.Vehicle(axle_spacings=np.array([3.2,1.2]),
                          axle_weights=np.array([6.5,9.25,9.25])*9.81)
platform_trailer = cba.Vehicle(axle_spacings=np.array([1.8,]*9),
                               axle_weights=np.array([12]*10)*9.81)

Three spacing combinations are explored for a convoy comprising of two front prime movers followed by two platform trailers and then two back prime movers.

[23]:
inter_spaces = [np.array([5.0,6.3,8.0,6.0,4.8]),
               np.array([4.8,6.0,7.5,6.0,5.0]),
               np.array([5.0,6.3,8.0,6.3,5.0])]

Now, similar to Example 3, run the analysis for each set of spacings and store the envelopes:

[24]:
envs = []
for s in inter_spaces:
    vehicle = cba.make_train([prime_mover]*2 + [platform_trailer]*2 + [prime_mover]*2,spacings=s)
    bridge_analysis = cba.BridgeAnalysis(bridge, vehicle)
    envs.append(bridge_analysis.run_vehicle(0.1))

And now augment the envelopes to get the overall envelope:

[25]:
envenv = cba.Envelopes.zero_like(envs[0])
for e in envs:
    envenv.augment(e)
bridge_analysis.plot_envelopes(envenv)
../_images/notebooks_bridge_49_0.png

Note that this overall envelope can only show the extremes of reaction values, since the time histories of reactions are not compatible.

Example 6 - Rail loading#

Using the VehicleLibrary.get_la_rail method we create a 300LA train load and calculate the values for Appendix C of AS5100.2.

First, create the vehicle using the library method, here the defaults of 300LA, 12m axle-group spacing (centre-to-centre), and 10 No. axle groups (length 150~m) are adequate. Note that we could rationalize the number of axle groups for shorter spans to reduce computation, but this is just a refinement.

[26]:
vehicle = cba.VehicleLibrary.get_la_rail()

Next create the list of spans we wish to analyse (all simply-supported), and we’ll store the results for each span in a list for bending moment, shear, and reactions.

Here we’ll just do from 10 to 100~m spans in 10~m increments, but all 99 spans could be done.

[27]:
spans = np.arange(10,101,10)
M = []
V = []
R = []

Now loop over each span and calculate the load effects

[28]:
for s in spans:
    L = [s]
    # Simply-supported with arbitrary EI
    bridge = cba.BeamAnalysis(L, 30e6, [-1, 0, -1, 0])
    bridge.npts = 500  # Use more points along the beam
    bridge_analysis = cba.BridgeAnalysis(bridge, vehicle)
    env = bridge_analysis.run_vehicle(0.1)
    cvals = bridge_analysis.critical_values(env)
    m = cvals["Mmax"]["val"]
    v = max(cvals["Vmax"]["val"], abs(cvals["Vmin"]["val"]))
    r = max(cvals["Rmax0"]["val"], cvals["Rmax1"]["val"])
    # now round to nearest 5 as code does
    M.append(round(m / 5) * 5)
    V.append(round(v / 5) * 5)
    R.append(round(r / 5) * 5)

The reason for calculating the shear and reaction is that the vehicle step distance can cause differences. The 0.1~m value selected here is quite small, especially for longer bridges, and so could be different to that used for generating the values in the code. Between the shear and reaction results, the code values should be replicated.

To display the results in a nice way, we use pandas and create a dataframe.

[29]:
results = list(zip(spans, M, V, R))
columns = ["Span (m)", "Moment (kNm)", "Shear (kN)", "Reaction (kN)"]
df = pd.DataFrame(results,columns=columns,index=spans)

Let’s have a look at the results, without showing the redundant index:

[30]:
df.style.hide()
[30]:
Span (m) Moment (kNm) Shear (kN) Reaction (kN)
10 2400 1040 1050
20 6300 1515 1530
30 12515 2005 2015
40 21555 2505 2515
50 32560 3015 3025
60 46405 3525 3535
70 62385 4025 4035
80 81045 4525 4535
90 102740 5020 5030
100 126300 5515 5525

Example 7 - AS5100.2 Appendix C#

First we will write a function that accepts a vehicle, loops over a specified span range extracting the critical load effects, and returns a pandas dataframe of the results. We can then use this function for both road and rail.

The function accepts the low and high spans of the range to consider. While the code examines from 1 to 100 m spans, this takes a while to compute, so for the present purposes, facilitate considering a smaller range.

The function also accepts a UDL, necessary for the road bridges. This UDL is applied to the BeamAnalysis object and retained in the BridgeAnalysis object and applied throughout the vehicle traverse.

We will record both shear and reactions, which can vary slightly due to the step spacing of the load as it traverses the beam.

[31]:
def critical_effects(vehicle,low_span=1,high_span=100,udl=0):
    spans = np.arange(low_span,high_span+1,1)
    M = []
    V = []
    R = []
    for L in spans:
        # Simply-supported with arbitrary EI
        bridge = cba.BeamAnalysis([L], 30e6, [-1, 0, -1, 0])
        bridge.npts = 500  # Use more points along the beam
        bridge.add_udl(i_span=1,w=udl)  # Add any UDL
        bridge_analysis = cba.BridgeAnalysis(bridge, vehicle)
        env = bridge_analysis.run_vehicle(0.1)  # small step

        # Extract critical values
        cvals = bridge_analysis.critical_values(env)
        m = cvals["Mmax"]["val"]
        v = max(cvals["Vmax"]["val"], abs(cvals["Vmin"]["val"]))
        r = max(cvals["Rmax0"]["val"], cvals["Rmax1"]["val"])

        # now round to nearest 5 as code does
        M.append(round(m / 5) * 5)
        V.append(round(v / 5) * 5)
        R.append(round(r / 5) * 5)

    results = list(zip(spans, M, V, R))
    columns = ["Bridge_length", "m", "v", "r"]
    df = pd.DataFrame(results,columns=columns,index=spans)
    return df

Let’s examine the range 40 to 50 m here:

[32]:
low_span = 40
high_span = 50

First, we examine the road loading in Table C1 of the code, considering the moving and stationary load models seperately:

[33]:
m1600_vehicle = cba.VehicleLibrary.get_m1600(6.25)
df_m1600 = critical_effects(m1600_vehicle,low_span=low_span,high_span=high_span,udl=6)
df_m1600
[33]:
Bridge_length m v r
40 40 10070 1120 1120
41 41 10490 1130 1135
42 42 10910 1145 1150
43 43 11330 1160 1160
44 44 11755 1170 1175
45 45 12180 1180 1185
46 46 12605 1195 1195
47 47 13030 1205 1210
48 48 13465 1215 1220
49 49 13895 1225 1230
50 50 14325 1235 1240
[34]:
s1600_vehicle = cba.VehicleLibrary.get_s1600(6.25)
df_s1600 = critical_effects(s1600_vehicle,low_span=low_span,high_span=high_span,udl=24)
df_s1600
[34]:
Bridge_length m v r
40 40 10695 1145 1150
41 41 11180 1165 1165
42 42 11665 1185 1185
43 43 12160 1200 1205
44 44 12660 1220 1220
45 45 13165 1240 1240
46 46 13675 1255 1260
47 47 14195 1275 1275
48 48 14720 1290 1290
49 49 15245 1305 1310
50 50 15785 1325 1325

Next, let’s examine the 300LA results shown in Table C2 of the code.

[35]:
rail_vehicle = cba.VehicleLibrary.get_la_rail(axle_weight=300)
df_rail = critical_effects(rail_vehicle,low_span=low_span,high_span=high_span)
df_rail
[35]:
Bridge_length m v r
40 40 21555 2505 2515
41 41 22545 2555 2565
42 42 23535 2610 2620
43 43 24520 2665 2675
44 44 25510 2720 2735
45 45 26525 2775 2785
46 46 27590 2825 2840
47 47 28790 2875 2890
48 48 30000 2925 2935
49 49 31270 2970 2980
50 50 32560 3015 3025

From the results, it can be seen that the values in the code differ somewhat. Presumably this is because the values in the code were calculated using a coarser increment in the vehicle position for the traverse.