<jupyter_start><jupyter_text>Block diagrams  Table of content- Basic components of block diagrams- How to compose block diagrams together- Block diagrams and system properties (e.g., Stability, etc) ------------------  Block Diagrams- One of the control system engineer’s favorite tools for organizing, communicating, simulating and solving problems- Standard representation of interconnected systems and subsystems using Transfer Functions- Makes it easier to identify inputs, outputs and dynamic systems- Block diagrams show us the interrelationship of systems and how signals flow between them.   - variables or signals: arrows ($\rightarrow$)   - systems: blocks with a transfer function, and arrows going in (inputs) and out (outputs)   - summation node: circle with arrows going in (with the appropriate signs) and an arrow going out   - branching points: represents two or more variables are replicas of the variable before the branching point- Always useful during the analysis. For example:     Block Diagram Albegra - Block diagrams can be quite complex, and when that happens it is useful to know how to calculate the transfer function between a specific input and a specific output.- For this, it is better to start with some easy cases: **Cascaded, Parallel and Feedback Loop.**- More complex cases are handled recursively applying these rules. Note that, given that we are restricting the analysis to linear systems, the impact of each input on a specific output is independent of other inputs that can be present (they can all be set to 0).  Cascaded Blocks    This can be easily calculated using algebric manipulation:$$Y(s)=G_2(s)U(s)$$$$U(s)=G_1(s)E(s)$$which means:$$Y(s)=G_2(s)G_1(s)E(s) \Rightarrow G(s) = G_1(s)G_2(s)$$  Parallel    This can be easily calculated using algebric manipulation:$$Y(s)=G_1(s)R(s) + G_2(s)R(s) + G_3(s)R(s) = (G_1(s) + G_2(s) + G_3(s))R(s)$$which means:$$ G(s) = G_1(s) + G_2(s) + G_3(s)$$ Note the presence of:- _take off points_: allow an unaltered signal to go along multiple paths.- _summing junctions_: sum or subtract signals  Feedback Loop    So, how do we do this one? $$Y(s)=G_1(s)*E(s)$$$$E(s)=R(s)-Y_m(s)$$$$Y_m(s)=G_2(s)Y(s)$$or $$Y(s)=G_1(s)*(R(s)-Y_m(s)) \rightarrow Y(s)=G_1(s)*(R(s)-G_2(s)Y(s)) $$and finally:$$Y(s)+G_1(s)G_2(s)Y(s) = G_1(s)*R(s) $$$$ G(s) = \frac{G_1(s)}{1 + G_1(s)G_2(s)} $$  Zero-Pole Cancellations - When we compose systems together we expect the order of the system being equal to the sum of the order of each individual system.- However, when we compose systems together and calculate the transfer function it might happen that the resulting function has less poles.   - In this case, we have cancellations that correspond to "hidden" parts of the system.   Let's consider an example:   where \begin{equation}G_1(s)=\frac{s+1}{s+2}\;\; \text{and}\;\; G_2(s)=\frac{1}{s}\end{equation} We reduce the feedback loop first:$$G_3(s)=\frac{1/s}{1+1/s} = \frac{1/s}{\frac{s+1}{s}} = \frac{1}{s+1}$$Then the serie:$$G(s) = \frac{s+1}{s+2} \frac{1}{s+1} = \frac{1}{s+2} $$This connection between two systems determined an "hidden" part, which is not visible in the input/output relationship and the system behaves like a first-order one.  Observability and Controllobality of Interconnected Systems: Cascaded Blocks    $$G(s) = G_1(s)G_2(s) = \frac{N_1(s)}{D_1(s)}\frac{N_2(s)}{D_2(s)}$$- If $N_1(s)$ and $D_2(s)$ have roots in common, then $G(s)$ is not controllable (zero-pole cancellation)- If $N_2(s)$ and $D_1(s)$ have roots in common, then $G(s)$ is not observable (pole-zero cancellation)  Observability and Controllobality of Interconnected Systems: Parallel Blocks    $$G(s) = G_1(s) + G_2(s) = \frac{N_1(s)}{D_1(s)} + \frac{N_2(s)}{D_2(s)} = \frac{N_1(s)D_2(s) + N_2(s)D_1(s)}{D_1(s)D_2(s)}$$- Cancellations can happen only when $D_1(s)$ and $D_2(s)$ have common roots (you can bring them out together in the numerator and then cancel them out with those in the denumerator). When this happens, we have a **non-observable and non-controllable** part of the system.  Observability and Controllobality of Interconnected Systems: Feedback Loop    $$G(s) = \frac{G_1(s)}{1 + G_1(s)G_2(s)} =\frac{\frac{N_1(s)}{D_1(s)}}{1 + \frac{N_1(s)}{D_1(s)}\frac{N_2(s)}{D_2(s)}} = \frac{N_1(s)D_2(s)}{D_1(s)D_2(s) + N_1(s)N_2(s)}$$- We have the equivalent of a serie interconnection: $G_1(s)G_2(s)$, same rules apply- Cancellations occur when $N_1(s)$ and $D_2(s)$ have common factors (you can bring them out together in the numerator and then cancel them out with those in the denumerator):   - When zero of $G_1(s)$ cancel a pole of $G_2(s)$ then we have a **non-observable and non-controllable** part of the system.   - When pole of $G_1(s)$ cancel a zero of $G_2(s)$ then we still have a **fully observable and fully controllable** system. This is because the denominator of $G(s)$ does not lose its degree Example:- zero of $G_1(s)$ cancels a pole of $G_2(s)$$$G_1(s)=\frac{s+1}{s+2}, G_2(s)=\frac{s+3}{s+1}$$$$\Rightarrow G(s)= \frac{\frac{s+1}{s+2}}{1+\frac{s+3}{s+2}} = \frac{s+3}{1+(s+3)}$$- pole of $G_1(s)$ cancels a zero of $G_2(s)$$$G_1(s)=\frac{s+1}{s+2}, G_2(s)=\frac{s+2}{s+3}$$$$\Rightarrow G(s)= \frac{\frac{s+1}{s+2}}{1+\frac{s+1}{s+3}} = \frac{\frac{(s+1)(s+3)}{s+2}}{(s+3)+(s+1)} = \frac{(s+1)(s+3)}{(s+2)((s+3)+(s+1))}$$  Stability Let's now analyse the stability of a system which is obtained through a composition of subsystems.Question:- Is the asymptotic stability of each indidual subsystem necessary/sufficient to guarantee the stability of the entire system?Assumption:-  Each subsystem are represented by rational functions with numerator and denominator prime between them.  Cascaded Bloacks    $$G(s) = G_1(s)G_2(s) = \frac{N_1(s)}{D_1(s)}\frac{N_2(s)}{D_2(s)}$$**No zero-pole cancellations**- The denominator of $G(s)$ is the product of each denominator, and the poles of $G(s)$ are the union of the poles of each subsystem.- If and only if, the poles of $G_1(s)$ and $G_2(s)$ have Re $<0$, then $G(s)$ is asymptotically stable.**Zero-pole/pole-zero cancellations**- In this case, the overall system has an hidden part corresponding to the zero-pole or pole-zero cancellation. In this case, _if the cancellation is of a pole with Re $>=0$ then the hidden part is not stable and the entire system is not stable_.- We call these types of cancellations, **critical**.- Note that this is not visible directly from the T.F.!  Parallel Blocks    $$G(s) = G_1(s) + G_2(s) = \frac{N_1(s)}{D_1(s)} + \frac{N_2(s)}{D_2(s)} = \frac{N_1(s)D_2(s) + N_2(s)D_1(s)}{D_1(s)D_2(s)}$$ The analysis is similar to the previous case (serie).If there are no cancellations, the poles of $G(s)$ are the product of the poles of $G_1(s)$ and of $G_2(s)$.- $G(s)$ is asymptotically stable, if and only if, the poles of $G_1(s)$ and $G_2(s)$ have Re $<0$.If there are cancellations, then the overall system has an hidden part corresponding to the zero-pole or pole-zero cancellation. If the cancellation is of a pole with Re $>=0$ then the hidden part is not stable and the entire system is not stable.Again, this is not visible analysing the final T.F.  Feedback Loop    $$G(s) = \frac{G_1(s)}{1 + G_1(s)G_2(s)} =\frac{\frac{N_1(s)}{D_1(s)}}{1 + \frac{N_1(s)}{D_1(s)}\frac{N_2(s)}{D_2(s)}} = \frac{N_1(s)D_2(s)}{D_1(s)D_2(s) + N_1(s)N_2(s)}$$- In this case, the poles of the entire system are not only dependednt on the poles of the two subsytems (the numerators are also playing a role).If we do not have cancellations, then the poles of the systems are the roots of the equation:$$D_1(s)D_2(s) + N_1(s)N_2(s) = 0$$_this is called characteristic equation of the feedback system._We can also write the equation above as:$$ 1 + \frac{N_1(s)D_2(s)}{D_1(s)D_2(s)} = 0$$The feedback system is asymptotically stable if and only if the roots of the previous equation are with Re $<0$, but this does not depend on the stability property of the individual systems.**Critical cancellations**- Note that when we calculate $\frac{N_1(s)D_2(s)}{D_1(s)D_2(s)}$ we can have _critical cancellations_ and these would correspond to hidden parts of the system (Note that we are assuming to use $y$ or $y_m$ as output variables of the system).- As before, if these critical cancellations are of poles with Re $\ge0$, these hidden parts are not asymptotically stable and neither is the full system.- This is not visible from the analysis of the T.F.- We have to verify if there are _critical_ cancellations before we analyse the stability of the feedback system.Since the poles of the feedback system are, in general, different from the poles of the individual subsystems, we can use this connection to effectively modify the dynamics of the system, placing the poles where we need them. For example, it is possible to stabilise a non-stable system, or obtain desired performance.Note that the opposite is also true: we can have a non-stable system even if we the individual subsystems are stable.  BIBO Stability- A system is BIBO stable (Bounded Input, Bounded Output) if for each limited input, there is a limited output- For linear systems: BIBO stability if and only if poles of the transfer functions have Re $<0$- **Caution! The poles of the T.F. are only those that are controllable and observable!**- BIBO stability only depends on the forced response of the system.  Example: Car driving uphill    We can model a car moving uphill using a linear model (small angle approximation) as:\begin{equation}A=\frac{df}{dx}=\begin{bmatrix}0 & 1\\0 & -\frac{\beta}{m}\end{bmatrix}\end{equation}\begin{equation}B=\frac{df}{du}=\begin{bmatrix}0 & 0\\\frac{\gamma}{m} & -g\end{bmatrix}\end{equation}and$$\dot{\mathbf{x}}=A\mathbf{x}+B\mathbf{u}$$$$y=[0 \; 1]\mathbf{x}$$Note that:- If $\theta = 0$ and $u=0$ (no slope, and no input)- then the equilibrium is $\forall x_1$, $x_2=0$ (the position does not matter, speed is 0).- this system has two inputs: the pedal and gravity We can calculate the Matrix Transfer Function as:$$G(s)=C(sI-A)^{-1}Bu$$\begin{equation}(sI-A) =\begin{bmatrix}s & -1\\0 & s+\frac{\beta}{m}\end{bmatrix}\end{equation}\begin{equation}(sI-A)^{-1} = \frac{1}{s(s+ \frac{\beta}{m})} \begin{bmatrix}s+ \frac{\beta}{m} & 1\\0 & s\end{bmatrix}\end{equation}\begin{equation}G(s) = \frac{1}{s(s+ \frac{\beta}{m})} \begin{bmatrix}s \frac{\gamma}{m} & -sg\end{bmatrix} =\begin{bmatrix}\frac{\frac{\gamma}{m}}{s+\frac{\beta}{m}} & \frac{-g}{s+\frac{\beta}{m}}\end{bmatrix}\end{equation}If we want to know the T.F. from the pedal to the velocity:$$G(s)=\frac{\frac{\gamma}{m}}{s+\frac{\beta}{m}}$$ What happens when we apply a step onto the pedal?Let's study the transfer function and include the input:$$Y(s) = G(s)U(s)=\frac{\frac{\gamma}{m}}{s+\frac{\beta}{m}} \frac{1}{s} = \frac{\alpha_1}{s+\frac{\beta}{m}} \frac{\alpha_2}{s}$$where $$\alpha_1=-\frac{\gamma}{\beta}, \; \alpha_2=\frac{\gamma}{\beta}$$and the inverse transform is:$$ y(t) = \alpha_1 e^{-\frac{\beta}{m}t} + \alpha_2 1(t)$$ We can implement this in Python:Let's define the step function first<jupyter_code>from classical_control_theory.intro_to_control_theory import step

