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.

bashcopy
# 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}]
  }'
Result: the tip node moves 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.

Pattern: describe → 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.

#StepHow
1DiscoverCall fem_capabilities (MCP) or GET /capabilities. It returns the element types, DOF names, load components, and analyses — read them, don't guess.
2Pick the elementUse 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.).
3Build the JSONFill nodes, materials, elements, supports, loads. Add enough supports to remove all rigid-body motion.
4Callfem_solve / fem_modal / fem_buckling, or POST to /solve with "analysis" set.
5Read & actIndex 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…UseAnalysis
Pin-jointed truss (2D)truss2dstatic
Space truss (3D)truss3dstatic
Beam / portal frame (2D bending)beam2dstatic
3D frame, torsion, biaxial bendingframe3dstatic
Panel / bracket / plane partquad4 or cststatic
Vibration — natural frequenciesany (needs rho)modal
Column / frame stabilitybeam2d / frame3dbuckling

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?".

agent ⇄ physicsbasecopy
# 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
Key idea: the agent only wrote geometry and a load. Every number it reasoned about — deflection, moment, stress — came back from the engine, so the answer is computed, not hallucinated.

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…CauseFix
no supports; stiffness is singularThe structure can drift or spin freely (rigid-body motion).Add supports until every rigid-body mode is removed, or add springs.
unknown element typetype misspelled or unsupported.Use one of truss2d, truss3d, beam2d, frame3d, cst, quad4.
needs N nodes, got MWrong 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 '…' invalidUsed 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 validApplied 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 buckleBuckling run with no compression in any member.Apply an axial (compressive) reference load along the member(s).
element references node N out of rangeA node index exceeds the nodes list.Node indices are zero-based; keep them < len(nodes).
Sanity checks the engine gives you for free: reactions sum to the applied load (equilibrium), free DOFs report ~0 reaction, and values like 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.

FieldTypeDescription
nodesnumber[][]List of coordinates, 2D or 3D.
materialsobjectNamed map of {E, nu, rho?} (Young's modulus, Poisson's ratio, density).
elementsobject[]{type, nodes, material, section, id?} — see the element reference.
supportsobject[]{node, dofs, values?}. DOFs: ux, uy, uz, rx, ry, rz. values prescribes non-zero displacement.
loadsobject[]{node, fx?, fy?, fz?, mx?, my?, mz?} — nodal forces and moments.
element_loadsobject[]{element, w:[wx,wy(,wz)]} — distributed load (force/length) on a beam or frame element.
springsobject[]{node, dof, k} — grounded elastic support of stiffness k.
gravitynumber[][gx, gy(, gz)] — acceleration vector for self-weight (needs rho).
analysisstring"static" (default), "modal", or "buckling".
titlestringOptional label echoed back in the summary.

Element reference

Choose an element family by type. The section keys differ per family.

typeDOF / nodesectionUse for
truss2dux, uyA2D bars / pin-jointed trusses
truss3dux, uy, uzA3D space trusses
springux, uykSpring assemblages, elastic links
beam2dux, uy, rzA, I2D frames, beams, Euler-Bernoulli bending
timo2dux, uy, rzA, I, ks?Timoshenko beam — includes shear (deep beams)
frame3dux…rz (6)A, Iy, Iz, J, G?, ref?3D frames: axial + torsion + biaxial bending
plate4uz, rx, ryt, ks?Reissner-Mindlin plate bending
cstux, uyt, kind2D continuum, linear triangle
quad4ux, uyt, kind2D continuum, bilinear quad
tri6ux, uyt, kind2D continuum, quadratic triangle (LST)
quad8ux, uyt, kind2D continuum, 8-node serendipity quad
tet4ux, uy, uz3D solid, linear tetrahedron
hex8ux, uy, uz3D solid, trilinear hexahedron
axiquad4ur, uzAxisymmetric 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.

