1. Introduction to Bond Graphs
Bond graphs are a domain-independent graphical notation for modelling dynamic physical systems. Every bond represents the flow of power (energy per unit time) between two elements and carries two conjugate variables whose product is power:
- Effort (e): voltage, force, pressure, torque, temperature, …
- Flow (f): current, velocity, volumetric flow rate, angular velocity, entropy flow, …
Because the same nine elements describe electrical, mechanical, hydraulic, thermal and rotational phenomena, bond graphs are particularly powerful for multi-physics systems where domains are coupled (e.g. a DC motor, a hydraulic actuator, or a robotic arm).
Power, half-arrows and causality
The half-arrow on each bond points in the direction of positive power flow. The short perpendicular bar — the causal stroke — marks the end at which effort is imposed. Flow is then necessarily imposed at the opposite end. Causality is not just decoration: it determines which variable each element computes and ultimately whether the model can be simulated as an ODE/DAE.
Rule of thumb. An I (inertia) wants its effort to be imposed (so it can integrate einto momentum p). A C (capacitor / spring) wants its flow to be imposed (so it can integrate finto displacement q). Sources always impose their own quantity by definition.
1b. A brief history of bond graphs
Bond graphs were invented by Henry M. Paynter at MIT in April 1959. Paynter, working on hydroelectric and hydraulic-servo problems, was looking for a notation that could describe the power flow through a multi-physics system without first committing to a particular domain (circuits, free-body diagrams, fluid networks). His insight was that every physical interaction can be drawn as a single line — a bond — carrying an effort and a flow whose product is power, and that energy storage, dissipation, and transduction can each be reduced to a small library of primitive elements joined by two kinds of junction.
Paynter first taught the method in his MIT course on system dynamics and published the consolidated theory in Analysis and Design of Engineering Systems (MIT Press, 1961). In that book the 0- and 1-junctions, the Se/Sf/R/C/I/TF/GY element set, and the half-arrow power-direction convention appear essentially in the form still used today.
Through the 1960s and 1970s two of Paynter's students, Dean Karnopp and Ronald Rosenberg, turned the notation into an engineering discipline. They added the causal stroke for systematically assigning which side of a bond computes effort and which computes flow, formalised the derivation of state equations directly from a causal bond graph, and wrote the first textbook aimed at practising engineers — System Dynamics: A Unified Approach (Karnopp, Margolis & Rosenberg, 1975, now in its 6th edition). Their treatment made bond graphs a standard tool for modelling electromechanical, hydraulic, and vehicle systems.
From the 1980s onwards the method spread into mechatronics and simulation tooling. Research groups in Twente (Breedveld, Broenink) and Lyon (Dauphin-Tanguy, Borne) extended bond graphs to thermal and thermodynamic systems, multibody dynamics, and fault diagnosis, and built dedicated software — CAMP-G, 20-sim, Dymola's bond-graph library, and others. The acausal modelling language Modelica (1997) is a direct intellectual descendant: its connector / flow-variable semantics generalise the 0-junction, and many Modelica libraries are internally organised as bond graphs.
Today bond graphs are taught in system-dynamics and mechatronics curricula worldwide and are routinely used for hybrid vehicles, robotics, energy systems, biomedical models, and any other domain where several physical effects are tightly coupled. BondGraph Studio follows the Karnopp-Margolis-Rosenberg conventions: the same nine elements, the same causal-stroke rules, and the same derivation of state-space equations described in the next chapters.
Further reading. Paynter, H. M., Analysis and Design of Engineering Systems, MIT Press, 1961. Karnopp, D. C., Margolis, D. L., & Rosenberg, R. C., System Dynamics: Modeling, Simulation, and Control of Mechatronic Systems, Wiley, 6th ed. 2012. Breedveld, P. C., Modeling and Simulation of Dynamic Systems using Bond Graphs, in Control Systems, Robotics and Automation, EOLSS, 2008.
2. The Nine Basic Elements
| Symbol | Name | Role | Electrical | Mechanical (translational) | Mechanical (rotational) | Hydraulic |
|---|---|---|---|---|---|---|
| Se | Effort source | Imposes effort | Voltage source | Force source | Torque source | Pressure source |
| Sf | Flow source | Imposes flow | Current source | Velocity source | Angular-velocity source | Pump (volumetric) |
| R | Resistor | Dissipates energy | Resistor | Linear damper | Rotary damper | Pipe friction |
| C | Capacitor | Stores effort (q) | Capacitor | Linear spring | Torsional spring | Tank/accumulator |
| I | Inertia | Stores flow (p) | Inductor | Mass | Rotational inertia | Fluid inertia |
| TF | Transformer | Scales e and f | Ideal transformer | Lever | Gear pair | Piston (area) |
| GY | Gyrator | Swaps e and f | Hall sensor | — | DC motor (K) | — |
| 0 | 0-junction (ô) | Common effort | Parallel node | Common force | Common torque | Common pressure |
| 1 | 1-junction (î) | Common flow | Series loop | Common velocity | Common ω | Common volumetric flow |
Mnemonic. The 0-junction behaves like a circuit node (same voltage, currents sum to zero). The 1-junction behaves like a circuit loop (same current, voltages sum to zero).
3. A guided tour of the editor
The editor has four main areas. Keep this docs window open side-by-side with the app while you follow the tutorials below.
- Toolbar (top). Save / load
.bgif, open built-in examples (book icon), zoom, delete selection, and open this documentation (the ? icon at the right). - Element Library (left). Drag a symbol onto the canvas to create an element.
- Canvas (centre). Hover an element to reveal its outline and central connection circle. Click the centre of one element, then the centre of a second to draw a bond between them. Bonds route automatically along the shortest path.
- Properties & Simulation (right). Edit parameters of the selected element/bond. The Simulation panel sets the time interval, step size and solver, then runs the model.

