Design

Overview

Introduction Control-system design is the discipline of turning dynamic models into implementable controllers and estimators that meet explicit performance, stability, and robustness targets. Mathematically, design starts from a system model and chooses decision variables (feedback gains, observer gains, weighting filters, or target pole sets) so closed-loop behavior satisfies engineering constraints. Practically, it answers business-critical questions: Will a machine settle fast enough to hit throughput goals? Will it remain stable after sensor replacement? Can one tuning strategy work across product variants? A broad reference is Control theory.

In this category, Boardflare focuses on classical and modern state-space design workflows powered by the Python Control Systems Library (python-control). The tools include direct pole assignment methods—ACKER and PLACE—optimal regulator synthesis for deterministic control—LQR and DLQR—optimal estimator synthesis for stochastic state reconstruction—LQE and DLQE—robust mixed-sensitivity H_\infty design via MIXSYN—and gain-dependent closed-loop root movement analysis via ROOT_LOCUS.

These functions are complementary rather than redundant. ACKER and PLACE are specification-driven: engineers pick desired poles and solve for gains. LQR and DLQR are cost-driven: engineers encode trade-offs in weighting matrices and let Riccati equations determine gains. LQE and DLQE are uncertainty-driven: engineers encode process and sensor noise assumptions to derive observer gains. MIXSYN is robustness-driven: engineers define frequency-shaped performance priorities and synthesize a controller that balances sensitivity and control effort under uncertainty. ROOT_LOCUS is insight-driven: it reveals how poles migrate as loop gain varies, helping teams understand tuning headroom and instability mechanisms before committing to a final design.

From a delivery standpoint, this matters because many organizations still prototype control logic in spreadsheets or mixed-tool workflows. Boardflare’s design category provides calculator-level access while preserving the same mathematical backbone used in production Python environments. That alignment supports the dual pillar strategy: each function page solves a narrow task quickly, while the category overview gives the higher-level framework for selecting the right method.

When to Use It Use this category when the job to be done is “choose a controller architecture and tuning method that meets measurable requirements,” not simply “run a simulation.” The most common trigger is a design decision with operational or financial consequences: cycle-time misses, oscillatory behavior, excessive actuator wear, poor disturbance rejection, or weak robustness margins that threaten uptime.

A first scenario is state-feedback redesign for a mechanical axis (robot joint, pick-and-place carriage, gimbal, or motor stage). The team usually has a linear model and target settling/overshoot behavior. If requirements are expressed directly as desired closed-loop poles, PLACE is typically the first choice, and ACKER is useful when the system is SISO and Ackermann-style direct construction is preferred. If requirements are instead framed as “penalize position error heavily, allow moderate control effort,” then LQR or DLQR is often better because it encodes trade-offs through Q and R rather than manually selecting pole coordinates.

A second scenario is observer design where not all states are measured directly. In process plants, embedded systems, and motion applications, estimates of unmeasured states (velocity, disturbance states, thermal states, etc.) are needed for high-quality feedback. Here LQE (continuous-time) and DLQE (discrete-time) are appropriate when uncertainty can be modeled through process noise covariance Q_N and sensor noise covariance R_N. These tools are especially valuable when sensors differ widely in quality or when noise characteristics drift between field sites.

A third scenario is robustness-oriented synthesis under modeling uncertainty and frequency-specific performance demands. When a team must enforce low sensitivity at low frequencies (for disturbance rejection), limit control activity at high frequencies (to reduce actuator/sensor amplification), and preserve stability margins, MIXSYN becomes relevant. It is common in advanced servo systems, aerospace control loops, power electronics, and precision instrumentation where classical single-objective tuning is insufficient.

A fourth scenario is gain exploration and intuition building before final tuning. ROOT_LOCUS helps answer practical questions such as: “If loop gain increases 2x, where do dominant poles move?” or “Is there a gain region with acceptable damping before instability?” This is useful for commissioning, troubleshooting, and early concept screening, especially when teams need a visual and table-based way to communicate design risk.

In short, use this category whenever design choices must be justified with explicit dynamics and repeatable calculations. It is less about one-off gain guessing and more about structured decision-making across performance, effort, uncertainty, and robustness.

How It Works All design tools in this category start from a linear model, typically state-space:

\dot{x}(t) = A x(t) + B u(t), \qquad y(t) = C x(t) + D u(t)

or its discrete-time counterpart:

x_{k+1} = A x_k + B u_k, \qquad y_k = C x_k + D u_k.

For state-feedback control, the implemented law is usually

u = -Kx,

which yields closed-loop dynamics

\dot{x} = (A - BK)x \quad \text{or} \quad x_{k+1} = (A - BK)x_k.

Design methods differ in how K is chosen.

With pole placement, PLACE and ACKER choose K so eigenvalues of (A-BK) match desired targets. If the pair (A,B) is controllable, a valid gain exists for feasible pole sets. ACKER uses Ackermann’s formula and is best suited to SISO structures, while PLACE handles broader state-feedback pole assignment workflows and is generally preferred for MIMO-compatible numerical practice.

With optimal control, LQR and DLQR choose K by minimizing a quadratic objective. For continuous-time:

J = \int_0^{\infty} \left(x^T Q x + u^T R u + 2x^T N u\right) dt,

and for discrete-time:

J = \sum_{k=0}^{\infty} \left(x_k^T Q x_k + u_k^T R u_k + 2x_k^T N u_k\right).

The gain is computed from an algebraic Riccati equation solution. Intuitively, larger entries in Q make state deviations more expensive; larger entries in R make control effort more expensive. LQR applies to continuous models, and DLQR applies to sampled-data models.

For estimation, LQE and DLQE construct a Kalman-style observer:

