BALRED

Balanced truncation reduces a state-space model by preserving the states with the largest Hankel singular values, which represent how strongly each state contributes to input-output dynamics.

For a continuous-time linear system

\dot{x} = Ax + Bu, \quad y = Cx + Du

balanced reduction computes a transformed realization where controllability and observability Gramians are equal and diagonal, then truncates low-energy states to produce a reduced model of selected order.

This wrapper accepts stable continuous-time state-space matrices and returns the reduced realization as deterministic matrix data so the result can be compared reliably in Excel and automated tests.

Excel Usage

=BALRED(A, B, C, D, order)
  • A (list[list], required): The state dynamics matrix A (NxN).
  • B (list[list], required): The input matrix B (NxM).
  • C (list[list], required): The output matrix C (Ny x N).
  • D (list[list], required): The feedthrough matrix D (Ny x M).
  • order (int, required): Desired order of the reduced model.

Returns (str): Stringified dictionary containing the reduced state-space matrices A, B, C, and D.

Example 1: Reduce stable diagonal system to order two

Inputs:

A B C D order
-1 0 0 0 1 1 1 1 1 0 2
0 -2 0 0 1
0 0 -3 0 1
0 0 0 -4 1

Excel formula:

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

Expected output:

"{'A': [[-1.8881943210548617, -1.0094735271597388], [-1.0094735271597395, -2.396610708833071]], 'B': [[1.9213621306674076], [0.5463180989081131]], 'C': [[1.9213621306674076, 0.5463180989081124]], 'D': [[0.0]]}"

Example 2: Reduce coupled second-order system to order one

Inputs:

A B C D order
-2 1 1 1 0 0 1
-1 -3 0.5

Excel formula:

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

Expected output:

"{'A': [[-2.03576165455737]], 'B': [[1.0281468837336207]], 'C': [[1.0281468837336207]], 'D': [[0.0]]}"

Example 3: Reduce stable MIMO system to order two

Inputs:

A B C D order
-3 1 0 1 0 1 0 0 0 0 2
0 -2 1 0 1 0 1 1 0 0
0 0 -1 1 1

Excel formula:

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

Expected output:

"{'A': [[-0.8992182054067066, 0.30850424263019266], [-0.1383294994280792, -3.6954672181661814]], 'B': [[0.8890469932778241, 1.2443170611921701], [0.8160586847390662, -0.4681882168801548]], 'C': [[0.34033169244847766, 0.8278820677431518], [1.4909405904144586, -0.4469488396593018]], 'D': [[0.0, 0.0], [0.0, 0.0]]}"

Example 4: Request full order from already stable system

Inputs:

A B C D order
-1 0 1 1 1 0 2
0 -4 2

Excel formula:

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

Expected output:

"{'A': [[-1.8936609374091684, -1.3719886811400714], [-1.3719886811400714, -3.106339062590834]], 'B': [[1.6097857971368428], [0.6392102059076513]], 'C': [[1.6097857971368423, 0.6392102059076515]], 'D': [[0.0]]}"

Python Code

Show Code
import control as ct
import numpy as np
from scipy.linalg import cholesky, solve_continuous_lyapunov, svd