4. Worked tutorials
All examples below ship with the app — open the Examplesmenu in the toolbar (book icon) and pick one to follow along. Each tutorial walks through the construction step by step and shows the simulation output you should obtain.
Tutorial A — Single-Joint Robotic Arm (rotational mechanics, recommended starting point)
A torque source τ_in drives a single joint that has rotational inertia J_arm, a torsional return spring and a damper. The bond graph is shown below; in rotational mechanics the three storage/dissipative elements all share the same angular velocity ω, so they must be wired through a single 1-junction.
Step-by-step construction
- Open the Examples menu and click Single-Joint Arm, or build it from scratch as follows.
- From the Element Library, drag an Effort Source (Se) onto the canvas. With it selected, set
label = τ_inandeffort = 2N·m in the Properties panel. - Drag a 1-Junction next to it.
- Drag an Inertia (I); set
label = J_arm,inductance = 0.5(this parameter is the rotational inertia J, kg·m²). - Drag a Capacitor (C); set
label = Spring,capacitance = 0.4(compliance 1/k, so stiffness k = 2.5 N·m/rad). - Drag a Resistor (R); set
label = Damper,resistance = 0.3N·m·s/rad. - Hover each element so its central connection circle appears. Click the circle of τ_in, then the circle of the 1-junction — a bond is created. Repeat from the 1-junction to J_arm, Spring, and Damper.
- For J_arm, click the bond and set
causal stroke = from(effort imposed at the junction side, so the inertia integrates effort into momentum). The other three bonds use the defaultto.
Run the simulation
- In the Simulation panel set
End = 10s,Step = 0.01s, solverBDF-2. - Click Run Simulation. A green dot and a mini-plot appear when it succeeds.

- Click Click to expand plots. The plot dialog opens; the chips at the bottom let you toggle each of the ten variables on or off.