\dot{\hat{x}} = A\hat{x} + Bu + L(y - C\hat{x})

or

\hat{x}_{k+1} = A\hat{x}_k + Bu_k + L(y_k - C\hat{x}_k).

The observer gain L is selected by minimizing estimation error covariance under assumed process and sensor noise. Inputs Q_N, R_N, and optionally cross-covariance N_N encode uncertainty structure. As with regulation, continuous-time uses LQE and discrete-time uses DLQE.

For robust frequency-shaped design, MIXSYN solves a mixed-sensitivity H_\infty problem. A typical objective is to minimize a norm of weighted closed-loop channels such as sensitivity S, complementary sensitivity T, and control sensitivity KS:

\left\|\begin{bmatrix}W_1 S \\ W_2 KS \\ W_3 T\end{bmatrix}\right\|_\infty,

where weighting functions encode priorities by frequency band. In the Boardflare calculator surface, users provide plant and performance weight transfer-function coefficients to synthesize a controller candidate. This is especially useful when “fast response” and “robust stability under uncertainty” must both be explicit design targets.

ROOT_LOCUS complements these synthesis methods by computing root trajectories of the characteristic equation as scalar gain varies. For a loop transfer function L(s), closed-loop poles satisfy

1 + K L(s) = 0.

Plotting roots over K reveals where instability appears, where damping improves, and where branch interactions constrain feasible gain ranges.

Under the hood, these methods align with python-control references: acker, place, lqr, dlqr, lqe, dlqe, mixsyn, and root_locus.

Key assumptions are important: model linearity near the operating point, adequate controllability/observability for synthesis goals, and physically meaningful weighting/noise selections. If these assumptions are violated, results may still be informative but should be interpreted as local guidance rather than universal guarantees.

Practical Example Consider an automated filling machine where a servo-driven valve position must track rapidly changing setpoints while suppressing flow disturbances. The business targets are concrete: lower overfill waste, maintain cycle-time SLA, and reduce actuator wear from aggressive commands. The controls team has a linearized plant model and must choose a design path that balances speed, noise sensitivity, and robustness.

Step 1: establish modeling and implementation domain. If the controller runs on a PLC with fixed sampling, the team works in discrete time and favors DLQR/DLQE; if they are at conceptual continuous-time design stage, they may begin with LQR/LQE. They verify state definitions, units, and actuator limits before tuning, because poor normalization often causes misleading matrix weights.

Step 2: define a baseline regulator. The team starts with DLQR, selecting Q to strongly penalize fill-level error and moderate valve-rate state excursions, with R tuned to limit command aggressiveness. They iterate Q/R until predicted settling and overshoot are acceptable. In a parallel branch, they test PLACE with candidate pole locations that approximate desired damping and bandwidth. This side-by-side comparison clarifies whether a spec-driven (pole placement) or cost-driven (LQR) approach is easier to maintain for future product variants.

Step 3: estimate unmeasured states. Flow sensors are noisy and not all internal states are measured, so the team designs an observer using DLQE. They increase R_N relative to Q_N when sensor noise dominates, reducing estimator reactivity to noisy measurements. If they were designing in continuous-time first, the analogous step would use LQE before discretization.

Step 4: stress gain sensitivity and intuition. Before locking gains, they run ROOT_LOCUS on a representative loop transfer function to see how dominant roots move as gain shifts due to calibration drift or field variability. This reveals whether small gain drift pushes poles toward weak damping, guiding safe commissioning limits.

Step 5: evaluate direct pole-assignment alternatives. For a SISO subsystem (for example, a simplified valve actuator stage), they test ACKER as a direct construction method from desired poles. Comparing ACKER and PLACE outputs helps validate controllability assumptions and detect cases where exact pole requests produce impractically large gains.

Step 6: escalate to robust synthesis when needed. Suppose field trials show model mismatch at low frequencies due to supply pressure variation and at high frequencies due to sensor dynamics. The team then prototypes MIXSYN with a low-frequency performance weight to enforce disturbance rejection while avoiding high-frequency over-amplification. This does not replace LQR-based work automatically; it provides a robustness-focused alternative when weighted sensitivity shaping better reflects real operating risk.

Step 7: finalize with explicit selection criteria. The team documents the selected method against measurable gates: closed-loop stability, settling-time threshold, actuator RMS usage, noise amplification, and expected tolerance to gain drift. By keeping each design attempt in Boardflare calculators and linking to the specific methods (DLQR, DLQE, ROOT_LOCUS, MIXSYN, etc.), the handoff to operations and quality teams is reproducible.

This workflow is materially more effective than ad hoc spreadsheet tuning because each adjustment maps to a known mathematical lever: poles for transient shape, Q/R for optimal trade-offs, Q_N/R_N for estimator trust balance, and weighting filters for robust frequency-domain priorities.

How to Choose Choose by decision context first (continuous vs discrete, deterministic vs stochastic, nominal performance vs robustness), then by data maturity (known poles vs uncertain noise models vs weighted frequency targets).

graph TD
    A[Start: What are you designing?] --> B{Need controller gain K?}
    B -- Yes --> C{Continuous or discrete model?}
    C -- Continuous --> D{Prefer direct pole targets?}
    C -- Discrete --> E{Prefer direct pole targets?}
    D -- Yes --> F[Use PLACE or ACKER for SISO]
    D -- No --> G[Use LQR]
    E -- Yes --> H[Use PLACE]
    E -- No --> I[Use DLQR]
    B -- No --> J{Need observer gain L?}
    J -- Continuous --> K[Use LQE]
    J -- Discrete --> L[Use DLQE]
    A --> M{Need robustness shaping by frequency?}
    M -- Yes --> N[Use MIXSYN]
    A --> O{Need gain sweep insight?}
    O -- Yes --> P[Use ROOT_LOCUS]

