Super-T Bridge Grillage Analysis#

This notebook demonstrates a complete grillage analysis workflow for a Super-T girder bridge:

  • Span: 33.5 m

  • Deck width: 11.565 m

  • Girders: 5 Super-T precast concrete girders

  • Loading: AS5100 M1600 traffic loading

The workflow covers model creation, load definition, moving load analysis, and result extraction.

Super-T bridge cross-section

The cross-section shows the five Super-T girders with a 180 mm RC slab and 65 mm asphalt overlay. The grillage node positions along the transverse (z) axis are shown at the bottom.

[1]:
import ospgrillage as og
import numpy as np
[2]:
kilo = 1e3
milli = 1e-3
N = 1
m = 1
mm = milli * m
m2 = m**2
m3 = m**3
m4 = m**4
kN = kilo * N
Pa = 1
MPa = N / mm**2
GPa = kilo * MPa
kPa = kilo * Pa

Materials#

[3]:
concrete = og.create_material(material="concrete", code="AS5100-2017", grade="65MPa")

Cross Sections#

The grillage model uses four section types corresponding to the structural elements of the bridge deck. Section properties are per-metre width for the transverse slab strips.

Grillage section types
[4]:
edge_longitudinal_section = og.create_section(
    A=0.934 * m2, J=0.1857 * m3, Iz=0.3478 * m4,
    Iy=0.213602 * m4, Az=0.444795 * m2, Ay=0.258704 * m2,
)
longitudinal_section = og.create_section(
    A=1.025 * m2, J=0.1878 * m3, Iz=0.3694 * m4,
    Iy=0.3634 * m4, Az=0.4979 * m2, Ay=0.309 * m2,
)
transverse_section = og.create_section(
    A=0.504 * m2, J=5.22303e-3 * m3, Iy=0.32928 * m4,
    Iz=1.3608e-3 * m4, Ay=0.42 * m2, Az=0.42 * m2,
    unit_width=True,
)
end_transverse_section = og.create_section(
    A=0.504 / 2 * m2, J=2.5e-3 * m3, Iy=2.73e-2 * m4,
    Iz=6.8e-4 * m4, Ay=0.21 * m2, Az=0.21 * m2,
    unit_width=True,
)

Grillage Members#

[5]:
longitudinal_beam = og.create_member(section=longitudinal_section, material=concrete)
edge_longitudinal_beam = og.create_member(section=edge_longitudinal_section, material=concrete)
transverse_slab = og.create_member(section=transverse_section, material=concrete)
end_transverse_slab = og.create_member(section=end_transverse_section, material=concrete)

Create the Grillage Model#

[6]:
L = 33.5 * m
w = 11.565 * m
n_l = 7
n_t = 11
edge_dist = 1.05 * m
angle = 0
[7]:
model = og.create_grillage(
    bridge_name="Super-T 33_5m",
    long_dim=L, width=w, skew=angle,
    num_long_grid=n_l, num_trans_grid=n_t,
    edge_beam_dist=edge_dist,
)
[8]:
model.set_member(longitudinal_beam, member="interior_main_beam")
model.set_member(longitudinal_beam, member="exterior_main_beam_1")
model.set_member(longitudinal_beam, member="exterior_main_beam_2")
model.set_member(edge_longitudinal_beam, member="edge_beam")
model.set_member(transverse_slab, member="transverse_slab")
model.set_member(end_transverse_slab, member="start_edge")
model.set_member(end_transverse_slab, member="end_edge")

Build and Visualise#

[9]:
model.create_osp_model(pyfile=False)
[10]:
og.plot_model(model, show_nodes=True, show_node_labels=True);
../_images/notebooks_super_t_tutorial_15_0.png

Dead Loads#

Girder Self-Weight#

[11]:
# Girder self-weight as line loads on each beam
beam_mag = 22.4 * kN / m
DL_self_weight = og.create_load_case(name="Super T self weight")

for z_pos in model.Mesh_obj.noz[1:-1]:
    p1 = og.create_load_vertex(x=0, z=z_pos, p=beam_mag)
    p2 = og.create_load_vertex(x=L, z=z_pos, p=beam_mag)
    beam_load = og.create_load(loadtype="line", point1=p1, point2=p2, name="girder SW")
    DL_self_weight.add_load(beam_load)

model.add_load_case(DL_self_weight)

Overlay Slab#

Area loads (e.g. self-weight of the overlay slab) are defined as patch loads using four load vertices.

Patch load definition
[12]:
overlay_p_mag = 4.32 * kN / m2
p1 = og.create_load_vertex(x=0, z=0, p=overlay_p_mag)
p2 = og.create_load_vertex(x=L, z=0, p=overlay_p_mag)
p3 = og.create_load_vertex(x=L, z=w, p=overlay_p_mag)
p4 = og.create_load_vertex(x=0, z=w, p=overlay_p_mag)

