Generate lifted form matrices for discrete state space system
70 views (last 30 days)
Show older comments
I have a discrete recursive state space equation
x_{k+1}=A_dx_k+B_du_k
y_k=Cx_k+Du_k
where x0 is nx x 1, y is M x ny, u is N x nu.
I want to work with a single equation where y =f([g(theta) = A B C D] , x0 , u). explicit input-output form.
The program that I wrote for this already works and leverages a few tricks, but I'm sure that this is a very well studied problem in Control theory. I want to have a function that:
- Names everything correctly (like Toeplitz matrix, Markov Parameter, Observability, convolution kernel)
- Is as fast as possible
- Can work as a function handle to preserve memory (sometimes my input data N is 100k long and that is not kind to my computer when Btheta can just be a matvec)
Ultimately I understand this program very well but not well enough that I think this is a good implementation. There must be some toolbox or app I am missing.
function [B_theta_c, y_theta_c, uc, I, A_theta, Hd, B_theta, yt] = toeplitzBtheta_FOCUSS_PreCalcs(y, theta, u, x0, sys_desc, choice, dat)
% Extract dimensions
[M, ny] = size(y); % M = number of output samples, ny = number of outputs
[N, nu] = size(u); % N = number of input samples, nu = number of inputs
nx = length(x0); % nx = number of states
[Ad, Bd, Cd, Dd, d] = StateSpace(theta, sys_desc, dat, nx, ny, nu);
% Pre-indexing for downsampling
indices = calculateDownsamplingIndices(N, M);
CAk = powers_CAk(Ad, Cd, N+1); % C*A^k powers are common to all three precalculations
CAk_needed = CAk(:, :, indices);
A_theta = reshape(permute(pagemtimes(CAk_needed, x0), [3 1 2]), M, ny);
Hd_step = reshape(permute(pagemtimes(CAk_needed, d), [3 1 2]), M, ny);
Hd = cumsum(Hd_step, 1);
B_theta_col = pagemtimes(CAk, Bd); % ny x nu x N+1
B_theta = zeros(ny*M, nu*N);
% Build B_theta row by row
% Fill in each row at once by dropping in the impulse array slice with the right delay
for i = 1:M
ii = indices(i); % downsampled time for this output row
BT_row_idx = (0:ny-1)*M + i; % rows for all ny outputs at time i
% --- Valid-lag mask (skip zeros): determine which input columns can affect output at time ii
t_vec = ii - (1:N); % 1×N
j_valid = find((t_vec > 0) & (t_vec <= N));
tj = t_vec(j_valid); % 1×K, K can be 0
% Gather the (ny×nu)×K impulse slices for this row; empty is fine
H_sel = B_theta_col(:, :, tj); % ny × nu × K
% --- Drop the entire ny×K block into place for each input channel (vectorized write)
for c = 1:nu
BT_col_idx = (c-1)*N + j_valid;
vals = reshape(H_sel(:, c, :), ny, []); % ny × K (or ny×0)
B_theta(BT_row_idx, BT_col_idx) = vals;
end
% % --- Drop the entire ny×K block into place for each input channel (vectorized write)
% row_block = zeros(ny, nu*N);
% cols = zeros(1, nu*numel(j_valid));
% p = 1;
% for k = 1:numel(j_valid)
% cols(p:p+nu-1) = (o:nu-1)*N + j_valid(k);
% p = p + nu;
% end
% row_block(:, cols) = H_sel(:,:);
% B_theta(BT_row_idx, :) = row_block; % assign the whole row block at once
% end
end
% Get the corresponding B_theta matrix for the chosen component
start_col = (choice-1)*N + 1;
end_col = choice*N;
B_theta_c = B_theta(:, start_col:end_col);
%Calculate the contribution from all OTHER components
uc = u(:, choice); % extract chosen signal
% uc = u(choice, :)';
B_theta_u_others = B_theta * u(:) - B_theta_c * u(:, choice);
yt = y - A_theta - Hd;
y_theta_c = yt(:) - B_theta_u_others;
I = speye(size(B_theta_c, 1));
end
function CAk = powers_CAk(Ad, Cd, N)
% powers_CAk - Precompute C*A^k matrices efficiently
%
% This function precomputes C*A^k for k = 0 to N-1 using efficient algorithms.
% For 2x2 matrices, it uses the Cayley-Hamilton recurrence relation.
% For larger matrices, it uses direct matrix multiplication.
% Get matrix dimensions
[nx, ~] = size(Ad);
ny = size(Cd, 1);
% Initialize output matrix
CAk = zeros(ny, nx, N);
% Set first matrix: C*A^0 = C
CAk(:, :, 1) = Cd;
CAk(:, :, 2) = Cd * Ad;
% For 2x2 matrices, use Cayley-Hamilton recurrence for efficiency
% This avoids computing matrix powers directly
if nx == 2
% Get trace and determinant for recurrence relation
s1 = trace(Ad); % s1 = λ1 + λ2
s2 = det(Ad); % s2 = λ1 * λ2
% Use recurrence: A^k = s1*A^(k-1) - s2*A^(k-2)
for k = 3:N
CAk(:, :, k) = s1 * CAk(:, :, k-1) - s2 * CAk(:, :, k-2);
end
else
% For larger matrices, use direct multiplication
% A^k = A^(k-1) * A
for k = 3:N
CAk(:, :, k) = CAk(:, :, k-1) * Ad;
end
end
end
10 Comments
Torsten
on 9 Oct 2025 at 20:25
Edited: Torsten
on 9 Oct 2025 at 20:48
Here is the computation of A_theta and B_theta for the example from above:
rng("default")
A = diag([0.7,0.5]); B = rand(2,2); C = rand(2,2); D = zeros(2,2);
x0 = [1;2];
u = rand(2,5); % five output samples
C5 = repmat({C},1,5);
D5 = repmat({D},1,5);
A_theta = blkdiag(C5{:})*[eye(size(A));A;A^2;A^3;A^4];
B_theta = blkdiag(C5{:})*[zeros(2,10);[B,zeros(2,8)];[A^1*B,B,zeros(2,6)];[A^2*B,A^1*B,B,zeros(2,4)];[A^3*B,A^2*B,A^1*B,B,zeros(2,2)]] + ...
blkdiag(D5{:});
y = A_theta*x0 + B_theta*u(:)
Do you see the pattern and can you transfer it to the general case ?
Can you deduce an efficient code to compute the matrices involved (A,A^2,...,A^4,A*B,A^2*B,A^3*B)?
Answers (0)
See Also
Categories
Find more on Sparse Matrices in Help Center and File Exchange
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!