Function Primary purpose Best fit Strengths Cautions
ACKER SISO pole placement via Ackermann formula Fast direct state-feedback design with explicit pole targets Exact pole targeting for controllable SISO models Not the best default for broader MIMO workflows; can yield large gains for aggressive poles
PLACE General state-feedback pole placement Pole-spec-driven controller design in continuous/discrete settings Flexible and practical for many pole-assignment tasks Pole choices still require engineering judgment on robustness and effort
LQR Continuous-time optimal regulator Continuous models with clear state/control trade-offs Systematic gain synthesis via Q, R, N weighting Weight selection can be non-intuitive without scaling discipline
DLQR Discrete-time optimal regulator Digital control implementation with fixed sample time Directly matches sampled implementation realities Sensitive to sample-time/model mismatch if discretization assumptions are weak
LQE Continuous-time Kalman estimator gain Continuous observer design with noise covariance assumptions Principled observer tuning from uncertainty models Performance depends on credible Q_N, R_N, and model structure
DLQE Discrete-time Kalman estimator gain Embedded/PLC observer design in sampled systems Natural fit for digital estimators Requires careful covariance interpretation at sample interval
MIXSYN Mixed-sensitivity H_\infty synthesis Robust design with frequency-shaped objectives Explicit robustness/performance shaping across bands Weight design complexity is higher than LQR/pole-placement workflows
ROOT_LOCUS Pole trajectory vs gain Commissioning intuition, gain-range risk analysis, communication Visual and tabular insight into stability trends Diagnostic/selection aid, not by itself a complete synthesis method

A practical selection sequence for most teams is:

  1. Use ROOT_LOCUS early for gain intuition and feasibility context.
  2. Choose a regulator path: PLACE/ACKER for explicit pole specs, or LQR/DLQR for weighting-based optimization.
  3. Add estimation with LQE or DLQE when states are not fully measured.
  4. Escalate to MIXSYN when robustness and frequency-shaped requirements dominate nominal tuning goals.

This decision framework keeps function-level calculators actionable while preserving a category-level methodology, which is exactly the intent of the dual pillar content strategy: specific tools for immediate execution and a coherent design narrative for expert-level choice.

ACKER

Ackermann’s formula is an alternative method for calculating the state feedback gain matrix K such that A - BK reaches desired pole locations.

While mathematically elegant, acker can be numerically unstable for high-order systems (typically N > 10) compared to the place function. It is primarily recommended for SISO systems of lower order.

Excel Usage

=ACKER(A, B, poles)
  • A (list[list], required): The state dynamics matrix A (NxN).
  • B (list[list], required): The input matrix B (Nx1 - MUST be SISO for Ackermann).
  • poles (list[list], required): The desired closed-loop pole locations (1xN or Nx1).

Returns (list[list]): The state feedback gain matrix K.

Example 1: SISO pole placement with acker

Inputs:

A B poles
0 1 0 -5 -6
-2 -3 1

Excel formula:

=ACKER({0,1;-2,-3}, {0;1}, {-5,-6})

Expected output:

Result
28 8
Example 2: Accept flat pole list input

Inputs:

A B poles
0 1 0 -4,-7
-2 -3 1

Excel formula:

=ACKER({0,1;-2,-3}, {0;1}, -4,-7)

Expected output:

Result
26 8
Example 3: Place moderate stable poles

Inputs:

A B poles
0 1 0 -2 -3
-1 -1 1

Excel formula:

=ACKER({0,1;-1,-1}, {0;1}, {-2,-3})

Expected output:

Result
5 4
Example 4: Place faster closed-loop poles

Inputs:

A B poles
0 1 0 -3 -8
0 0 1

Excel formula:

=ACKER({0,1;0,0}, {0;1}, {-3,-8})

Expected output:

Result
24 11

Python Code

Show Code
import control as ct
import numpy as np

def acker(A, B, poles):
    """
    Pole placement using Ackermann's formula.

    See: https://python-control.readthedocs.io/en/latest/generated/control.acker.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        A (list[list]): The state dynamics matrix A (NxN).
        B (list[list]): The input matrix B (Nx1 - MUST be SISO for Ackermann).
        poles (list[list]): The desired closed-loop pole locations (1xN or Nx1).

    Returns:
        list[list]: The state feedback gain matrix K.
    """
    try:
        def to_np(x):
            if x is None:
                return None
            if not isinstance(x, list):
                return np.array([[float(x)]])
            if len(x) > 0 and not isinstance(x[0], list):
                x = [x]
            return np.array([[float(v) if v is not None and str(v) != "" else 0.0 for v in row] for row in x])

        def parse_poles(x):
            values = []
            if not isinstance(x, list):
                x = [x]
            for item in x:
                if isinstance(item, list):
                    for v in item:
                        if v is not None and str(v) != "":
                            s = str(v)
                            values.append(complex(s.replace('i', 'j')) if 'i' in s or 'j' in s else float(v))
                elif item is not None and str(item) != "":
                    s = str(item)
                    values.append(complex(s.replace('i', 'j')) if 'i' in s or 'j' in s else float(item))
            return values

        a_np = to_np(A)
        b_np = to_np(B)
        p_list = parse_poles(poles)

        if len(p_list) == 0:
            return "Error: poles must contain at least one value"

        K = ct.acker(a_np, b_np, p_list)

        return K.tolist()
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The state dynamics matrix A (NxN).
The input matrix B (Nx1 - MUST be SISO for Ackermann).
The desired closed-loop pole locations (1xN or Nx1).

DLQE

The discrete-time linear quadratic estimator (DLQE) calculates the optimal estimator gain matrix L for discrete-time systems with process and measurement noise.

