Worked examples

Four scripts you can paste and run. Each one states the expected output for its exact inputs and the closed-form solution it was checked against, so you can confirm your install reproduces them before trusting a model of your own.

1. Cantilever with an end load

A 5 m IPE 180 cantilever, fully fixed at one end, carrying a 1 kN downward point load at the tip. This is the simplest model that exercises the whole pipeline: geometry, material, section, support, load case and solve.

from fers_core import FERS, Node, Member, Section, Material, MemberSet, NodalSupport, NodalLoad

model = FERS()

node1 = Node(0, 0, 0)  # fixed end
node2 = Node(5, 0, 0)  # free end (5 m span)

steel = Material(name="Steel S235", e_mod=210e9, g_mod=80.769e9, density=7850, yield_stress=235e6)
# IPE 180 from the FERS section library: i_z = strong axis (in play here), i_y = weak axis
section = Section(name="IPE 180", material=steel, i_y=1.009e-6, i_z=13.17e-6, j=0.0477e-6, area=0.00240)

beam = Member(start_node=node1, end_node=node2, section=section)
node1.nodal_support = NodalSupport()  # fully fixed
model.add_member_set(MemberSet(members=[beam]))

lc = model.create_load_case(name="End Load")
NodalLoad(node=node2, load_case=lc, magnitude=-1000, direction=(0, 1, 0))

model.run_analysis()

dy = model.resultsbundle.loadcases["End Load"].displacement_nodes["2"].dy
print(f"Tip deflection: {dy*1000:.3f} mm")
# Expected output: Tip deflection: -15.065 mm
# Hand check: δ = PL³/3EI_z = 1000·5³ / (3 · 210e9 · 13.17e-6) = 15.07 mm ✓

Hand check: δ = PL³ / 3EI_z = 1000·5³ / (3 · 210×10⁹ · 13.17×10⁻⁶) = 15.07 mm. The solver returns 15.065 mm.

2. Simply supported beam with a centre load

A 10 m IPE 300 in two 5 m members so there is a node at mid-span to load and to read. A pin at one end, a roller at the other.

Note the explicit support conditions. The pin holds the three translations and the torsional rotation but leaves the bending rotations free — hold the torsion or the beam has a rigid-body twist mode and the stiffness matrix is singular. The roller additionally releases longitudinal translation so the beam can extend freely.

from fers_core import FERS, Node, Member, Section, Material, MemberSet, NodalSupport, NodalLoad

model = FERS()

node1 = Node(0, 0, 0)
node2 = Node(5, 0, 0)   # mid-span
node3 = Node(10, 0, 0)  # right support

steel = Material(name="Steel S235", e_mod=210e9, g_mod=80.769e9, density=7850, yield_stress=235e6)
section = Section(name="IPE 300", material=steel, i_y=6.038e-6, i_z=83.58e-6, j=0.199e-6, area=0.00538)

beam1 = Member(start_node=node1, end_node=node2, section=section)
beam2 = Member(start_node=node2, end_node=node3, section=section)

# Pin at node1 — bending rotations free, torsion (RX) held so the beam
# has no rigid-body twist mode. Roller at node3 — X translation free too.
pin = NodalSupport(
    displacement_conditions={"X": "Fixed", "Y": "Fixed", "Z": "Fixed"},
    rotation_conditions={"X": "Fixed", "Y": "Free", "Z": "Free"},
)
roller = NodalSupport(
    displacement_conditions={"X": "Free", "Y": "Fixed", "Z": "Fixed"},
    rotation_conditions={"X": "Free", "Y": "Free", "Z": "Free"},
)
node1.nodal_support = pin
node3.nodal_support = roller

model.add_member_set(MemberSet(members=[beam1, beam2]))

lc = model.create_load_case(name="Centre Load")
NodalLoad(node=node2, load_case=lc, magnitude=-10000, direction=(0, 1, 0))

model.run_analysis()

dy_mid = model.resultsbundle.loadcases["Centre Load"].displacement_nodes["2"].dy
print(f"Mid-span deflection: {dy_mid*1000:.3f} mm")
# Expected output: Mid-span deflection: -11.870 mm
# Hand check: δ = PL³/48EI_z = 10000·10³ / (48 · 210e9 · 83.58e-6) = 11.87 mm ✓

Hand check: δ = PL³ / 48EI_z = 10000·10³ / (48 · 210×10⁹ · 83.58×10⁻⁶) = 11.87 mm. The solver returns 11.870 mm.

3. Portal frame under horizontal load

Two 4 m HEA 200 columns with fixed bases and a 6 m IPE 300 rafter, loaded by a 5 kN horizontal force at the top of the left column. This is the smallest model where frame action — rather than a single member — determines the answer.

from fers_core import FERS, Node, Member, Section, Material, MemberSet, NodalSupport, NodalLoad

model = FERS()