jsoncopy
{
  "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.

javascriptcopy
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:

json · remotecopy
// MCP client config — remote endpoint
{
  "mcpServers": {
    "physicsbase": {
      "url": "https://<your-app>.up.railway.app/mcp"
    }
  }
}

Local (stdio) — run the server as a subprocess instead:

json · stdiocopy
{
  "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.

pythoncopy
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).

json · response (excerpt)copy
{
  "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%.

Reading it: a load factor of 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.

PhysicsukNotes
Heat conductiontemperatureconductivitysource = heat generation; c = ρ·cp for transient
Mass diffusionconcentrationdiffusivityFick's law; identical math
Potential flowvelocity potential1inviscid, irrotational; ∇u = velocity
Seepage / Darcyhydraulic headpermeabilitygroundwater flow
Transportconcentrationdiffusivityset 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.

json · POST /fieldcopy
{
  "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.

json · convection-diffusioncopy
{
  "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).

python · SDKcopy
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.

json · POST /damagecopy
{
  "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,…}] }
On CFD: physicsbase covers inviscid potential flow and scalar transport (convection-diffusion) through the field kernel. Full viscous Navier-Stokes — pressure-velocity coupling, turbulence — is the honest frontier on the roadmap, not yet claimed.

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

bashcopy
cd fem-engine
pip install -e ".[api,mcp,dev]"      # core is just numpy + scipy + pydantic

Solve in Python (no server)

pythoncopy
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

bashcopy
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

bashcopy
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

bashcopy
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

SuiteValidates against
test_validation.pyAxial bar PL/AE, symmetric truss equilibrium, cantilever tip PL³/3EI and rotation ML/EI, CST/Quad4 uniaxial tension, 3D bar.
test_advanced.py3D 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.pySpring 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.pyHeat 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.pyREST 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.

python · a real testcopy
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)
Status: 53 / 53 passing. Every suite is executed and green — core elements, 3D frames, modal, buckling, 3D solids, thermal, axisymmetric, tractions, and transient all match their closed-form targets to tolerance. Run 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.

Every row is validated. A means the capability is checked against a closed-form solution in the executed test suite (53 / 53 passing).
Curriculum topicCapabilityStatus
Direct stiffness & spring assemblagesspring
Bars & trusses (2D / 3D)truss2d, truss3d
Beams & plane framesbeam2d
Space frames, grids (torsion, biaxial bending)frame3d
Distributed loads & equivalent nodal loadselement_loads
Plane stress / plane strain, linearcst, quad4
Higher-order plane elements (LST, Q8)tri6, quad8
Isoparametric formulation & Gauss quadraturequad4/quad8/hex8
3D solids (tetrahedra, hexahedra)tet4, hex8
Axisymmetric solidsaxiquad4
Thermal stress / thermal loadsthermal + material alpha
Body forces & surface tractionsbody_forces, tractions
Self-weight (gravity)gravity
Supports: pinned/fixed/roller, settlement, elasticsupports, values, springs
Statically indeterminate structuresdirect global solve
Dynamics: natural frequencies & mode shapesmodal (consistent mass)
Transient response (time integration)transient (Newmark)
Elastic stability / linear bucklingbuckling (geometric stiffness)
Heat conduction (steady + transient)field + field_* elements
Mass diffusion (Fick)field (same kernel)
Convection / Robin boundariesconvections
Convection-diffusion transportfield + velocity
Potential (inviscid) flow, seepagefield, k=1
Coupled thermo-mechanicalthermo_mechanical
Nonlinear continuum damagedamage
Shear-deformable (Timoshenko) beamstimo2d
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.

Solved Warren truss coloured by member force
json · problemcopy
{
  "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.

Solved portal frame swaying under lateral load
json · problemcopy
{
  "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.

Solved cantilever plate von Mises field
python · build the meshcopy
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.

pythoncopy
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}")
Every iteration is a real solve. The agent is choosing 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.

json · problemcopy
{
  "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.

json · problemcopy
{
  "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².

json · problemcopy
{
  "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%.

bashcopy
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.

json · problemcopy
{
  "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.

json · problemcopy
{
  "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.

json · problemcopy
{
  "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.

python · build itcopy
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.

json · supportscopy
"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.

json · problemcopy
{
  "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.

Solved 3D hex8 block, von Mises stress, isometric
json · one hex8 cellcopy
{
  "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

Solved axisymmetric pressure vessel, hoop stress cross-section

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.

json · wall segment (r-z)copy
{
  // 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.

bashcopy
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.

json · problemcopy
{
  "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.

json · elementscopy
  "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
  ]
All elements in a model share the same per-node DOF layout, so 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.