# def step(t, step_time=0):
#     """Heaviside step function"""
#     return 1 * (t >= step_time)<jupyter_output><empty_output><jupyter_text>And now we can plot the response of the system $y(t)$ to the step input:<jupyter_code># We decide the parameters of the car
m = 1
beta = 1
gamma = 1

# We set the simulation parameters
t = np.linspace(0, 15)

# Now we can calculate the output of the system
alpha_1 = -gamma/beta
alpha_2 = gamma/beta

y = alpha_1*np.exp(-beta/m*t) + alpha_2*step(t)<jupyter_output><empty_output><jupyter_text>And we can plot it:<jupyter_code>plt.plot(t, y, linewidth=4)
plt.xlabel('time (s)')
plt.ylabel('speed (m/s)')
plt.grid()<jupyter_output><empty_output><jupyter_text>Essentially we predict that the car will increase its speed to try and match our step input. But what does the actual car do?We implemented a car already in notebook `02_Intro_to_control_theory`, but here it is again for convenience:<jupyter_code>class Car:
    _g = 9.8 # Gravity
    
    def __init__(self, x0, params):
        self._x_1 = x0[0] # position (along the road)
        self._x_2 = x0[1] # velocity (along the road)
        self._m, self._alpha, self._beta, self._gamma = params
        
    def step(self, dt, u, theta):
        self.theta = theta
        self._x_1 = self._x_1 + dt*self._x_2
        self._x_2 = self._x_2 + dt*(-self._alpha/self._m*abs(self._x_2)*self._x_2 - \
                                    self._beta/self._m*self._x_2 + self._gamma/self._m*u - \
                                    Car._g*np.sin(theta))
        
    def speedometer(self):        
        v = self._x_2
        return (v,)  
    
    # Utility function to simplify plotting
    def sensor_i(self):
        # Rotation matrix to get back to the main frame.
        R = np.array(((np.cos(theta), -np.sin(theta)), (np.sin(theta), np.cos(theta))))
        x_i, y_i = R.dot(np.array([[self._x_1],[0]]))
        v = self._x_2
        return (x_i, y_i, v)<jupyter_output><empty_output><jupyter_text>We can now run it:<jupyter_code># We define the parameters of the car
