Structural analysis in Python

Build a beam or a frame in a few lines, solve it with a Rust finite-element engine, and get reactions, internal forces, deflections and a Eurocode member check back as ordinary data you can loop over, assert on and put in a report. Run the solver locally with pip install FERS, or call the hosted REST API from any language — the same engine that runs in your browser on the free calculators.

Two ways to run it

Locally

pip install FERS gives you the model-building layer and pulls in the compiled solver as a binary wheel. Nothing leaves your machine, there is no request round-trip, and it works offline and inside a locked-down network. The right choice for batch work, notebooks and CI.

Hosted

POST /api/sdk/solve takes a model as JSON and returns the solved results. No install and no wheel to vendor, so it works from Node, Go, a shell script or a spreadsheet as well as from Python — and it is the same endpoint the MCP server calls underneath.

Quick start

1. Install

pip install FERS

The package needs Python 3.11 or newer. It installs the model-building layer plus the compiled solver.

2. Build a beam, solve it, and check it by hand

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)

# i_z is the strong axis, i_y the weak one — the convention that catches
# everyone exactly once.
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]))

load_case = model.create_load_case(name="End Load")
NodalLoad(node=node2, load_case=load_case, 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")
# Tip deflection: -15.065 mm
# Hand check:  PL^3 / 3EI_z = 1000 * 5**3 / (3 * 210e9 * 13.17e-6) = 15.0655 mm

That is the whole point of the exercise: a number you can verify with a formula you already know, before you trust the solver with a model you cannot check by hand. The same closed-form comparisons are published on the accuracy page, and the standard NAFEMS benchmarks are re-solved live in the browser.

Eurocode 3 checks from a script

A plain model gives you forces. check_beam goes one step further: it adds a ULS combination and an EN 1993-1-1 member check over the span, so one call returns bending, shear, combined N+M and lateral-torsional buckling utilizations with the per-clause trace behind each.

from fers_core import check_beam

# 7.5 m simply supported IPE400 in S275, 12 kN/m characteristic, ULS factor 1.35
beam = check_beam(7.5, "IPE400", material="S275", udl=12_000, uls_factor=1.35)
beam.run_analysis()

for row in beam.unity_check_results():
    print(row["governing"]["demand"])
# 0.8002749049232664

Buckling lengths default to the full span for a single-member model, so if the compression flange is restrained along its length — by decking, by a slab, by purlins — that is a modelling decision you make explicitly rather than one the default quietly makes for you. The worked EC3 example shows the same check with every clause written out.

The thing a web form cannot do

Five sections, five solves, one loop:

import os
import requests

KEY = os.environ["FERS_API_KEY"]

for section in ["IPE300", "IPE330", "IPE360", "IPE400", "IPE450"]:
    response = requests.post(
        "https://ferscloud.com/api/sdk/check-beam",
        headers={"X-API-Key": KEY},
        json={
            "span_m": 7.5,
            "section": section,
            "material": "steel_S275",
            "support": "simply_supported",
            "udl": 12.0,                       # kN/m, characteristic
            "idempotency_key": f"sweep-{section}-7m5",
        },
        timeout=60,
    )
    response.raise_for_status()
    check = response.json()["check"]
    print(f"{section:<8} UC {check['governing_utilization']:.3f}"
          f"  governed by {check['governing_check']}")

That is the argument for scripting a structural calculation. The marginal cost of the sixth variant is one more entry in a list, not another twenty clicks — and the same loop works over spans, load cases, steel grades, or a CSV of members exported from your model. What it reports is which limit state governs and by how much; which section you then choose is your call, not the script's.

Pass a stable idempotency_key and reuse it on retries: a repeat with the same key replays the stored result instead of charging a second solve.

Call it from any language

curl -s https://ferscloud.com/api/sdk/solve \
  -H "X-API-Key: keyId.secret" \
  -H "Content-Type: application/json" \
  -d '{ "model": { /* FERS model JSON */ }, "idempotency_key": "run-123" }'

Endpoints: solve, validate, check-beam, create-beam, sections, schema, models and me, all under /api/sdk/. The full machine-readable spec is at ferscloud.com/api/openapi.json.

Two habits save a lot of grief. Call validate before solve — it is free and it catches broken references and misspelled keys that the solver would otherwise silently resolve to a default. And ask schema for the contract rather than guessing field names, for the same reason.

Use it from an AI agent

The same solver is exposed as a remote MCP server, so Claude, ChatGPT, Cursor and VS Code can call it directly:

{
  "mcpServers": {
    "fers": {
      "url": "https://ferscloud.com/api/mcp",
      "headers": { "X-API-Key": "keyId.secret" }
    }
  }
}

The clients, the one-click installers, the OAuth flow and the full tool list are on the MCP server page.

Units, axes and sign conventions

Metres, newtons and newtons per metre in the Python layer; elastic modulus in pascals; deflections come back in metres, so multiply by 1000 for millimetres. The REST beam helpers take kN and kN/m, which is why the sweep above passes 12.0 where the local call takes 12_000.

The axis convention catches everyone once: i_z is the strong (major) axis and i_y the weak one. The full set of axis, sign and unit conventions is written out on the modeling conventions page, and it is the first thing to read if a result comes back an order of magnitude off.

What it costs

The Python package is free to install and the solver runs locally, with a 100-member ceiling on the free tier. Hosted solves — REST and MCP — are free for the first 100 successful solves per rolling week, then charged from a prepaid balance, or unlimited on Pro at €19.95 a month. Only successful solves are charged. See pricing.

Frequently asked questions

Start with the free tier

The package installs without an account. Create a free account when you want hosted solves, the MCP server, or to save models.

Prefer not to write code? Try the free 2D frame calculator or the Eurocode 3 steel beam check in your browser.