diff --git a/.gitignore b/.gitignore index 9359defa9..435b7f106 100644 --- a/.gitignore +++ b/.gitignore @@ -44,4 +44,4 @@ env.bak/ venv.bak/ # Files for MacOS -.DS_Store +.DS_Store \ No newline at end of file diff --git a/control/dde.py b/control/dde.py new file mode 100644 index 000000000..cbb9b3cf4 --- /dev/null +++ b/control/dde.py @@ -0,0 +1,404 @@ +# dde.py - Delay differential equations + +"""Delay differential equations. + +This module contains a minimal implementation of +a delay differential equation (DDE) solver using the +Method of Steps (MoS) approach and scipy's solve_ivp function. +The solver is designed to handle delayed +linear time-invariant (delayLTI) systems. + +""" + +import numpy as np + +from scipy.integrate import solve_ivp, OdeSolution +from scipy.interpolate import PchipInterpolator +from typing import List + + +def dde_response( + delay_sys, T, U=0, X0=0, params=None, + transpose=False, return_x=False, squeeze=None, + t_eval=None +): + """Compute the output of a delay linear system given the input. + + Parameters + ---------- + delay_sys : DelayLTI + Delay I/O system for which forced response is computed. + T : array_like + An array representing the time points where the input is specified. + The time points must be uniformly spaced. + U : array_like or float, optional + Input array giving input at each time T. If a scalar is passed, + it is converted to an array with the same scalar value at each time. + Defaults to 0. + X0 : array_like or float, default=0. + Initial condition of the state vector. If a scalar is passed, + it is converted to an array with that scalar as the initial state. + params : dict, optional + If system is a nonlinear I/O system, set parameter values. + transpose : bool, default=False + If set to True, the input and output arrays will be transposed + to match the format used in certain legacy systems or libraries. + return_x : bool, default=None + Used if the time response data is assigned to a tuple. If False, + return only the time and output vectors. If True, also return the + the state vector. If None, determine the returned variables by + `config.defaults['forced_response.return_x']`, which was True + before version 0.9 and is False since then. + squeeze : bool, optional + By default, if a system is single-input, single-output (SISO) then + the output response is returned as a 1D array (indexed by time). + If `squeeze` is True, remove single-dimensional entries from + the shape of the output even if the system is not SISO. If + `squeeze` is False, keep the output as a 2D array (indexed by + the output number and time) even if the system is SISO. + The default behavior can be overridden by + `config.defaults['control.squeeze_time_response']`. + + Returns + ------- + resp : `TimeResponseData` + Input/output response data object. When accessed as a tuple, + returns ``(time, outputs)`` (default) or ``(time, outputs, states)`` + if `return_x` is True. + """ + from .timeresp import TimeResponseData, _check_convert_array + from .delaylti import DelayLTI + + if not isinstance(delay_sys, DelayLTI): + raise TypeError("Input must be a DelayLTI") + + n_states = delay_sys.P.A.shape[0] + n_inputs = delay_sys.P.B1.shape[1] # External inputs u + n_outputs = delay_sys.P.C1.shape[0] # External outputs y + + if U is not None: + U = np.asarray(U) + if T is not None: + T = np.asarray(T) + + T = _check_convert_array( + T, [("any",), (1, "any")], "Parameter `T`: ", + squeeze=True, transpose=transpose + ) + + n_steps = T.shape[0] + dt = (T[-1] - T[0]) / (n_steps - 1) + if not np.allclose(np.diff(T), dt): + raise ValueError("Parameter `T`: time values must be equally spaced.") + + X0 = _check_convert_array( + X0, [(n_states,), (n_states, 1)], "Parameter `X0`: ", squeeze=True + ) + + # Test if U has correct shape and type + legal_shapes = ( + [(n_steps,), (1, n_steps)] if n_inputs == 1 else [(n_inputs, n_steps)] + ) + U = _check_convert_array( + U, legal_shapes, "Parameter `U`: ", squeeze=False, transpose=transpose + ) + xout = np.zeros((n_states, n_steps)) + xout[:, 0] = X0 + yout = np.zeros((n_outputs, n_steps)) + tout = T + + if t_eval is None: + t_eval = T + xout, yout = _solve_dde(delay_sys, T, U, X0, t_eval) + + return TimeResponseData( + tout, + yout, + xout, + U, + params=params, + issiso=delay_sys.issiso(), + sysname=delay_sys.name, + plot_inputs=True, + title="Forced response for " + delay_sys.name, + trace_types=["forced"], + transpose=transpose, + return_x=return_x, + squeeze=squeeze, + ) + + +def _pchip_interp_u(T, U): + """Create PCHIP interpolator functions for the + input signal(s) U over time T. + + For time points `t < T[0]`, the interpolator returns 0. + + Parameters + ---------- + T : array_like + Time vector, 1D array. + U : array_like + Input signal(s). Can be: + - 0D array (scalar): Assumed constant input. (Note: this path might + not be hit if U is pre-processed by `_check_convert_array`). + - 1D array `(n_steps,)`: Single input signal. + - 2D array `(n_inputs, n_steps)`: Multiple input signals. + + Returns + ------- + np.ndarray of PchipInterpolator or scalar + If U is 1D or 2D, returns a 1D NumPy array of PchipInterpolator + objects, one for each input signal. + If U is a scalar (0D), returns U itself. + """ + def negative_wrapper(interp): + return lambda t: interp(t) if t >= T[0] else 0 + + if np.ndim(U) == 1: + # Single input signal, U.shape is (n_steps,) + return np.array([negative_wrapper(PchipInterpolator(T, U))]) + elif np.ndim(U) == 0: + # Scalar input, assumed constant. + return U + else: + # Multiple input signals, U.shape is (n_inputs, n_steps) + return np.array([ + negative_wrapper(PchipInterpolator(T, ui)) for ui in U + ]) + + +class _DDEHistory: + """ + Stores the computed solution history for a DDE and provides a callable + interface to retrieve the state x(t) at any requested past time t. + The history object is callable: `history(t)` returns the state vector x(t). + + Handles three regimes: + 1. t <= t0: Uses the provided initial history function. + 2. t0 < t <= t_last_computed: + Interpolates using dense output from solve_ivp segments. + 3. t > t_last_computed: Performs constant + interpolation using theextrapolation using the last computed state + (the state at `t_last_computed`). + + Attributes + ---------- + initial_history_func : callable + Function `f(t)` that returns the state vector for `t <= t0`. + t0 : float + Initial time. History before or at this time + is given by `initial_history_func`. + segments : list of OdeSolution + List of `OdeSolution` objects from `scipy.integrate.solve_ivp`, + each representing a computed segment of the solution. + last_valid_time : float + The time at the end of the most recently added solution segment. + last_state : np.ndarray + The state vector at `last_valid_time`. + """ + + def __init__(self, initial_history_func, t0): + self.initial_history_func = initial_history_func + self.t0: float = t0 + # Stores OdeResult objects from solve_ivp + self.segments: List[OdeSolution] = [] + self.last_valid_time: float = t0 + + initial_state = np.asarray(initial_history_func(t0)) + self.last_state = initial_state + + def add_segment(self, segment: OdeSolution): + """ + Adds a new computed solution segment (from solve_ivp) to the history. + + Parameters + ---------- + segment : OdeSolution + The solution object returned by `solve_ivp` for a time segment. + """ + + self.segments.append(segment) + self.last_valid_time = segment.t[-1] + self.last_state = segment.y[:, -1] + + def __call__(self, t): + """Return the state vector x(t) by looking up or + interpolating from history. + + Parameters + ---------- + t : float + Time at which to retrieve the state. + + Returns + ------- + np.ndarray + State vector x(t). + """ + if t <= self.t0: + return np.asarray(self.initial_history_func(self.t0)) + elif t > self.last_valid_time: + return self.last_state + else: + for segment in self.segments: + if segment.t[0] <= t <= segment.t[-1]: + return segment.sol(t) + # Fallback: should ideally not be reached + # if t is within (t0, last_valid_time] + # and segments cover this range. + return np.zeros_like(self.last_state) # Deal with first call + + +def _dde_wrapper(t, x, A, B1, B2, C2, D21, tau_list, u_func, history_x): + """ + Wrapper function for DDE solver using scipy's solve_ivp. + Computes the derivative dx/dt for the DDE system. + + The system is defined by: + dx/dt = A @ x(t) + B1 @ u(t) + B2 @ z_delayed_vector(t) + where: + z_delayed_vector(t) is a vector where the k-th component is + z_k(t - tau_list[k]) and + z_k(t') = (C2 @ x(t') + D21 @ u(t'))_k. + (Assuming D22 is zero for the internal feedback path). + + Parameters + ---------- + t : float + Current time. + x : np.ndarray + Current state vector, x(t). + A, B1, B2, C2, D21 : np.ndarray + State-space matrices of the underlying `PartitionedStateSpace`. + tau_list : array_like + List or array of time delays. + u_func : array_like of callable + Array of interpolating functions for the input u(t). `u_funci` + gives the i-th input signal at time t. + history_x : DdeHistory + Callable history object to retrieve past states x(t-tau). + + Returns + ------- + np.ndarray + The derivative of the state vector, dx/dt. + """ + z_delayed = [] + for i, tau in enumerate(tau_list): + u_delayed = np.array([u_func[i](t - tau) for i in range(len(u_func))]) + z = C2 @ history_x(t - tau) + D21 @ u_delayed + z_delayed.append(z[i]) + z_delayed = np.array(z_delayed).flatten() + + u_current = np.array([u_func[i](t) for i in range(len(u_func))]) + dxdt = A @ x + B1 @ u_current + B2 @ z_delayed + return dxdt.flatten() + + +def _solve_dde(delay_sys, T, U, X0, t_eval): + """ + Solving delay differential equation using Method Of Steps. + + Parameters + ---------- + delay_sys : DelayLTI + Delay I/O system for which forced response is computed. + T : array_like + An array representing the time points where the input is specified. + The time points must be uniformly spaced. + U : array_like or float, optional + Input array giving input at each time in `T`. + X0 : array_like or float, default=0. + Initial condition. + t_eval : array-list, optional + List of times at which the time response should be computed. + + Returns + ------- + xout : array_like + Array containing the state vector at each time step. + yout : array_like + Array containing the output vector at each time step. + + """ + + def initial_history_func(t): + """Initial history function for the DDE solver.""" + return np.zeros(X0.shape) + + t0, tf = T[0], T[-1] + u_func = _pchip_interp_u(T, U) + + history_x = _DDEHistory(initial_history_func, t0) # to access x(t-tau) + current_t = 0 + current_x = np.asarray(X0).flatten() + + A, B1, B2, C1, C2 = ( + delay_sys.P.A, + delay_sys.P.B1, + delay_sys.P.B2, + delay_sys.P.C1, + delay_sys.P.C2, + ) + D11, D12, D21 = ( + delay_sys.P.D11, + delay_sys.P.D12, + delay_sys.P.D21, + ) # in control systems, D22 is always 0 + tau_list = delay_sys.tau + + solution_ts = [current_t] + solution_xs = [current_x] + + # TODO: handle discontinuity propagation + discontinuity_times = set(tau_list) + while current_t < tf: + t_stop = min(discontinuity_times) if discontinuity_times else tf + if not np.isclose(t_stop, tf): + discontinuity_times.remove(t_stop) + local_t_eval = [t for t in t_eval if current_t < t <= t_stop] + + sol_segment = solve_ivp( + fun=_dde_wrapper, + t_span=(current_t, t_stop), + t_eval=local_t_eval, + y0=current_x, + method="LSODA", + dense_output=True, + args=(A, B1, B2, C2, D21, tau_list, u_func, history_x), + rtol=1e-9, + atol=1e-12, + ) + + # --- Update History and Store Results --- + history_x.add_segment(sol_segment) + segment_ts = sol_segment.t + segment_xs = sol_segment.y + + solution_ts.extend(segment_ts) + new_x = [segment_xs[:, i] for i in range(segment_xs.shape[1])] + solution_xs.extend(new_x) + + current_t = sol_segment.t[-1] + current_x = segment_xs[:, -1] + + solution_xs = np.array(solution_xs) + solution_ts = np.array(solution_ts) + + z_delayed = [] + u_current = [] + for i, ti in enumerate(solution_ts): + z_delayed.append([]) + for j, tau in enumerate(tau_list): + z = C2 @ history_x(ti - tau) + D21 @ np.array( + [u_func[i](ti - tau) for i in range(len(u_func))] + ) + z_delayed[i].append(z[j]) + u_current.append([u_func[i](ti) for i in range(len(u_func))]) + + z_delayed = np.array(z_delayed) + u_current = np.array(u_current) + + solution_ys = C1 @ solution_xs.T + D11 @ u_current.T + D12 @ z_delayed.T + return solution_xs.T, solution_ys diff --git a/control/delaylti.py b/control/delaylti.py new file mode 100644 index 000000000..50a372e35 --- /dev/null +++ b/control/delaylti.py @@ -0,0 +1,870 @@ +# delaylti.py - DelayLTI class and functions for delayed linear systems + +"""DelayLTI class and functions for delayed linear systems. + +This module contains the delayLTI class and related functions for +creating and manipulating delayed linear sytems. + +""" + +import numpy as np +from .lti import LTI +from .partitionedssp import PartitionedStateSpace +from .statesp import ss, StateSpace, tf2ss +from .xferfcn import TransferFunction +from scipy.linalg import solve, LinAlgError, inv, eigvals + + +class DelayLTI(LTI): + """Delay Linear Time Invariant (DelayLTI) class. + + The DelayLTI class is a subclass of the LTI class that represents a + linear time-invariant (LTI) system with time delays. It is designed to + handle systems where the output depends not only on the current input + but also on past inputs. + + Parameters + ---------- + P : PartitionedStateSpace + The underlying partitioned state-space representation of the system. + tau : array_like, optional + An array of time delays associated with the system. + **kwargs : keyword arguments + Additional keyword arguments for the LTI system. + + Attributes + ---------- + P : PartitionedStateSpace + The underlying partitioned state-space representation of the system. + tau : array_like + An array of time delays associated with the system. + A : array_like + The state matrix. + B : array_like + The input matrix. + C : array_like + The output matrix. + D : array_like + The direct feedthrough matrix. + B1 : array_like + The input matrix for external inputs. + B2 : array_like + The input matrix for delayed inputs. + C1 : array_like + The output matrix for external outputs. + C2 : array_like + The output matrix for delayed outputs. + D11 : array_like + The direct feedthrough matrix for external inputs to external outputs. + D12 : array_like + The direct feedthrough matrix for delayed inputs to external outputs. + D21 : array_like + The direct feedthrough matrix for external inputs to delayed outputs. + D22 : array_like + The direct feedthrough matrix for delayed inputs to delayed outputs. + ninputs : int + The number of external inputs. + noutputs : int + The number of external outputs. + nstates : int + The number of states. + + Methods + ------- + from_ss(sys, tau) + Create a DelayLTI system from a StateSpace system. + from_tf(sys, tau) + Create a DelayLTI system from a TransferFunction system. + size() + Return the number of outputs and inputs. + poles() + Compute poles of the non-delayed part of the system. + zeros() + Compute zeros of the non-delayed part of the system. + feedback(other=1, sign=-1) + Feedback interconnection between two DelayLTI systems or a DelayLTI + system and a static gain. + issiso() + Check if the system is single-input, single-output. + __call__(x, squeeze=False, warn_infinite=True) + Evaluate the system's frequency response at complex frequencies. + to_ss() + Convert to StateSpace (not implemented for DelayLTI). + to_tf() + Convert to TransferFunction (not implemented for DelayLTI). + + Notes + ----- + The way to create a DelayLTI object is by multiplying a transfer + function object with a delay(tau) or exp(-tau*s). For example + + >>> ct.tf([1], [1,1]) * delay(1.5) + + Or + + >>> s = ct.tf('s') + >>> ct.tf([1], [1,1]) * exp(-1.5*s) + + It's possible to create MIMO delayed systems from arrays + of SISO delayed systems, see function mimo_delay. + + """ + + def __init__(self, P: PartitionedStateSpace, tau=None): + """Initialize the DelayLTI object. + + Parameters + ---------- + P : PartitionedStateSpace + The underlying partitioned state-space + representation of the system. + tau : array_like, optional + An array of time delays associated with the system. + """ + if not isinstance(P, PartitionedStateSpace): + raise TypeError("Input must be a PartitionedStateSpace") + + self.P = P + self.tau = np.array([]) if tau is None else np.array(tau) + self.nu = self.P.sys.ninputs - len(self.tau) + self.ny = self.P.sys.noutputs - len(self.tau) + super().__init__(self.nu, self.ny, self.P.sys.nstates) + + @classmethod + def from_ss(cls, sys: StateSpace, tau=None): + """Create a DelayLTI system from a StateSpace system. + + Parameters + ---------- + sys : StateSpace + The underlying state-space representation of the system. + tau : array_like, optional + An array of time delays associated with the system. + + Returns + ------- + DelayLTI + The DelayLTI system. + + """ + if not isinstance(sys, StateSpace): + raise TypeError("Input must be a StateSpace") + + tau = np.array([]) if tau is None else np.array(tau) + + nu = sys.D.shape[1] - len(tau) + ny = sys.D.shape[0] - len(tau) + + if nu < 0 or ny < 0: + raise ValueError("tau is too long") + + psys = PartitionedStateSpace(sys, nu, ny) + return cls(psys, tau) + + @classmethod + def from_tf(cls, sys: TransferFunction, tau: np.ndarray = None): + """Create a DelayLTI system from a TransferFunction system. + + Parameters + ---------- + sys : TransferFunction + The underlying transfer function representation of the system. + tau : array_like, optional + An array of time delays associated with the system. + + Returns + ------- + DelayLTI + The DelayLTI system. + + """ + if not isinstance(sys, TransferFunction): + raise TypeError("Input must be a TransferFunction") + return DelayLTI.from_ss(tf2ss(sys), tau) + + def size(self): + """Return the number of outputs and inputs.""" + return (self.noutputs, self.ninputs) + + # Poles and zeros functions for DelayLTI + # are not supported by julia ControlSystems.jl + + # Might not be accurate for delayLTI, to be discussed + + # Poles and zeros computed are the ones + # of the system without taking into accoutn the delay + + def poles(self): + """Compute the poles of a delay lti system. + + Notes + ----- + This method computes the poles of the underlying LTI system (P.A) + and does not account for the time delays. The poles of a system + with delays are generally infinite in number. + """ + + return eigvals(self.P.A).astype(complex) \ + if self.nstates else np.array([]) + + def zeros(self): + """Compute the zeros of the non-delayed part of the LTI system. + + Notes + ----- + This method computes the zeros of the underlying LTI system (P.A, + P.B, P.C, P.D) and does not account for the time delays. + """ + + if not self.nstates: + return np.array([]) + + # Use AB08ND from Slycot if it's available, otherwise use + # scipy.lingalg.eigvals(). + try: + from slycot import ab08nd + + out = ab08nd( + self.P.A.shape[0], + self.P.B.shape[1], + self.P.C.shape[0], + self.P.A, + self.P.B, + self.P.C, + self.P.D, + ) + nu = out[0] + if nu == 0: + return np.array([]) + else: + # Use SciPy generalized eigenvalue function + return eigvals( + out[8][0:nu, 0:nu], out[9][0:nu, 0:nu] + ).astype(complex) + + except ImportError: # Slycot unavailable. Fall back to SciPy. + if self.P.C.shape[0] != self.P.D.shape[1]: + raise NotImplementedError( + "StateSpace.zero only supports systems with the same " + "number of inputs as outputs." + ) + + # This implements the QZ algorithm for finding transmission zeros + # from + # https://dspace.mit.edu/bitstream/handle/1721.1/841/P-0802-06587335.pdf. + # The QZ algorithm solves the generalized eigenvalue problem: given + # `L = [A, B; C, D]` and `M = [I_nxn 0]`, find all finite lambda + # for which there exist nontrivial solutions of the equation + # `Lz - lamba Mz`. + # + # The generalized eigenvalue problem is only solvable if its + # arguments are square matrices. + L = np.concatenate( + ( + np.concatenate((self.P.A, self.P.B), axis=1), + np.concatenate((self.P.C, self.P.D), axis=1), + ), + axis=0, + ) + M = np.pad( + np.eye(self.P.A.shape[0]), + ((0, self.P.C.shape[0]), (0, self.P.B.shape[1])), + "constant", + ) + return np.array([ + x for x in eigvals(L, M, overwrite_a=True) if not np.isinf(x) + ], dtype=complex) + + def _isstatic(self): + """Check if the system is static.""" + return self.nstates == 0 + + def __mul__(self, other): + """Multiply two DelayLTI systems or a DelayLTI system with a scalar. + + Parameters + ---------- + other : DelayLTI, scalar, TransferFunction, StateSpace + The other system or scalar to multiply with. + + Returns + ------- + DelayLTI + The resulting DelayLTI system. + + Raises + ------ + TypeError + If the operand type is not supported. + """ + + if isinstance(other, (int, float, complex)): + new_B = np.hstack([self.P.B1 * other, self.P.B2]) + new_D = np.block([ + [self.P.D11 * other, self.P.D12 * other], + [self.P.D21 * other, self.P.D22 * other] + ]) + + new_P = PartitionedStateSpace( + ss(self.P.A, new_B, self.P.C, new_D), + self.P.nu1, self.P.ny1 + ) + return DelayLTI(new_P, self.tau) + + elif isinstance(other, DelayLTI): + psys_new = self.P * other.P + tau_new = np.concatenate([self.tau, other.tau]) + return DelayLTI(psys_new, tau_new) + + elif isinstance(other, TransferFunction): + dlti = tf2dlti(other) + return self * dlti + + elif isinstance(other, StateSpace): + return self * DelayLTI.from_ss(other) + + else: + raise TypeError( + "Unsupported operand type(s) for *: '{}' and '{}'".format( + type(self), type(other) + ) + ) + + def __rmul__(self, other): + """ + Right multiply a DelayLTI system by a scalar or LTI system. + + Parameters + ---------- + other : scalar, TransferFunction, StateSpace + The scalar or system to multiply with. + + Returns + ------- + DelayLTI + The resulting DelayLTI system. + + Note that this function does not call __mul__ + for scalar multiplication. + + """ + if isinstance(other, (int, float, complex)): + new_C = np.vstack([self.P.C1 * other, self.P.C2]) + new_D = np.block([ + [self.P.D11 * other, self.P.D12 * other], + [self.P.D21, self.P.D22] + ]) + + new_P = PartitionedStateSpace( + ss(self.P.A, self.P.B, new_C, new_D), + self.P.nu1, self.P.ny1 + ) + return DelayLTI(new_P, self.tau) + + elif isinstance(other, TransferFunction): + dlti = tf2dlti(other) + return dlti * self + + elif isinstance(other, StateSpace): + return DelayLTI.from_ss(other) * self + + else: + raise TypeError(f"Unsupported operand type(s) for *:\ + {type(other)} and {type(self)}") + + def __add__(self, other): + """Add two DelayLTI systems or a DelayLTI system with a scalar. + + Parameters + ---------- + other : DelayLTI, scalar, TransferFunction, StateSpace + The other system or scalar to add. + + Returns + ------- + DelayLTI + The resulting DelayLTI system. + + Raises + ------ + TypeError + If the operand type is not supported. + """ + + if isinstance(other, (int, float, complex)): + new_D = self.P.sys.D.copy() + new_D[: self.ny, : self.nu] += other + pnew = PartitionedStateSpace( + ss(self.P.A, self.P.B, self.P.C, new_D), self.P.nu1, self.P.ny1 + ) + return DelayLTI(pnew, self.tau) + elif isinstance(other, DelayLTI): + psys_new = self.P + other.P + tau_new = np.concatenate([self.tau, other.tau]) + return DelayLTI(psys_new, tau_new) + else: + sys = _convert_to_delay_lti(other) + return self + sys + + def __sub__(self, other): + """Subtract two DelayLTI systems or a DelayLTI system and a scalar.""" + return self + (-other) + + def __neg__(self): + """Negate a DelayLTI system.""" + return self * -1 + + def __rsub__(self, other): + """Right subtract a DelayLTI system from a scalar or LTI system.""" + return -self + other + + def __eq__(self, other): + """Check for equality between two DelayLTI systems. + + Parameters + ---------- + other : DelayLTI + The other DelayLTI system to compare against. + + Returns + ------- + bool + True if the systems are equal, False otherwise. + + Raises + ------ + TypeError + If `other` is not a DelayLTI object. + + Notes + ----- + Two DelayLTI systems are considered equal if their underlying + PartitionedStateSpace representations are equal and their delay + vectors (`tau`) are element-wise identical. + """ + if not isinstance(other, DelayLTI): + raise TypeError(f"{other} is not a DelayLTI object,\ + is {type(other)}") + return (self.P == other.P) and (self.tau == other.tau) + + def feedback(self, other=1, sign=-1): + """Standard or LFT feedback interconnection for DelayLTI. + + If `other` is a static gain (scalar, matrix, or static LTI system), + computes the standard feedback loop: u = r + sign*other*y, y = self*u. + The resulting system maps the external input `r` and the internal + delay input `w` to the external output `y` and the internal delay + output `z`. + + If `other` is also a `DelayLTI`, computes the LFT feedback + interconnection by calling `feedback` on the underlying + `PartitionedStateSpace` objects and concatenating the delay vectors. + + Parameters + ---------- + other : scalar, array, LTI system, or DelayLTI + The system or gain in the feedback path. + sign : int, optional {-1, 1} + Sign of the feedback. Default is -1 (negative feedback). + + Returns + ------- + DelayLTI + The closed-loop system. + """ + + if isinstance(other, DelayLTI): + psys_new = self.P.feedback(other.P) + tau_new = np.concatenate([self.tau, other.tau]) + return DelayLTI(psys_new, tau_new) + + elif isinstance(other, (StateSpace, TransferFunction)): + other_delay_lti = _convert_to_delay_lti(other) + return self.feedback(other_delay_lti) + + else: + # Convert feedback 'other' to a static gain matrix K + if isinstance(other, (int, float, complex, np.number)): + if not self.issiso(): + raise ValueError("Scalar feedback gain\ + requires SISO system G.") + K = np.array([[other]], dtype=float) + elif isinstance(other, np.ndarray): + K = np.asarray(other, dtype=float) + if K.ndim == 0: + K = K.reshape(1, 1) + elif K.ndim == 1: + if self.nu != 1: + raise ValueError("1D array feedback \ + requires SISO system G.") + K = K.reshape(self.ninputs, 1) + elif K.ndim != 2: + raise ValueError("Feedback gain must \ + be scalar, 1D, or 2D array.") + else: + raise TypeError( + f"Unsupported type for static feedback: {type(other)}" + ) + + # Check dimensions of K + if K.shape != (self.nu, self.ny): + raise ValueError( + f"Feedback gain K has incompatible shape.\ + Expected ({self.nu}, {self.ny}), got {K.shape}.") + + # Get matrices from self's underlying PartitionedStateSpace + P_g = self.P + A_g, B1_g, B2_g = P_g.A, P_g.B1, P_g.B2 + C1_g, C2_g = P_g.C1, P_g.C2 + D11_g, D12_g = P_g.D11, P_g.D12 + D21_g, D22_g = P_g.D21, P_g.D22 + taus = self.tau + n_states = self.nstates + n_w = B2_g.shape[1] # Delay input dimension + n_z = C2_g.shape[0] # Delay output dimension + n_r = self.nu # Reference input dimension + + # Promote types, handle empty states + T = np.promote_types(A_g.dtype if A_g.size > 0 else float, K.dtype) + if n_states == 0: + A_g = np.zeros((0, 0), dtype=T) + + # Calculate closed-loop matrices for map [r, w] -> [y, z] + F = np.eye(self.nu, dtype=T) - sign * K @ D11_g + try: + invF_signK = solve(F, sign * K) + invF = solve(F, np.eye(self.nu, dtype=T)) + except LinAlgError: + raise ValueError("Algebraic loop; I - sign*K*D11 is singular.") + + A_new = A_g + B1_g @ invF_signK @ C1_g + B1_new = B1_g @ invF + B2_new = B2_g + B1_g @ invF_signK @ D12_g + C1_new = C1_g + D11_g @ invF_signK @ C1_g + C2_new = C2_g + D21_g @ invF_signK @ C1_g + D11_new = D11_g @ invF + D12_new = D12_g + D11_g @ invF_signK @ D12_g + D21_new = D21_g @ invF + D22_new = D22_g + D21_g @ invF_signK @ D12_g + + B_new = ( + np.hstack([B1_new, B2_new]) + if B1_new.size > 0 or B2_new.size > 0 + else np.zeros((n_states, n_r + n_w), dtype=T) + ) + C_new = ( + np.vstack([C1_new, C2_new]) + if C1_new.size > 0 or C2_new.size > 0 + else np.zeros((self.ny + n_z, n_states), dtype=T) + ) + D_new = ( + np.block([[D11_new, D12_new], [D21_new, D22_new]]) + if D11_new.size > 0 + or D12_new.size > 0 + or D21_new.size > 0 + or D22_new.size > 0 + else np.zeros((self.ny + n_z, n_r + n_w), dtype=T) + ) + + clsys_ss = StateSpace(A_new, B_new, C_new, D_new, self.dt) + clsys_part = PartitionedStateSpace(clsys_ss, nu1=n_r, ny1=self.ny) + return DelayLTI(clsys_part, taus) + + def __call__(self, x, squeeze=False, warn_infinite=True): + """Evaluate the frequency response of the system. + + Parameters + ---------- + x : array_like + Complex frequencies at which to evaluate the frequency response. + squeeze : bool, optional + If squeeze=True, access to the output response will remove + single-dimensional entries from the shape of the inputs, + outputs, and states even if the system is not SISO. If + squeeze=False, keep the input as a 2D or 3D array (indexed + by the input (if multi-input), trace (if single input) and + time) and the output and states as a 3D array (indexed by the + output/state, trace, and time) even if the system is SISO. + warn_infinite : bool, optional + If True, issue a warning if an infinite value is found in the + frequency response. + + Returns + ------- + out : array_like + Frequency response of the system. + """ + x_arr = np.atleast_1d(x).astype(complex, copy=False) + + if len(x_arr.shape) > 1: + raise ValueError("input list must be 1D") + + out = np.empty((self.ny, self.nu, len(x_arr)), dtype=complex) + + sys_call = self.P.sys( + x_arr, + squeeze=squeeze, + warn_infinite=warn_infinite + ) + for i, xi in enumerate(x_arr): + P11_fr = sys_call[: self.ny, : self.nu, i] + P12_fr = sys_call[: self.ny, self.nu:, i] + P21_fr = sys_call[self.ny:, : self.nu, i] + P22_fr = sys_call[self.ny:, self.nu:, i] + delay_term_inv = np.exp(xi * self.tau) + delay_term_fr = np.diag(delay_term_inv) + out[:, :, i] = P11_fr + \ + P12_fr @ inv(delay_term_fr - P22_fr) @ P21_fr + return out + + def __str__(self): + """Return a string representation of the DelayLTI system.""" + s = ( + f"DelayLTI with {self.noutputs} outputs, {self.ninputs} inputs, " + f"{self.nstates} states, and {len(self.tau)} delays.\n" + ) + s += f"Delays: {self.tau}\n" + s += "Underlying PartitionedStateSpace P:\n" + str(self.P) + s += "\n" + return s + + def __repr__(self): + """Return a string representation of the DelayLTI system (for eval).""" + return ( + f"{type(self).__name__}(P={self.P.__repr__()},\ + tau={self.tau.__repr__()})" + ) + + def to_ss(self, *args, **kwargs): + """Convert to `StateSpace` object (not implemented for DelayLTI).""" + raise NotImplementedError( + "Conversion of DelayLTI to StateSpace is not supported." + ) + + def to_tf(self, *args, **kwargs): + """Convert to `TransferFunction` object + (not implemented for DelayLTI). + """ + raise NotImplementedError( + "Conversion of DelayLTI to TransferFunction is not supported." + ) + + +def delay(tau): + """ + Create a pure delay system. + + Parameters + ---------- + tau : float, list, or NumPy array + The time delay(s) for the system. If a list or NumPy array is + provided, each element represents a separate delay. + + Returns + ------- + DelayLTI + A DelayLTI system representing the pure delay. + + Raises + ------ + TypeError + If tau is not a number, list, or NumPy array. + + """ + + if isinstance(tau, (int, float)): + tau_arr = [float(tau)] + elif isinstance(tau, (list, np.ndarray)): + tau_arr = [float(t) for t in tau] + else: + raise TypeError("tau must be a number, list, or NumPy array") + + D = np.array([[0, 1], [1, 0]]) + + ny, nu = D.shape[0], D.shape[1] + + A = np.zeros((0, 0)) + B = np.zeros((0, nu)) + C = np.zeros((ny, 0)) + + P = PartitionedStateSpace(ss(A, B, C, D), 1, 1) + return DelayLTI(P, tau_arr) + + +def exp(G): + """ + Create delay in the form of exp(-τ*s) where s=tf("s") + + Parameters + ---------- + G : TransferFunction + The transfer function representing the delay. + + Returns + ------- + DelayLTI + A DelayLTI system representing the pure delay. + + Raises + ------ + ValueError + If the input is not of the form -τ*s, τ>0. + """ + num = G.num[0][0] + den = G.den[0][0] + + if not (len(den) == 1 and len(num) == 2 and num[0] < 0 and num[1] == 0): + raise ValueError("Input must be of the form -τ*s, τ>0.") + + return delay(-num[0] / den[0]) + + +def tf2dlti(tf: TransferFunction): + """ + Convert a TransferFunction to a DelayLTI system with no delays. + + Parameters + ---------- + tf : TransferFunction + The transfer function to convert. + + Returns + ------- + DelayLTI + The equivalent DelayLTI system (with tau = []). + + Raises + ------ + TypeError + If `tf` is not a TransferFunction object. + """ + if not isinstance(tf, TransferFunction): + raise TypeError("Input must be a TransferFunction") + + ss_tf = tf2ss(tf) + return DelayLTI.from_ss(ss_tf) + + +def ss2dlti(ss: StateSpace): + """ + Convert a StateSpace to a DelayLTI + """ + return DelayLTI.from_ss(ss) + + +def _convert_to_delay_lti(sys): + """ + Convert a StateSpace system to a DelayLTI system with no delays. + + Parameters + ---------- + ss : StateSpace + The state-space system to convert. + + Returns + ------- + DelayLTI + The equivalent DelayLTI system (with tau = []). + + Raises + ------ + TypeError + If `ss` is not a StateSpace object. + """ + if isinstance(sys, DelayLTI): + return sys + elif isinstance(sys, StateSpace): + return DelayLTI.from_ss(sys) + elif isinstance(sys, TransferFunction): + return tf2dlti(sys) + else: + raise TypeError( + "Unsupported system type for DelayLTI \ + conversion: {}".format(type(sys)) + ) + + +def vcat(*systems: list[DelayLTI]) -> DelayLTI: + """Vertically concatenate a list of DelayLTI systems. + + Parameters + ---------- + *systems : list of DelayLTI + The systems to be concatenated. + + Returns + ------- + DelayLTI + The resulting DelayLTI system. + + Raises + ------ + TypeError + If any of the inputs are not DelayLTI systems. + """ + + from .partitionedssp import vcat_pss + + if not all(isinstance(sys, DelayLTI) for sys in systems): + raise TypeError("All inputs must be DelayLTIs") + + part_ssp = [sys.P for sys in systems] + P = vcat_pss(*part_ssp) + tau = np.concatenate([sys.tau for sys in systems]) + return DelayLTI(P, tau) + + +def hcat(*systems: list[DelayLTI]) -> DelayLTI: + """Horizontally concatenate a list of DelayLTI systems. + + Parameters + ---------- + *systems : list of DelayLTI + The systems to be concatenated. + + Returns + ------- + DelayLTI + The resulting DelayLTI system. + + Raises + ------ + TypeError + If any of the inputs are not DelayLTI systems. + """ + + from .partitionedssp import hcat_pss + + if not (all(isinstance(sys, DelayLTI) for sys in systems)): + raise TypeError("All inputs must be DelayLTIs") + + part_ssp = [sys.P for sys in systems] + P = hcat_pss(*part_ssp) + tau = np.concatenate([sys.tau for sys in systems]) + return DelayLTI(P, tau) + + +def mimo_delay(array: np.ndarray[DelayLTI]): + """Create a MIMO delay system from an array of DelayLTI systems. + + Parameters + ---------- + array : np.ndarray of DelayLTI + An array of DelayLTI systems. + + Returns + ------- + DelayLTI + The resulting DelayLTI system. + + Raises + ------ + TypeError + If any element in the array is not a DelayLTI system. + """ + + if not all(isinstance(item, DelayLTI) for row in array for item in row): + raise TypeError("All elements in the array must be DelayLTI systems") + + rows = [hcat(*row) for row in array] + return vcat(*rows) diff --git a/control/freqplot.py b/control/freqplot.py index cba975e77..dc24e3075 100644 --- a/control/freqplot.py +++ b/control/freqplot.py @@ -33,6 +33,7 @@ from .margins import stability_margins from .statesp import StateSpace from .xferfcn import TransferFunction +from .delaylti import DelayLTI __all__ = ['bode_plot', 'NyquistResponseData', 'nyquist_response', 'nyquist_plot', 'singular_values_response', @@ -350,7 +351,7 @@ def bode_plot( # If we were passed a list of systems, convert to data if any([isinstance( - sys, (StateSpace, TransferFunction)) for sys in data]): + sys, (StateSpace, TransferFunction, DelayLTI)) for sys in data]): data = frequency_response( data, omega=omega, omega_limits=omega_limits, omega_num=omega_num, Hz=Hz) diff --git a/control/iosys.py b/control/iosys.py index 29f5bfefb..d7b32d977 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -758,7 +758,12 @@ def isdtime(self, strict=False): def issiso(self): """Check to see if a system is single input, single output.""" - return self.ninputs == 1 and self.noutputs == 1 + from .delaylti import DelayLTI + if isinstance(self, DelayLTI): + # DelayLTI special case: external and internal inputs/outputs + return self.nu == 1 and self.ny == 1 + else: + return self.ninputs == 1 and self.noutputs == 1 # Test to see if a system is SISO diff --git a/control/julia/README.md b/control/julia/README.md new file mode 100644 index 000000000..c6d8f5ea6 --- /dev/null +++ b/control/julia/README.md @@ -0,0 +1,18 @@ +# Use of Julia for time delay implementation + +The implementation of continuous time delays was done by porting some functionalities of ControlSystems.jl to python. So it seemed natural to compare results from python to this library. + +The ``compute_tests.jl`` file follows the structure of the ``delay_lti_test.py`` file to produce results and plots about delay systems. Theses results are then exported to json format, and compared to python-control pure delays implementation, as a way to benchmark it. + +In order to run the ``compute_tests.jl`` file, the user should install: +- the julia REPL from https://julialang.org/downloads/ +- the ControlSystems.jl package from https://github.com/JuliaControl/ControlSystems.jl +- the JSON.jl package from https://github.com/JuliaIO/JSON.jl + +Then, the user should open a terminal: +```bash +cd /control/julia +julia compute_tests.jl +``` + +The ``utils.py`` file contains helper functions to deserialize data from json to python. \ No newline at end of file diff --git a/control/julia/compute_tests.jl b/control/julia/compute_tests.jl new file mode 100644 index 000000000..721fc8e06 --- /dev/null +++ b/control/julia/compute_tests.jl @@ -0,0 +1,258 @@ +using ControlSystems +using JSON + +# ------------------ +# Transfer functions +# ------------------ + +s = tf("s") + +simple_siso_tf = tf([1], [1, 1]) + +tf_one = tf([1], [1]) + +delay_siso_tf = 1 / (s + 1) * delay(1.5) + +delay_siso_tf2 = 3 / (2 * s + 5) * delay(0.5) + +delay_tito_tf = [1/(s+1)*exp(-0.5*s) 1/(s+2)*exp(-s); 1/(s+3)*exp(-s) 1/(s+4)*exp(-s)] + +wood_berry = [12.8/(16.7s+1)*exp(-s) -18.9/(21s+1)*exp(-3s); 6.6/(10.9s+1)*exp(-7s) -19.4/(14.4s+1)*exp(-3s)] + + +function dlti2dict(dlti) + """ + Convert a DelayLtiSystem to a dictionary for JSON serialization. + + Args: + dlti: The DelayLtiSystem to convert. + + Returns: + A dictionary representation of the DelayLtiSystem. + """ + + return Dict( + "A" => Dict( + "data" => dlti.P.A, + "dim" => size(dlti.P.A) + ), + "B" => Dict( + "data" => dlti.P.B, + "dim" => size(dlti.P.B) + ), + "C" => Dict( + "data" => dlti.P.C, + "dim" => size(dlti.P.C) + ), + "D" => Dict( + "data" => dlti.P.D, + "dim" => size(dlti.P.D) + ), + "tau" => Dict( + "data" => dlti.Tau, + "dim" => size(dlti.Tau) + ) + ) +end + + +function test_tf2dlti(tf) + """ + Convert a TransferFunction to a DelayLtiSystem and then to a dictionary. + + Args: + tf: The TransferFunction to convert. + + Returns: + A dictionary representation of the DelayLtiSystem. + """ + + dlti = DelayLtiSystem(tf) + return dlti2dict(dlti) +end + + +function test_delay_function(tau) + """ + Convert a delay to a DelayLtiSystem and then to a dictionary. + + Args: + tau: The delay to convert. + + Returns: + A dictionary representation of the DelayLtiSystem. + """ + + dlti = delay(tau) + return dlti2dict(dlti) +end + + +function test_exp_delay(tau) + """ + Convert an exponential delay to a DelayLtiSystem and then to a dictionary. + + Args: + tau: The delay to convert. + + Returns: + A dictionary representation of the DelayLtiSystem. + """ + + dlti = exp(-tau * s) + return dlti2dict(dlti) +end + +function complex_array_to_dict(arr) + """ + Convert a complex array to a dictionary for JSON serialization. + + Args: + arr: The complex array to convert. + + Returns: + A dictionary representation of the complex array. + """ + + return Dict( + "real" => real(arr), + "imag" => imag(arr) + ) +end + +function test_siso_freq_resp(tf, w) + """ + Convert a SISO frequency response to a dictionary for JSON serialization. + + Args: + tf: The TransferFunction to convert. + w: The frequency vector. + + Returns: + A dictionary representation of the frequency response. + """ + + arr = collect(Iterators.Flatten(freqresp(tf, w))) + return complex_array_to_dict(arr) +end + +function test_tito_freq_response(tf, w) + """ + Convert a TITO frequency response to a dictionary for JSON serialization. + + Args: + tf: The TransferFunction to convert. + w: The frequency vector. + + Returns: + A dictionary representation of the frequency response. + """ + + resp = freqresp(tf, w) + resp_11 = resp[1, 1, :] + resp_12 = resp[1, 2, :] + resp_21 = resp[2, 1, :] + resp_22 = resp[2, 2, :] + + return Dict( + "r11" => complex_array_to_dict(resp_11), + "r12" => complex_array_to_dict(resp_12), + "r21" => complex_array_to_dict(resp_21), + "r22" => complex_array_to_dict(resp_22), + ) +end + +function test_step_response(tf, t) + return step(tf, t).y +end + +function test_forced_response(tf, u, t, x0) + # TO BE IMPLEMENTED + #return lsim(tf, u, t, x0=x0) +end + +function main() + """ + Main function to compute and export test results. + """ + + results_TestConstructors = Dict( + "test_tf2dlti" => Dict( + "simple_siso_tf" => test_tf2dlti(simple_siso_tf), + "tf_one" => test_tf2dlti(tf_one) + ), + "test_delay_function" => Dict( + "1" => test_delay_function(1), + "1.5" => test_delay_function(1.5), + "10" => test_delay_function(10) + ), + "test_exp_delay" => Dict( + "1" => test_exp_delay(1), + "1.5" => test_exp_delay(1.5), + "10" => test_exp_delay(10) + ), + "test_siso_delay" => dlti2dict(delay_siso_tf), + "test_build_wood_berry" => dlti2dict(wood_berry) + ) + + results_TestOperators = Dict( + "test_siso_add" => dlti2dict(delay_siso_tf + delay_siso_tf2), + "test_siso_add_constant" => dlti2dict(delay_siso_tf + 2.5), + "test_siso_sub" => dlti2dict(delay_siso_tf - delay_siso_tf2), + "test_siso_sub_constant" => dlti2dict(delay_siso_tf - 2.5), + "test_siso_mul" => dlti2dict(delay_siso_tf * delay_siso_tf2), + "test_siso_mul_constant" => dlti2dict(delay_siso_tf * 2.), + "test_siso_rmul_constant" => dlti2dict(2. * delay_siso_tf), + "test_mimo_add" => dlti2dict(wood_berry + wood_berry), + "test_mimo_add_constant" => dlti2dict(wood_berry + 2.7), + "test_mimo_mul" => dlti2dict(wood_berry * wood_berry), + "test_mimo_mul_constant" => dlti2dict(wood_berry * 2.7) + ) + + results_TestDelayLtiMethods = Dict( + "test_feedback" => Dict( + "empty" => dlti2dict(feedback(delay_siso_tf, 1)), + "tf_one" => dlti2dict(feedback(delay_siso_tf, tf_one)), + "delay_siso_tf" => dlti2dict(feedback(delay_siso_tf, delay_siso_tf)) + ), + "test_mimo_feedback" => dlti2dict(feedback(wood_berry, wood_berry)), + + "test_siso_freq_resp" => test_siso_freq_resp(delay_siso_tf, exp10.(LinRange(-2,2,100))), + "test_tito_freq_response" => test_tito_freq_response(wood_berry, exp10.(LinRange(-2,2,100))), + + ) + + results_TestTimeResp = Dict( + "test_mimo_step_response" => Dict( + "y11" => test_step_response(wood_berry, 0:0.1:100)[1, :, 1], + "y12" => test_step_response(wood_berry, 0:0.1:100)[1, :, 2], + "y21" => test_step_response(wood_berry, 0:0.1:100)[2, :, 1], + "y22" => test_step_response(wood_berry, 0:0.1:100)[2, :, 2] + ), + # TO BE IMPLEMENTED + "test_mimo_forced_response" => Dict( + "y11" => test_forced_response(wood_berry, ones(100), 0:0.1:100, [0, 0])[1, :, 1], + "y12" => test_forced_response(wood_berry, ones(100), 0:0.1:100, [0, 0])[1, :, 2], + "y21" => test_forced_response(wood_berry, ones(100), 0:0.1:100, [0, 0])[2, :, 1], + "y22" => test_forced_response(wood_berry, ones(100), 0:0.1:100, [0, 0])[2, :, 2] + ) + ) + + results = Dict( + "TestConstructors" => results_TestConstructors, + "TestOperators" => results_TestOperators, + "TestDelayLtiMethods" => results_TestDelayLtiMethods, + "TestTimeResp" => results_TestTimeResp, + ) + + script_dir = @__DIR__ + output_file = joinpath(script_dir, "julia_results.json") + open(output_file, "w") do io + JSON.print(io, results, 4) + end + + println("Expected results exported to julia_results.json") +end + +# Run the main function +main() \ No newline at end of file diff --git a/control/julia/julia_results.json b/control/julia/julia_results.json new file mode 100644 index 000000000..610ddb9d4 --- /dev/null +++ b/control/julia/julia_results.json @@ -0,0 +1,8141 @@ +{ + "TestTimeResp": { + "test_mimo_step_response": { + "y12": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.3437591982978894e-6, + -0.08978739139278828, + -0.1791469027870749, + -0.26808190423866235, + -0.35659441241806067, + -0.4446864344154163, + -0.5323599677860239, + -0.6196170005956255, + -0.7064595114654756, + -0.7928894696172331, + -0.8789088349175974, + -0.9645195579227566, + -1.0497235799226154, + -1.1345228329848143, + -1.2189192399985482, + -1.302914714718151, + -1.386511161806517, + -1.469710476878274, + -1.5525145465427697, + -1.6349252484468597, + -1.716944451317466, + -1.7985740150039775, + -1.8798157905204074, + -1.9606716200873666, + -2.0411433371738488, + -2.1212327665387822, + -2.200941724272437, + -2.280272017837588, + -2.3592254461105027, + -2.4378037994217427, + -2.5160088595967323, + -2.593842399996209, + -2.6713061855563898, + -2.74840197282902, + -2.825131510021202, + -2.9014965370350168, + -2.9774987855070068, + -3.053139978847424, + -3.128421832279311, + -3.2033460528774076, + -3.2779143396068315, + -3.3521283833616375, + -3.425989867003143, + -3.4995004653980852, + -3.5726618454566084, + -3.6454756661700567, + -3.717943578648596, + -3.790067226158653, + -3.861848244160178, + -3.93328826034373, + -4.004388894667385, + -4.075151759393472, + -4.14557845912513, + -4.215670590842699, + -4.285429743939914, + -4.354857500259977, + -4.423955434131404, + -4.492725112403728, + -4.561168094483032, + -4.629285932367303, + -4.6970801706816365, + -4.764552346713253, + -4.8317039904463615, + -4.898536624596848, + -4.965051764646806, + -5.031250918878907, + -5.097135588410597, + -5.162707267228137, + -5.227967442220476, + -5.292917593212968, + -5.357559193000939, + -5.421893707383074, + -5.485922595194656, + -5.549647308340645, + -5.6130692918286025, + -5.6761899838014624, + -5.739010815570138, + -5.801533211645981, + -5.8637585897730755, + -5.9256883609603905, + -5.987323929513786, + -6.048666693067842, + -6.1097180426175575, + -6.17047936254989, + -6.230952030675156, + -6.29113741825826, + -6.351036890049799, + -6.4106518043170055, + -6.469983512874547, + -6.529033361115187, + -6.58780268804028, + -6.646292826290142, + -6.70450510217427, + -6.762440835701412, + -6.820101340609509, + -6.877487924395471, + -6.934601888344837, + -6.991444527561277, + -7.0480171309959605, + -7.104320981476789, + -7.160357355737479, + -7.216127524446511, + -7.271632752235954, + -7.326874297730127, + -7.3818534135741585, + -7.43657134646237, + -7.491029337166559, + -7.5452286205641315, + -7.599170425666103, + -7.652855975644973, + -7.7062864878624495, + -7.7594631738970605, + -7.812387239571632, + -7.865059884980622, + -7.917482304517343, + -7.969655686901035, + -8.02158121520383, + -8.073260066877571, + -8.124693413780523, + -8.175882422203937, + -8.226828252898493, + -8.277532061100631, + -8.327994996558743, + -8.37821820355924, + -8.428202820952505, + -8.477949982178718, + -8.527460815293551, + -8.576736442993758, + -8.625777982642616, + -8.674586546295284, + -8.723163240724006, + -8.771509167443206, + -8.819625422734472, + -8.867513097671415, + -8.915173278144412, + -8.962607044885214, + -9.009815473491479, + -9.056799634451131, + -9.103560593166662, + -9.150099409979278, + -9.196417140192938, + -9.242514834098298, + -9.28839353699651, + -9.334054289222943, + -9.379498126170757, + -9.424726078314395, + -9.469739171232943, + -9.514538425633377, + -9.559124857373732, + -9.603499477486112, + -9.647663292199638, + -9.691617302963245, + -9.7353625064684, + -9.778899894671708, + -9.822230454817399, + -9.865355169459711, + -9.908275016485183, + -9.950990969134805, + -9.993503996026124, + -10.035815061175173, + -10.07792512401835, + -10.119835139434164, + -10.161546057764903, + -10.203058824838159, + -10.244374381988305, + -10.285493666077812, + -10.326417609518513, + -10.367147140292742, + -10.407683181974367, + -10.448026653749746, + -10.48817847043856, + -10.528139542514564, + -10.56791077612623, + -10.607493073117292, + -10.646887331047205, + -10.686094443211482, + -10.725115298661969, + -10.763950782226992, + -10.802601774531425, + -10.84106915201666, + -10.879353786960476, + -10.917456547496824, + -10.95537829763552, + -10.993119897281812, + -11.030682202255909, + -11.06806606431236, + -11.105272331159396, + -11.142301846478132, + -11.1791554499417, + -11.215833977234302, + -11.25233826007014, + -11.2886691262123, + -11.3248273994915, + -11.360813899824784, + -11.396629443234112, + -11.432274841864855, + -11.467750904004225, + -11.5030584340996, + -11.538198232776752, + -11.573171096858026, + -11.607977819380379, + -11.64261918961339, + -11.677095993077145, + -11.711409011560043, + -11.745559023136543, + -11.77954680218478, + -11.813373119404153, + -11.847038741832785, + -11.88054443286491, + -11.91389095226821, + -11.947079056201005, + -11.980109497229442, + -12.012983024344525, + -12.045700382979113, + -12.078262315024835, + -12.110669558848883, + -12.142922849310787, + -12.175022917779062, + -12.206970492147786, + -12.238766296853125, + -12.270411052889743, + -12.301905477827162, + -12.33325028582603, + -12.364446187654305, + -12.395493890703394, + -12.42639409900417, + -12.457147513242955, + -12.4877548307774, + -12.518216745652294, + -12.548533948615317, + -12.578707127132681, + -12.608736965404741, + -12.638624144381502, + -12.668369341778048, + -12.697973232089929, + -12.72743648660844, + -12.756759773435853, + -12.785943757500567, + -12.814989100572175, + -12.843896461276485, + -12.872666495110439, + -12.901299854456992, + -12.929797188599895, + -12.958159143738422, + -12.986386363002024, + -13.014479486464909, + -13.042439151160556, + -13.07026599109617, + -13.097960637267041, + -13.125523717670875, + -13.152955857322006, + -13.180257678265596, + -13.207429799591727, + -13.234472837449436, + -13.261387405060699, + -13.288174112734318, + -13.314833567879782, + -13.341366375021028, + -13.367773135810145, + -13.39405444904103, + -13.42021091066295, + -13.446243113794072, + -13.472151648734904, + -13.497937102981677, + -13.523600061239675, + -13.549141105436483, + -13.574560814735204, + -13.599859765547567, + -13.625038531547007, + -13.650097683681679, + -13.6750377901874, + -13.69985941660053, + -13.72456312577081, + -13.749149477874106, + -13.773619030425124, + -13.79797233829005, + -13.822209953699133, + -13.846332426259202, + -13.870340302966133, + -13.894234128217253, + -13.91801444382368, + -13.941681789022613, + -13.965236700489568, + -13.988679712350523, + -14.012011356194057, + -14.035232161083387, + -14.058342653568372, + -14.081343357697452, + -14.104234795029527, + -14.127017484645792, + -14.149691943161498, + -14.172258684737677, + -14.194718221092792, + -14.21707106151434, + -14.239317712870411, + -14.261458679621171, + -14.283494463830307, + -14.305425565176416, + -14.32725248096431, + -14.348975706136338, + -14.370595733283563, + -14.392113052656963, + -14.413528152178536, + -14.434841517452359, + -14.456053631775617, + -14.477164976149536, + -14.498176029290317, + -14.519087267639973, + -14.539899165377138, + -14.560612194427831, + -14.581226824476127, + -14.601743522974845, + -14.622162755156122, + -14.642484984041964, + -14.66271067045477, + -14.682840273027741, + -14.70287424821532, + -14.722813050303516, + -14.742657131420208, + -14.762406941545418, + -14.782062928521489, + -14.801625538063252, + -14.821095213768134, + -14.840472397126216, + -14.85975752753024, + -14.878951042285577, + -14.89805337662014, + -14.91706496369426, + -14.9359862346105, + -14.954817618423425, + -14.973559542149353, + -14.992212430776018, + -15.010776707272218, + -15.0292527925974, + -15.047641105711204, + -15.06594206358297, + -15.08415608120119, + -15.102283571582912, + -15.120324945783125, + -15.138280612904051, + -15.156150980104446, + -15.173936452608821, + -15.191637433716636, + -15.209254324811441, + -15.226787525369978, + -15.244237432971241, + -15.261604443305496, + -15.278888950183243, + -15.296091345544154, + -15.31321201946596, + -15.33025136017329, + -15.347209754046487, + -15.364087585630351, + -15.38088523764288, + -15.397603090983928, + -15.41424152474386, + -15.430800916212132, + -15.44728164088586, + -15.463684072478332, + -15.480008582927471, + -15.496255542404281, + -15.51242531932124, + -15.52851828034065, + -15.544534790382949, + -15.56047521263499, + -15.576339908558282, + -15.592129237897177, + -15.607843558687026, + -15.623483227262314, + -15.639048598264715, + -15.654540024651157, + -15.669957857701814, + -15.685302447028073, + -15.700574140580464, + -15.715773284656548, + -15.730900223908767, + -15.745955301352264, + -15.760938858372661, + -15.775851234733803, + -15.790692768585446, + -15.805463796470944, + -15.820164653334876, + -15.834795672530635, + -15.84935718582799, + -15.863849523420612, + -15.878273013933553, + -15.89262798443071, + -15.906914760422234, + -15.921133665871913, + -15.935285023204512, + -15.949369153313096, + -15.9633863755663, + -15.977337007815567, + -15.991221366402362, + -16.005039766165343, + -16.0187925204475, + -16.032479941103265, + -16.04610233850557, + -16.059660021552904, + -16.0731532976763, + -16.08658247284631, + -16.099947851579962, + -16.113249736947637, + -16.126488430579965, + -16.13966423267464, + -16.152777442003252, + -16.16582835591805, + -16.178817270358675, + -16.191744479858908, + -16.20461027755329, + -16.21741495518382, + -16.230158803106555, + -16.24284211029818, + -16.25546516436258, + -16.268028251537338, + -16.280531656700266, + -16.29297566337582, + -16.305360553741554, + -16.317686608634517, + -16.329954107557615, + -16.342163328685952, + -16.35431454887314, + -16.36640804365757, + -16.378444087268676, + -16.390422952633113, + -16.40234491138101, + -16.414210233852078, + -16.426019189101748, + -16.4377720449073, + -16.449469067773897, + -16.461110522940658, + -16.47269667438665, + -16.484227784836897, + -16.495704115768326, + -16.507125927415682, + -16.518493478777454, + -16.529807027621732, + -16.541066830492063, + -16.552273142713254, + -16.56342621839717, + -16.5745263104485, + -16.585573670570497, + -16.596568549270657, + -16.607511195866437, + -16.61840185849087, + -16.629240784098233, + -16.640028218469613, + -16.650764406218496, + -16.661449590796312, + -16.672084014497955, + -16.682667918467274, + -16.693201542702546, + -16.703685126061913, + -16.714118906268805, + -16.724503119917323, + -16.734838002477606, + -16.745123788301186, + -16.755360710626267, + -16.76554900158305, + -16.775688892198975, + -16.78578061240397, + -16.79582439103566, + -16.80582045584456, + -16.815769033499237, + -16.825670349591437, + -16.835524628641227, + -16.845332094102076, + -16.85509296836591, + -16.86480747276816, + -16.87447582759279, + -16.884098252077283, + -16.893674964417624, + -16.903206181773225, + -16.91269212027188, + -16.922132995014643, + -16.9315290200807, + -16.94088040853225, + -16.95018737241933, + -16.959450122784588, + -16.96866886966812, + -16.977843822112202, + -16.98697518816603, + -16.996063174890452, + -17.00510798836266, + -17.014109833680845, + -17.02306891496887, + -17.031985435380893, + -17.040859597105964, + -17.049691601372626, + -17.058481648453455, + -17.067229937669627, + -17.07593666739542, + -17.08460203506273, + -17.093226237165517, + -17.10180946926429, + -17.110351925990525, + -17.1188538010511, + -17.127315287232655, + -17.13573657640599, + -17.144117859530407, + -17.15245932665803, + -17.160761166938137, + -17.169023568621427, + -17.1772467190643, + -17.185430804733105, + -17.193576011208368, + -17.201682523189, + -17.20975052449648, + -17.21778019807903, + -17.225771726015754, + -17.233725289520788, + -17.24164106894738, + -17.249519243792008, + -17.257359992698426, + -17.265163493461728, + -17.272929923032383, + -17.280659457520233, + -17.28835227219851, + -17.296008541507778, + -17.303628439059914, + -17.311212137642034, + -17.318759809220424, + -17.32627162494442, + -17.333747755150306, + -17.341188369365153, + -17.348593636310703, + -17.355963723907152, + -17.363298799276983, + -17.370599028748753, + -17.377864577860855, + -17.385095611365283, + -17.39229229323136, + -17.399454786649464, + -17.40658325403471, + -17.413677857030663, + -17.42073875651297, + -17.42776611259304, + -17.434760084621647, + -17.44172083119257, + -17.448648510146153, + -17.455543278572925, + -17.462405292817134, + -17.46923470848031, + -17.476031680424775, + -17.482796362777165, + -17.489528908931934, + -17.49622947155481, + -17.50289820258628, + -17.50953525324502, + -17.516140774031328, + -17.522714914730535, + -17.529257824416412, + -17.535769651454544, + -17.54225054350568, + -17.5487006475291, + -17.555120109785946, + -17.56150907584253, + -17.56786769057364, + -17.574196098165825, + -17.580494442120653, + -17.586762865258, + -17.593001509719233, + -17.59921051697049, + -17.605390027805853, + -17.611540182350545, + -17.617661120064113, + -17.6237529797436, + -17.629815899526672, + -17.635850016894764, + -17.64185546867619, + -17.647832391049256, + -17.653780919545333, + -17.659701189051948, + -17.665593333815828, + -17.671457487445945, + -17.67729378291656, + -17.683102352570224, + -17.688883328120784, + -17.69463684065637, + -17.700363020642357, + -17.706061997924348, + -17.711733901731098, + -17.717378860677446, + -17.722997002767244, + -17.728588455396235, + -17.734153345354976, + -17.739691798831682, + -17.745203941415106, + -17.750689898097384, + -17.756149793276855, + -17.7615837507609, + -17.766991893768743, + -17.772374344934246, + -17.77773122630869, + -17.78306265936353, + -17.788368764993173, + -17.7936496635177, + -17.798905474685604, + -17.8041363176765, + -17.809342311103837, + -17.81452357301757, + -17.819680220906854, + -17.824812371702702, + -17.82992014178064, + -17.83500364696333, + -17.840063002523216, + -17.845098323185137, + -17.85010972312891, + -17.855097315991944, + -17.86006121487179, + -17.865001532328726, + -17.869918380388306, + -17.874811870543894, + -17.879682113759195, + -17.884529220470778, + -17.88935330059055, + -17.8941544635083, + -17.898932818094146, + -17.903688472701, + -17.908421535167037, + -17.91313211281814, + -17.917820312470326, + -17.922486240432185, + -17.927130002507273, + -17.931751703996518, + -17.936351449700595, + -17.940929343922328, + -17.945485490469046, + -17.95001999265492, + -17.954532953303328, + -17.959024474749157, + -17.96349465884117, + -17.967943606944278, + -17.972371419941847, + -17.97677819823799, + -17.981164041759836, + -17.98552904995981, + -17.98987332181788, + -17.994196955843794, + -17.998500050079333, + -18.0027827021005, + -18.007045009019784, + -18.011287067488304, + -18.015508973698058, + -18.019710823384052, + -18.023892711826512, + -18.028054733853015, + -18.03219698384065, + -18.036319555718176, + -18.040422542968116, + -18.044506038628917, + -18.048570135297016, + -18.052614925128978, + -18.056640499843574, + -18.060646950723836, + -18.06463436861917, + -18.068602843947392, + -18.072552466696763, + -18.076483326428065, + -18.080395512276603, + -18.084289112954252, + -18.088164216751434, + -18.092020911539148, + -18.09585928477097, + -18.09967942348499, + -18.103481414305843, + -18.10726534344663, + -18.111031296710895, + -18.114779359494573, + -18.118509616787897, + -18.122222153177372, + -18.12591705284765, + -18.129594399583464, + -18.133254276771517, + -18.136896767402376, + -18.140521954072366, + -18.14412991898541, + -18.147720743954935, + -18.15129451040569, + -18.15485129937562, + -18.15839119151769, + -18.161914267101718, + -18.165420606016177, + -18.16891028777005, + -18.172383391494584, + -18.175839995945122, + -18.17928017950286, + -18.182704020176637, + -18.186111595604725, + -18.18950298305654, + -18.192878259434444, + -18.196237501275462, + -18.199580784753014, + -18.20290818567867, + -18.206219779503837, + -18.209515641321488, + -18.212795845867856, + -18.21606046752414, + -18.219309580318185, + -18.222543257926148, + -18.225761573674202, + -18.228964600540163, + -18.232152411155152, + -18.235325077805275, + -18.238482672433214, + -18.241625266639897, + -18.244752931686094, + -18.247865738494045, + -18.250963757649078, + -18.254047059401184, + -18.25711571366665, + -18.260169790029593, + -18.26320935774357, + -18.266234485733168, + -18.269245242595506, + -18.272241696601856, + -18.27522391569914, + -18.278191967511503, + -18.281145919341842, + -18.284085838173308, + -18.287011790670864, + -18.28992384318276, + -18.29282206174205, + -18.295706512068108, + -18.29857725956809, + -18.301434369338445, + -18.30427790616635, + -18.307107934531228, + -18.30992451860618, + -18.312727722259442, + -18.31551760905585, + -18.318294242258254, + -18.32105768482898, + -18.323807999431246, + -18.326545248430573, + -18.329269493896227, + -18.331980797602597, + -18.334679221030598, + -18.337364825369097, + -18.34003767151626, + -18.34269782008096, + -18.345345331384145, + -18.347980265460187, + -18.350602682058284, + -18.353212640643765, + -18.355810200399485, + -18.358395420227136, + -18.36096835874859, + -18.363529074307237, + -18.366077624969297, + -18.368614068525144, + -18.371138462490617, + -18.37365086410831, + -18.3761513303489, + -18.3786399179124, + -18.38111668322948, + -18.38358168246272, + -18.386034971507897, + -18.38847660599526, + -18.390906641290773, + -18.39332513249738, + -18.39573213445626, + -18.39812770174805, + -18.400511888694115, + -18.402884749357746, + -18.405246337545417, + -18.407596706807983, + -18.409935910441884, + -18.412264001490403, + -18.41458103274481, + -18.4168870567456, + -18.419182125783674, + -18.421466291901496, + -18.423739606894323, + -18.42600212231134, + -18.428253889456855, + -18.430494959391442, + -18.432725382933103, + -18.43494521065844, + -18.437154492903776, + -18.439353279766305, + -18.441541621105237, + -18.44371956654291, + -18.445887165465948, + -18.44804446702634, + -18.45019152014258, + -18.45232837350078, + -18.454455075555742, + -18.456571674532107, + -18.4586782184254, + -18.46077475500315, + -18.46286133180596, + -18.464937996148574, + -18.467004795120992, + -18.46906177558948, + -18.471108984197684, + -18.473146467367652, + -18.475174271300897, + -18.477192441979465, + -18.47920102516694, + -18.48120006640952, + -18.483189611037027, + -18.485169704163923, + -18.487140390690374, + -18.48910171530323, + -18.491053722477062, + -18.49299645647515, + -18.4949299613505, + -18.49685428094685, + -18.498769458899634, + -18.500675538637026, + -18.502572563380856, + -18.504460576147636, + -18.506339619749543, + -18.50820973679534, + -18.5100709696914, + -18.51192336064263, + -18.51376695165343, + -18.515601784528673, + -18.51742790087462, + -18.51924534209989, + -18.521054149416376, + -18.522854363840185, + -18.524646026192602, + -18.52642917710095, + -18.528203856999582, + -18.52997010613074, + -18.531727964545496, + -18.53347747210467, + -18.535218668479704, + -18.536951593153585, + -18.538676285421737, + -18.54039278439289, + -18.54210112899001, + -18.543801357951125, + -18.545493509830276, + -18.54717762299831, + -18.54885373564381, + -18.550521885773943, + -18.552182111215302, + -18.553834449614804, + -18.555478938440512, + -18.55711561498248, + -18.558744516353638, + -18.560365679490584, + -18.56197914115447, + -18.563584937931786, + -18.565183106235228, + -18.566773682304515, + -18.568356702207186, + -18.569932201839467, + -18.571500216927035, + -18.57306078302585, + -18.574613935522972, + -18.576159709637338, + -18.577698140420594, + -18.579229262757845, + -18.58075311136849, + -18.582269720806984, + -18.583779125463625, + -18.585281359565347, + -18.586776457176473, + -18.58826445219951, + -18.589745378375913, + -18.59121926928682, + -18.592686158353878, + -18.594146078839923, + -18.5955990638498, + -18.597045146331077, + -18.598484359074792, + -18.59991673471623, + -18.60134230573562, + -18.602761104458892, + -18.604173163058423, + -18.605578513553734, + -18.606977187812248, + -18.608369217549996, + -18.60975463433233, + -18.611133469574664, + -18.612505754543154, + -18.613871520355445, + -18.615230797981333, + -18.616583618243496, + -18.617930011818192, + -18.61927000923594, + -18.620603640882237, + -18.62193093699822, + -18.62325192768135, + -18.624566642886133, + -18.62587511242475, + -18.627177365967786, + -18.628473433044842, + -18.62976334304525, + -18.631047125218732, + -18.632324808676042, + -18.633596422389658, + -18.63486199519441, + -18.636121555788137, + -18.637375132732373, + -18.638622754452943, + -18.639864449240648, + -18.64110024525188, + -18.64233017050928, + -18.643554252902362, + -18.64477252018815, + -18.645984999991803, + -18.647191719807253, + -18.6483927069978, + -18.649587988796778, + -18.650777592308124, + -18.651961544507035, + -18.653139872240544, + -18.654312602228146, + -18.655479761062416, + -18.65664137520959, + -18.65779747101017, + -18.658948074679536, + -18.660093212308517, + -18.661232909864008, + -18.662367193189535, + -18.66349608800586, + -18.66461961991156, + -18.66573781438358, + -18.666850696777864, + -18.66795829232988, + -18.669060626155236, + -18.6701577232502, + -18.6712496084923, + -18.67233630664089, + -18.6734178423377, + -18.674494240107396, + -18.675565524358134, + -18.67663171938212, + -18.67769284935616, + -18.678748938342192, + -18.679800010287877, + -18.680846089027078, + -18.681887198280446, + -18.682923361655956, + -18.683954602649404, + -18.684980944645, + -18.68600241091584, + -18.68701902462447, + -18.688030808823395, + -18.689037786455614, + -18.690039980355134, + -18.691037413247482, + -18.69203010775022, + -18.693018086373474, + -18.69400137152043, + -18.694979985487844, + -18.695953950466556, + -18.696923288541974, + -18.697888021694602, + -18.698848171800506, + -18.699803760631855, + -18.700754809857358, + -18.701701341042796, + -18.702643375651512, + -18.703580935044865, + -18.704514040482756, + -18.70544271312407, + -18.706366974027183, + -18.707286844150442, + -18.708202344352603, + -18.709113495393364, + -18.71002031793378, + -18.71092283253675, + -18.711821059667507, + -18.712715019694045, + -18.713604732887614 + ], + "y22": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -2.011491392513816e-6, + -0.13425751525896482, + -0.2675839189151568, + -0.3999876521927742, + -0.5314751003283802, + -0.6620526043702274, + -0.791726461484058, + -0.9205029252567921, + -1.0483882059980858, + -1.1753884710398652, + -1.3015098450337221, + -1.4267584102462887, + -1.551140206852554, + -1.674661233227152, + -1.7973274462336506, + -1.9191447615117916, + -2.0401190537628184, + -2.16025615703276, + -2.279561864993782, + -2.3980419312236023, + -2.515702069482933, + -2.6325479539910654, + -2.7485852196994918, + -2.863819462563649, + -2.978256239812809, + -3.091901070218039, + -3.204759434358392, + -3.3168367748851857, + -3.428138496784478, + -3.5386699676377398, + -3.648436517880671, + -3.757443441060327, + -3.8656959940903386, + -3.973199397504468, + -4.0799588357083705, + -4.185979457229577, + -4.291266374965841, + -4.39582466643167, + -4.499659374003204, + -4.602775505161395, + -4.705178032733466, + -4.806871895132765, + -4.907861996596906, + -5.0081532074242725, + -5.107750364208899, + -5.206658270073712, + -5.304881694902169, + -5.402425375568286, + -5.499294016165072, + -5.595492288231392, + -5.69102483097725, + -5.785896251507522, + -5.880111125044133, + -5.9736739951466955, + -6.066589373931626, + -6.158861742289756, + -6.250495550102411, + -6.341495216456018, + -6.431865129855208, + -6.521609648434454, + -6.610733100168261, + -6.699239783079871, + -6.787133965448538, + -6.874419886015368, + -6.961101754187732, + -7.04718375024228, + -7.132670025526524, + -7.217564702659043, + -7.301871875728298, + -7.385595610490062, + -7.468739944563518, + -7.551308887625951, + -7.63330642160613, + -7.714736500876328, + -7.7956030524430275, + -7.875909976136314, + -7.955661144797934, + -8.034860404468066, + -8.113511574570804, + -8.191618448098335, + -8.269184791793888, + -8.346214346333356, + -8.422710826505714, + -8.498677921392149, + -8.574119294543989, + -8.649038584159353, + -8.72343940325863, + -8.797325339858702, + -8.870699957145984, + -8.943566793648264, + -9.015929363405345, + -9.08779115613851, + -9.15915563741882, + -9.23002624883424, + -9.300406408155622, + -9.370299509501505, + -9.43970892350182, + -9.508637997460434, + -9.577090055516567, + -9.64506839880512, + -9.71257630561585, + -9.779617031551481, + -9.846193809684708, + -9.912309850714104, + -9.977968343118972, + -10.0431724533131, + -10.107925325797456, + -10.172230083311858, + -10.23608982698554, + -10.299507636486734, + -10.362486570171164, + -10.425029665229552, + -10.487139937834081, + -10.548820383283852, + -10.610073976149343, + -10.670903670415843, + -10.731312399625924, + -10.791303077020904, + -10.85087859568134, + -10.910041828666557, + -10.968795629153192, + -11.027142830572792, + -11.085086246748464, + -11.142628672030561, + -11.199772881431462, + -11.256521630759377, + -11.31287765675125, + -11.368843677204755, + -11.424422391109342, + -11.479616478776423, + -11.534428601968605, + -11.58886140402807, + -11.642917510004041, + -11.696599526779396, + -11.749910043196364, + -11.802851630181383, + -11.855426840869093, + -11.907638210725437, + -11.959488257669962, + -12.010979482197238, + -12.062114367497436, + -12.112895379576097, + -12.163324967373036, + -12.213405562880467, + -12.263139581260273, + -12.312529420960477, + -12.361577463830914, + -12.410286075238094, + -12.458657604179278, + -12.506694383395754, + -12.554398729485335, + -12.601772943014083, + -12.64881930862724, + -12.695540095159435, + -12.74193755574407, + -12.788013927921995, + -12.833771433749408, + -12.879212279905017, + -12.92433865779646, + -12.969152743665987, + -13.013656698695407, + -13.057852669110305, + -13.10174278628357, + -13.145329166838147, + -13.188613912749144, + -13.231599111445176, + -13.27428683590905, + -13.316679144777732, + -13.35877808244161, + -13.40058567914311, + -13.442103951074582, + -13.483334900475558, + -13.52428051572929, + -13.564942771458638, + -13.605323628621328, + -13.645425034604475, + -13.685248923318536, + -13.724797215290554, + -13.764071817756777, + -13.80307462475465, + -13.841807517214127, + -13.880272363048416, + -13.918471017244032, + -13.956405321950264, + -13.994077106568021, + -14.03148818783804, + -14.068640369928513, + -14.105535444522094, + -14.142175190902284, + -14.178561376039273, + -14.214695754675109, + -14.250580069408363, + -14.286216050778147, + -14.321605417347559, + -14.356749875786594, + -14.39165112095441, + -14.426310835981091, + -14.460730692348818, + -14.49491234997245, + -14.528857457279607, + -14.562567651290134, + -14.596044557695084, + -14.62928979093509, + -14.662304954278222, + -14.695091639897326, + -14.727651428946784, + -14.759985891638786, + -14.792096587319048, + -14.82398506454199, + -14.85565286114546, + -14.887101504324855, + -14.918332510706792, + -14.949347386422255, + -14.980147627179196, + -15.010734718334712, + -15.041110134966635, + -15.071275341944695, + -15.101231794001164, + -15.13098093580098, + -15.160524202011466, + -15.189863017371469, + -15.218998796760106, + -15.247932945264981, + -15.276666858249937, + -15.30520192142237, + -15.333539510900032, + -15.361680993277421, + -15.389627725691664, + -15.417381055887967, + -15.44494232228463, + -15.47231285403757, + -15.49949397110443, + -15.526486984308246, + -15.553293195400634, + -15.5799138971246, + -15.606350373276852, + -15.63260389876974, + -15.658675739692725, + -15.68456715337343, + -15.710279388438295, + -15.735813684872765, + -15.761171274081121, + -15.78635337894585, + -15.811361213886602, + -15.836195984918788, + -15.860858889711714, + -15.885351117646358, + -15.909673849872725, + -15.933828259366784, + -15.957815510987082, + -15.98163676153087, + -16.005293159789932, + -16.02878584660596, + -16.052115954925576, + -16.07528460985498, + -16.098292928714187, + -16.121142021090947, + -16.143832988894218, + -16.166366926407314, + -16.188744920340707, + -16.210968049884386, + -16.233037386759946, + -16.25495399527225, + -16.276718932360748, + -16.29833324765047, + -16.319797983502628, + -16.341114175064895, + -16.362282850321325, + -16.383305030141912, + -16.404181728331846, + -16.424913951680377, + -16.4455027000094, + -16.46594896622165, + -16.48625373634858, + -16.506417989597942, + -16.52644269840097, + -16.546328828459313, + -16.566077338791587, + -16.58568918177962, + -16.605165303214402, + -16.624506642341665, + -16.643714131907213, + -16.66278869820188, + -16.681731261106204, + -16.70054273413481, + -16.719224024480425, + -16.737776033057667, + -16.756199654546478, + -16.77449577743525, + -16.79266528406371, + -16.810709050665437, + -16.828627947410148, + -16.846422838445648, + -16.86409458193948, + -16.88164403012037, + -16.89907202931926, + -16.916379420010177, + -16.933567036850736, + -16.950635708722384, + -16.967586258770414, + -16.9844195044436, + -17.001136257533684, + -17.017737324214483, + -17.034223505080774, + -17.05059559518692, + -17.066854384085183, + -17.083000655863835, + -17.099035189184942, + -17.11495875732193, + -17.130772128196874, + -17.146476064417524, + -17.162071323314102, + -17.17755865697581, + -17.192938812287075, + -17.208212530963635, + -17.22338054958824, + -17.2384435996462, + -17.253402407560692, + -17.268257694727726, + -17.283010177551, + -17.297660567476402, + -17.312209571026354, + -17.326657889833857, + -17.341006220676356, + -17.355255255509306, + -17.36940568149957, + -17.38345818105855, + -17.39741343187511, + -17.411272106948225, + -17.42503487461946, + -17.438702398605205, + -17.45227533802867, + -17.46575434745168, + -17.479140076906237, + -17.492433171925864, + -17.505634273576756, + -17.51874401848867, + -17.531763038885636, + -17.544691962616465, + -17.55753141318499, + -17.57028200978017, + -17.58294436730593, + -17.595519096410825, + -17.608006803517487, + -17.620408090851864, + -17.632723556472268, + -17.644953794298214, + -17.65709939413907, + -17.66916094172249, + -17.681139018722668, + -17.69303420278838, + -17.70484706757086, + -17.716578182751444, + -17.728228114069065, + -17.7397974233475, + -17.75128666852251, + -17.762696403668713, + -17.774027179026326, + -17.78527954102768, + -17.79645403232358, + -17.80755119180949, + -17.818571554651506, + -17.829515652312153, + -17.84038401257605, + -17.851177159575318, + -17.861895613814905, + -17.87253989219765, + -17.883110508049224, + -17.89360797114289, + -17.904032787724084, + -17.914385460534813, + -17.924666488837932, + -17.9348763684412, + -17.945015591721184, + -17.95508464764702, + -17.965084021803992, + -17.975014196416947, + -17.984875650373535, + -17.994668859247344, + -18.004394295320775, + -18.01405242760788, + -18.02364372187694, + -18.033168640672937, + -18.04262764333987, + -18.05202118604289, + -18.061349721790318, + -18.070613700455475, + -18.079813568798386, + -18.088949770487332, + -18.09802274612022, + -18.107032933245858, + -18.115980766385057, + -18.12486667705156, + -18.133691093772878, + -18.14245444211093, + -18.151157144682603, + -18.1597996211801, + -18.16838228839119, + -18.176905560219318, + -18.185369847703537, + -18.193775559038375, + -18.20212309959348, + -18.21041287193319, + -18.21864527583595, + -18.22682070831356, + -18.234939563630363, + -18.24300223332224, + -18.251009106215488, + -18.25896056844558, + -18.266857003475764, + -18.274698792115593, + -18.28248631253927, + -18.290219940303874, + -18.297900048367495, + -18.305527007107194, + -18.313101184336894, + -18.3206229453251, + -18.32809265281251, + -18.335510667029528, + -18.342877345713607, + -18.350193044126534, + -18.357458115071537, + -18.36467290891032, + -18.371837773579937, + -18.378953054609582, + -18.38601909513726, + -18.393036235926328, + -18.400004815381926, + -18.406925169567305, + -18.41379763222001, + -18.420622534768015, + -18.42740020634567, + -18.434130973809598, + -18.440815161754436, + -18.44745309252851, + -18.454045086249362, + -18.460591460819206, + -18.467092531940242, + -18.473548613129896, + -18.479960015735923, + -18.486327048951434, + -18.492650019829803, + -18.498929233299485, + -18.505164992178702, + -18.511357597190056, + -18.517507346975037, + -18.52361453810842, + -18.529679465112558, + -18.53570242047162, + -18.541683694645638, + -18.54762357608457, + -18.55352235124218, + -18.55938030458987, + -18.565197718630383, + -18.570974873911428, + -18.576712049039227, + -18.58240952069193, + -18.588067563632965, + -18.59368645072431, + -18.599266452939606, + -18.604807839377266, + -18.610310877273438, + -18.615775832014887, + -18.621202967151806, + -18.626592544410506, + -18.631944823706064, + -18.637260063154834, + -18.642538519086916, + -18.647780446058494, + -18.65298609686413, + -18.65815572254895, + -18.66328957242075, + -18.668387894062025, + -18.673450933341893, + -18.678478934427964, + -18.683472139798123, + -18.688430790252205, + -18.693355124923617, + -18.698245381290885, + -18.70310179518906, + -18.70792460082115, + -18.712714030769387, + -18.71747031600643, + -18.722193685906532, + -18.726884368256567, + -18.731542589267054, + -18.736168573583043, + -18.740762544294956, + -18.745324722949334, + -18.749855329559534, + -18.754354582616337, + -18.75882269909848, + -18.76325989448313, + -18.767666382756268, + -18.77204237642299, + -18.7763880865178, + -18.780703722614756, + -18.784989492837564, + -18.789245603869656, + -18.793472260964123, + -18.79766966795362, + -18.801838027260217, + -18.80597753990513, + -18.810088405518446, + -18.81417082234871, + -18.818224987272536, + -18.82225109580406, + -18.82624934210439, + -18.830219918990966, + -18.834163017946846, + -18.838078829129966, + -18.841967541382278, + -18.845829342238893, + -18.8496644179371, + -18.853472953425342, + -18.85725513237216, + -18.861011137175037, + -18.864741148969188, + -18.868445347636314, + -18.872123911813244, + -18.875777018900592, + -18.879404845071278, + -18.883007565279048, + -18.886585353266895, + -18.890138381575436, + -18.893666821551246, + -18.89717084335512, + -18.900650615970267, + -18.90410630721048, + -18.907538083728188, + -18.91094611102255, + -18.91433055344739, + -18.91769157421915, + -18.921029335424738, + -18.92434399802937, + -18.927635721884315, + -18.930904665734605, + -18.934150987226705, + -18.937374842916103, + -18.94057638827485, + -18.943755777699085, + -18.94691316451646, + -18.95004870099354, + -18.953162538343157, + -18.956254826731662, + -18.95932571528623, + -18.962375352102, + -18.965403884249245, + -18.968411457780444, + -18.97139821773734, + -18.97436430815794, + -18.97730987208344, + -18.98023505156514, + -18.98313998767129, + -18.986024820493896, + -18.988889689155467, + -18.991734731815736, + -18.994560085678316, + -18.99736588699732, + -19.000152271083913, + -19.002919372312878, + -19.00566732412906, + -19.008396259053818, + -19.01110630869141, + -19.013797603735334, + -19.016470273974655, + -19.019124448300232, + -19.02176025471096, + -19.024377820319927, + -19.026977271360543, + -19.029558733192648, + -19.03212233030853, + -19.034668186338955, + -19.037196424059108, + -19.039707165394514, + -19.042200531426946, + -19.04467664240023, + -19.04713561772606, + -19.049577575989762, + -19.052002634955997, + -19.054410911574447, + -19.05680252198547, + -19.059177581525674, + -19.061536204733507, + -19.063878505354747, + -19.066204596348026, + -19.06851458989025, + -19.070808597382015, + -19.07308672945299, + -19.07534909596723, + -19.077595806028505, + -19.079826967985536, + -19.082042689437227, + -19.08424307723786, + -19.086428237502236, + -19.088598275610813, + -19.09075329621477, + -19.092893403241053, + -19.09501869989741, + -19.097129288677323, + -19.099225271365007, + -19.101306749040283, + -19.103373822083448, + -19.10542659018015, + -19.107465152326153, + -19.109489606832142, + -19.111500051328452, + -19.11349658276978, + -19.115479297439865, + -19.117448290956105, + -19.119403658274212, + -19.121345493692754, + -19.123273890857718, + -19.125188942767032, + -19.12709074177504, + -19.128979379596935, + -19.13085494731324, + -19.132717535374145, + -19.1345672336039, + -19.136404131205136, + -19.138228316763147, + -19.140039878250207, + -19.14183890302978, + -19.14362547786074, + -19.145399688901563, + -19.14716162171445, + -19.148911361269505, + -19.150648991948795, + -19.152374597550445, + -19.154088261292642, + -19.155790065817676, + -19.157480093195932, + -19.159158424929824, + -19.16082514195776, + -19.16248032465799, + -19.16412405285253, + -19.165756405811003, + -19.16737746225445, + -19.16898730035914, + -19.17058599776032, + -19.172173631555964, + -19.173750278310525, + -19.17531601405857, + -19.17687091430851, + -19.178415054046173, + -19.17994850773849, + -19.181471349337027, + -19.18298365228158, + -19.184485489503732, + -19.185976933430325, + -19.187458055987, + -19.188928928601637, + -19.190389622207793, + -19.19184020724817, + -19.193280753677946, + -19.194711330968207, + -19.19613200810926, + -19.19754285361396, + -19.19894393552106, + -19.200335321398423, + -19.20171707834635, + -19.203089273000746, + -19.204451971536393, + -19.20580523967012, + -19.207149142663948, + -19.208483745328294, + -19.209809112025027, + -19.211125306670624, + -19.212432392739238, + -19.213730433265745, + -19.21501949084881, + -19.21629962765388, + -19.217570905416185, + -19.218833385443748, + -19.220087128620293, + -19.221332195408223, + -19.222568645851506, + -19.223796539578576, + -19.225015935805246, + -19.22622689333749, + -19.22742947057436, + -19.22862372551074, + -19.22980971574017, + -19.230987498457637, + -19.232157130462287, + -19.23331866816023, + -19.234472167567194, + -19.235617684311265, + -19.236755273635573, + -19.237884990400925, + -19.23900688908849, + -19.24012102380239, + -19.241227448272326, + -19.242326215856185, + -19.243417379542574, + -19.244500991953426, + -19.245577105346484, + -19.246645771617853, + -19.24770704230451, + -19.248760968586755, + -19.249807601290726, + -19.250846990890807, + -19.251879187512074, + -19.252904240932747, + -19.25392220058652, + -19.254933115565027, + -19.255937034620143, + -19.25693400616636, + -19.257924078283153, + -19.258907298717226, + -19.259883714884896, + -19.260853373874316, + -19.261816322447768, + -19.262772607043946, + -19.263722273780143, + -19.264665368454526, + -19.265601936548308, + -19.266532023227942, + -19.267455673347342, + -19.268372931449985, + -19.26928384177112, + -19.270188448239843, + -19.271086794481253, + -19.271978923818562, + -19.27286487927514, + -19.27374470357665, + -19.274618439153056, + -19.27548612814068, + -19.276347812384284, + -19.277203533439, + -19.278053332572423, + -19.278897250766544, + -19.279735328719724, + -19.280567606848713, + -19.281394125290525, + -19.282214923904444, + -19.283030042273886, + -19.283839519708337, + -19.284643395245272, + -19.285441707651973, + -19.286234495427472, + -19.287021796804353, + -19.28780364975061, + -19.28858009197151, + -19.28935116091136, + -19.290116893755354, + -19.290877327431335, + -19.2916324986116, + -19.29238244371467, + -19.29312719890701, + -19.29386680010482, + -19.29460128297574, + -19.295330682940566, + -19.296055035174998, + -19.29677437461126, + -19.297488735939883, + -19.298198153611292, + -19.2989026618375, + -19.299602294593782, + -19.300297085620276, + -19.30098706842363, + -19.301672276278616, + -19.30235274222971, + -19.303028499092743, + -19.303699579456413, + -19.304366015683925, + -19.30502783991449, + -19.305685084064912, + -19.30633777983113, + -19.30698595868971, + -19.307629651899425, + -19.30826889050269, + -19.3089037053271, + -19.309534126986936, + -19.310160185884584, + -19.310781912212065, + -19.311399335952434, + -19.312012486881255, + -19.312621394568055, + -19.313226088377696, + -19.31382659747186, + -19.314422950810386, + -19.31501517715271, + -19.31560330505926, + -19.316187362892784, + -19.316767378819776, + -19.317343380811796, + -19.317915396646814, + -19.318483453910602, + -19.319047579997992, + -19.319607802114255, + -19.320164147276387, + -19.320716642314398, + -19.321265313872654, + -19.321810188411106, + -19.322351292206616, + -19.322888651354173, + -19.323422291768196, + -19.323952239183768, + -19.324478519157857, + -19.325001157070595, + -19.325520178126446, + -19.32603560735546, + -19.326547469614486, + -19.327055789588314, + -19.327560591790956, + -19.32806190056674, + -19.328559740091524, + -19.329054134373887, + -19.329545107256227, + -19.330032682415972, + -19.33051688336667, + -19.33099773345915, + -19.331475255882662, + -19.33194947366595, + -19.332420409678424, + -19.3328880866312, + -19.333352527078226, + -19.333813753417388, + -19.33427178789155, + -19.334726652589662, + -19.3351783694478, + -19.335626960250227, + -19.336072446630475, + -19.336514850072337, + -19.336954191910948, + -19.33739049333379, + -19.337823775381707, + -19.33825405894996, + -19.338681364789174, + -19.339105713506402, + -19.339527125566068, + -19.339945621290973, + -19.340361220863297, + -19.340773944325523, + -19.341183811581466, + -19.341590842397167, + -19.341995056401892, + -19.34239647308907, + -19.342795111817214, + -19.343190991810893, + -19.343584132161606, + -19.34397455182874, + -19.34436226964049, + -19.344747304294735, + -19.345129674359978, + -19.345509398276192, + -19.345886494355753, + -19.346260980784322, + -19.346632875621683, + -19.347002196802663, + -19.347368962137953, + -19.34773318931499, + -19.348094895898832, + -19.348454099332937, + -19.348810816940095, + -19.349165065923177, + -19.349516863366013, + -19.34986622623423, + -19.35021317137601, + -19.350557715522967, + -19.350899875290917, + -19.351239667180675, + -19.35157710757889, + -19.35191221275878, + -19.352244998880977, + -19.35257548199425, + -19.352903678036306, + -19.353229602834578, + -19.35355327210694, + -19.353874701462523, + -19.354193906402404, + -19.354510902320403, + -19.354825704503813, + -19.35513832813412, + -19.355448788287767, + -19.355757099936845, + -19.356063277949833, + -19.356367337092326, + -19.356669292027725, + -19.356969157317966, + -19.3572669474242, + -19.35756267670749, + -19.357856359429555, + -19.358148009753368, + -19.35843764174393, + -19.35872526936888, + -19.3590109064992, + -19.359294566909902, + -19.35957626428063, + -19.359856012196403, + -19.360133824148193, + -19.360409713533613, + -19.360683693657585, + -19.36095577773292, + -19.361225978881023, + -19.361494310132482, + -19.361760784427688, + -19.362025414617516, + -19.36228821346388, + -19.362549193640398, + -19.36280836773296, + -19.363065748240363, + -19.363321347574924, + -19.363575178063034, + -19.363827251945814, + -19.364077581379643, + -19.364326178436777, + -19.364573055105954, + -19.3648182232929, + -19.365061694820994, + -19.36530348143177, + -19.365543594785496, + -19.36578204646177, + -19.366018847960042, + -19.36625401070019, + -19.366487546023045, + -19.36671946519096, + -19.366949779388374, + -19.367178499722282, + -19.36740563722286, + -19.367631202843913, + -19.36785520746345, + -19.368077661884225, + -19.368298576834196, + -19.36851796296712, + -19.368735830862985, + -19.368952191028576, + -19.369167053897993, + -19.36938042983308, + -19.369592329124018, + -19.36980276198975, + -19.3700117385785, + -19.370219268968285, + -19.370425363167342, + -19.370630031114686, + -19.370833282680522, + -19.371035127666755, + -19.371235575807475, + -19.371434636769376, + -19.37163232015229, + -19.371828635489592, + -19.37202359224868, + -19.37221719983145, + -19.372409467574716, + -19.372600404750703, + -19.37279002056744, + -19.372978324169242, + -19.373165324637153, + -19.373351030989358, + -19.37353545218165, + -19.373718597107825, + -19.373900474600134, + -19.374081093429723, + -19.374260462307014, + -19.37443858988217, + -19.374615484745487, + -19.37479115542779, + -19.374965610400903, + -19.375138858077996, + -19.375310906814033, + -19.375481764906144, + -19.375651440594044, + -19.375819942060442, + -19.37598727743139, + -19.37615345477673, + -19.37631848211045, + -19.37648236739106, + -19.376645118522024, + -19.37680674335209, + -19.37696724967571 + ], + "y21": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 6.234356826390706e-6, + 0.0602797291975042, + 0.12000278500607195, + 0.1791804285879041, + 0.23781764084180476, + 0.2959193571792291, + 0.353490467939701, + 0.41053581880242196, + 0.4670602111941198, + 0.5230684026931862, + 0.5785651074301074, + 0.633554996484256, + 0.6880426982770397, + 0.7420327989614764, + 0.7955298428081936, + 0.8485383325879222, + 0.9010627299504909, + 0.95310745580035, + 1.0046768906686774, + 1.0557753750820726, + 1.106407209927902, + 1.1565766568163005, + 1.2062879384388552, + 1.2555452389240382, + 1.3043527041893592, + 1.3527144422903326, + 1.4006345237662512, + 1.4481169819827884, + 1.4951658134714931, + 1.541784978266155, + 1.5879784002361228, + 1.6337499674165799, + 1.6791035323357806, + 1.7240429123393277, + 1.7685718899114542, + 1.8126942129933985, + 1.856413595298871, + 1.8997337166266197, + 1.9426582231701681, + 1.9851907278246914, + 2.027334810491119, + 2.0690940183774535, + 2.110471866297332, + 2.151471836965857, + 2.1920973812927333, + 2.2323519186727347, + 2.272238837273501, + 2.3117614943207183, + 2.3509232163806906, + 2.38972729964033, + 2.4281770101846014, + 2.4662755842714135, + 2.5040262286040185, + 2.5414321206009087, + 2.5784964086632565, + 2.6152222124399183, + 2.651612623090002, + 2.6876707035430516, + 2.7233994887568453, + 2.7588019859728425, + 2.793881174969307, + 2.828640008312104, + 2.8630814116032166, + 2.8972082837269855, + 2.931023497094105, + 2.9645298978833967, + 2.997730306281359, + 3.030627516719546, + 3.0632242981097644, + 3.095523394077128, + 3.1275275231909956, + 3.1592393791937776, + 3.1906616312276728, + 3.2217969240593183, + 3.252647878302398, + 3.2832170906382236, + 3.3135071340342845, + 3.3435205579608165, + 3.3732598886053813, + 3.402727629085496, + 3.43192625965932, + 3.4608582379344086, + 3.4895259990745706, + 3.5179319560048286, + 3.5460784996145183, + 3.5739679989585196, + 3.6016028014566586, + 3.6289852330912877, + 3.6561175986030583, + 3.683002181684913, + 3.709641245174293, + 3.736037031243603, + 3.762191761588928, + 3.788107637617036, + 3.8137868406306636, + 3.8392315320121115, + 3.8644438534051697, + 3.88942592689537, + 3.914179855188606, + 3.938707721788113, + 3.9630115911698294, + 3.9870935089561663, + 4.010955502088177, + 4.034599578996175, + 4.058027729768764, + 4.081241926320357, + 4.104244122557136, + 4.127036254541518, + 4.149620240655108, + 4.171997981760168, + 4.194171361359606, + 4.216142245755512, + 4.237912484206238, + 4.259483909082053, + 4.280858336019366, + 4.302037564073548, + 4.3230233758703545, + 4.3438175377559665, + 4.3644217999456645, + 4.38483789667114, + 4.4050675463264595, + 4.425112451612705, + 4.4449742996812835, + 4.464654762275936, + 4.484155495873445, + 4.503478141823053, + 4.52262432648462, + 4.5415956613655055, + 4.560393743256211, + 4.579020154364779, + 4.597476462449963, + 4.615764220953186, + 4.633884969129287, + 4.651840232176086, + 4.669631521362752, + 4.687260334157003, + 4.70472815435115, + 4.722036452186983, + 4.73918668447952, + 4.756180294739629, + 4.7730187132955155, + 4.789703357413125, + 4.8062356314154195, + 4.822616926800587, + 4.8388486223591585, + 4.8549320842900565, + 4.870868666315589, + 4.886659709795388, + 4.902306543839314, + 4.917810485419322, + 4.933172839480309, + 4.9483948990499504, + 4.963477945347532, + 4.978423247891788, + 4.993232064607759, + 5.00790564193266, + 5.022445214920801, + 5.036852007347535, + 5.051127231812259, + 5.06527208984049, + 5.079287771984975, + 5.093175457925915, + 5.10693631657025, + 5.120571506150043, + 5.134082174319974, + 5.147469458253925, + 5.160734484740706, + 5.17387837027889, + 5.186902221170784, + 5.199807133615551, + 5.212594193801469, + 5.225264477997363, + 5.237819052643184, + 5.250258974439772, + 5.262585290437804, + 5.274799038125909, + 5.286901245518008, + 5.298892931239829, + 5.3107751046146445, + 5.322548765748234, + 5.334214905613045, + 5.345774506131622, + 5.3572285402592374, + 5.368577972065786, + 5.379823756816942, + 5.390966841054542, + 5.4020081626762755, + 5.412948651014611, + 5.42378922691502, + 5.43453080281349, + 5.445174282813313, + 5.455720562761191, + 5.4661705303226364, + 5.476525065056676, + 5.4867850384898995, + 5.496951314189796, + 5.507024747837457, + 5.517006187299589, + 5.5268964726998675, + 5.536696436489672, + 5.54640690351813, + 5.556028691101557, + 5.565562609092248, + 5.575009459946628, + 5.584370038792816, + 5.593645133497527, + 5.602835524732406, + 5.61194198603972, + 5.620965283897474, + 5.629906177783925, + 5.638765420241498, + 5.647543756940139, + 5.656241926740068, + 5.664860661753968, + 5.673400687408613, + 5.6818627225059135, + 5.690247479283434, + 5.698555663474326, + 5.706787974366733, + 5.714945104862658, + 5.723027741536265, + 5.731036564691688, + 5.738972248420275, + 5.7468354606573335, + 5.754626863238349, + 5.7623471119546865, + 5.769996856608794, + 5.7775767410688905, + 5.7850874033231605, + 5.7925294755334535, + 5.799903584088492, + 5.807210349656592, + 5.814450387237909, + 5.821624306216193, + 5.828732710410089, + 5.835776198123949, + 5.842755362198205, + 5.849670790059254, + 5.856523063768903, + 5.86331276007337, + 5.870040450451813, + 5.876706701164445, + 5.883312073300189, + 5.889857122823899, + 5.896342400623165, + 5.902768452554669, + 5.909135819490144, + 5.915445037361885, + 5.921696637207858, + 5.927891145216409, + 5.934029082770538, + 5.940110966491796, + 5.946137308283759, + 5.952108615375111, + 5.958025390362354, + 5.96388813125209, + 5.969697331502953, + 5.975453480067138, + 5.981157061431547, + 5.986808555658582, + 5.99240843842654, + 5.9979571810696575, + 6.00345525061778, + 6.008903109835661, + 6.0143012172619335, + 6.019650027247682, + 6.024949989994701, + 6.030201551593383, + 6.035405154060258, + 6.04056123537521, + 6.045670229518327, + 6.050732566506444, + 6.055748672429329, + 6.060718969485535, + 6.065643876017963, + 6.070523806549045, + 6.075359171815653, + 6.0801503788036655, + 6.0848978307822135, + 6.089601927337637, + 6.094263064407105, + 6.0988816343119545, + 6.103458025790703, + 6.107992624031762, + 6.112485810705873, + 6.116937963998219, + 6.121349458640267, + 6.125720665941299, + 6.130051953819665, + 6.134343686833763, + 6.138596226212702, + 6.1428099298867265, + 6.1469851525173365, + 6.151122245527125, + 6.155221557129383, + 6.1592834323573795, + 6.163308213093429, + 6.16729623809765, + 6.171247843036481, + 6.175163360510937, + 6.1790431200846, + 6.182887448311364, + 6.186696668762915, + 6.190471102055962, + 6.194211065879238, + 6.197916875020217, + 6.201588841391632, + 6.205227274057716, + 6.208832479260208, + 6.212404760444152, + 6.2159444182834145, + 6.2194517507060025, + 6.222927052919144, + 6.226370617434121, + 6.229782734090905, + 6.23316369008254, + 6.236513769979323, + 6.239833255752759, + 6.243122426799279, + 6.246381559963776, + 6.249610929562887, + 6.252810807408098, + 6.255981462828617, + 6.2591231626940385, + 6.262236171436807, + 6.265320751074479, + 6.268377161231773, + 6.271405659162421, + 6.2744064997708255, + 6.277379935633504, + 6.280326217020361, + 6.283245591915746, + 6.286138306039327, + 6.2890046028667745, + 6.291844723650245, + 6.294658907438703, + 6.297447391098027, + 6.300210409330955, + 6.302948194696836, + 6.305660977631201, + 6.308348986465164, + 6.3110124474446385, + 6.313651584749379, + 6.316266620511851, + 6.318857774835924, + 6.321425265815405, + 6.323969309552388, + 6.326490120175447, + 6.32898790985766, + 6.331462888834456, + 6.333915265421328, + 6.336345246031354, + 6.338753035192576, + 6.3411388355652125, + 6.343502847958713, + 6.3458452713486695, + 6.348166302893554, + 6.350466137951317, + 6.352744970095836, + 6.355002991133195, + 6.357240391117841, + 6.359457358368576, + 6.3616540794844045, + 6.36383073936025, + 6.365987521202498, + 6.368124606544439, + 6.370242175261529, + 6.372340405586543, + 6.374419474124569, + 6.3764795558678715, + 6.378520824210626, + 6.380543450963509, + 6.382547606368163, + 6.384533459111525, + 6.386501176340015, + 6.388450923673617, + 6.390382865219817, + 6.3922971635874095, + 6.394193979900189, + 6.396073473810507, + 6.397935803512716, + 6.3997811257564825, + 6.401609595859976, + 6.403421367722948, + 6.405216593839678, + 6.406995425311817, + 6.408758011861103, + 6.410504501841959, + 6.412235042253985, + 6.413949778754325, + 6.415648855669931, + 6.417332416009715, + 6.419000601476572, + 6.420653552479324, + 6.422291408144522, + 6.423914306328169, + 6.425522383627316, + 6.427115775391561, + 6.428694615734445, + 6.430259037544729, + 6.431809172497591, + 6.433345151065705, + 6.4348671025302195, + 6.436375154991647, + 6.43786943538063, + 6.439350069468645, + 6.440817181878574, + 6.442270896095201, + 6.443711334475603, + 6.445138618259441, + 6.446552867579182, + 6.447954201470197, + 6.449342737880782, + 6.450718593682091, + 6.452081884677962, + 6.453432725614678, + 6.454771230190616, + 6.456097511065816, + 6.457411679871474, + 6.45871384721932, + 6.460004122710944, + 6.461282614947016, + 6.462549431536424, + 6.463804679105333, + 6.465048463306161, + 6.46628088882647, + 6.467502059397776, + 6.468712077804287, + 6.469911045891546, + 6.471099064575003, + 6.472276233848516, + 6.473442652792763, + 6.474598419583582, + 6.475743631500236, + 6.476878384933591, + 6.478002775394247, + 6.479116897520562, + 6.480220845086626, + 6.481314711010149, + 6.482398587360279, + 6.483472565365363, + 6.4845367354206145, + 6.485591187095726, + 6.48663600914241, + 6.487671289501861, + 6.488697115312169, + 6.489713572915645, + 6.490720747866091, + 6.491718724936007, + 6.492707588123709, + 6.49368742066042, + 6.494658305017262, + 6.495620322912203, + 6.496573555316934, + 6.497518082463678, + 6.498453983851955, + 6.499381338255265, + 6.500300223727719, + 6.501210717610616, + 6.502112896538937, + 6.503006836447811, + 6.503892612578898, + 6.504770299486729, + 6.505639971044975, + 6.506501700452659, + 6.507355560240332, + 6.508201622276168, + 6.509039957772016, + 6.509870637289394, + 6.510693730745422, + 6.511509307418717, + 6.512317435955218, + 6.513118184373963, + 6.51391162007282, + 6.514697809834148, + 6.515476819830433, + 6.516248715629848, + 6.5170135622017735, + 6.5177714239222695, + 6.518522364579482, + 6.519266447379031, + 6.520003734949316, + 6.520734289346791, + 6.521458172061193, + 6.522175444020704, + 6.522886165597095, + 6.5235903966108, + 6.524288196335949, + 6.524979623505363, + 6.5256647363154885, + 6.5263435924313065, + 6.527016248991178, + 6.527682762611657, + 6.528343189392257, + 6.528997584920166, + 6.529646004274933, + 6.530288502033102, + 6.5309251322728015, + 6.531555948578305, + 6.532181004044525, + 6.532800351281502, + 6.533414042418819, + 6.534022129109994, + 6.534624662536829, + 6.535221693413707, + 6.535813271991878, + 6.536399448063676, + 6.536980270966714, + 6.53755578958804, + 6.538126052368238, + 6.538691107305526, + 6.539251001959783, + 6.539805783456549, + 6.540355498491004, + 6.540900193331882, + 6.541439913825382, + 6.541974705399016, + 6.542504613065439, + 6.5430296814262325, + 6.5435499546756555, + 6.544065476604376, + 6.544576290603148, + 6.545082439666465, + 6.545583966396182, + 6.546080913005093, + 6.546573321320493, + 6.547061232787699, + 6.547544688473528, + 6.548023729069768, + 6.5484983948965825, + 6.54896872590593, + 6.549434761684907, + 6.549896541459089, + 6.550354104095828, + 6.550807488107525, + 6.551256731654872, + 6.551701872550066, + 6.552142948259989, + 6.552579995909362, + 6.553013052283866, + 6.553442153833247, + 6.5538673366743785, + 6.554288636594299, + 6.554706089053231, + 6.555119729187556, + 6.555529591812781, + 6.555935711426467, + 6.5563381222111285, + 6.556736858037116, + 6.557131952465456, + 6.557523438750693, + 6.557911349843675, + 6.558295718394332, + 6.558676576754424, + 6.55905395698026, + 6.5594278908354005, + 6.559798409793332, + 6.560165545040113, + 6.560529327477003, + 6.560889787723048, + 6.561246956117685, + 6.561600862723273, + 6.561951537327631, + 6.562299009446548, + 6.562643308326258, + 6.562984462945916, + 6.563322502020023, + 6.563657454000852, + 6.563989347080842, + 6.564318209194958, + 6.564644068023066, + 6.564966950992242, + 6.565286885279092, + 6.565603897812034, + 6.5659180152735654, + 6.566229264102505, + 6.566537670496232, + 6.566843260412877, + 6.567146059573514, + 6.567446093464322, + 6.567743387338724, + 6.56803796621953, + 6.568329854901029, + 6.568619077951084, + 6.568905659713187, + 6.569189624308518, + 6.56947099563798, + 6.5697497973842, + 6.570026053013531, + 6.570299785778016, + 6.570571018717356, + 6.570839774660844, + 6.571106076229291, + 6.571369945836926, + 6.57163140569328, + 6.571890477805057, + 6.572147183977995, + 6.572401545818688, + 6.572653584736418, + 6.572903321944944, + 6.573150778464289, + 6.573395975122524, + 6.573638932557504, + 6.573879671218618, + 6.574118211368499, + 6.574354573084728, + 6.574588776261541, + 6.5748208406114905, + 6.575050785667106, + 6.575278630782539, + 6.575504395135186, + 6.575728097727319, + 6.575949757387669, + 6.576169392773018, + 6.576387022369768, + 6.576602664495488, + 6.576816337300476, + 6.577028058769272, + 6.577237846722174, + 6.577445718816739, + 6.57765169254926, + 6.577855785256262, + 6.578058014115939, + 6.578258396149613, + 6.5784569482231525, + 6.578653687048405, + 6.578848629184601, + 6.579041791039744, + 6.579233188872001, + 6.579422838791054, + 6.579610756759464, + 6.579796958594026, + 6.579981459967085, + 6.580164276407865, + 6.580345423303767, + 6.5805249159016626, + 6.5807027693091955, + 6.580878998496037, + 6.581053618295151, + 6.581226643404038, + 6.581398088385973, + 6.581567967671245, + 6.581736295558348, + 6.581903086215209, + 6.582068353680355, + 6.582232111864123, + 6.582394374549804, + 6.582555155394819, + 6.582714467931869, + 6.582872325570061, + 6.583028741596054, + 6.58318372917516, + 6.583337301352466, + 6.583489471053929, + 6.583640251087453, + 6.583789654143986, + 6.58393769279857, + 6.584084379511406, + 6.584229726628916, + 6.584373746384753, + 6.584516450900866, + 6.584657852188487, + 6.58479796214916, + 6.584936792575752, + 6.585074355153414, + 6.585210661460604, + 6.585345722970025, + 6.585479551049614, + 6.585612156963496, + 6.585743551872918, + 6.58587374683721, + 6.586002752814696, + 6.586130580663623, + 6.5862572411430875, + 6.586382744913919, + 6.586507102539598, + 6.5866303244871265, + 6.586752421127919, + 6.586873402738685, + 6.586993279502268, + 6.587112061508534, + 6.587229758755194, + 6.587346381148654, + 6.587461938504864, + 6.5875764405501185, + 6.587689896921898, + 6.587802317169663, + 6.587913710755663, + 6.588024087055746, + 6.588133455360123, + 6.588241824874176, + 6.588349204719212, + 6.588455603933237, + 6.5885610314717304, + 6.588665496208372, + 6.5887690069358165, + 6.588871572366416, + 6.588973201132951, + 6.589073901789382, + 6.5891736828115315, + 6.589272552597834, + 6.58937051947002, + 6.589467591673816, + 6.589563777379661, + 6.589659084683363, + 6.589753521606809, + 6.589847096098621, + 6.5899398160348275, + 6.59003168921954, + 6.59012272338559, + 6.590212926195199, + 6.590302305240611, + 6.590390868044727, + 6.590478622061761, + 6.59056557467784, + 6.590651733211648, + 6.590737104915024, + 6.590821696973582, + 6.590905516507322, + 6.590988570571208, + 6.591070866155793, + 6.591152410187774, + 6.5912332095305946, + 6.5913132709850295, + 6.591392601289731, + 6.591471207121826, + 6.591549095097451, + 6.591626271772327, + 6.591702743642311, + 6.5917785171439265, + 6.591853598654927, + 6.591927994494816, + 6.592001710925382, + 6.59207475415124, + 6.592147130320329, + 6.592218845524455, + 6.592289905799782, + 6.592360317127352, + 6.592430085433592, + 6.592499216590794, + 6.592567716417641, + 6.5926355906796585, + 6.592702845089727, + 6.592769485308561, + 6.5928355169451685, + 6.592900945557344, + 6.592965776652119, + 6.5930300156862325, + 6.593093668066599, + 6.593156739150745, + 6.593219234247277, + 6.593281158616319, + 6.593342517469956, + 6.5934033159726795, + 6.593463559241809, + 6.593523252347942, + 6.593582400315358, + 6.593641008122457, + 6.593699080702182, + 6.593756622942412, + 6.593813639686403, + 6.593870135733172, + 6.593926115837907, + 6.593981584712382, + 6.5940365470253255, + 6.594091007402847, + 6.594144970428794, + 6.594198440645157, + 6.594251422552454, + 6.594303920610091, + 6.594355939236763, + 6.5944074828108015, + 6.59445855567055, + 6.594509162114749, + 6.5945593064028625, + 6.594608992755473, + 6.594658225354608, + 6.594707008344099, + 6.594755345829952, + 6.5948032418806575, + 6.594850700527563, + 6.594897725765195, + 6.594944321551597, + 6.594990491808677, + 6.595036240422515, + 6.595081571243713, + 6.595126488087699, + 6.5951709947350565, + 6.595215094931855, + 6.59525879238994, + 6.595302090787269, + 6.5953449937682045, + 6.595387504943829, + 6.595429627892251, + 6.595471366158897, + 6.595512723256822, + 6.595553702666992, + 6.595594307838582, + 6.595634542189281, + 6.595674409105548, + 6.5957139119429335, + 6.595753054026332, + 6.595791838650275, + 6.595830269079214, + 6.595868348547779, + 6.595906080261073, + 6.595943467394915, + 6.5959805130961255, + 6.596017220482795, + 6.596053592644528, + 6.5960896326427205, + 6.596125343510809, + 6.596160728254521, + 6.596195789852149, + 6.596230531254769, + 6.596264955386526, + 6.596299065144845, + 6.5963328634006935, + 6.596366352998829, + 6.596399536758018, + 6.596432417471297, + 6.596464997906186, + 6.596497280804931, + 6.596529268884745, + 6.596560964838012, + 6.596592371332544, + 6.596623491011775, + 6.596654326495004, + 6.596684880377617, + 6.596715155231283, + 6.596745153604199, + 6.596774878021285, + 6.5968043309843996, + 6.596833514972563, + 6.596862432442145, + 6.596891085827092, + 6.59691947753912, + 6.596947609967912, + 6.596975485481344, + 6.5970031064256505, + 6.5970304751256545, + 6.597057593884938, + 6.597084464986046, + 6.597111090690687, + 6.597137473239902, + 6.5971636148542805, + 6.597189517734121, + 6.597215184059629, + 6.597240615991112, + 6.59726581566913, + 6.597290785214711, + 6.5973155267295045, + 6.597340042295967, + 6.597364333977542, + 6.597388403818823, + 6.597412253845741, + 6.597435886065713, + 6.59745930246783, + 6.597482505023021, + 6.597505495684207, + 6.597528276386486, + 6.597550849047274, + 6.597573215566478, + 6.597595377826662, + 6.597617337693186, + 6.597639097014386, + 6.597660657621712, + 6.597682021329886, + 6.597703189937068, + 6.597724165224983, + 6.597744948959098, + 6.597765542888746, + 6.597785948747288, + 6.597806168252261, + 6.597826203105507, + 6.597846054993337, + 6.597865725586654, + 6.5978852165411, + 6.5979045294972085, + 6.597923666080518, + 6.597942627901731, + 6.597961416556837, + 6.5979800336272465, + 6.597998480679937, + 6.598016759267567, + 6.598034870928625, + 6.59805281718754, + 6.598070599554821, + 6.59808821952719, + 6.59810567858769, + 6.598122978205831, + 6.598140119837696, + 6.5981571049260666, + 6.5981739349005615, + 6.5981906111777295, + 6.598207135161195, + 6.598223508241756, + 6.598239731797507, + 6.598255807193969, + 6.598271735784181, + 6.598287518908833, + 6.598303157896368, + 6.598318654063092, + 6.598334008713302, + 6.5983492231393726, + 6.5983642986218864, + 6.5983792364297225, + 6.598394037820171, + 6.59840870403905, + 6.59842323632079, + 6.598437635888558, + 6.598451903954342, + 6.598466041719062, + 6.598480050372681, + 6.598493931094279, + 6.598507685052185, + 6.598521313404047, + 6.5985348172969385, + 6.598548197867473, + 6.598561456241866, + 6.598574593536062, + 6.598587610855807, + 6.598600509296746, + 6.598613289944531, + 6.5986259538748815, + 6.598638502153711, + 6.598650935837187, + 6.598663255971832, + 6.5986754635946205, + 6.598687559733045, + 6.598699545405226 + ], + "y11": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 7.889459835640341e-6, + 0.07642552514944899, + 0.152386937507522, + 0.22789485024732214, + 0.3029519708211862, + 0.3775609905176124, + 0.45172458455774894, + 0.5254454121913261, + 0.5987261167919986, + 0.6715693259521364, + 0.7439776515770347, + 0.8159536899785725, + 0.8875000219683085, + 0.9586192129500073, + 1.0293138130116462, + 1.0995863570168378, + 1.1694393646957257, + 1.2388753407353368, + 1.307896774869377, + 1.376506141967526, + 1.4447059021241608, + 1.512498500746573, + 1.57988636864265, + 1.6468719221080388, + 1.7134575630127795, + 1.7796456788874362, + 1.8454386430087004, + 1.9108388144844894, + 1.9758485383385378, + 2.0404701455944765, + 2.1047059533594217, + 2.1685582649070554, + 2.232029369760213, + 2.2951215437729755, + 2.3578370492122827, + 2.4201781348390297, + 2.482147035988725, + 2.543745974651629, + 2.604977159552423, + 2.6658427862294207, + 2.726345037113269, + 2.7864860816052293, + 2.846268076154948, + 2.905693164337784, + 2.96476347693167, + 3.023481131993513, + 3.081848234935142, + 3.139866878598802, + 3.1975391433321936, + 3.254867097063068, + 3.3118527953733765, + 3.368498281572975, + 3.4248055867728917, + 3.480776729958154, + 3.5364137180601856, + 3.5917185460287624, + 3.6466931969035508, + 3.701339641885208, + 3.7556598404060644, + 3.809655740200382, + 3.863329277374193, + 3.916682376474722, + 3.9697169505593948, + 4.022434901264432, + 4.074838118873034, + 4.126928482383166, + 4.178707859574924, + 4.230178107077516, + 4.281341070435826, + 4.332198584176592, + 4.3827524718741895, + 4.4330045462160115, + 4.4829566090674735, + 4.532610451536615, + 4.58196785403832, + 4.63103058635817, + 4.67980040771589, + 4.728279066828433, + 4.776468301972682, + 4.8243698410477736, + 4.871985401637066, + 4.919316691069717, + 4.966365406481904, + 5.013133234877677, + 5.059621853189454, + 5.1058329283381445, + 5.151768117292917, + 5.197429067130622, + 5.242817415094838, + 5.287934788654594, + 5.332782805562706, + 5.377363073913796, + 5.421677192201951, + 5.4657267493780335, + 5.509513324906668, + 5.553038488822862, + 5.59630380178831, + 5.639310815147348, + 5.682061070982583, + 5.724556102170193, + 5.766797432434875, + 5.808786576404493, + 5.850525039664382, + 5.892014318811334, + 5.933255901507267, + 5.974251266532556, + 6.015001883839064, + 6.055509214602849, + 6.095774711276554, + 6.135799817641493, + 6.175585968859411, + 6.215134591523952, + 6.254447103711807, + 6.29352491503356, + 6.33236942668424, + 6.370982031493555, + 6.409364113975836, + 6.447517050379681, + 6.485442208737303, + 6.523140948913585, + 6.560614622654835, + 6.597864573637262, + 6.634892137515144, + 6.671698641968733, + 6.708285406751858, + 6.744653743739238, + 6.78080495697353, + 6.816740342712085, + 6.8524611894734235, + 6.88796877808345, + 6.923264381721363, + 6.958349265965312, + 6.993224688837787, + 7.027891900850707, + 7.062352145050282, + 7.096606657061566, + 7.1306566651327685, + 7.164503390179299, + 7.198148045827537, + 7.231591838458362, + 7.264835967250391, + 7.297881624222991, + 7.330729994279017, + 7.363382255247298, + 7.39583957792487, + 7.428103126118958, + 7.460174056688702, + 7.4920535195866425, + 7.52374265789995, + 7.555242607891421, + 7.586554499040208, + 7.617679454082324, + 7.648618589050899, + 7.679373013316205, + 7.709943829625425, + 7.740332134142195, + 7.770539016485911, + 7.800565559770792, + 7.830412840644732, + 7.860081929327894, + 7.889573889651079, + 7.9188897790938855, + 7.948030648822618, + 7.976997543727976, + 8.00579150246253, + 8.034413557477952, + 8.062864735062046, + 8.091146055375544, + 8.119258532488681, + 8.147203174417562, + 8.1749809831603, + 8.202592954732948, + 8.230040079205219, + 8.257323340735969, + 8.284443717608504, + 8.311402182265649, + 8.338199701344612, + 8.36483723571166, + 8.39131574049655, + 8.4176361651268, + 8.44379945336171, + 8.469806543326223, + 8.49565836754455, + 8.521355852973603, + 8.546899921036246, + 8.572291487654327, + 8.597531463281518, + 8.622620752935969, + 8.64756025623274, + 8.672350867416084, + 8.696993475391489, + 8.721488963757567, + 8.745838210837727, + 8.77004208971167, + 8.7941014682467, + 8.818017209128834, + 8.841790169893748, + 8.865421202957513, + 8.888911155647161, + 8.912260870231076, + 8.935471183949181, + 8.958542929042975, + 8.981476932785366, + 9.004274017510323, + 9.026935000642386, + 9.049460694725953, + 9.071851907454434, + 9.0941094416992, + 9.116234095538367, + 9.138226662285433, + 9.1600879305177, + 9.181818684104558, + 9.203419702235607, + 9.22489175944857, + 9.246235625657082, + 9.267452066178292, + 9.288541841760306, + 9.309505708609468, + 9.330344418417459, + 9.351058718388277, + 9.371649351264995, + 9.392117055356433, + 9.412462564563597, + 9.432686608406009, + 9.452789912047868, + 9.472773196324042, + 9.492637177765923, + 9.51238256862712, + 9.532010076908985, + 9.55152040638602, + 9.570914256631092, + 9.59019232304053, + 9.609355296859054, + 9.628403865204563, + 9.647338711092772, + 9.666160513461701, + 9.684869947196022, + 9.70346768315126, + 9.721954388177833, + 9.740330725144991, + 9.758597352964552, + 9.776754926614553, + 9.794804097162723, + 9.812745511789831, + 9.830579813812895, + 9.848307642708239, + 9.865929634134433, + 9.883446419955089, + 9.900858628261496, + 9.918166883395166, + 9.935371805970204, + 9.952474012895573, + 9.969474117397205, + 9.98637272903999, + 10.003170453749643, + 10.019867893834414, + 10.036465648006699, + 10.052964311404503, + 10.069364475612774, + 10.085666728684627, + 10.101871655162412, + 10.117979836098696, + 10.133991849077082, + 10.149908268232917, + 10.165729664273893, + 10.181456604500497, + 10.197089652826357, + 10.212629369798467, + 10.228076312617276, + 10.24343103515668, + 10.258694087983866, + 10.27386601837907, + 10.288947370355194, + 10.303938684677302, + 10.31884049888203, + 10.333653347296844, + 10.348377761059204, + 10.363014268135617, + 10.377563393340546, + 10.392025658355262, + 10.40640158174651, + 10.42069167898514, + 10.434896462464565, + 10.449016441519142, + 10.46305212244244, + 10.477004008505379, + 10.490872599974296, + 10.50465839412887, + 10.518361885279946, + 10.531983564787282, + 10.545523921077143, + 10.558983439659828, + 10.57236260314708, + 10.58566189126938, + 10.598881780893162, + 10.612022746037894, + 10.625085257893096, + 10.63806978483522, + 10.650976792444448, + 10.663806743521393, + 10.676560098103678, + 10.68923731348245, + 10.701838844218761, + 10.714365142159878, + 10.72681665645548, + 10.73919383357376, + 10.751497117317438, + 10.763726948839683, + 10.775883766659904, + 10.78796800667951, + 10.799980102197505, + 10.811920483926052, + 10.823789580005904, + 10.835587816021748, + 10.847315615017491, + 10.858973397511397, + 10.870561581511188, + 10.882080582529028, + 10.893530813596406, + 10.904912685278978, + 10.916226605691248, + 10.92747298051124, + 10.938652212995017, + 10.949764703991155, + 10.960810851955108, + 10.971791052963502, + 10.98270570072833, + 10.993555186611074, + 11.00433989963674, + 11.015060226507797, + 11.025716551618052, + 11.036309257066433, + 11.046838722670685, + 11.057305325980993, + 11.067709442293514, + 11.07805144466384, + 11.088331703920373, + 11.098550588677618, + 11.108708465349409, + 11.118805698162031, + 11.1288426491673, + 11.138819678255528, + 11.148737143168438, + 11.158595399511992, + 11.168394800769127, + 11.178135698312447, + 11.18781844141682, + 11.197443377271885, + 11.207010850994529, + 11.216521205641229, + 11.225974782220382, + 11.235371919704512, + 11.244712955042441, + 11.253998223171356, + 11.263228057028824, + 11.272402787564733, + 11.281522743753161, + 11.290588252604161, + 11.299599639175497, + 11.308557226584291, + 11.317461336018619, + 11.326312286749017, + 11.335110396139939, + 11.343855979661129, + 11.352549350898931, + 11.361190821567542, + 11.369780701520185, + 11.378319298760214, + 11.386806919452173, + 11.395243867932747, + 11.403630446721703, + 11.411966956532723, + 11.420253696284185, + 11.428490963109892, + 11.43667905236971, + 11.444818257660176, + 11.452908870825013, + 11.460951181965596, + 11.468945479451367, + 11.476892049930152, + 11.484791178338458, + 11.492643147911684, + 11.500448240194274, + 11.50820673504982, + 11.515918910671083, + 11.523585043589982, + 11.531205408687502, + 11.538780279203557, + 11.54630992674678, + 11.55379462130426, + 11.561234631251232, + 11.568630223360698, + 11.575981662812987, + 11.583289213205267, + 11.590553136560997, + 11.597773693339319, + 11.604951142444401, + 11.61208574123472, + 11.619177745532292, + 11.626227409631833, + 11.633234986309896, + 11.640200726833918, + 11.647124880971239, + 11.654007696998057, + 11.660849421708324, + 11.667650300422602, + 11.674410576996857, + 11.681130493831205, + 11.6878102918786, + 11.694450210653468, + 11.701050488240314, + 11.707611361302241, + 11.714133065089444, + 11.720615833447642, + 11.727059898826464, + 11.733465492287786, + 11.739832843514012, + 11.746162180816317, + 11.752453731142822, + 11.758707720086738, + 11.764924371894459, + 11.771103909473599, + 11.777246554400978, + 11.783352526930582, + 11.789422046001441, + 11.7954553292455, + 11.801452592995409, + 11.807414052292282, + 11.813339920893412, + 11.819230411279932, + 11.82508573466443, + 11.830906100998536, + 11.836691718980436, + 11.842442796062363, + 11.848159538458024, + 11.853842151150014, + 11.859490837897152, + 11.865105801241787, + 11.87068724251707, + 11.876235361854155, + 11.881750358189398, + 11.887232429271474, + 11.892681771668473, + 11.898098580774949, + 11.903483050818922, + 11.908835374868845, + 11.914155744840532, + 11.919444351504028, + 11.924701384490463, + 11.929927032298835, + 11.935121482302785, + 11.940284920757309, + 11.94541753280543, + 11.950519502484854, + 11.95559101273454, + 11.960632245401298, + 11.965643381246272, + 11.970624599951448, + 11.975576080126087, + 11.980497999313119, + 11.98539053399553, + 11.990253859602673, + 11.99508815051657, + 11.99989358007816, + 12.004670320593505, + 12.009418543339983, + 12.014138418572426, + 12.018830115529218, + 12.023493802438372, + 12.028129646523551, + 12.032737814010076, + 12.03731847013088, + 12.041871779132434, + 12.046397904280635, + 12.05089700786666, + 12.055369251212785, + 12.059814794678179, + 12.064233797664636, + 12.06862641862231, + 12.072992815055375, + 12.077333143527692, + 12.081647559668417, + 12.085936218177572, + 12.09019927283161, + 12.094436876488905, + 12.098649181095256, + 12.102836337689324, + 12.106998496408048, + 12.111135806492033, + 12.11524841629089, + 12.119336473268572, + 12.123400124008647, + 12.127439514219565, + 12.131454788739875, + 12.135446091543416, + 12.139413565744492, + 12.14335735360299, + 12.147277596529491, + 12.151174435090338, + 12.155048009012663, + 12.158898457189421, + 12.162725917684355, + 12.166530527736944, + 12.170312423767335, + 12.174071741381226, + 12.177808615374733, + 12.181523179739218, + 12.1852155676661, + 12.188885911551631, + 12.19253434300163, + 12.19616099283622, + 12.199765991094509, + 12.203349467039253, + 12.206911549161498, + 12.210452365185173, + 12.21397204207168, + 12.21747070602445, + 12.220948482493458, + 12.224405496179735, + 12.227841871039812, + 12.231257730290205, + 12.234653196411793, + 12.238028391154243, + 12.241383435540351, + 12.24471844987039, + 12.248033553726431, + 12.251328865976623, + 12.254604504779453, + 12.25786058758799, + 12.261097231154087, + 12.26431455153258, + 12.267512664085437, + 12.270691683485905, + 12.273851723722615, + 12.276992898103666, + 12.280115319260698, + 12.283219099152925, + 12.286304349071147, + 12.289371179641746, + 12.292419700830647, + 12.295450021947268, + 12.29846225164843, + 12.301456497942267, + 12.304432868192082, + 12.307391469120207, + 12.31033240681183, + 12.313255786718793, + 12.316161713663382, + 12.319050291842077, + 12.321921624829288, + 12.324775815581077, + 12.327612966438842, + 12.33043317913299, + 12.333236554786593, + 12.336023193918983, + 12.3387931964494, + 12.341546661700544, + 12.344283688402147, + 12.347004374694512, + 12.349708818132026, + 12.352397115686669, + 12.355069363751486, + 12.357725658144041, + 12.360366094109859, + 12.362990766325824, + 12.365599768903598, + 12.368193195392985, + 12.370771138785276, + 12.3733336915166, + 12.375880945471222, + 12.378412991984852, + 12.380929921847914, + 12.383431825308797, + 12.385918792077103, + 12.388390911326841, + 12.390848271699651, + 12.393290961307974, + 12.395719067738199, + 12.39813267805382, + 12.400531878798539, + 12.402916755999398, + 12.40528739516983, + 12.407643881312753, + 12.409986298923602, + 12.412314731993359, + 12.414629264011575, + 12.416929977969357, + 12.41921695636234, + 12.421490281193654, + 12.423750033976853, + 12.42599629573885, + 12.428229147022817, + 12.430448667891072, + 12.432654937927948, + 12.434848036242649, + 12.437028041472086, + 12.439195031783704, + 12.44134908487827, + 12.443490277992678, + 12.445618687902693, + 12.447734390925724, + 12.449837462923561, + 12.451927979305085, + 12.454006015028977, + 12.4560716446064, + 12.458124942103682, + 12.46016598114496, + 12.46219483491483, + 12.46421157616097, + 12.46621627719673, + 12.46820900990375, + 12.47018984573453, + 12.472158855714985, + 12.474116110446996, + 12.476061680110938, + 12.477995634468206, + 12.479918042863709, + 12.481828974228355, + 12.48372849708153, + 12.485616679533544, + 12.487493589288082, + 12.48935929364463, + 12.49121385950089, + 12.493057353355177, + 12.49488984130879, + 12.496711389068407, + 12.498522061948425, + 12.500321924873301, + 12.502111042379896, + 12.503889478619758, + 12.505657297361465, + 12.507414561992869, + 12.5091613355234, + 12.51089768058631, + 12.512623659440925, + 12.514339333974862, + 12.516044765706273, + 12.517740015786039, + 12.519425144999962, + 12.521100213770938, + 12.522765282161128, + 12.524420409874129, + 12.526065656257082, + 12.527701080302837, + 12.52932674065203, + 12.530942695595217, + 12.53254900307495, + 12.534145720687844, + 12.535732905686677, + 12.537310614982399, + 12.53887890514621, + 12.540437832411564, + 12.541987452676192, + 12.543527821504119, + 12.545058994127627, + 12.546581025449278, + 12.548093970043839, + 12.549597882160263, + 12.55109281572364, + 12.552578824337104, + 12.554055961283787, + 12.555524279528703, + 12.556983831720654, + 12.55843467019414, + 12.559876846971198, + 12.561310413763298, + 12.562735421973182, + 12.564151922696704, + 12.565559966724688, + 12.566959604544708, + 12.56835088634294, + 12.569733862005927, + 12.571108581122383, + 12.572475092984988, + 12.573833446592115, + 12.575183690649633, + 12.576525873572612, + 12.577860043487085, + 12.579186248231771, + 12.580504535359772, + 12.581814952140313, + 12.583117545560393, + 12.584412362326503, + 12.585699448866297, + 12.586978851330235, + 12.588250615593264, + 12.589514787256448, + 12.590771411648602, + 12.592020533827931, + 12.593262198583625, + 12.594496450437493, + 12.595723333645529, + 12.596942892199513, + 12.598155169828601, + 12.599360210000865, + 12.600558055924878, + 12.601748750551247, + 12.602932336574154, + 12.604108856432905, + 12.605278352313421, + 12.606440866149782, + 12.607596439625707, + 12.608745114176056, + 12.609886930988328, + 12.611021931004114, + 12.612150154920597, + 12.61327164319197, + 12.614386436030921, + 12.615494573410068, + 12.616596095063372, + 12.617691040487594, + 12.618779448943682, + 12.619861359458195, + 12.620936810824704, + 12.622005841605164, + 12.623068490131322, + 12.624124794506077, + 12.62517479260484, + 12.626218522076915, + 12.627256020346822, + 12.628287324615663, + 12.629312471862436, + 12.630331498845367, + 12.631344442103245, + 12.6323513379567, + 12.633352222509538, + 12.634347131650014, + 12.635336101052115, + 12.636319166176873, + 12.637296362273592, + 12.638267724381148, + 12.639233287329215, + 12.640193085739542, + 12.641147154027179, + 12.642095526401702, + 12.643038236868477, + 12.643975319229826, + 12.64490680708628, + 12.645832733837773, + 12.646753132684827, + 12.647668036629765, + 12.648577478477872, + 12.649481490838577, + 12.650380106126642, + 12.651273356563296, + 12.652161274177415, + 12.653043890806645, + 12.653921238098555, + 12.654793347511795, + 12.65566025031718, + 12.65652197759885, + 12.657378560255356, + 12.658230029000789, + 12.659076414365876, + 12.659917746699055, + 12.660754056167603, + 12.661585372758672, + 12.662411726280402, + 12.663233146362968, + 12.664049662459648, + 12.66486130384789, + 12.665668099630338, + 12.666470078735891, + 12.667267269920755, + 12.668059701769435, + 12.668847402695803, + 12.669630400944087, + 12.67040872458988, + 12.671182401541191, + 12.671951459539383, + 12.672715926160224, + 12.673475828814837, + 12.674231194750694, + 12.67498205105261, + 12.675728424643687, + 12.676470342286304, + 12.677207830583058, + 12.67794091597772, + 12.678669624756202, + 12.67939398304747, + 12.680114016824513, + 12.680829751905247, + 12.681541213953448, + 12.682248428479692, + 12.682951420842231, + 12.683650216247951, + 12.684344839753228, + 12.685035316264855, + 12.685721670540937, + 12.686403927191751, + 12.68708211068067, + 12.687756245325001, + 12.688426355296874, + 12.689092464624121, + 12.689754597191106, + 12.690412776739622, + 12.691067026869705, + 12.691717371040493, + 12.692363832571086, + 12.69300643464135, + 12.693645200292776, + 12.694280152429286, + 12.69491131381806, + 12.695538707090371, + 12.69616235474236, + 12.69678227913588, + 12.697398502499272, + 12.698011046928166, + 12.698619934386294, + 12.699225186706242, + 12.699826825590273, + 12.700424872611071, + 12.701019349212528, + 12.701610276710523, + 12.702197676293661, + 12.70278156902407, + 12.703361975838114, + 12.703938917547168, + 12.704512414838375, + 12.705082488275353, + 12.70564915829897, + 12.706212445228045, + 12.706772369260097, + 12.70732895047207, + 12.70788220882103, + 12.708432164144913, + 12.708978836163213, + 12.709522244477691, + 12.710062408573094, + 12.710599347817828, + 12.711133081464682, + 12.711663628651499, + 12.712191008401858, + 12.712715239625776, + 12.713236341120364, + 12.713754331570525, + 12.7142692295496, + 12.71478105352004, + 12.715289821834094, + 12.715795552734424, + 12.716298264354794, + 12.716797974720702, + 12.717294701750022, + 12.71778846325368, + 12.718279276936242, + 12.718767160396595, + 12.71925213112855, + 12.719734206521474, + 12.72021340386093, + 12.720689740329265, + 12.721163233006264, + 12.721633898869735, + 12.722101754796117, + 12.722566817561114, + 12.723029103840261, + 12.723488630209548, + 12.723945413146, + 12.724399469028263, + 12.72485081413722, + 12.725299464656533, + 12.725745436673268, + 12.726188746178433, + 12.726629409067565, + 12.72706744114132, + 12.727502858106007, + 12.727935675574171, + 12.72836590906515, + 12.728793574005623, + 12.72921868573018, + 12.72964125948185, + 12.73006131041267, + 12.73047885358421, + 12.73089390396812, + 12.731306476446674, + 12.731716585813286, + 12.732124246773068, + 12.732529473943327, + 12.7329322818541, + 12.733332684948694, + 12.733730697584168, + 12.734126334031886, + 12.734519608477997, + 12.73491053502396, + 12.735299127687053, + 12.735685400400856, + 12.736069367015777, + 12.73645104129953, + 12.736830436937625, + 12.737207567533888, + 12.737582446610906, + 12.737955087610555, + 12.738325503894444, + 12.738693708744417, + 12.739059715363032, + 12.73942353687401, + 12.739785186322743, + 12.740144676676723, + 12.740502020826021, + 12.740857231583774, + 12.741210321686602, + 12.741561303795098, + 12.741910190494263, + 12.742256994293962, + 12.74260172762939, + 12.742944402861477, + 12.743285032277383, + 12.743623628090894, + 12.74396020244288, + 12.744294767401739, + 12.744627334963807, + 12.74495791705381, + 12.745286525525275, + 12.745613172160962, + 12.745937868673291, + 12.746260626704752, + 12.746581457828336, + 12.746900373547936, + 12.747217385298754, + 12.747532504447749, + 12.747845742293993, + 12.748157110069117, + 12.74846661893769, + 12.74877427999762, + 12.74908010428058, + 12.749384102752362, + 12.749686286313304, + 12.749986665798662, + 12.750285251978996, + 12.750582055560583, + 12.750877087185764, + 12.75117035743336, + 12.751461876819022, + 12.751751655795623, + 12.75203970475364, + 12.752326034021504, + 12.752610653865998, + 12.752893574492605, + 12.753174806045871, + 12.753454358609794, + 12.753732242208155, + 12.754008466804907, + 12.7542830423045, + 12.754555978552258, + 12.75482728533474, + 12.755096972380056, + 12.75536504935826, + 12.755631525881663, + 12.755896411505185, + 12.756159715726712, + 12.756421447987417, + 12.756681617672122, + 12.756940234109605, + 12.75719730657295, + 12.757452844279896, + 12.757706856393135, + 12.757959352020666, + 12.758210340216106, + 12.758459829979017, + 12.758707830255249, + 12.758954349937222, + 12.75919939786429, + 12.759442982823018, + 12.759685113547512, + 12.759925798719756, + 12.760165046969872, + 12.760402866876488, + 12.760639266966995, + 12.760874255717878, + 12.761107841555027, + 12.761340032854017, + 12.76157083794043, + 12.761800265090134, + 12.762028322529586, + 12.762255018436148, + 12.762480360938339, + 12.762704358116173, + 12.762927018001406, + 12.763148348577849, + 12.763368357781655, + 12.763587053501585, + 12.763804443579321, + 12.764020535809713, + 12.764235337941075, + 12.764448857675479, + 12.764661102668994, + 12.764872080531996, + 12.765081798829419, + 12.765290265081033, + 12.765497486761722, + 12.76570347130173, + 12.765908226086957 + ] + } + }, + "TestOperators": { + "test_mimo_add": { + "B": { + "data": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "dim": [ + 8, + 10 + ] + }, + "A": { + "data": [ + [ + -0.059880239520958084, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -0.047619047619047616, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + -0.09174311926605504, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -0.06944444444444445, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + -0.059880239520958084, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.047619047619047616, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.09174311926605504, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.06944444444444445 + ] + ], + "dim": [ + 8, + 8 + ] + }, + "C": { + "data": [ + [ + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 10, + 8 + ] + }, + "D": { + "data": [ + [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 1.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 10, + 10 + ] + }, + "tau": { + "data": [ + 1.0, + 3.0, + 7.0, + 3.0, + 1.0, + 3.0, + 7.0, + 3.0 + ], + "dim": [ + 8 + ] + } + }, + "test_mimo_mul_constant": { + "B": { + "data": [ + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 1.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "dim": [ + 4, + 6 + ] + }, + "A": { + "data": [ + [ + -0.059880239520958084, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -0.047619047619047616, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + -0.09174311926605504, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -0.06944444444444445 + ] + ], + "dim": [ + 4, + 4 + ] + }, + "C": { + "data": [ + [ + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 6, + 4 + ] + }, + "D": { + "data": [ + [ + 0.0, + 0.0, + 2.7, + 0.0, + 2.7, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 2.7, + 0.0, + 2.7 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 6, + 6 + ] + }, + "tau": { + "data": [ + 1.0, + 3.0, + 7.0, + 3.0 + ], + "dim": [ + 4 + ] + } + }, + "test_siso_sub": { + "B": { + "data": [ + [ + 0.0, + -0.0 + ], + [ + 1.0, + 0.0 + ], + [ + 0.0, + 1.0 + ] + ], + "dim": [ + 2, + 3 + ] + }, + "A": { + "data": [ + [ + -1.0, + 0.0 + ], + [ + 0.0, + -2.5 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 1.5, + 0.0, + 0.0 + ] + ], + "dim": [ + 3, + 2 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0, + -1.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 3, + 3 + ] + }, + "tau": { + "data": [ + 1.5, + 0.5 + ], + "dim": [ + 2 + ] + } + }, + "test_siso_sub_constant": { + "B": { + "data": [ + [ + 0.0 + ], + [ + 1.0 + ] + ], + "dim": [ + 1, + 2 + ] + }, + "A": { + "data": [ + [ + -1.0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 1 + ] + }, + "D": { + "data": [ + [ + -2.5, + 1.0 + ], + [ + 0.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "test_siso_rmul_constant": { + "B": { + "data": [ + [ + 0.0 + ], + [ + 1.0 + ] + ], + "dim": [ + 1, + 2 + ] + }, + "A": { + "data": [ + [ + -1.0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 2.0, + 0.0 + ] + ], + "dim": [ + 2, + 1 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 0.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "test_siso_mul_constant": { + "B": { + "data": [ + [ + 0.0 + ], + [ + 1.0 + ] + ], + "dim": [ + 1, + 2 + ] + }, + "A": { + "data": [ + [ + -1.0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 1 + ] + }, + "D": { + "data": [ + [ + 0.0, + 2.0 + ], + [ + 0.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "test_mimo_mul": { + "B": { + "data": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "dim": [ + 8, + 10 + ] + }, + "A": { + "data": [ + [ + -0.059880239520958084, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -0.047619047619047616, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + -0.09174311926605504, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -0.06944444444444445, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + -0.059880239520958084, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.047619047619047616, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.09174311926605504, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.06944444444444445 + ] + ], + "dim": [ + 8, + 8 + ] + }, + "C": { + "data": [ + [ + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.7664670658682635, + 0.0, + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + -0.8999999999999999, + 0.0, + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.6055045871559632, + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -1.347222222222222, + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 10, + 8 + ] + }, + "D": { + "data": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 10, + 10 + ] + }, + "tau": { + "data": [ + 1.0, + 3.0, + 7.0, + 3.0, + 1.0, + 3.0, + 7.0, + 3.0 + ], + "dim": [ + 8 + ] + } + }, + "test_siso_add": { + "B": { + "data": [ + [ + 0.0, + 0.0 + ], + [ + 1.0, + 0.0 + ], + [ + 0.0, + 1.0 + ] + ], + "dim": [ + 2, + 3 + ] + }, + "A": { + "data": [ + [ + -1.0, + 0.0 + ], + [ + 0.0, + -2.5 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 1.5, + 0.0, + 0.0 + ] + ], + "dim": [ + 3, + 2 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0, + 1.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 3, + 3 + ] + }, + "tau": { + "data": [ + 1.5, + 0.5 + ], + "dim": [ + 2 + ] + } + }, + "test_siso_mul": { + "B": { + "data": [ + [ + 0.0, + 0.0 + ], + [ + 1.0, + 0.0 + ], + [ + 0.0, + 1.0 + ] + ], + "dim": [ + 2, + 3 + ] + }, + "A": { + "data": [ + [ + -1.0, + 0.0 + ], + [ + 0.0, + -2.5 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.5, + 0.0 + ] + ], + "dim": [ + 3, + 2 + ] + }, + "D": { + "data": [ + [ + 0.0, + 0.0, + 1.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 3, + 3 + ] + }, + "tau": { + "data": [ + 1.5, + 0.5 + ], + "dim": [ + 2 + ] + } + }, + "test_siso_add_constant": { + "B": { + "data": [ + [ + 0.0 + ], + [ + 1.0 + ] + ], + "dim": [ + 1, + 2 + ] + }, + "A": { + "data": [ + [ + -1.0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 1 + ] + }, + "D": { + "data": [ + [ + 2.5, + 1.0 + ], + [ + 0.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "test_mimo_add_constant": { + "B": { + "data": [ + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 1.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "dim": [ + 4, + 6 + ] + }, + "A": { + "data": [ + [ + -0.059880239520958084, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -0.047619047619047616, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + -0.09174311926605504, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -0.06944444444444445 + ] + ], + "dim": [ + 4, + 4 + ] + }, + "C": { + "data": [ + [ + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 6, + 4 + ] + }, + "D": { + "data": [ + [ + 2.7, + 2.7, + 1.0, + 0.0, + 1.0, + 0.0 + ], + [ + 2.7, + 2.7, + 0.0, + 1.0, + 0.0, + 1.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 6, + 6 + ] + }, + "tau": { + "data": [ + 1.0, + 3.0, + 7.0, + 3.0 + ], + "dim": [ + 4 + ] + } + } + }, + "TestConstructors": { + "test_tf2dlti": { + "simple_siso_tf": { + "B": { + "data": [ + [ + 1 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "A": { + "data": [ + [ + -1 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 1 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "D": { + "data": [ + [ + 0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "tau": { + "data": [], + "dim": [ + 0 + ] + } + }, + "tf_one": { + "B": { + "data": [ + [] + ], + "dim": [ + 0, + 1 + ] + }, + "A": { + "data": [], + "dim": [ + 0, + 0 + ] + }, + "C": { + "data": [], + "dim": [ + 1, + 0 + ] + }, + "D": { + "data": [ + [ + 1 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "tau": { + "data": [], + "dim": [ + 0 + ] + } + } + }, + "test_build_wood_berry": { + "B": { + "data": [ + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 1.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "dim": [ + 4, + 6 + ] + }, + "A": { + "data": [ + [ + -0.059880239520958084, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -0.047619047619047616, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + -0.09174311926605504, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -0.06944444444444445 + ] + ], + "dim": [ + 4, + 4 + ] + }, + "C": { + "data": [ + [ + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 6, + 4 + ] + }, + "D": { + "data": [ + [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 6, + 6 + ] + }, + "tau": { + "data": [ + 1.0, + 3.0, + 7.0, + 3.0 + ], + "dim": [ + 4 + ] + } + }, + "test_exp_delay": { + "1": { + "B": { + "data": [ + [], + [] + ], + "dim": [ + 0, + 2 + ] + }, + "A": { + "data": [], + "dim": [ + 0, + 0 + ] + }, + "C": { + "data": [], + "dim": [ + 2, + 0 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.0 + ], + "dim": [ + 1 + ] + } + }, + "1.5": { + "B": { + "data": [ + [], + [] + ], + "dim": [ + 0, + 2 + ] + }, + "A": { + "data": [], + "dim": [ + 0, + 0 + ] + }, + "C": { + "data": [], + "dim": [ + 2, + 0 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "10": { + "B": { + "data": [ + [], + [] + ], + "dim": [ + 0, + 2 + ] + }, + "A": { + "data": [], + "dim": [ + 0, + 0 + ] + }, + "C": { + "data": [], + "dim": [ + 2, + 0 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 10.0 + ], + "dim": [ + 1 + ] + } + } + }, + "test_siso_delay": { + "B": { + "data": [ + [ + 0.0 + ], + [ + 1.0 + ] + ], + "dim": [ + 1, + 2 + ] + }, + "A": { + "data": [ + [ + -1.0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 1 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 0.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "test_delay_function": { + "1": { + "B": { + "data": [ + [], + [] + ], + "dim": [ + 0, + 2 + ] + }, + "A": { + "data": [], + "dim": [ + 0, + 0 + ] + }, + "C": { + "data": [], + "dim": [ + 2, + 0 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.0 + ], + "dim": [ + 1 + ] + } + }, + "1.5": { + "B": { + "data": [ + [], + [] + ], + "dim": [ + 0, + 2 + ] + }, + "A": { + "data": [], + "dim": [ + 0, + 0 + ] + }, + "C": { + "data": [], + "dim": [ + 2, + 0 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "10": { + "B": { + "data": [ + [], + [] + ], + "dim": [ + 0, + 2 + ] + }, + "A": { + "data": [], + "dim": [ + 0, + 0 + ] + }, + "C": { + "data": [], + "dim": [ + 2, + 0 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 1.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 10.0 + ], + "dim": [ + 1 + ] + } + } + } + }, + "TestDelayLtiMethods": { + "test_feedback": { + "delay_siso_tf": { + "B": { + "data": [ + [ + 0.0, + 0.0 + ], + [ + 1.0, + 0.0 + ], + [ + 0.0, + 1.0 + ] + ], + "dim": [ + 2, + 3 + ] + }, + "A": { + "data": [ + [ + -1.0, + 0.0 + ], + [ + 0.0, + -1.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "C": { + "data": [ + [ + 1.0, + 0.0, + 1.0 + ], + [ + 0.0, + -1.0, + 0.0 + ] + ], + "dim": [ + 3, + 2 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 3, + 3 + ] + }, + "tau": { + "data": [ + 1.5, + 1.5 + ], + "dim": [ + 2 + ] + } + }, + "empty": { + "B": { + "data": [ + [ + 0.0 + ], + [ + 1.0 + ] + ], + "dim": [ + 1, + 2 + ] + }, + "A": { + "data": [ + [ + -1.0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 1.0, + -1.0 + ] + ], + "dim": [ + 2, + 1 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 0.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + }, + "tf_one": { + "B": { + "data": [ + [ + 0.0 + ], + [ + 1.0 + ] + ], + "dim": [ + 1, + 2 + ] + }, + "A": { + "data": [ + [ + -1.0 + ] + ], + "dim": [ + 1, + 1 + ] + }, + "C": { + "data": [ + [ + 1.0, + -1.0 + ] + ], + "dim": [ + 2, + 1 + ] + }, + "D": { + "data": [ + [ + 0.0, + 1.0 + ], + [ + 0.0, + 0.0 + ] + ], + "dim": [ + 2, + 2 + ] + }, + "tau": { + "data": [ + 1.5 + ], + "dim": [ + 1 + ] + } + } + }, + "test_tito_freq_response": { + "r12": { + "real": [ + -17.979545539270337, + -17.80080817901333, + -17.589543229062834, + -17.340734466620393, + -17.048957615295066, + -16.708499550214572, + -16.313549184302005, + -15.85847403757536, + -15.338191130105187, + -14.748630247557251, + -14.08727096524883, + -13.353712684114113, + -12.550212327912854, + -11.682103225679397, + -10.757999400210041, + -9.789700467177209, + -8.791748364345382, + -7.780645194703653, + -6.77380915756393, + -5.788403697114647, + -4.840204761966178, + -3.9426621489255704, + -3.1062664297028433, + -2.3382679838953697, + -1.6427292312041606, + -1.0208420507548333, + -0.47141780912310505, + 0.008543516095805998, + 0.42328288926552626, + 0.7777646148050352, + 1.0772417273196127, + 1.3269146985415978, + 1.5316799912700436, + 1.6959570779212823, + 1.8235793487446414, + 1.9177344287645302, + 1.9809415354656503, + 2.0150567185922394, + 2.0213005671394275, + 2.000306979192757, + 1.9521957986742915, + 1.876676552975799, + 1.773195175210415, + 1.6411402870531193, + 1.4801297857673967, + 1.2904008490539232, + 1.0733246148715239, + 0.8320565474178327, + 0.5723083770628592, + 0.3031784490352936, + 0.03789368577303131, + -0.20581021359933915, + -0.4061097563642434, + -0.5389263752494392, + -0.5818615439788665, + -0.5207189981398878, + -0.35829769632735353, + -0.12340172886874234, + 0.12471652477431537, + 0.30405438482750385, + 0.337218273380767, + 0.19791349643592104, + -0.044297017426789584, + -0.22785970586314502, + -0.1987689909970892, + 0.023380727220823967, + 0.1891025115903759, + 0.07443232450773135, + -0.14010548935163072, + -0.06374423002896482, + 0.13027604265184586, + -0.02115043562649843, + -0.0799551758147477, + 0.10109741710217417, + -0.07902323999789639, + 0.05706777985256912, + -0.05172517399847418, + 0.06022493217155967, + -0.063113533631419, + 0.025510508873653734, + 0.042948208793136704, + -0.01586729960159359, + -0.039742826401768565, + -0.03933889763172318, + -0.03217778287538818, + -0.004144263303007504, + 0.030152922601707346, + -0.020569563815090952, + 0.02105854171182995, + -0.01985575909205607, + -0.018100973985880973, + -0.017304131428496097, + -0.010587177486381905, + 0.014138679495106879, + -0.00125338200821709, + -0.00701808799857972, + 0.008054751670420123, + -0.008347457720106043, + -0.0002937462920334337, + -0.008997705819017333 + ], + "imag": [ + 4.342619517073938, + 4.724806025295414, + 5.1320049788371085, + 5.563264561669552, + 6.0167350654387, + 6.489458865311868, + 6.977156784848183, + 7.47403252587984, + 7.9726262655251645, + 8.463757497363597, + 8.936602956557591, + 9.378954217211833, + 9.77768719682191, + 10.119449552768986, + 10.39153249827225, + 10.582847011872238, + 10.684882562976957, + 10.692504111813498, + 10.60445242341483, + 10.423456838554033, + 10.155939321390012, + 9.81136446358357, + 9.401350225600211, + 8.938683031177717, + 8.436374837908431, + 7.906866892809745, + 7.361439206777721, + 6.809840335440722, + 6.260118216093046, + 5.718613088203613, + 5.190066761550856, + 4.677805073756644, + 4.183958198230711, + 3.709693144937356, + 3.2554421118370738, + 2.8211181410407695, + 2.4063154322809384, + 2.0104957343351835, + 1.633164717731685, + 1.2740433679830578, + 0.9332393422808171, + 0.6114218194820902, + 0.31000030646136595, + 0.03130251550821797, + -0.22126213298716477, + -0.4430797725510945, + -0.6282950189646908, + -0.7699291694610503, + -0.8602530433171849, + -0.891543586581522, + -0.8573704891952135, + -0.7545384045814909, + -0.5857090205738471, + -0.36248350379729893, + -0.10827268519038567, + 0.14040513344632233, + 0.3363551529643246, + 0.43046783977153713, + 0.3885172347024771, + 0.21399293082315407, + -0.03259372445101514, + -0.2369105024545742, + -0.2777733827223607, + -0.11734338273808059, + 0.12259355794171213, + 0.21150216001254316, + 0.042815335500071765, + -0.16022073343676937, + -0.07926308000029737, + 0.13209726900244684, + 0.029813061197126362, + -0.11992090546801883, + 0.07692870294914742, + 0.0002460820330156346, + -0.047337232293552876, + 0.061547514039147905, + -0.056331748719427095, + 0.03505301648345487, + 0.006930040610082755, + -0.05192410443961511, + 0.030563211032726, + 0.04533346400354927, + 0.018323043007691008, + 0.006519737860931946, + 0.016872569062135863, + 0.032844945067921655, + 0.0008315092285931376, + -0.01822910195093327, + 0.013553239570347883, + 0.011243753574243587, + -0.010228726967818413, + -0.007710188908937525, + 0.013633054898708348, + -0.006889112211002009, + 0.014275623961371196, + 0.0110110669406258, + 0.008756181807002703, + -0.0069164327516228466, + -0.009873118718470351, + -0.0002031541953267352 + ] + }, + "r11": { + "real": [ + 12.43128816593835, + 12.358336017309398, + 12.271526787736379, + 12.168474760438365, + 12.046487013817968, + 11.902567694673207, + 11.733444253944395, + 11.535624284042607, + 11.305492545778371, + 11.03945759523041, + 10.734155293680907, + 10.386711487150423, + 9.995057527806516, + 9.558279961265896, + 9.076970731718406, + 8.553529508386978, + 7.9923598284576105, + 7.399901173356435, + 6.784454211939663, + 6.155786883835384, + 5.524549715424967, + 4.901569353408882, + 4.297117266395177, + 3.7202564479360047, + 3.1783506935058363, + 2.67678493929047, + 2.2189029587527584, + 1.8061323869336094, + 1.4382443975577974, + 1.1136883264828072, + 0.8299472071288009, + 0.5838732961066524, + 0.37197800734332626, + 0.19066450836993976, + 0.03640167197906004, + -0.0941552704761483, + -0.20408864510080785, + -0.29617709607461673, + -0.3728799669104546, + -0.43633935541928387, + -0.48839344774672594, + -0.5305962610425458, + -0.5642402662307592, + -0.5903794670954846, + -0.6098513835989521, + -0.6232970609174918, + -0.6311787493735481, + -0.6337953263702912, + -0.6312959113341831, + -0.6236925086108341, + -0.6108729494412299, + -0.592615938023275, + -0.5686106773811407, + -0.5384843820952704, + -0.5018419665619038, + -0.45832325041490285, + -0.4076839409591818, + -0.3499070106515678, + -0.2853501091502618, + -0.21493104034257932, + -0.14034512731244936, + -0.06429281708532214, + 0.009329833022601843, + 0.07536304799547297, + 0.1275707621265259, + 0.15926229807770054, + 0.16453854037701823, + 0.14026912274052408, + 0.08863643541612089, + 0.019534790750579276, + -0.04869104635601392, + -0.092394501101518, + -0.09155486655554447, + -0.0434769399816274, + 0.026101954727285753, + 0.06872592822762003, + 0.04687264185681756, + -0.02004148721876953, + -0.054042185885970415, + -0.00760858694647483, + 0.04391738001863822, + 0.004676403552874922, + -0.03689569846489549, + 0.018507911982609565, + 0.011027350572800474, + -0.025005640080616162, + 0.02568741788980782, + -0.022718741343590407, + 0.020936102056912178, + -0.019150495349870126, + 0.011353412196151337, + 0.0060283956243197875, + -0.014031994776318462, + -0.008359158992222976, + 0.0003647585514889078, + 0.002097797791679799, + -0.0024804124923506685, + -0.008986537350776683, + 7.933603945029554e-5, + 0.0038850821975851724 + ], + "imag": [ + -2.2040229903890376, + -2.4055411951240657, + -2.622616000974477, + -2.85556679125228, + -3.104423705408224, + -3.368829959399902, + -3.6479282700501523, + -3.940234259116282, + -4.243503180665107, + -4.554600971794905, + -4.869396254455911, + -5.182695808987789, + -5.488250819095851, + -5.778862753451961, + -6.046613544822822, + -6.283232484744117, + -6.480591217427942, + -6.631290529780363, + -6.729274093428178, + -6.770383614186616, + -6.752765832314651, + -6.6770594956126015, + -6.546327677096264, + -6.365748044418277, + -6.14211724946262, + -5.883253351144822, + -5.59738624874044, + -5.292612374589998, + -4.976463903965641, + -4.655613706471871, + -4.335712580263525, + -4.021338835031188, + -3.716032488507119, + -3.4223854912612466, + -3.1421629510837734, + -2.876435915373175, + -2.625712157695306, + -2.390056595787404, + -2.169197019619673, + -1.9626136880188965, + -1.769613219791124, + -1.589388292447561, + -1.421065199357977, + -1.2637414988835867, + -1.1165159645363287, + -0.9785129170246855, + -0.8489028540035857, + -0.7269211295418373, + -0.6118862883468039, + -0.5032195261724143, + -0.4004666052505409, + -0.30332335753676204, + -0.2116655854439276, + -0.1255836063196159, + -0.04542071727069005, + 0.028186750103759775, + 0.09427266059654694, + 0.1515163005144927, + 0.1982437502790757, + 0.23247672554088666, + 0.25205861922894385, + 0.25489537675413476, + 0.2393521051741667, + 0.2048370128253847, + 0.15256927604902282, + 0.08644864759570142, + 0.013804166087691448, + -0.05440439297514317, + -0.10457637205481073, + -0.12337208659177808, + -0.10287214507188865, + -0.04709161986773513, + 0.02337238224858419, + 0.07431334452580744, + 0.07397907485865568, + 0.019649866356320482, + -0.04522020590124208, + -0.05585753112977387, + 0.0018029905731695305, + 0.04867765452571895, + 0.00930283737574529, + -0.040635634813495615, + 0.005269385356166265, + 0.028472443316663185, + -0.028910630235054184, + 0.013022728954821837, + 0.0002739603091159187, + -0.005633390817610891, + 0.004066499552758985, + 0.003299818509018253, + -0.013587326528977543, + 0.01496477639672639, + 0.004381468082663159, + -0.010465641522816949, + -0.012198865550599444, + -0.010920454476846246, + -0.009823942617634675, + -0.0021152295724793505, + 0.008411590638668729, + -0.006607063762326209 + ] + }, + "r22": { + "real": [ + -18.915248629777597, + -18.818534114344438, + -18.703099779307514, + -18.56557164596919, + -18.402074259132487, + -18.208201859070403, + -17.979009410718177, + -17.709034484525514, + -17.39236372394293, + -17.022759941957233, + -16.593866906740217, + -16.09950736255759, + -15.534084233759618, + -14.893083706713258, + -14.173660998046055, + -13.375265641208296, + -12.500236173898468, + -11.554270529040117, + -10.546667266457904, + -9.490243399120851, + -8.400872516165366, + -7.296649472144543, + -6.196762029325518, + -5.120214750087282, + -4.084585207909633, + -3.1049852213637728, + -2.193352903396712, + -1.3581307879964102, + -0.6043130562713402, + 0.06621028692877973, + 0.6541129610793954, + 1.1619987569828307, + 1.5937614254004508, + 1.9539994708602446, + 2.247521127673765, + 2.4789532966265373, + 2.652453532218505, + 2.7715163096256807, + 2.83886263318731, + 2.856404112264212, + 2.825277610586564, + 2.745953463682208, + 2.6184282855501837, + 2.442521783680849, + 2.218304628237282, + 1.9466892163218188, + 1.6302134222701334, + 1.274032847897525, + 0.8870999077566379, + 0.4834346471115818, + 0.0832663877776031, + -0.2863656314421349, + -0.592215501389611, + -0.7975604149836688, + -0.8681036951379899, + -0.7817926580954525, + -0.5420923486781136, + -0.1916436398555898, + 0.18088124386770826, + 0.4521390152227134, + 0.5050788781522239, + 0.29886027938423637, + -0.06346372251295096, + -0.33995294864148573, + -0.298548670216058, + 0.03336255780611403, + 0.2827489113412085, + 0.1124396193941759, + -0.20925252751482462, + -0.09611902663619168, + 0.19486083771447865, + -0.03112943158137676, + -0.11999306230537929, + 0.1513302993825827, + -0.11813079757157524, + 0.08523688835308758, + -0.07727088490118084, + 0.0900620330224121, + -0.0944907652968597, + 0.03829583158913529, + 0.06423094002368505, + -0.02383091329396667, + -0.059520471883937213, + -0.05889619223177922, + -0.048189494636034655, + -0.006243062570851471, + 0.0451353319873735, + -0.030772628489114603, + 0.03151045910869639, + -0.02973163989482227, + -0.02708785188700699, + -0.02589746974521368, + -0.015856623313825167, + 0.021168304744243527, + -0.0018836301481688117, + -0.01051068708161923, + 0.012053482281977366, + -0.012492698320439748, + -0.00043617262123654433, + -0.01346871961822188 + ], + "imag": [ + 3.3057085066163894, + 3.6127066683888227, + 3.9448932964715984, + 4.303301496861762, + 4.688665057110565, + 5.101291532899661, + 5.540907154072057, + 6.0064728005435715, + 6.495973433590731, + 7.006188266663765, + 7.532455914625273, + 8.068457729697327, + 8.60605282858416, + 9.135208246763947, + 9.644074269124324, + 10.119254163772386, + 10.546304788874558, + 10.910476650458309, + 11.197659441319944, + 11.395448224345893, + 11.494198865734404, + 11.487915235628803, + 11.374818752128508, + 11.157497224503725, + 10.842605272426276, + 10.440172050557381, + 9.962639433492598, + 9.423787620051547, + 8.837700623968608, + 8.217889420325198, + 7.576640947440485, + 6.9246122263956575, + 6.270651185565627, + 5.621803549252459, + 4.983457316690631, + 4.359579024376977, + 3.7530046063985885, + 3.1657584070746108, + 2.5993841211339794, + 2.05527961245246, + 1.5350329158289715, + 1.040758815637163, + 0.5754337582168062, + 0.14322075082034663, + -0.2502358297888014, + -0.59758449923624, + -0.8896766723199003, + -1.1157164056493167, + -1.2637990501111944, + -1.3220333735152963, + -1.2804683113287971, + -1.1340154137464138, + -0.88640658320664, + -0.554860630026135, + -0.17444989092003704, + 0.19987912382739384, + 0.4968823626101177, + 0.6421273661314307, + 0.5832415712776812, + 0.3243473310657503, + -0.044634454292340926, + -0.3523544542322688, + -0.4161933510076372, + -0.17775066371050088, + 0.18180859432346044, + 0.3167542014546065, + 0.06541708656994971, + -0.23934531099900216, + -0.11946282946002104, + 0.19739087287740992, + 0.045258051819745994, + -0.17959973875774188, + 0.11483094765323414, + 0.0007393596109930061, + -0.07112285728405465, + 0.09230396672039123, + -0.08446643160708937, + 0.05262319880749238, + 0.010228129558354596, + -0.0776718367975225, + 0.04583242917601909, + 0.06783232011089697, + 0.02736477699097288, + 0.009702511942248014, + 0.025214275687164233, + 0.04916096313704033, + 0.0012777124020500739, + -0.02730787420615017, + 0.02030714844230243, + 0.016814464106062334, + -0.01532516934899406, + -0.011553378545014457, + 0.020400859776369875, + -0.01030432866217134, + 0.021368715140975353, + 0.016479283832946556, + 0.013110722732914588, + -0.010356584726514444, + -0.01477930811683578, + -0.0003070438427949238 + ] + }, + "r21": { + "real": [ + 6.456806400453239, + 6.427947543700158, + 6.393375187644098, + 6.3520033875310515, + 6.302559582114024, + 6.243561185721288, + 6.173293184574207, + 6.089788971763697, + 5.990817654307498, + 5.873882295096301, + 5.736234963092649, + 5.574915919520709, + 5.38682549445441, + 5.168837755957292, + 4.917964289115123, + 4.631573468179081, + 4.307664696686308, + 3.945187695254357, + 3.54438432387941, + 3.107116276438488, + 2.6371296124949817, + 2.140201237541224, + 1.6241180010507852, + 1.0984589157170876, + 0.5741836118341503, + 0.06306857633940752, + -0.4229340023244604, + -0.87232292725337, + -1.2745739500550426, + -1.6205056618900473, + -1.902466073019002, + -2.1143625297700295, + -2.2515916074280087, + -2.3109466650560067, + -2.2905887437386045, + -2.1901642152430094, + -2.011142769994167, + -1.7574319609882518, + -1.43629495018049, + -1.059546023807074, + -0.6449079422317704, + -0.217267078039979, + 0.19065856733144365, + 0.539010885682689, + 0.7839085163923682, + 0.8841851479059631, + 0.8125726987668168, + 0.570745116654013, + 0.20462486628832652, + -0.1883096737969229, + -0.47258838733403585, + -0.5215193912709415, + -0.2950753357426149, + 0.08807371097372083, + 0.36366476720648777, + 0.2936758995637603, + -0.065213907506587, + -0.29894808264680284, + -0.08464081617289526, + 0.23257255905525748, + 0.06546978557363038, + -0.20738983126284408, + 0.0693028678671942, + 0.09454966382289584, + -0.15230407730456613, + 0.1382481044318476, + -0.11348587852796453, + 0.10500719542317855, + -0.10706254145905894, + 0.08538746943129889, + 0.00013864408952014246, + -0.0813929789208096, + -0.016229963142076916, + 0.03421325121458908, + 0.0414996295586669, + 0.019260377444086856, + -0.03265666650530345, + -0.030373714038262407, + 0.04131819644827307, + -0.034035166413741165, + -0.0045548343631747635, + 0.022830165960750277, + 0.01568611666883509, + -0.021142120421470317, + 0.01388370864325637, + -0.021678743689387228, + -0.020252878853129867, + -0.002225073071142497, + -0.0039556697184909085, + 0.005523570519956949, + -0.013825527551196603, + 0.005590083344374726, + -0.006110247250606604, + 0.010580702734716462, + 0.0019535150617730115, + 0.008518115523435644, + -0.00789601807963814, + -0.00031678243823877306, + 0.00046223762258196515, + -0.0032984249938573704 + ], + "imag": [ + -1.1654146900771192, + -1.275504238261316, + -1.3952134196613368, + -1.525135808951057, + -1.6658201654080644, + -1.8177375696952263, + -1.9812386527804906, + -2.1564990566329816, + -2.3434513830761534, + -2.5417023327129895, + -2.750434686598742, + -2.9682954506970254, + -3.193274083068084, + -3.4225784106657136, + -3.652520595183895, + -3.878430964204859, + -4.09462279613827, + -4.294434683997667, + -4.470376744370216, + -4.614400329401237, + -4.718296377229871, + -4.77420537768458, + -4.775195494266592, + -4.715841328265156, + -4.592722629460435, + -4.404767107002289, + -4.153386506328756, + -3.8423952808674433, + -3.477745466690269, + -3.0671469410490886, + -2.619659661993002, + -2.1453409330780775, + -1.655009861622837, + -1.160160091069027, + -0.6730173636788513, + -0.2067042699366139, + 0.22455931737729504, + 0.6053278390248329, + 0.9190231112104983, + 1.148445888648517, + 1.2769494556619931, + 1.2905432853897645, + 1.1811539349425253, + 0.95108947819774, + 0.6183260582168065, + 0.22144631435988973, + -0.1781274695182204, + -0.5003173814745683, + -0.6614123257925069, + -0.6026930010439868, + -0.3289112898796782, + 0.060078050919870604, + 0.3767993822520846, + 0.4272844139867687, + 0.16087163389218007, + -0.21241277082103482, + -0.3238275987635541, + -0.0354905375588738, + 0.260968871928572, + 0.09174878459265134, + -0.21822340897568573, + -0.0096386578331521, + 0.17603512013437658, + -0.14414934018593165, + 0.038461529606795115, + 0.03709086724495221, + -0.06428091620840329, + 0.055653602887128506, + -0.016251565429515925, + -0.049446661519207374, + 0.08990705949458201, + -0.009289289705677327, + -0.07285886178018194, + -0.05878249121079965, + -0.04602582479906211, + -0.053081217945457324, + -0.03975912940617042, + 0.03571064977033943, + -0.010839061154228356, + 0.01888116666641433, + -0.035170300514430236, + -0.022868109139146643, + -0.024916560173710044, + -0.016514231492333612, + 0.02011863433012642, + 0.005109029532660125, + -0.0012914392803147683, + 0.018356801049832554, + -0.016377533873436455, + -0.014323584392833242, + -0.002125184924407931, + -0.011453942942727047, + -0.0098755776488937, + -0.00011712878439016248, + -0.00944135163937912, + -0.0021482303801674697, + 0.0013128032711124604, + 0.007286436828345648, + 0.006629306459054316, + 0.0050777891062560015 + ] + } + }, + "test_mimo_feedback": { + "B": { + "data": [ + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + ], + "dim": [ + 8, + 10 + ] + }, + "A": { + "data": [ + [ + -0.059880239520958084, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + -0.047619047619047616, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + -0.09174311926605504, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -0.06944444444444445, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + -0.059880239520958084, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.047619047619047616, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.09174311926605504, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.06944444444444445 + ] + ], + "dim": [ + 8, + 8 + ] + }, + "C": { + "data": [ + [ + 0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.7664670658682635, + 0.0, + 0.7664670658682635, + 0.0 + ], + [ + -0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -0.8999999999999999, + 0.0, + -0.8999999999999999, + 0.0 + ], + [ + 0.0, + 0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.6055045871559632, + 0.0, + 0.6055045871559632 + ], + [ + 0.0, + -1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.347222222222222, + 0.0, + -1.347222222222222 + ], + [ + 0.0, + 0.0, + -0.7664670658682635, + 0.0, + -0.7664670658682635, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.8999999999999999, + 0.0, + 0.8999999999999999, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + -0.6055045871559632, + 0.0, + -0.6055045871559632, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.347222222222222, + 0.0, + 1.347222222222222, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 10, + 8 + ] + }, + "D": { + "data": [ + [ + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 1.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ] + ], + "dim": [ + 10, + 10 + ] + }, + "tau": { + "data": [ + 1.0, + 3.0, + 7.0, + 3.0, + 1.0, + 3.0, + 7.0, + 3.0 + ], + "dim": [ + 8 + ] + } + }, + "test_siso_freq_resp": { + "real": [ + 0.9996375439798979, + 0.9995634312726295, + 0.9994741671370168, + 0.9993666552530321, + 0.999237167083981, + 0.99908121303276, + 0.9988933874503182, + 0.9986671822395532, + 0.9983947627635261, + 0.9980666985392813, + 0.9976716397463046, + 0.9971959288676081, + 0.9966231347756939, + 0.9959334942394207, + 0.9951032431288468, + 0.9941038165099754, + 0.9929008933437563, + 0.9914532576576418, + 0.9897114439166157, + 0.9876161300348277, + 0.9850962373081802, + 0.9820666929589262, + 0.9784258086709292, + 0.9740522285356137, + 0.9688014038241506, + 0.9625015622730949, + 0.9549491594125434, + 0.9459038334419049, + 0.9350829394272876, + 0.9221558212063877, + 0.9067381004379472, + 0.8883864336722017, + 0.8665944230560981, + 0.8407906760670428, + 0.8103404009543473, + 0.7745523915091108, + 0.7326937684053461, + 0.6840153376849258, + 0.6277907774720298, + 0.5633728780129739, + 0.4902694689018042, + 0.40824015081694337, + 0.3174122070875165, + 0.21840995593936785, + 0.11248651027122411, + 0.001641155706260852, + -0.11129935164175528, + -0.22265962734415376, + -0.32797697505164664, + -0.4221611357112527, + -0.4997585058263344, + -0.55531619758517, + -0.5838334214868793, + -0.5812849089473644, + -0.5452026982257325, + -0.47530358539375506, + -0.37414093002783655, + -0.24772998226030973, + -0.10603460602502184, + 0.03689544850765837, + 0.163475443354048, + 0.25428260135266945, + 0.29137658766182767, + 0.2634742255584535, + 0.17246061536666463, + 0.03927095986185361, + -0.09481872294498477, + -0.17692182848634166, + -0.16507730459105968, + -0.060032676072388685, + 0.07413268414875133, + 0.13395334052399674, + 0.0610174960974431, + -0.06975679846479863, + -0.09313772876012177, + 0.025934061526366826, + 0.08116736041226703, + -0.033328025868165176, + -0.050456466352268005, + 0.06145146603825666, + -0.023790764711986843, + -0.01164993683455088, + 0.028094619517517096, + -0.030057717552314775, + 0.02221753549779696, + -0.003620320024132159, + -0.022559742304732594, + 0.028197022583963373, + 0.01265424121150209, + -0.013302129669150599, + -0.02020475258354317, + -0.017882613190878055, + -0.006554881991346374, + 0.014655691602204724, + 0.000944019041138025, + -0.004257696080973819, + -0.004643396819873846, + 0.010959344674057785, + 0.010974231856788667, + 0.007217967580181465 + ], + "imag": [ + -0.024995812946127068, + -0.027431934219113052, + -0.030105271862338058, + -0.03303885684441913, + -0.036257934309874534, + -0.03979016938930197, + -0.043665869841421755, + -0.04791822613112859, + -0.0525835692753272, + -0.05770164638426992, + -0.06331591324456566, + -0.06947384247065888, + -0.07622724461511865, + -0.08363259807084238, + -0.09175138148508818, + -0.10065039956078467, + -0.11040208931859637, + -0.12108478884384631, + -0.13278294387710093, + -0.14558721886237722, + -0.15959446766698268, + -0.1749075044296957, + -0.1916345960427577, + -0.2098885736642593, + -0.22978543033607415, + -0.25144223418842526, + -0.27497414094902234, + -0.300490235110327, + -0.3280878666756117, + -0.3578450821924996, + -0.3898106800313958, + -0.4239913604759359, + -0.4603354080288266, + -0.4987123630856, + -0.5388882523855643, + -0.5804962073952337, + -0.6230027775548482, + -0.6656710221748017, + -0.7075226177394369, + -0.7473027903910795, + -0.7834538397594977, + -0.8141051786018723, + -0.8370897798193744, + -0.8499980576452956, + -0.8502796738335039, + -0.8354007066554325, + -0.8030575539931093, + -0.751440156357626, + -0.6795270170911017, + -0.5873854352991381, + -0.47644491995156885, + -0.3497113918344513, + -0.21189364718913414, + -0.06941810951605784, + 0.06969206393078606, + 0.19610650928623855, + 0.299698110811019, + 0.37035124841572953, + 0.39916086952467084, + 0.3800982171863752, + 0.3121302920468994, + 0.20157093976443763, + 0.06406865232764666, + -0.07489134372736657, + -0.18262004861081965, + -0.22672820233378352, + -0.18805963874096887, + -0.07618454336708898, + 0.061240135678342605, + 0.14923045517330516, + 0.12680648132772035, + 0.005894971987373343, + -0.1060585765190556, + -0.08715109760964115, + 0.0411449750223617, + 0.08916232616058803, + -0.024101915560650607, + -0.06963017462205673, + 0.049056299066853694, + 0.01840452262643518, + -0.05341105609671893, + 0.052002178488688384, + -0.039618229956131325, + 0.032491972651787646, + -0.03366719746221515, + 0.036580224069183376, + -0.02476129275952057, + -0.011690266278196328, + 0.02476963547555173, + 0.02157423746489494, + 0.01118719108988631, + 0.011094055800076006, + 0.018020439623851116, + 0.009513307662500076, + -0.015892797519920294, + 0.013867881656646677, + -0.012375830184461655, + -0.004995457554294962, + -4.563593820485174e-5, + -0.006920328388981937 + ] + } + } +} diff --git a/control/julia/test.ipynb b/control/julia/test.ipynb new file mode 100644 index 000000000..1aa2ad04f --- /dev/null +++ b/control/julia/test.ipynb @@ -0,0 +1,87 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "using ControlSystems\n", + "using Plots\n", + "\n", + "s = tf(\"s\")\n", + "wood_berry = [12.8/(16.7s+1)*exp(-s) -18.9/(21s+1)*exp(-3s); 6.6/(10.9s+1)*exp(-7s) -19.4/(14.4s+1)*exp(-3s)]\n", + "res = step(wood_berry, 0:0.1:100).y" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "res[1, :, 1]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "2×1001 Matrix{Float64}:\n", + " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 … -0.0263751 -0.0320198 -0.0376148\n", + " 0.0 0.0 0.0 0.0 0.0 0.0 0.0 -5.92877 -5.93249 -5.93617" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "using Interpolations, StaticArrays, ControlSystems, Plots\n", + "\n", + "# Define system\n", + "s = tf(\"s\")\n", + "wood_berry = [\n", + " 12.8/(16.7s+1)*exp(-s) -18.9/(21s+1)*exp(-3s);\n", + " 6.6/(10.9s+1)*exp(-7s) -19.4/(14.4s+1)*exp(-3s)\n", + "]\n", + "\n", + "# Time and raw inputs\n", + "t = 0:0.1:100\n", + "u1 = 1.0 .+ 0.2*sin.(0.05 .* t)\n", + "u2 = 0.5 .+ 0.1.*(t .> 50)\n", + "\n", + "# 1-D ZOH interpolants with zero extrapolation\n", + "itp1 = interpolate((t,), u1, Gridded(Constant()))\n", + "itp2 = interpolate((t,), u2, Gridded(Constant()))\n", + "eitp1 = extrapolate(itp1, 0)\n", + "eitp2 = extrapolate(itp2, 0)\n", + "\n", + "# Correct input function signature: u(x, t)\n", + "u_fun = (x, τ) -> @SVector [eitp1(τ); eitp2(τ)]\n", + "\n", + "# Simulate\n", + "y, tout, xhist = lsim(wood_berry, u_fun, t)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Julia 1.11.3", + "language": "julia", + "name": "julia-1.11" + }, + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.3" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/control/julia/utils.py b/control/julia/utils.py new file mode 100644 index 000000000..58ca7f538 --- /dev/null +++ b/control/julia/utils.py @@ -0,0 +1,99 @@ +import numpy as np +import json + +from pathlib import Path + + +def _recursive_reshape(node): + """Reshape arrays in a nested dictionary or list. + + This function recursively traverses a nested dictionary or list, + looking for dictionaries with "data" and "dim" keys. If found, it + reshapes the "data" array according to the "dim" tuple. + + Parameters + ---------- + node : dict or list + The nested dictionary or list to process. + + Returns + ------- + dict or list + The processed dictionary or list with reshaped arrays. + """ + + if isinstance(node, dict) and ("data" in node) and ("dim" in node): + data = node["data"] + dim = tuple(node["dim"]) + + array_data = np.array(data) + + if len(dim) == 1: + return array_data + + elif len(dim) > 1 and (np.shape(array_data) == dim) and (dim[0] != dim[1]): + return array_data + else: + return np.transpose(array_data) + + elif isinstance(node, dict): + new_node = {} + for key, value in node.items(): + new_node[key] = _recursive_reshape(value) + return new_node + + elif isinstance(node, list) and any(isinstance(item, dict) for item in node): + new_node = [] + for i, item in enumerate(node): + new_node[i] = _recursive_reshape(item) + return new_node + + else: + return node + + +def load_julia_results(json_path: str): + """Load Julia results from a JSON file and reshape arrays. + + This function loads data from a JSON file, which is assumed to + contain results from a Julia simulation. It then reshapes arrays + within the loaded data using the _recursive_reshape function. + + Parameters + ---------- + json_path : str + The path to the JSON file containing the Julia results. + + Returns + ------- + dict + The reshaped Julia results. + """ + + with open(json_path, "r") as f: + json_content = json.load(f) + + reshaped_julia = _recursive_reshape(json_content) + return reshaped_julia + + +def assert_delayLTI(dlti, julia_results): + """Assert that a DelayLTI object matches the Julia results. + + Parameters + ---------- + dlti : DelayLTI + The DelayLTI object to compare. + julia_results : dict + The Julia results to compare against. + """ + assert np.allclose(dlti.P.A, julia_results["A"]) + assert np.allclose(dlti.P.B, julia_results["B"]) + assert np.allclose(dlti.P.C, julia_results["C"]) + assert np.allclose(dlti.P.D, julia_results["D"]) + assert np.allclose(dlti.tau, julia_results["tau"]) + + +# Load julia results file +script_dir = Path(__file__).parent +julia_json = load_julia_results(f"{script_dir}/julia_results.json") diff --git a/control/nlsys.py b/control/nlsys.py index 30f06f819..9da21021a 100644 --- a/control/nlsys.py +++ b/control/nlsys.py @@ -23,6 +23,7 @@ from . import config from .config import _process_param, _process_kwargs +from .dde import dde_response from .iosys import InputOutputSystem, _parse_spec, _process_iosys_keywords, \ common_timebase, iosys_repr, isctime, isdtime from .timeresp import TimeResponseData, TimeResponseList, \ @@ -1580,6 +1581,7 @@ def input_output_response( results. """ + from .delaylti import DelayLTI # # Process keyword arguments # @@ -1626,7 +1628,7 @@ def input_output_response( return TimeResponseList(responses) # Sanity checking on the input - if not isinstance(sys, NonlinearIOSystem): + if not (isinstance(sys, NonlinearIOSystem) or isinstance(sys, DelayLTI)): raise TypeError("System of type ", type(sys), " not valid") # Compute the time interval and number of steps @@ -1702,6 +1704,12 @@ def input_output_response( # Process initial states X0, nstates = _process_vector_argument(X0, "X0", sys.nstates) + # Case DelayLTI + if isinstance(sys, DelayLTI): + return dde_response( + sys, T, U, X0, t_eval=t_eval, params=params + ) + # Update the parameter values (prior to evaluating outfcn) sys._update_params(params) diff --git a/control/partitionedssp.py b/control/partitionedssp.py new file mode 100644 index 000000000..b643acc4c --- /dev/null +++ b/control/partitionedssp.py @@ -0,0 +1,550 @@ +# partitionedssp.py - PartitionedStateSpace class and functions +# for Partitioned state-space systems + +"""PartitionedStateSpace class +and functions for Partitioned state-space systems + +This module contains the PartitionedStateSpace class and +functions for creating and manipulating partitioned state-space systems. +This class is needed to handle systems with time delays (delayLTI class). +""" + +import numpy as np +from scipy.linalg import block_diag, solve + +from .statesp import ss, StateSpace + + +class PartitionedStateSpace: + """Partitioned State Space class. + + The PartitionedStateSpace class represents a state-space system + partitioned into two parts: external and internal. It is used to + handle systems with time delays. + + Parameters + ---------- + sys : StateSpace + The underlying state-space representation of the system. + nu1 : int + The number of external inputs. + ny1 : int + The number of external outputs. + + Attributes + ---------- + sys : StateSpace + The underlying state-space representation of the system. + nu1 : int + The number of external inputs. + ny1 : int + The number of external outputs. + A : array_like + The state matrix. + B : array_like + The input matrix. + C : array_like + The output matrix. + D : array_like + The direct feedthrough matrix. + B1 : array_like + The input matrix for external inputs. + B2 : array_like + The input matrix for delayed inputs. + C1 : array_like + The output matrix for external outputs. + C2 : array_like + The output matrix for delayed outputs. + D11 : array_like + The direct feedthrough matrix for external inputs to external outputs. + D12 : array_like + The direct feedthrough matrix for delayed inputs to external outputs. + D21 : array_like + The direct feedthrough matrix for external inputs to delayed outputs. + D22 : array_like + The direct feedthrough matrix for delayed inputs to delayed outputs. + nstates : int + The number of states. + noutputs_total : int + The total number of outputs. + ninputs_total : int + The total number of inputs. + nu2 : int + The number of delayed inputs. + ny2 : int + The number of delayed outputs. + + Methods + ------- + from_matrices(A, B1, B2, C1, C2, D11, D12, D21, D22) + Create a PartitionedStateSpace system from matrices. + """ + + def __init__(self, sys: StateSpace, nu1: int, ny1: int): + """Initialize the PartitionedStateSpace object. + + Parameters + ---------- + sys : StateSpace + The underlying state-space representation of the system. + nu1 : int + The number of external inputs. + ny1 : int + The number of external outputs. + + Raises + ------ + TypeError + If the input is not a StateSpace object. + ValueError + If the number of external inputs or outputs is invalid. + """ + if not isinstance(sys, StateSpace): + raise TypeError("Input must be a StateSpace") + if nu1 > sys.ninputs or nu1 < 0: + raise ValueError("Invalid number of external inputs") + if ny1 > sys.noutputs or ny1 < 0: + raise ValueError("Invalid number of external outputs") + + self.sys = sys + self.nu1 = nu1 + self.ny1 = ny1 + + self.A = self.sys.A + self.B = self.sys.B + self.C = self.sys.C + self.D = self.sys.D + + self.nstates = sys.nstates + self.noutputs_total = sys.noutputs + self.ninputs_total = sys.ninputs + + # Dimension of external input w + self.nu2 = self.ninputs_total - self.nu1 + # Dimension of external output z + self.ny2 = self.noutputs_total - self.ny1 + + @property + def B1(self): + return self.B[:, : self.nu1] + + @property + def B2(self): + return self.B[:, self.nu1:] + + @property + def C1(self): + return self.C[: self.ny1, :] + + @property + def C2(self): + return self.C[self.ny1:, :] + + @property + def D11(self): + return self.D[: self.ny1, : self.nu1] + + @property + def D12(self): + return self.D[: self.ny1, self.nu1:] + + @property + def D21(self): + return self.D[self.ny1:, : self.nu1] + + @property + def D22(self): + return self.D[self.ny1:, self.nu1:] + + @classmethod + def from_matrices(cls, A, B1, B2, C1, C2, D11, D12, D21, D22): + """Create a PartitionedStateSpace system from matrices. + + Parameters + ---------- + A : array_like + The state matrix. + B1 : array_like + The input matrix for external inputs. + B2 : array_like + The input matrix for delayed inputs. + C1 : array_like + The output matrix for external outputs. + C2 : array_like + The output matrix for delayed outputs. + D11 : array_like + The direct feedthrough matrix for external inputs + to external outputs. + D12 : array_like + The direct feedthrough matrix for delayed inputs + to external outputs. + D21 : array_like + The direct feedthrough matrix for external inputs + to delayed outputs. + D22 : array_like + The direct feedthrough matrix for delayed inputs + to delayed outputs (should be zeros for + delay LTI). + + Returns + ------- + PartitionedStateSpace + The PartitionedStateSpace system. + + Raises + ------ + ValueError + If the matrices have incompatible shapes. + """ + + nx = A.shape[0] + nw = B1.shape[1] + nu = B2.shape[1] + nz = C1.shape[0] + ny = C2.shape[0] + + # Shape validations + if A.shape[1] != nx and nx != 0: + raise ValueError("A must be square") + if B1.shape[0] != nx: + raise ValueError("B1 must have the same row size as A") + if B2.shape[0] != nx: + raise ValueError("B2 must have the same row size as A") + if C1.shape[1] != nx: + raise ValueError("C1 must have the same column size as A") + if C2.shape[1] != nx: + raise ValueError("C2 must have the same column size as A") + if D11.shape[1] != nw: + raise ValueError("D11 must have the same column size as B1") + if D21.shape[1] != nw: + raise ValueError("D21 must have the same column size as B1") + if D12.shape[1] != nu: + raise ValueError("D12 must have the same column size as B2") + if D22.shape[1] != nu: + raise ValueError("D22 must have the same column size as B2") + if D11.shape[0] != nz: + raise ValueError("D11 must have the same row size as C1") + if D12.shape[0] != nz: + raise ValueError("D12 must have the same row size as C1") + if D21.shape[0] != ny: + raise ValueError("D21 must have the same row size as C2") + if D22.shape[0] != ny: + raise ValueError("D22 must have the same row size as C2") + + B = np.hstack((B1, B2)) + C = np.vstack((C1, C2)) + D = np.block([[D11, D12], [D21, D22]]) + + sys = ss(A, B, C, D) + + return cls(sys, nw, nz) + + def __add__(self, other): + """Add two PartitionedStateSpace systems. + + Parameters + ---------- + other : PartitionedStateSpace + The other system to add. + + Returns + ------- + PartitionedStateSpace + The resulting PartitionedStateSpace system. + + Raises + ------ + TypeError + If the operand type is not supported. + """ + + if not isinstance(other, PartitionedStateSpace): + raise TypeError("Can only add PartitionedStateSpace objects") + + A = block_diag(self.A, other.A) + B1 = np.vstack((self.B1, other.B1)) + B2 = block_diag(self.B2, other.B2) + B = np.hstack((B1, B2)) + + C1 = np.hstack((self.C1, other.C1)) + C2 = block_diag(self.C2, other.C2) + C = np.vstack((C1, C2)) + + D11 = self.D11 + other.D11 + D12 = np.hstack((self.D12, other.D12)) + D21 = np.vstack((self.D21, other.D21)) + D22 = block_diag(self.D22, other.D22) + D = np.block([[D11, D12], [D21, D22]]) + + P = ss(A, B, C, D) + return PartitionedStateSpace( + P, self.nu1 + other.nu1, self.ny1 + other.ny1 + ) + + def __mul__(self, other): + """Multiply two PartitionedStateSpace systems. + + Parameters + ---------- + other : PartitionedStateSpace + The other system to multiply with. + + Returns + ------- + PartitionedStateSpace + The resulting PartitionedStateSpace system. + + Raises + ------ + TypeError + If the operand type is not supported. + """ + + if not isinstance(other, PartitionedStateSpace): + raise TypeError("Can only multiply PartitionedStateSpace objects") + + A = np.block( + [ + [self.A, self.B1 @ other.C1], + [np.zeros((other.A.shape[0], self.A.shape[1])), other.A], + ] + ) + + B = np.block( + [ + [ + self.B1 @ other.D11, + self.B2, self.B1 @ other.D12 + ], + [ + other.B1, + np.zeros((other.B2.shape[0], self.B2.shape[1])), + other.B2 + ] + ] + ) + + C = np.block( + [ + [ + self.C1, + self.D11 @ other.C1, + ], + [ + self.C2, + self.D21 @ other.C1 + ], + [ + np.zeros((other.C2.shape[0], self.C2.shape[1])), + other.C2 + ] + ] + ) + + D = np.block( + [ + [self.D11 @ other.D11, self.D12, self.D11 @ other.D12], + [self.D21 @ other.D11, self.D22, self.D21 @ other.D12], + [ + other.D21, + np.zeros((other.D22.shape[0], self.D22.shape[1])), + other.D22, + ], + ] + ) + + P = ss(A, B, C, D) + return PartitionedStateSpace(P, other.nu1, self.ny1) + + def __eq__(self, other): + return ( + np.allclose(self.A, other.A) + and np.allclose(self.B, other.B) + and np.allclose(self.C, other.C) + and np.allclose(self.D, other.D) + and self.nu1 == other.nu1 + and self.ny1 == other.ny1 + ) + + def feedback(self, other): + """Feedback interconnection for PartitionedStateSpace. + + Parameters + ---------- + other : PartitionedStateSpace + The system in the feedback path. + + Returns + ------- + PartitionedStateSpace + The resulting PartitionedStateSpace system. + + Raises + ------ + TypeError + If the operand type is not supported. + """ + + if not isinstance(other, PartitionedStateSpace): + raise TypeError("Feedback connection only defined\ + for PartitionedStateSpace objects.") + + # Pre-calculate repeated inverses + I_self = np.eye(self.D11.shape[0]) + I_other = np.eye(other.D11.shape[0]) + + X_11 = solve( + I_other + other.D11 @ self.D11, + np.hstack((-other.D11 @ self.C1, -other.C1)) + ) + X_21 = solve( + I_self + self.D11 @ other.D11, + np.hstack((self.C1, -self.D11 @ other.C1)) + ) + + X_12 = solve( + I_other + other.D11 @ self.D11, + np.hstack((I_other, -other.D11 @ self.D12, -other.D12)), + ) # maybe I_other + X_22 = solve( + I_self + self.D11 @ other.D11, + np.hstack((self.D11, self.D12, -self.D11 @ other.D12)), + ) + + A_new = np.vstack((self.B1 @ X_11, other.B1 @ X_21)) + \ + block_diag(self.A, other.A) + + B_new = np.vstack((self.B1 @ X_12, other.B1 @ X_22)) + tmp = block_diag(self.B2, other.B2) + B_new[:, -tmp.shape[1]:] += tmp + + C_new = np.vstack([ + self.D11 @ X_11, + self.D21 @ X_11, + other.D21 @ X_21, + ]) + np.vstack([ + np.hstack([ + self.C1, + np.zeros((self.C1.shape[0], other.C1.shape[1])) + ]), + block_diag(self.C2, other.C2), + ]) + + D_new = np.vstack([ + self.D11 @ X_12, + self.D21 @ X_12, + other.D21 @ X_22, + ]) + tmp = np.vstack([ + np.hstack([ + self.D12, + np.zeros((self.D12.shape[0], other.D12.shape[1])) + ]), + block_diag(self.D22, other.D22), + ]) + D_new[:, -tmp.shape[1]:] += tmp + + P_new = StateSpace(A_new, B_new, C_new, D_new) + + return PartitionedStateSpace(P_new, other.nu1, self.ny1) + + def __str__(self): + s = "PartitionedStateSpace\n" + s += "A = \n" + s += str(self.A) + s += "\nB = \n" + s += str(self.B) + s += "\nC = \n" + s += str(self.C) + s += "\nD = \n" + s += str(self.D) + s += "\n" + return s + + +def vcat_pss(*systems: list[PartitionedStateSpace]) -> PartitionedStateSpace: + """Vertically concatenate a list of PartitionedStateSpace systems. + + Parameters + ---------- + *systems : list of PartitionedStateSpace + The systems to be concatenated. + + Returns + ------- + PartitionedStateSpace + The resulting PartitionedStateSpace system. + + Raises + ------ + TypeError + If any of the inputs are not PartitionedStateSpace systems. + ValueError + If the systems do not have the same number of inputs. + """ + + if not all(isinstance(pss, PartitionedStateSpace) for pss in systems): + raise TypeError("All arguments must be PartitionedStateSpace objects") + + nu1 = systems[0].nu1 + + if not (all(space.nu1 == nu1 for space in systems)): + raise ValueError("All PartitionedStateSpace objects\ + must have the same input dimension") + + A = block_diag(*[space.A for space in systems]) + B1 = np.vstack([space.B1 for space in systems]) + B2 = block_diag(*[space.B2 for space in systems]) + C1 = block_diag(*[space.C1 for space in systems]) + C2 = block_diag(*[space.C2 for space in systems]) + D11 = np.vstack([space.D11 for space in systems]) + D12 = block_diag(*[space.D12 for space in systems]) + D21 = np.vstack([space.D21 for space in systems]) + D22 = block_diag(*[space.D22 for space in systems]) + + return PartitionedStateSpace.from_matrices( + A, B1, B2, C1, C2, D11, D12, D21, D22 + ) + + +def hcat_pss(*systems: list[PartitionedStateSpace]) -> PartitionedStateSpace: + """Horizontally concatenate a list of PartitionedStateSpace systems. + + Parameters + ---------- + *systems : list of PartitionedStateSpace + The systems to be concatenated. + + Returns + ------- + PartitionedStateSpace + The resulting PartitionedStateSpace system. + + Raises + ------ + TypeError + If any of the inputs are not PartitionedStateSpace systems. + ValueError + If the systems do not have the same number of outputs. + """ + if not all(isinstance(pss, PartitionedStateSpace) for pss in systems): + raise TypeError("All arguments must be PartitionedStateSpace objects") + + ny1 = systems[0].ny1 + if not (all(space.ny1 == ny1 for space in systems)): + raise ValueError("All PartitionedStateSpace objects\ + must have the same output dimension") + + A = block_diag(*[space.A for space in systems]) + B1 = block_diag(*[space.B1 for space in systems]) + B2 = block_diag(*[space.B2 for space in systems]) + C1 = np.hstack([space.C1 for space in systems]) + C2 = block_diag(*[space.C2 for space in systems]) + D11 = np.hstack([space.D11 for space in systems]) + D12 = np.hstack([space.D12 for space in systems]) + D21 = block_diag(*[space.D21 for space in systems]) + D22 = block_diag(*[space.D22 for space in systems]) + + return PartitionedStateSpace.from_matrices( + A, B1, B2, C1, C2, D11, D12, D21, D22 + ) diff --git a/control/statesp.py b/control/statesp.py index 65529b99d..bdc565669 100644 --- a/control/statesp.py +++ b/control/statesp.py @@ -2383,8 +2383,9 @@ def _convert_to_statespace(sys, use_prefix_suffix=False, method=None): import itertools from .xferfcn import TransferFunction + from .delaylti import DelayLTI - if isinstance(sys, StateSpace): + if isinstance(sys, StateSpace) or isinstance(sys, DelayLTI): return sys elif isinstance(sys, TransferFunction): diff --git a/control/tests/dde_test.py b/control/tests/dde_test.py new file mode 100644 index 000000000..64c065ef1 --- /dev/null +++ b/control/tests/dde_test.py @@ -0,0 +1,255 @@ +import numpy as np +import pytest +import matplotlib.pyplot as plt + +from control.xferfcn import tf +from control.delaylti import delay, exp, mimo_delay +from control.julia.utils import julia_json + +s = tf("s") + + +@pytest.fixture +def simple_siso_tf(): + return tf([1], [1, 1]) + + +@pytest.fixture +def delay_siso_tf(): + P_tf = 1 / (s + 1) + D = delay(1.5) + return P_tf * D + + +@pytest.fixture +def wood_berry(): + # Construct a 2x2 MIMO system with delays + G_wb = mimo_delay( + [ + [ + 12.8 / (16.7 * s + 1) * exp(-s), + -18.9 / (21.0 * s + 1) * exp(-3 * s) + ], + [ + 6.6 / (10.9 * s + 1) * exp(-7 * s), + -19.4 / (14.4 * s + 1) * exp(-3 * s) + ] + ] + ) + return G_wb + + +class TestTimeResp: + def test_siso_delayed_step_response( + self, delay_siso_tf, simple_siso_tf, plot=False + ): + from control.timeresp import step_response + + timepts = np.linspace(0, 10, 1001) + step = step_response(simple_siso_tf, timepts=timepts) + delay_step = step_response(delay_siso_tf, timepts=timepts) + # Construct a manually delayed step response + # by shifting the step response + hand_delayed_step = np.zeros_like(step.y[0][0]) + count = 0 + for i, t in enumerate(step.t): + if t >= 1.5: + hand_delayed_step[i] = step.y[0][0][count] + count += 1 + if plot: + plt.figure() + plt.plot(delay_step.y[0][0] - hand_delayed_step, label="error") + plt.legend() + plt.show() + assert np.allclose(delay_step.y[0][0], hand_delayed_step) + + def test_siso_delayed_step_response_mos( + self, delay_siso_tf, simple_siso_tf, plot=False + ): + from control.timeresp import step_response + + timepts = np.linspace(0, 10, 1001) + step = step_response(simple_siso_tf, timepts=timepts) + delay_step = step_response(delay_siso_tf, timepts=timepts) + # Construct a manually delayed step response + # by shifting the step response + hand_delayed_step = np.zeros_like(step.y[0][0]) + count = 0 + for i, t in enumerate(step.t): + if t >= 1.5: + hand_delayed_step[i] = step.y[0][0][count] + count += 1 + + if plot: + plt.figure() + plt.plot(delay_step.y[0][0] - hand_delayed_step, label="error") + plt.legend() + plt.show() + assert np.allclose(delay_step.y[0][0], hand_delayed_step, atol=1e-5) + + # wood berry step response compared to julia + def test_mimo_step_response(self, wood_berry, plot=False): + from control.timeresp import step_response + import matplotlib.pyplot as plt + + timepts = np.linspace(0, 100, 1001) + step = step_response(wood_berry, timepts=timepts) + print(step.y[0].shape) + + if plot: + plt.figure() + plt.plot( + step.y[0][0] + - julia_json["TestTimeResp"]["test_mimo_step_response"]["y11"] + ) + plt.plot( + step.y[1][0] + - julia_json["TestTimeResp"]["test_mimo_step_response"]["y21"] + ) + plt.plot( + step.y[0][1] + - julia_json["TestTimeResp"]["test_mimo_step_response"]["y12"] + ) + plt.plot( + step.y[1][1] + - julia_json["TestTimeResp"]["test_mimo_step_response"]["y22"] + ) + plt.title("Step response") + plt.show() + + # Precision is currently between 1e-5 and 1e-6 + # compared to julia solver for mimo + assert np.allclose( + step.y[0][0], + julia_json["TestTimeResp"]["test_mimo_step_response"]["y11"], + atol=1e-5, + ) + assert np.allclose( + step.y[0][1], + julia_json["TestTimeResp"]["test_mimo_step_response"]["y12"], + atol=1e-5, + ) + assert np.allclose( + step.y[1][1], + julia_json["TestTimeResp"]["test_mimo_step_response"]["y22"], + atol=1e-5, + ) + assert np.allclose( + step.y[1][0], + julia_json["TestTimeResp"]["test_mimo_step_response"]["y21"], + atol=1e-5, + ) + + def test_forced_response(self, delay_siso_tf, simple_siso_tf, plot=False): + from control.timeresp import forced_response + + timepts = np.linspace(0, 10, 1001) + inputs = np.sin(timepts) + resp = forced_response(simple_siso_tf, timepts=timepts, inputs=inputs) + delay_resp = forced_response( + delay_siso_tf, timepts=timepts, inputs=inputs + ) + hand_delayed_resp = np.zeros_like(resp.y[0]) + count = 0 + for i, t in enumerate(resp.t): + if t >= 1.5: + hand_delayed_resp[i] = resp.y[0][count] + count += 1 + + # Optionally, inspect the plot: + if plot: + plt.figure() + plt.plot(resp.t, inputs, label="input") + plt.plot(resp.t, resp.y[0], label="response") + plt.plot(resp.t, delay_resp.y[0], label="delay LTI") + plt.plot(resp.t, hand_delayed_resp, label="hand delay") + plt.legend() + plt.show() + + plt.figure() + plt.plot(resp.t, delay_resp.y[0] - hand_delayed_resp) + plt.show() + + assert np.allclose(delay_resp.y[0], hand_delayed_resp, atol=1e-5) + + def test_mimo_forced_response(self, wood_berry, plot=False): + from control.timeresp import forced_response + + timepts = np.linspace(0, 100, 10001) + inputs = np.array([np.sin(timepts), np.cos(timepts)]) + resp = forced_response(wood_berry, timepts=timepts, inputs=inputs) + + resp_wb_11 = forced_response( + 12.8 / (16.7 * s + 1), timepts=timepts, inputs=inputs[0] + ) + resp_wb_12 = forced_response( + -18.9 / (21.0 * s + 1), timepts=timepts, inputs=inputs[1] + ) + resp_wb_21 = forced_response( + 6.6 / (10.9 * s + 1), timepts=timepts, inputs=inputs[0] + ) + resp_wb_22 = forced_response( + -19.4 / (14.4 * s + 1), timepts=timepts, inputs=inputs[1] + ) + + hand_delayed_resp_y1 = np.zeros_like(resp.y[0]) + hand_delayed_resp_y2 = np.zeros_like(resp.y[1]) + count11 = 0 + count12 = 0 + count21 = 0 + count22 = 0 + for i, t in enumerate(resp.t): + if t >= 1: + hand_delayed_resp_y1[i] = resp_wb_11.y[0][count11] + count11 += 1 + if t >= 3: + hand_delayed_resp_y1[i] += resp_wb_12.y[0][count12] + count12 += 1 + hand_delayed_resp_y2[i] += resp_wb_22.y[0][count22] + count22 += 1 + if t >= 7: + hand_delayed_resp_y2[i] += resp_wb_21.y[0][count21] + count21 += 1 + # plot = True + if plot: + plt.figure() + plt.plot( + resp.t, + resp.y[0] - hand_delayed_resp_y1, label="y1 - hand y1" + ) + plt.plot( + resp.t, + resp.y[1] - hand_delayed_resp_y2, label="y2 - hand y2" + ) + + plt.legend() + plt.show() + + assert np.allclose(resp.y[0], hand_delayed_resp_y1, atol=1e-5) + assert np.allclose(resp.y[1], hand_delayed_resp_y2, atol=1e-5) + + + def test_input_output_response(self, delay_siso_tf, wood_berry): + from control.nlsys import input_output_response + from control.timeresp import forced_response + + timepts = np.linspace(0, 10, 1001) + + inputs_siso = np.sin(timepts) + io_resp_siso = input_output_response( + delay_siso_tf, timepts=timepts, inputs=inputs_siso + ) + forced_resp_siso = forced_response( + delay_siso_tf, timepts=timepts, inputs=inputs_siso + ) + assert np.allclose(io_resp_siso.y[0], forced_resp_siso.y[0]) + + inputs_mimo = np.array([np.sin(timepts), np.cos(timepts)]) + io_resp_mimo = input_output_response( + wood_berry, timepts=timepts, inputs=inputs_mimo + ) + forced_resp_mimo = forced_response( + wood_berry, timepts=timepts, inputs=inputs_mimo + ) + assert np.allclose(io_resp_mimo.y[0], forced_resp_mimo.y[0]) + assert np.allclose(io_resp_mimo.y[1], forced_resp_mimo.y[1]) diff --git a/control/tests/delay_lti_test.py b/control/tests/delay_lti_test.py new file mode 100644 index 000000000..0b2786608 --- /dev/null +++ b/control/tests/delay_lti_test.py @@ -0,0 +1,265 @@ +import numpy as np +import pytest + +from control.delaylti import ( + delay, tf2dlti, exp, hcat, + vcat, mimo_delay, ss2dlti +) +from control.statesp import ss +from control.xferfcn import tf +from control.julia.utils import julia_json, assert_delayLTI + +s = tf("s") + + +@pytest.fixture +def simple_siso_tf(): + return tf([1], [1, 1]) + + +@pytest.fixture +def tf_one(): + return tf([1], [1]) + + +@pytest.fixture +def delay_siso_tf(): + P_tf = 1 / (s + 1) + D = delay(1.5) + return P_tf * D + + +@pytest.fixture +def delay_siso_tf2(): + P_tf = 3 / (2 * s + 5) + D = delay(0.5) + return P_tf * D + + +@pytest.fixture +def wood_berry(): + # Construct a 2x2 MIMO system with delays + G_wb = mimo_delay( + [ + [ + 12.8 / (16.7 * s + 1) * exp(-s), + -18.9 / (21.0 * s + 1) * exp(-3 * s) + ], + [ + 6.6 / (10.9 * s + 1) * exp(-7 * s), + -19.4 / (14.4 * s + 1) * exp(-3 * s) + ] + ] + ) + return G_wb + + +class TestConstructors: + @pytest.mark.parametrize("key", ["simple_siso_tf", "tf_one"]) + def test_tf2dlti(self, request, key): + tf = request.getfixturevalue(key) + delay_lti = tf2dlti(tf) + julia_results = julia_json["TestConstructors"]["test_tf2dlti"] + assert_delayLTI(delay_lti, julia_results[key]) + + @pytest.mark.parametrize("tau", [1, 1.5, 10]) + def test_delay_function(self, tau): + dlti = delay(tau) + julia_results = julia_json["TestConstructors"]["test_delay_function"] + assert_delayLTI(dlti, julia_results[str(tau)]) + + @pytest.mark.parametrize("tau", [1, 1.5, 10]) + def test_exp_delay(self, tau): + G = exp(-tau * s) + julia_results = julia_json["TestConstructors"]["test_exp_delay"] + assert_delayLTI(G, julia_results[str(tau)]) + + @pytest.mark.parametrize("tau", [1, 1.5, 10]) + def test_two_ways_delay(self, tau): + delay_exp = exp(-tau * s) + delay_pure = delay(tau) + assert delay_exp == delay_pure + + def test_siso_delay(self, delay_siso_tf): + assert_delayLTI( + delay_siso_tf, julia_json["TestConstructors"]["test_siso_delay"] + ) + + def test_build_wood_berry(self, wood_berry): + assert_delayLTI( + wood_berry, julia_json["TestConstructors"]["test_build_wood_berry"] + ) + + +class TestOperators: + def test_siso_add(self, delay_siso_tf, delay_siso_tf2): + assert_delayLTI( + delay_siso_tf + delay_siso_tf2, + julia_json["TestOperators"]["test_siso_add"] + ) + + def test_siso_add_constant(self, delay_siso_tf): + assert_delayLTI( + delay_siso_tf + 2.5, + julia_json["TestOperators"]["test_siso_add_constant"] + ) + + def test_siso_sub(self, delay_siso_tf, delay_siso_tf2): + assert_delayLTI( + delay_siso_tf - delay_siso_tf2, + julia_json["TestOperators"]["test_siso_sub"] + ) + + def test_siso_sub_constant(self, delay_siso_tf): + assert_delayLTI( + delay_siso_tf - 2.5, + julia_json["TestOperators"]["test_siso_sub_constant"] + ) + + def test_siso_mul(self, delay_siso_tf, delay_siso_tf2): + assert_delayLTI( + delay_siso_tf * delay_siso_tf2, + julia_json["TestOperators"]["test_siso_mul"] + ) + + def test_siso_mul_constant(self, delay_siso_tf): + assert_delayLTI( + delay_siso_tf * 2.0, + julia_json["TestOperators"]["test_siso_mul_constant"] + ) + + def test_siso_rmul_constant(self, delay_siso_tf): + assert_delayLTI( + 2.0 * delay_siso_tf, + julia_json["TestOperators"]["test_siso_rmul_constant"] + ) + + def test_mimo_add(self, wood_berry): + assert_delayLTI( + wood_berry + wood_berry, + julia_json["TestOperators"]["test_mimo_add"] + ) + + def test_mimo_add_constant(self, wood_berry): + assert_delayLTI( + wood_berry + 2.7, + julia_json["TestOperators"]["test_mimo_add_constant"] + ) + + def test_mimo_mul(self, wood_berry): + assert_delayLTI( + wood_berry * wood_berry, + julia_json["TestOperators"]["test_mimo_mul"] + ) + + def test_mimo_mul_constant(self, wood_berry): + assert_delayLTI( + wood_berry * 2.7, + julia_json["TestOperators"]["test_mimo_mul_constant"] + ) + + +class TestDelayLtiMethods: + @pytest.mark.parametrize("key", ["empty", "tf_one", "delay_siso_tf"]) + def test_feedback(self, request, key, delay_siso_tf): + G = delay_siso_tf + julia_results = julia_json["TestDelayLtiMethods"]["test_feedback"] + if key == "empty": + H = G.feedback() + else: + tf = request.getfixturevalue(key) + H = G.feedback(tf) + assert_delayLTI(H, julia_results[key]) + + def test_mimo_feedback(self, wood_berry): + G = wood_berry.feedback(wood_berry) + assert_delayLTI( + G, julia_json["TestDelayLtiMethods"]["test_mimo_feedback"] + ) + + def test_siso_freq_resp(self, delay_siso_tf): + from control.lti import frequency_response + + w = np.logspace(-2, 2, 100, base=10) + resp = frequency_response(delay_siso_tf, w).complex + assert np.allclose( + np.real(resp), + julia_json["TestDelayLtiMethods"]["test_siso_freq_resp"]["real"], + ) + assert np.allclose( + np.imag(resp), + julia_json["TestDelayLtiMethods"]["test_siso_freq_resp"]["imag"], + ) + + def test_tito_freq_response(self, wood_berry): + from control.lti import frequency_response + + w = np.logspace(-2, 2, 100, base=10) + resp = frequency_response(wood_berry, w).complex + assert np.allclose( + np.real(resp[0][0]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r11"]["real"], + ) + assert np.allclose( + np.imag(resp[0][0]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r11"]["imag"], + ) + + assert np.allclose( + np.real(resp[0][1]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r12"]["real"], + ) + assert np.allclose( + np.imag(resp[0][1]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r12"]["imag"], + ) + + assert np.allclose( + np.real(resp[1][0]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r21"]["real"], + ) + assert np.allclose( + np.imag(resp[1][0]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r21"]["imag"], + ) + + assert np.allclose( + np.real(resp[1][1]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r22"]["real"], + ) + assert np.allclose( + np.imag(resp[1][1]), + julia_json["TestDelayLtiMethods"] + ["test_tito_freq_response"]["r22"]["imag"], + ) + + def test_call(self): + sys = tf([1], [1, 1]) + dlti = tf2dlti(sys) * delay(1) + freq_resp = dlti(1j * 2 * np.pi) + expected = 1 / (2 * np.pi * 1j + 1) + assert np.allclose(freq_resp, expected) + + @pytest.mark.parametrize( + "cat_func, expected_A, expected_tau", + [(vcat, [[-1, 0], [0, -1]], [1, 2]), (hcat, [[-1, 0], [0, -1]], [1, 2])], + ) + def test_cat(self, cat_func, expected_A, expected_tau): + # Create two simple delayed state-space systems + sys1 = ss([[-1]], [[1]], [[1]], [[0]]) + dlti1 = ss2dlti(sys1) * delay(1) + dlti2 = ss2dlti(sys1) * delay(2) + dlti_cat = cat_func(dlti1, dlti2) + assert np.allclose(dlti_cat.P.A, np.array(expected_A)) + assert np.allclose(dlti_cat.tau, np.array(expected_tau)) + + def test_issiso(self, delay_siso_tf, wood_berry): + assert delay_siso_tf.issiso() + assert not wood_berry.issiso() diff --git a/control/tests/delay_test.py b/control/tests/delay_test.py index 24263c3b8..65571b727 100644 --- a/control/tests/delay_test.py +++ b/control/tests/delay_test.py @@ -92,3 +92,11 @@ def testT0(self): np.array(refnum), np.array(num)) np.testing.assert_array_almost_equal_nulp( np.array(refden), np.array(den)) + + + + + + + + \ No newline at end of file diff --git a/control/tests/partionedssp_test.py b/control/tests/partionedssp_test.py new file mode 100644 index 000000000..ec5a7c38e --- /dev/null +++ b/control/tests/partionedssp_test.py @@ -0,0 +1,374 @@ +import numpy as np +import pytest +from control.statesp import ss +from control.partitionedssp import PartitionedStateSpace, vcat_pss, hcat_pss + + +class TestPartitionedStateSpace: + def test_init(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + pss = PartitionedStateSpace(sys, 1, 1) + + assert np.array_equal(pss.A, A) + assert np.array_equal(pss.B, B) + assert np.array_equal(pss.C, C) + assert np.array_equal(pss.D, D) + assert np.array_equal(pss.B1, B[:, :1]) + assert np.array_equal(pss.B2, B[:, 1:]) + assert np.array_equal(pss.C1, C[:1, :]) + assert np.array_equal(pss.C2, C[1:, :]) + assert np.array_equal(pss.D11, D[:1, :1]) + assert np.array_equal(pss.D12, D[:1, 1:]) + assert np.array_equal(pss.D21, D[1:, :1]) + assert np.array_equal(pss.D22, D[1:, 1:]) + assert pss.nu1 == 1 + assert pss.ny1 == 1 + assert pss.nu2 == 1 + assert pss.ny2 == 1 + assert pss.nstates == 2 + assert pss.ninputs_total == 2 + assert pss.noutputs_total == 2 + + def test_init_invalid_input(self): + with pytest.raises(TypeError): + PartitionedStateSpace("not a StateSpace", 1, 1) + + def test_init_invalid_nu1(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + with pytest.raises(ValueError): + PartitionedStateSpace(sys, 3, 1) + with pytest.raises(ValueError): + PartitionedStateSpace(sys, -1, 1) + + def test_init_invalid_ny1(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + with pytest.raises(ValueError): + PartitionedStateSpace(sys, 1, 3) + with pytest.raises(ValueError): + PartitionedStateSpace(sys, 1, -1) + + def test_from_matrices(self): + A = np.array([[1, 2], [3, 4]]) + B1 = np.array([[5], [7]]) + B2 = np.array([[6], [8]]) + C1 = np.array([[9, 10]]) + C2 = np.array([[11, 12]]) + D11 = np.array([[13]]) + D12 = np.array([[14]]) + D21 = np.array([[15]]) + D22 = np.array([[16]]) + + pss = PartitionedStateSpace.from_matrices(A, B1, B2, C1, C2, D11, D12, D21, D22) + + assert np.array_equal(pss.A, A) + assert np.array_equal(pss.B1, B1) + assert np.array_equal(pss.B2, B2) + assert np.array_equal(pss.C1, C1) + assert np.array_equal(pss.C2, C2) + assert np.array_equal(pss.D11, D11) + assert np.array_equal(pss.D12, D12) + assert np.array_equal(pss.D21, D21) + assert np.array_equal(pss.D22, D22) + assert pss.nu1 == 1 + assert pss.ny1 == 1 + assert pss.nu2 == 1 + assert pss.ny2 == 1 + assert pss.nstates == 2 + assert pss.ninputs_total == 2 + assert pss.noutputs_total == 2 + + def test_from_matrices_invalid_shapes(self): + A = np.array([[1, 2], [3, 4]]) + B1 = np.array([[5], [7]]) + B2 = np.array([[6], [8]]) + C1 = np.array([[9, 10]]) + C2 = np.array([[11, 12]]) + D11 = np.array([[13]]) + D12 = np.array([[14]]) + D21 = np.array([[15]]) + D22 = np.array([[16]]) + + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, B1, B2, C1, C2, D11, D12, D21, np.array([[16, 17]]) + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, B1, B2, C1, C2, D11, D12, np.array([[15, 16]]), D22 + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, B1, B2, C1, C2, D11, np.array([[14, 15]]), D21, D22 + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, B1, B2, C1, C2, np.array([[13, 14]]), D12, D21, D22 + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, B1, B2, C1, np.array([[11, 12, 13]]), D11, D12, D21, D22 + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, B1, B2, np.array([[9, 10, 11]]), C2, D11, D12, D21, D22 + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, B1, np.array([[6, 7], [8, 9]]), C1, C2, D11, D12, D21, D22 + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + A, np.array([[5, 6], [7, 8]]), B2, C1, C2, D11, D12, D21, D22 + ) + with pytest.raises(ValueError): + PartitionedStateSpace.from_matrices( + np.array([[1, 2, 3], [4, 5, 6]]), B1, B2, C1, C2, D11, D12, D21, D22 + ) + + def test_add_invalid_type(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + pss = PartitionedStateSpace(sys, 1, 1) + with pytest.raises(TypeError): + pss + "not a PartitionedStateSpace" + + def test_mul_invalid_type(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + pss = PartitionedStateSpace(sys, 1, 1) + with pytest.raises(TypeError): + pss * "not a PartitionedStateSpace" + + def test_feedback_invalid_type(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + pss = PartitionedStateSpace(sys, 1, 1) + with pytest.raises(TypeError): + pss.feedback("not a PartitionedStateSpace") + + def test_vcat_pss(self): + A1 = np.array([[1, 2], [3, 4]]) + B1_1 = np.array([[5], [7]]) + B1_2 = np.array([[6], [8]]) + C1_1 = np.array([[9, 10]]) + C1_2 = np.array([[11, 12]]) + D11_1 = np.array([[13]]) + D12_1 = np.array([[14]]) + D21_1 = np.array([[15]]) + D22_1 = np.array([[16]]) + pss1 = PartitionedStateSpace.from_matrices( + A1, B1_1, B1_2, C1_1, C1_2, D11_1, D12_1, D21_1, D22_1 + ) + + A2 = np.array([[1, 2], [3, 4]]) + B2_1 = np.array([[5], [7]]) + B2_2 = np.array([[6], [8]]) + C2_1 = np.array([[9, 10]]) + C2_2 = np.array([[11, 12]]) + D11_2 = np.array([[13]]) + D12_2 = np.array([[14]]) + D21_2 = np.array([[15]]) + D22_2 = np.array([[16]]) + pss2 = PartitionedStateSpace.from_matrices( + A2, B2_1, B2_2, C2_1, C2_2, D11_2, D12_2, D21_2, D22_2 + ) + + pss_vcat = vcat_pss(pss1, pss2) + + assert np.array_equal( + pss_vcat.A, np.block([[A1, np.zeros_like(A1)], [np.zeros_like(A2), A2]]) + ) + assert np.array_equal(pss_vcat.B1, np.vstack((B1_1, B2_1))) + assert np.array_equal( + pss_vcat.B2, + np.block([[B1_2, np.zeros_like(B2_2)], [np.zeros_like(B1_2), B2_2]]), + ) + assert np.array_equal( + pss_vcat.C1, + np.block([[C1_1, np.zeros_like(C2_1)], [np.zeros_like(C1_1), C2_1]]), + ) + assert np.array_equal( + pss_vcat.C2, + np.block([[C1_2, np.zeros_like(C2_2)], [np.zeros_like(C1_2), C2_2]]), + ) + assert np.array_equal(pss_vcat.D11, np.vstack((D11_1, D11_2))) + assert np.array_equal( + pss_vcat.D12, + np.block([[D12_1, np.zeros_like(D12_2)], [np.zeros_like(D12_1), D12_2]]), + ) + assert np.array_equal(pss_vcat.D21, np.vstack((D21_1, D21_2))) + assert np.array_equal( + pss_vcat.D22, + np.block([[D22_1, np.zeros_like(D22_2)], [np.zeros_like(D22_1), D22_2]]), + ) + assert pss_vcat.nu1 == 1 + assert pss_vcat.ny1 == 2 + assert pss_vcat.nu2 == 2 + assert pss_vcat.ny2 == 2 + assert pss_vcat.nstates == 4 + assert pss_vcat.ninputs_total == 3 + assert pss_vcat.noutputs_total == 4 + + def test_vcat_pss_invalid_type(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + pss = PartitionedStateSpace(sys, 1, 1) + with pytest.raises(TypeError): + vcat_pss(pss, "not a PartitionedStateSpace") + + def test_vcat_pss_invalid_input_dimension(self): + A1 = np.array([[1, 2], [3, 4]]) + B1_1 = np.array([[5], [7]]) + B1_2 = np.array([[6], [8]]) + C1_1 = np.array([[9, 10]]) + C1_2 = np.array([[11, 12]]) + D11_1 = np.array([[13]]) + D12_1 = np.array([[14]]) + D21_1 = np.array([[15]]) + D22_1 = np.array([[16]]) + pss1 = PartitionedStateSpace.from_matrices( + A1, B1_1, B1_2, C1_1, C1_2, D11_1, D12_1, D21_1, D22_1 + ) + + A2 = np.array([[1, 2], [3, 4]]) + B2_1 = np.array([[5, 6], [7, 8]]) + B2_2 = np.array([[6, 7], [8, 9]]) + C2_1 = np.array([[9, 10]]) + C2_2 = np.array([[11, 12]]) + D11_2 = np.array([[13, 14]]) + D12_2 = np.array([[14, 15]]) + D21_2 = np.array([[15, 16]]) + D22_2 = np.array([[16, 17]]) + pss2 = PartitionedStateSpace.from_matrices( + A2, B2_1, B2_2, C2_1, C2_2, D11_2, D12_2, D21_2, D22_2 + ) + + with pytest.raises(ValueError): + vcat_pss(pss1, pss2) + + def test_hcat_pss(self): + A1 = np.array([[1, 2], [3, 4]]) + B1_1 = np.array([[5], [7]]) + B1_2 = np.array([[6], [8]]) + C1_1 = np.array([[9, 10]]) + C1_2 = np.array([[11, 12]]) + D11_1 = np.array([[13]]) + D12_1 = np.array([[14]]) + D21_1 = np.array([[15]]) + D22_1 = np.array([[16]]) + pss1 = PartitionedStateSpace.from_matrices( + A1, B1_1, B1_2, C1_1, C1_2, D11_1, D12_1, D21_1, D22_1 + ) + + A2 = np.array([[1, 2], [3, 4]]) + B2_1 = np.array([[5], [7]]) + B2_2 = np.array([[6], [8]]) + C2_1 = np.array([[9, 10]]) + C2_2 = np.array([[11, 12]]) + D11_2 = np.array([[13]]) + D12_2 = np.array([[14]]) + D21_2 = np.array([[15]]) + D22_2 = np.array([[16]]) + pss2 = PartitionedStateSpace.from_matrices( + A2, B2_1, B2_2, C2_1, C2_2, D11_2, D12_2, D21_2, D22_2 + ) + + pss_hcat = hcat_pss(pss1, pss2) + + assert np.array_equal( + pss_hcat.A, np.block([[A1, np.zeros_like(A1)], [np.zeros_like(A2), A2]]) + ) + assert np.array_equal( + pss_hcat.B1, + np.block([[B1_1, np.zeros_like(B2_1)], [np.zeros_like(B1_1), B2_1]]), + ) + assert np.array_equal( + pss_hcat.B2, + np.block([[B1_2, np.zeros_like(B2_2)], [np.zeros_like(B1_2), B2_2]]), + ) + assert np.array_equal(pss_hcat.C1, np.hstack((C1_1, C2_1))) + assert np.array_equal( + pss_hcat.C2, + np.block([[C1_2, np.zeros_like(C2_2)], [np.zeros_like(C1_2), C2_2]]), + ) + assert np.array_equal(pss_hcat.D11, np.hstack((D11_1, D11_2))) + assert np.array_equal(pss_hcat.D12, np.hstack((D12_1, D12_2))) + assert np.array_equal( + pss_hcat.D21, + np.block([[D21_1, np.zeros_like(D21_2)], [np.zeros_like(D21_1), D21_2]]), + ) + assert np.array_equal( + pss_hcat.D22, + np.block([[D22_1, np.zeros_like(D22_2)], [np.zeros_like(D22_1), D22_2]]), + ) + assert pss_hcat.nu1 == 2 + assert pss_hcat.ny1 == 1 + assert pss_hcat.nu2 == 2 + assert pss_hcat.ny2 == 2 + assert pss_hcat.nstates == 4 + assert pss_hcat.ninputs_total == 4 + assert pss_hcat.noutputs_total == 3 + + def test_hcat_pss_invalid_type(self): + A = np.array([[1, 2], [3, 4]]) + B = np.array([[5, 6], [7, 8]]) + C = np.array([[9, 10], [11, 12]]) + D = np.array([[13, 14], [15, 16]]) + sys = ss(A, B, C, D) + pss = PartitionedStateSpace(sys, 1, 1) + with pytest.raises(TypeError): + hcat_pss(pss, "not a PartitionedStateSpace") + + def test_hcat_pss_invalid_output_dimension(self): + A1 = np.array([[1, 2], [3, 4]]) + B1_1 = np.array([[5], [7]]) + B1_2 = np.array([[6], [8]]) + C1_1 = np.array([[9, 10]]) + C1_2 = np.array([[11, 12]]) + D11_1 = np.array([[13]]) + D12_1 = np.array([[14]]) + D21_1 = np.array([[15]]) + D22_1 = np.array([[16]]) + pss1 = PartitionedStateSpace.from_matrices( + A1, B1_1, B1_2, C1_1, C1_2, D11_1, D12_1, D21_1, D22_1 + ) + + A2 = np.array([[1, 2], [3, 4]]) + B2_1 = np.array([[5], [7]]) + B2_2 = np.array([[6], [8]]) + C2_1 = np.array([[9, 10], [11, 12]]) + C2_2 = np.array([[11, 12], [13, 14]]) + D11_2 = np.array([[13], [14]]) + D12_2 = np.array([[14], [15]]) + D21_2 = np.array([[15], [16]]) + D22_2 = np.array([[16], [17]]) + pss2 = PartitionedStateSpace.from_matrices( + A2, B2_1, B2_2, C2_1, C2_2, D11_2, D12_2, D21_2, D22_2 + ) + + with pytest.raises(ValueError): + hcat_pss(pss1, pss2) diff --git a/control/timeresp.py b/control/timeresp.py index bd549589a..7144bdb6e 100644 --- a/control/timeresp.py +++ b/control/timeresp.py @@ -45,28 +45,35 @@ from scipy.linalg import eig, eigvals, matrix_balance, norm from . import config -from . config import _process_kwargs, _process_param +from .config import _process_kwargs, _process_param from .exception import pandas_check from .iosys import NamedSignal, isctime, isdtime from .timeplot import time_response_plot - -__all__ = ['forced_response', 'step_response', 'step_info', - 'initial_response', 'impulse_response', 'TimeResponseData', - 'TimeResponseList'] +from .dde import dde_response + +__all__ = [ + "forced_response", + "step_response", + "step_info", + "initial_response", + "impulse_response", + "TimeResponseData", + "TimeResponseList", +] # Dictionary of aliases for time response commands _timeresp_aliases = { # param: ([alias, ...], [legacy, ...]) - 'timepts': (['T'], []), - 'inputs': (['U'], ['u']), - 'outputs': (['Y'], ['y']), - 'initial_state': (['X0'], ['x0']), - 'final_output': (['yfinal'], []), - 'return_states': (['return_x'], []), - 'evaluation_times': (['t_eval'], []), - 'timepts_num': (['T_num'], []), - 'input_indices': (['input'], []), - 'output_indices': (['output'], []), + "timepts": (["T"], []), + "inputs": (["U"], ["u"]), + "outputs": (["Y"], ["y"]), + "initial_state": (["X0"], ["x0"]), + "final_output": (["yfinal"], []), + "return_states": (["return_x"], []), + "evaluation_times": (["t_eval"], []), + "timepts_num": (["T_num"], []), + "input_indices": (["input"], []), + "output_indices": (["output"], []), } @@ -250,6 +257,7 @@ class TimeResponseData: See `TimeResponseData.__call__` for more information. """ + # # Class attributes # @@ -278,12 +286,27 @@ class TimeResponseData: squeeze = None def __init__( - self, time, outputs, states=None, inputs=None, issiso=None, - output_labels=None, state_labels=None, input_labels=None, - title=None, transpose=False, return_x=False, squeeze=None, - multi_trace=False, trace_labels=None, trace_types=None, - plot_inputs=True, sysname=None, params=None, success=True, - message=None + self, + time, + outputs, + states=None, + inputs=None, + issiso=None, + output_labels=None, + state_labels=None, + input_labels=None, + title=None, + transpose=False, + return_x=False, + squeeze=None, + multi_trace=False, + trace_labels=None, + trace_types=None, + plot_inputs=True, + sysname=None, + params=None, + success=True, + message=None, ): """Create an input/output time response object. @@ -336,8 +359,7 @@ def __init__( raise ValueError("Output vector is the wrong shape") # Check and store labels, if present - self.output_labels = _process_labels( - output_labels, "output", self.noutputs) + self.output_labels = _process_labels(output_labels, "output", self.noutputs) # Make sure time dimension of output is the right length if self.t.shape[-1] != self.y.shape[-1]: @@ -357,9 +379,12 @@ def __init__( self.nstates = self.x.shape[0] # Make sure the shape is OK - if multi_trace and \ - (self.x.ndim != 3 or self.x.shape[1] != self.ntraces) or \ - not multi_trace and self.x.ndim != 2: + if ( + multi_trace + and (self.x.ndim != 3 or self.x.shape[1] != self.ntraces) + or not multi_trace + and self.x.ndim != 2 + ): raise ValueError("State vector is the wrong shape") # Make sure time dimension of state is the right length @@ -367,8 +392,7 @@ def __init__( raise ValueError("State vector does not match time vector") # Check and store labels, if present - self.state_labels = _process_labels( - state_labels, "state", self.nstates) + self.state_labels = _process_labels(state_labels, "state", self.nstates) # # Input vector (optional) @@ -386,16 +410,13 @@ def __init__( self.plot_inputs = plot_inputs # Make sure the shape is OK and figure out the number of inputs - if multi_trace and self.u.ndim == 3 and \ - self.u.shape[1] == self.ntraces: + if multi_trace and self.u.ndim == 3 and self.u.shape[1] == self.ntraces: self.ninputs = self.u.shape[0] - elif multi_trace and self.u.ndim == 2 and \ - self.u.shape[0] == self.ntraces: + elif multi_trace and self.u.ndim == 2 and self.u.shape[0] == self.ntraces: self.ninputs = 1 - elif not multi_trace and self.u.ndim == 2 and \ - self.ntraces == 0: + elif not multi_trace and self.u.ndim == 2 and self.ntraces == 0: self.ninputs = self.u.shape[0] elif not multi_trace and self.u.ndim == 1: @@ -412,19 +433,17 @@ def __init__( raise ValueError("Input vector does not match time vector") # Check and store labels, if present - self.input_labels = _process_labels( - input_labels, "input", self.ninputs) + self.input_labels = _process_labels(input_labels, "input", self.ninputs) # Check and store trace labels, if present - self.trace_labels = _process_labels( - trace_labels, "trace", self.ntraces) + self.trace_labels = _process_labels(trace_labels, "trace", self.ntraces) self.trace_types = trace_types # Figure out if the system is SISO if issiso is None: # Figure out based on the data if self.ninputs == 1: - issiso = (self.noutputs == 1) + issiso = self.noutputs == 1 elif self.ninputs > 1: issiso = False else: @@ -487,25 +506,28 @@ def __call__(self, **kwargs): response = copy(self) # Update any keywords that we were passed - response.transpose = kwargs.pop('transpose', self.transpose) - response.squeeze = kwargs.pop('squeeze', self.squeeze) - response.return_x = kwargs.pop('return_x', self.return_x) + response.transpose = kwargs.pop("transpose", self.transpose) + response.squeeze = kwargs.pop("squeeze", self.squeeze) + response.return_x = kwargs.pop("return_x", self.return_x) # Check for new labels - input_labels = kwargs.pop('input_labels', None) + input_labels = kwargs.pop("input_labels", None) if input_labels is not None: response.input_labels = _process_labels( - input_labels, "input", response.ninputs) + input_labels, "input", response.ninputs + ) - output_labels = kwargs.pop('output_labels', None) + output_labels = kwargs.pop("output_labels", None) if output_labels is not None: response.output_labels = _process_labels( - output_labels, "output", response.noutputs) + output_labels, "output", response.noutputs + ) - state_labels = kwargs.pop('state_labels', None) + state_labels = kwargs.pop("state_labels", None) if state_labels is not None: response.state_labels = _process_labels( - state_labels, "state", response.nstates) + state_labels, "state", response.nstates + ) # Make sure there were no extraneous keywords if kwargs: @@ -515,7 +537,6 @@ def __call__(self, **kwargs): @property def time(self): - """Time vector. Time values of the input/output response(s). @@ -542,8 +563,8 @@ def outputs(self): """ # TODO: move to __init__ to avoid recomputing each time? y = _process_time_response( - self.y, issiso=self.issiso, - transpose=self.transpose, squeeze=self.squeeze) + self.y, issiso=self.issiso, transpose=self.transpose, squeeze=self.squeeze + ) return NamedSignal(y, self.output_labels, self.input_labels) # Getter for states (implements squeeze processing) @@ -566,12 +587,16 @@ def states(self): """ # TODO: move to __init__ to avoid recomputing each time? x = _process_time_response( - self.x, transpose=self.transpose, - squeeze=self.squeeze, issiso=False) + self.x, transpose=self.transpose, squeeze=self.squeeze, issiso=False + ) # Special processing for SISO case: always retain state index - if self.issiso and self.ntraces == 1 and x.ndim == 3 and \ - self.squeeze is not False: + if ( + self.issiso + and self.ntraces == 1 + and x.ndim == 3 + and self.squeeze is not False + ): # Single-input, single-output system with single trace x = x[:, 0, :] @@ -606,8 +631,8 @@ def inputs(self): return None u = _process_time_response( - self.u, issiso=self.issiso, - transpose=self.transpose, squeeze=self.squeeze) + self.u, issiso=self.issiso, transpose=self.transpose, squeeze=self.squeeze + ) return NamedSignal(u, self.input_labels, self.input_labels) # Getter for legacy state (implements non-standard squeeze processing) @@ -630,8 +655,12 @@ def _legacy_states(self): if self.x is None: return None - elif self.ninputs == 1 and self.noutputs == 1 and \ - self.ntraces == 1 and self.x.ndim == 3: + elif ( + self.ninputs == 1 + and self.noutputs == 1 + and self.ntraces == 1 + and self.x.ndim == 3 + ): # Single-input, single-output system with single trace x = self.x[:, 0, :] @@ -686,23 +715,32 @@ def to_pandas(self): import pandas # Create a dict for setting up the data frame - data = {'time': np.tile( - self.time, self.ntraces if self.ntraces > 0 else 1)} + data = {"time": np.tile(self.time, self.ntraces if self.ntraces > 0 else 1)} if self.ntraces > 0: - data['trace'] = np.hstack([ - np.full(self.time.size, label) for label in self.trace_labels]) + data["trace"] = np.hstack( + [np.full(self.time.size, label) for label in self.trace_labels] + ) if self.ninputs > 0: data.update( - {name: self.u[i].reshape(-1) - for i, name in enumerate(self.input_labels)}) + { + name: self.u[i].reshape(-1) + for i, name in enumerate(self.input_labels) + } + ) if self.noutputs > 0: data.update( - {name: self.y[i].reshape(-1) - for i, name in enumerate(self.output_labels)}) + { + name: self.y[i].reshape(-1) + for i, name in enumerate(self.output_labels) + } + ) if self.nstates > 0: data.update( - {name: self.x[i].reshape(-1) - for i, name in enumerate(self.state_labels)}) + { + name: self.x[i].reshape(-1) + for i, name in enumerate(self.state_labels) + } + ) return pandas.DataFrame(data) @@ -725,6 +763,7 @@ def plot(self, *args, **kwargs): # objects. # + class TimeResponseList(list): """List of TimeResponseData objects with plotting capability. @@ -733,6 +772,7 @@ class TimeResponseList(list): plots the individual `TimeResponseData` objects. """ + def plot(self, *args, **kwargs): """Plot a list of time responses. @@ -742,10 +782,9 @@ def plot(self, *args, **kwargs): from .ctrlplot import ControlPlot lines = None - label = kwargs.pop('label', [None] * len(self)) + label = kwargs.pop("label", [None] * len(self)) for i, response in enumerate(self): - cplt = TimeResponseData.plot( - response, *args, label=label[i], **kwargs) + cplt = TimeResponseData.plot(response, *args, label=label[i], **kwargs) if lines is None: lines = cplt.lines else: @@ -808,9 +847,9 @@ def _process_labels(labels, signal, length): # Helper function for checking array_like parameters -def _check_convert_array(in_obj, legal_shapes, err_msg_start, squeeze=False, - transpose=False): - +def _check_convert_array( + in_obj, legal_shapes, err_msg_start, squeeze=False, transpose=False +): """Helper function for checking array_like parameters. * Check type and shape of `in_obj`. @@ -860,14 +899,17 @@ def _check_convert_array(in_obj, legal_shapes, err_msg_start, squeeze=False, """ # convert nearly everything to an array. out_array = np.asarray(in_obj) - if (transpose): + if transpose: out_array = np.transpose(out_array) # Test element data type, elements must be numbers legal_kinds = set(("i", "f", "c")) # integer, float, complex if out_array.dtype.kind not in legal_kinds: - err_msg = "Wrong element data type: '{d}'. Array elements " \ - "must be numbers.".format(d=str(out_array.dtype)) + err_msg = ( + "Wrong element data type: '{d}'. Array elements must be numbers.".format( + d=str(out_array.dtype) + ) + ) raise TypeError(err_msg_start + err_msg) # If array is zero dimensional (in_obj is scalar): @@ -878,7 +920,7 @@ def _check_convert_array(in_obj, legal_shapes, err_msg_start, squeeze=False, if "any" in s_legal: continue the_val = out_array[()] - out_array = np.empty(s_legal, 'd') + out_array = np.empty(s_legal, "d") out_array.fill(the_val) break @@ -902,8 +944,9 @@ def shape_matches(s_legal, s_actual): break else: legal_shape_str = " or ".join([str(s) for s in legal_shapes]) - err_msg = "Wrong shape (rows, columns): {a}. Expected: {e}." \ - .format(e=legal_shape_str, a=str(out_array.shape)) + err_msg = "Wrong shape (rows, columns): {a}. Expected: {e}.".format( + e=legal_shape_str, a=str(out_array.shape) + ) raise ValueError(err_msg_start + err_msg) # Convert shape @@ -918,9 +961,19 @@ def shape_matches(s_legal, s_actual): # Forced response of a linear system def forced_response( - sysdata, timepts=None, inputs=0., initial_state=0., transpose=False, - params=None, interpolate=False, return_states=None, squeeze=None, - **kwargs): + sysdata, + timepts=None, + inputs=0.0, + initial_state=0.0, + transpose=False, + params=None, + interpolate=False, + return_states=None, + squeeze=None, + **kwargs, +): + from .delaylti import DelayLTI + """Compute the output of a linear system given the input. As a convenience for parameters `U`, `X0`: Numbers (scalars) are @@ -1039,12 +1092,14 @@ def forced_response( # Process keyword arguments _process_kwargs(kwargs, _timeresp_aliases) - T = _process_param('timepts', timepts, kwargs, _timeresp_aliases) - U = _process_param('inputs', inputs, kwargs, _timeresp_aliases, sigval=0.) + T = _process_param("timepts", timepts, kwargs, _timeresp_aliases) + U = _process_param("inputs", inputs, kwargs, _timeresp_aliases, sigval=0.0) X0 = _process_param( - 'initial_state', initial_state, kwargs, _timeresp_aliases, sigval=0.) + "initial_state", initial_state, kwargs, _timeresp_aliases, sigval=0.0 + ) return_x = _process_param( - 'return_states', return_states, kwargs, _timeresp_aliases, sigval=None) + "return_states", return_states, kwargs, _timeresp_aliases, sigval=None + ) if kwargs: raise TypeError("unrecognized keyword(s): ", str(kwargs)) @@ -1053,218 +1108,279 @@ def forced_response( if isinstance(sysdata, (list, tuple)): responses = [] for sys in sysdata: - responses.append(forced_response( - sys, T, inputs=U, initial_state=X0, transpose=transpose, - params=params, interpolate=interpolate, - return_states=return_x, squeeze=squeeze)) + responses.append( + forced_response( + sys, + T, + inputs=U, + initial_state=X0, + transpose=transpose, + params=params, + interpolate=interpolate, + return_states=return_x, + squeeze=squeeze, + ) + ) return TimeResponseList(responses) else: sys = sysdata - if not isinstance(sys, (StateSpace, TransferFunction)): + if not isinstance(sys, (StateSpace, TransferFunction, DelayLTI)): if isinstance(sys, NonlinearIOSystem): if interpolate: - warnings.warn( - "interpolation not supported for nonlinear I/O systems") + warnings.warn("interpolation not supported for nonlinear I/O systems") return input_output_response( - sys, T, U, X0, params=params, transpose=transpose, - return_x=return_x, squeeze=squeeze) + sys, + T, + U, + X0, + params=params, + transpose=transpose, + return_x=return_x, + squeeze=squeeze, + ) else: - raise TypeError('Parameter `sys`: must be a `StateSpace` or' - ' `TransferFunction`)') + raise TypeError( + "Parameter `sys`: must be a `StateSpace` or `TransferFunction`)" + ) # If return_x was not specified, figure out the default if return_x is None: - return_x = config.defaults['forced_response.return_x'] + return_x = config.defaults["forced_response.return_x"] # If return_x is used for TransferFunction, issue a warning if return_x and isinstance(sys, TransferFunction): warnings.warn( "return_x specified for a transfer function system. Internal " - "conversion to state space used; results may meaningless.") + "conversion to state space used; results may meaningless." + ) # If we are passed a transfer function and X0 is non-zero, warn the user if isinstance(sys, TransferFunction) and np.any(X0 != 0): warnings.warn( "Non-zero initial condition given for transfer function system. " "Internal conversion to state space used; may not be consistent " - "with given X0.") - - sys = _convert_to_statespace(sys) - A, B, C, D = np.asarray(sys.A), np.asarray(sys.B), np.asarray(sys.C), \ - np.asarray(sys.D) - # d_type = A.dtype - n_states = A.shape[0] - n_inputs = B.shape[1] - n_outputs = C.shape[0] - - # Convert inputs to numpy arrays for easier shape checking - if U is not None: - U = np.asarray(U) - if T is not None: - # T must be array_like - T = np.asarray(T) - - # Set and/or check time vector in discrete-time case - if isdtime(sys): - if T is None: - if U is None or (U.ndim == 0 and U == 0.): - raise ValueError('Parameters `T` and `U` can\'t both be ' - 'zero for discrete-time simulation') - # Set T to equally spaced samples with same length as U - if U.ndim == 1: - n_steps = U.shape[0] - else: - n_steps = U.shape[1] - dt = 1. if sys.dt in [True, None] else sys.dt - T = np.array(range(n_steps)) * dt - else: - if U.ndim == 0: - U = np.full((n_inputs, T.shape[0]), U) + "with given X0." + ) + + if isinstance(sys, DelayLTI): + # step size must be small enough to ensure accuracy. + # Stiff problems may require very small step size or specific dde solver + return dde_response( + sysdata, + T=timepts, + U=inputs, + X0=initial_state, + params=params, + transpose=transpose, + return_x=return_states, + squeeze=squeeze, + ) else: - if T is None: - raise ValueError('Parameter `T` is mandatory for continuous ' - 'time systems.') - - # Test if T has shape (n,) or (1, n); - T = _check_convert_array(T, [('any',), (1, 'any')], - 'Parameter `T`: ', squeeze=True, - transpose=transpose) - - n_steps = T.shape[0] # number of simulation steps - - # equally spaced also implies strictly monotonic increase, - dt = (T[-1] - T[0]) / (n_steps - 1) - if not np.allclose(np.diff(T), dt): - raise ValueError("Parameter `T`: time values must be equally " - "spaced.") - - # create X0 if not given, test if X0 has correct shape - X0 = _check_convert_array(X0, [(n_states,), (n_states, 1)], - 'Parameter `X0`: ', squeeze=True) - - # Test if U has correct shape and type - legal_shapes = [(n_steps,), (1, n_steps)] if n_inputs == 1 else \ - [(n_inputs, n_steps)] - U = _check_convert_array(U, legal_shapes, - 'Parameter `U`: ', squeeze=False, - transpose=transpose) - - xout = np.zeros((n_states, n_steps)) - xout[:, 0] = X0 - yout = np.zeros((n_outputs, n_steps)) - - # Separate out the discrete and continuous-time cases - if isctime(sys, strict=True): - # Solve the differential equation, copied from scipy.signal.ltisys. - - # Faster algorithm if U is zero - # (if not None, it was converted to array above) - if U is None or np.all(U == 0): - # Solve using matrix exponential - expAdt = sp.linalg.expm(A * dt) - for i in range(1, n_steps): - xout[:, i] = expAdt @ xout[:, i-1] - yout = C @ xout - - # General algorithm that interpolates U in between output points + sys = _convert_to_statespace(sys) + A, B, C, D = ( + np.asarray(sys.A), + np.asarray(sys.B), + np.asarray(sys.C), + np.asarray(sys.D), + ) + # d_type = A.dtype + n_states = A.shape[0] + n_inputs = B.shape[1] + n_outputs = C.shape[0] + + # Convert inputs to numpy arrays for easier shape checking + if U is not None: + U = np.asarray(U) + if T is not None: + # T must be array_like + T = np.asarray(T) + + # Set and/or check time vector in discrete-time case + if isdtime(sys): + if T is None: + if U is None or (U.ndim == 0 and U == 0.0): + raise ValueError( + "Parameters `T` and `U` can't both be " + "zero for discrete-time simulation" + ) + # Set T to equally spaced samples with same length as U + if U.ndim == 1: + n_steps = U.shape[0] + else: + n_steps = U.shape[1] + dt = 1.0 if sys.dt in [True, None] else sys.dt + T = np.array(range(n_steps)) * dt + else: + if U.ndim == 0: + U = np.full((n_inputs, T.shape[0]), U) else: - # convert input from 1D array to 2D array with only one row - if U.ndim == 1: - U = U.reshape(1, -1) # pylint: disable=E1103 - - # Algorithm: to integrate from time 0 to time dt, with linear - # interpolation between inputs u(0) = u0 and u(dt) = u1, we solve - # xdot = A x + B u, x(0) = x0 - # udot = (u1 - u0) / dt, u(0) = u0. - # - # Solution is - # [ x(dt) ] [ A*dt B*dt 0 ] [ x0 ] - # [ u(dt) ] = exp [ 0 0 I ] [ u0 ] - # [u1 - u0] [ 0 0 0 ] [u1 - u0] - - M = np.block([[A * dt, B * dt, np.zeros((n_states, n_inputs))], - [np.zeros((n_inputs, n_states + n_inputs)), - np.identity(n_inputs)], - [np.zeros((n_inputs, n_states + 2 * n_inputs))]]) - expM = sp.linalg.expm(M) - Ad = expM[:n_states, :n_states] - Bd1 = expM[:n_states, n_states+n_inputs:] - Bd0 = expM[:n_states, n_states:n_states + n_inputs] - Bd1 - - for i in range(1, n_steps): - xout[:, i] = (Ad @ xout[:, i-1] - + Bd0 @ U[:, i-1] + Bd1 @ U[:, i]) - yout = C @ xout + D @ U - tout = T - - else: - # Discrete type system => use SciPy signal processing toolbox - - # sp.signal.dlsim assumes T[0] == 0 - spT = T - T[0] - - if sys.dt is not True and sys.dt is not None: - # Make sure that the time increment is a multiple of sampling time - - # First make sure that time increment is bigger than sampling time - # (with allowance for small precision errors) - if dt < sys.dt and not np.isclose(dt, sys.dt): - raise ValueError("Time steps `T` must match sampling time") - - # Now check to make sure it is a multiple (with check against - # sys.dt because floating point mod can have small errors - if not (np.isclose(dt % sys.dt, 0) or - np.isclose(dt % sys.dt, sys.dt)): - raise ValueError("Time steps `T` must be multiples of " - "sampling time") - sys_dt = sys.dt - - # sp.signal.dlsim returns not enough samples if - # T[-1] - T[0] < sys_dt * decimation * (n_steps - 1) - # due to rounding errors. - # https://github.com/scipyscipy/blob/v1.6.1/scipy/signal/ltisys.py#L3462 - scipy_out_samples = int(np.floor(spT[-1] / sys_dt)) + 1 - if scipy_out_samples < n_steps: - # parentheses: order of evaluation is important - spT[-1] = spT[-1] * (n_steps / (spT[-1] / sys_dt + 1)) + if T is None: + raise ValueError( + "Parameter `T` is mandatory for continuous time systems." + ) + + # Test if T has shape (n,) or (1, n); + T = _check_convert_array( + T, + [("any",), (1, "any")], + "Parameter `T`: ", + squeeze=True, + transpose=transpose, + ) + + n_steps = T.shape[0] # number of simulation steps + + # equally spaced also implies strictly monotonic increase, + dt = (T[-1] - T[0]) / (n_steps - 1) + if not np.allclose(np.diff(T), dt): + raise ValueError("Parameter `T`: time values must be equally spaced.") + + # create X0 if not given, test if X0 has correct shape + X0 = _check_convert_array( + X0, [(n_states,), (n_states, 1)], "Parameter `X0`: ", squeeze=True + ) + + # Test if U has correct shape and type + legal_shapes = ( + [(n_steps,), (1, n_steps)] if n_inputs == 1 else [(n_inputs, n_steps)] + ) + U = _check_convert_array( + U, legal_shapes, "Parameter `U`: ", squeeze=False, transpose=transpose + ) + + xout = np.zeros((n_states, n_steps)) + xout[:, 0] = X0 + yout = np.zeros((n_outputs, n_steps)) + + # Separate out the discrete and continuous-time cases + if isctime(sys, strict=True): + # Solve the differential equation, copied from scipy.signal.ltisys. + + # Faster algorithm if U is zero + # (if not None, it was converted to array above) + if U is None or np.all(U == 0): + # Solve using matrix exponential + expAdt = sp.linalg.expm(A * dt) + for i in range(1, n_steps): + xout[:, i] = expAdt @ xout[:, i - 1] + yout = C @ xout + + # General algorithm that interpolates U in between output points + else: + # convert input from 1D array to 2D array with only one row + if U.ndim == 1: + U = U.reshape(1, -1) # pylint: disable=E1103 + + # Algorithm: to integrate from time 0 to time dt, with linear + # interpolation between inputs u(0) = u0 and u(dt) = u1, we solve + # xdot = A x + B u, x(0) = x0 + # udot = (u1 - u0) / dt, u(0) = u0. + # + # Solution is + # [ x(dt) ] [ A*dt B*dt 0 ] [ x0 ] + # [ u(dt) ] = exp [ 0 0 I ] [ u0 ] + # [u1 - u0] [ 0 0 0 ] [u1 - u0] + + M = np.block( + [ + [A * dt, B * dt, np.zeros((n_states, n_inputs))], + [ + np.zeros((n_inputs, n_states + n_inputs)), + np.identity(n_inputs), + ], + [np.zeros((n_inputs, n_states + 2 * n_inputs))], + ] + ) + expM = sp.linalg.expm(M) + Ad = expM[:n_states, :n_states] + Bd1 = expM[:n_states, n_states + n_inputs :] + Bd0 = expM[:n_states, n_states : n_states + n_inputs] - Bd1 + + for i in range(1, n_steps): + xout[:, i] = Ad @ xout[:, i - 1] + Bd0 @ U[:, i - 1] + Bd1 @ U[:, i] + + yout = C @ xout + D @ U + tout = T else: - sys_dt = dt # For unspecified sampling time, use time incr - - # Discrete time simulation using signal processing toolbox - dsys = (A, B, C, D, sys_dt) - - # Use signal processing toolbox for the discrete-time simulation - # Transpose the input to match toolbox convention - tout, yout, xout = sp.signal.dlsim(dsys, np.transpose(U), spT, X0) - tout = tout + T[0] - - if not interpolate: - # If dt is different from sys.dt, resample the output - inc = int(round(dt / sys_dt)) - tout = T # Return exact list of time steps - yout = yout[::inc, :] - xout = xout[::inc, :] - else: - # Interpolate the input to get the right number of points - U = sp.interpolate.interp1d(T, U)(tout) + # Discrete type system => use SciPy signal processing toolbox + + # sp.signal.dlsim assumes T[0] == 0 + spT = T - T[0] + + if sys.dt is not True and sys.dt is not None: + # Make sure that the time increment is a multiple of sampling time + + # First make sure that time increment is bigger than sampling time + # (with allowance for small precision errors) + if dt < sys.dt and not np.isclose(dt, sys.dt): + raise ValueError("Time steps `T` must match sampling time") + + # Now check to make sure it is a multiple (with check against + # sys.dt because floating point mod can have small errors + if not (np.isclose(dt % sys.dt, 0) or np.isclose(dt % sys.dt, sys.dt)): + raise ValueError( + "Time steps `T` must be multiples of sampling time" + ) + sys_dt = sys.dt + + # sp.signal.dlsim returns not enough samples if + # T[-1] - T[0] < sys_dt * decimation * (n_steps - 1) + # due to rounding errors. + # https://github.com/scipyscipy/blob/v1.6.1/scipy/signal/ltisys.py#L3462 + scipy_out_samples = int(np.floor(spT[-1] / sys_dt)) + 1 + if scipy_out_samples < n_steps: + # parentheses: order of evaluation is important + spT[-1] = spT[-1] * (n_steps / (spT[-1] / sys_dt + 1)) - # Transpose the output and state vectors to match local convention - xout = np.transpose(xout) - yout = np.transpose(yout) + else: + sys_dt = dt # For unspecified sampling time, use time incr + + # Discrete time simulation using signal processing toolbox + dsys = (A, B, C, D, sys_dt) + + # Use signal processing toolbox for the discrete-time simulation + # Transpose the input to match toolbox convention + tout, yout, xout = sp.signal.dlsim(dsys, np.transpose(U), spT, X0) + tout = tout + T[0] + + if not interpolate: + # If dt is different from sys.dt, resample the output + inc = int(round(dt / sys_dt)) + tout = T # Return exact list of time steps + yout = yout[::inc, :] + xout = xout[::inc, :] + else: + # Interpolate the input to get the right number of points + U = sp.interpolate.interp1d(T, U)(tout) + + # Transpose the output and state vectors to match local convention + xout = np.transpose(xout) + yout = np.transpose(yout) return TimeResponseData( - tout, yout, xout, U, params=params, issiso=sys.issiso(), - output_labels=sys.output_labels, input_labels=sys.input_labels, - state_labels=sys.state_labels, sysname=sys.name, plot_inputs=True, - title="Forced response for " + sys.name, trace_types=['forced'], - transpose=transpose, return_x=return_x, squeeze=squeeze) + tout, + yout, + xout, + U, + params=params, + issiso=sys.issiso(), + output_labels=sys.output_labels, + input_labels=sys.input_labels, + state_labels=sys.state_labels, + sysname=sys.name, + plot_inputs=True, + title="Forced response for " + sys.name, + trace_types=["forced"], + transpose=transpose, + return_x=return_x, + squeeze=squeeze, + ) # Process time responses in a uniform way -def _process_time_response( - signal, issiso=False, transpose=None, squeeze=None): +def _process_time_response(signal, issiso=False, transpose=None, squeeze=None): """Process time response signals. This function processes the outputs (or inputs) of time response @@ -1306,19 +1422,19 @@ def _process_time_response( """ # If squeeze was not specified, figure out the default (might remain None) if squeeze is None: - squeeze = config.defaults['control.squeeze_time_response'] + squeeze = config.defaults["control.squeeze_time_response"] # Figure out whether and how to squeeze output data - if squeeze is True: # squeeze all dimensions + if squeeze is True: # squeeze all dimensions signal = np.squeeze(signal) - elif squeeze is False: # squeeze no dimensions + elif squeeze is False: # squeeze no dimensions pass - elif squeeze is None: # squeeze signals if SISO + elif squeeze is None: # squeeze signals if SISO if issiso: if signal.ndim == 3: - signal = signal[0][0] # remove input and output + signal = signal[0][0] # remove input and output else: - signal = signal[0] # remove input + signal = signal[0] # remove input else: raise ValueError("Unknown squeeze value") @@ -1332,9 +1448,18 @@ def _process_time_response( def step_response( - sysdata, timepts=None, initial_state=0., input_indices=None, - output_indices=None, timepts_num=None, transpose=False, - return_states=False, squeeze=None, params=None, **kwargs): + sysdata, + timepts=None, + initial_state=0.0, + input_indices=None, + output_indices=None, + timepts_num=None, + transpose=False, + return_states=False, + squeeze=None, + params=None, + **kwargs, +): # pylint: disable=W0622 """Compute the step response for a linear system. @@ -1422,18 +1547,16 @@ def step_response( # Process keyword arguments _process_kwargs(kwargs, _timeresp_aliases) - T = _process_param('timepts', timepts, kwargs, _timeresp_aliases) + T = _process_param("timepts", timepts, kwargs, _timeresp_aliases) X0 = _process_param( - 'initial_state', initial_state, kwargs, _timeresp_aliases, sigval=0.) - input = _process_param( - 'input_indices', input_indices, kwargs, _timeresp_aliases) - output = _process_param( - 'output_indices', output_indices, kwargs, _timeresp_aliases) + "initial_state", initial_state, kwargs, _timeresp_aliases, sigval=0.0 + ) + input = _process_param("input_indices", input_indices, kwargs, _timeresp_aliases) + output = _process_param("output_indices", output_indices, kwargs, _timeresp_aliases) return_x = _process_param( - 'return_states', return_states, kwargs, _timeresp_aliases, - sigval=False) - T_num = _process_param( - 'timepts_num', timepts_num, kwargs, _timeresp_aliases) + "return_states", return_states, kwargs, _timeresp_aliases, sigval=False + ) + T_num = _process_param("timepts_num", timepts_num, kwargs, _timeresp_aliases) if kwargs: raise TypeError("unrecognized keyword(s): ", str(kwargs)) @@ -1449,11 +1572,20 @@ def step_response( if isinstance(sysdata, (list, tuple)): responses = [] for sys in sysdata: - responses.append(step_response( - sys, T, initial_state=X0, input_indices=input, - output_indices=output, timepts_num=T_num, - transpose=transpose, return_states=return_x, squeeze=squeeze, - params=params)) + responses.append( + step_response( + sys, + T, + initial_state=X0, + input_indices=input, + output_indices=output, + timepts_num=T_num, + transpose=transpose, + return_states=return_x, + squeeze=squeeze, + params=params, + ) + ) return TimeResponseList(responses) else: sys = sysdata @@ -1463,7 +1595,8 @@ def step_response( warnings.warn( "Non-zero initial condition given for transfer function system. " "Internal conversion to state space used; may not be consistent " - "with given X0.") + "with given X0." + ) # Convert to state space so that we can simulate if isinstance(sys, LTI) and sys.nstates is None: @@ -1500,16 +1633,22 @@ def step_response( # Save a label and type for this plot trace_labels.append(f"From {sys.input_labels[i]}") - trace_types.append('step') + trace_types.append("step") # Create a set of single inputs system for simulation U = np.zeros((sys.ninputs, T.size)) U[i, :] = np.ones_like(T) - response = forced_response(sys, T, U, X0, squeeze=True, params=params) + response = forced_response( + sys, + T, + U, + X0, + squeeze=True, + params=params, + ) inpidx = i if input is None else 0 - yout[:, inpidx, :] = response.y if output is None \ - else response.y[output] + yout[:, inpidx, :] = response.y if output is None else response.y[output] xout[:, inpidx, :] = response.x uout[:, inpidx, :] = U if input is None else U[i] @@ -1517,24 +1656,40 @@ def step_response( issiso = sys.issiso() or (input is not None and output is not None) # Select only the given input and output, if any - input_labels = sys.input_labels if input is None \ - else sys.input_labels[input] - output_labels = sys.output_labels if output is None \ - else sys.output_labels[output] + input_labels = sys.input_labels if input is None else sys.input_labels[input] + output_labels = sys.output_labels if output is None else sys.output_labels[output] return TimeResponseData( - response.time, yout, xout, uout, issiso=issiso, - output_labels=output_labels, input_labels=input_labels, - state_labels=sys.state_labels, title="Step response for " + sys.name, - transpose=transpose, return_x=return_x, squeeze=squeeze, - sysname=sys.name, params=params, trace_labels=trace_labels, - trace_types=trace_types, plot_inputs=False) + response.time, + yout, + xout, + uout, + issiso=issiso, + output_labels=output_labels, + input_labels=input_labels, + state_labels=sys.state_labels, + title="Step response for " + sys.name, + transpose=transpose, + return_x=return_x, + squeeze=squeeze, + sysname=sys.name, + params=params, + trace_labels=trace_labels, + trace_types=trace_types, + plot_inputs=False, + ) def step_info( - sysdata, timepts=None, timepts_num=None, final_output=None, - params=None, SettlingTimeThreshold=0.02, RiseTimeLimits=(0.1, 0.9), - **kwargs): + sysdata, + timepts=None, + timepts_num=None, + final_output=None, + params=None, + SettlingTimeThreshold=0.02, + RiseTimeLimits=(0.1, 0.9), + **kwargs, +): """Step response characteristics (rise time, settling time, etc). Parameters @@ -1634,18 +1789,17 @@ def step_info( # Process keyword arguments _process_kwargs(kwargs, _timeresp_aliases) - T = _process_param('timepts', timepts, kwargs, _timeresp_aliases) - T_num = _process_param( - 'timepts_num', timepts_num, kwargs, _timeresp_aliases) - yfinal = _process_param( - 'final_output', final_output, kwargs, _timeresp_aliases) + T = _process_param("timepts", timepts, kwargs, _timeresp_aliases) + T_num = _process_param("timepts_num", timepts_num, kwargs, _timeresp_aliases) + yfinal = _process_param("final_output", final_output, kwargs, _timeresp_aliases) if kwargs: raise TypeError("unrecognized keyword(s): ", str(kwargs)) if isinstance(sysdata, (StateSpace, TransferFunction, NonlinearIOSystem)): T, Yout = step_response( - sysdata, T, timepts_num=T_num, squeeze=False, params=params) + sysdata, T, timepts_num=T_num, squeeze=False, params=params + ) if yfinal: InfValues = np.atleast_2d(yfinal) else: @@ -1655,9 +1809,11 @@ def step_info( ninputs = sysdata.ninputs else: # Time series of response data - errmsg = ("`sys` must be a LTI system, or time response data" - " with a shape following the python-control" - " time series data convention.") + errmsg = ( + "`sys` must be a LTI system, or time response data" + " with a shape following the python-control" + " time series data convention." + ) try: Yout = np.array(sysdata, dtype=float) except ValueError: @@ -1670,8 +1826,9 @@ def step_info( else: raise ValueError(errmsg) if T is None or Yout.shape[2] != len(np.squeeze(T)): - raise ValueError("For time response data, a matching time vector" - " must be given") + raise ValueError( + "For time response data, a matching time vector must be given" + ) T = np.squeeze(T) noutputs = Yout.shape[0] ninputs = Yout.shape[1] @@ -1701,17 +1858,19 @@ def step_info( # RiseTime tr_lower_index = np.nonzero( sgnInf * (yout - RiseTimeLimits[0] * InfValue) >= 0 - )[0][0] + )[0][0] tr_upper_index = np.nonzero( sgnInf * (yout - RiseTimeLimits[1] * InfValue) >= 0 - )[0][0] + )[0][0] rise_time = T[tr_upper_index] - T[tr_lower_index] # SettlingTime outside_threshold = np.nonzero( - np.abs(yout/InfValue - 1) >= SettlingTimeThreshold)[0] - settled = 0 if outside_threshold.size == 0 \ - else outside_threshold[-1] + 1 + np.abs(yout / InfValue - 1) >= SettlingTimeThreshold + )[0] + settled = ( + 0 if outside_threshold.size == 0 else outside_threshold[-1] + 1 + ) # MIMO systems can have unsettled channels without infinite # InfValue if settled < len(T): @@ -1724,7 +1883,7 @@ def step_info( y_os = (sgnInf * yout).max() dy_os = np.abs(y_os) - np.abs(InfValue) if dy_os > 0: - overshoot = np.abs(100. * dy_os / InfValue) + overshoot = np.abs(100.0 * dy_os / InfValue) else: overshoot = 0 @@ -1732,7 +1891,7 @@ def step_info( y_us_index = (sgnInf * yout).argmin() y_us = yout[y_us_index] if (sgnInf * y_us) < 0: - undershoot = (-100. * y_us / InfValue) + undershoot = -100.0 * y_us / InfValue else: undershoot = 0 @@ -1745,16 +1904,16 @@ def step_info( steady_state_value = InfValue retij = { - 'RiseTime': float(rise_time), - 'SettlingTime': float(settling_time), - 'SettlingMin': float(settling_min), - 'SettlingMax': float(settling_max), - 'Overshoot': float(overshoot), - 'Undershoot': float(undershoot), - 'Peak': float(peak_value), - 'PeakTime': float(peak_time), - 'SteadyStateValue': float(steady_state_value) - } + "RiseTime": float(rise_time), + "SettlingTime": float(settling_time), + "SettlingMin": float(settling_min), + "SettlingMax": float(settling_max), + "Overshoot": float(overshoot), + "Undershoot": float(undershoot), + "Peak": float(peak_value), + "PeakTime": float(peak_time), + "SteadyStateValue": float(steady_state_value), + } retrow.append(retij) ret.append(retrow) @@ -1763,9 +1922,17 @@ def step_info( def initial_response( - sysdata, timepts=None, initial_state=0, output_indices=None, - timepts_num=None, params=None, transpose=False, return_states=False, - squeeze=None, **kwargs): + sysdata, + timepts=None, + initial_state=0, + output_indices=None, + timepts_num=None, + params=None, + transpose=False, + return_states=False, + squeeze=None, + **kwargs, +): # pylint: disable=W0622 """Compute the initial condition response for a linear system. @@ -1836,16 +2003,15 @@ def initial_response( """ # Process keyword arguments _process_kwargs(kwargs, _timeresp_aliases) - T = _process_param('timepts', timepts, kwargs, _timeresp_aliases) + T = _process_param("timepts", timepts, kwargs, _timeresp_aliases) X0 = _process_param( - 'initial_state', initial_state, kwargs, _timeresp_aliases, sigval=0.) - output = _process_param( - 'output_indices', output_indices, kwargs, _timeresp_aliases) + "initial_state", initial_state, kwargs, _timeresp_aliases, sigval=0.0 + ) + output = _process_param("output_indices", output_indices, kwargs, _timeresp_aliases) return_x = _process_param( - 'return_states', return_states, kwargs, _timeresp_aliases, - sigval=False) - T_num = _process_param( - 'timepts_num', timepts_num, kwargs, _timeresp_aliases) + "return_states", return_states, kwargs, _timeresp_aliases, sigval=False + ) + T_num = _process_param("timepts_num", timepts_num, kwargs, _timeresp_aliases) if kwargs: raise TypeError("unrecognized keyword(s): ", str(kwargs)) @@ -1861,10 +2027,19 @@ def initial_response( if isinstance(sysdata, (list, tuple)): responses = [] for sys in sysdata: - responses.append(initial_response( - sys, T, initial_state=X0, output_indices=output, - timepts_num=T_num, transpose=transpose, - return_states=return_x, squeeze=squeeze, params=params)) + responses.append( + initial_response( + sys, + T, + initial_state=X0, + output_indices=output, + timepts_num=T_num, + transpose=transpose, + return_states=return_x, + squeeze=squeeze, + params=params, + ) + ) return TimeResponseList(responses) else: sys = sysdata @@ -1877,22 +2052,39 @@ def initial_response( # Select only the given output, if any yout = response.y if output is None else response.y[output] - output_labels = sys.output_labels if output is None \ - else sys.output_labels[output] + output_labels = sys.output_labels if output is None else sys.output_labels[output] # Store the response without an input return TimeResponseData( - response.t, yout, response.x, None, params=params, issiso=issiso, - output_labels=output_labels, input_labels=None, - state_labels=sys.state_labels, sysname=sys.name, - title="Initial response for " + sys.name, trace_types=['initial'], - transpose=transpose, return_x=return_x, squeeze=squeeze) + response.t, + yout, + response.x, + None, + params=params, + issiso=issiso, + output_labels=output_labels, + input_labels=None, + state_labels=sys.state_labels, + sysname=sys.name, + title="Initial response for " + sys.name, + trace_types=["initial"], + transpose=transpose, + return_x=return_x, + squeeze=squeeze, + ) def impulse_response( - sysdata, timepts=None, input_indices=None, output_indices=None, - timepts_num=None, transpose=False, return_states=False, squeeze=None, - **kwargs): + sysdata, + timepts=None, + input_indices=None, + output_indices=None, + timepts_num=None, + transpose=False, + return_states=False, + squeeze=None, + **kwargs, +): # pylint: disable=W0622 """Compute the impulse response for a linear system. @@ -1970,16 +2162,13 @@ def impulse_response( # Process keyword arguments _process_kwargs(kwargs, _timeresp_aliases) - T = _process_param('timepts', timepts, kwargs, _timeresp_aliases) - input = _process_param( - 'input_indices', input_indices, kwargs, _timeresp_aliases) - output = _process_param( - 'output_indices', output_indices, kwargs, _timeresp_aliases) + T = _process_param("timepts", timepts, kwargs, _timeresp_aliases) + input = _process_param("input_indices", input_indices, kwargs, _timeresp_aliases) + output = _process_param("output_indices", output_indices, kwargs, _timeresp_aliases) return_x = _process_param( - 'return_states', return_states, kwargs, _timeresp_aliases, - sigval=False) - T_num = _process_param( - 'timepts_num', timepts_num, kwargs, _timeresp_aliases) + "return_states", return_states, kwargs, _timeresp_aliases, sigval=False + ) + T_num = _process_param("timepts_num", timepts_num, kwargs, _timeresp_aliases) if kwargs: raise TypeError("unrecognized keyword(s): ", str(kwargs)) @@ -1995,9 +2184,18 @@ def impulse_response( if isinstance(sysdata, (list, tuple)): responses = [] for sys in sysdata: - responses.append(impulse_response( - sys, T, input=input, output=output, T_num=T_num, - transpose=transpose, return_x=return_x, squeeze=squeeze)) + responses.append( + impulse_response( + sys, + T, + input=input, + output=output, + T_num=T_num, + transpose=transpose, + return_x=return_x, + squeeze=squeeze, + ) + ) return TimeResponseList(responses) else: sys = sysdata @@ -2012,10 +2210,12 @@ def impulse_response( # Check to make sure there is not a direct term if np.any(sys.D != 0) and isctime(sys): - warnings.warn("System has direct feedthrough: `D != 0`. The " - "infinite impulse at `t=0` does not appear in the " - "output.\n" - "Results may be meaningless!") + warnings.warn( + "System has direct feedthrough: `D != 0`. The " + "infinite impulse at `t=0` does not appear in the " + "output.\n" + "Results may be meaningless!" + ) # Only single input and output are allowed for now if isinstance(input, (list, tuple)): @@ -2048,7 +2248,7 @@ def impulse_response( # Save a label for this plot trace_labels.append(f"From {sys.input_labels[i]}") - trace_types.append('impulse') + trace_types.append("impulse") # # Compute new X0 that contains the impulse @@ -2063,15 +2263,14 @@ def impulse_response( else: X0 = 0 U = np.zeros((sys.ninputs, T.size)) - U[i, 0] = 1./sys.dt # unit area impulse + U[i, 0] = 1.0 / sys.dt # unit area impulse # Simulate the impulse response for this input response = forced_response(sys, T, U, X0) # Store the output (and states) inpidx = i if input is None else 0 - yout[:, inpidx, :] = response.y if output is None \ - else response.y[output] + yout[:, inpidx, :] = response.y if output is None else response.y[output] xout[:, inpidx, :] = response.x uout[:, inpidx, :] = U if input is None else U[i] @@ -2079,18 +2278,27 @@ def impulse_response( issiso = sys.issiso() or (input is not None and output is not None) # Select only the given input and output, if any - input_labels = sys.input_labels if input is None \ - else sys.input_labels[input] - output_labels = sys.output_labels if output is None \ - else sys.output_labels[output] + input_labels = sys.input_labels if input is None else sys.input_labels[input] + output_labels = sys.output_labels if output is None else sys.output_labels[output] return TimeResponseData( - response.time, yout, xout, uout, issiso=issiso, - output_labels=output_labels, input_labels=input_labels, - state_labels=sys.state_labels, trace_labels=trace_labels, - trace_types=trace_types, title="Impulse response for " + sys.name, - sysname=sys.name, plot_inputs=False, transpose=transpose, - return_x=return_x, squeeze=squeeze) + response.time, + yout, + xout, + uout, + issiso=issiso, + output_labels=output_labels, + input_labels=input_labels, + state_labels=sys.state_labels, + trace_labels=trace_labels, + trace_types=trace_types, + title="Impulse response for " + sys.name, + sysname=sys.name, + plot_inputs=False, + transpose=transpose, + return_x=return_x, + squeeze=squeeze, + ) # utility function to find time period and time increment using pole locations @@ -2142,12 +2350,12 @@ def _ideal_tfinal_and_dt(sys, is_step=True): """ from .statesp import _convert_to_statespace - sqrt_eps = np.sqrt(np.spacing(1.)) - default_tfinal = 5 # Default simulation horizon + sqrt_eps = np.sqrt(np.spacing(1.0)) + default_tfinal = 5 # Default simulation horizon default_dt = 0.1 - total_cycles = 5 # Number cycles for oscillating modes - pts_per_cycle = 25 # Number points divide period of osc - log_decay_percent = np.log(1000) # Reduction factor for real pole decays + total_cycles = 5 # Number cycles for oscillating modes + pts_per_cycle = 25 # Number points divide period of osc + log_decay_percent = np.log(1000) # Reduction factor for real pole decays if sys._isstatic(): tfinal = default_tfinal @@ -2159,13 +2367,12 @@ def _ideal_tfinal_and_dt(sys, is_step=True): p = eigvals(A) # Array Masks # unstable - m_u = (np.abs(p) >= 1 + sqrt_eps) + m_u = np.abs(p) >= 1 + sqrt_eps p_u, p = p[m_u], p[~m_u] if p_u.size > 0: m_u = (p_u.real < 0) & (np.abs(p_u.imag) < sqrt_eps) if np.any(~m_u): - t_emp = np.max( - log_decay_percent / np.abs(np.log(p_u[~m_u]) / dt)) + t_emp = np.max(log_decay_percent / np.abs(np.log(p_u[~m_u]) / dt)) tfinal = max(tfinal, t_emp) # zero - negligible effect on tfinal @@ -2175,25 +2382,25 @@ def _ideal_tfinal_and_dt(sys, is_step=True): m_nr = (p.real < 0) & (np.abs(p.imag) < sqrt_eps) p_nr, p = p[m_nr], p[~m_nr] if p_nr.size > 0: - t_emp = np.max(log_decay_percent / np.abs((np.log(p_nr)/dt).real)) + t_emp = np.max(log_decay_percent / np.abs((np.log(p_nr) / dt).real)) tfinal = max(tfinal, t_emp) # discrete integrators m_int = (p.real - 1 < sqrt_eps) & (np.abs(p.imag) < sqrt_eps) p_int, p = p[m_int], p[~m_int] # pure oscillatory modes - m_w = (np.abs(np.abs(p) - 1) < sqrt_eps) + m_w = np.abs(np.abs(p) - 1) < sqrt_eps p_w, p = p[m_w], p[~m_w] if p_w.size > 0: - t_emp = total_cycles * 2 * np.pi / np.abs(np.log(p_w)/dt).min() + t_emp = total_cycles * 2 * np.pi / np.abs(np.log(p_w) / dt).min() tfinal = max(tfinal, t_emp) if p.size > 0: - t_emp = log_decay_percent / np.abs((np.log(p)/dt).real).min() + t_emp = log_decay_percent / np.abs((np.log(p) / dt).real).min() tfinal = max(tfinal, t_emp) if p_int.size > 0: tfinal = tfinal * 5 - else: # cont time + else: # cont time sys_ss = _convert_to_statespace(sys) # Improve conditioning via balancing and zeroing tiny entries # See for [[1,2,0], [9,1,0.01], [1,2,10*np.pi]] @@ -2203,10 +2410,10 @@ def _ideal_tfinal_and_dt(sys, is_step=True): # Reciprocal of inner product for each eigval, (bound the # ~infs by 1e12) # G = Transfer([1], [1,0,1]) gives zero sensitivity (bound by 1e-12) - eig_sens = np.reciprocal(maximum(1e-12, einsum('ij,ij->j', l, r).real)) + eig_sens = np.reciprocal(maximum(1e-12, einsum("ij,ij->j", l, r).real)) eig_sens = minimum(1e12, eig_sens) # Tolerances - p[np.abs(p) < np.spacing(eig_sens * norm(b, 1))] = 0. + p[np.abs(p) < np.spacing(eig_sens * norm(b, 1))] = 0.0 # Incorporate balancing to outer factors l[perm, :] *= np.reciprocal(sca)[:, None] r[perm, :] *= sca[:, None] @@ -2215,29 +2422,29 @@ def _ideal_tfinal_and_dt(sys, is_step=True): origin = False # Computing the "size" of the response of each simple mode wn = np.abs(p) - if np.any(wn == 0.): + if np.any(wn == 0.0): origin = True dc = np.zeros_like(p, dtype=float) # well-conditioned nonzero poles, np.abs just in case - ok = np.abs(eig_sens) <= 1/sqrt_eps + ok = np.abs(eig_sens) <= 1 / sqrt_eps # the averaged t->inf response of each simple eigval on each i/o # channel. See, A = [[-1, k], [0, -2]], response sizes are # k-dependent (that is R/L eigenvector dependent) - dc[ok] = norm(v[ok, :], axis=1)*norm(w[:, ok], axis=0)*eig_sens[ok] - dc[wn != 0.] /= wn[wn != 0] if is_step else 1. - dc[wn == 0.] = 0. + dc[ok] = norm(v[ok, :], axis=1) * norm(w[:, ok], axis=0) * eig_sens[ok] + dc[wn != 0.0] /= wn[wn != 0] if is_step else 1.0 + dc[wn == 0.0] = 0.0 # double the oscillating mode magnitude for the conjugate - dc[p.imag != 0.] *= 2 + dc[p.imag != 0.0] *= 2 # Now get rid of noncontributing integrators and simple modes if any - relevance = (dc > 0.1*dc.max()) | ~ok + relevance = (dc > 0.1 * dc.max()) | ~ok psub = p[relevance] wnsub = wn[relevance] tfinal, dt = [], [] - ints = wnsub == 0. - iw = (psub.imag != 0.) & (np.abs(psub.real) <= sqrt_eps) + ints = wnsub == 0.0 + iw = (psub.imag != 0.0) & (np.abs(psub.real) <= sqrt_eps) # Pure imaginary? if np.any(iw): @@ -2247,15 +2454,14 @@ def _ideal_tfinal_and_dt(sys, is_step=True): texp_mode = log_decay_percent / np.abs(psub[~iw & ~ints].real) tfinal += texp_mode.tolist() dt += minimum( - texp_mode / 50, - (2 * np.pi / pts_per_cycle / wnsub[~iw & ~ints]) + texp_mode / 50, (2 * np.pi / pts_per_cycle / wnsub[~iw & ~ints]) ).tolist() # All integrators? if len(tfinal) == 0: - return default_tfinal*5, default_dt*5 + return default_tfinal * 5, default_dt * 5 - tfinal = np.max(tfinal)*(5 if origin else 1) + tfinal = np.max(tfinal) * (5 if origin else 1) dt = np.min(dt) return tfinal, dt @@ -2263,14 +2469,13 @@ def _ideal_tfinal_and_dt(sys, is_step=True): def _default_time_vector(sysdata, N=None, tfinal=None, is_step=True): """Returns a time vector that has a reasonable number of points. - if system is discrete time, N is ignored """ + if system is discrete time, N is ignored""" from .lti import LTI if isinstance(sysdata, (list, tuple)): tfinal_max = N_max = 0 for sys in sysdata: - timevec = _default_time_vector( - sys, N=N, tfinal=tfinal, is_step=is_step) + timevec = _default_time_vector(sys, N=N, tfinal=tfinal, is_step=is_step) tfinal_max = max(tfinal_max, timevec[-1]) N_max = max(N_max, timevec.size) return np.linspace(0, tfinal_max, N_max, endpoint=True) @@ -2280,19 +2485,18 @@ def _default_time_vector(sysdata, N=None, tfinal=None, is_step=True): # For non-LTI system, need tfinal if not isinstance(sys, LTI): if tfinal is None: - raise ValueError( - "can't automatically compute T for non-LTI system") + raise ValueError("can't automatically compute T for non-LTI system") elif isinstance(tfinal, (int, float, np.number)): if N is None: return np.linspace(0, tfinal) else: return np.linspace(0, tfinal, N) else: - return tfinal # Assume we got passed something appropriate + return tfinal # Assume we got passed something appropriate N_max = 5000 - N_min_ct = 100 # min points for cont time systems - N_min_dt = 20 # more common to see just a few samples in discrete time + N_min_ct = 100 # min points for cont time systems + N_min_dt = 20 # more common to see just a few samples in discrete time ideal_tfinal, ideal_dt = _ideal_tfinal_and_dt(sys, is_step=is_step) @@ -2301,17 +2505,17 @@ def _default_time_vector(sysdata, N=None, tfinal=None, is_step=True): if tfinal is None: # for discrete time, change from ideal_tfinal if N too large/small # [N_min, N_max] - N = int(np.clip(np.ceil(ideal_tfinal/sys.dt)+1, N_min_dt, N_max)) - tfinal = sys.dt * (N-1) + N = int(np.clip(np.ceil(ideal_tfinal / sys.dt) + 1, N_min_dt, N_max)) + tfinal = sys.dt * (N - 1) else: - N = int(np.ceil(tfinal/sys.dt)) + 1 - tfinal = sys.dt * (N-1) # make tfinal integer multiple of sys.dt + N = int(np.ceil(tfinal / sys.dt)) + 1 + tfinal = sys.dt * (N - 1) # make tfinal integer multiple of sys.dt else: if tfinal is None: # for continuous time, simulate to ideal_tfinal but limit N tfinal = ideal_tfinal if N is None: # [N_min, N_max] - N = int(np.clip(np.ceil(tfinal/ideal_dt)+1, N_min_ct, N_max)) + N = int(np.clip(np.ceil(tfinal / ideal_dt) + 1, N_min_ct, N_max)) return np.linspace(0, tfinal, N, endpoint=True)