m = 1
alpha = 1
beta = 1
gamma = 1
params = (m, alpha, beta, gamma)

# Let's define its initial conditions
x_0 = (0,0) # position and speed
theta = np.radians(0) # no slope

# Define the input: step function
u = 1 # input (e.g. 0 or 1)

# Create the car
car = Car(x_0, params)

# run it!
t0, tf, dt = 0, 10, 0.1

#rotation to get back to the inertial frame..
c, s = np.cos(theta), np.sin(theta)
R = np.array(((c, -s), (s, c)))

position = []
velocity = []
time = []
for t in np.arange(t0, tf, dt):
    car.step(dt, u, theta)
    v = car.speedometer() # this is in the along road frame
    velocity.append(v)
    time.append(t)
    
plt.plot(np.arange(t0, tf, dt), velocity, linewidth=3)
plt.xlabel('time (s)')
plt.ylabel('speed (m/s)')
plt.grid()<jupyter_output><empty_output><jupyter_text>Hmm, the speed has a step response but it does not go to 1...The reason is that the actual car does behave differently from our linear model. Let's try another car:<jupyter_code>#export
class SimplerCar:
    _g = 9.8
    
    def __init__(self, x0, params):
        self._x_1 = x0[0] # position (along the road)
        self._x_2 = x0[1] # velocity
        self._m, self._alpha, self._beta, self._gamma = params
        
    def step(self, dt, u, theta):
        self.theta = theta
        self._x_1 = self._x_1 + dt*self._x_2
        self._x_2 = self._x_2 + dt*(- self._beta/self._m*self._x_2 + \
                                    self._gamma/self._m*u - Car._g*np.sin(theta))
        
    def speedometer(self):        
        v = self._x_2
        return (v,)<jupyter_output><empty_output><jupyter_text>And now we can run it again,<jupyter_code># We define the parameters of the car
