Goal: choose a multiplier \(a\) s.t. the power of the amplified signal is approximiately equal to some specified target power, \(d^2\). We take the input signal, \(r(t)\) and multiply it to get \(s(t) = a\cdot r(t)\). Specifically,

\(d^2 = E((a \cdot r(t))^2)\)

A bit of finagling, and we get

\(a^2 \approx \frac{d^2}{avg[r^2(kT)]}\)

We use the boring old average for this avg function, ie the arithmetic mean of the samples. The arithmetic mean is a kind of janky lowpass filter, so maybe it would be better to use one of those instead, but, hey, here we are.

We now want to make this adaptive, so it can react to changes in the environment. In the text, the following is carefully derived (p112):

$ J_{Least Squares} = . a[k+1] = a[k] - avg { (s^{2[k]-d}2) } | _{k=NN-j} $

There is also a note that one should avoid letting \(a[k]\) get too close to zero, which will clearly cause the rightmost fraction to grow rather large, which may blow out the fitting mechanism.

They also derive a different function:

$ J_{Naive} = a[k+1] = a[k] - avg{sgn(a[k])(s^{2[k]-d}2)} $

In [4]:

```
# agcgrad.m: Minimize J(a) = avg{|a|((1/3)a*a*r*r-ds)} by choice of a
n = 10000
vr = 1.0
r = sqrt(vr)*random.normal(0,1,n)
ds = 0.15 # desired output power, 0.15 = 0.39^2
mu = 0.001 # stepsize
lenavg = 10 # j in above
# Initialize our two output arrays
a = zeros(n)
s = zeros(n)
for k in range(0,lenavg):
a[k] = 1
s[k] = r[k]
for k in range(lenavg,n):
a_kmo = a[k-1]
s_window = s[k-lenavg:k]
ak = a_kmo - mu * mean( sign(a_kmo)*s_window*s_window - ds)
a[k] = ak
s[k] = ak * r[k]
fig = figure()
ax1 = fig.add_subplot(311)
ax1.plot(a)
ax2 = fig.add_subplot(312)
ax2.plot(r)
ax3 = fig.add_subplot(313)
ax3.plot(s)
a[n-1]
```

Out[4]:

In [7]:

```
def genAGC(ds, mu, lenavg):
"""Returns a function that implements AGC for the given params
ds = 0.15 # desired output power, 0.15 = 0.39^2
mu = 0.001 # stepsize
lenavg = 10 # j in above
"""
def retval(r):
# Initialize our two output arrays
a = zeros(n)
s = zeros(n)
for k in range(0,lenavg):
a[k] = 1
s[k] = r[k]
for k in range(lenavg,n):
a_kmo = a[k-1]
s_window = s[k-lenavg:k]
ak = a_kmo - mu * mean( sign(a_kmo)*s_window*s_window - ds)
a[k] = ak
s[k] = ak * r[k]
return (a,s)
return retval
def runOne(vr, mu, lenavg):
n = 10000
vr = 1.0
r = sqrt(vr)*random.normal(0,1,n)
agc = genAGC(0.15, mu, lenavg)
(a,s) = agc(r)
# Draw plots
fig = figure()
ax1 = fig.add_subplot(411)
ax1.plot(a)
ax2 = fig.add_subplot(412)
ax2.plot(r)
ax3 = fig.add_subplot(413)
ax3.plot(s)
# Look at how the convergence is doing after it's settled in a bit
ax4 = fig.add_subplot(414)
ax4.plot(diff(a)[2000:n-1])
```

In [16]:

```
# a, b: Look at the behavior of convergence with varying mu values
runOne(1.0, 0.00001, 10)
runOne(1.0, 0.001, 10)
runOne(1.0, 0.01, 10)
runOne(1.0, 0.1, 10)
runOne(1.0, 1.0, 10)
```

In [17]:

```
# c) Look at the behavior of convergence with varying variance of input signal
runOne(1.0, 0.001, 10)
runOne(0.1, 0.001, 10)
runOne(10.0, 0.001, 10)
```

In [19]:

```
# d,e: how does lenavg affect convergence?
runOne(1.0, 0.001, 10)
runOne(1.0, 0.001, 3)
runOne(1.0, 0.001, 20)
runOne(1.0, 0.001, 50)
runOne(1.0, 0.001, 100)
```

*6.20.* Try initailizing a(1)=-2. What minimum does it find? What happens to the data?

In [20]:

```
# agcgrad.m: Minimize J(a) = avg{|a|((1/3)a*a*r*r-ds)} by choice of a
n = 10000
vr = 1.0
r = sqrt(vr)*random.normal(0,1,n)
ds = 0.15 # desired output power, 0.15 = 0.39^2
mu = 0.001 # stepsize
lenavg = 10 # j in above
# Initialize our two output arrays
a = zeros(n)
s = zeros(n)
for k in range(0,lenavg):
a[k] = -2
s[k] = r[k]
for k in range(lenavg,n):
a_kmo = a[k-1]
s_window = s[k-lenavg:k]
ak = a_kmo - mu * mean( sign(a_kmo)*s_window*s_window - ds)
a[k] = ak
s[k] = ak * r[k]
fig = figure()
ax1 = fig.add_subplot(311)
ax1.plot(a)
ax2 = fig.add_subplot(312)
ax2.plot(r)
ax3 = fig.add_subplot(313)
ax3.plot(s)
a[n-1]
```

Out[20]: