physicsbase docs
Everything an agent needs to describe a structure, solve it, and read the answers. One problem format works across REST, MCP, and the Python SDK.
Quickstart
A problem is a flat JSON object: nodes, materials,
elements, supports, and loads. POST it
to /solve and read displacements, reactions, and element
stresses straight back.
# solve a single-bar problem
curl -X POST https://api.physicsbase.dev/solve \
-H "content-type: application/json" \
-d '{
"nodes": [[0,0],[2,0]],
"materials": {"steel": {"E": 200e9, "nu": 0.3}},
"elements": [{"type":"truss2d","nodes":[0,1],
"material":"steel","section":{"A":0.01}}],
"supports": [{"node":0,"dofs":["ux","uy"]},
{"node":1,"dofs":["uy"]}],
"loads": [{"node":1,"fx":50000}]
}'
ux = 5e-05 m, the support reacts with -50 kN, and
the bar carries 50 kN of tension — all returned as JSON.Authentication
None. physicsbase is free during the research preview — no API key, no login, no bearer token. Point your agent, the MCP server, or the Python SDK straight at the endpoint and call it. Every solve is stateless, so there's nothing to provision and nothing to leak.
How agents use it
The division of labour is the whole point. The agent does the translation — turning a request like "a 3 m steel cantilever with 5 kN on the tip" into nodes, elements, supports, and loads. physicsbase does the physics — assembling the stiffness matrix, applying boundary conditions, and solving. The agent never derives a formula or risks a hallucinated equation; it reads solved numbers and reasons on top of them.
solve → inspect
elements[].von_mises or displacements[] → decide →
(optionally) adjust the section and solve again.Agent guide — the reliable loop
Follow these five steps and a call will succeed the first time. Each step names the exact field or tool involved.
| # | Step | How |
|---|---|---|
| 1 | Discover | Call fem_capabilities (MCP) or GET /capabilities. It returns the element types, DOF names, load components, and analyses — read them, don't guess. |
| 2 | Pick the element | Use the chooser below to map the physical problem to a type. Everything in a model must share the same DOF layout (all 2D-planar, all 3D, etc.). |
| 3 | Build the JSON | Fill nodes, materials, elements, supports, loads. Add enough supports to remove all rigid-body motion. |
| 4 | Call | fem_solve / fem_modal / fem_buckling, or POST to /solve with "analysis" set. |
| 5 | Read & act | Index into displacements, reactions, elements[]. Check the error contract first — a returned {"error": …} tells you exactly what to fix. |
Choosing an element & analysis
| The problem is… | Use | Analysis |
|---|---|---|
| Pin-jointed truss (2D) | truss2d | static |
| Space truss (3D) | truss3d | static |
| Beam / portal frame (2D bending) | beam2d | static |
| 3D frame, torsion, biaxial bending | frame3d | static |
| Panel / bracket / plane part | quad4 or cst | static |
| Vibration — natural frequencies | any (needs rho) | modal |
| Column / frame stability | beam2d / frame3d | buckling |
A full agent transcript
What an MCP agent actually does, end to end, to answer "is my 3 m cantilever safe under a 5 kN tip load?".
# 1. discover — learn the schema before building anything
→ fem_capabilities()
← { "element_types": { "beam2d": "…section {A, I}", … }, "analyses": [ … ] }
# 2+3. translate the words into a problem and solve
→ fem_solve({
"nodes": [[0,0],[1,0],[2,0],[3,0]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [
{"type":"beam2d","nodes":[0,1],"material":"steel","section":{"A":0.01,"I":8e-6}},
{"type":"beam2d","nodes":[1,2],"material":"steel","section":{"A":0.01,"I":8e-6}},
{"type":"beam2d","nodes":[2,3],"material":"steel","section":{"A":0.01,"I":8e-6}}
],
"supports": [ {"node":0,"dofs":["ux","uy","rz"]} ],
"loads": [ {"node":3,"fy":-5000} ]
})
← { "displacements": [ …, {"uy": -0.0268, "rz": -0.0134} ],
"reactions": [ {"fy": 5000, "mz": 15000}, … ],
"elements": [ {"moment_i": 15000, …}, … ] }
# 4. reason on the returned numbers (no physics guessed by the agent)
tip_drop = 26.8 mm # from displacements[-1].uy
M_wall = 15 kN·m # from reactions[0].mz / elements[0].moment_i
sigma = M_wall * c / I = 94 MPa
verdict = 94 MPa < 250 MPa yield -> safe, factor of safety 2.7
Error contract & troubleshooting
The engine fails loudly and specifically. Over MCP a bad problem returns
{"error": "…"} (never a stack trace); over REST it returns
422 with a detail string. Both messages are written
to be actioned directly. Common causes:
| Message contains… | Cause | Fix |
|---|---|---|
| no supports; stiffness is singular | The structure can drift or spin freely (rigid-body motion). | Add supports until every rigid-body mode is removed, or add springs. |
| unknown element type | type misspelled or unsupported. | Use one of truss2d, truss3d, beam2d, frame3d, cst, quad4. |
| needs N nodes, got M | Wrong node count for the element. | truss/beam = 2, cst = 3, quad4 = 4, frame3d = 2. |
| is 3D but model is 2D (or vice-versa) | Mixed 2D and 3D elements, or 2-vs-3 coordinate mismatch. | Keep one dimensionality; give 3D nodes 3 coordinates. |
| support dof '…' invalid | Used a DOF the model doesn't have (e.g. rz in a truss-only model). | Check dof_names from /capabilities: 2D truss has ux,uy; add a beam2d for rz; frame3d has all six. |
| load component '…' not valid | Applied e.g. mz where no rotational DOF exists. | Only apply components the model supports (moments need beam/frame elements). |
| has no mass matrix (modal) | Ran modal without rho. | Give every material a non-zero rho. |
| no axial force … nothing to buckle | Buckling run with no compression in any member. | Apply an axial (compressive) reference load along the member(s). |
| element references node N out of range | A node index exceeds the nodes list. | Node indices are zero-based; keep them < len(nodes). |
7e-12 are floating-point zero. If a reaction on a
free node is large, a support or connectivity is wrong.Problem schema
Coordinates are [x, y] for 2D models or [x, y, z]
for 3D. Node indices are zero-based and refer to positions in the
nodes array. Units are consistent SI in / SI out — feed metres
and newtons, get metres and newton-metres back.
| Field | Type | Description |
|---|---|---|
nodes | number[][] | List of coordinates, 2D or 3D. |
materials | object | Named map of {E, nu, rho?} (Young's modulus, Poisson's ratio, density). |
elements | object[] | {type, nodes, material, section, id?} — see the element reference. |
supports | object[] | {node, dofs, values?}. DOFs: ux, uy, uz, rx, ry, rz. values prescribes non-zero displacement. |
loads | object[] | {node, fx?, fy?, fz?, mx?, my?, mz?} — nodal forces and moments. |
element_loads | object[] | {element, w:[wx,wy(,wz)]} — distributed load (force/length) on a beam or frame element. |
springs | object[] | {node, dof, k} — grounded elastic support of stiffness k. |
gravity | number[] | [gx, gy(, gz)] — acceleration vector for self-weight (needs rho). |
analysis | string | "static" (default), "modal", or "buckling". |
title | string | Optional label echoed back in the summary. |
Element reference
Choose an element family by type. The section keys
differ per family.
| type | DOF / node | section | Use for |
|---|---|---|---|
truss2d | ux, uy | A | 2D bars / pin-jointed trusses |
truss3d | ux, uy, uz | A | 3D space trusses |
spring | ux, uy | k | Spring assemblages, elastic links |
beam2d | ux, uy, rz | A, I | 2D frames, beams, Euler-Bernoulli bending |
timo2d | ux, uy, rz | A, I, ks? | Timoshenko beam — includes shear (deep beams) |
frame3d | ux…rz (6) | A, Iy, Iz, J, G?, ref? | 3D frames: axial + torsion + biaxial bending |
plate4 | uz, rx, ry | t, ks? | Reissner-Mindlin plate bending |
cst | ux, uy | t, kind | 2D continuum, linear triangle |
quad4 | ux, uy | t, kind | 2D continuum, bilinear quad |
tri6 | ux, uy | t, kind | 2D continuum, quadratic triangle (LST) |
quad8 | ux, uy | t, kind | 2D continuum, 8-node serendipity quad |
tet4 | ux, uy, uz | — | 3D solid, linear tetrahedron |
hex8 | ux, uy, uz | — | 3D solid, trilinear hexahedron |
axiquad4 | ur, uz | — | Axisymmetric solid (r-z plane) |
For cst and quad4, t is thickness and
kind is "plane_stress" (default) or
"plane_strain". Give quad4 nodes counter-clockwise.
For frame3d, Iy/Iz are the second
moments about the local y/z axes, J the torsion constant,
G defaults to E/2(1+ν), and ref is an
optional orientation vector for the cross-section (defaults sensibly, and
flips to global Y for vertical members).
Result schema
Every solve returns the same shape, so an agent can index straight into it.
{
"displacements": [ { "ux": 0.0, "uy": 0.0 }, … ], // per node, by DOF
"reactions": [ { "fx": -50000.0, "fy": 0.0 }, … ], // per node
"elements": [
{ "id": "span_1", "type": "beam2d",
"moment_i": 10000.0, "shear_i": 5000.0, … } // depends on type
],
"summary": { "n_dof": 9, "max_abs_displacement": 0.0079,
"solve_ms": 1.2 }
}
Element results vary by family: trusses return axial_force and
axial_stress; beams return shear_i/j and
moment_i/j; continuum elements return a stress vector
and von_mises. Treat values like 7e-12 as
floating-point zero.
REST API
POST/solve — solve a problem.
GET/capabilities — discover element
types and DOF names. GET/health —
liveness check.
const res = await fetch("https://api.physicsbase.dev/solve", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(problem)
});
const out = await res.json();
console.log(out.displacements.at(-1).uy); // tip deflection
MCP server
Give any MCP-capable agent (Claude and others) a solver as native tools.
Eight tools are exposed: fem_capabilities,
fem_example, fem_solve (static),
fem_modal (frequencies), fem_buckling
(critical loads), fem_transient, fem_damage, and
fem_field (heat/diffusion/flow).
Remote (recommended) — the deployed service hosts MCP over
streamable HTTP at /mcp, so a client can connect by URL with
nothing to install and no key:
// MCP client config — remote endpoint
{
"mcpServers": {
"physicsbase": {
"url": "https://<your-app>.up.railway.app/mcp"
}
}
}
Local (stdio) — run the server as a subprocess instead:
{
"mcpServers": {
"physicsbase": {
"command": "python",
"args": ["-m", "femengine.mcp.server"]
}
}
}
The agent calls fem_capabilities once to learn the schema, then
passes a problem object to fem_solve and receives the results
JSON as the tool response.
Python SDK
Build models in code, or solve a JSON spec. With no base_url the
client solves in-process — handy for tests and local agents; pass a URL to
hit a running server.
from femengine import Model, solve
from femengine.materials import Material
from femengine.client import FEMClient
# fluent model builder
m = Model()
m.add_nodes([[0,0], [2,0]])
m.add_material("steel", Material(E=200e9, nu=0.3))
m.add_element("truss2d", [0,1], "steel", {"A": 0.01})
m.add_support(0, ["ux","uy"]); m.add_support(1, ["uy"])
m.add_load(1, fx=50_000)
res = solve(m)
print(res.displacements[1]["ux"]) # 5e-05
# …or solve a JSON spec via the client
out = FEMClient().solve(problem_dict) # in-process
out = FEMClient("https://api.physicsbase.dev").solve(problem_dict)
Advanced analyses
Beyond linear statics, the engine solves two eigenvalue problems. Set
"analysis" in the problem, or call the dedicated endpoints
(/modal, /buckling) and MCP tools.
Modal — natural frequencies
Solves (K − ω²M) φ = 0 with consistent mass matrices. Materials
must include rho; applied loads are ignored. Returns
frequencies_hz, angular_frequencies, and normalised
mode_shapes (per node, per DOF).
{
"frequencies_hz": [ 4.79, 30.0, 84.1, … ],
"angular_frequencies": [ 30.1, 188.5, 528.3, … ],
"mode_shapes": [ [ {"ux":0.0,"uy":0.0,"rz":0.0}, … ], … ],
"summary": { "num_modes": 6, "analysis": "modal" }
}
Buckling — critical loads
Runs a reference static solve, forms the geometric stiffness from the member
axial forces, and solves (K + λ Kg) φ = 0. The applied
loads define the reference pattern; load_factors multiply that
pattern to reach buckling, and critical_loads give the absolute
magnitudes. Matches Euler columns to within ~1%.
3.4 means the
structure buckles at 3.4× the load you applied. Factor below 1 means the
applied load already exceeds the critical load.Multiphysics
Beyond structures, physicsbase solves scalar-field problems through one
unified kernel. The governing equation is
−∇·(k∇u) + v·∇u = Q; what u, k, and
Q mean chooses the physics.
| Physics | u | k | Notes |
|---|---|---|---|
| Heat conduction | temperature | conductivity | source = heat generation; c = ρ·cp for transient |
| Mass diffusion | concentration | diffusivity | Fick's law; identical math |
| Potential flow | velocity potential | 1 | inviscid, irrotational; ∇u = velocity |
| Seepage / Darcy | hydraulic head | permeability | groundwater flow |
| Transport | concentration | diffusivity | set velocity → convection-diffusion |
Field elements: field_line2 (1D), field_tri3,
field_quad4 (2D), field_tet4, field_hex8
(3D) — one DOF (the scalar) per node. Boundary conditions: prescribed
value (Dirichlet), flux (Neumann), and
convection (Robin: q = h(u − u∞)). Steady or
transient (θ-method). Call POST /field or the
fem_field MCP tool.
Heat conduction with a source
A 1D bar with uniform heat generation and cold ends develops a parabolic
temperature profile peaking at Q L² / 8k.
{
"nodes": [[0],[0.25],[0.5],[0.75],[1]],
"materials": { "steel": { "k": 45 } }, // W/m·K
"elements": [ /* field_line2 chain */ ],
"element_sources": [ /* {element:i, Q:1e5} heat gen per element */ ],
"values": [ {"node":0,"value":20}, {"node":4,"value":20} ]
}
// -> { "values": [20, …, T_mid, …, 20], "fluxes": [ … ] }
Potential flow & transport
With k = 1 and no source the field solves Laplace — a velocity
potential whose gradient is the flow field. Add a velocity and
the same solver becomes convection-diffusion transport (a scalar carried by
a flow), matching the analytical boundary-layer profile
(e^{Pe·x} − 1)/(e^{Pe} − 1) at low Péclet number.
{
"nodes": [ /* 1D chain 0 … 1 */ ],
"materials": { "m": { "k": 1.0 } }, // diffusivity D
"elements": [ /* field_line2 */ ],
"velocity": [1.0], // advection speed
"values": [ {"node":0,"value":0}, {"node":-1,"value":1} ]
}
Coupled thermo-mechanical
Real multiphysics: solve the heat field, map nodal temperatures onto the
structure as per-element ΔT, and solve for the thermal stress — in one call.
A fully restrained bar heated to T returns
σ = −Eα(T − T_ref).
from femengine import thermo_mechanical
res = thermo_mechanical(field_model, struct_model, T_ref=20.0)
res.structural["elements"][0]["axial_stress"] # thermal stress
res.element_dT # ΔT applied per element
Nonlinear damage
Continuum isotropic damage: stiffness degrades as
σ = (1 − d)·D:ε with exponential softening past a threshold
strain. Loading is applied incrementally with a secant iteration; drive it
with prescribed displacements to trace the softening branch.
{
"analysis": "damage", "k0": 1e-4, "kf": 5e-4, "increments": 30,
"nodes": [[0,0],[1,0]],
"materials": { "concrete": { "E": 30e9, "nu": 0.2 } },
"elements": [ {"type":"truss2d","nodes":[0,1],"material":"concrete","section":{"A":0.01}} ],
"supports": [ {"node":0,"dofs":["ux","uy"]},
{"node":1,"dofs":["ux","uy"],"values":[3e-4,0]} ]
}
// -> { "history":[{load_factor,max_damage,…}], "elements":[{damage,…}] }
Run it locally
The engine is a self-contained Python package (NumPy + SciPy). You can run the solver directly, stand up the REST API, or expose it over MCP.
Install
cd fem-engine
pip install -e ".[api,mcp,dev]" # core is just numpy + scipy + pydantic
Solve in Python (no server)
from femengine import Model, solve
from femengine.materials import Material
m = Model()
m.add_nodes([[0,0], [3,0]])
m.add_material("steel", Material(E=210e9, nu=0.3))
m.add_element("beam2d", [0,1], "steel", {"A":0.01, "I":8e-6})
m.add_support(0, ["ux","uy","rz"])
m.add_load(1, fy=-5000)
print(solve(m).displacements[-1]) # tip deflection
Start the REST API
femengine-api --port 8000 # or: uvicorn femengine.api.server:app
curl -s localhost:8000/capabilities # discover elements & analyses
curl -s -X POST localhost:8000/solve \
-H "content-type: application/json" \
-d @examples/cantilever_beam.json # solve a saved problem
Interactive API docs are served at localhost:8000/docs.
Endpoints: /solve, /modal, /buckling,
/transient, /capabilities, /health.
Expose it over MCP
python -m femengine.mcp.server # tools: fem_solve, fem_modal,
# fem_buckling, fem_transient, …
Validation & tests
The engine isn't trusted on faith — every element and analysis is checked against a closed-form solution. The test suite is the specification: if a formula the engine returns doesn't match the textbook answer to tolerance, the test fails.
Run the suite
cd fem-engine
pytest -q # all suites
pytest tests/test_validation.py -q # core elements
pytest tests/test_advanced.py -q # 3D frames, modal, buckling
pytest tests/test_textbook.py -q # solids, thermal, axisym, transient
What each check proves
| Suite | Validates against |
|---|---|
test_validation.py | Axial bar PL/AE, symmetric truss equilibrium, cantilever tip PL³/3EI and rotation ML/EI, CST/Quad4 uniaxial tension, 3D bar. |
test_advanced.py | 3D frame bending about both axes + torsion TL/GJ, UDL deflections wL⁴/8EI & 5wL⁴/384EI, self-weight bar, spring, cantilever & simply-supported natural frequencies, Euler buckling (pinned, cantilever, fixed-fixed). |
test_textbook.py | Spring assemblage, restrained/free thermal bar −EαΔT, uniform-strain patch tests for tri6/quad8/tet4/hex8/axiquad4, hex8 uniaxial, edge traction, body force, undamped step-response 2× overshoot. |
test_multiphysics.py | Heat conduction (linear, parabolic source QL²/8k, convection, flux BC), 2D/3D field patch tests, transient→steady, potential flow, convection-diffusion boundary layer, coupled thermal stress, and the nonlinear damage law. |
test_api.py | REST endpoints, capability discovery, error handling, MCP tool round-trip. |
How the numbers get validated
The pattern is the same everywhere: build a problem whose answer is known in closed form, solve it, assert equality to tolerance.
def test_buckling_pinned():
m, p = _column("pinned") # pin-ended steel column
res = buckling(m, num_modes=1)
expected = math.pi**2 * p["E"] * p["I"] / p["L"]**2 # Euler's Pcr
assert res.critical_loads[0] == pytest.approx(expected, rel=1e-2)
pytest -q to reproduce it.Textbook coverage
The engine targets the full sweep of finite-element topics in a standard undergraduate and graduate structural-mechanics curriculum. Each row is a topic and the capability that solves it.
| Curriculum topic | Capability | Status |
|---|---|---|
| Direct stiffness & spring assemblages | spring | ✓ |
| Bars & trusses (2D / 3D) | truss2d, truss3d | ✓ |
| Beams & plane frames | beam2d | ✓ |
| Space frames, grids (torsion, biaxial bending) | frame3d | ✓ |
| Distributed loads & equivalent nodal loads | element_loads | ✓ |
| Plane stress / plane strain, linear | cst, quad4 | ✓ |
| Higher-order plane elements (LST, Q8) | tri6, quad8 | ✓ |
| Isoparametric formulation & Gauss quadrature | quad4/quad8/hex8 | ✓ |
| 3D solids (tetrahedra, hexahedra) | tet4, hex8 | ✓ |
| Axisymmetric solids | axiquad4 | ✓ |
| Thermal stress / thermal loads | thermal + material alpha | ✓ |
| Body forces & surface tractions | body_forces, tractions | ✓ |
| Self-weight (gravity) | gravity | ✓ |
| Supports: pinned/fixed/roller, settlement, elastic | supports, values, springs | ✓ |
| Statically indeterminate structures | direct global solve | ✓ |
| Dynamics: natural frequencies & mode shapes | modal (consistent mass) | ✓ |
| Transient response (time integration) | transient (Newmark) | ✓ |
| Elastic stability / linear buckling | buckling (geometric stiffness) | ✓ |
| Heat conduction (steady + transient) | field + field_* elements | ✓ |
| Mass diffusion (Fick) | field (same kernel) | ✓ |
| Convection / Robin boundaries | convections | ✓ |
| Convection-diffusion transport | field + velocity | ✓ |
| Potential (inviscid) flow, seepage | field, k=1 | ✓ |
| Coupled thermo-mechanical | thermo_mechanical | ✓ |
| Nonlinear continuum damage | damage | ✓ |
| Shear-deformable (Timoshenko) beams | timo2d | ✓ |
| Plate bending (Reissner-Mindlin) | plate4 | ✓ |
| Elastoplasticity (von Mises / J2) | plasticity | ✓ |
| Linear-elastic fracture, K₁ (XFEM) | examples/xfem.py | ✓ |
Known gaps on the roadmap, deliberately not yet claimed: full shell elements (membrane + bending), geometric nonlinearity (large deformation), and contact. Plate bending, shear-deformable beams, and von Mises plasticity are now in the engine (rows above). The assembly core is built to extend to the rest.
Worked examples
Complete problems, each solved by the engine and checked against a
closed-form result. Copy any spec and send it to /solve,
/modal, /buckling, or /transient.
Truss bridge · truss2d
A 15-member Warren truss, pinned at one end and on a roller at the
other, with 60 kN hung at each interior bottom node. The engine
returns each member's axial force — bottom chord in tension, top chord
in compression — plus a mid-span deflection of 9.58 mm.
{
"title": "Warren truss, 3 x 60 kN",
"nodes": [[0,0],[4,0],[8,0],[12,0],[16,0],
[2,3],[6,3],[10,3],[14,3]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [
{ "type":"truss2d", "nodes":[0,1], "material":"steel", "section":{"A":2e-3} },
// …bottom & top chords, diagonals (15 members total)
],
"supports": [ {"node":0,"dofs":["ux","uy"]}, {"node":4,"dofs":["uy"]} ],
"loads": [ {"node":1,"fy":-60000}, {"node":2,"fy":-60000},
{"node":3,"fy":-60000} ]
}
Portal frame · beam2d
Two columns fixed at the base, a beam across the top, and a
40 kN lateral (wind) load at the top-left corner. The frame sways
2.74 mm and the base develops a
48.4 kN·m moment. Each member is subdivided so the
bending shape comes through.
{
"title": "Portal frame, 40 kN lateral",
"nodes": [[0,0],[0,4],[6,4],[6,0]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [
{ "type":"beam2d", "nodes":[0,1], "material":"steel", "section":{"A":0.012,"I":3e-4} },
{ "type":"beam2d", "nodes":[1,2], "material":"steel", "section":{"A":0.012,"I":3e-4} },
{ "type":"beam2d", "nodes":[2,3], "material":"steel", "section":{"A":0.012,"I":3e-4} }
],
"supports": [ {"node":0,"dofs":["ux","uy","rz"]}, {"node":3,"dofs":["ux","uy","rz"]} ],
"loads": [ {"node":1,"fx":40000} ]
}
Cantilever plate · quad4
A 6 × 2 m plate meshed into 192 quad elements, clamped
along the left edge, with a 200 kN downward shear on the right. The
engine returns per-element von Mises stress — peaking at
76.3 MPa in the clamped corners — and a tip deflection
of 5.79 mm.
nx, ny, Lx, Ly = 24, 8, 6.0, 2.0
nid = lambda ix, iy: iy*(nx+1) + ix
problem = {
"materials": {"steel": {"E": 200e9, "nu": 0.3}},
"nodes": [[x, y] for y in linspace(0,Ly,ny+1)
for x in linspace(0,Lx,nx+1)],
"elements": [
{"type":"quad4",
"nodes":[nid(ix,iy),nid(ix+1,iy),nid(ix+1,iy+1),nid(ix,iy+1)],
"material":"steel", "section":{"t":0.02,"kind":"plane_stress"}}
for iy in range(ny) for ix in range(nx)
],
"supports": [{"node":nid(0,iy),"dofs":["ux","uy"]} for iy in range(ny+1)],
"loads": [{"node":nid(nx,iy),"fy":-200000/(ny+1)} for iy in range(ny+1)]
}
Design loop — an agent sizing a beam
The pattern that makes physicsbase more than a calculator: solve, read the result, adjust, solve again — until a target factor of safety is met.
import httpx
YIELD, TARGET = 250e6, 2.0 # Pa, desired factor of safety
I = 4e-6
for _ in range(12):
problem = build_cantilever(I=I) # agent's own translation step
out = httpx.post(API, json=problem).json()
M = max(abs(e["moment_i"]) for e in out["elements"])
sigma = M * c / I
fos = YIELD / sigma
if fos >= TARGET:
break
I *= fos / TARGET * 1.1 # grow the section, try again
print(f"sized: I={I:.2e} m^4, FoS={fos:.2f}")
I; physicsbase is telling it the truth about the
resulting moment and stress.3D building frame · frame3d
A single-storey space frame: four columns fixed at the base, four roof
beams, and a lateral wind load at a corner. frame3d carries
axial force, torsion, and bending about both local axes at once — the roof
diaphragm twists as well as drifts. Each member returns
axial_force, torsion, and biaxial end moments.
{
"title": "3D frame, 30 kN corner wind load",
"nodes": [[0,0,0],[6,0,0],[6,0,5],[0,0,5],
[0,4,0],[6,4,0],[6,4,5],[0,4,5]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [
// 4 columns (0-4,1-5,2-6,3-7) + 4 roof beams (4-5,5-6,6-7,7-4)
{ "type":"frame3d", "nodes":[0,4], "material":"steel",
"section":{"A":8e-3,"Iy":4e-5,"Iz":4e-5,"J":6e-5} }
// …7 more members, same section
],
"supports": [
{"node":0,"dofs":["ux","uy","uz","rx","ry","rz"]},
{"node":1,"dofs":["ux","uy","uz","rx","ry","rz"]},
{"node":2,"dofs":["ux","uy","uz","rx","ry","rz"]},
{"node":3,"dofs":["ux","uy","uz","rx","ry","rz"]}
],
"loads": [ {"node":7,"fx":30000} ]
}
Distributed load + self-weight · element_loads · gravity
A cantilever carrying a uniform line load plus its own weight. Distributed
loads become exact equivalent nodal forces and moments; gravity turns
rho into a body force. Under the UDL alone the tip drops
w L⁴ / 8EI = 24.1 mm — matched by the engine.
{
"title": "Cantilever, 4 kN/m UDL + self-weight",
"nodes": [[0,0],[1,0],[2,0],[3,0]],
"materials": { "steel": { "E": 210e9, "nu": 0.3, "rho": 7850 } },
"elements": [
{ "type":"beam2d", "nodes":[0,1], "material":"steel", "section":{"A":0.01,"I":8e-6} },
{ "type":"beam2d", "nodes":[1,2], "material":"steel", "section":{"A":0.01,"I":8e-6} },
{ "type":"beam2d", "nodes":[2,3], "material":"steel", "section":{"A":0.01,"I":8e-6} }
],
"element_loads": [
{"element":0,"w":[0,-4000]}, {"element":1,"w":[0,-4000]},
{"element":2,"w":[0,-4000]}
],
"gravity": [0, -9.81],
"supports": [ {"node":0,"dofs":["ux","uy","rz"]} ]
}
Euler column buckling · analysis: buckling
A 3 m pin-ended steel column under axial compression. The buckling
analysis returns the load factor on the applied reference load. For this
section the engine reports a critical load of
≈ 1.84 MN — within 1% of Euler's
Pcr = π²EI/L².
{
"title": "Pin-ended column buckling",
"analysis": "buckling", "num_modes": 3,
"nodes": [[0,0],[0.375,0],[0.75,0], /* …down to */ [3,0]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [ /* chain of beam2d, section {A:0.02, I:8e-6} */ ],
"supports": [ {"node":0,"dofs":["ux","uy"]},
{"node":8,"dofs":["uy"]} ],
"loads": [ {"node":8,"fx":-1000} ] // reference compression
}
// -> { "load_factors": [1842.x, …], "critical_loads": [1.842e6, …] }
Beam natural frequencies · analysis: modal
The same cantilever, asked for its vibration modes instead of a
deflection. With rho supplied, the engine returns the natural
frequencies; the fundamental is
f₁ = (1.875² / 2π)·√(EI / ρA L⁴) ≈ 9.1 Hz, matched to
better than 1%.
curl -X POST https://api.physicsbase.dev/modal \
-d '{ "analysis":"modal", "num_modes":3,
"nodes": [[0,0], … ,[3,0]],
"materials": {"steel":{"E":210e9,"nu":0.3,"rho":7850}},
"elements": [ /* beam2d chain, {A:0.01,I:8e-6} */ ],
"supports": [{"node":0,"dofs":["ux","uy","rz"]}] }'
# -> { "frequencies_hz": [9.1, 57.0, 159.6], … }
Continuous beam — statically indeterminate · beam2d
A two-span beam on three supports under a uniform load. It's statically indeterminate, so the interior reaction can't be found from statics alone — the engine solves it directly. Expect a hogging (negative) moment over the middle support and an interior reaction larger than the end reactions.
{
"title": "Two-span continuous beam, 5 kN/m",
"nodes": [[0,0],[1,0],[2,0],[3,0],[4,0],
[5,0],[6,0],[7,0],[8,0]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [ /* 8 beam2d spans, section {A:0.01, I:2e-5} */ ],
"element_loads": [ /* {element:i, w:[0,-5000]} for i in 0..7 */ ],
"supports": [ {"node":0,"dofs":["ux","uy"]},
{"node":4,"dofs":["uy"]},
{"node":8,"dofs":["uy"]} ]
}
// reactions[4].fy is the large interior reaction; the span-0 element
// moment_j over the middle support is negative (hogging).
Multi-storey frame under wind · beam2d
A two-storey, one-bay moment frame with lateral loads at each floor. Fixed
column bases carry the overturning; the engine returns storey drift
(relative ux between floors) and the column base moments used
for design.
{
"title": "2-storey moment frame, wind",
// columns at x=0 and x=6; floors at y=0,3,6
"nodes": [[0,0],[0,3],[0,6],[6,0],[6,3],[6,6]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [
{"type":"beam2d","nodes":[0,1],"material":"steel","section":{"A":0.012,"I":3e-4}},
{"type":"beam2d","nodes":[1,2],"material":"steel","section":{"A":0.012,"I":3e-4}},
{"type":"beam2d","nodes":[3,4],"material":"steel","section":{"A":0.012,"I":3e-4}},
{"type":"beam2d","nodes":[4,5],"material":"steel","section":{"A":0.012,"I":3e-4}},
{"type":"beam2d","nodes":[1,4],"material":"steel","section":{"A":0.012,"I":3e-4}},
{"type":"beam2d","nodes":[2,5],"material":"steel","section":{"A":0.012,"I":3e-4}}
],
"supports": [ {"node":0,"dofs":["ux","uy","rz"]},
{"node":3,"dofs":["ux","uy","rz"]} ],
"loads": [ {"node":1,"fx":20000}, {"node":2,"fx":20000} ]
}
3D truss tower · truss3d
A single bay of a lattice tower: a square base and a square top with four legs and cross bracing, under a vertical and a lateral load at the top. All members are axial-only; the engine returns each member force and the top node's 3D displacement.
{
"title": "Lattice tower bay",
// base square z=0 (nodes 0-3), top square z=4 (nodes 4-7)
"nodes": [[0,0,0],[2,0,0],[2,2,0],[0,2,0],
[0,0,4],[2,0,4],[2,2,4],[0,2,4]],
"materials": { "steel": { "E": 210e9, "nu": 0.3 } },
"elements": [ /* 4 legs (0-4,1-5,2-6,3-7), top ring, base ring, diagonals;
every one: type truss3d, section {A:1e-3} */ ],
"supports": [ /* pin all four base nodes: dofs ["ux","uy","uz"] */ ],
"loads": [ {"node":4,"fz":-50000,"fx":15000} ]
}
Beam on an elastic foundation · springs
A grade beam resting on soil: a grounded vertical spring at every node models the subgrade (Winkler foundation). The load sinks the beam into the soil rather than into rigid supports — reactions are distributed through the springs.
k_soil = 2.0e6 # N/m per node (subgrade modulus × tributary length)
problem = {
"nodes": [[x, 0] for x in range(11)], # 10 m beam, 1 m spacing
"materials": {"c": {"E": 30e9, "nu": 0.2}},
"elements": [ {"type":"beam2d","nodes":[i,i+1],"material":"c",
"section":{"A":0.09,"I":6.75e-4}} for i in range(10) ],
"springs": [ {"node":i,"dof":"uy","k":k_soil} for i in range(11) ],
# one horizontal restraint stops rigid sliding
"supports": [ {"node":0,"dofs":["ux"]} ],
"loads": [ {"node":5,"fy":-120000} ] # central column load
}
Support settlement · prescribed displacement
Force a known displacement instead of a load: give a support a
values array. Here the middle support of a continuous beam
settles 10 mm — the engine returns the extra moments and reactions the
settlement induces.
"supports": [
{ "node": 0, "dofs": ["ux","uy"] },
{ "node": 4, "dofs": ["uy"], "values": [-0.010] }, // 10 mm settlement
{ "node": 8, "dofs": ["uy"] }
]
Thermal stress in a restrained bar · thermal
Give the material an expansion coefficient alpha and each
element a temperature rise dT. A bar fixed at both ends and
heated can't expand, so it develops a compressive stress
σ = −E·α·ΔT — returned directly.
{
"title": "Restrained bar, +50°C",
"nodes": [[0,0],[1,0]],
"materials": { "steel": { "E": 200e9, "nu": 0.3, "alpha": 12e-6 } },
"elements": [ {"type":"truss2d","nodes":[0,1],"material":"steel","section":{"A":0.01}} ],
"supports": [ {"node":0,"dofs":["ux","uy"]}, {"node":1,"dofs":["ux","uy"]} ],
"thermal": [ {"element":0,"dT":50} ]
}
// elements[0].axial_stress = -E*alpha*dT = -120 MPa (compression)
3D solid stress block · hex8 / tet4
Full 3D stress with trilinear bricks or linear tets. A clamped block under
end shear returns the 3D stress tensor and von Mises per element —
peaking (red) at the fixed face. Build a structured grid of hex8
cells and clamp one face.
{
"nodes": [[0,0,0],[1,0,0],[1,1,0],[0,1,0],
[0,0,1],[1,0,1],[1,1,1],[0,1,1]],
"materials": { "steel": { "E": 200e9, "nu": 0.3 } },
"elements": [ {"type":"hex8","nodes":[0,1,2,3,4,5,6,7],"material":"steel"} ],
"supports": [ /* clamp the x=0 face: nodes 0,3,4,7 in ux,uy,uz */ ],
"loads": [ /* tension on the x=1 face nodes 1,2,5,6 */ ]
}
// elements[0].stress = [sxx, syy, szz, sxy, syz, szx], elements[0].von_mises
Axisymmetric pressure vessel · axiquad4
Model a body of revolution in the r-z half-plane; axiquad4
carries the hoop strain automatically and integrates over the full ring,
so nodal loads are total ring forces. Ideal for thick cylinders, discs, and
nozzles.
{
// nodes are [r, z]; keep r > 0 (off the axis)
"nodes": [[1.0,0],[1.2,0],[1.2,1],[1.0,1]],
"materials": { "steel": { "E": 200e9, "nu": 0.3 } },
"elements": [ {"type":"axiquad4","nodes":[0,1,2,3],"material":"steel"} ],
"supports": [ /* restrain uz on z=0 edge; radial free */ ],
"loads": [ /* internal pressure as radial ring loads on inner nodes */ ]
}
// stress = [srr, szz, s_hoop, srz] per element
Transient step response · analysis: transient
Newmark time integration of the dynamic response. A load applied suddenly
(a step) and held makes an undamped structure overshoot to about twice its
static deflection — the classic dynamic amplification. Add Rayleigh
damping to watch it decay.
curl -X POST https://api.physicsbase.dev/transient \
-d '{ "analysis":"transient", "dt":2e-4, "n_steps":150,
"damping":[0.5, 1e-5], "monitor":[1,"uy"],
"nodes": [[0,0], … ], "materials": {"s":{"E":210e9,"nu":0.3,"rho":7850}},
"elements": [ /* beam2d chain */ ],
"supports": [{"node":0,"dofs":["ux","uy","rz"]}],
"loads": [{"node":1,"fy":-5000}] }'
# -> { "peak_displacement": …, "peak_time": …,
# "monitor": { "history": [ … ] }, "frames": [ … ] }
Combined load + thermal · superposition
Mechanical and thermal effects add in a single linear solve — no need to run cases separately. Here a beam carries a tip load and is heated, and the reactions reflect both at once.
{
"title": "Fixed-fixed beam: load + heating",
"nodes": [[0,0],[1,0],[2,0],[3,0],[4,0]],
"materials": { "steel": { "E": 210e9, "nu": 0.3, "alpha": 12e-6 } },
"elements": [ /* 4 beam2d spans, section {A:0.01, I:8e-6} */ ],
"supports": [ {"node":0,"dofs":["ux","uy","rz"]},
{"node":4,"dofs":["ux","uy","rz"]} ],
"loads": [ {"node":2,"fy":-8000} ],
"thermal": [ /* {element:i, dT:40} for each span -> axial thrust */ ]
}
// reactions carry both the bending from the load and the axial
// thrust from restrained thermal expansion, in one solve.
Mixed elements — braced frame · beam2d + truss2d
Different element types share one model: rigid beam2d members
for the moment frame plus pin-ended truss2d diagonals for the
bracing. The engine assembles them into one stiffness matrix — the braces
pick up axial force, the frame carries bending.
"elements": [
{"type":"beam2d","nodes":[0,1],"material":"s","section":{"A":0.012,"I":3e-4}},
{"type":"beam2d","nodes":[1,2],"material":"s","section":{"A":0.012,"I":3e-4}},
{"type":"beam2d","nodes":[3,2],"material":"s","section":{"A":0.012,"I":3e-4}},
{"type":"truss2d","nodes":[0,2],"material":"s","section":{"A":6e-4}}, // diagonal brace
{"type":"truss2d","nodes":[3,1],"material":"s","section":{"A":6e-4}} // diagonal brace
]
beam2d (which adds rz) and truss2d
mix freely in 2D — the truss simply doesn't engage the rotational DOF.Roadmap: shell elements, geometric & material nonlinearity, and contact. The assembly core is built to extend.