Square Silo#

../../../../_images/square_silo.png

Modelling a square steel silo on a braced leg tower and reading the bending moments out of its hopper:

  • Generate the whole model from a handful of dimensions, walking the four faces

  • Build the leg tower with a bracing ring and a V-brace per face

  • Cap it with a bin shell, a roof and a pyramidal hopper down to a square outlet

  • Fill the hopper with a solid of bulk material, whose weight loads the panels

  • Support the legs on elastic foundation springs

  • Read the surface internal forces for the four hopper panels only

Keywords:
square silo surfaces solid bulk material thickness member hinge rib nodal support second order surface results
from math import inf, pi
from dlubal.api import rfem

# -------------------------------------------------------
# This example models a square steel silo on a braced leg
# tower and reads the bending moments out of its hopper
# panels.
#
# The whole model is generated from the parameters below.
# Everything is square in plan and symmetric about both
# axes, so each part is built by walking the four faces
# rather than by listing coordinates: change PLAN_SIZE or
# any level and the nodes, lines, members and surfaces
# follow.
#
# RFEM's global Z axis points DOWN, so a level h metres
# above the ground is entered as z = -h. The helper
# functions below take heights and do that conversion in
# one place.
# -------------------------------------------------------

# Editable parameters (SI units)
MODEL_NAME = 'square_silo'

# Geometry, as heights above the leg bases
PLAN_SIZE = 3.0                  # square silo, outside dimension [m]
BRACING_HEIGHT = 3.0             # horizontal bracing ring, mid-height of the legs [m]
FLOOR_HEIGHT = 6.0               # top of the legs, underside of the bin [m]
ROOF_HEIGHT = 12.0               # top of the bin [m]
OUTLET_SIZE = 1.5                # square outlet, side length [m]
OUTLET_HEIGHT = 4.0              # outlet above the leg bases [m]

# Materials and sections
STEEL = "S450 | EN 1993-1-1:2005-05"
BULK_SOLID = "Sand, well-graded (SW) | DIN 18196:2011-05"
SECTION_LEG = "IPN 300"
SECTION_FLOOR_RIB = "UPE 200"
SECTION_BRACING = "MSH KHP 88.9x3.6"
SECTION_BIN_POST = "LU 300/200/10/10/0"

# Plate thicknesses
THICKNESS_HOPPER = 0.010         # [m], the hopper works hardest
THICKNESS_SHELL = 0.008          # [m], bin walls and the outlet plate
THICKNESS_ROOF = 0.005           # [m]

# Elastic foundation under each leg base
SUPPORT_SPRING_HORIZONTAL = 700000.0    # [N/m]
SUPPORT_SPRING_VERTICAL = 5000000.0     # [N/m]

# Torsional spring in the bracing connections
HINGE_TORSIONAL_STIFFNESS = 28000.0     # [Nm/rad]

# Object numbers. Each group owns a block so the generated numbers stay
# readable and the surfaces can refer to line numbers without a lookup.
N_LEG_BASE, N_BRACING, N_FLOOR, N_OUTLET, N_ROOF = 1, 5, 13, 21, 25
L_LEG_LOWER, L_LEG_UPPER, L_FLOOR_RING = 1, 5, 9
L_BRACE_LOWER, L_BRACE_UPPER, L_BRACE_RING = 17, 25, 33
L_OUTLET, L_VALLEY, L_POST, L_ROOF = 41, 45, 49, 53

FACES = range(4)                 # the four sides of the square, everywhere


def corner(k, size, height):
    """Corner k of a square of the given size, centred in plan on PLAN_SIZE."""
    inset = (PLAN_SIZE - size) / 2
    a, b = inset, PLAN_SIZE - inset
    x, y = [(a, a), (b, a), (b, b), (a, b)][k % 4]
    return x, y, -height


def ring_point(i, height):
    """Point i of the eight-point perimeter: corner, edge midpoint, corner, ...

    Even i are the corners, odd i the midpoints the bracing runs up to.
    """
    if i % 2 == 0:
        return corner(i // 2, PLAN_SIZE, height)
    x0, y0, _ = corner(i // 2, PLAN_SIZE, height)
    x1, y1, _ = corner(i // 2 + 1, PLAN_SIZE, height)
    return (x0 + x1) / 2, (y0 + y1) / 2, -height


def node(no, point):
    x, y, z = point
    return rfem.structure_core.Node(no=no, coordinate_1=x, coordinate_2=y, coordinate_3=z)


def line(no, start, end):
    return rfem.structure_core.Line(no=no, definition_nodes=[start, end])


def define_structure() -> list:
    """Materials, sections, plate thicknesses and the whole geometry."""

    objects = [
        rfem.structure_core.Material(no=1, name=STEEL),
        rfem.structure_core.Material(no=2, name=BULK_SOLID),

        rfem.structure_core.CrossSection(no=1, material=1, name=SECTION_LEG),
        rfem.structure_core.CrossSection(no=2, material=1, name=SECTION_FLOOR_RIB),
        rfem.structure_core.CrossSection(no=3, material=1, name=SECTION_BRACING),
        rfem.structure_core.CrossSection(no=4, material=1, name=SECTION_BIN_POST),

        # Torsional spring at both ends of every braced member. The second
        # hinge also frees minor-axis bending, which is what makes the bracing
        # act as a strut rather than a frame member.
        rfem.types_for_members.MemberHinge(
            no=1,
            axial_release_n=inf, axial_release_vy=inf, axial_release_vz=inf,
            moment_release_mt=HINGE_TORSIONAL_STIFFNESS),
        rfem.types_for_members.MemberHinge(
            no=2,
            axial_release_n=inf, axial_release_vy=inf, axial_release_vz=inf,
            moment_release_mt=HINGE_TORSIONAL_STIFFNESS, moment_release_mz=inf),
    ]

    # --- nodes ---------------------------------------------------------------
    objects += [node(N_LEG_BASE + k, corner(k, PLAN_SIZE, 0.0)) for k in FACES]
    objects += [node(N_BRACING + i, ring_point(i, BRACING_HEIGHT)) for i in range(8)]
    objects += [node(N_FLOOR + i, ring_point(i, FLOOR_HEIGHT)) for i in range(8)]
    objects += [node(N_OUTLET + k, corner(k, OUTLET_SIZE, OUTLET_HEIGHT)) for k in FACES]
    objects += [node(N_ROOF + k, corner(k, PLAN_SIZE, ROOF_HEIGHT)) for k in FACES]

    # --- lines ---------------------------------------------------------------
    # Legs, in two lifts so the bracing ring has something to frame into.
    objects += [line(L_LEG_LOWER + k, N_LEG_BASE + k, N_BRACING + 2 * k) for k in FACES]
    objects += [line(L_LEG_UPPER + k, N_BRACING + 2 * k, N_FLOOR + 2 * k) for k in FACES]
    # The eight floor-ring segments, which are also the bin wall and hopper edges.
    objects += [line(L_FLOOR_RING + i, N_FLOOR + i, N_FLOOR + (i + 1) % 8) for i in range(8)]
    # Vertical bracing: a V per face, springing from the two corners up to the
    # edge midpoint of the level above.
    for f in FACES:
        objects += [
            line(L_BRACE_LOWER + 2 * f, N_LEG_BASE + f, N_BRACING + 2 * f + 1),
            line(L_BRACE_LOWER + 2 * f + 1, N_LEG_BASE + (f + 1) % 4, N_BRACING + 2 * f + 1),
            line(L_BRACE_UPPER + 2 * f, N_BRACING + 2 * f, N_FLOOR + 2 * f + 1),
            line(L_BRACE_UPPER + 2 * f + 1, N_BRACING + 2 * ((f + 1) % 4), N_FLOOR + 2 * f + 1),
        ]
    objects += [line(L_BRACE_RING + i, N_BRACING + i, N_BRACING + (i + 1) % 8) for i in range(8)]
    # Hopper, bin posts and roof.
    objects += [line(L_OUTLET + k, N_OUTLET + k, N_OUTLET + (k + 1) % 4) for k in FACES]
    objects += [line(L_VALLEY + k, N_FLOOR + 2 * k, N_OUTLET + k) for k in FACES]
    objects += [line(L_POST + k, N_FLOOR + 2 * k, N_ROOF + k) for k in FACES]
    objects += [line(L_ROOF + k, N_ROOF + k, N_ROOF + (k + 1) % 4) for k in FACES]

    # --- members -------------------------------------------------------------
    braced = dict(member_hinge_start=1, member_hinge_end=2)
    no = 1
    for k in FACES:                                      # legs, lower lift
        objects.append(rfem.structure_core.Member(
            no=no, line=L_LEG_LOWER + k, cross_section_start=1, **braced)); no += 1
    for k in FACES:                                      # legs, upper lift
        objects.append(rfem.structure_core.Member(
            no=no, line=L_LEG_UPPER + k, cross_section_start=1, **braced)); no += 1
    for i in range(8):                                   # floor ring, ribs in the plate
        objects.append(rfem.structure_core.Member(
            no=no, line=L_FLOOR_RING + i, cross_section_start=2,
            type=rfem.structure_core.Member.TYPE_RIB)); no += 1
    for i in range(16):                                  # vertical bracing
        objects.append(rfem.structure_core.Member(
            no=no, line=L_BRACE_LOWER + i, cross_section_start=3, **braced)); no += 1
    for i in range(8):                                   # horizontal bracing ring
        # The torsional hinge sits at the CORNER end of each segment, so it
        # alternates between the start and the end as the ring is walked.
        hinge = dict(member_hinge_start=1) if i % 2 == 0 else dict(member_hinge_end=2)
        objects.append(rfem.structure_core.Member(
            no=no, line=L_BRACE_RING + i, cross_section_start=3, **hinge)); no += 1
    for k in FACES:                                      # bin corner posts
        # The channel has to open towards the inside of the bin, so each post is
        # turned a further quarter turn as the corners are walked.
        objects.append(rfem.structure_core.Member(
            no=no, line=L_POST + k, cross_section_start=4,
            rotation_angle=pi / 2 - k * pi / 2)); no += 1

    # --- surfaces ------------------------------------------------------------
    # Outlet plate, then the four hopper panels, the four bin walls, the bin
    # floor and the roof. Each panel is closed by walking its own face.
    objects.append(rfem.structure_core.Surface(
        no=1, boundary_lines=[L_OUTLET + k for k in FACES]))
    for f in FACES:
        objects.append(rfem.structure_core.Surface(
            no=2 + f,
            boundary_lines=[L_VALLEY + f, L_OUTLET + f, L_VALLEY + (f + 1) % 4,
                            L_FLOOR_RING + 2 * f + 1, L_FLOOR_RING + 2 * f]))
    for f in FACES:
        objects.append(rfem.structure_core.Surface(
            no=6 + f,
            boundary_lines=[L_FLOOR_RING + 2 * f, L_FLOOR_RING + 2 * f + 1,
                            L_POST + (f + 1) % 4, L_ROOF + f, L_POST + f]))
    # The bin floor closes the stored solid from above. It is the free surface
    # of the bulk material, not a steel plate, so it carries no thickness.
    objects.append(rfem.structure_core.Surface(
        no=10, boundary_lines=[L_FLOOR_RING + i for i in range(8)],
        type=rfem.structure_core.Surface.TYPE_WITHOUT_THICKNESS))
    objects.append(rfem.structure_core.Surface(
        no=11, boundary_lines=[L_ROOF + k for k in FACES]))

    # Assigned AFTER the surfaces exist. A Thickness created first is accepted
    # but its assigned_to_surfaces is dropped, and every surface then silently
    # falls back to thickness 1 - which is how this model used to run with the
    # hopper's 10 mm plate everywhere instead of 10 / 8 / 5 mm.
    objects += [
        rfem.structure_core.Thickness(
            no=1, material=1, uniform_thickness=THICKNESS_HOPPER,
            assigned_to_surfaces=[2, 3, 4, 5]),
        rfem.structure_core.Thickness(
            no=2, material=1, uniform_thickness=THICKNESS_SHELL,
            assigned_to_surfaces=[1, 6, 7, 8, 9]),
        rfem.structure_core.Thickness(
            no=3, material=1, uniform_thickness=THICKNESS_ROOF,
            assigned_to_surfaces=[11]),
    ]

    objects += [
        # The stored solid, bounded by the hopper, the outlet plate and the bin
        # floor. Its self weight is what loads the hopper panels.
        rfem.structure_core.Solid(
            no=1, type=rfem.structure_core.Solid.TYPE_STANDARD, material=2,
            boundary_surfaces=[1, 2, 3, 4, 5, 10]),

        # Every leg base on the same elastic foundation.
        rfem.types_for_nodes.NodalSupport(
            no=1,
            nodes=[N_LEG_BASE + k for k in FACES],
            spring_x=SUPPORT_SPRING_HORIZONTAL,
            spring_y=SUPPORT_SPRING_HORIZONTAL,
            spring_z=SUPPORT_SPRING_VERTICAL),
    ]
    return objects


def define_loading() -> list:
    """Analysis settings and load cases."""

    return [
        rfem.loading.StaticAnalysisSettings(no=1),
        rfem.loading.StaticAnalysisSettings(
            no=2,
            analysis_type=rfem.loading.StaticAnalysisSettings.ANALYSIS_TYPE_SECOND_ORDER_P_DELTA,
            number_of_load_increments=2),

        rfem.loading.LoadCase(
            no=1, name="Self weight", static_analysis_settings=1,
            self_weight_active=True),
        rfem.loading.LoadCase(
            no=2, name="Stability - second order", static_analysis_settings=2,
            action_category=rfem.loading.LoadCase.ACTION_CATEGORY_PERMANENT_IMPOSED_GQ,
            self_weight_active=True),
    ]


with rfem.Application() as rfem_app:

    rfem_app.close_all_models(save_changes=False)
    rfem_app.create_model(name=MODEL_NAME)
    rfem_app.delete_all_objects()

    rfem_app.create_object_list(define_structure() + define_loading())
    rfem_app.calculate_all(skip_warnings=True)

    # Results: bending in the four hopper panels, which carry the stored solid.
    hopper_panels = rfem.results.ResultsFilter(
        column_id='surface_no',
        filter_expression=', '.join(str(2 + f) for f in FACES),
    )

    print("\nHopper panels | Basic internal forces at grid points:")
    print(rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_SURFACES_BASIC_INTERNAL_FORCES_GRID_POINTS,
        filters=[hopper_panels],
    ).data)

    print("\nHopper panels | Basic internal forces at mesh nodes:")
    print(rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_SURFACES_BASIC_INTERNAL_FORCES_MESH_NODES,
        filters=[hopper_panels],
    ).data)