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.82This 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?
Why does the pin hold the torsional rotation?
My numbers differ in the last digit — is something wrong?
i_y / i_z or a units mistake.