## Intro to `generic_linear`

The `generic_linear`

controller is designed to be suitable for any practical linear control law, from the simple leaky integrator, to POLC with arbitrary order IIR filters, to LQG. The entire pipeline in it’s fullest generality is outlined below (`python`

syntax for readability - though the actual implementation is in `cublas`

accelerated `c++`

).

#### Most General Pipeline:

`centroids`

are computed in the centroider object, and the output of the controller is the `com`

vector, to be sent to the DMs.

```
# compute appropriate slope vector including delay:
if POLC:
s_now = comp_polc(centroids)
else:
s_now = centroids
# update circular buffer of slopes:
for i in range(nslope_buffer-1):
s[i+1] = s[i]
s[0] = s_now
# clear temporary variable:
x_now *= 0.0
# do recursions of x with matA:
for i in range(nstate_buffer):
x_now += matA[i] @ x[i]
# do innovations of s with matL:
for i in range(nslope_buffer):
x_now += matL[i] @ s[i]
# update circular buffer of states:
for i in range(nstate_buffer-1):
x[i+1] = x[i]
x[0] = x_now
# modal projection:
if MODAL:
u_now = matK @ x_now
else:
u_now = x_now
# update circular buffer of iir inputs:
for i in range(n_iir_in-1):
u_in[i+1] = u_in[i]
u_in[0] = u_now
# perform iir recursions:
if n_iir_in > 0:
u_now *= 0.0
for i in range(n_iir_in):
u_now += iir_b[i] * u_in[i]
for i in range(n_iir_out):
u_now += iir_a[i] * u_out[i]
# update circular buffer of iir outputs:
for i in range(n_iir_out-1):
u_out[i+1] = u_out[i]
u_out[0] = u_now
# do final modal projection to command space
if MODAL:
com = matF @ u_now
else:
com = u_now
```

Without a specific application in mind, the above pseudo-code can be overwhelming. To understand the pipeline, it is useful to consider a few example uses.

#### Leaky Integrator

The leaky integrator is classically defined as:

```
com = (1-leak) * com - gain * ( R @ centroids )
```

where `0.0 < leak < 1.0`

is the leak factor (e.g., `leak=0.01`

), `gain`

is the gain applied to the closed loop error signal, and `R`

is the reconstructor/control matrix. To implement the leaky integrator in `generic_linear`

, the following parameters should be chosen at config-time:

```
p_controller.set_nslope_buffer(1) # (default value)
p_controller.set_nstates(0) # (default value)
p_controller.set_nstate_buffer(0) # (default value)
p_controller.set_niir_in(1)
p_controller.set_niir_out(1)
p_controller.set_modal(False) # (default value)
p_controller.set_polc(False) # (default value)
```

Consider now the equivalent `generic_linear`

pipeline with these parameters set, and the superfluous operations removed:

```
# compute appropriate slope vector including delay:
s[0] = centroids
# do innovations of s with matL:
u_in[0] = matL[0] @ s[0]
# perform iir recursions:
u_now = iir_b[0] * u_in[0]
u_now += iir_a[0] * u_out[0]
# update circular buffer of iir outputs:
u_out[0] = u_now
# do final modal projection to command space
com = u_now
```

To match the leaky integrator, the appropriate RTC data can be set:

```
NACTU,NSLOPE = R.shape
supervisor.rtc.set_L_matrix(0,0,R)
supervisor.rtc.set_iir_a_vector(0,0,leak*np.ones(NACTU))
supervisor.rtc.set_iir_b_vector(0,0,-gain*np.ones(NACTU))
```

N.B. The `iir_a`

and `iir_b`

vectors are required to be set *as vectors* even if the operations are scalar. Compared to the unavoidable GEMV, these operations are still negligible.

#### POLC With EWMA Filter

The classical choice when implementing POLC is to use the Exponentially Weighted Moving Average* (EWMA) IIR filter. That is:

```
com = (1-g) * com - g * (R @ s_ol)
```

Where `s_ol`