# Portal frame: two columns (h=4 m) + one beam (L=6 m)
n1 = Node(0, 0, 0)   # left base
n2 = Node(0, 4, 0)   # left top
n3 = Node(6, 4, 0)   # right top
n4 = Node(6, 0, 0)   # right base

steel = Material(name="Steel S235", e_mod=210e9, g_mod=80.769e9, density=7850, yield_stress=235e6)
col_sec  = Section(name="HEA 200", material=steel, i_y=13.36e-6, i_z=36.93e-6, j=0.206e-6, area=0.00538)
beam_sec = Section(name="IPE 300", material=steel, i_y=6.038e-6, i_z=83.58e-6, j=0.199e-6, area=0.00538)

col_left  = Member(start_node=n1, end_node=n2, section=col_sec)
col_right = Member(start_node=n4, end_node=n3, section=col_sec)
rafter    = Member(start_node=n2, end_node=n3, section=beam_sec)

n1.nodal_support = NodalSupport()  # fixed base
n4.nodal_support = NodalSupport()  # fixed base

model.add_member_set(MemberSet(members=[col_left, col_right, rafter]))

lc = model.create_load_case(name="Wind")
# Horizontal wind load on left column top
NodalLoad(node=n2, load_case=lc, magnitude=5000, direction=(1, 0, 0))

model.run_analysis()

dx = model.resultsbundle.loadcases["Wind"].displacement_nodes["2"].dx
print(f"Sway at left top: {dx*1000:.3f} mm")
# Expected output: Sway at left top: 5.310 mm
# Hand check (slope-deflection, axial deformation neglected): 5.30 mm ✓

Hand check by slope-deflection, neglecting axial deformation: 5.30 mm. The solver returns 5.310 mm. A fuller treatment with diagrams is on the portal frame worked example.

4. Eurocode 3 steel member check

check_beam builds a single-span beam, solves it and runs the EN 1993-1-1 member checks in one call. Here: a 7.5 m simply supported IPE 400 in S355 under a 13 kN/m characteristic UDL, factored by 1.35, with the compression flange unrestrained over the full span.

The result carries a per-clause trace rather than a single number, so each utilization can be read as a hand calculation — the intermediate values that produced it are all there.

from fers_core import check_beam

# 7.5 m simply supported IPE 400 in S355, 13 kN/m characteristic UDL,
# ULS factor 1.35, compression flange unrestrained over the full span
model = check_beam(
    span=7.5,
    section="IPE400",
    material="S355",
    udl=13_000,       # N/m characteristic (13 kN/m)
    uls_factor=1.35,
)

model.run_analysis()

check = model.unity_check_results()[0]
print(f"Governing UC: {check['max_utilization']:.2f}")
for step in check["governing"]["trace"]:
    print(f"  {step['label']:22s} {step['value']:.2f}")

# Expected output (solver-computed, matches the published worked example):
# Governing UC: 0.82
#   Bending y (6.2.5)      0.00
#   Bending z (6.2.5)      0.27
#   Shear z (6.2.6)        0.00
#   Shear y (6.2.6)        0.07
#   Combined N+M (6.2.1)   0.27
#   LTB (6.3.2)            0.82
#   Governing              0.82

This reproduces the published Eurocode 3 worked example, where the same case is set out clause by clause. Lateral-torsional buckling governs at UC 0.82.

Verify before you trust

Every example above prints a number next to the closed-form solution it should reproduce. That is deliberate, and it is the habit worth copying: solve a case whose answer you already know before scaling up to one you do not.

The accuracy benchmarks do this across a wider set of closed-form cases, and the NAFEMS page runs the standard finite-element benchmarks live in your browser with the target-versus-FERS error for each.

Related pages

See also

Frequently asked questions

Why does the simply supported example need two members?

Only so there is a node at mid-span to apply the load to and read the displacement from. The element formulation is exact for a prismatic member, so splitting the span does not change the answer — it just gives you somewhere to attach the load.

Why does the pin hold the torsional rotation?

Releasing all three rotations at both ends of a straight beam leaves it free to spin about its own axis. That rigid-body mode makes the stiffness matrix singular and the solve fails. Holding RX at one support removes the mode without restraining any bending.

My numbers differ in the last digit — is something wrong?

No. The printed values are rounded to three decimals of a millimetre; the closed-form comparison is quoted to the precision the formula justifies. A difference beyond that usually means swapped i_y / i_z or a units mistake.

Where do the section properties come from?

The FERS steel section library, which ships with the package. You can also pass properties directly, as these examples do, or compute them for an arbitrary shape with the section properties calculator.

Can I run these without an account?

Yes. All four solve locally with no credentials. Only cloud storage and the metered API/agent channels need a key.

How do I do the same thing from JavaScript?

The same engine is published as a WebAssembly npm package with an identical model format — see using FERS from JavaScript.