It minimizes the expected squared error of the state estimate x_e[n] using sensor measurements y[n].

Excel Usage

=DLQE(A, G, C, QN, RN, NN)
  • A (list[list], required): The discrete state dynamics matrix A (NxN).
  • G (list[list], required): The discrete process noise input matrix G (NxNw).
  • C (list[list], required): The output matrix C (Ny x N).
  • QN (list[list], required): Process noise covariance matrix (Nw x Nw).
  • RN (list[list], required): Sensor noise covariance matrix (Ny x Ny).
  • NN (list[list], optional, default: null): Optional cross-covariance matrix (Nw x Ny).

Returns (list[list]): The discrete Kalman estimator gain matrix L.

Example 1: Discrete LQE design

Inputs:

A G C QN RN
0.5 0 1 0 1 1 0.1 0 1
0 0.3 0 1 0 0.1

Excel formula:

=DLQE({0.5,0;0,0.3}, {1,0;0,1}, {1,1}, {0.1,0;0,0.1}, {1})

Expected output:

Result
0.0514686
0.0260185
Example 2: Single-state discrete estimator

Inputs:

A G C QN RN
0.6 1 1 0.2 0.5

Excel formula:

=DLQE({0.6}, {1}, {1}, {0.2}, {0.5})

Expected output:

0.206232

Example 3: Coupled-state measurement model

Inputs:

A G C QN RN
0.9 0.2 1 0 1 -1 0.03 0 0.4
0.1 0.8 0 1 0 0.03

Excel formula:

=DLQE({0.9,0.2;0.1,0.8}, {1,0;0,1}, {1,-1}, {0.03,0;0,0.03}, {0.4})

Expected output:

Result
0.273274
0.0608544
Example 4: Partially observed discrete estimator

Inputs:

A G C QN RN
0.7 0.1 1 0 1 0 0.05 0 0.3
0 0.6 0 1 0 0.02

Excel formula:

=DLQE({0.7,0.1;0,0.6}, {1,0;0,1}, {1,0}, {0.05,0;0,0.02}, {0.3})

Expected output:

Result
0.151352
0.00438577

Python Code

Show Code
import control as ct
import numpy as np

def dlqe(A, G, C, QN, RN, NN=None):
    """
    Linear quadratic estimator (Kalman filter) for discrete-time systems.

    See: https://python-control.readthedocs.io/en/latest/generated/control.dlqe.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        A (list[list]): The discrete state dynamics matrix A (NxN).
        G (list[list]): The discrete process noise input matrix G (NxNw).
        C (list[list]): The output matrix C (Ny x N).
        QN (list[list]): Process noise covariance matrix (Nw x Nw).
        RN (list[list]): Sensor noise covariance matrix (Ny x Ny).
        NN (list[list], optional): Optional cross-covariance matrix (Nw x Ny). Default is None.

    Returns:
        list[list]: The discrete Kalman estimator gain matrix L.
    """
    try:
        def to_np(x):
            if x is None:
                return None
            if not isinstance(x, list):
                return np.array([[float(x)]])
            if len(x) > 0 and not isinstance(x[0], list):
                x = [x]
            return np.array([[float(v) if v is not None and str(v) != "" else 0.0 for v in row] for row in x])

        a_np = to_np(A)
        g_np = to_np(G)
        c_np = to_np(C)
        qn_np = to_np(QN)
        rn_np = to_np(RN)
        nn_np = to_np(NN)

        if nn_np is None:
            L, P, E = ct.dlqe(a_np, g_np, c_np, qn_np, rn_np)
        else:
            L, P, E = ct.dlqe(a_np, g_np, c_np, qn_np, rn_np, nn_np)

        return L.tolist()
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The discrete state dynamics matrix A (NxN).
The discrete process noise input matrix G (NxNw).
The output matrix C (Ny x N).
Process noise covariance matrix (Nw x Nw).
Sensor noise covariance matrix (Ny x Ny).
Optional cross-covariance matrix (Nw x Ny).

DLQR

The discrete-time linear quadratic regulator (DLQR) calculates the optimal state-feedback gain matrix K such that the feedback law u[n] = -Kx[n] minimizes the quadratic cost function:

J = \sum_{n=0}^{\infty} (x[n]^T Q x[n] + u[n]^T R u[n] + 2x[n]^T N u[n])

This function accepts discrete-time system matrices A and B, along with weight matrices Q, R, and optionally N. It returns the optimal gain matrix K.

Excel Usage

=DLQR(A, B, Q, R, N)
  • A (list[list], required): The discrete-time state dynamics matrix A (NxN).
  • B (list[list], required): The discrete-time input matrix B (NxM).
  • Q (list[list], required): The state weighting matrix Q (NxN). Should be positive semi-definite.
  • R (list[list], required): The input weighting matrix R (MxM). Should be positive definite.
  • N (list[list], optional, default: null): Optional cross-weighting matrix N (NxM).

Returns (list[list]): The optimal discrete-time state-feedback gain matrix K.

Example 1: Discrete double integrator LQR

Inputs:

A B Q R
1 1 0.5 1 0 1
0 1 1 0 1

Excel formula:

=DLQR({1,1;0,1}, {0.5;1}, {1,0;0,1}, {1})

Expected output:

Result
0.434483 1.02847
Example 2: DLQR with explicit zero cross-weight matrix

Inputs:

A B Q R N
1 0.2 0 2 0 0.5 0
0 1 1 0 1 0

Excel formula:

=DLQR({1,0.2;0,1}, {0;1}, {2,0;0,1}, {0.5}, {0;0})

Expected output:

Result
0.907482 0.975615
Example 3: Single-state discrete regulator

