Build and solve a model in Python

Seven objects cover almost every model: Node, Material, Section, Member, MemberSet, NodalSupport and NodalLoad. You assemble them, call run_analysis(), and read a results bundle keyed by load case.

The core objects

Everything is imported from fers_core. A model is a FERS instance holding member sets; loads hang off load cases rather than off the model directly, so the same geometry can carry several independent load cases.

  • Node — an X, Y, Z point. Y is up. Nodes own their support condition.
  • Material — E-modulus, shear modulus, density, yield stress. All SI: pascals and kg/m³.
  • Section — area and second moments of area, referencing a material.
  • Member — a 2-node beam element with six degrees of freedom per node, referencing a section.
  • MemberSet — the group you add to the model; also the unit design checks are applied to.
  • NodalSupport — the boundary condition assigned to a node.
  • NodalLoad — a force or moment applied to a node within a load case.
from fers_core import (
    FERS, Node, Member, Section, Material,
    MemberSet, NodalSupport, NodalLoad
)

Define geometry, material and section

Coordinates are in metres and material properties in pascals. The section below is an IPE 180 taken from the FERS steel section library.

Note the axis convention, because it is the single most common source of a wrong answer: i_z is the strong axis and i_y the weak one. A beam bending in its usual plane is governed by i_z.

# Nodes (x, y, z coordinates in metres)
node1 = Node(0, 0, 0)
node2 = Node(5, 0, 0)

# Material (Steel S235)
steel = Material(
    name="Steel S235",
    e_mod=210e9,     # Pa
    g_mod=80.769e9,  # Pa
    density=7850,    # kg/m³
    yield_stress=235e6  # Pa
)

# Cross-section (IPE 180), values from the FERS steel section library.
# Axis convention: i_z is the STRONG axis (bending in the local x-y plane),
# i_y the weak axis — see ferscloud.com/conventions
section = Section(
    name="IPE 180",
    material=steel,
    i_y=1.009e-6,  # m⁴ (weak axis)
    i_z=13.17e-6,  # m⁴ (strong axis)
    j=0.0477e-6,   # m⁴
    area=0.00240   # m²
)

Full axis, sign and unit conventions are on the conventions page. The JSON contract takes angles in degrees, not radians.

Create the member, apply a support and a load

A bare NodalSupport() is fully fixed. For a pin or a roller, pass explicit displacement_conditions and rotation_conditions — see the worked examples for both.

Load directions are global unit vectors, so (0, 1, 0) with a negative magnitude is a downward force.

# Create beam element
beam = Member(start_node=node1, end_node=node2, section=section)

# Fixed support at node1
node1.nodal_support = NodalSupport()

# Organise members into a set and add to model
model = FERS()
model.add_member_set(MemberSet(members=[beam]))

# Load case with a −1 kN point load at node2
lc = model.create_load_case(name="Gravity")
NodalLoad(node=node2, load_case=lc, magnitude=-1000, direction=(0, 1, 0))

Run the analysis and read results

run_analysis() solves every load case. Results come back on model.resultsbundle, keyed by load-case name, with displacements and reactions keyed by node id as a string.

The comments show the actual output for these exact inputs.

model.run_analysis()

results = model.resultsbundle.loadcases["Gravity"]

dy = results.displacement_nodes["2"].dy
Vy = results.reaction_nodes["1"].nodal_forces.fy
Mz = results.reaction_nodes["1"].nodal_forces.mz

print(f"Tip deflection : {dy*1000:.3f} mm")   # -15.065 mm
print(f"Reaction Vy    : {Vy:.1f} N")         # 1000.0 N
print(f"Reaction Mz    : {Mz:.1f} Nm")        # 5000.0 Nm

Hand check: a 5 m cantilever bending about its strong axis gives δ = PL³ / 3EI_z = 1000·5³ / (3 · 210×10⁹ · 13.17×10⁻⁶) = 15.07 mm, with a 1 kN reaction and a 5 kN·m fixing moment from equilibrium. The solver returns 15.065 mm.

Beyond linear statics

The same model object carries the settings for the other analysis types. Second-order (P-Delta) analysis, linear buckling with a critical load factor, modal analysis and response-spectrum seismic analysis are all driven from analysis settings rather than from a different API.

Design checks are attached to member sets and evaluated after the solve, returning per-clause utilizations with the intermediate values that produced them — so a check can be read as a hand calculation rather than a single number.

  • Verify against closed-form solutions first — see accuracy benchmarks and the NAFEMS set.
  • For EN 1993-1-1 member checks, check_beam builds, solves and checks a single span in one call — see the examples.

Related pages

See also

Frequently asked questions

Why are my displacements keyed by a string?

Result dictionaries are keyed by node and member id as strings, because the results bundle round-trips through JSON. displacement_nodes["2"], not [2].

What is a MemberSet for?

It is the unit you add to the model and the unit design checks apply to. Grouping members that form one physical element — a column spliced from two members, say — lets a buckling length or a code check span the whole thing.

How do I model a pin or a roller?

Pass explicit conditions to NodalSupport. A pin fixes the three translations and leaves bending rotations free; hold the torsional rotation as well or the member has a rigid-body twist mode and the solve is singular. The simply supported example shows both.

Which second moment of area is the strong axis?

i_z. FERS treats local z as the strong bending axis throughout — the solver, the design checks and the diagrams all agree on this. Swapping i_y and i_z is the most common modelling error.

Do I need an internet connection to solve?

No. run_analysis() runs the compiled solver in your own process. Network access is only used if you call a cloud endpoint.

Can I get the deflected shape rather than just end values?

Yes, and it is load-exact rather than interpolated between end displacements — a fixed-fixed beam under a uniformly distributed load reports the true wL⁴/384EI sag. Request it in the analysis options.