From 68c36f28580f16676c91149671e35a670239b730 Mon Sep 17 00:00:00 2001 From: Michael Hayes Date: Sun, 8 Dec 2024 14:26:47 +1300 Subject: [PATCH] Implement LTIFilter.response --- doc/examples/schematics/L2.png | Bin 0 -> 6224 bytes doc/examples/schematics/L2.sch | 1 + doc/tutorials.rst | 1 + lcapy/expr.py | 3 +++ lcapy/ltifilter.py | 23 +++++++++++++---------- lcapy/sexpr.py | 6 +++++- 6 files changed, 23 insertions(+), 11 deletions(-) create mode 100644 doc/examples/schematics/L2.png create mode 100644 doc/examples/schematics/L2.sch diff --git a/doc/examples/schematics/L2.png b/doc/examples/schematics/L2.png new file mode 100644 index 0000000000000000000000000000000000000000..62597ee0e25e9ea5208e88fed2fb13f10d194564 GIT binary patch literal 6224 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 ax = v_i.plot((-1, 10), label='input') >>> ax = v_o.plot((-1, 10), axes=ax, label='output') >>> ax.legend() + >>> # Note, the show() method is not required when using IPython or Jupyter >>> plt.show() .. image:: examples/tutorials/basic/VRC2plot.png diff --git a/lcapy/expr.py b/lcapy/expr.py index b3d9f160..93ff3ed2 100644 --- a/lcapy/expr.py +++ b/lcapy/expr.py @@ -1701,6 +1701,9 @@ def convolve(self, x, commutate=False, **assumptions): If `commutate` is True, swap order of functions in integral. + `taumin` defaults to -oo and `taumax` defaults to oo. These + are relaxed if either or both of the expressions are causal. + The result is an unevaluated integral. It can be evaluated using the `doit()` method. diff --git a/lcapy/ltifilter.py b/lcapy/ltifilter.py index 41834b61..f5905a1a 100644 --- a/lcapy/ltifilter.py +++ b/lcapy/ltifilter.py @@ -7,7 +7,7 @@ from .expr import expr, equation, ExprTuple from .differentialequation import DifferentialEquation from .functions import Derivative, Function, exp, cos -from .texpr import TimeDomainExpression +from .texpr import texpr from .transfer import transfer from .symbols import t, s, omega0, j, pi, f from .utils import isiterable @@ -177,7 +177,7 @@ def sdomain_initial_response(self, ic=None): from .sym import ssym if ic is None: - ic = () + return s * 0 if not isiterable(ic): ic = (ic, ) @@ -221,16 +221,19 @@ def initial_response(self, ic=None): Ysi = self.sdomain_initial_response(ic) return Ysi(t) - def response(self, x, ic=None, ni=None): - """Calculate response of filter to input `x` given a list of initial conditions - `ic` for time indexes specified by `ni`. If `ni` is a tuple, - this specifies the first and last (inclusive) time index. - - The initial conditions are valid prior to the time indices given by the ni - `x` can be an expression, a sequence, or a list/array of values. + def response(self, x, ic=None, use_time_domain=False): + """Calculate response of filter to input expression `x` given a list + of initial conditions `ic`. """ - return 0 + x = texpr(x) + + if use_time_domain: + return self.impulse_response().convolve(x) + self.initial_response(ic) + else: + + Y = self.transfer_function() * x(s) + self.sdomain_initial_response(ic) + return Y(t) def subs(self, *args, **kwargs): diff --git a/lcapy/sexpr.py b/lcapy/sexpr.py index 6f711e70..6c955922 100644 --- a/lcapy/sexpr.py +++ b/lcapy/sexpr.py @@ -493,7 +493,9 @@ def lti_filter(self, normalize_a0=True): return LTIFilter.from_transfer_function(self, normalize_a0) def dlti_filter(self, method='bilinear', alpha=0.5): - """Create DLTI filter using bilinear transform.""" + """Create DLTI filter using bilinear transform. + + See `discretize` for available methods.""" from .transfer import transfer @@ -764,6 +766,8 @@ def discretize(self, method=None, alpha=0.5, scale=None): 'backward-diff', 'backward-euler' 'simpson', 'matched-Z', 'zero-pole-matching' + See also `dlti_filter`. + """ signal = self.is_signal or self.is_squared or self.is_power