Inputs:

A B Q R
0.95 1 1 0.1

Excel formula:

=DLQR({0.95}, {1}, {1}, {0.1})

Expected output:

0.86967

Example 4: Coupled-state discrete regulator

Inputs:

A B Q R
1 0.1 0 3 0 1
0.05 0.9 1 0 2

Excel formula:

=DLQR({1,0.1;0.05,0.9}, {0;1}, {3,0;0,2}, {1})

Expected output:

Result
0.912358 0.764562

Python Code

Show Code
import control as ct
import numpy as np

def dlqr(A, B, Q, R, N=None):
    """
    Linear quadratic regulator design for discrete-time systems.

    See: https://python-control.readthedocs.io/en/latest/generated/control.dlqr.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        A (list[list]): The discrete-time state dynamics matrix A (NxN).
        B (list[list]): The discrete-time input matrix B (NxM).
        Q (list[list]): The state weighting matrix Q (NxN). Should be positive semi-definite.
        R (list[list]): The input weighting matrix R (MxM). Should be positive definite.
        N (list[list], optional): Optional cross-weighting matrix N (NxM). Default is None.

    Returns:
        list[list]: The optimal discrete-time state-feedback gain matrix K.
    """
    try:
        def to_np(x):
            if x is None:
                return None
            if not isinstance(x, list):
                return np.array([[float(x)]])
            if len(x) > 0 and not isinstance(x[0], list):
                x = [x]
            return np.array([[float(v) if v is not None and str(v) != "" else 0.0 for v in row] for row in x])

        a_np = to_np(A)
        b_np = to_np(B)
        q_np = to_np(Q)
        r_np = to_np(R)
        n_np = to_np(N)

        if n_np is None:
            K, S, E = ct.dlqr(a_np, b_np, q_np, r_np)
        else:
            K, S, E = ct.dlqr(a_np, b_np, q_np, r_np, n_np)

        return K.tolist()
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The discrete-time state dynamics matrix A (NxN).
The discrete-time input matrix B (NxM).
The state weighting matrix Q (NxN). Should be positive semi-definite.
The input weighting matrix R (MxM). Should be positive definite.
Optional cross-weighting matrix N (NxM).

LQE

The linear quadratic estimator (LQE), commonly known as the Kalman filter, calculates the optimal estimator gain matrix L to minimize the state estimation error covariance for a system with process noise and measurement noise.

Given covariances QN (process noise) and RN (sensor noise), it produces the gain L for the stationary Kalman filter: \dot{x}_e = A x_e + B u + L(y - C x_e - D u)

Excel Usage

=LQE(A, G, C, QN, RN, NN)
  • A (list[list], required): The state dynamics matrix A (NxN).
  • G (list[list], required): The process noise input matrix G (NxNw).
  • C (list[list], required): The output matrix C (Ny x N).
  • QN (list[list], required): Process noise covariance matrix (Nw x Nw).
  • RN (list[list], required): Sensor noise covariance matrix (Ny x Ny).
  • NN (list[list], optional, default: null): Optional cross-covariance matrix (Nw x Ny).

Returns (list[list]): The Kalman estimator gain matrix L.

Example 1: Simple LQE design

Inputs:

A G C QN RN
-1 0 1 0 1 1 0.1 0 1
0 -2 0 1 0 0.1

Excel formula:

=LQE({-1,0;0,-2}, {1,0;0,1}, {1,1}, {0.1,0;0,0.1}, {1})

Expected output:

Result
0.0484323
0.0244557
Example 2: Single-state continuous estimator

Inputs:

A G C QN RN
-0.8 1 1 0.2 0.7

Excel formula:

=LQE({-0.8}, {1}, {1}, {0.2}, {0.7})

Expected output:

0.16214

Example 3: Coupled-state continuous estimator

Inputs:

A G C QN RN
-1.2 0.4 1 0 1 -0.5 0.04 0 0.25
-0.2 -0.9 0 1 0 0.04

Excel formula:

=LQE({-1.2,0.4;-0.2,-0.9}, {1,0;0,1}, {1,-0.5}, {0.04,0;0,0.04}, {0.25})

Expected output:

Result
0.0632024
-0.0321583
Example 4: Partially observed continuous estimator

Inputs:

A G C QN RN
-0.6 0.2 1 0 1 0 0.08 0 0.4
0 -1.1 0 1 0 0.03

Excel formula:

=LQE({-0.6,0.2;0,-1.1}, {1,0;0,1}, {1,0}, {0.08,0;0,0.03}, {0.4})

Expected output:

Result
0.149316
0.0036862

Python Code

Show Code
import control as ct
import numpy as np

def lqe(A, G, C, QN, RN, NN=None):
    """
    Linear quadratic estimator (Kalman filter) for continuous-time systems.

    See: https://python-control.readthedocs.io/en/latest/generated/control.lqe.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        A (list[list]): The state dynamics matrix A (NxN).
        G (list[list]): The process noise input matrix G (NxNw).
        C (list[list]): The output matrix C (Ny x N).
        QN (list[list]): Process noise covariance matrix (Nw x Nw).
        RN (list[list]): Sensor noise covariance matrix (Ny x Ny).
        NN (list[list], optional): Optional cross-covariance matrix (Nw x Ny). Default is None.

    Returns:
        list[list]: The Kalman estimator gain matrix L.
    """
    try:
        def to_np(x):
            if x is None:
                return None
            if not isinstance(x, list):
                return np.array([[float(x)]])
            if len(x) > 0 and not isinstance(x[0], list):
                x = [x]
            return np.array([[float(v) if v is not None and str(v) != "" else 0.0 for v in row] for row in x])

        a_np = to_np(A)
        g_np = to_np(G)
        c_np = to_np(C)
        qn_np = to_np(QN)
        rn_np = to_np(RN)
        nn_np = to_np(NN)

        # The control.lqe() implementation currently doesn't support nonzero
        # cross-covariance (NN) despite accepting it as an argument.
        # Accept explicit zero NN for compatibility, otherwise error.
        if nn_np is not None:
            if not np.allclose(nn_np, 0):
                return "Error: cross-covariance (NN) is not implemented"
            nn_np = None

        if nn_np is None:
            L, P, E = ct.lqe(a_np, g_np, c_np, qn_np, rn_np)
        else:
            L, P, E = ct.lqe(a_np, g_np, c_np, qn_np, rn_np, nn_np)

        return L.tolist()
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The state dynamics matrix A (NxN).
The process noise input matrix G (NxNw).
The output matrix C (Ny x N).
Process noise covariance matrix (Nw x Nw).
Sensor noise covariance matrix (Ny x Ny).
Optional cross-covariance matrix (Nw x Ny).