is the pseudo-open loop slope vector, not necessarily computed using the same commands as `com`

(due to >1 delay, or voltage clipping). Here, `g`

is the so-called *POLC gain*. Restricting the control law to the above template results in the EWMA filter, with unity DC gain. This is achieved in the `generic_linear`

controller in a similar way to the leaky integrator. The configuration is:

```
p_controller.set_nslope_buffer(1) # (default value)
p_controller.set_nstates(0) # (default value)
p_controller.set_nstate_buffer(0) # (default value)
p_controller.set_niir_in(1)
p_controller.set_niir_out(1)
p_controller.set_polc(True)
p_controller.set_modal(False) # (default value)
```

The only difference here is the POLC flag being set to `True`

, which implies that the RTC will require the `matD`

(interaction matrix) to be set. I.e., the appropriate RTC data is:

```
NACTU,NSLOPE = R.shape
supervisor.rtc.set_L_matrix(0,0,R)
supervisor.rtc.set_D_matrix(0,iMat)
supervisor.rtc.set_iir_a_vector(0,0,(1-g)*np.ones(NACTU))
supervisor.rtc.set_iir_b_vector(0,0,-g*np.ones(NACTU))
```

By default, all RTC matrices in the `generic_linear`

controller are set to zero, so even if the `iMat`

has been computed already, it will not be set in the `generic_linear`

controller unless done by the user as above.

#### Modal POLC with IIR Filter

The general IIR filter when used in a POLC scheme can have higher order filtering than the EWMA filter above. In general, this would look like:

```
u_in[0] = R @ s_ol # open loop modal command vector
u *= 0.0
for i in range(N):
u += b[i] * u_in[i] # modal iir filter (in)
for i in range(M):
u += a[i] * u_out[i] # modal iir filter (out)
u_out[1:] = u_out[:-1] # update circular buffers
u_in[1:] = u_in[:-1]
u_out[0] = u
com = F @ u # project modal iir output to commands space
```

The choice of a higher order (`M`

,`N`

) IIR filter is useful for temporal filtering, including vibration rejection or suppression of WFS noise. The above controller can be achieved using the following configuration:

```
p_controller.set_nslope_buffer(1) # (default value)
p_controller.set_nstates(0) # (default value)
p_controller.set_nstate_buffer(0) # (default value)
p_controller.set_niir_in(N)
p_controller.set_niir_out(M)
p_controller.set_polc(True)
p_controller.set_modal(True)
```

Then, setting the RTC data:

```
supervisor.rtc.set_L_matrix(0,0,R)
supervisor.rtc.set_D_matrix(0,iMat)
supervisor.rtc.set_F_matrix(0,F)
for i in range(N):
supervisor.rtc.set_iir_a_vector(0,i,a[i])
for i in range(M):
supervisor.rtc.set_iir_b_vector(0,i,b[i])
```

Where `a`

and `b`

are lists of vectors containing the desired IIR coefficients. Note that (e.g.) each mode can (and often will) be filtered independently, i.e., with different IIR coefficients. For example, a notch filter could be used to filter vibrations in tip/tilt but not in other modes.

#### LQG

The effective closed-loop LQG control law for a first-order discrete LTI system looks like:

```
x = A @ x + L @ s_ol
com = K @ x
```

which is achieved in the `generic_linear`

controller by using the following configuration:

```
p_controller.set_nslope_buffer(1) # (default value)
p_controller.set_nstates(NSTATES)
p_controller.set_nstate_buffer(1)
p_controller.set_niir_in(0) # (default value)
p_controller.set_niir_out(0) # (default value)
p_controller.set_modal(False) # (default value)
p_controller.set_polc(True)
```

where `NSTATES = A.shape[0]`

. The appropriate RTC data is then set as:

```
supervisor.rtc.set_A_matrix(0,0,A)
supervisor.rtc.set_L_matrix(0,0,L)
supervisor.rtc.set_D_matrix(0,iMat)
supervisor.rtc.set_K_matrix(0,K)
```