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) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy