Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7108c36
control: add InputDelay/OutputDelay/IODelay property and delay helpers
pganguli Jul 22, 2026
8d5f37e
control: compose InputDelay/OutputDelay/IODelay across series/paralle…
pganguli Jul 22, 2026
d049bdc
control: c2d/d2c delay handling and DelayModeling option
pganguli Jul 22, 2026
e292c85
control: apply stored delay in time-domain responses
pganguli Jul 22, 2026
bbfc8ce
ss: add InternalDelay property with feedback/connect/freqresp support
pganguli Jul 22, 2026
661ecd1
control: InternalDelay discretization and time-domain simulation
pganguli Jul 22, 2026
57b6d26
control: guard zero/pole/stepinfo against InternalDelay, MIMO hardening
pganguli Jul 23, 2026
14a644c
tech debt: dedupe InternalDelay simulation helpers, trim verbose tests
pganguli Jul 23, 2026
44056fc
sys_connect: fix uniform-column IODelay bug, add exotic-topology tests
pganguli Jul 23, 2026
ec9c485
sys_connect: decompose dense per-entry IODelay via shadow states
pganguli Jul 23, 2026
b2d67d8
lti: add pade() for tf/zpk with shared per-delay order-vector helper
pganguli Jul 23, 2026
587953a
lti/pade: add ss ordinary-delay support via tf roundtrip
pganguli Jul 23, 2026
50e0111
pade: implement ss InternalDelay Padé substitution via lft()
pganguli Jul 23, 2026
27ee41b
stepinfo: support InternalDelay via Pade-approximated stability check
pganguli Jul 24, 2026
80ad1f0
c2d: error clearly when InternalDelay rounds to 0 samples
pganguli Jul 24, 2026
aa93003
zero/pole: point InternalDelay guard messages at pade()
pganguli Jul 24, 2026
ac47ef8
__time_response__: fix inadequate auto-horizon for delay-dominated In…
pganguli Jul 24, 2026
242ed7c
c2d: exact zoh discretization of SISO fractional delay via split-ZOH …
pganguli Jul 24, 2026
884a2ba
zpk: propagate InputDelay/OutputDelay/IODelay when converting from tf/ss
pganguli Jul 24, 2026
9312ca1
c2d: extend exact fractional-delay zoh to MIMO (per-column-uniform de…
pganguli Jul 24, 2026
f762b8e
lti: fix stale delay fields after transpose/ctranspose
pganguli Jul 24, 2026
11946dc
add exact discrete delay (z^-k) ss realization helper
pganguli Jul 24, 2026
47f1304
pade: support discrete-time input via exact (not approximate) delay a…
pganguli Jul 24, 2026
10b2aa5
pade: validate n in the discrete ordinary-delay path too, matching th…
pganguli Jul 24, 2026
a4be112
pade: fix unwanted figure-window side effect in discrete n-validation
pganguli Jul 24, 2026
9895ff9
c2d: implement DelayModeling=state for InternalDelay via exact loop c…
pganguli Jul 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions inst/@lti/__lti_group__.m
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@
## retlti.outgroup remains empty struct
endif

if (strcmpi (dim, "blkdiag"))
## Block-diagonal append: I/O delays concatenate with zero cross-terms,
## exactly like the state/b/c/d blocks. (Previously these were dropped,
## which silently discarded delays through append/feedback/connect.)
retlti.indelay = [lti1.indelay(:); lti2.indelay(:)];
retlti.outdelay = [lti1.outdelay(:); lti2.outdelay(:)];
retlti.iodelay = blkdiag (lti1.iodelay, lti2.iodelay);
else
## horzcat/vertcat/times share inputs and/or outputs; a general delay
## merge is ambiguous, so keep the historical zeroing for those modes.
retlti.indelay = zeros (numel (retlti.inname), 1);
retlti.outdelay = zeros (numel (retlti.outname), 1);
retlti.iodelay = zeros (numel (retlti.outname), numel (retlti.inname));
endif

if (lti1.tsam == lti2.tsam)
retlti.tsam = lti1.tsam;
elseif (lti1.tsam == -1 && lti2.tsam > 0)
Expand Down Expand Up @@ -90,3 +105,21 @@
ret = cell2struct ([ca; cb], [fa; fb]);

endfunction


%!test
%! h = [tf(1,[1 1]); tf(1,[1 2])];
%! assert (totaldelay (h), zeros (2, 1));

%!test
%! h1 = [tf(1,[1 1]); tf(1,[1 2])];
%! assert (hasdelay (h1), false);
%! h2 = [tf(1,[1 1]), tf(1,[1 2])];
%! assert (hasdelay (h2), false);

%!test
%! h = [tf(1,[1 1]); tf(1,[1 2])];
%! [p, m] = size (h);
%! assert (size (h.InputDelay), [m, 1]);
%! assert (size (h.OutputDelay), [p, 1]);
%! assert (size (h.IODelay), [p, m]);
6 changes: 6 additions & 0 deletions inst/@lti/__lti_keys__.m
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@

## cell vector of lti-specific keys
keys = {"tsam";
"inputdelay";
"outputdelay";
"iodelay";
"inname";
"outname";
"ingroup";
Expand All @@ -38,6 +41,9 @@

## cell vector of lti-specific assignable values
vals = {"scalar (sample time in seconds)";
"m-by-1 real matrix (input delay: seconds for ct models, samples for dt models)";
"p-by-1 real matrix (output delay: seconds for ct models, samples for dt models)";
"p-by-m real matrix (I/O delay: seconds for ct models, samples for dt models)";
"m-by-1 cell vector of strings";
"p-by-1 cell vector of strings";
"struct with indices as fields";
Expand Down
11 changes: 11 additions & 0 deletions inst/@lti/__lti_prune__.m
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@
lti.outname = lti.outname(out_idx);
lti.inname = lti.inname(in_idx);

## delays follow the selected I/O (previously left at their old size,
## harmless only while they were always zero)
if (! (ischar (out_idx) && strcmp (out_idx, ":")))
lti.outdelay = lti.outdelay(out_idx);
lti.iodelay = lti.iodelay(out_idx, :);
endif
if (! (ischar (in_idx) && strcmp (in_idx, ":")))
lti.indelay = lti.indelay(in_idx);
lti.iodelay = lti.iodelay(:, in_idx);
endif

endfunction


Expand Down
87 changes: 87 additions & 0 deletions inst/@lti/__pade_order_vector__.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
## Copyright (C) 2026 Prateek Ganguli
##
## This file is part of LTI Syncope.
##
## LTI Syncope is free software: you can redistribute it and/or modify
## it under the terms of the GNU General Public License as published by
## the Free Software Foundation, either version 3 of the License, or
## (at your option) any later version.
##
## LTI Syncope is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with LTI Syncope. If not, see <http://www.gnu.org/licenses/>.

## Compute the per-nonzero-delay Pade approximation order vector for
## pade(sys, n).
##
## Shared helper for @lti/pade.m (tf/zpk) and the future ss-specific pade
## dispatches (Tasks 2/3) -- build the per-entry order list once here and
## have every case index into it consistently.
##
## Ordering convention (this codebase's own, since MATLAB's internal
## ordering isn't documented/verifiable): the total nonzero-delay count,
## and the order in which "n" (if a vector) is consumed, walks:
## 1. InputDelay nonzero entries, in input order.
## 2. OutputDelay nonzero entries, in output order.
## 3. IODelay nonzero entries, in column-major (Octave linear index) order.
## 4. InternalDelay ports, in the existing internal port order returned
## by get(sys, "internaldelay") (always empty for tf/zpk, since those
## types can never carry InternalDelay).
##
## Inputs:
## sys - an LTI model.
## n - scalar or vector Pade order. If scalar, the same order is
## used for every nonzero delay. If a vector, its length must
## equal the total nonzero-delay count (computed per the ordering
## above); anything else is an error.
##
## Outputs:
## orders - column vector of per-delay-entry orders, length equal to the
## total nonzero-delay count, in the documented order above.
## idx - struct with fields "input", "output", "iod", "internal", each
## a vector of linear/element indices (into InputDelay,
## OutputDelay, IODelay(:), and internaldelay(:) respectively)
## identifying which entries are nonzero, in the same order
## "orders" was built in -- so callers can walk
## idx.input/idx.output/idx.iod/idx.internal in lockstep with
## orders(1:numel(idx.input)), orders(numel(idx.input)+1 : ...),
## etc.
function [orders, idx] = __pade_order_vector__ (sys, n)

if (nargin != 2 || ! isa (sys, "lti"))
print_usage ();
endif

[indelay, outdelay, iodelay] = get (sys, "inputdelay", "outputdelay", "iodelay");

## InternalDelay is a ss-specific property; get (sys, "internaldelay")
## errors for tf/zpk models, so only query it for ss (always empty for
## tf/zpk, matching the documented ordering convention above).
if (isa (sys, "ss"))
internaldelay = get (sys, "internaldelay");
else
internaldelay = [];
endif

idx.input = find (indelay(:) != 0);
idx.output = find (outdelay(:) != 0);
idx.iod = find (iodelay(:) != 0);
idx.internal = find (internaldelay(:) != 0);

total = numel (idx.input) + numel (idx.output) + numel (idx.iod) + numel (idx.internal);

if (isscalar (n))
orders = repmat (n, total, 1);
else
if (numel (n) != total)
error ("pade: order vector length (%d) does not match the number of nonzero delays (%d)",
numel (n), total);
endif
orders = n(:);
endif

endfunction
Loading