Nonlinear Nodal Support#

../../../../_images/nonlinear_nodal_support.png

Modelling a support that cannot be pulled down, to check the impact on reactions and internal forces:

  • Create a two-span continuous beam loaded on the first span only

  • Assign a nodal support that fails in tension using the nonlinearity failure if positive P_Z'

  • Calculate the same model with a linear and with an uplift-releasing end support

  • Retrieve support forces, member deformations, and bending moments for both cases

  • Compare the lift-off of the released support against the linear reference

Keywords:
continuous beam nodal support support nonlinearity uplift support force member deformation
from math import inf

from dlubal.api import common, rfem

# -------------------------------------------------------
# Two-span continuous beam on a nonlinear nodal support.
#
# A support that simply rests on its bearing cannot pull the
# beam down. This is modelled with the nodal support
# nonlinearity "failure if positive P_Z'", which deactivates
# the support as soon as its reaction turns into tension.
#
# The same model is calculated twice and compared:
#   1) linear support      - the support may act in tension
#   2) uplift-releasing    - the support fails in tension
#
# With the load on the first span only, the far support of a
# two-span beam is pulled upwards. Releasing it turns the
# system into a single span with a free overhang, so the
# reactions, the span moment, and the deflections all change.
#
# Assumptions:
#   - single-line 2D beam in the global XZ plane
#   - Z-up coordinate system
#   - supports pinned in plane (rotation about Y released)
#   - self-weight ignored, so results match hand calculation
# -------------------------------------------------------

# Editable parameters (SI units)
MODEL_NAME = "nonlinear_nodal_support"

SPAN = 6.0
MATERIAL = "S235"
CROSS_SECTION = "IPE 300"

SPAN_LOAD = -20_000.0  # N/m on the first span, global Z direction


def define_structure() -> list:
    """Define the beam geometry, material, cross-section, and the two inner supports."""

    return [
        rfem.structure_core.Material(
            no=1,
            name=MATERIAL,
        ),
        rfem.structure_core.CrossSection(
            no=1,
            name=CROSS_SECTION,
            material=1,
        ),
        # Nodes
        rfem.structure_core.Node(no=1, coordinate_1=0.0, coordinate_2=0.0, coordinate_3=0.0),
        rfem.structure_core.Node(no=2, coordinate_1=SPAN, coordinate_2=0.0, coordinate_3=0.0),
        rfem.structure_core.Node(no=3, coordinate_1=2.0 * SPAN, coordinate_2=0.0, coordinate_3=0.0),
        # Lines
        rfem.structure_core.Line(no=1, definition_nodes=[1, 2]),
        rfem.structure_core.Line(no=2, definition_nodes=[2, 3]),
        # Members: 1 loaded span, 2 unloaded span
        rfem.structure_core.Member(no=1, line=1, cross_section_start=1),
        rfem.structure_core.Member(no=2, line=2, cross_section_start=1),
        # End support, held in all directions
        rfem.types_for_nodes.NodalSupport(
            no=1,
            nodes=[1],
            spring=common.Vector3d(x=inf, y=inf, z=inf),
            rotational_restraint=common.Vector3d(x=inf, y=0.0, z=inf),
        ),
        # Inner support, free to slide along the beam axis
        rfem.types_for_nodes.NodalSupport(
            no=2,
            nodes=[2],
            spring=common.Vector3d(x=0.0, y=inf, z=inf),
            rotational_restraint=common.Vector3d(x=inf, y=0.0, z=inf),
        ),
    ]


def end_support(uplift_releasing: bool):
    """Create the far support, either linear or failing under tension."""

    nonlinearity = (
        rfem.types_for_nodes.NodalSupport.SPRING_Z_NONLINEARITY_FAILURE_IF_POSITIVE
        if uplift_releasing
        else rfem.types_for_nodes.NodalSupport.SPRING_Z_NONLINEARITY_NONE
    )

    return rfem.types_for_nodes.NodalSupport(
        no=3,
        nodes=[3],
        spring=common.Vector3d(x=0.0, y=inf, z=inf),
        rotational_restraint=common.Vector3d(x=inf, y=0.0, z=inf),
        spring_z_nonlinearity=nonlinearity,
    )


def define_loading() -> list:
    """Define the analysis settings, the load case, and the load on the first span."""

    return [
        rfem.loading.StaticAnalysisSettings(
            no=1,
            analysis_type=rfem.loading.StaticAnalysisSettings.ANALYSIS_TYPE_GEOMETRICALLY_LINEAR,
        ),
        rfem.loading.LoadCase(
            no=1,
            name="Load on span 1",
            action_category=rfem.loading.LoadCase.ACTION_CATEGORY_PERMANENT_G,
            static_analysis_settings=1,
            self_weight_active=False,
        ),
        rfem.loads.MemberLoad(
            no=1,
            load_case=1,
            members=[1],
            load_type=rfem.loads.MemberLoad.LOAD_TYPE_FORCE,
            load_distribution=rfem.loads.MemberLoad.LOAD_DISTRIBUTION_UNIFORM,
            load_direction=rfem.loads.MemberLoad.LOAD_DIRECTION_GLOBAL_Z_OR_USER_DEFINED_W_TRUE_LENGTH,
            magnitude=SPAN_LOAD,
        ),
    ]


def calculate_case(rfem_app, uplift_releasing: bool) -> dict:
    """Apply the requested end support, recalculate, and collect the comparison values."""

    rfem_app.update_object(end_support(uplift_releasing))

    calculation_info = rfem_app.calculate_all(skip_warnings=True)
    if not calculation_info.succeeded:
        raise RuntimeError("Calculation failed.")

    support_forces = rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_NODES_SUPPORT_FORCES,
    ).data
    reactions = {int(row.node_no): float(row.p_z) for row in support_forces.itertuples()}

    deformations = rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_MEMBERS_GLOBAL_DEFORMATIONS,
    ).data
    internal_forces = rfem_app.get_results(
        results_type=rfem.results.STATIC_ANALYSIS_MEMBERS_INTERNAL_FORCES,
    ).data

    return {
        "reactions": reactions,
        "deflection": deformations[deformations["member_no"] == 1]["u_z"].min(),
        "uplift": float(deformations[deformations["node_no"] == 3]["u_z"].iloc[0]),
        "moment": internal_forces[internal_forces["member_no"] == 1]["m_y"].max(),
    }


def print_case(label: str, result: dict) -> None:
    """Print one calculated case as a single comparison row."""

    reactions = result["reactions"]
    print(
        f"{label:<22}"
        f"{reactions[1] / 1000:>10.2f}"
        f"{reactions[2] / 1000:>10.2f}"
        f"{reactions[3] / 1000:>10.2f}"
        f"{result['deflection'] * 1000:>13.2f}"
        f"{result['uplift'] * 1000:>13.2f}"
        f"{result['moment'] / 1000:>13.2f}"
    )


with rfem.Application() as rfem_app:

    print(f"\nCreating model: {MODEL_NAME}")
    rfem_app.close_all_models(save_changes=False)
    rfem_app.create_model(name=MODEL_NAME)

    base_data = rfem_app.get_base_data()
    base_data.main.surfaces_active = False
    base_data.general_settings.global_axes_orientation = (
        rfem.BaseData.GeneralSettings.GLOBAL_AXES_ORIENTATION_ZUP
    )
    rfem_app.set_base_data(base_data=base_data)

    rfem_app.delete_all_objects()
    rfem_app.create_object_list(
        define_structure()
        + [end_support(uplift_releasing=False)]
        + define_loading()
    )

    linear = calculate_case(rfem_app, uplift_releasing=False)
    nonlinear = calculate_case(rfem_app, uplift_releasing=True)

    print(
        f"\n{'End support':<22}"
        f"{'R1 [kN]':>10}{'R2 [kN]':>10}{'R3 [kN]':>10}"
        f"{'u_z,1 [mm]':>13}{'u_z,3 [mm]':>13}{'M_y,1 [kNm]':>13}"
    )
    print("-" * 91)
    print_case("linear", linear)
    print_case("uplift-releasing", nonlinear)

    print(
        "\nA positive R3 means the linear support holds the beam down. "
        "Once the support is allowed to fail in tension, R3 drops to zero, "
        "the beam lifts off by u_z,3, and the loaded span carries the full "
        f"single-span moment of {abs(SPAN_LOAD) * SPAN ** 2 / 8 / 1000:.2f} kNm."
    )