<jupyter_start><jupyter_text>Transmission between Antennas![Experiment](transmission/experiment.jpg)In this experiment, a stationary wave appears between transmitting and receiving antennas.By slowly varying their distance and mapping this wave, it is possible to find the frequency of the transmitted signal.**Objectvie:** find the frequency of the transmitted signal.<jupyter_code># Import necessary modules
import numpy
from matplotlib import pyplot

# Only in Jupyter Notebooks
%matplotlib notebook<jupyter_output><empty_output><jupyter_text>The experimental data is in a comma-separated values (CSV) file.The *magic* command `%cat` inside the Jupyter Notebook (or IPython REPL) shows the contents of a file.<jupyter_code>%cat transmission/data.csv<jupyter_output><empty_output><jupyter_text>The data can be read manually and stored in a list of lists of floats:```pythondata = []with open('transmissao/dados.csv') as infile:    print(infile.readline())    for line in infile:        data.append([float(i) for i in line.split(';')])data = numpy.array(data)```Alternatively, the `csv` built-in module contains the `reader` function to automate the process and take care of more general data.In this case, a third option is more indicated because the file contains only nummerical data that will be later manipulated: the function `loadtxt` from `numpy`.It will be faster, simpler and it will provide the data in a `numpy` array ready for vector manipulations.<jupyter_code>numpy.loadtxt?
data = numpy.loadtxt('transmission/data.csv', delimiter=';', skiprows=1)
data
data.shape
data[0, :] # View row 0 (first), all columns
data[0, :].shape<jupyter_output><empty_output><jupyter_text>The `x` values can be extracted from the first column of the `data` array; all remaining columns contain the measurements `v`.**Be careful:** `x` does not copy the values from `data`, it is a *view* of them, therefore changing an element in `x` will also affect `data`.<jupyter_code>x = data[:, 0]
x
v = data[:, 1:]
v
fig, ax = pyplot.subplots(1, 1)

ax.plot(x, v[:, 0], '.')
ax.plot(x, v[:, 1], '.')
ax.plot(x, v[:, 2], '.')
ax.plot(x, v[:, 3], '.')<jupyter_output><empty_output><jupyter_text>If the means and standard deviations from the 4 experiment runs are required, they can be easily calculated:<jupyter_code>v0 = v[0, :] # First row of the measurement data: 4 measurements taken at x[0]
print(v0, numpy.mean(v0), numpy.std(v0))
%%timeit
y = numpy.zeros(len(x))
for i in range(len(x)):
    y[i] = numpy.mean(v[i, :])
%%timeit
y = numpy.mean(v, axis=1)
y = numpy.mean(v, axis=1)
e = numpy.std(v, axis=1)
y
fig, ax = pyplot.subplots(1, 1)

ax.errorbar(x, y, e, capsize=3)

ax.set_xlabel('Distance (mm)')
ax.set_ylabel('Signal (normalized)')

ax.grid()<jupyter_output><empty_output><jupyter_text>The spacial period of the standing wave $\ell$ is directly related to the frequency $f$ and wavelength $\lambda$ of the transmitted signal:\begin{align*}\lambda &= 2\ell \\f &= \frac{c}{\lambda} = \frac{c}{2\ell}\end{align*}There are several ways of finding $\ell$ from the available data.Due to the sparsity of points and existing uncertainties, a good solution is to use a model of the experiment to fit the data.That is preferable to simply finding the distance between consecutive peaks and valeys or using a Discrete Fourier Transform to find the main spacial frequency of the data, because it introduces information about the data that is not present in the measurements.In the previous notebook a linear fit was enough to model the data, but now the model is, in a first approximation, a sinusoidal curve.A non-linear fitting method is required:<jupyter_code>from scipy.optimize import curve_fit

curve_fit?<jupyter_output><empty_output><jupyter_text>Nosso modelo deve ser uma função da posição e dos parâmetros que queremos otimizar.Por se tratar de um problema não-linear, é preciso dar um chute inicial razoável para o algoritmo de ajuste.<jupyter_code>def model(x, l, a, x0, y0):
    return y0 + a * numpy.sin(2*numpy.pi/l*(x - x0))

model(0.25, 5, 2, 0, 0)
x_fit = numpy.array([0, 0.25, 0.5, 0.75, 1])
model(x_fit, 10, 1, 0, 0)
args = [10, 1, 0, 0]
model(x_fit, *args)
init = (15, 0.2, 291, 0.75)

x_fit = numpy.linspace(x[0], x[-1], 201)

y_init = model(x_fit, *init)

fig, ax = pyplot.subplots(1, 1)

ax.errorbar(x, y, e, capsize=3, ls='', marker='.')
ax.plot(x_fit, y_init)
opt, cov = curve_fit(model, x, y, init, sigma=e)

print(opt)
print(numpy.sqrt(numpy.diag(cov)))
y_fit = model(x_fit, *opt)

fig, ax = pyplot.subplots(1, 1)

ax.errorbar(x, y, e, capsize=3, ls='', marker='.')
ax.plot(x_fit, y_fit)

ax.set_xlabel('Distance (mm)')
ax.set_ylabel('Signal (normalized)')

ax.grid()<jupyter_output><empty_output><jupyter_text>Conhecendo o modelo, sabemos qua a distância entre máximos é a metade do comprimento de onda do sinal transmitido.<jupyter_code>f = 3e8 / (2 * opt[0] * 1e-3)
print(f'Frequency: {f * 1e-9:.1f} GHz')<jupyter_output><empty_output><jupyter_text>ExerciseModify the model to accept the transmitted signal frequency $f$ instead of the the standing wave period $\ell$ as an argument. BonusFind the uncertainty of the frequency based on the model fitting from the covariance matrix.<jupyter_code>def model2(x, f, a, x0, y0):
    ...<jupyter_output><empty_output><jupyter_text>AppendixAn even more accurate model is presented below.It models the multiple reflections at both antenas and losses.<jupyter_code>def model3(x, f, a, e0, g, p0, p1):
    phi = (a + 6.67e-3j * numpy.pi * f) * (x - 280)
    y = e0 * (numpy.exp(1j * p0 - phi) + g * numpy.exp(1j * p1 + phi))
    return numpy.abs(y)**2

init3 = (10, 0.001, 0.9, 0.1, 0, 0)
opt3, cov3 = curve_fit(model3, x, y, init3)

y_init = model3(x_fit, *init3)
y_fit = model3(x_fit, *opt3)

fig, ax = pyplot.subplots(2, 1, figsize=(6, 6))

for a in ax:
    a.errorbar(x, y, e, capsize=3, ls='', marker='.')
    a.set_ylabel('Signal (normalized)')
    a.grid()
ax[1].set_xlabel('Distance (mm)')

ax[0].plot(x_fit, y_init)
ax[1].plot(x_fit, y_fit)

print('Frequency: ({:.2f} ± {:.2f}) GHz'.format(opt3[0], cov3[0, 0]**0.5))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.13411841674844618, 'fraction_numerical': 0.024206738632646385, 'mean_word_length': 5.1519114688128775, 'pattern_counts': {'":': 0, '<': 41, '<?xml version=': 0, '>': 41, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '10', 'dir': 'jupyter-structured-clean-dedup', 'id': '307800', 'license': 'Unlicense', 'n_tokens_mistral': 2285, 'n_tokens_neox': 2173, 'n_words': 729, 'path': 'part2/2-Transmission.ipynb', 'repo_name': 'heitzmann/python_short_course', 'size': '6114'}