LQR

The linear quadratic regulator (LQR) calculates the optimal state-feedback gain matrix K such that the feedback law u = -Kx minimizes the quadratic cost function:

J = \int_{0}^{\infty} (x^T Q x + u^T R u + 2x^T N u) dt

This function accepts system matrices A and B, or an LTI system object, along with weight matrices Q, R, and optionally N. It returns the optimal gain matrix K.

Excel Usage

=LQR(A, B, Q, R, N)
  • A (list[list], required): The state dynamics matrix A (NxN).
  • B (list[list], required): The input matrix B (NxM).
  • Q (list[list], required): The state weighting matrix Q (NxN). Should be positive semi-definite.
  • R (list[list], required): The input weighting matrix R (MxM). Should be positive definite.
  • N (list[list], optional, default: null): Optional cross-weighting matrix N (NxM).

Returns (list[list]): The optimal state-feedback gain matrix K.

Example 1: Double integrator LQR

Inputs:

A B Q R
0 1 0 1 0 1
0 0 1 0 1

Excel formula:

=LQR({0,1;0,0}, {0;1}, {1,0;0,1}, {1})

Expected output:

Result
1 1.73205
Example 2: LQR with explicit zero cross-weight matrix

Inputs:

A B Q R N
0 1 0 2 0 0.8 0
-1 -0.5 1 0 1 0

Excel formula:

=LQR({0,1;-1,-0.5}, {0;1}, {2,0;0,1}, {0.8}, {0;0})

Expected output:

Result
0.870829 1.30046
Example 3: Single-state continuous regulator

Inputs:

A B Q R
-1 1 1 0.5

Excel formula:

=LQR({-1}, {1}, {1}, {0.5})

Expected output:

0.732051

Example 4: Coupled-state continuous regulator

Inputs:

A B Q R
0 1 0 4 0 1
-2 -1 1 0 2

Excel formula:

=LQR({0,1;-2,-1}, {0;1}, {4,0;0,2}, {1})

Expected output:

Result
0.828427 1.15797

Python Code

Show Code
import control as ct
import numpy as np

def lqr(A, B, Q, R, N=None):
    """
    Linear quadratic regulator design for continuous-time systems.

    See: https://python-control.readthedocs.io/en/latest/generated/control.lqr.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        A (list[list]): The state dynamics matrix A (NxN).
        B (list[list]): The input matrix B (NxM).
        Q (list[list]): The state weighting matrix Q (NxN). Should be positive semi-definite.
        R (list[list]): The input weighting matrix R (MxM). Should be positive definite.
        N (list[list], optional): Optional cross-weighting matrix N (NxM). Default is None.

    Returns:
        list[list]: The optimal state-feedback gain matrix K.
    """
    try:
        def to_np(x):
            if x is None:
                return None
            if not isinstance(x, list):
                return np.array([[float(x)]])
            if len(x) > 0 and not isinstance(x[0], list):
                x = [x]
            return np.array([[float(v) if v is not None and str(v) != "" else 0.0 for v in row] for row in x])

        a_np = to_np(A)
        b_np = to_np(B)
        q_np = to_np(Q)
        r_np = to_np(R)
        n_np = to_np(N)

        if n_np is None:
            K, S, E = ct.lqr(a_np, b_np, q_np, r_np)
        else:
            K, S, E = ct.lqr(a_np, b_np, q_np, r_np, n_np)

        return K.tolist()
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The state dynamics matrix A (NxN).
The input matrix B (NxM).
The state weighting matrix Q (NxN). Should be positive semi-definite.
The input weighting matrix R (MxM). Should be positive definite.
Optional cross-weighting matrix N (NxM).

MIXSYN

Mixed-sensitivity synthesis (mixsyn) is an H_\infty control design method that shapes the closed-loop sensitivity functions using weight functions W_1 (performance), W_2 (control effort), and W_3 (robustness).

The underlying optimization seeks a controller K that minimizes the norm of the weighted closed-loop map:

\left\| \begin{bmatrix} W_1 S \\ W_2 K S \\ W_3 T \end{bmatrix} \right\|_\infty

where S = (I + GK)^{-1} is the sensitivity function and T = GK(I + GK)^{-1} is the complementary sensitivity function. In python-control 0.10.2 this synthesis path depends on the Slycot routine sb10ad. When that backend is unavailable in the runtime, this wrapper returns an error string instead of a controller.

Excel Usage

=MIXSYN(G_num, G_den, W_perf_num, W_perf_den)
  • G_num (list[list], required): Numerator of the plant G.
  • G_den (list[list], required): Denominator of the plant G.
  • W_perf_num (list[list], required): Numerator of performance weight W_perf.
  • W_perf_den (list[list], required): Denominator of performance weight W_perf.