p_J_arm (angular momentum) and q_Spring (angular displacement) settle to the equilibrium q = τ/k = 2/2.5 = 0.8 rad.Sanity check. The natural frequency is ωn = √(k/J) = √(2.5/0.5) ≈ 2.24 rad/s, period ≈ 2.81 s. You should count roughly 3.5 oscillations in the 10 s window — which matches the figure.
Tutorial B — Series RLC Circuit (electrical, the "Rosetta-stone" example)
Topologically identical to the arm above, but with electrical labels. A voltage source drives a resistor, inductor and capacitor in series — they share the same current and therefore connect through a 1-junction.
- Drag an Se (V_in,
effort = 10V), a 1-Junction, an R (1 Ω), an I (L = 0.1 H, set asinductance = 0.1) and a C (10 mF,capacitance = 0.01). - Wire all three storage/dissipative elements to the 1-junction; wire V_in to the same 1-junction.
- Set the bond going to I with causal stroke
from(the inductor integrates voltage into flux), the othersto. - Run for 2 s with step 1 ms. The capacitor charge
q_Crises monotonically; the currentf_1_Rshows a damped first-order response.
Tutorial C — DC Motor (electrical ↔ mechanical, multi-physics)
The gyrator couples two domains by exchanging effort and flow: a current produces a torque and an angular velocity produces a back-EMF. The motor constant K is the gyrator modulus (units: N·m/A or, equivalently, V·s/rad).
What is τ_load?
On the mechanical side we add an effort sourcecalled τ_load. It models an external load torque applied to the shaft (gravity, payload, gripper reaction, brake, …) — i.e. the torque the motor has to push against. Physically it closes the power balance: without it the motor would accelerate freely until friction alone limited it, which is rarely what you want to study.
We chose to write it as an effort source because in most use cases the load torque is known (or estimated) as a function of time. If instead you know the shaft speed imposed by an external mechanism, replace it by a flow source Sf with the prescribed angular velocity. The previous version of these docs called this element Se_load, which was confusing because it conflated the symbol "Se" with the variable name; we now write τ_load with units N·m to make the physical meaning explicit.
Step-by-step construction
- Electrical 1-junction. Drag an Se (
V_dc = 12V), a 1-junction, an R (R_a = 1Ω, armature resistance) and an I (L_a = 0.01H, armature inductance). Wire them together so the current is shared (1-junction). - Gyrator. Drag a GY, set
modulus = 0.1N·m/A. The "left" port (first bond) sees the electrical side, the "right" port the mechanical side. - Mechanical 1-junction. Drag a 1-junction, an I (
J_rotor = 0.001kg·m²), an R (b_fric = 0.01N·m·s/rad) and an Se (τ_load = 0.05N·m). - Wire: GY → mechanical 1-junction; 1-junction → J_rotor (causal stroke
from); 1-junction → b_fric (to); 1-junction → τ_load (to). - Set
End = 1s, step 1 ms, BDF-2, and run. The shaft accelerates until the back-EMF and friction balance the supply voltage;p_J_rotorrises and saturates.
More examples (brief)
- Mass-spring-damper — same topology as the arm but in translation. I = mass, C = 1/stiffness, R = viscous damping. Available from the Examples menu.
- Hydraulic-mechanical actuator — a TF with modulus equal to the piston area A couples pressure × volumetric flow on the hydraulic side to force × velocity on the mechanical side.
- Thermo-mechanical bimetallic strip — a TF converts a temperature difference into a deflection; C represents heat capacity and another C represents the structural spring.
5. Extracting state-space variables
In a bond graph the state variables are exactly the generalised displacements q stored on each Celement and the generalised momenta p stored on each I element. The simulator names them automatically:
q_label— displacement on the C-element (charge, position, angle, fluid volume, …).p_label— momentum on the I-element (flux linkage, linear momentum, angular momentum, fluid momentum, …).
Every other simulator output (e_*, f_*) is an algebraic function of the states and the source inputs. To recover physically meaningful quantities, divide by the storage parameter:
| Element | Stored state | Physical reading |
|---|---|---|
| C (translational spring) | q | position x = q (m); spring force F = q/C |
| C (torsional spring) | q | angle θ = q (rad); spring torque τ = q/C |
| I (mass) | p | velocity v = p/m (m/s); kinetic energy = p²/(2m) |
| I (rotor) | p | angular velocity ω = p/J (rad/s) |
| I (inductor) | p | current i = p/L (A) |
Deriving the state-space equations by hand
The simulator does this automatically, but knowing the procedure is essential for sanity-checking results, designing controllers, and writing reports. The goal is to obtain the canonical form
ẋ = A x + B u y = C x + D uwhere x collects the q on each integral-causalC and the p on each integral-causal I,u collects the Se/Sf sources, and yis any chosen output. The recipe is mechanical:
- Assign causality (SCAP). Apply the Sequential Causality Assignment Procedure: (a) propagate causality from each source, (b) give every I integral causality (effort in, flow out) and every C integral causality (flow in, effort out), then propagate through 0/1 junctions, TF and GY, (c) finally assign the remaining R elements. If a storage element is forced into derivative causality, it is not an independent state — its variable is algebraically determined by the other states and contributes a dependent equation (and, for linear systems, a reduced-order model).
- List the state vector.
x = [q_C₁, …, p_I₁, …]ᵀ, one entry per integral-causal storage element. - Write constitutive laws. For each element, express its output variable (the one not imposed by its causal stroke) in terms of its state or input:
- C:
e = q / C - I:
f = p / I - R (resistive causality):
e = R forf = e / R - Se:
e = u(t); Sf:f = u(t) - TF (modulus m):
e₁ = m e₂,f₂ = m f₁ - GY (modulus r):
e₁ = r f₂,e₂ = r f₁
- C:
- Apply junction laws. At a 1-junctionall flows are equal and efforts sum (with bond half-arrow signs) to zero. At a 0-junction all efforts are equal and flows sum to zero. These are your algebraic equations.
- Solve for the state derivatives. Use the integration laws
q̇ = f(on each C) andṗ = e(on each I) and eliminate the algebraic variables until each ẋ is expressed purely in terms of x and u. - Read off A, B, C, D. Collect coefficients into matrices. For nonlinear constitutive laws keep ẋ = f(x,u) and linearise around an operating point if needed.
Worked example — series RLC (Tutorial B)
One 1-junction connects V_in (Se), R, L (I) and C. SCAP gives L and C integral causality and R resistive causality. States: x = [q_C, p_L]ᵀ, input u = V_in.
Constitutive laws: e_C = q_C / C, f_L = p_L / L, e_R = R · f_R. The 1-junction enforces a common flow f = f_L = f_R = f_C and effort balance V_in = e_R + e_L + e_C. Therefore e_L = V_in − R (p_L/L) − q_C/C.
Integration laws then give
q̇_C = f_C = p_L / L ṗ_L = e_L = −(1/C) q_C − (R/L) p_L + V_inwhich is the state-space form with
A = [[ 0, 1/L ], [ −1/C, −R/L ]] B = [[0], [1]]Choosing output = capacitor voltage gives y = (1/C) q_C, so C = [1/C, 0], D = 0. The eigenvalues of A recover the familiar damped-oscillator poles s = −R/(2L) ± √((R/(2L))² − 1/(LC)).
Handling derivative causality
If two I elements share a 1-junction (e.g. two masses rigidly connected), one is forced into derivative causality. Its momentum is no longer independent — instead p_dep = (I_dep / I_indep) · p_indep. Substitute this algebraic relation back into the effort balance; the result is an ODE in the reduced state vector, exactly as Lagrangian mechanics with a constraint would give. The simulator handles this implicitly via the DAE formulation, but doing it by hand once makes the structure obvious.
How to read them off in the editor
- Run the simulation. Open the plot dialog (Click to expand plots).
- The variable chips at the bottom list every output. State variables are easy to spot — they start with
p_orq_. - Click chips to toggle traces on/off. Click + Add Plot to compare several states on separate axes (recommended when their magnitudes differ).
Scaling up: a humanoid robot
A full humanoid with n rigid joints has 2n state variables — one momentum per actuated inertia and one displacement per joint compliance — plus extra states for any flexible links, transmission elasticity, foot-ground contact springs, and motor inductances. The bond-graph workflow is identical to Tutorial A applied joint-by-joint:
- Each rigid link or rotor → an I-element on a 1-junction representing the joint's common angular velocity.
- Each transmission elasticity (harmonic-drive flex, belt) → a C-element on a 0-junction connecting the actuator-side and load-side 1-junctions.
- Each motor → a GY (back-EMF/torque coupling) bridging an electrical 1-junction to the mechanical 1-junction.
- Each gear pair / lever → a TF.
- Multibody coupling between adjacent links is captured by sharing the kinematic 1-junctions (or 0-junctions if forces are common).
Once the model runs, every state variable carries a p_ or q_ name derived from the element label you typed in the Properties panel. So a model with elements named J_hipR, J_kneeR, spring_hipR, spring_kneeR… will produce outputs p_J_hipR, p_J_kneeR, q_spring_hipR, q_spring_kneeR that you can plot or export as the state-space trajectory.
Caveat. The current solver is a dense BDF integrator suitable for a few dozen states. A full humanoid (typically 50–200 states) will work but be slow; build the model joint-by-joint, validate each subsystem, and consider saving recurring substructures (a leg, an arm) as reusable components via the toolbar's Save as component button.
6. Thermodynamics and the classical machines
Bond graphs extend naturally to thermal and thermodynamic systems by choosing a power-conjugate pair for the heat and work flows. Two conventions are common; both are supported by the same nine elements.
| Convention | Effort e | Flow f | Product e·f |
|---|---|---|---|
| Pseudo bond graph (heat transfer) | Temperature T [K] | Heat-flow rate Q̇ [W] | Power-like, not true power |
| True bond graph (thermodynamics) | Temperature T [K] | Entropy flow Ṡ [W/K] | T·Ṡ = heat power [W] |
| Compression work | Pressure p [Pa] | Volumetric flow V̇ [m³/s] | p·V̇ = mechanical power [W] |
With these pairs, the standard elements take a thermodynamic interpretation:
- C: thermal capacitance (heat capacity m·cp) or compressible volume.
- R: thermal resistance (conduction, convection) or throttle / flow restriction.
- I: rarely used in thermal — fluid inertia in pipes uses I = ρL/A.
- TF: piston (force ↔ pressure with ratio 1/A) or ideal heat exchanger ratio.
- GY: thermo-electric Seebeck/Peltier coupling, or Carnot-cycle conversion between T·Ṡ and p·V̇ at a moving boundary.
- Se, Sf: a fixed reservoir temperature, an imposed heat flux, or a pressure source.
Tutorial D — Lumped thermal mass cooling to ambient
The "RC of thermodynamics": a hot body with heat capacity Cth = m·cp loses heat through a wall of thermal resistance Rth to an ambient held at temperature Tamb.
- Place an Se (label
T_amb, value 293 K). - Place a 0-junction next to it (heat flow leaving the body splits between R and the source — both see the same temperature difference reference, so we use a 1-junction for the difference; here we route through the simpler RC topology described below).
- Use a 1-junction to enforce equal heat-flow rate Q̇ through R and into C: connect Cth (label
C_body, 4180 J/K for 1 kg of water) and Rth (labelR_wall, 0.5 K/W) on this 1-junction; the sourceT_ambsits on the other side of R. - Set the initial state
q_C_body= 60·4180 = 250 800 J (i.e. the body is at 60 °C above ambient). Equivalently, the temperature is T = q/C. - Run for 600 s. Plot
q_C_body; divide by Cth to recover temperature. The decay is exponential with time-constant τ = R·C ≈ 2090 s.
Tutorial E — Carnot heat engine as a GY
The classical heat engines (Carnot, Stirling, Otto, Brayton) all convert a thermal power T·Ṡ into a mechanical power F·v or τ·ω. In bond-graph terms, the converter is a gyrator (because effort on one side maps to flow on the other) augmented with a dissipator capturing the second-law losses.
The gyrator modulus is the Carnot efficiency η = 1 − Tcold/Thot: it sets the ratio between heat flow in and mechanical power out. Real machines add an R element on the cold-side junction representing the unavoidable entropy generation. A Stirling engine differs only by adding a C (regenerator heat capacity) bridging the two junctions; an Otto cycle replaces the GY by a periodically modulated TF whose ratio follows the piston motion.
Why this matters. Modelling the engine as GY + R + C exposes the entropy-balance inequality (Ṡgen ≥ 0) as the integrated power on the R element — exactly the second law. The same template scales up to HVAC chillers, refrigeration cycles and cryocoolers.
7. Automatic control — PI & PID in a bond graph
Bond graphs are usually used for the plant — the physics being controlled — while the controller lives in a separate block diagram. In BondGraph Studio you can do both in one model because Se and Sf sources accept an expression that can read the model's state variables (the q_* and p_*generated by every C and I element). That is enough to close a feedback loop without leaving the diagram.
Tutorial F — Single-tank PI level control
The plant is one tank: capacitance C_h (cross- section area, in unit-volume per unit-height), gravity drain R (linear). With h = q_h / C_h the mass balance is
q̇_h = u(t) − h / R
We close the loop with a Sf pump whose flow is set by the PI law and a second integrator C that carries the running error. The error is fed in by a Sf err with expression sp − q_h; its target C "Int" therefore satisfies q̇_Int = sp − h, which is exactly the integral of the error.
- Open Examples → Tank PI Control.
- Look at the pump source: its
expressionis1.5*(2 - q_h) + 0.6*q_Int— that isKp·(sp − h) + Ki·∫(sp − h)dt. - Run a simulation for 30 s with step 0.02 s, BDF-2.
- Plot
q_h: the level rises smoothly to the setpointsp = 2; integral action removes any steady-state offset that a pure-P controller would leave behind. - Plot
q_Intalongside: it grows during the transient and settles at the constant value the controller needs to hold the equilibrium pump flow.
Tutorial G — Single-tank PID with filtered derivative
A pure derivative term Kd·dE/dt would be ill-posed — it amplifies measurement steps and noise. The textbook "practical" PID instead uses a low-pass filter of cutoff N. We add a filter state q_Df via a third C element whose driving Sf has expression N·(E − q_Df):
q̇_Df = N·(E − q_Df), D = Kd·N·(E − q_Df), u = Kp·E + Ki·q_Int + D
- Open Examples → Tank PID Control.
- Note the three feedback channels in the diagram: pump (control), err → Int (integral), dfilt → Df (filtered derivative). All three Sf elements reference the same measurement
q_h. - Run for 30 s, step 0.02 s.
- Compare
q_hto the PI run: rise time is shorter and overshoot smaller because the derivative term starts braking before the level reaches setpoint. - Try detuning: raise
Kdtoo far and the loop becomes oscillatory; lowerNand the derivative becomes sluggish (filter dominates).
Why this is a real bond graph. The integrator and derivative-filter blocks are not bolted on as block-diagram extras — they are ordinary C elements driven by ordinary Sf elements at ordinary 0-junctions. The DAE solver sees a single unified system; tests in simulation.test.ts assert that the PI loop hits the setpoint with no offset and that PID overshoot does not exceed PI overshoot.
Recipes — turning a bond-graph plant into a controlled system
- Read a state. Any C or I element creates a named state
q_<label>orp_<label>. Use that exact name in a Se / Sf expression to feed it back. - Add an integrator. A C with capacitance 1 driven by a Sf with expression
xon a 0-junction gives youq = ∫x dtfor free. - Add a filtered derivative. A C with capacitance 1 driven by
N·(x − q)implements a first-order low-pass; the outputKd·N·(x − q)approximatesKd·dx/dtwith cutoffN. - Saturate actuators. The expression engine ships
sat(u, lo, hi); wrap the control law insat(...)to model a pump that maxes out. - Use Se for force/torque control, Sf for speed/flow control. The plant decides which port type the controller drives.
8. Non-linear phenomena and hysteresis
The constitutive law of an element does not have to be linear. BondGraph Studio interprets the parameter you type for an R, C or I as the coefficient of the standard linear law (e = R·f, e = q/C, f = p/I), but the same elements can equally well represent nonlinear maps — the solver is a general implicit DAE integrator and does not assume linearity.
Smooth nonlinearities
- Saturating spring (rubber, ligament): e = k·q + k₃·q³ — replace the linear C by a polynomial constitutive in a custom element.
- Quadratic damper (turbulent flow, aerodynamic drag): e = c·f·|f| — the orifice equation Δp = ρ/(2Cd²A²)·Q² is exactly this form.
- Stribeck friction: e = (Fc + (Fs−Fc)·exp(−|f|/vs))·sign(f) + b·f — a single nonlinear R combining static, Coulomb and viscous components.
- Diode: i = Is(exp(v/VT)−1) — a nonlinear R between an electrical 1-junction and ground.
Hysteresis: when the constitutive law has memory
Hysteresis (ferromagnetic B–H loops, mechanical backlash, magnetorheological dampers, shape-memory alloys, plasticity) is fundamentally different from a smooth nonlinearity: the output depends on the history of the input, not just its current value. In bond-graph terms this means the constitutive map carries its own internal state, so it is no longer a memory-less Ror C but a small dynamical subsystem.
Three modelling strategies, in order of increasing fidelity:
- Coulomb / dead-zone R. The simplest hysteresis is dry friction or backlash. Model it as a nonlinear R whose effort is Fc·sign(f); this gives the rectangular hysteresis loop characteristic of stick-slip without needing extra states.
- Bouc–Wen element. Introduce one auxiliary state z per hysteretic element governed by ż = A·f − β·|f|·|z|n−1z − γ·f·|z|n, and define the effort as e = α·k·q + (1−α)·k·z. Implement this as a custom C-like element with the extra state
z_appearing in the variable list. Suitable for seismic isolators, MR dampers and smooth ferromagnetic loops. - Preisach / Maxwell-slip model. Several Coulomb-R elements in parallel, each with a different breakaway threshold, superpose to approximate any monotone hysteresis loop. This is the bond-graph counterpart of a Preisach hysteron bank and is the standard approach for piezo actuators and SMA wires.
Practical recipe. Build a custom subsystem (Toolbar → Save as component) containing a 1-junction with (a) a linear C (elastic part), (b) a Bouc–Wen R, and (c) a small viscous R for numerical regularisation. Expose a single external bond. The result behaves like a hysteretic spring you can drop into any larger model — exactly how commercial codes (20-sim, Modelica's Magnetic.Hysteresis) ship these models.
Numerical caveats
- Discontinuous laws (ideal Coulomb friction, ideal diodes) cause Newton non-convergence around the switching point. Smooth them: replace sign(f) by tanh(f/ε) with ε small but finite (e.g. 1e-3 of the operating flow).
- Hysteresis loops often need BDF-2 with a step size ten times smaller than for the equivalent linear model. Watch the Newton-failure counter in the simulation panel.
- Energy is no longer conserved on the hysteresis loop — the area enclosed equals the dissipated energy per cycle. Check this against an R-element power integral as a sanity test.
8b. Hybrid systems — events, mode switching & the expression engine
The expression engine
R, C, I, Se and Sf elements accept an optional expression in their properties panel. Variables in scope: f, t (R),q, t (C), p, t (I), t(sources). Functions: sin, cos, tan, exp, log, sqrt, abs, sign, tanh, pow, min, max, plus the non-smooth helpersstep(x), dead(x, b),sat(x, lo, hi), and constants PI, E. Expressions are parsed once and symbolically differentiated to produce analytical Jacobians for Newton — no finite differences in the inner loop.
Discrete events (state-jump)
C and I elements gain an Event section: a guardexpression that fires on a downward zero-crossing (g ≥ 0 → g < 0), and a reset map of the formstate := expr; state := expr. The solver bisects to the crossing instant, applies the reset atomically, then cold-restarts at order 1. See the bouncing ball example (q_height guard,p_mass := -0.8*p_mass reset) — successive impacts dissipate 1 − e² of kinetic energy.
Mode switching (structural changes)
R elements support modes: each mode declares a constitutive law (kind: 'effort' → e = expr(f, t, …)or kind: 'flow' → f = expr(e, t, …)), and transitions between modes fire when their guard crosses zero (only while the source mode is active). The properties panel offers an + Add modes button that seeds an ideal-diode template:
{
"current": "off",
"laws": {
"off": { "kind": "flow", "expression": "0" },
"on": { "kind": "effort", "expression": "0" }
},
"transitions": [
{ "from": "off", "to": "on", "guard": "-e" },
{ "from": "on", "to": "off", "guard": "f" }
]
}Two worked examples ship in the example gallery:Ideal Diode (mode-switch) — exact half-wave rectifier; and Karnopp Stick-Slip — a mass on a moving belt that sticks (f = v_b) until the spring force exceeds static friction, then slips.
Choosing the right tool
| Phenomenon | Approach |
|---|---|
| Smooth nonlinearity (Stribeck, soft diode) | R/C/I expression |
| Impulsive collision, restitution | C or I event + reset map |
| Ideal diode, ideal clutch, stick-slip | R modes + transitions |
9. Common pitfalls
- Wrong junction. Series elements share flow(1-junction); parallel elements share effort (0-junction). Swapping them is the most frequent error and yields algebraic loops, causal conflicts or unphysical results.
- Power direction reversed. The half-arrow defines the positive convention. A wrong arrow does not break the model but flips the sign of plotted quantities — always interpret signs relative to the arrow direction shown on the canvas.
- Causal conflict on storage elements. Each Cprefers integral causality on flow; each I prefers integral causality on effort. A forced derivative causality usually signals a missing element (e.g. two capacitors directly in parallel without a resistor between them, or two inertias on the same shaft without compliance).
- Two effort sources on the same 1-junction (or two flow sources on the same 0-junction) — over-determined; the DAE has more equations than variables and the solver reports an "Over-determined" mismatch.
- Floating subgraphs. Every element except sources must eventually connect back to a reference (ground, fixed frame, ambient pressure). Otherwise the resulting DAE is singular and the Newton solver fails.
- Zero-valued parameters. Setting R = 0, I = 0 or C = 0 turns a regular ODE into a high-index DAE. Use a small positive value (e.g. 1e-6) when you really mean "negligible".
- Mixing units. Bond graphs do not enforce units. Stay in consistent SI throughout one model, or scale parameters very carefully — and label sources with their units in the label field (e.g.
τ_in [N·m]).
10. Understanding error messages
The simulator reports problems in three categories.
Structural errors (before integration)
Empty DAE system — no variables or equations.You pressed Run without any bonds. Connect at least two elements.Under-determined: N variables but only M equations. Missing K equation(s).A junction is missing a constraint, or an element has no bond. Look for elements highlighted with no incoming/outgoing line.Over-determined: N variables but M equations. K excess equation(s).Two effort sources meet at a 1-junction (or two flow sources at a 0-junction), or a redundant bond duplicates a constraint. Remove or replace one of the conflicting sources.- The error pane lists all variables and equations — scan for duplicates or missing element names.
Numerical errors (during integration)
Newton convergence issues: K/N steps failed— the solver could not find a consistent solution. Common causes: a sign error producing a runaway, a truly stiff system, or a parameter set that puts the system on the edge of stability. ReduceEnd, decreaseStep, switch to BDF-2, or check parameter signs.- If the Jacobian becomes singular the solver applies a small Levenberg perturbation and continues; persistent singularity usually means a missing R or a high-index DAE — see the pitfalls above.
Validation (after integration)
- Sanity-check the steady state by hand: a torque source τ on a 1-junction with stiffness k should settle at q = τ/k. If your simulation drifts away, suspect a wrong junction or reversed half-arrow.
- State variables that grow without bound indicate either a missing dissipation (R) or an unstable causality assignment.
Tip. The error message lists every variable and equation in the assembled DAE, including the labels you typed in the Properties panel. Use distinctive labels like R_arm, J_rotor, tau_load rather than the defaults so the error text tells you exactly which element is involved.
11. SPDM workflow & traceability
Engineering models rarely live in isolation — they pass through a chain of stages from initial design to a signed-off report. BondGraph Studio bakes a lightweight Simulation Process & Data Management (SPDM) workflow into the editor so each transition is logged with a timestamp and an optional note. The full history travels with the model in the BGIF file.
The five stages
| Stage | Purpose | Guard to enter |
|---|---|---|
Model Design | Lay out elements and bonds. | Initial state — no guard. |
Validation | Freeze topology for review. | At least one element and one bond. |
Simulation | Run the configured solver. | No orphan elements (every non-annotation element touches a bond). |
Analysis | Inspect plots and derived signals. | The most recent simulation finished with status completed. |
Report | Final, ready-to-share artefact. | Always allowed once analysis is reached. |
Working with the panel
- The SPDM Workflow panel sits in the right column and collapses by default. Open it to see the current stage, the guard blocking the next advance (if any), and the full transition log.
- Type a short note before clicking Advance →; it is stored alongside the timestamp so reviewers can see whyeach transition happened, not just when.
- Use the reopen link on a completed stage to step backwards. Backward steps are always allowed and are themselves logged so the iteration is visible in the history.
- Forward skipping is forbidden — you must walk the chain one stage at a time. This keeps the audit trail dense enough to be useful when handing the model to a colleague.
Persistence
Saving the model with the Save toolbar action writes a standard BGIF file with the workflow state attached in the layout.workflow extension field. Loading the file restores the current stage and the entire history exactly as they were — no separate database is involved. If you load a BGIF that has no workflow block (e.g. one produced by another tool), the editor starts a fresh history from the Model Designstage.
Tip. The history is append-only inside one session. If you need a clean log — e.g. before a formal review — use the Reset workflow link at the bottom of the panel to start a new traceability record without touching the model itself.
12. HTTP API for AI agents and external tools
Every BondGraph Studio deployment exposes a single, public, no-auth HTTP endpoint that runs the same in-browser simulator on a model supplied in BGIF format and returns the time-series result as JSON. The endpoint is CORS-open and works from any client — browsers, curl, LangChainrequests tools, OpenAI/Anthropic tool-use agents, n8n, Zapier, etc. There is no server-side state, no rate limit beyond what the edge worker enforces, and no API key.
Endpoint
| Method | URL | Use when |
|---|---|---|
GET | /api/public/simulate (no params) | Self-describing usage doc as JSON — agents call this to learn the schema. |
GET | /api/public/simulate?bgif=<urlencoded JSON>&endTime=… | Small models (URL ≲ 8 KB on most clients). |
POST | /api/public/simulate with JSON body { bgif, config?, format? } | Larger models — recommended default for agents. |
OPTIONS | /api/public/simulate | CORS preflight (handled automatically). |
Request shape
The bgif field accepts either the full { bgif, layout } wrapper produced by the editor's Save action, or a bare BGIF document { bgifVersion, components, systems }. Layout information is ignored — only topology, parameters and the optional defaultSimulation block influence the result.
config is an optional partial SimulationConfig; any field you omit falls back to the model's defaultSimulation and then to { startTime: 0, endTime: 10, stepSize: 0.01, solver: "bdf2" }. Setting tolerance > 0 switches the BDF solver into adaptive-step mode and interpolates output onto the stepSize grid.
Response shape
{
"status": "completed" | "error",
"error": "<message, present only on error>",
"time": [t0, t1, …],
"variables": {
"q_C1": [v0, v1, …],
"p_I1": [v0, v1, …],
…
},
"meta": {
"modelId": "<bgif system id>",
"modelName": "<human-readable name>",
"variableCount": <int>,
"sampleCount": <int>,
"config": { startTime, endTime, stepSize, solver, tolerance? },
"durationMs": <wall-clock cost of this call>
}
}Variable names follow the convention used everywhere else in BondGraph Studio: q_<label> for C-element charges/displacements, p_<label> for I-element momenta, plus per-bond e_* / f_* traces. Subsystem-flattened names use a dot, e.g. q_Thermal Zone.C_zone.
Example — POST with curl
curl -X POST https://xn--lda.se/api/public/simulate \
-H "content-type: application/json" \
-d '{
"bgif": { "bgifVersion": "1.0.0", "components": [...], "systems": [...] },
"config": { "endTime": 5, "stepSize": 0.01, "solver": "bdf2" }
}'Example — GET with a tiny model
MODEL='{"bgifVersion":"1.0.0","components":[…],"systems":[…]}'
curl -G https://xn--lda.se/api/public/simulate \
--data-urlencode "bgif=$MODEL" \
--data-urlencode "endTime=2" \
--data-urlencode "stepSize=0.01"Producing the BGIF payload
- Build the model in the editor and use the toolbar's Save action — the downloaded
.bgiffile is exactly the JSON the API expects (either fieldbgifalone or the full{ bgif, layout }object). - Programmatic agents can synthesize BGIF directly. The schema is documented on the project's GitHub and is round-tripped by the editor — anything the editor can save, the API can run.
Errors
All errors return HTTP 200 with status: "error" when the model parses but the solver fails (e.g. structural-singular DAE, mode-switch guard divergence), and HTTP 400 with the same body shape when the request itself is malformed (invalid JSON, missing bgif, bad config values). This keeps tool wrappers simple: read status first, then either variables or error.
OpenAPI / Swagger
The endpoint ships a full OpenAPI 3.1specification at /api/public/openapi.json. The complete BGIF JSON Schema (draft-07) is embedded under components.schemas.BGIF, so a single fetch gives agents and code generators everything they need to construct a valid request body and parse the response. An interactive Swagger UI rendering of the same spec is served at /api-docs — useful for trying calls from the browser and as a human-friendly companion to this section.
- Generate a typed client:
openapi-typescript /api/public/openapi.json,openapi-generator-cli, etc. - Feed an LLM tool-loader: most agent frameworks (LangChain
OpenAPISpec, OpenAI Assistants OpenAPI actions, Anthropic tool-use templates) accept the spec URL directly. - Validate a BGIF document offline: extract
components.schemas.BGIFand run it through any JSON Schema draft-07 validator.
Agent discovery hops. Four entry points, in order of machine-friendliness: /api/mcp (MCP JSON-RPC, native for Claude/Cursor/AI SDK), /api/public/openapi.json (full OpenAPI spec), /api/public/simulate (self-describing usage JSON when called with no query string), and /llms.txt(agent-readable site index linking to all of them). Every page also advertises these via <link rel="service">tags in the document head.
Pointing your AI agent to this site
When the app is published every page includes machine-readable discoverability metadata in the HTML <head>:
<link rel="describedby" href="/llms.txt">— an agent-readable site index (likerobots.txtfor LLMs) that lists every public endpoint, tool, and curl example.<link rel="service" href="/api/mcp">— the MCP JSON-RPC endpoint.<link rel="service" href="/api/public/openapi.json">— the OpenAPI specification.<link rel="service" href="/api/public/simulate">— the direct simulation API.
Any agent that fetches the root URL and parses <head>will see the full service map automatically. You can also give an agent the direct endpoint URLs or the MCP client configuration below.
MCP client configuration
Claude Desktop, Cursor, Windsurf, and any app using the AI SDKcreateMCPClient can connect natively. Add this server block to the host's MCP config file:
{
"mcpServers": {
"bondgraph": { "url": "https://xn--lda.se/api/mcp" }
}
}The host will run the MCP handshake (initialize →tools/list), discover all bondgraph-studio tools, and make them available to the agent. No API key is required.
Direct HTTP API (no MCP host needed)
If your agent framework does not speak MCP, use the plain HTTP endpoint directly. A single POST to/api/public/simulate with a JSON body returns the same time-series data the MCP simulate tool returns. See the endpoint table and curl examples above.
MCP (Model Context Protocol)
The same simulator is exposed as an MCP server at POST /api/mcp (Streamable HTTP, no auth, CORS open). Hosts that speak MCP — Claude Desktop, Cursor, Windsurf, and any app using the AI SDK createMCPClient — can discover and call it natively. Tools published, grouped by capability:
- Simulation & rendering.
simulate(run a BGIF model and return time-series),render_svg(editor-style SVG with bond half-arrows, causal strokes, junction circles; optionalshowPortLabelsand hex theming). - Validation & diagnostics.
validate_bgif(parse + structural errors; passanalyze:truefor an embedded causality summary),analyze_causality(SCAP — per-bond causality, state vars, algebraic loops, derivative-causality elements, estimated DAE index, well-posedness verdict),check_units(dimensional / domain type-check across electrical, mechanical, fluid, thermal, magnetic, chemical; advisory by default, opt-instrict). - Symbolic export.
export_equationswithform:"dae" | "ode" | "modelica"— emits a structured DAE, a reduced explicit ODE (single-unknown linear-solve eliminates simple algebraic loops), or compilable Modelica source with parameter docstrings,unit="…"attributes,start=on non-zero states, and dialect→Modelica rewrites forPI,step,dead,**,log2,cbrt,round, andt→time. Subsystems are flattened automatically. - Batch & calibration.
parameter_sweep(Cartesian product over parameter axes with per-output reduction or full timeseries),fit_parameters(Nelder-Mead calibration of free parameters to a target trajectory, with bounds and per-series weights). - Linearization & frequency-domain.
linearize(small-signal state-space{A, B?, C?, D?}around an operating point — supports"auto","initial", or a user-supplied state map),analyze_frequency(poles with damping ratio and natural frequency,stableverdict, spectral abscissa, and Bode magnitude+phase when inputs/outputs are given). - Model editing (round-trip).
add_element,remove_element,set_parameter,rename_element,connect,disconnect, and the transactional batch wrapperapply_edits— each takes a BGIF wrapper and returns the updated wrapper so an agent can iterate without resending the whole document. - Examples & libraries.
list_examples,get_example, plus the BGLIB v1 suite:list_libraries,get_library,validate_library,fetch_library,build_library.
Every tool's full inputs, JSON-RPC template, and example response live on the dedicated MCP Tool Referencepage.
Drop-in client config:
{
"mcpServers": {
"bondgraph": { "url": "https://xn--lda.se/api/mcp" }
}
}JSON-RPC + curl examples
Streamable HTTP MCP is just JSON-RPC 2.0 over POST. The spec requires Accept: application/json, text/event-streamon every call — without it some servers reject with HTTP 406. Each example below is a complete, runnable invocation.
1. Initialize & list tools — handshake every MCP host runs first:
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18",
"capabilities":{},
"clientInfo":{"name":"curl","version":"1"}}}'
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'2. list_examples — discover built-in models:
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"list_examples","arguments":{}}}'3. get_example — fetch the full BGIF for one example:
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":4,"method":"tools/call",
"params":{"name":"get_example",
"arguments":{"name":"RLC Circuit"}}}'4. validate_bgif — sanity-check before simulating:
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":5,"method":"tools/call",
"params":{"name":"validate_bgif",
"arguments":{"bgif":{"bgifVersion":1,"components":[],"systems":[]}}}}'5. simulate — run a model and get time-series JSON:
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":6,"method":"tools/call",
"params":{"name":"simulate",
"arguments":{
"bgif": { "bgifVersion":1, "components":[…], "systems":[…] },
"config": {"endTime":5,"stepSize":0.01,"solver":"bdf2"},
"format": "compact"
}}}'6. render_svg — get an SVG image of the model:
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":7,"method":"tools/call",
"params":{"name":"render_svg",
"arguments":{
"bgif": { "bgifVersion":1, "components":[…], "systems":[…] },
"theme": "dark"
}}}' \
| jq -r '.result.content[0].text' > model.svgThe same renderer is also exposed as a plain HTTP endpoint — handy for embedding in markdown, docs, or <img src>: GET /api/public/render.svg?bgif=<urlencoded JSON>&theme=dark(also accepts POST with a JSON body for large models). Response Content-Type is image/svg+xml.
13. Sharing component libraries (BGLIB v1)
Reusable subsystems live in the Component Librariespanel on the left edge of the editor. You can export any library as a single shareable JSON file — BGLIB v1 — and re-import it on another machine, post it to a gist, drop it in a GitHub repo, or hand the URL to a teammate.
Export
Hover any library header in the panel and click the download icon. You'll be asked for an optional author and description, then the browser saves a <library>.bglib.json file containing every component in that library plus its metadata.
Import
Use the two buttons at the top of the panel:
- ↑ File — pick a
.bglib.jsonor legacy.bgiffile from disk (multi-select supported). - ⇩ URL — paste any HTTPS URL (GitHub raw, gist, S3, personal site). The library is fetched, validated, and merged into your local library. Existing components with the same id are overwritten so authors can ship updates by bumping the file.
BGLIB is just JSON — no scripts, no executable code. The editor validates structure on import and only ever stores the parsed SubsystemDef objects in your browser's localStorage. You can revoke a library at any time by removing its components from the panel.
BGLIB v1 wire format
{
"bglibVersion": "1.0.0",
"name": "Acme Pneumatics",
"version": "0.3.1",
"author": "Acme Engineering",
"description": "Single-acting cylinders, valves, accumulators.",
"license": "MIT",
"homepage": "https://github.com/acme/bondgraph-pneumatics",
"bgif": {
"bgifVersion": "1.0.0",
"components": [ /* SubsystemDef components in BGIF v1 form */ ],
"systems": []
}
}MCP tools for libraries
Agents connected to /api/mcp get five extra tools so they can help users discover, validate, fetch, and author libraries. See the MCP Tool Reference for full inputs and example responses for each one.
list_libraries— list the libraries shipped with BondGraph Studio (Buildings & HVAC, Power & Energy, Automotive, Hydraulics, Data Center).get_library— return the full BGLIB v1 document for a shipped library, ready to drop into Import → File.validate_library— parse a candidate BGLIB and report metadata + component count, or the exact parser error.fetch_library— download a BGLIB from any HTTPS URL and return a summary (so an agent can preview a library before recommending the user import it).build_library— wrap a set of BGIF components into a fresh BGLIB document with given metadata.
curl -sS https://xn--lda.se/api/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":10,"method":"tools/call",
"params":{"name":"fetch_library",
"arguments":{"url":"https://example.com/pumps.bglib.json"}}}'Roadmap. File + URL sharing is the MVP. A future cloud registry (browse / search / star / publish, with author profiles and curated featured libraries) would slot on top of the same BGLIB v1 schema — anything you share today will keep working.