overlay_slab = og.create_load(loadtype="patch", name="overlay", point1=p1, point2=p2, point3=p3, point4=p4)
DL_overlay = og.create_load_case(name="Overlay self weight")
DL_overlay.add_load(overlay_slab)
model.add_load_case(DL_overlay)

Superimposed Dead Loads (SIDL)#

Asphalt surfacing and precast edge barriers.

[13]:
# Asphalt surfacing
asphalt_udl = 1.43 * kN / m2
p1 = og.create_load_vertex(x=0, z=0, p=asphalt_udl)
p2 = og.create_load_vertex(x=L, z=0, p=asphalt_udl)
p3 = og.create_load_vertex(x=L, z=w, p=asphalt_udl)
p4 = og.create_load_vertex(x=0, z=w, p=asphalt_udl)
asphalt_surfacing = og.create_load(loadtype="patch", name="asphalt surfacing", point1=p1, point2=p2, point3=p3, point4=p4)

# Edge barriers
barrier_udl = 6.54 * kN / m
left_barrier = og.create_load(
    loadtype="line", name="left barrier",
    point1=og.create_load_vertex(x=0, z=0, p=barrier_udl),
    point2=og.create_load_vertex(x=L, z=0, p=barrier_udl),
)
right_barrier = og.create_load(
    loadtype="line", name="right barrier",
    point1=og.create_load_vertex(x=0, z=w, p=barrier_udl),
    point2=og.create_load_vertex(x=L, z=w, p=barrier_udl),
)

SIDL = og.create_load_case(name="SIDL")
SIDL.add_load(asphalt_surfacing)
SIDL.add_load(left_barrier)
SIDL.add_load(right_barrier)
model.add_load_case(SIDL)

Traffic Loads — M1600#

The AS5100 M1600 load model consists of point loads (axle groups) and a uniform distributed load (UDL).

M1600 load model

Loads are applied to three design lanes across the deck width, each with a lane factor and a dynamic load allowance (DLA).

[14]:
# M1600 parameters
udl_line_load = 6 * kN / m
udl_width = 3.2 * m
udl_mag = udl_line_load / udl_width

# Lane positions
n_design_lanes = 3
x_coord = [0, 0, 0]
z_coord = [w / 2, w / 2 - 3.2 * m, w / 2 + 3.2 * m]

# Lane factors (AS5100)
alf = [1.0, 0.8, 0.4]
dla = 1.3  # dynamic load allowance

M1600 Load Cases per Lane#

Create static M1600 load cases for each lane, including both axle groups and UDL.

[15]:
gap = 6.25 * m
m1600_L1_lc = og.create_load_case(name="M1600 L1")
m1600_L2_lc = og.create_load_case(name="M1600 L2")
m1600_L3_lc = og.create_load_case(name="M1600 L3")
m1600_lc_list = [m1600_L1_lc, m1600_L2_lc, m1600_L3_lc]

M1600_moving_loads = []

for i in range(3):
    # Create vehicle from built-in M1600 generator
    M1600_vehicle_generator = og.create_load_model(model_type="M1600", gap=gap)
    M1600_vehicle_n = M1600_vehicle_generator.create()
    M1600_vehicle_n.set_global_coord(og.Point(x_coord[i], 0, z_coord[i]))
    m1600_lc_list[i].add_load(M1600_vehicle_n, load_factor=alf[i])
    M1600_moving_loads.append(M1600_vehicle_n)

    # UDL patch for this lane
    vertex_1 = og.create_load_vertex(x=-L, z=z_coord[i] - udl_width / 2, p=udl_mag)
    vertex_2 = og.create_load_vertex(x=2 * L, z=z_coord[i] - udl_width / 2, p=udl_mag)
    vertex_3 = og.create_load_vertex(x=2 * L, z=z_coord[i] + udl_width / 2, p=udl_mag)
    vertex_4 = og.create_load_vertex(x=-L, z=z_coord[i] + udl_width / 2, p=udl_mag)
    M1600_udl = og.create_load(
        loadtype="patch", name="M1600 Lane UDL",
        point1=vertex_1, point2=vertex_2, point3=vertex_3, point4=vertex_4,
    )
    m1600_lc_list[i].add_load(M1600_udl, load_factor=alf[i])
    model.add_load_case(m1600_lc_list[i], load_factor=dla)

Moving Load Analysis#

Define a path for the M1600 vehicles to traverse the bridge.

[16]:
start = og.create_point(x=-25, y=0, z=0)
end = og.Point(L, 0, 0)
m1600_path = og.create_moving_path(start_point=start, end_point=end)

moving_load_list = []
for i in range(3):
    name = f"Moving M1600 L{i+1}"
    moving_m1600 = og.create_moving_load(name=name)
    moving_m1600.set_path(m1600_path)
    moving_m1600.add_load(M1600_moving_loads[i])
    model.add_load_case(moving_m1600)
    moving_load_list.append(moving_m1600)

Analysis#

[17]:
model.analyze()

Results#

Results are returned as an xarray Dataset indexed by Loadcase, Node/Element, and Component.