Returns (str): String representation of the synthesized state-space controller K, or an error string if the required synthesis backend is unavailable.

Example 1: Basic mixed sensitivity synthesis

Inputs:

G_num G_den W_perf_num W_perf_den
1 1 0.1 0.1 1 1 0.01

Excel formula:

=MIXSYN({1}, {1,0.1}, {0.1,1}, {1,0.01})

Expected output:

"Error: can't find slycot subroutine sb10ad"

Example 2: Mixed sensitivity for first-order plant

Inputs:

G_num G_den W_perf_num W_perf_den
2 1 2 0.5 1 1 0.1

Excel formula:

=MIXSYN({2}, {1,2}, {0.5,1}, {1,0.1})

Expected output:

"Error: can't find slycot subroutine sb10ad"

Example 3: Accept flat list coefficient input

Inputs:

G_num G_den W_perf_num W_perf_den
1 1,0.5 0.2,1 1,0.02

Excel formula:

=MIXSYN(1, 1,0.5, 0.2,1, 1,0.02)

Expected output:

"Error: can't find slycot subroutine sb10ad"

Example 4: Heavier low-frequency performance weighting

Inputs:

G_num G_den W_perf_num W_perf_den
1 1 1 1 5 1 0.05

Excel formula:

=MIXSYN({1}, {1,1}, {1,5}, {1,0.05})

Expected output:

"Error: can't find slycot subroutine sb10ad"

Python Code

Show Code
import control as ct
import numpy as np

def mixsyn(G_num, G_den, W_perf_num, W_perf_den):
    """
    Mixed-sensitivity H-infinity controller synthesis.

    See: https://python-control.readthedocs.io/en/latest/generated/control.mixsyn.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        G_num (list[list]): Numerator of the plant G.
        G_den (list[list]): Denominator of the plant G.
        W_perf_num (list[list]): Numerator of performance weight W_perf.
        W_perf_den (list[list]): Denominator of performance weight W_perf.

    Returns:
        str: String representation of the synthesized state-space controller K, or an error string if the required synthesis backend is unavailable.
    """
    try:
        def to_coeff_list(x):
            values = []
            if not isinstance(x, list):
                return [float(x)]
            source = [x] if len(x) > 0 and not isinstance(x[0], list) else x
            for row in source:
                if isinstance(row, list):
                    for val in row:
                        if val is not None and str(val) != "":
                            values.append(float(val))
                elif row is not None and str(row) != "":
                    values.append(float(row))
            return values

        g_num = to_coeff_list(G_num)
        g_den = to_coeff_list(G_den)
        w_perf_num = to_coeff_list(W_perf_num)
        w_perf_den = to_coeff_list(W_perf_den)

        G = ct.tf(g_num, g_den)
        W_perf = ct.tf(w_perf_num, w_perf_den)

        K, CL, info = ct.mixsyn(G, w1=W_perf)
        return str(K)
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

Numerator of the plant G.
Denominator of the plant G.
Numerator of performance weight W_perf.
Denominator of performance weight W_perf.

PLACE

Pole placement (or full-state feedback gain design) calculates a gain matrix K such that the closed-loop system dynamics A - BK have a desired set of eigenvalues (poles).

This method is used when you want absolute control over the location of the system’s poles to achieve specific response characteristics like damping and settling time, provided the system is controllable.

Excel Usage

=PLACE(A, B, poles)
  • A (list[list], required): The state dynamics matrix A (NxN).
  • B (list[list], required): The input matrix B (NxM).
  • poles (list[list], required): The desired closed-loop pole locations (1xN or Nx1).

Returns (list[list]): The state feedback gain matrix K.

Example 1: Double integrator pole placement

Inputs:

A B poles
0 1 0 -2 -5
0 0 1

Excel formula:

=PLACE({0,1;0,0}, {0;1}, {-2,-5})

Expected output:

Result
10 7
Example 2: Accept flat list for desired poles

Inputs:

A B poles
0 1 0 -3,-6
0 0 1

Excel formula:

=PLACE({0,1;0,0}, {0;1}, -3,-6)

Expected output:

Result
18 9
Example 3: Place poles for damped model

Inputs:

A B poles
0 1 0 -4 -5
-2 -1 1

Excel formula:

=PLACE({0,1;-2,-1}, {0;1}, {-4,-5})

Expected output:

Result
18 8
Example 4: Place farther-left poles for faster response

Inputs:

A B poles
0 1 0 -6 -7
-1 -2 1

Excel formula:

=PLACE({0,1;-1,-2}, {0;1}, {-6,-7})

Expected output:

Result
41 11

Python Code

Show Code
import control as ct
import numpy as np

def place(A, B, poles):
    """
    Pole placement for state feedback gain design.

    See: https://python-control.readthedocs.io/en/latest/generated/control.place.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        A (list[list]): The state dynamics matrix A (NxN).
        B (list[list]): The input matrix B (NxM).
        poles (list[list]): The desired closed-loop pole locations (1xN or Nx1).

    Returns:
        list[list]: The state feedback gain matrix K.
    """
    try:
        def to_np(x):
            if x is None:
                return None
            if not isinstance(x, list):
                return np.array([[float(x)]])
            if len(x) > 0 and not isinstance(x[0], list):
                x = [x]
            return np.array([[float(v) if v is not None and str(v) != "" else 0.0 for v in row] for row in x])

        def parse_poles(x):
            values = []
            if not isinstance(x, list):
                x = [x]
            for item in x:
                if isinstance(item, list):
                    for v in item:
                        if v is not None and str(v) != "":
                            s = str(v)
                            values.append(complex(s.replace('i', 'j')) if 'i' in s or 'j' in s else float(v))
                elif item is not None and str(item) != "":
                    s = str(item)
                    values.append(complex(s.replace('i', 'j')) if 'i' in s or 'j' in s else float(item))
            return values

        a_np = to_np(A)
        b_np = to_np(B)
        p_list = parse_poles(poles)

        if len(p_list) == 0:
            return "Error: poles must contain at least one value"

        K = ct.place(a_np, b_np, p_list)

        return K.tolist()
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The state dynamics matrix A (NxN).
The input matrix B (NxM).
The desired closed-loop pole locations (1xN or Nx1).