def balred(A, B, C, D, order):
    """
    Balanced reduced order model of a system.

    See: https://python-control.readthedocs.io/en/latest/generated/control.balred.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).
        C (list[list]): The output matrix C (Ny x N).
        D (list[list]): The feedthrough matrix D (Ny x M).
        order (int): Desired order of the reduced model.

    Returns:
        str: Stringified dictionary containing the reduced state-space matrices A, B, C, and D.
    """
    try:
      def to_2d_array(values, name):
        if not isinstance(values, list):
          values = [[values]]
        elif values and not isinstance(values[0], list):
          values = [[item] for item in values]

        try:
          array = np.asarray(values, dtype=float)
        except (TypeError, ValueError):
          return None, f"Error: {name} must contain only numeric values"

        if array.ndim != 2:
          return None, f"Error: {name} must be a 2D matrix"

        return array, None

      def clean_matrix(values):
        matrix = np.real_if_close(values, tol=1000)
        matrix = np.where(np.abs(matrix) < 1e-12, 0.0, matrix)
        return np.asarray(matrix, dtype=float).tolist()

      def canonicalize_state_signs(a_matrix, b_matrix, c_matrix):
        a_matrix = np.asarray(a_matrix, dtype=float).copy()
        b_matrix = np.asarray(b_matrix, dtype=float).copy()
        c_matrix = np.asarray(c_matrix, dtype=float).copy()

        for index in range(a_matrix.shape[0]):
          signature = np.concatenate((c_matrix[:, index], b_matrix[index, :], a_matrix[:, index], a_matrix[index, :]))
          significant = signature[np.abs(signature) > 1e-12]
          if significant.size and significant[0] < 0:
            a_matrix[:, index] *= -1
            a_matrix[index, :] *= -1
            b_matrix[index, :] *= -1
            c_matrix[:, index] *= -1

        return a_matrix, b_matrix, c_matrix

      A_mat, error = to_2d_array(A, "A")
      if error:
        return error
      B_mat, error = to_2d_array(B, "B")
      if error:
        return error
      C_mat, error = to_2d_array(C, "C")
      if error:
        return error
      D_mat, error = to_2d_array(D, "D")
      if error:
        return error

      nstates = A_mat.shape[0]
      if nstates == 0 or A_mat.shape[1] != nstates:
        return "Error: A must be a non-empty square matrix"

      if B_mat.shape[0] != nstates:
        return "Error: B must have the same number of rows as A"

      if C_mat.shape[1] != nstates:
        return "Error: C must have the same number of columns as A"

      if D_mat.shape != (C_mat.shape[0], B_mat.shape[1]):
        return "Error: D must have dimensions compatible with B and C"

      desired_order = int(order)
      if desired_order < 1 or desired_order > nstates:
        return "Error: order must be between 1 and the number of states in A"

      eigenvalues = np.linalg.eigvals(A_mat)
      if np.any(np.real(eigenvalues) >= 0):
        return "Error: balanced reduction requires a stable continuous-time A matrix"

      try:
        system = ct.ss(A_mat.tolist(), B_mat.tolist(), C_mat.tolist(), D_mat.tolist())
        reduced = ct.balred(system, desired_order)
        a_red = np.asarray(reduced.A, dtype=float)
        b_red = np.asarray(reduced.B, dtype=float)
        c_red = np.asarray(reduced.C, dtype=float)
        d_red = np.asarray(reduced.D, dtype=float)
      except Exception:
        gram_controllability = solve_continuous_lyapunov(A_mat, -(B_mat @ B_mat.T))
        gram_observability = solve_continuous_lyapunov(A_mat.T, -(C_mat.T @ C_mat))
        gram_controllability = 0.5 * (gram_controllability + gram_controllability.T)
        gram_observability = 0.5 * (gram_observability + gram_observability.T)

        factor_controllability = cholesky(gram_controllability, lower=True)
        factor_observability = cholesky(gram_observability, lower=True)
        left_vectors, singular_values, right_vectors_h = svd(
          factor_observability.T @ factor_controllability
        )

        if np.any(singular_values[:desired_order] <= 0):
          return "Error: system must be controllable and observable for balanced reduction"

        sigma_inv_sqrt = np.diag(1.0 / np.sqrt(singular_values))
        transform = factor_controllability @ right_vectors_h.T @ sigma_inv_sqrt
        inverse_transform = sigma_inv_sqrt @ left_vectors.T @ factor_observability.T

        balanced_a = inverse_transform @ A_mat @ transform
        balanced_b = inverse_transform @ B_mat
        balanced_c = C_mat @ transform

        a_red = balanced_a[:desired_order, :desired_order]
        b_red = balanced_b[:desired_order, :]
        c_red = balanced_c[:, :desired_order]
        d_red = D_mat

      a_red, b_red, c_red = canonicalize_state_signs(a_red, b_red, c_red)

      return str({
        "A": clean_matrix(a_red),
        "B": clean_matrix(b_red),
        "C": clean_matrix(c_red),
        "D": clean_matrix(d_red),
      })
    except Exception as e:
        return f"Error: {str(e)}"

Online Calculator

The state dynamics matrix A (NxN).
The input matrix B (NxM).
The output matrix C (Ny x N).
The feedthrough matrix D (Ny x M).
Desired order of the reduced model.