Generate lifted form matrices for discrete state space system

70 visualizaciones (últimos 30 días)
Moses
Moses el 6 de Oct. de 2025 a las 1:03
Comentada: Moses el 9 de Oct. de 2025 a las 21:47
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:
  1. Names everything correctly (like Toeplitz matrix, Markov Parameter, Observability, convolution kernel)
  2. Is as fast as possible
  3. 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 comentarios
Torsten
Torsten hace alrededor de 19 horas
Editada: Torsten hace alrededor de 18 horas
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(:)
y = 10×1
1.1894 1.1913 1.7789 1.6595 1.5379 1.4484 1.8397 1.5497 1.8095 1.3427
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
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)?
Moses
Moses hace alrededor de 17 horas
Yes, that's not the hard part -- Calculating the successive powers involved was a part of my old program (the powers_CAk function plus that last line with pagemtimes)
[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
B_theta_col = pagemtimes(CAk, Bd);
Right now I'm just using the command
h = impulse(sys_d, Kmax);
Where sys-d is the discritized state space and Kmax is a truncated impulse horizon (can be considered to be N)
This gives me All of the C*A^k*B powers I need.
The problem I have is with forming that final matrix. I know the structure: lower triangular Toeplitz (meaning every diagonal is the same and the upper triangle is all zeros), with a certain sampling rate such that rows are removed from the full toeplitz matrix (or never created).
My current implementation to get B_Theta costs a big M long for loop (M could be up to 100k long)
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 = h(:, :, 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
end
SO in summary, I've solved the challenge of computing A_theta*x0 and B_theta_col, but efficiently forming the full B_theta matrix is vexing me.
I'm especially not sure if I want the matrix is a block-toeplitz form (where each ny or nu row/column is stacked to keep the matrix 2D, (M*ny, N*nu)) or in tensor form where there is an M*N matrix that contains elements of dimension ny*nu
I need the B_theta matrix to multiply against other matricies later, so I'm leaning toward keeping block_toeplitz.
Also like I said M can be very large - so I a mtrying to avoid blkdiag and repmat, and instead using the sparse() matrix command.

Iniciar sesión para comentar.

Respuestas (0)

Categorías

Más información sobre Sparse Matrices en Help Center y File Exchange.

Productos


Versión

R2025b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!

Translated by