Coverage for D:\Ralf Gerlich\git\modypy\modypy\linearization.py : 0%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Provides functions to determine the jacobi matrix for linearizing the system
3around a given state with specified inputs.
4"""
5from typing import List
7import numpy as np
8from scipy.misc import central_diff_weights
10from modypy.model import Port, System, SystemState
13class LinearizationConfiguration:
14 """
15 Represents the configuration for the determination of the system jacobian
18 Attributes:
19 system
20 The system for which the jacobian shall be determined
21 time
22 The system time for which the jacobian shall be determined
23 (default: 0)
24 state
25 The state around which the jacobian shall be determined (default: 0)
26 inputs
27 The input values around which the jacobian shall be determined
28 (default: 0)
29 outputs
30 List of :class:`OutputDescriptor` instances for the signals to be
31 considered as outputs (default: representations of all output ports
32 in the system)
33 num_outputs
34 The sum of sizes of all signals to be considered as outputs
35 default_step_size
36 The default step size to use for the numerical differentiation
37 (default: 0.1)
38 interpolation_order
39 The interpolation order to use for numerical differentiation
40 (default: 3)
42 """
44 def __init__(self,
45 system: System,
46 time=0,
47 state=None,
48 inputs=None):
49 self.system = system
50 self.time = time
52 if state is None:
53 self.state = np.zeros(self.system.num_states)
54 else:
55 self.state = state
57 if inputs is None:
58 self.inputs = np.zeros(self.system.num_inputs)
59 else:
60 self.inputs = inputs
62 self.outputs: List[OutputDescriptor] = list()
63 self.num_outputs = 0
65 self.default_step_size = 0.1
66 self.interpolation_order = 3
69class OutputDescriptor:
70 """
71 Represents information about an output signal for the determination of the
72 system jacobian
74 Attributes:
75 config
76 The :class:`LinearizationConfiguration` instance to which this
77 output descriptor belongs
78 port
79 The :class:`port <modypy.model.Port>` object that is used as output
80 output_index
81 The index of the first line allocated to this signal in the
82 output and feed-through matrices
83 """
85 def __init__(self, config: LinearizationConfiguration, port: Port):
86 self.config = config
87 self.port = port
89 # Assign an output index
90 self.output_index = self.config.num_outputs
91 self.config.num_outputs += self.port.size
92 self.config.outputs.append(self)
94 @property
95 def output_slice(self):
96 """A slice representing the index range of this output"""
98 return slice(self.output_index, self.port.size)
101def system_jacobian(config: LinearizationConfiguration,
102 single_matrix=False):
103 """Numerically determine the jacobian of the system at the given state and
104 input setting.
106 The function uses polygonal interpolation of the given order on the
107 components of the system derivative and output function, choosing
108 interpolation points at a given distance from the given state and input
109 values.
111 This can be used in conjunction with `find_steady_state` to determine
112 an LTI approximating the behaviour around the steady state.
114 NOTE: This function currently does not honor clocks.
116 Args:
117 config
118 The :class:`LinearizationConfiguration` instance describing the
119 linearization to be performed
120 single_matrix
121 Flag indicating whether a single matrix shall be returned. The
122 default is `False`.
124 Returns:
125 the jacobian, if `single_matrix` is `True`, and a tuple of
126 system matrix, input matrix, output matrix and feed-through matrix, if
127 `single_matrix` is `False`.
128 Raises:
129 ValueError if the system does not have states or inputs
130 """
132 if config.system.num_states + config.system.num_inputs == 0:
133 raise ValueError("Cannot linearize system without states and inputs")
135 num_invars = config.system.num_states + config.system.num_inputs
136 num_outvars = config.system.num_states + config.num_outputs
137 half_offset = config.interpolation_order >> 1
138 weights = _get_central_diff_weights(config.interpolation_order)
140 jac = np.zeros((num_outvars, num_invars))
141 x_ref0 = np.concatenate((config.state, config.inputs), axis=None)
143 for var_ind in range(num_invars):
144 x_step = config.default_step_size * np.eye(N=1,
145 M=num_invars,
146 k=var_ind).ravel()
147 for k in range(config.interpolation_order):
148 x_k = x_ref0 + (k - half_offset) * x_step
149 y_k = _system_function(config, x_k)
150 jac[:, var_ind] += weights[k] * y_k
151 jac[:, var_ind] /= config.default_step_size
153 if single_matrix == "struct":
154 system_matrix = jac[:config.system.num_states,
155 :config.system.num_states]
156 input_matrix = jac[:config.system.num_states,
157 config.system.num_states:]
158 output_matrix = jac[config.system.num_states:,
159 :config.system.num_states]
160 feed_through_matrix = jac[config.system.num_states:,
161 config.system.num_states:]
162 return SystemJacobian(config=config,
163 system_matrix=system_matrix,
164 input_matrix=input_matrix,
165 output_matrix=output_matrix,
166 feed_through_matrix=feed_through_matrix)
167 if single_matrix:
168 return jac
169 return jac[:config.system.num_states, :config.system.num_states], \
170 jac[:config.system.num_states, config.system.num_states:], \
171 jac[config.system.num_states:, :config.system.num_states], \
172 jac[config.system.num_states:, config.system.num_states:]
175class SystemJacobian:
176 def __init__(self,
177 config: LinearizationConfiguration,
178 system_matrix,
179 input_matrix,
180 output_matrix,
181 feed_through_matrix):
182 self.config = config
183 self.system_matrix = system_matrix
184 self.input_matrix = input_matrix
185 self.output_matrix = output_matrix
186 self.feed_through_matrix = feed_through_matrix
189def _get_central_diff_weights(order):
190 """Determine the weights for central differentiation"""
192 if order == 3:
193 weights = np.array([-1, 0, 1]) / 2.0
194 elif order == 5:
195 weights = np.array([1, -8, 0, 8, -1]) / 12.0
196 elif order == 7:
197 weights = np.array([-1, 9, -45, 0, 45, -9, 1]) / 60.0
198 elif order == 9:
199 weights = np.array([3, -32, 168, -672, 0, 672, -168, 32, -3]) / 840.0
200 else:
201 weights = central_diff_weights(order, 1)
202 return weights
205def _system_function(config: LinearizationConfiguration, x_ref):
206 """
207 Determine the value of the vector of state derivatives and outputs
208 given the vector of states and inputs
210 Args:
211 config
212 The :class:`LinearizationConfiguration` instance describing the
213 linearization to be performed
214 x_ref
215 The vector of states and inputs
217 Returns:
218 The vector of state derivatives and outputs
219 """
220 state = x_ref[:config.system.num_states]
221 inputs = x_ref[config.system.num_states:]
223 system_state = SystemState(time=config.time,
224 system=config.system,
225 state=state,
226 inputs=inputs)
228 outputs = np.zeros(config.num_outputs)
229 for output in config.outputs:
230 outputs[output.output_index:output.output_index + output.port.size] = \
231 np.ravel(output.port(system_state))
233 return np.concatenate((config.system.state_derivative(system_state),
234 outputs))