[18]:
results = model.get_results()
results
[18]:
<xarray.Dataset> Size: 14MB
Dimensions:        (Loadcase: 156, Node: 77, Component: 30, Element: 136,
                    Nodes: 2)
Coordinates:
  * Loadcase       (Loadcase) <U53 33kB 'Super T self weight' ... 'Moving M16...
  * Node           (Node) int64 616B 1 2 3 4 5 6 7 8 ... 70 71 72 73 74 75 76 77
  * Component      (Component) <U9 1kB 'Mx_i' 'Mx_j' 'My_i' ... 'x' 'y' 'z'
  * Element        (Element) int64 1kB 1 2 3 4 5 6 7 ... 131 132 133 134 135 136
  * Nodes          (Nodes) <U1 8B 'i' 'j'
Data variables:
    displacements  (Loadcase, Node, Component) object 3MB nan nan ... 0.0
    velocity       (Loadcase, Node, Component) object 3MB nan nan ... nan nan
    acceleration   (Loadcase, Node, Component) object 3MB nan nan ... nan nan
    forces         (Loadcase, Element, Component) object 5MB -49532.626215054...
    ele_nodes      (Element, Nodes) object 2kB 1 2 2 3 3 4 ... 75 12 76 13 77 14
[19]:
# Extract elements and nodes for exterior beam (Beam 1)
member_name = "exterior_main_beam_1"
ext_beam_elements = model.get_element(member=member_name, options="elements")
ext_beam_nodes = model.get_element(member=member_name, options="nodes")
print(f"Beam 1 elements: {ext_beam_elements}")
print(f"Beam 1 nodes: {ext_beam_nodes[0]}")
Beam 1 elements: [20, 33, 46, 59, 72, 85, 98, 111, 124, 131]
Beam 1 nodes: [2, 16, 23, 30, 37, 44, 51, 58, 65, 72, 9]

Plotting Results#

Use backend="plotly" to see load effects on all main beams in an interactive 3D view. The member parameter accepts an og.Members flag to control which member groups appear. Here we use og.Members.LONGITUDINAL for the BMD and SFD to show only the main beams (filtering out transverse slab strips), while the deflection plot includes all members.

[20]:
og.plot_bmd(model, results, members=og.Members.LONGITUDINAL, loadcase="M1600 L1", backend="plotly", figsize=(14, 8));
[21]:
og.plot_sfd(model, results, members=og.Members.LONGITUDINAL, loadcase="M1600 L1", backend="plotly", figsize=(14, 8));
[22]:
og.plot_tmd(model, results, loadcase="M1600 L1", backend="plotly", figsize=(14, 8));
[23]:
og.plot_def(model, results, loadcase="M1600 L1", backend="plotly", figsize=(14, 8));

Load Combinations#

Define load factors and compute combined results.

[24]:
DL_factor = 1.0
LL_factor = 1.0
SIDL_factor = 1.3

load_combinations = {
    "Super T self weight": DL_factor,
    "M1600 L1": LL_factor,
    "M1600 L2": LL_factor,
    "M1600 L3": LL_factor,
    "SIDL": SIDL_factor,
    "Overlay self weight": DL_factor,
}

combination_results = model.get_results(combinations=load_combinations)
combination_results
[24]:
<xarray.Dataset> Size: 93kB
Dimensions:        (Node: 77, Component: 30, Element: 136, Nodes: 2)
Coordinates:
  * Node           (Node) int64 616B 1 2 3 4 5 6 7 8 ... 70 71 72 73 74 75 76 77
  * Component      (Component) <U9 1kB 'Mx_i' 'Mx_j' 'My_i' ... 'x' 'y' 'z'
  * Element        (Element) int64 1kB 1 2 3 4 5 6 7 ... 131 132 133 134 135 136
  * Nodes          (Nodes) <U1 8B 'i' 'j'
Data variables:
    displacements  (Node, Component) object 18kB nan nan ... 0.0
    velocity       (Node, Component) object 18kB nan nan nan nan ... nan nan nan
    acceleration   (Node, Component) object 18kB nan nan nan nan ... nan nan nan
    forces         (Element, Component) object 33kB -190594.99977261 ... nan
    ele_nodes      (Element, Nodes) object 2kB 6.3 12.6 12.6 ... 81.9 485.1 88.2
[25]:
comb_bending = combination_results.forces.sel(Component="Mz_i", Element=ext_beam_elements)
print(f"Load combination max BM = {max(comb_bending.values / 1000):.2f} kN m")
Load combination max BM = 8056.09 kN m

Summary#

This notebook demonstrated a complete Super-T bridge grillage analysis workflow:

  1. Created materials, sections, and members

  2. Built the grillage model with create_grillage()

  3. Defined dead, superimposed dead, and traffic (M1600) loads

  4. Set up moving load analysis

  5. Ran the analysis and extracted results

  6. Plotted bending moments and deflections

  7. Computed load combinations

For advanced result processing including envelopes and xarray manipulation, see the Advanced Results notebook.