m = 1
alpha = 1
beta = 1
gamma = 1
params = (m, alpha, beta, gamma)

# Let's define its initial conditions
x_0 = (0,0) # position and speed
theta = np.radians(0) # no slope

# Define the input: step function
u = 1 # input (e.g. 0 or 1)

# Create the car
car = SimplerCar(x_0, params)

# run it!
t0, tf, dt = 0, 10, 0.1

#rotation to get back to the inertial frame..
c, s = np.cos(theta), np.sin(theta)
R = np.array(((c, -s), (s, c)))

position = []
velocity = []
time = []
for t in np.arange(t0, tf, dt):
    car.step(dt, u, theta)
    v = car.speedometer() # this is in the along road frame
    velocity.append(v)
    time.append(t)
    
plt.plot(time, velocity, linewidth=3)
plt.xlabel('time (s)')
plt.ylabel('speed (m/s)')
plt.grid()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1469628112398387, 'fraction_numerical': 0.020546629968635984, 'mean_word_length': 4.194148936170213, 'pattern_counts': {'":': 0, '<': 33, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '479006', 'license': 'Apache-2.0', 'n_tokens_mistral': 5565, 'n_tokens_neox': 5315, 'n_words': 1863, 'path': '04_Block_Diagrams.ipynb', 'repo_name': 'andreamunafo/automatic_control', 'size': '15623'}