ROOT_LOCUS

The root locus explores how the closed-loop poles of a system shift as a scalar loop gain k is varied.

The pole locations are defined by the characteristic equation

1 + k G(s) = 0

for the open-loop transfer function G(s). This wrapper uses the legacy numeric return mode of control.root_locus(..., plot=False) and returns a spreadsheet-friendly 2D array whose first row contains the evaluated gains and whose subsequent rows contain one root-locus branch per row as complex-number strings across the gain sweep.

Excel Usage

=ROOT_LOCUS(sysdata, gains)
  • sysdata (list[list], required): The system transfer function (numerator and denominator rows).
  • gains (list[list], optional, default: null): Optional list of gains to evaluate. If left blank, they are auto-computed.

Returns (list[list]): A 2D array where the first row contains the evaluated gains and each subsequent row contains one branch of closed-loop poles as complex-number strings.

Example 1: Root locus for a second order system

Inputs:

sysdata gains
1 0.1 1 10
1

Excel formula:

=ROOT_LOCUS({1;1,2,0}, {0.1,1,10})

Expected output:

Result
0.1 1 10
(-1.9486832980505138+0j) (-1+0j) (-0.9999999999999998-3j)
(-0.051316701949486204+0j) (-1+0j) (-0.9999999999999998+3j)
Example 2: Root locus with explicit gain sweep for first-order plant

Inputs:

sysdata gains
1 0 0.5 1 2 5
1

Excel formula:

=ROOT_LOCUS({1;1,1}, {0,0.5,1,2,5})

Expected output:

Result
0 0.5 1 2 5
(-1+0j) (-1.5+0j) (-2+0j) (-3+0j) (-6+0j)
Example 3: Root locus at selected gain points

Inputs:

sysdata gains
1 0 2 8
1

Excel formula:

=ROOT_LOCUS({1;1,3,2}, {0,2,8})

Expected output:

Result
0 2 8
(-2+0j) (-1.5-1.3228756555322951j) (-1.5-2.7838821814150108j)
(-1+0j) (-1.5+1.3228756555322951j) (-1.5+2.7838821814150108j)
Example 4: Root locus for integrator with pole

Inputs:

sysdata gains
1 0.2 1 5
1

Excel formula:

=ROOT_LOCUS({1;1,1,0}, {0.2,1,5})

Expected output:

Result
0.2 1 5
(-0.7236067977499789+0j) (-0.5-0.8660254037844385j) (-0.4999999999999999-2.1794494717703365j)
(-0.27639320225002106+0j) (-0.5+0.8660254037844385j) (-0.4999999999999999+2.1794494717703365j)

Python Code

Show Code
import control as ct
import numpy as np

def root_locus(sysdata, gains=None):
    """
    Calculate the root locus for an LTI system.

    See: https://python-control.readthedocs.io/en/latest/generated/control.root_locus.html

    This example function is provided as-is without any representation of accuracy.

    Args:
        sysdata (list[list]): The system transfer function (numerator and denominator rows).
        gains (list[list], optional): Optional list of gains to evaluate. If left blank, they are auto-computed. Default is None.

    Returns:
        list[list]: A 2D array where the first row contains the evaluated gains and each subsequent row contains one branch of closed-loop poles as complex-number strings.
    """
    try:
        if not sysdata or len(sysdata) < 2:
            return "Error: sysdata must contain numerator and denominator rows"

        num = []
        num_source = sysdata[0]
        if not isinstance(num_source, list):
            num = [float(num_source)]
        else:
            num_rows = [num_source] if len(num_source) > 0 and not isinstance(num_source[0], list) else num_source
            for row in num_rows:
                if isinstance(row, list):
                    for val in row:
                        if val is not None and str(val) != "":
                            num.append(float(val))
                elif row is not None and str(row) != "":
                    num.append(float(row))

        den = []
        den_source = sysdata[1]
        if not isinstance(den_source, list):
            den = [float(den_source)]
        else:
            den_rows = [den_source] if len(den_source) > 0 and not isinstance(den_source[0], list) else den_source
            for row in den_rows:
                if isinstance(row, list):
                    for val in row:
                        if val is not None and str(val) != "":
                            den.append(float(val))
                elif row is not None and str(row) != "":
                    den.append(float(row))

        sys = ct.tf(num, den)

        g_in = None
        if gains is not None:
            g_in = []
            if not isinstance(gains, list):
                g_in = [float(gains)]
            else:
                gain_rows = [gains] if len(gains) > 0 and not isinstance(gains[0], list) else gains
                for row in gain_rows:
                    if isinstance(row, list):
                        for val in row:
                            if val is not None and str(val) != "":
                                g_in.append(float(val))
                    elif row is not None and str(row) != "":
                        g_in.append(float(row))
            if len(g_in) == 0:
                g_in = None

        roots, gains_out = ct.root_locus(sys, gains=g_in, plot=False)

        res = [gains_out.tolist()]
        for i in range(roots.shape[1]):
            res.append([str(complex(r)) for r in roots[:, i]])

        return res
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The system transfer function (numerator and denominator rows).
Optional list of gains to evaluate. If left blank, they are auto-computed.