| 
 | 1 | +# Linear Analysis  | 
 | 2 | + | 
 | 3 | +Linear analysis refers to the process of linearizing a nonlinear model and analysing the resulting linear dynamical system. To facilitate linear analysis, ModelingToolkit provides the concept of an [`AnalysisPoint`](@ref), which can be inserted in-between two causal blocks (such as those from `ModelingToolkitStandardLibrary.Blocks` sub module). Once a model containing analysis points is built, several operations are available:  | 
 | 4 | + | 
 | 5 | +  - [`get_sensitivity`](@ref) get the [sensitivity function (wiki)](https://en.wikipedia.org/wiki/Sensitivity_(control_systems)), $S(s)$, as defined in the field of control theory.  | 
 | 6 | +  - [`get_comp_sensitivity`](@ref) get the complementary sensitivity function $T(s) : S(s)+T(s)=1$.  | 
 | 7 | +  - [`get_looptransfer`](@ref) get the (open) loop-transfer function where the loop starts and ends in the analysis point. For a typical simple feedback connection with a plant $P(s)$ and a controller $C(s)$, the loop-transfer function at the plant output is $P(s)C(s)$.  | 
 | 8 | +  - [`linearize`](@ref) can be called with two analysis points denoting the input and output of the linearized system.  | 
 | 9 | +  - [`open_loop`](@ref) return a new (nonlinear) system where the loop has been broken in the analysis point, i.e., the connection the analysis point usually implies has been removed.  | 
 | 10 | + | 
 | 11 | +An analysis point can be created explicitly using the constructor [`AnalysisPoint`](@ref), or automatically when connecting two causal components using `connect`:  | 
 | 12 | + | 
 | 13 | +```julia  | 
 | 14 | +connect(comp1.output, :analysis_point_name, comp2.input)  | 
 | 15 | +```  | 
 | 16 | + | 
 | 17 | +A single output can also be connected to multiple inputs:  | 
 | 18 | + | 
 | 19 | +```julia  | 
 | 20 | +connect(comp1.output, :analysis_point_name, comp2.input, comp3.input, comp4.input)  | 
 | 21 | +```  | 
 | 22 | + | 
 | 23 | +!!! warning "Causality"  | 
 | 24 | +      | 
 | 25 | +    Analysis points are *causal*, i.e., they imply a directionality for the flow of information. The order of the connections in the connect statement is thus important, i.e., `connect(out, :name, in)` is different from `connect(in, :name, out)`.  | 
 | 26 | + | 
 | 27 | +The directionality of an analysis point can be thought of as an arrow in a block diagram, where the name of the analysis point applies to the arrow itself.  | 
 | 28 | + | 
 | 29 | +```  | 
 | 30 | +┌─────┐         ┌─────┐  | 
 | 31 | +│     │  name   │     │  | 
 | 32 | +│  out├────────►│in   │  | 
 | 33 | +│     │         │     │  | 
 | 34 | +└─────┘         └─────┘  | 
 | 35 | +```  | 
 | 36 | + | 
 | 37 | +This is signified by the name being the middle argument to `connect`.  | 
 | 38 | + | 
 | 39 | +Of the above mentioned functions, all except for [`open_loop`](@ref) return the output of [`ModelingToolkit.linearize`](@ref), which is  | 
 | 40 | + | 
 | 41 | +```julia  | 
 | 42 | +matrices, simplified_sys = linearize(...)  | 
 | 43 | +# matrices = (; A, B, C, D)  | 
 | 44 | +```  | 
 | 45 | + | 
 | 46 | +i.e., `matrices` is a named tuple containing the matrices of a linear state-space system on the form  | 
 | 47 | + | 
 | 48 | +```math  | 
 | 49 | +\begin{aligned}  | 
 | 50 | +\dot x &= Ax + Bu\\  | 
 | 51 | +y &= Cx + Du  | 
 | 52 | +\end{aligned}  | 
 | 53 | +```  | 
 | 54 | + | 
 | 55 | +## Example  | 
 | 56 | + | 
 | 57 | +The following example builds a simple closed-loop system with a plant $P$ and a controller $C$. Two analysis points are inserted, one before and one after $P$. We then derive a number of sensitivity functions and show the corresponding code using the package ControlSystemBase.jl  | 
 | 58 | + | 
 | 59 | +```@example LINEAR_ANALYSIS  | 
 | 60 | +using ModelingToolkitStandardLibrary.Blocks, ModelingToolkit  | 
 | 61 | +@named P = FirstOrder(k = 1, T = 1) # A first-order system with pole in -1  | 
 | 62 | +@named C = Gain(-1)             # A P controller  | 
 | 63 | +t = ModelingToolkit.get_iv(P)  | 
 | 64 | +eqs = [connect(P.output, :plant_output, C.input)  # Connect with an automatically created analysis point called :plant_output  | 
 | 65 | +       connect(C.output, :plant_input, P.input)]  | 
 | 66 | +sys = ODESystem(eqs, t, systems = [P, C], name = :feedback_system)  | 
 | 67 | +
  | 
 | 68 | +matrices_S = get_sensitivity(sys, :plant_input)[1] # Compute the matrices of a state-space representation of the (input)sensitivity function.  | 
 | 69 | +matrices_T = get_comp_sensitivity(sys, :plant_input)[1]  | 
 | 70 | +```  | 
 | 71 | + | 
 | 72 | +Continued linear analysis and design can be performed using ControlSystemsBase.jl.  | 
 | 73 | +We create `ControlSystemsBase.StateSpace` objects using  | 
 | 74 | + | 
 | 75 | +```@example LINEAR_ANALYSIS  | 
 | 76 | +using ControlSystemsBase, Plots  | 
 | 77 | +S = ss(matrices_S...)  | 
 | 78 | +T = ss(matrices_T...)  | 
 | 79 | +bodeplot([S, T], lab = ["S" "" "T" ""], plot_title = "Bode plot of sensitivity functions",  | 
 | 80 | +    margin = 5Plots.mm)  | 
 | 81 | +```  | 
 | 82 | + | 
 | 83 | +The sensitivity functions obtained this way should be equivalent to the ones obtained with the code below  | 
 | 84 | + | 
 | 85 | +```@example LINEAR_ANALYSIS_CS  | 
 | 86 | +using ControlSystemsBase  | 
 | 87 | +P = tf(1.0, [1, 1]) |> ss  | 
 | 88 | +C = 1                      # Negative feedback assumed in ControlSystems  | 
 | 89 | +S = sensitivity(P, C)      # or feedback(1, P*C)  | 
 | 90 | +T = comp_sensitivity(P, C) # or feedback(P*C)  | 
 | 91 | +```  | 
 | 92 | + | 
 | 93 | +We may also derive the loop-transfer function $L(s) = P(s)C(s)$ using  | 
 | 94 | + | 
 | 95 | +```@example LINEAR_ANALYSIS  | 
 | 96 | +matrices_L = get_looptransfer(sys, :plant_output)[1]  | 
 | 97 | +L = ss(matrices_L...)  | 
 | 98 | +```  | 
 | 99 | + | 
 | 100 | +which is equivalent to the following with ControlSystems  | 
 | 101 | + | 
 | 102 | +```@example LINEAR_ANALYSIS_CS  | 
 | 103 | +L = P * (-C) # Add the minus sign to build the negative feedback into the controller  | 
 | 104 | +```  | 
 | 105 | + | 
 | 106 | +To obtain the transfer function between two analysis points, we call `linearize`  | 
 | 107 | + | 
 | 108 | +```@example LINEAR_ANALYSIS  | 
 | 109 | +using ModelingToolkit # hide  | 
 | 110 | +matrices_PS = linearize(sys, :plant_input, :plant_output)[1]  | 
 | 111 | +```  | 
 | 112 | + | 
 | 113 | +this particular transfer function should be equivalent to the linear system `P(s)S(s)`, i.e., equivalent to  | 
 | 114 | + | 
 | 115 | +```@example LINEAR_ANALYSIS_CS  | 
 | 116 | +feedback(P, C)  | 
 | 117 | +```  | 
 | 118 | + | 
 | 119 | +### Obtaining transfer functions  | 
 | 120 | + | 
 | 121 | +A statespace system from [ControlSystemsBase](https://juliacontrol.github.io/ControlSystems.jl/stable/man/creating_systems/) can be converted to a transfer function using the function `tf`:  | 
 | 122 | + | 
 | 123 | +```@example LINEAR_ANALYSIS_CS  | 
 | 124 | +tf(S)  | 
 | 125 | +```  | 
 | 126 | + | 
 | 127 | +## Gain and phase margins  | 
 | 128 | + | 
 | 129 | +Further linear analysis can be performed using the [analysis methods from ControlSystemsBase](https://juliacontrol.github.io/ControlSystems.jl/stable/lib/analysis/). For example, calculating the gain and phase margins of a system can be done using  | 
 | 130 | + | 
 | 131 | +```@example LINEAR_ANALYSIS_CS  | 
 | 132 | +margin(P)  | 
 | 133 | +```  | 
 | 134 | + | 
 | 135 | +(they are infinite for this system). A Nyquist plot can be produced using  | 
 | 136 | + | 
 | 137 | +```@example LINEAR_ANALYSIS_CS  | 
 | 138 | +nyquistplot(P)  | 
 | 139 | +```  | 
 | 140 | + | 
 | 141 | +## Index  | 
 | 142 | + | 
 | 143 | +```@index  | 
 | 144 | +Pages = ["linear_analysis.md"]  | 
 | 145 | +```  | 
 | 146 | + | 
 | 147 | +```@autodocs  | 
 | 148 | +Modules = [ModelingToolkit]  | 
 | 149 | +Pages   = ["systems/analysis_points.jl"]  | 
 | 150 | +Order   = [:function, :type]  | 
 | 151 | +Private = false  | 
 | 152 | +```  | 
0 commit comments