This simulation builds a real transfer function G(s) = K·Π(s−zᵢ)/Π(s−pᵢ) from the poles and zeros you place, then genuinely evaluates G(jω) as complex-number arithmetic across a swept, log-spaced frequency range. It plots the actual computed magnitude 20·log₁₀|G(jω)| in decibels and phase ∠G(jω) in degrees against log-frequency — a real Bode plot, not a sketch — then locates the gain-crossover frequency (where |G|=0 dB) and phase-crossover frequency (where phase=−180°) by interpolation on those curves to report the classical gain margin and phase margin, and a live stability verdict.
An s-plane view where × marks poles and ○ marks zeros of the open-loop transfer function, plus two genuine frequency-response curves: magnitude in dB and phase in degrees, both computed point-by-point from G(jω). The amber marker is the gain-crossover frequency ω_gc; the purple marker is the phase-crossover frequency ω_pc. Gain margin and phase margin are read directly off those two points.
Drag the × and ○ markers on the s-plane to move poles and zeros, or type exact Re/Im values once one is selected. Use + Pole, + Pole pair, + Zero, + Zero pair to add real or complex-conjugate roots, and Remove selected to delete one. The Gain K slider scales the whole transfer function; the frequency-range sliders control how many decades the sweep covers. Watch the gain margin, phase margin and stability verdict update live as you edit.
The default example G(s) = K/[s(s+1)(s+5)] is a classic type-1 system: Routh–Hurwitz shows its closed loop is stable only for 0 < K < 30. Push the Gain K slider toward 30 and watch the gain margin shrink toward 0 dB and the verdict flip from stable to marginal to unstable — exactly the boundary the Nyquist/Bode stability criterion predicts.
A Bode plot is a pair of graphs — magnitude in decibels and phase in degrees, both against frequency on a logarithmic axis — that show how a linear system responds to sinusoidal inputs of every frequency. It is produced by substituting s = jω into the system's transfer function G(s) and evaluating the resulting complex number G(jω) at many frequencies ω; this simulation does exactly that using real complex-number arithmetic, not an approximation.
Gain margin is how much the open-loop gain could increase, in decibels, before the closed-loop system becomes unstable; it is measured at the phase-crossover frequency ω_pc, the frequency where the phase first reaches −180°, as GM = −20·log₁₀|G(jω_pc)|. Phase margin is how much additional phase lag the loop could tolerate before instability; it is measured at the gain-crossover frequency ω_gc, where |G(jω_gc)| = 0 dB (unity gain), as PM = 180° + ∠G(jω_gc). Both are read directly off the plotted curves in this simulation, by interpolating between the two nearest sample points that bracket the crossover.
Under classical Bode/Nyquist stability theory, for a large class of open-loop transfer functions closing the loop with unity negative feedback remains stable as long as, at the frequency where the gain equals 0 dB, the phase has not yet reached −180° (positive phase margin), and at the frequency where the phase reaches −180°, the gain is still below 0 dB (positive gain margin). If either condition fails, the loop gain and phase align at −180° with gain ≥ 1, so a small disturbance is fed back in phase and amplified every cycle — the definition of instability. This simulation flags "marginal" rather than simply "stable" when a margin is positive but small, because real systems have model uncertainty and small margins leave little robustness headroom.
Yes. Every pole and zero you place is stored as a complex number; the simulation multiplies out G(s) = K·Π(s−zᵢ)/Π(s−pᵢ), substitutes s = jω for 400 log-spaced frequencies between the chosen limits, and performs genuine complex multiplication and division (using the standard a+bi arithmetic rules) at every point. Magnitude is 20·log₁₀ of the resulting complex number's modulus and phase is its argument, unwrapped across the sweep so it doesn't jump by ±360° — the same computation a textbook or MATLAB's bode() function performs.
Each pole of a transfer function contributes an angle that approaches −90° as frequency rises past that pole's break frequency, and each zero contributes +90° in the same way; a system with three poles and no zeros therefore ends up near −270° at high frequency, not somewhere between −180° and +180°. This simulation "unwraps" the raw arctangent output so the phase curve is continuous instead of snapping back every time the raw angle would jump across ±180°, which is essential for correctly finding the −180° phase-crossover frequency.
If the magnitude curve never crosses 0 dB anywhere in the chosen frequency range, there is no gain-crossover frequency and phase margin cannot be located that way — the loop gain is either always below or always above unity across the swept band. Likewise, if the phase never reaches −180° in range, there is no finite gain margin. Widening the frequency range with the ω min / ω max sliders will usually reveal the crossover if one exists at all.
A pole at s = 0 makes G(s) a "type-1" system, meaning it contains a free integrator; type-1 systems are common in practice (motor position control, level control) because the integrator drives steady-state error to zero for a step input. They are also a standard teaching example for Bode analysis because their phase starts at −90° at low frequency and their gain margin is finite and gain-dependent, making the stability boundary easy to see by sliding K.