# Equation Blocks

```python
import re
import sys
import json
import numpy as np
from operator import itemgetter
from itertools import chain
#############################################
# Settings
#############################################
```

```python
nb_infile = "book/content/chapter3.ipynb"
nb_outfile = "book/content/chapter3_new.ipynb"
bib_filename = None
```

```python
# Load Jupyter Notebook as Dictionary
try:
    with open(nb_infile,'r') as fp:
        nb = json.load(fp)
    fp.close()
    print("Input Jupyter Notebook read successfully")
except:
    print("Couldn't find Jupyter Notebook. Check your input path")
```

    Input Jupyter Notebook read successfully

```python
def split_cell_content(cell_content, latex_markers_positions):
    first_idx = latex_markers_positions[0]
    last_idx = latex_markers_positions[-1]
    latex_markers_positions = [0] + latex_markers_positions + [-1]

    eqs_begin_indices = latex_markers_positions[1:-1:2]
    eqs_end_indices = list(np.array( latex_markers_positions[2::2] ) + 1)
    eqs_idx_pairs = list(zip(eqs_begin_indices, eqs_end_indices))

    text_begin_indices = list(np.array( latex_markers_positions[2::2] ) + 1)
    text_end_indices = list(np.array( latex_markers_positions[3:-1:2] ) + 0)
    text_idx_pairs = [(0,first_idx)] + list(zip(text_begin_indices, text_end_indices)) + [(last_idx+1, -1)]

    all_idx_pairs = [(x, 'txt') for x in text_idx_pairs] + [(x, 'eq') for x in eqs_idx_pairs]
    all_idx_pairs.sort(key = lambda x: x[0][0])

    cell_content_split = [cell_content[idx_pair[0][0]: idx_pair[0][1]] for idx_pair in all_idx_pairs]
    cell_content_type = [idx_pair[1] for idx_pair in all_idx_pairs]

    return cell_content_split, cell_content_type
```

```python
def insert_math_env(cell_content_split, cell_content_type):
    cell_content_new = []
    N = len(cell_content_split)
    for i in range(N):
        content_chunk = cell_content_split[i]
        content_type = cell_content_type[i]
        if content_type == "eq":
            # Define label name as empty character by default
            label_name = ""
            for line in content_chunk:
                # Find all LaTeX equation labels
                item_matched = re.findall(latex_eqlabel_pattern, line)
                if len(item_matched) > 0:
                    # Extract label name
                    try:
                        # if label name accompanied by LaTeX label marker
                        label_type, label_name = item_matched[0].strip().split(":")
                    except:
                        # or without
                        label_name = item_matched[0].strip()

            # Wrap equation block up, MyST syntax
            upper = ["```{math}\n", "---\n", "label: "+label_name+"\n", "---\n"]
            body  =  content_chunk
            lower = ["```\n"]
            content_chunk =  list(chain.from_iterable([upper, body, lower]))
            cell_content_new.append(content_chunk)
        else:
            cell_content_new.append(content_chunk)

    return list(chain.from_iterable(cell_content_new))
```

```python
markdown_env_equation = ["```{math}", "```"] # MyST Markdown Equation environment markers
markdown_eqlabel = ["---", "label: ", "---"] # MyST Markdown equation label format

latex_eqlabel_pattern = re.compile(r'\\label\{(.+?)\}') # LaTeX equation label pattern
latex_env_equation = [r"\\begin{equation}", r"\\end{equation}"] #LaTeX equation environment markers
latex_env_eqnarray = [r"\\begin{eqnarray}", r"\\end{eqnarray}"] #LaTeX eqnarray environment markers
```

```python
PATTERNS = latex_env_equation + latex_env_eqnarray

for i in range(len(nb['cells'])):
    cell = nb['cells'][i]
    latex_markers_positions = []
    cell_content = cell['source']

    for j in range(len(cell['source'])):
        item = cell['source'][j]

        for string in PATTERNS:
            item_matched = re.findall(string, item)
            if len(item_matched) > 0:
                latex_markers_positions.append(j)
    if len(latex_markers_positions) > 0:
        cell_content_split, cell_content_type = split_cell_content(cell_content, latex_markers_positions)
        cell_content_modified = insert_math_env(cell_content_split, cell_content_type)
        nb['cells'][i]['source'] = cell_content_modified
```

    Xt
    Ito
    deterministic_system
    noisy_saddle
    noisy_saddle_solutions
    noisy_saddle_RDS
    invariance
    invariance_x
    invariance_y
    stationary_orbit
    general_noisy_saddle_solutions
    x_increments
    y_increments
    higher_order_x
    higher_order_y

```python
with open(nb_outfile,'w') as fp:
    json.dump(nb, fp)
fp.close()
```

# Equation referencing

```python
%%writefile scripts/citations_latex2myst.py
def replace_citation_syntax(references_matched_raw, item):
    """Replace native LaTeX syntax for inline citations in a cell item (paragraph) with MyST Markdown"""
    item_modified = item
    for match in references_matched_raw:
        ref_tags_raw = re.split(",", match)
        ref_tags_clean = [x.strip() for x in ref_tags_raw]

        # Replace LaTeX syntax with new one in cell items
        new_syntax_items = ["{eq}`"]+[",".join(ref_tags_clean)]+["`"]
        separator = ''
        chunk_new = separator.join(new_syntax_items)
        chunk_original = '\eqref{'+match+'}'
        item_modified = item_modified.replace(chunk_original, chunk_new)
    return item_modified

def latex_to_new_syntax_bib(nb):
    for i in range(len(nb['cells'])):
        cell = nb['cells'][i]
        for j in range(len(cell['source'])):
            item = cell['source'][j]
            references_matched_raw = re.findall(reference_pattern,item)
            if references_matched_raw:
                item_modified = replace_citation_syntax(references_matched_raw, item)
                nb['cells'][i]['source'][j] = item_modified
    return nb

if __name__ == "__main__":
    import re
    import sys
    import json

    # Script inputs
    nb_infile = sys.argv[1]  # Input NoteBook (.ipynb)
    nb_outfile = sys.argv[2] # Output Notebook (.ipynb)

    # Matching patterns of native LaTeX syntax for replacement
    reference_pattern = re.compile(r'\\eqref\{(.+?)\}')

    # Load Jupyter Notebook via JSON
    try:
        with open(nb_infile,'r') as fp:
            nb = json.load(fp)
        fp.close()
    except:
        error_mssg = "Couldn't find Jupyter Notebook. Check your input path."
        print(error_mssg)
        sys.exit()

    # Modify syntax in all cells of input NoteBook
    nb_modified = latex_to_new_syntax_bib(nb)

    # Save modified Notebook
    with open(nb_outfile,'w') as fp:
        json.dump(nb_modified, fp)
    fp.close()
    output_mssg = "New Notebook successfully generated"
    print(output_mssg)
```

```python

```

# Inline Citations and Bibliography Syntax

```python
%%writefile scripts/citations_latex2myst.py
def replace_citation_syntax(citations_matched_raw, item):
    """Replace native LaTeX syntax for inline citations in a cell item (paragraph) with MyST Markdown"""
    item_modified = item
    for match in citations_matched_raw:
        bib_tags_raw = re.split(",", match)
        bib_tags_clean = [x.strip() for x in bib_tags_raw]

        # Replace LaTeX syntax with new one in cell items
        new_syntax_items = ["{cite}`"]+[",".join(bib_tags_clean)]+["`"]
        separator = ''
        chunk_new = separator.join(new_syntax_items)
        chunk_original = '\cite{'+match+'}'
        item_modified = item_modified.replace(chunk_original, chunk_new)
    return item_modified

def replace_bibliography_syntax(bibliography_matched_raw, item):
    # Only one single bibliography line exists
    bib_filename = bibliography_matched_raw[0].strip()
    separator = ''
    new_syntax_items = ["```{bibliography} "]+[bib_filename+"\n"]+["```\n"]
    chunk_new = separator.join(new_syntax_items)
    chunk_original = r'\bibliography{'+bibliography_matched_raw[0]+'}'
    item_modified = item.replace(chunk_original, chunk_new)
    return item_modified

def latex_to_new_syntax_bib(nb):
    for i in range(len(nb['cells'])):
        cell = nb['cells'][i]
        for j in range(len(cell['source'])):
            item = cell['source'][j]

            citations_matched_raw = re.findall(citations_pattern,item)
            bibliography_matched_raw = re.findall(bibliography_pattern,item)

            if citations_matched_raw:
                item_modified = replace_citation_syntax(citations_matched_raw, item)
                nb['cells'][i]['source'][j] = item_modified

            elif bibliography_matched_raw:
                item_modified = replace_bibliography_syntax(bibliography_matched_raw, item)
                nb['cells'][i]['source'][j] = item_modified
    return nb

if __name__ == "__main__":
    import re
    import sys
    import json

    # Script inputs
    nb_infile = sys.argv[1]  # Input NoteBook (.ipynb)
    nb_outfile = sys.argv[2] # Output Notebook (.ipynb)
    bib_filename = sys.argv[3] # BibTeX file name (.bib)

    # Matching patterns of native LaTeX syntax for replacement
    citations_pattern = re.compile(r'\\cite\{(.+?)\}')
    bibliography_pattern = re.compile(r'\\bibliography\{(.+?)\}')

    # Load Jupyter Notebook via JSON
    try:
        with open(nb_infile,'r') as fp:
            nb = json.load(fp)
        fp.close()
    except:
        error_mssg = "Couldn't find Jupyter Notebook. Check your input path."
        print(error_mssg)
        sys.exit()

    # Modify syntax in all cells of input NoteBook
    nb_modified = latex_to_new_syntax_bib(nb)

    # Save modified Notebook
    with open(nb_outfile,'w') as fp:
        json.dump(nb_modified, fp)
    fp.close()
    output_mssg = "New Notebook successfully generated"
    print(output_mssg)
```


# Labbrapport: laboration 2 - reaktionskinetik för ett koboltkomplex

**Utförd av:** L., H.
**Datum:** 2013-01-11
**Handledare:** Taha Ahmed

## Syfte

Syftet med laborationen är att ...

## Teori

$\left[\mathrm{Co\left(NH_3\right)_5H_2O}\right]^{3+} \;\xrightarrow[-\mathrm{H_2O}]{+\mathrm{NO_2^-}}\; \left[\mathrm{Co\left(NH_3\right)_5NO_2}\right]^{2+} \rightarrow \left[\mathrm{Co\left(NH_3\right)_5ONO}\right]^{2+}$ (mekanism 1)

$\left[\mathrm{Co\left(NH_3\right)_5H_2O}\right]^{3+} \;\xrightarrow[-\mathrm{H_2O}]{+\mathrm{NO_2^-}}\; \left[\mathrm{Co\left(NH_3\right)_5ONO}\right]^{2+} \rightarrow \left[\mathrm{Co\left(NH_3\right)_5NO_2}\right]^{2+}$ (mekanism 2)

Vi följer ligandutbytet spektrofotometriskt

### Reaktionsmekanism för ligandutbytet

$$r = -\frac{d\left[\left[\mathrm{Co(NH_3)_5H_2O}\right]^{3+}\right]}{dt} = k_1 \left[\left[\mathrm{Co(NH_3)_5H_2O}\right]^{3+}\right] \left[\mathrm{HNO_2}\right] \left[\mathrm{NO_2^-}\right]$$

$$r = -\frac{d\left[\left[\mathrm{Co(NH_3)_5H_2O}\right]^{3+}\right]}{dt} = k_1^\prime \left[\left[\mathrm{Co(NH_3)_5H_2O}\right]^{3+}\right]$$

Där $k_1^\prime = k_1 \left[\mathrm{HNO_2}\right] \left[\mathrm{NO_2^-}\right]$.
Jämvikt inställer sig mellan de två isomererna. Den observerade hastighetskonstanten är av första ordningen, $k_2 = k_+ + k_-$.

$$r = -\frac{d\left[\left[\mathrm{Co(NH_3)_5H_2O}\right]^{3+}\right]}{dt} = k_2 \left(\left[\left[\mathrm{Co(NH_3)_5H_2O}\right]^{3+}\right] - \left[\left[\mathrm{Co(NH_3)_5H_2O}\right]^{3+}\right]_\mathrm{eq}\right)$$

### Beskrivning av den kinetiska modellen

\begin{equation}
R \;\;\xrightarrow[]{k_1^\prime}\;\; I \;\;\xrightarrow[]{k_2}\;\; P
\end{equation}

\begin{equation}\tag{4}
[R]_t = [R]_0 \exp(-k_1^\prime t)
\end{equation}

\begin{equation}\tag{5}
[I]_t = \frac{k_1^\prime [R]_0}{k_2 - k_1^\prime} \left(\exp(-k_1^\prime t) - \exp(-k_2 t)\right)
\end{equation}

\begin{equation}\tag{6}
[P]_t = [R]_0 - [R]_t - [I]_t
\end{equation}

\begin{equation}\tag{7}
\mathrm{Abs}_\mathrm{lösning} = {\mathrm{Abs_R}} + {\mathrm{Abs_I}} + {\mathrm{Abs_P}} = l\left([R\:]\varepsilon_\mathrm{R} + [I\:]\varepsilon_\mathrm{I} + [P\:]\varepsilon_\mathrm{P}\right)
\end{equation}

\begin{equation}\tag{8}
\mathrm{Abs}_t = l[R\:]_0\left(\varepsilon_P + \left(\varepsilon_R + \frac{\varepsilon_I k_1^\prime - \varepsilon_P k_2}{k_2 - k_1^\prime} \right)\exp(-k_1^\prime t) + \frac{k_1^\prime(\varepsilon_P - \varepsilon_I)}{k_2 - k_1^\prime}\exp(-k_2 t)\right)
\end{equation}

\begin{equation}\tag{9}
\mathrm{Abs}_t = \mathrm{Abs} _\infty + \Delta\mathrm{Abs}_1\exp(-k_1^\prime t) + \Delta\mathrm{Abs}_2\exp(-k_2 t)
\end{equation}

\begin{equation}\tag{10}
\mathrm{Abs}_t = \mathrm{Abs} _\infty + \Delta\mathrm{Abs}_1\exp(-t/\tau_1) + \Delta\mathrm{Abs}_2\exp(-t/\tau_2), \quad\quad k_1^\prime = \left(\tau_1\right)^{-1}, \quad k_2 = \left(\tau_2\right)^{-1}
\end{equation}

## Svar till förstuderingsfrågor

### Del 1

1. Bestäm våglängden vid absorptionsmaxima för de tre koboltkomplexen (aqua, nitro, nitrito).
    Vilken färg har ljus med motsvarande våglängd?
    Vilken färg har lösningar som absorberar vid respektive våglängd?

2. Skissa absorbansen, `Abs`,  vid 510 nm mot tiden, `t`, för de två föreslagna reaktionsmekanismerna.

3. Vilken eller vilka av följande våglängder kan användas för att urskilja mellan de två föreslagna reaktionsmekanismerna?
    - 415 nm,
    - 450 nm,
    - 482 nm,
    - 495 nm,
    - 510 nm.

4. Antag att mekanism 1 är korrekt. Vilken eller vilka av de ovanstående våglängderna skulle vara bra för att använda för att bestämma hastighetskonstanten för
    - reaktion 1?
    - reaktion 2?
    - reaktion 1 och reaktion 2?

5. Går det att använda resonemanget från fråga 4 (ovan) om vi istället antar att mekanism 2 är korrekt? Vilka skillnader kan förutses?

### Del 2

6. Vilka är de önskvärda reaktionsförhållandena för att ligandutbytesreaktionen ska bli en pseudo-första ordningens reaktion?

7. Nämn två parametrar som är viktiga att kontrollera under ett kinetikexperiment.

## Utförande

### Utrustning och kemikalier

Spektrofotometer (fabrikat, modell), termostat (fabrikat, modell), pH-meter, etc.
Co-lösningar (koncentrationer, och dylikt).

### Beredning av reaktionslösningar

1. **Tillredde reaktionslösningar**
    Lösning av $\mathrm{Co}$-komplex vatten.
    Lösning av $\mathrm{NaNO_2}$ i vatten, med tillsats av stark syra.

2. **Startade upp termostaten**

3. **Tillverkade lösning för mätning av "slutproduktens" absorbans**

4. **Förvärmde lösningarna (temperatur?)**

5. **Kalibrerade spektrofotometerns baslinje med avjonat vatten**

6. **Kinetikstudier vid $510~\mathrm{nm}$ och $45^{\circ}\mathrm{C}$**

7. **Bestämde $[\mathrm{HNO_2}]$ och $[\mathrm{NO_2^-}]$**

8. **Mätte absorbans för startmaterialet**

9. **Mätte absorbans för "slutprodukten"**

## Resultat

Vi börjar med att plotta den uppmätta absorbanskurvan. För att kunna göra det behöver vi *importera* den funktion som vi vill använda för plottning (vi använder `matplotlib`). Detta gäller för resten av dokumentet.

```python
import matplotlib.pyplot as plt
# Öka bredden på text-output till 140 kolumner
numpy.set_printoptions(linewidth = 140)
# Gör plottar lite större än annars
figsize(14, 8.75)
```

Nedan plottar vi vår experimentellt uppmätta absorbanskurva, Abs mot tid i sekunder.
Det är tydligt att isomeriseringsreaktionen följer reaktionsmekanism 2 (Abs ökar först, sjunker sedan).

```python
filnamn = "data-Lindgren-Holm.csv"
# perhaps we could use scipy.loadtxt() instead? More pedagogical if it avoids the "unpack" parameter
# from scipy import loadtxt
# loadtxt("TEK0006.CSV", delimiter=",", usecols=(0,1)) # load cols number 0 and 1
x_data, y_data = numpy.loadtxt(filnamn, delimiter=",", unpack=True)
plt.plot(x_data, y_data)
plt.xlabel("Tid/sekunder")
plt.ylabel("Abs")
plt.title(filnamn)
plt.yscale("linear")
plt.show()
```

Här har vi vissa saker som förmodligen bör lösas:

1. Hur tar vi enklast bort outliers i data?

## Analys av kinetikdata -- anpassning av kinetikspår

Vi importerar två funktioner som vi kommer att använda för kurvanpassningen (`curve_fit`) och för annan beräkning (`chdtrc`).

```python
# fit with arbitrary function
# från modulen scipy.optimize importerar vi funktionen curve_fit, som optimiserar anpassningen baserat på non-linear least squares
from scipy.optimize import curve_fit
# något obskyr funktion, källa http://docs.scipy.org/doc/scipy/reference/generated/scipy.special.chdtrc.html
from scipy.special import chdtrc
# uncertainties för att enklare utföra beräkningar med osäkerheter
from uncertainties import unumpy
```

De fyra följande rutorna med kod är viktiga, och bör skrivas för hand av studenten.

```python
# Definiera den kinetiska modellen
def kinetic_model(t, abs0, abs1, abs2, tau1, tau2):
    return abs0 + abs1 * exp(-t/tau1) + abs2 * exp(-t/tau2)
```

```python
# Namngivning i strängen p_names av rent kosmetiska skäl (används senare...)
p_names = ("abs0", "abs1", "abs2", "tau1", "tau2")
# Initialvärden för parametrarna till vår kinetiska modell (gissningar)
p_guess = (2.0E-1,  # abs0 = p_guess[0]
          -5.0E-2,  # abs1 = p_guess[1]
           2.0E-1,  # abs2 = p_guess[2]
           250,     # tau1 = p_guess[3]
           900)     # tau2 = p_guess[4]
# Beräkna absorbansvärden baserat på initialvärdena ovan (för senare jämförelse med anpassade parametrar)
y_initial = kinetic_model(x_data, *p_guess)
```

```python
# Använd den kinetiska modellen för att anpassa parametrarna
# popt: anpassade parametrar, pcov: deras kovariansmatris (statistik)
popt, pcov = curve_fit(kinetic_model,
                       x_data,
                       y_data,
                       p0 = p_guess,
                       maxfev = int(1E2))
# maxfev is the maximum number of iterations tried; you
# can try increasing this value if the fit fails.
```

```python
# Stoppa in de anpassade parametrarna i den kinetiska modellen och beräkna anpassad absorbans-kurva
y_fit = kinetic_model(x_data, *popt)
# Beräkna residualer (skillnad mellan experimentell absorbans och anpassad absorbans-kurva)
y_residual = y_data - y_fit
# Calculate degrees of freedom of fit
DoF = len(x_data) - len(p_guess)
# Calculate Chi-Squared
chisq = sum((y_residual)**2)
```

Resterande rutor med kod i denna sektion ("Analys av kinetikdata") kan med fördel kopieras och klistras in verbatim, ingen anledning att knappa in rad-för-rad.

```python
plot_exp, = plt.plot(x_data, y_data, "k:", linewidth = 2, label = "Experimental")
plot_init, = plt.plot(x_data, y_initial, "c-", linewidth = 2, label = "Initial fit")
plot_final, = plt.plot(x_data, y_fit, "r--", linewidth = 2, label = "Optimized fit")
# Skapa en legend
legend(loc = 1, ncol = 1)
# Skapa titel och benämning på axlarna
plt.title("Comparing experimental data, initial fit, and optimized fit")
plt.xlabel("Time/seconds")
plt.ylabel("Abs")
plt.show()
```

```python
# Show residual: difference between data and optimized fit
plt.plot(x_data, y_residual, "r-")
plt.title("Residual")
# Draw horizontal line at 0
plt.axhline(y = 0)
# Label axes
plt.xlabel("Time/seconds")
plt.ylabel("Abs")
plt.show()
```

```python
print "Antal datapunkter  =", len(x_data)
print "Antal parametrar   =", len(p_guess)
print "Degrees of freedom =", DoF
```

    Antal datapunkter  = 1601
    Antal parametrar   = 5
    Degrees of freedom = 1596

```python
print "Kovariansmatris : \n", pcov, "\n"
```

    Kovariansmatris :
    [[  6.10020726e-09  -4.71160660e-08   4.24484128e-08   1.13179885e-04  -2.72915619e-04]
     [ -4.71160660e-08   4.31034079e-07  -3.98674727e-07  -1.09888474e-03   2.34604279e-03]
     [  4.24484128e-08  -3.98674727e-07   3.72520016e-07   1.04321100e-03  -2.15564451e-03]
     [  1.13179885e-04  -1.09888474e-03   1.04321100e-03   3.04216653e+00  -5.87442427e+00]
     [ -2.72915619e-04   2.34604279e-03  -2.15564451e-03  -5.87442427e+00   1.31057966e+01]]

```python
# Göm undan i en funktion?
print "Correlation Matrix :"
for i,row in enumerate(pcov):
    for j in range(len(popt)) :
        print "%10f"%(pcov[i, j]/sqrt(pcov[i, i]*pcov[j, j])), # note: comma at end of print statement suppresses new line
    print # required for proper formatting of matrix
```

    Correlation Matrix :
      1.000000  -0.918842   0.890460   0.830817  -0.965216
     -0.918842   1.000000  -0.994920  -0.959632   0.987071
      0.890460  -0.994920   1.000000   0.979954  -0.975597
      0.830817  -0.959632   0.979954   1.000000  -0.930341
     -0.965216   0.987071  -0.975597  -0.930341   1.000000

```python
print "Estimated parameters with uncertainties (initial guess in parentheses)"
poptwerr = range(len(popt))
for i in range(len(popt)):
    # Here (inside the for-loop) we should build up a unumpy array (arrary with uncertainty values) for each parameter
    poptwerr[i] = unumpy.uarray([popt[i], pcov[i,i]**0.5*max(1, sqrt(chisq/DoF))])
    print ("  %s = %10.5f +/- %10.8f   (%9.5f)" %(p_names[i], unumpy.nominal_values(poptwerr[i]), unumpy.std_devs(poptwerr[i]), p_guess[i]))
```

    Estimated parameters with uncertainties (initial guess in parentheses)
      abs0 =    0.15877 +/- 0.00007810   (  0.20000)
      abs1 =   -0.10565 +/- 0.00065653   ( -0.05000)
      abs2 =    0.22035 +/- 0.00061034   (  0.20000)
      tau1 =  408.55501 +/- 1.74418076   (250.00000)
      tau2 = 1358.81341 +/- 3.62019289   (900.00000)

```python
print "Curve fit converged with:"
print "  ChiSq     = %1.5E" %(chisq)
print "  DoF       = %4g" %(DoF)
print "  ChiSq/DoF = %1.5E" %(chisq/DoF)
print "  CDF       = %4.2f%%" %(100*chdtrc(DoF,chisq))
```

    Curve fit converged with:
      ChiSq     = 1.29030E-04
      DoF       = 1596
      ChiSq/DoF = 8.08461E-08
      CDF       = 100.00%

**Next step:** calculate $k$ and $K$, preferably without losing uncertainties
Perhaps we should use the `uncertainties` package!

```python
k1prim = 1 / poptwerr[3]
k2 = 1 / poptwerr[4]
print "k1prim =", k1prim, "ENHET?"
print "k2 =", k2, "ENHET?"
```

    k1prim = 0.00244765079648+/-1.04493772085e-05 ENHET?
    k2 = 0.00073593621875+/-1.96070413922e-06 ENHET?

Ta 5 ml av $\mathrm{HNO_2}/\mathrm{NaNO_2}$-lösningen och späd ut det med 5 ml avjonat vatten (så att koncentrationen blir samma som under kinetikmätningen). Mät pH på lösningen.
Från känd koncentration av $\mathrm{NaNO_2}$, känt pH och $p\mathrm{K_a}$-värde för $\mathrm{HNO_2}$ (se SI-data) kan ni sedan räkna ut $[\mathrm{HNO_2}]$ och $[\mathrm{NO_2^-}]$.
Vi saknar värden för $[\mathrm{NaNO_2}]$, $[\mathrm{HNO_2}]$ och $[\mathrm{NO_2^-}]$.

```python
conc_NaNO2 = 0.5 # mol/L
conc_HNO2 = 0.0125 # mol/L
conc_NO2m = 0.5 # mol/L
k1 = k1prim / (conc_HNO2 * conc_NO2m)
print "k1 =", k1 # HÄR BEHÖVS KORREKTA VÄRDEN
print "k2 =", k2
```

    k1 = 0.391624127437+/-0.00167190035336
    k2 = 0.00073593621875+/-1.96070413922e-06

```python
# Conc Co is known (each lab group should calculate from weighed mass)
conc_Co = 1 # HÄR BEHÖVS LÄMPLIGT VÄRDE
# Abs / (conc * length)
# Abs read from plot at 510 nm, conc in M, length in dm
epsilon510_ONO = 0.35 / (6.7E-3 * 0.1)
epsilon510_NO2 = 0.15 / (6.7E-3 * 0.1)
```

\begin{equation}
A_\infty = \varepsilon_\mathrm{ONO^-} \cdot [\mathrm{ONO^-}] \cdot l + \varepsilon_\mathrm{NO_2^-} \cdot [\mathrm{NO_2^-}] \cdot l
\end{equation}

\begin{equation}
[\mathrm{Co^{3+}}] = [\mathrm{ONO^-}] + [\mathrm{NO_2^-}] \quad\Leftrightarrow\quad [\mathrm{ONO^-}] = [\mathrm{Co^{3+}}] - [\mathrm{NO_2^-}]
\end{equation}

\begin{equation}
[\mathrm{NO_2^-}] = \frac{A_\infty - \varepsilon_\mathrm{ONO^-} \cdot [\mathrm{Co^{3+}}] \cdot l}{\varepsilon_\mathrm{NO_2^-} \cdot l - \varepsilon_\mathrm{ONO^-} \cdot l}
\end{equation}

## Slutsats

Anpassningen ...

## Referenser

1. http://dawes.wordpress.com/2011/01/02/scientific-python/
2. http://packages.python.org/uncertainties/numpy_guide.html
3. http://web.uvic.ca/~jalexndr/192UncertRules.pdf
4. http://spiff.rit.edu/classes/phys273/uncert/uncert.html

```python

```


# Circuit

```python
from pytket.circuit.display import render_circuit_jupyter

from lambeq import AtomicType, BobcatParser, IQPAnsatz

N = AtomicType.NOUN
S = AtomicType.SENTENCE
```

```python
parser = BobcatParser()
diagram = parser.sentence2diagram('Alice runs')
diagram.draw()
```

```python
ansatz = IQPAnsatz({N: 1, S: 1}, n_layers=2)
discopy_circuit = ansatz(diagram)
discopy_circuit.draw(figsize=(10, 15))
```

```python
tket_circuit = ansatz(diagram).to_tk()

# This does not render properly on GitHub, please view it at:
# https://cqcl.github.io/lambeq/examples/circuit.html
render_circuit_jupyter(tket_circuit)
```

<!DOCTYPE html>

    <meta charset="UTF-8">

q[0]

q[1]

q[2]

c[0]

c[1]

                    Rx(2*Alice__n_0)

                    H

                    H

                    Rz(2*Alice__n_1)

                    Rz(2*runs__n.r@s_0)

                    Rx(2*Alice__n_2)

                    H

                    H

                    Rz(2*runs__n.r@s_1)

                    X

z

                    H

z

q[0]

q[1]

q[2]

c[0]

c[1]

```python
from sympy import default_sort_key

# Make sure you sort your symbols as they are returned as a set.
parameters = sorted(tket_circuit.free_symbols(), key=default_sort_key)

param_dict = {p: i * 0.001 for i, p in enumerate(parameters)}
param_dict
```

    {Alice__n_0: 0.0,
     Alice__n_1: 0.001,
     Alice__n_2: 0.002,
     runs__n.r@s_0: 0.003,
     runs__n.r@s_1: 0.004}

```python
tket_circuit.symbol_substitution(param_dict)

# This does not render properly on GitHub, please view it at:
# https://cqcl.github.io/lambeq/examples/circuit.html
render_circuit_jupyter(tket_circuit)
```

<!DOCTYPE html>

    <meta charset="UTF-8">

q[0]

q[1]

q[2]

c[0]

c[1]

                    Rx(0.0)

                    H

                    H

                    Rz(0.002)

                    Rz(0.006)

                    Rx(0.004)

                    H

                    H

                    Rz(0.008)

                    X

z

                    H

z

q[0]

q[1]

q[2]

c[0]

c[1]


# Transformadores
__UNLZ - Facultad de Ingeniería__
__Electrotecnia__
__Alumno:__ Daniel Antonio Lorenzo

## Introducción
Uno de los dispositivos que han hecho posible el sistema de potencia de ca comercial como se le conoce hoy en día es el transformador.
Los transformadores tienen muchas aplicaciones. Se usan en sistemas de potencia eléctrica para aumentar el voltaje en transmisiones a larga distancia y para disminuirlo luego otra vez a un nivel seguro para su empleo en casas y oficinas. Se utilizan en fuentes de potencia para equipo electrónico con el fin de subir o bajar el voltaje, en sistemas de audio para igualar las cargas de las bocinas a los amplificadores, en telefonía, en radio y en sistemas de televisión para acoplar señales, etcétera.

Un transformador es un __circuito acoplado__ magnéticamente, es decir, un circuito en el cual el campo magnético producido por la corriente que varía en el tiempo induce voltaje en otro. Para ilustrarlo, en la figura se muestra un transformador básico con núcleo de hierro. Se compone de dos bobinas devanadas en un núcleo común. La corriente alterna en uno de los devanados establece un flujo que enlaza el otro devanado e induce un voltaje en él. Entonces, la potencia fluye de un circuito al otro a través de un medio del campo magnético, sin ninguna conexión eléctrica entre los dos lados. El devanado al cual se le suministra potencia se llama __primario__, mientras que el devanado el cual toma la potencia se le llama __secundario__. La potencia puede fluir en cualquier dirección, ya que cualquiera de los devanados puede usarse como primario o secundario.

Los transformadores se clasifican en dos tipos: de núcleo de hierro y de núcleo de aire. Se inicia con los del tipo de núcleo de hierro. Estos transformadores se usan por lo general en aplicaciones de baja frecuencia.

El hierro (en realidad un acero especial llamado acero de transformador) se usa para núcleos porque incremente el acoplamiento entre las bobinas proporcionando una trayectoria fácil para el flujo magnético. Se usan dos tipos básicos en la construcción del núcleo de hierro: el __tipo núcleo__ y el __tipo acorazado__. En ambos casos, los núcleos se fabrican con láminas de acero, aisladas unas de otras mediante una delgada cubierta de cerámica u otro material para ayudar a minimizar las pérdidas por corrientes parásitas.

Sin embargo, el hierro tiene pérdidas de potencia considerables debidas a la histéresis y a las corrientes parásitas a altas frecuencias, por esa razón no es útil como material de núcleo por arriba de 50 kHz. Para aplicaciones de alta frecuencia (como en circuitos de radio) se usan los tipos de __núcleo de aire__ y __núcleo de ferrita__.

__Tipo núcleo y tipo acorazado:__

> Para el tipo núcleo (izquierda), los devanados están en brazos separados, mientras que para el tipo acorazado (derecha) ambos devanados están en el mismo brazo.

__Direcciones del devanado__

Una de las ventajas de un transformador es que puede usarse para cambiar la polaridad de un voltaje de ca. Esto se ilustra en la siguiente figura para un par de transformadores de núcleo de hierro. Para el transformador (a) los voltajes del primario y del secundario están en fase (por razones que se discutirán después), mientras que en (b) están fuera de fase por 180°.

A primera vista, los transformadores de núcleo de hierro parecen difíciles de analizar porque tienen características tales como resistencia del devanado, pérdidas en el núcleo y fugas de flujo que parecen difíciles de manejar. Por fortuna, estos efectos son pequeños y a menudo se pueden pasar por alto. El resultado es el __transformador ideal__. Sin embargo, una vez que se conoce cómo analizar un transformador ideal, es relativamente fácil agregar los efectos no ideales.

## Transformador ideal

Un transformador ideal es un transformador de acoplamiento unitario sin pérdidas en el que las bobinas primaria y secundaria tienen autoinductacias infinitas.

__En un transformador ideal:__
1. No se toma en cuenta la resistencia de sus bobinas
2. Se desprecian sus pérdidas en el núcleo
3. Se supone que todo el flujo está confinado en su núcleo
4. Se supone que se necesita una corriente ínfima para establecer el flujo en su núcleo

Los transformadores de potencia de núcleo de hierro bien diseñados son casi ideales.

Se dice que dos bobinas están acopladas mutuamente si el flujo magnético 𝜙 que emana de una de ellas pasa por la otra. La inductancia mutua entre las dos bobinas está dada por

$$ M = k \sqrt{L_1 L_2} $$

donde $k$ es el coeficiente de acoplamiento $0<k<1$.

La inductancia mutua es la capacidad de un inductor de inducir una tensión en un inductor cercano, medida en henrys (H).

Un __transformador ideal__ es aquel con acoplamiento perfecto $(k=1)$. Consta de dos (o más) bobinas con gran número de vueltas devanadas en un núcleo común de alta permeabilidad. A causa de esta permeabilidad del núcleo, el flujo enlaza a todas las vueltas de ambas bobinas, lo que da por resultado un acoplamiento perfecto.

Los transformadores de núcleo de hierro son una aproximación muy cercana de transformadores ideales. Se les emplea en sistemas de potencia y en electrónica.

Cuando se aplica una tensión senoidal al devanado primario, como se advierte en la figura, por ambos devanados pasa el mismo flujo magnético $\phi$. De acuerdo con la ley de Faraday, la tensión en el devanado primario es

$$ v_1 = N_1 \frac{d \phi}{dt} \tag{a} $$

mientras que a través del devanado secundario es

$$ v_2 = N_2 \frac{d \phi}{dt} \tag{b} $$

Al dividir la ecuación (b) entre la ecuación (a) se obtiene

$$ \frac{v_2}{v_1} = \frac{N_2}{N_1} = \frac{1}{a} = n \tag{1} $$

donde $n$ es, de nueva cuanta, la *relación de vueltas* o *relación de transformación*. Pueden usarse las tensiones fasoriales $V_1$ y $V_2$ en lugar de los valores instantáneos $v_1$ y $v_2$. Así, la ecuación (1) puede escribirse como

$$ \frac{V_2}{V_1} = \frac{N_2}{N_1} = \frac{1}{a} = n \tag{2} $$

Por efecto de la conservación de la potencia, la energía suministrada al devanador primario debe ser igual a la energía absorbida por el devanado secundario, ya que en un transformador ideal no hay pérdidas. Esto implica que

$$ v_1 i_1 = v_2 i_2 \tag{3} $$

En forma fasorial, la ecuación (3) se convierte, junto con la ecuación (2), en

$$ \frac{I_1}{I_2} = \frac{V_2}{V_1} = \frac{1}{a} = n \tag{4} $$

lo que indica que las corrientes primaria y secundaria se determinan con la relación de vueltas en forma inversa que las tensiones. Así,

$$ \frac{I_2}{I_1} = \frac{N_1}{N_2} = a = \frac{1}{n} \tag{5} $$

Cuando $n=1$, el transformador se llama por lo general *transformador de aislamiento*. Si $n>1$, se tiene un *transformador elevador*, pues la tensión aumenta de primaria a secundaria $(V_2 > V_1)$. Por otra parte, $n<1$, el transformador es un transformador reductor, ya que la tensión se reduce de primaria a secundaria $(V_2 < V_1)$

Un transformador reductor es aquel cuya tensión secundaria es menor que su tensión primaria.

Un transformador elevador es aquel cuya tensión secundaria es mayor que su tensión primaria.

La capacidad nominal de los transformadores suele especificarse como $V_1/V_2$. Un transformador con capacidad nominal de 2400/120 V debe tener 2400 V en el devanado primario y 120 en el secundario (es decir, se trata de un transformador reductor). Téngase presente que las capacidades nominales de tensión están en rms.

Las companías de electricidad generan a menudo cierta tensión conveniente y se sirven de un transformador elevador para aumentar la tensión a fin de que la energía eléctrica pueda transmitirse a muy alta tensión y baja corriente por las líneas de transmisión, lo cual permite ahorros significativos. Cerca de las residencias de los consumidores, se emplean transformadores reductores para disminuir las tensión.

Es importante saber cómo obtener la polaridad apropiada de las tensiones y la dirección de las corrientes del transformador de la figura. Si la polaridad de $V_1$ o $V_2$ o la dirección de $I_1$ o $I_2$ cambia, podría ser necesario reemplazar $n$ en las ecuaciones (1) a (5) por $-n$- Las dos reglas simples por seguir son:

1. Si tanto $V_1$ como $V_2$ son *ambas* positivas o negativas en las terminales con marca, se usa $+n$ en la ecuación.
De lo contrario, se usa $-n$.
2. Si tanto $I_1$ como $I_2$ *ambas* entran o salen de las terminales marcadas, se usa $-n$ en la ecuación (5).
De lo contrario, se usa $+n$.

Estas reglas se demuestran en los cuatro circuitos de la figura

Usando las ecuaciones (2) y (5), siempre es posible expresar $V_1$ en término de $V_2$ e $I_2$ o viceversa:

$$ V_1 = \frac{V_2}{n} \qquad \mbox{o} \qquad V_2 = n V_1 \tag{6} $$

$$ I_1 = n I_2 \qquad \mbox{o} \qquad I_2 = \frac{I_1}{n} \tag{7} $$

La potencia compleja en el devanado primario es

$$ S_1 = V_1 I^\ast_1 = \frac{V_2}{n} (n I_2)^\ast = V_2 I^\ast_2 = S_2 \tag{8} $$

lo que indica que la potencia compleja provista al devanado primario se entrega al devanado secundario sin pérdidas. El transformador no absorbe potencia.

$$ Z_{ent} = \frac{V_2}{I_1} = \frac{1}{n^2} \frac{V_2}{I_2} \tag{9} $$

En la figura siguiente es evidente que $V_2/I_2 = Z_L$ de modo que

$$ Z_{ent} = \frac{Z_L}{n^2} \tag{10} $$

La impedancia de entrada también se llama *impedancia reflejada*, puesto que parecería que la impedancia de carga se reflejara en el lado primario. Esta capacidad del transformador para convertir una impedancia dada en otra impedancia proporciona un medio de *acoplamiento de impedancias* que garantice la transferencia de potencia máxima.

Al analizar un circuito que contiene un transformador ideal, es práctica común eliminar el transformador reflejando impedancias y fuentes de un lado del transformador al otro.

Supóngase que en el circuito de la figura, se desea reflejar el lado secundario del circuito en el lado primario. Se halla el equivalente de Thevenin del circuito a la derecha de las terminales *a-b*. Se obtiene $V_{TH}$ como la tensión de circuito abierto en las terminales *a-b*, como se observa en la figura.

Circuito con transformador ideal cuyos circuitos equivalentes se desea hallar.

(a) Obtención de $V_{TH}$   ;   (b) Obtención de $Z_{TH}$

Dado que las terminales *a-b* están abiertas, $I_1 = 0 = I_2$, de manera que $V_2 = V_{s2}$. Así, con base en la ecuación (6)

$$ V_{TH} = V_1 = \frac{V_2}{n} = \frac{V_{s2}}{n} \tag{11} $$

Para obtener $Z_{TH}$, se elimina la fuente de tensión del bobinado secundario y se inserta una fuente unitaria entre las terminales *a-b*.

Partiendo de las ecuaciones (6) y (7), $I_1 = n I_2$ y $V_1 = V_2/n$, de modo que

$$ Z_{Th} = \frac{V_1}{I_1} = \frac{V_2/n}{n I_2} = \frac{Z_2}{n^2} \qquad ; \qquad V_2 = Z_2 I_2 \tag{12} $$

Una vez que se tiene $V_{TH}$ y $Z_{TH}$, se añade el equivalente de Thevenin a la parte del circuito de la figura a la izquierda de las terminales *a-b*

Circuito equivalente al de la figura anterior obtenido reflejando el circuito secundario en el lado primario.

Circuito equivalente obtenido reflejando el circuito primario en el lado secundario.

La regla general para eliminar el transformador y reflejar el circuito secundario en el lado primario es: divida la impedancia secundaria entre $n^2$, divida la tensión secundaria entre $n$ y multiplique la corriente secundaria por $n$.

De acuerdo con la ecuación (8), la potencia se mantiene sin cambios ya sea que se calcule en el lado primario o en el secundario. Sin embargo, debe tomarse en cuenta que este método de reflexión sólo se aplica si no hay conexiones externas sobre los devanados primario y secundario, se aplica simplemente el análisis regular de lazo y de nodo. Ejemplos de circuitos en los que hay conexiones externas entre los devanados primario y secundario se dan en las figuras. Adviértase asimismo que si la ubicación de las marcas en la figura cambia, quizá tendría que reemplazarse $n$ por $-n$ para obedecer la regla del punto.

## Ejemplo 13.7
Un transformador ideal tiene capacidad nominal de 2400/120 V, 9,6 kVA y 50 vueltas en el lado secundario.Calcule:

a) la razón de vueltas.

b) el número de vueltas en el lado primario.

c) las capacidades nominales de corriente de los devanados primario y secundario.

### Solución
__a)__ Éste es un transformador reductor, ya que $V_1 = 2400 \, \mathrm{V} > V_2 = 120 \, \mathrm{V}$.

$$ n = \frac{V_2}{V_1} = \frac{120}{2400} = 0,05 $$

__b)__
$$ n = \frac{N_2}{N_1} \qquad \Rightarrow \qquad 0,05 = \frac{50}{N_1} $$

o sea
$$ N_1 = \frac{50}{0,05} = 1000 \, \mbox{vueltas} $$

__c)__
$$ S = V_1 I_1 = V_2 I_2 = 9,6 \, \mathrm{kVA} $$

Por lo tanto,
$$ I_1 = \frac{S}{V_1} = \frac{9600}{2400} = 4 \, \mathrm{A} $$

$$ I_2 = \frac{S}{V_2} = \frac{9600}{120} = 80 \, \mathrm{A} $$

```python
# Datos:
V1 = 2400 # V
V2 = 120 # V
N2 = 50
S = 9.6e3 # VA
```

```python
n = V2/V1
N1 = N2/n
I1 = S/V1
I2 = S/V2
```

```python
print('a) ítem')
print('n = %.2f'%n)
print('b) ítem')
print('N1 = %.0f'%N1)
print('c) ítem')
print('I1 = %.1f A'%I1)
print('I2 = %.1f A'%I2)
```

    a) ítem
    n = 0.05
    b) ítem
    N1 = 1000
    c) ítem
    I1 = 4.0 A
    I2 = 80.0 A

```python
%reset -s -f
```

## Problema de práctica 13.7
La corriente primaria que entra a un transformador ideal con capacidad nominal de 3300/110 V es de 3 A. Calcule:

a) la razón de vueltas.

b) la capacidad nominal en kVA.

c) la corriente secundaria.

### Solución
a) Éste es un transformador reductor, ya que $V_1 = 3300 \, \mathrm{V} > V_2 = 110 \, \mathrm{V}$.

$$ n = \frac{V_2}{V_1} = \frac{1}{30} \cong 0,03333 $$

b) Capacidad nominal en kVA
$$ S = V_2 I_2 = V_2 \left( \frac{I_1}{n} \right) = 9,9 \, \mathrm{kVA} $$

c) Corriente secundaria
$$ I_2 = \frac{I_1}{n} = 90 \, \mathrm{A} $$

```python
# Datos:
V1 = 3300 # V
V2 =  110 # V
I1 =    3 # A
```

```python
# item a) razón de vueltas.
n = V2/V1
# item c) corriente secundaria.
I2 = I1/n
# item b) capacidad nominal en kVA.
S = V2*I2
```

```python
print('a) Razón de vualtas:     n  = %.5f'%n)
print('b) Capacidad nominal:    S  = %.2f kVA'%(S/1000) )
print('c) Corriente secundaria: I2 = %.1f A'%I2)
```

    a) Razón de vualtas:     n  = 0.03333
    b) Capacidad nominal:    S  = 9.90 kVA
    c) Corriente secundaria: I2 = 90.0 A

```python
%reset -s -f
```

## Ejemplo 13.8
En referencia al circuito con transformador ideal de la figura, halle:
1. la corriente $I_1$
2. la tensión de salida $V_0$
3. la potencia compleja suministrada por la fuente.

### Solución

```python
import cmath
import numpy as np
```

```python
# Datos:
Vs = 120
Z1 = 4 - 6j # Ohm
Z2 = 20 # Ohm
n = 2
```

```python
# a) La impedancia Z2 puede reflejarse en el lado primario
ZR = Z2/n**2
Zent = Z1 + ZR
I1 = Vs/Zent
# b) Puesto que tanto I1 como I2 salen de las terminales marcas
I2 = -(1/n)*I1
Vo = I2*Z2
# c) La potencia compleja suministrada es
S = Vs*I1.conjugate()
```

```python
print('a) La impedancia Z2 puede reflejarse en el lado primario')
print('   I1 = (%.2f < %.2f°) A'%(abs(I1) , np.rad2deg( cmath.phase(I1) ) ))
print('b) Puesto que tanto I1 como I2 salen de las terminales marcas')
print('   I2 = (%.2f < %.2f°) A'%(abs(I2) , np.rad2deg( cmath.phase(I2) ) ))
print('   Vo = (%.2f < %.2f°) V'%(abs(Vo) , np.rad2deg( cmath.phase(Vo) ) ))
print('c) La potencia compleja suministrada es')
print('   S  = (%.2f < %.2f) VA'%(abs(S) , np.rad2deg( cmath.phase(S) )))
```

    a) La impedancia Z2 puede reflejarse en el lado primario
       I1 = (11.09 < 33.69°) A
    b) Puesto que tanto I1 como I2 salen de las terminales marcas
       I2 = (5.55 < -146.31°) A
       Vo = (110.94 < -146.31°) V
    c) La potencia compleja suministrada es
       S  = (1331.28 < -33.69) VA

```python
%reset -s -f
```

## Problema de práctica 13.8
En el circuito con transformador ideal de la figura, halle $V_0$ y la potencia compleja suministrada por la fuente.

### Solución

```python
import cmath
import numpy as np
```

```python
# Datos:
Vs = 120 # V
Z1 = 2 # Ohm
Z2 = 16 - 24j # Ohm
C1 = -24j # Ohm
n = 4
```

```python
ZR = Z2/n**2
Zent = Z1 + ZR
I1 = Vs/Zent
I2 = -(1/n)*I1
Vo = I2*C1
S = Vs*I1.conjugate()
```

```python
print('Vo = (%.1f < %.2f°) V'%(abs(Vo) , np.rad2deg( cmath.phase(Vo) ) ))
print('S  = (%.3f < %.2f) kVA'%(abs(S)/1e3 , np.rad2deg( cmath.phase(S) )))
```

    Vo = (214.7 < 116.57°) V
    S  = (4.293 < -26.57) kVA

```python
%reset -s -f
```

## Ejemplo 13.9
Calcule la potencia suministrada a la resistencia de $10 \, \Omega$ en el circuito con transformador ideal de la figura.

## Solución
En este circuito no puede realizarse el reflejo en el lado secundario o primario; hay una conexión directa entre los lados primario y secundario debido al resistor de $30 \, \Omega$. Se aplica el análisis de lazos.

En cuanto al lazo 1
$$ -120 + (20 + 30)I_1 - 30I_2 + V_1 = 0  $$

o sea
$$ 50 I_1 - 30 I_2 + V_1 = 120 \tag{1} $$

En cuanto al lazo 2
$$ -V_2 + (10 + 30)I_2 + 30I_2 = 0 $$

o sea
$$ -30I_1 + 40I_2 + V_2 = 0 \tag{2} $$

En las terminales del transformador

$$ V_2 = - \frac{1}{2} V_1 \tag{3} $$

$$ I_2 = 2 I_1 \tag{4} $$

(Nótese que $n = 1/2$) Ahora se tiene cuatro ecuaciones y cuatro incógnitas, pero la meta es obtener $I_2$. Así, se sustituye $V_1$ e $I_1$ en términos de $V_2$ e $I_2$ en las ecuaciones (1) y (2). La ecuación (1) se convierte en

$$ -55 I_1 - 2 V_2 = 120 \tag{5} $$

y la ecuación (2) en
$$ 15 I_2 + 40 I_2 - V_2 = 0 $$

$$ V_2 = 55 I_2 \tag{6} $$

Al sustituir la ecuación (6) en la ecuación (5)

$$ -165 I_2 = 120 $$

$$ I_2 = - \frac{120}{165} = - 0,7272 \, \mathrm{A} $$

La potencia absorbida por la resistencia de $10 \, \Omega$ es

$$ P = I_2^2 Z_2 = 5,3 \, \mathrm{W} $$

```python
# Datos:
Vs = 120 # V (rms)
Z1 = 20 # Ohm
Z2 = 10 # Ohm
R1 = 30 # Ohm
```

En forma matricial

$$\left[
\begin{array}{cccc}
50 & - 30 & 1 & 0 \\
-30 & 40 & 0 & 1 \\
0 & 0 & -1/2 & 1 \\
2 & 1 & 0 & 0 \\
\end{array}
\right]
\left[
\begin{array}{c}
I_1 \\
I_2 \\
V_1 \\
V_2 \\
\end{array}
\right]
=
\left[
\begin{array}{c}
120 \\
0 \\
0 \\
0 \\
\end{array}
\right]$$

$$ A I = B $$

Entonces
$$ I = A^{-1} B $$

### Usando numpy

```python
import numpy as np
import cmath
```

```python
A = np.array([[50,-30,1,0],[-30,40,0,1],[0,0,-1/2,1],[2,1,0,0]])
B = np.array([[120],[0],[0],[0]])
```

```python
I = np.dot( np.linalg.inv(A) , B )
```

```python
I1 = I[0] ; I2 = I[1]
P = I2**2 * Z2
```

```python
print('I1 = %.4f A'%I[0])
print('I2 = %.4f A'%I[1])
print('P = %.2f W'%P)
```

    I1 = 0.3636 A
    I2 = -0.7273 A
    P = 5.29 W

### Usando sympy

```python
import sympy as sp
```

```python
M = sp.Matrix([[50,-30,1,0,120],[-30,40,0,1,0],[0,0,-1/2,1,0],[2,1,0,0,0]])
M
```

$\displaystyle \left[\begin{matrix}50 & -30 & 1 & 0 & 120\\-30 & 40 & 0 & 1 & 0\\0 & 0 & -0.5 & 1 & 0\\2 & 1 & 0 & 0 & 0\end{matrix}\right]$

```python
S = M.rref()
Sol = S[0]
```

```python
Sol
```

$\displaystyle \left[\begin{matrix}1 & 0 & 0 & 0 & 0.363636363636364\\0 & 1 & 0 & 0 & -0.727272727272727\\0 & 0 & 1 & 0 & 80.0\\0 & 0 & 0 & 1 & 40.0\end{matrix}\right]$

```python
I1 = Sol[0,4]
I2 = Sol[1,4]
V1 = Sol[2,4]
V2 = Sol[3,4]
```

```python
%reset -s -f
```


Table of Contents

1  Kiểm soát Thứ mà bạn Không thấy

2  Hiệu Ứng Cố Định

3  Biểu diễn Hiệu Ứng Cố Định

4  Hiệu ứng thời gian

5  Khi Dữ Liệu Bảng Không thể Giúp bạn

6  Ý tưởng chủ đạo

7  Tài liệu tham khảo

## Kiểm soát Điều mà bạn Không thấy
Các phương pháp như điểm xu hướng, hồi quy tuyến tính và ghép cặp rất tốt để kiểm soát nhiễu khi sử dụng dữ liệu không ngẫu nhiên, nhưng chúng lệ thuộc vào một giả thiết quan trọng: không nhiễu có điều kiện:

$
(Y_0, Y_1) \perp T | X
$

Nghĩa là chúng đòi hỏi tất cả các biến nhiễu đều đã được biết và đo lường, vì thế chúng ta có thể cố định chúng và khiến can thiệp tốt như thể ngẫu nhiên. Thách thức chính với điều này là đôi khi chúng ta không thể đo lường một biến nhiễu nào đó. Ví dụ, trong một bài toán kinh tế lao động cổ điển: ước lượng tác động thu nhập của hôn nhân đối với nam giới. Một sự thật được biết rộng rãi trong Kinh tế học là nam giới đã lập gia đình có thu nhập cao hơn nam giới độc thân. Điều còn chưa sáng tỏ là liệu mối quan hệ này có tính nhân quả hay không. Có thể do những người có học vấn tốt thường dễ kết hôn và đồng thời có thu nhập tốt hơn, nghĩa là học vấn là một biến nhiễu của tác động đối với thu nhập của hôn nhân. Ý thức được yếu tố gây nhiễu này, chúng ta có thể đo lường học vấn của các cá nhân tham gia nghiên cứu và chạy một mô hình hồi quy kiểm soát nó. Nhưng một biến nhiễu tiềm năng khác là độ điển trai. Có khả năng những người điển trai dễ kết hôn và cũng dễ kiếm được công việc thu nhập tốt. Đáng tiếc là độ điển trai không phải một đặc tính kiểu như trí tuệ. Nó không hề dễ định lượng chút nào.

Điều này đặt chúng ta vào một tình thế rất khó khăn, vì nếu chúng ta có những biễn nhiễu không được đo lường, chúng ta có thiên lệch. Một giải pháp là sử dụng biến công cụ, giống như chúng ta đã thấy trong bài trước. Nhưng tìm ra biến công cụ tốt không dễ và đòi hỏi rất nhiều sự sáng tạo. Ở đây chúng ta sẽ tìm hiểu một biện pháp khác tận dụng yếu tố thời gian và cấu trúc thời gian của dữ liệu.

Ý tưởng là sử dụng **dữ liệu bảng**. Dữ liệu bảng là khi ta có **nhiều quan sát cho cùng một đối tượng trong nhiều khoảng thời gian khác nhau**. Dữ liệu bảng rất phổ biến trong kinh doanh, nơi mà người ta lưu giữ các bản ghi về hành vi tiêu dùng của cùng một khách hàng tại nhiều thời điểm. Lý do để ta sử dụng dữ liệu bảng là vì nó cho phép ta so sánh cùng một đối tượng trước và sau can thiệp và theo dõi hành vi của họ. Trước khi đi sâu vào các công thức toán, hãy xem nó có ý nghĩa trực quan như thế nào.

```python
import warnings
warnings.filterwarnings('ignore')

import pandas as pd
import numpy as np
from matplotlib import style
from matplotlib import pyplot as plt
import statsmodels.formula.api as smf
import graphviz as gr

from linearmodels.panel import PanelOLS

%matplotlib inline
pd.set_option("display.max_columns", 6)
style.use("fivethirtyeight")
```

Trước hết, hãy nhìn vào đồ thị nhân quả mà chúng ta đã xây dựng cho nhiều quan sát của cùng một đối tượng theo thời gian. Giả sử chúng ta có tình huống mà tình trạng hôn nhân tại thời điểm 1 chi phối thu nhập ở cùng thời điểm và tình trạng hôn nhân ngay sau đó. Điều này cũng áp dụng cho thời điểm 2 và 3. Và giả sử độ điển trai là như nhau ở các thời điểm (giả định này quá cứng nhắc, nhưng có thể phù hợp nếu khoảng cách thời gian đủ ngắn ví dụ một vài năm) và nó chi phối cả tình trạng hôn nhân lẫn thu nhập.

```python
g = gr.Digraph()
g.edge("Kết_hôn_1", "Thu_nhập_1")
g.edge("Kết_hôn_1", "Kết_hôn_2")
g.edge("Kết_hôn_2", "Thu_nhập_2")
g.edge("Kết_hôn_2", "Kết_hôn_3")
g.edge("Kết_hôn_3", "Thu_nhập_3")

g.edge("Điển_trai", "Kết_hôn_1")
g.edge("Điển_trai", "Kết_hôn_2")
g.edge("Điển_trai", "Kết_hôn_3")

g.edge("Điển_trai", "Thu_nhập_1")
g.edge("Điển_trai", "Thu_nhập_2")
g.edge("Điển_trai", "Thu_nhập_3")

g
```

Lưu ý là chúng ta không thể kiểm soát độ điển trai vì chúng ta không thể lượng hóa nó. Nhưng chúng ta vẫn có thể sử dụng cấu trúc bảng và yếu tố này không còn là trở ngại nữa. Ý tưởng là chúng ta có thể thấy độ điển trai - cũng như nhiều đặc điểm khác không thay đổi theo thời gian như các đặc tính của một người. Và mặc dù chúng ta không thể kiểm soát các đặc tính này một cách trực tiếp, chúng ta có thể kiểm soát luôn bản thân đối tượng được nghiên cứu.

```python
g = gr.Digraph()
g.edge("Kết_hôn_1", "Thu_nhập_1")
g.edge("Kết_hôn_1", "Kết_hôn_2")
g.edge("Kết_hôn_2", "Thu_nhập_2")
g.edge("Kết_hôn_2", "Kết_hôn_3")
g.edge("Kết_hôn_3", "Thu_nhập_3")

g.edge("Đối tượng (Điển trai, Trí tuệ...)", "Kết_hôn_1")
g.edge("Đối tượng (Điển trai, Trí tuệ...)", "Kết_hôn_2")
g.edge("Đối tượng (Điển trai, Trí tuệ...)", "Kết_hôn_3")

g.edge("Đối tượng (Điển trai, Trí tuệ...)", "Thu_nhập_1")
g.edge("Đối tượng (Điển trai, Trí tuệ...)", "Thu_nhập_2")
g.edge("Đối tượng (Điển trai, Trí tuệ...)", "Thu_nhập_3")

g
```

Thử nghĩ xem. Chúng ta không biết các đặc tính như điển trai, trí tuệ, nhưng chúng ta biết chúng cùng thuộc về một người. Vì thế, chúng tạo ra một biến giả để xác định người đó và đưa nó vào một mô hình tuyến tính. Đây là điều chúng ta nhắc tới ở trên khi chúng ta kiểm soát đối tượng được nghiên cứu. Khi ước lượng tác động của hôn nhân đối với thu nhập sử dụng biến giả chỉ người trong mô hình của chúng ta, mô hình hồi quy sẽ xác định tác động của hôn nhân khi **giữ cố định người được nghiên cứu**. Việc thêm vào biến giả này được gọi là mô hình hiệu ứng cố định.

## Hiệu Ứng Cố Định

Để mô tả vấn đề bằng công thức toán học, trước hết hãy nhìn vào dữ liệu mà chúng ta có. Trong ví dụ trên, chúng ta muốn ước lượng tác động của hôn nhân đối với thu nhập. Dữ liệu gồm 2 biến `kết_hôn` và `log_lương`, cho nhiều đối tượng (`nr`) qua nhiều năm. Lưu ý là tiền lương đã được lấy log. Thêm vào đó, chúng ta đưa vào các biến kiểm soát khác như số giờ làm việc mỗi năm, số năm học vấn,...

```python
from linearmodels.datasets import wage_panel
data = wage_panel.load()
data.head()
```

      nr
      year
      black
      ...
      lwage
      expersq
      occupation

      0
      13
      1980
      0
      ...
      1.197540
      1
      9

      1
      13
      1981
      0
      ...
      1.853060
      4
      9

      2
      13
      1982
      0
      ...
      1.344462
      9
      9

      3
      13
      1983
      0
      ...
      1.433213
      16
      9

      4
      13
      1984
      0
      ...
      1.568125
      25
      5

5 rows × 12 columns

Generally, the fixed effect model is defined as

$
y_{it} = \beta X_{it} + \gamma U_i + e_{it}
$

where \\(y_{it}\\) is the outcome of individual \\(i\\) at time \\(t\\), \\(X_{it}\\) is the vector of variables for individual \\(i\\) at time \\(t\\), \\(U_i\\) is a set of unobservables for individual \\(i\\) that are unchanging through time, hence the lack of the time subscript, and \\(e_{it}\\) is the error term. For the education example, \\(y_{it}\\) is log wages,  \\(X_{it}\\) are the observable variables that change in time, like marriage and experience and \\(U_i\\) are the variables that are not observed but constant for each individual, like beauty and intelligence.

Now, remember how I've said that using panel data with a fixed effect model is as simple as adding a dummy for the entities. It's true, but in practice, we don't actually do it. Imagine a dataset where we have 1 million customers. If we add one dummy for each of them, we would end up with 1 million columns, which is probably not a good idea. Instead, we use the trick of partitioning the linear regression into 2 separate models. We've seen this before, but now is a good time to recap it. Suppose you have a linear regression model with a set of features \\(X_1\\) and another set of features \\(X_2\\).

$
\hat{Y} = \hat{\beta_1} X_1 + \hat{\beta_2} X_2
$

You can get the exact same \\(\hat{\beta_1}\\) parameter by doing

1. regress the the outcome \\(y\\) on the second set of features \\(\hat{y^*} = \hat{\gamma_1} X_2\\)
2. regress the first set of features on the second \\(\hat{X_1} = \hat{\gamma_2} X_2\\)
3. obtain the residuals \\(\tilde{X}_1 = X_1 - \hat{X_1}\\) and \\(\tilde{y}_1 = y_1 - \hat{y^*}\\)
4. regress the residuals of the outcome on the residuals of the features \\(\hat{y} = \hat{\beta_1} \tilde{X}_1\\)

The parameter from this last regression will be exactly the same as running the regression with all the features. But how exactly does this help us? Well, we can break the estimation of the model with the entity dummies into 2. First, we use the dummies to predict the outcome and the feature. These are steps 1 and 2 above.

Now, remember how running a regression on a dummy variable is as simple as estimating the mean for that dummy? If you don't, let's use our data to show how this is true. Let's run a model where we predict wages as a function of the year dummy.

Thông thường mô hình hiệu ứng cố định được mô tả như sau:

$
y_{it} = \beta X_{it} + \gamma U_i + e_{it}
$

trong đó \\(y_{it}\\) là kết quả của cá nhân \\(i\\) tại thời điểm \\(t\\), \\(X_{it}\\) là véc-tơ  chứa các biến số của cá nhân \\(i\\) tại thời điểm \\(t\\), \\(U_i\\) là các biến không quan sát được không đổi theo thời gian của cá nhân \\(i\\)  (vì thế không có chỉ số thời gian), và \\(e_{it}\\) là phần dư. Trong ví dụ về giáo dục, \\(y_{it}\\) là log tiền lương,  \\(X_{it}\\) là các biến quan sát được thay đổi theo thời gian, như tình trạng hôn nhân và kinh nghiệm và \\(U_i\\) là các biến không được quan sát nhưng không đổi cho mỗi cá nhân, như vẻ điển trai và trí tuệ.

Nào hãy nhớ lại những gì tôi đã nói. Sử dụng dữ liệu bảng với mô hình hiệu ứng cố định đơn giản giống như đưa vào mô hình các biến giả cho các đối tượng. Điều này đúng, nhưng trong thực tế, chúng ta thường không làm vậy. Hãy tưởng tượng một bộ dữ liệu với 1 triệu khách hàng. Nếu chúng ta đưa thêm 1 biến giả cho mỗi khách hàng, chúng ta sẽ phải tạo thêm 1 triệu cột dữ liệu nữa, và đó không phải ý hay. Thay vào đó, chúng ta sẽ sử dụng một mẹo, phân tách mô hình hồi quy tuyến tính thành 2 mô hình. Chúng ta từng nhìn thấy nó trước đây, nhưng bây giờ là lúc thích hợp để ôn lại. Giả sử bạn có một mô hình hồi quy tuyến tính với một nhóm các thuộc tính \\(X_1\\) và một nhóm các thuộc tính khác \\(X_2\\).

$
\hat{Y} = \hat{\beta_1} X_1 + \hat{\beta_2} X_2
$

Bạn có thể thu được giá trị chính xác của \\(\hat{\beta_1}\\) bằng cách:

1. hồi quy kết quả \\(y\\) theo nhóm các thuộc tính thứ hai \\(\hat{y^*} = \hat{\gamma_1} X_2\\)
2. hồi quy mỗi thuộc tính trong nhóm thứ nhất theo nhóm thứ hai  \\(\hat{X_1} = \hat{\gamma_2} X_2\\)
3. tính toán các phần dư \\(\tilde{X}_1 = X_1 - \hat{X_1}\\) và \\(\tilde{y}_1 = y_1 - \hat{y^*}\\)
4. hồi quy phần dư của kết quả theo phần dư của các thuộc tính \\(\hat{y} = \hat{\beta_1} \tilde{X}_1\\)

Tham số thu được từ mô hình hồi quy sau cùng sẽ giống hệt nếu ta chạy hồi quy theo tất cả các thuộc tính. Nhưng điều này có thể giúp ích chúng ta như thế nào? Chúng ta có thể phân chia việc ước lượng mô hình với các biến giả đối tượng thành hai. Trước hết, chúng ta sử dụng các biến giả để dự đoán kết quả và các thuộc tính. Chúng tương ứng với các bước 1 và 2 trên đây.

Nào, hãy nhớ rằng việc chạy hồi quy theo biến giả  đơn giản chỉ là ước lượng giá trị trung bình theo biến giả đó phải không? Nếu bạn không đồng ý, chúng ta hãy sử dụng dữ liệu để chứng minh. Hãy chạy một mô hình để dự đoán lương theo biến giả năm.

```python
mod = smf.ols("lwage ~ C(year)", data=data).fit()
mod.summary().tables[1]
```

         	            coef	     std err	      t	      P>|t|	  [0.025	    0.975]

  Intercept	           1.3935	     0.022	    63.462	  0.000	     1.350	     1.437

  C(year)[T.1981]	     0.1194	     0.031	     3.845	  0.000	     0.059	     0.180

  C(year)[T.1982]	     0.1782	     0.031	     5.738	  0.000	     0.117	     0.239

  C(year)[T.1983]	     0.2258	     0.031	     7.271	  0.000	     0.165	     0.287

  C(year)[T.1984]	     0.2968	     0.031	     9.558	  0.000	     0.236	     0.358

  C(year)[T.1985]	     0.3459	     0.031	    11.140	  0.000	     0.285	     0.407

  C(year)[T.1986]	     0.4062	     0.031	    13.082	  0.000	     0.345	     0.467

  C(year)[T.1987]	     0.4730	     0.031	    15.232	  0.000	     0.412	     0.534

Hãy lưu ý cách mô hình này dự đoán mức lương bình quân cho năm 1980 là 1.3935, năm 1981 là {{1.3935+0.1194}} (1.3935+0.1194), và tương tự cho các năm khác. Bây giờ nếu chúng ta tính giá trị trung bình theo năm, chúng ta thu được kết quả giống hệt (cộng giá trị hệ số chặn với hệ số của các năm để thu được giá trị trung bình năm).

```python
data.groupby("year")["lwage"].mean()
```

    year
    1980    1.393477
    1981    1.512867
    1982    1.571667
    1983    1.619263
    1984    1.690295
    1985    1.739410
    1986    1.799719
    1987    1.866479
    Name: lwage, dtype: float64

Điều này nghĩa là việc tính giá trị trung bình cho mỗi người trong bảng của chúng ta tương đương với việc hồi quy các biến khác theo các biến giả cho cá nhân. Điều này gợi ý quy trình ước lượng sau:

1. Tạo ra các biến khử giá trị trung bình theo thời gian bằng cách trừ đi giá trị trung bình của mỗi cá nhân:
\\(\ddot{Y}_{it} = Y_{it} -  \bar{Y}_i\\)
\\(\ddot{X}_{it} = X_{it} -  \bar{X}_i\\)
2. Chạy mô hình hồi quy \\(\ddot{Y}_{it}\\) theo \\(\ddot{X}_{it}\\)

Chú ý rằng, các biến không quan sát được và không đổi theo thời gian \\(U_i\\) tiêu biến, vì \\(\bar{U_i}=U_i\\):

\begin{align}
Y_{it} & = \beta X_{it} + \gamma U_i + e_{it} \\
\bar{Y}_{i} & = \beta \bar{X}_{it} + \gamma U_i + \bar{e}_{it} \\
(Y_{it} - \bar{Y}_{i}) & = (\beta X_{it} - \beta \bar{X}_{it}) + (\gamma U_i - \gamma U_i) + (e_{it}-\bar{e}_{it}) \\
(Y_{it} - \bar{Y}_{i}) & = \beta(X_{it} - \bar{X}_{it}) + (e_{it}-\bar{e}_{it}) \\
\ddot{Y}_{it} & = \beta \ddot{X}_{it} + \ddot{e}_{it} \\
\end{align}

Thực ra không chỉ các biến không quan sát được và không đổi theo thời gian tiêu biến. Điều này xảy ra cả với các biến quan sát được và không đổi theo thời gian. Vì lí do này, bạn không thể đưa vào mô hình bất kì biến nào không đổi theo thời gian, vì chúng sẽ tạo thành một  tổ hợp tuyến tính với các biến giả và mô hình sẽ không hoạt động.

Để kiểm tra các biến này, chúng ta chỉ cần nhóm dữ liệu theo cá nhân và tính tổng của độ lệch chuẩn. Giá trị của biến số nào bằng 0 thì biến đó cố định cho mỗi đối tượng.

```python
data.groupby("nr").std().sum()
```

    year            1334.971910
    black              0.000000
    exper           1334.971910
    hisp               0.000000
    hours         203098.215649
    married          140.372801
    educ               0.000000
    union            106.512445
    lwage            173.929670
    expersq        17608.242825
    occupation       739.222281
    dtype: float64

Trong dữ liệu của chúng ta, cần phải loại bỏ các biến giả cho chủng tộc như `black` và `hisp`, vì chúng cố định cho mỗi cá nhân. Hơn nữa, chúng ta phải loại bỏ học vấn `eudc`. Chúng ta cũng sẽ không sử dụng nghề nghiệp vì nó có thể đóng vai trò trung gian cho tác động của hôn nhân đối với tiền lương (Ví dụ những người độc thân có thể làm những công việc dài giờ hơn).

Để chạy mô hình hiệu ứng cố định, trước hết, hãy tính dữ liệu trung bình.

```python
Y = "lwage"
T = "married"
X = [T, "expersq", "union", "hours"]

mean_data = data.groupby("nr")[X+[Y]].mean()
mean_data.head()
```

      married
      expersq
      union
      hours
      lwage

      nr

      13
      0.000
      25.5
      0.125
      2807.625
      1.255652

      17
      0.000
      61.5
      0.000
      2504.125
      1.637786

      18
      1.000
      61.5
      0.000
      2350.500
      2.034387

      45
      0.125
      35.5
      0.250
      2225.875
      1.773664

      110
      0.500
      77.5
      0.125
      2108.000
      2.055129

Để khử giá trị trung bình (theo thời gian) của dữ liệu, chúng ta cần đặt chỉ số của dữ liệu gốc trùng với chỉ số cá nhân, `nr`. Sau đó chúng ta đơn giản chỉ cần lấy giá trị khung dữ liệu trừ đi giá trị trung bình của khung dữ liệu.

```python
deamed_data = (data
               .set_index("nr") # set the index as the person indicator
               [X+[Y]]
               - mean_data) # subtract the mean data

deamed_data.head()
```

Cuối cùng, chúng ta có thể chạy mô hình hiệu ứng cố định sử dụng dữ liệu đã khử giá trị trung bình theo thời gian.

```python
mod = smf.ols(f"{Y} ~ {'+'.join(X)}", data=deamed_data).fit()
mod.summary().tables[1]
```

      	         coef	     std err	      t	      P>|t|	  [0.025	    0.975]

  Intercept	 -8.327e-17	     0.005	 -1.64e-14	  1.000	    -0.010	     0.010

  married	       0.1147	     0.017	     6.756	  0.000	     0.081	     0.148

  expersq	       0.0040	     0.000	    21.958	  0.000	     0.004	     0.004

  union	         0.0784	     0.018	     4.261	  0.000	     0.042	     0.115

  hours	      -8.46e-05	  1.25e-05	    -6.744	  0.000	    -0.000	    -6e-05

Nếu chúng ta tin rằng hiệu ứng cố định loại trừ hoàn toàn thiên lệch thiếu biến, mô hình này cho ta biết việc kết hôn làm tăng thu nhập của nam giới thêm 11%. Kết quả này có ý nghĩa thống kê. Một chi tiết ở đây là đối với mô hình hiệu ứng cố định, các sai số chuẩn cần được nhóm cụm. Do đó thay vì tính ước lượng bằng tay (chỉ phù hợp cho lý do sư phạm), chúng ta có thể dùng thư viện `linearmodels` và đặt tham số thực `cluster_entity` là True.

```python
from linearmodels.panel import PanelOLS
mod = PanelOLS.from_formula("lwage ~ expersq+union+married+hours+EntityEffects",
                            data=data.set_index(["nr", "year"]))

result = mod.fit(cov_type='clustered', cluster_entity=True)
result.summary.tables[1]
```

Parameter Estimates

     	     Parameter	 Std. Err.	 T-stat	  P-value	 Lower CI	  Upper CI

  expersq	  0.0040	    0.0002	   16.552	  0.0000	   0.0035	    0.0044

  union	    0.0784	    0.0236	   3.3225	  0.0009	   0.0322	    0.1247

  married	  0.1147	    0.0220	   5.2213	  0.0000	   0.0716	    0.1577

  hours	   -8.46e-05	 2.22e-05	  -3.8105	 0.0001	   -0.0001	 -4.107e-05

Bạn có thể thấy các ước lượng hệ số giống với trường hợp sử dụng dữ liệu đã khử giá trị trung bình theo thời gian. Khác biệt duy nhất là các sai số chuẩn lớn hơn một chút. Nào, hãy so sánh chúng với OLS model đơn giản không tận dụng cấu trúc thời gian của dữ liệu. Với mô hình này, chúng ta đưa trở lại các biến không đổi theo thời gian.

```python
mod = smf.ols("lwage ~ expersq+union+married+hours+black+hisp+educ", data=data).fit()
mod.summary().tables[1]
```

      	         coef	     std err	      t	      P>|t|	  [0.025	    0.975]

  Intercept	     0.2654	     0.065	     4.103	  0.000	     0.139	     0.392

  expersq	       0.0032	     0.000	    15.750	  0.000	     0.003	     0.004

  union	         0.1829	     0.017	    10.598	  0.000	     0.149	     0.217

  married	       0.1410	     0.016	     8.931	  0.000	     0.110	     0.172

  hours	      -5.32e-05	  1.34e-05	    -3.978	  0.000	 -7.94e-05	  -2.7e-05

  black	        -0.1347	     0.024	    -5.679	  0.000	    -0.181	    -0.088

  hisp	          0.0132	     0.021	     0.632	  0.528	    -0.028	     0.054

  educ	          0.1057	     0.005	    22.550	  0.000	     0.097	     0.115

Mô hình này cho rằng kết hôn làm tăng tiền lương của nam giới thêm 14%. Mức tác động này cao hơn kết quả chúng ta tìm được với mô hình hiệu ứng cố định. Điều này gợi ý rằng có thiên lệch thiếu biến do các yếu tố cố định theo từng cá nhân, như trí tuệ và độ điển trai chưa được đưa vào mô hình.

## Biểu diễn Hiệu Ứng Cố Định
Để mở rộng hiểu biết về cách  mô hình hiệu ứng cố định hoạt động, hãy cùng xem xét một ví dụ khác. Giả sử bạn làm việc cho một công ty công nghệ và cần ước lượng tác động của chiến dịch marketing billboard (biển quảng cáo ngoài trời) cho dịch vụ mua bán qua ứng dụng. Khi nhìn vào dữ liệu quá khứ, bạn nhận thấy phòng marketing có xu hướng chi tiền cho biển quảng cáo ở các thành phố có lượng mua hàng thấp. Điều này có lí phải không? Họ đã chẳng cần marketing nhiều nếu nơi đó có lượng bán hàng ồ ạt. Nếu bạn chạy một mô hình hồi quy sử dụng dữ liệu này thì sẽ trông như thể chi phí marketing cao dẫn đến lượng mua hàng qua ứng dụng thấp.

```python
toy_panel = pd.DataFrame({
    "mkt_costs":[5,4,3.5,3, 10,9.5,9,8, 4,3,2,1, 8,7,6,4],
    "purchase":[12,9,7.5,7, 9,7,6.5,5, 15,14.5,14,13, 11,9.5,8,5],
    "city":["C0","C0","C0","C0", "C2","C2","C2","C2", "C1","C1","C1","C1", "C3","C3","C3","C3"]
})

m = smf.ols("purchase ~ mkt_costs", data=toy_panel).fit()

plt.scatter(toy_panel.mkt_costs, toy_panel.purchase)
plt.plot(toy_panel.mkt_costs, m.fittedvalues, c="C5", label="Đường Hồi quy")
plt.xlabel("Chi phí Marketing (ngàn)")
plt.ylabel("Lượng mua hàng qua ứng dụng (ngàn)")
plt.title("Mô hình OLS đơn giản")
plt.legend();
```

Hiểu rõ về suy luận nhân quả, bạn quyết định chạy một mô hình hiệu ứng cố định, đưa các biến chỉ báo thành phố dưới dạng các biến giả vào mô hình. Mô hình hiệu ứng cố định kiểm soát các đặc tính của thành phố không đổi theo thời gian, nếu một thành phố còn e dè với sản phẩm của bạn, nó sẽ nắm bắt điều này. Khi chạy mô hình này, cuối cùng bạn cũng thấy việc tăng thêm chi phí marketing quả thật mang lại lượng mua hàng qua ứng dụng nhiều hơn.

```python
fe = smf.ols("purchase ~ mkt_costs + C(city)", data=toy_panel).fit()

fe_toy = toy_panel.assign(y_hat = fe.fittedvalues)

plt.scatter(toy_panel.mkt_costs, toy_panel.purchase, c=toy_panel.city)
for city in fe_toy["city"].unique():
    plot_df = fe_toy.query(f"city=='{city}'")
    plt.plot(plot_df.mkt_costs, plot_df.y_hat, c="C5")

plt.title("Mô Hình Hiệu Ứng Cố Định")
plt.xlabel("Chi phí Marketing (ngàn)")
plt.ylabel("Lượng mua hàng qua ứng dụng (ngàn)");
```

Hãy dành một phút để đánh giá đồ thị phía trên và để nó cho bạn biết thực ra hiệu ứng cố định đang làm gì. Chú ý rằng hiệu ứng cố định đang ước lượng **một đường hồi quy cho mỗi thành phố**. Và hãy lưu ý các đường này song song với nhau. Độ dốc của đường hồi quy là tác động của chi phí marketing với lượng mua hàng qua ứng dụng. Vì thế **hiệu ứng cố định giả định rằng tác động nhân quả không đổi theo đối tượng**, trong trường hợp này là các địa bàn thành phố. Đây có thể là điểm yếu và cũng có thể là điểm mạnh, phụ thuộc vào việc bạn dùng nó vào việc gì. Nó là điểm yếu nếu bạn muốn tìm hiểu tác động nhân quả cụ thể cho từng thành phố. Vì mô hình FE giả định tác động này cố định theo đối tượng, bạn sẽ không thấy khác biệt trong tác động nhân quả. Tuy nhiên, nếu bạn muốn đánh giá tác động tổng thể của marketing với lượng mua hàng qua ứng dụng, cấu trúc dữ liệu bảng sẽ rất hữu dụng cho hiệu ứng cố định.

## Hiệu ứng thời gian

Giống như những gì chúng ta đã làm với hiệu ứng cố định cho các cá nhân, chúng ta có thể thiết kế một mô hình hiệu ứng cố định cho thời gian. Nếu ta đưa vào mô hình một biến giả cho mỗi cá nhân để kiểm soát các đặc tính cố định cho mỗi cá nhân,  ta có thể đưa vào các biến giả thời gian để kiểm soát các biến không đổi theo thời gian. Một ví dụ là biến lạm phát. Mức giá và tiền lương có xu hướng tăng theo thời gian. Nếu tiền lương và tỉ lệ kết hôn cũng tăng theo thời gian, thì thời gian là một biến nhiễu. Một ví dụ chặt chẽ hơn, giả sử tỉ lệ kết hôn tăng theo thời gian. Vì lạm phát làm mức lương tăng theo thời gian, một phần tương quan cùng chiều ta thấy giữa việc kết hôn và mức lương đơn giản là do cả hai đang tăng theo thời gian. Để sửa chữa nó, ta có thể đưa vào một biến biến giả cho mỗi thời điểm. Trong `linear models`, điều này được thực hiện dễ dàng bằng cách thêm `TimeEffects` vào công thức của chúng ta và đặt giá trị `cluster_time` bằng True.

```python
mod = PanelOLS.from_formula("lwage ~ expersq+union+married+hours+EntityEffects+TimeEffects",
                            data=data.set_index(["nr", "year"]))

result = mod.fit(cov_type='clustered', cluster_entity=True, cluster_time=True)
result.summary.tables[1]
```

Parameter Estimates

     	     Parameter	 Std. Err.	 T-stat	  P-value	 Lower CI	  Upper CI

  expersq	  -0.0062	   0.0008	   -8.1479	 0.0000	   -0.0077	   -0.0047

  union	    0.0727	    0.0228	   3.1858	  0.0015	   0.0279	    0.1174

  married	  0.0476	    0.0177	   2.6906	  0.0072	   0.0129	    0.0823

  hours	    -0.0001	  3.546e-05	 -3.8258	 0.0001	   -0.0002	 -6.614e-05

Trong mô hình mới này, tác động của hôn nhân đối với tiền lương giảm mạnh từ `0.1147` xuống `0.0476`. Kết quả này vẫn có ý nghĩa thống kê ở mức 99%, vì thế nam giới vẫn có thể kì vọng tăng lương nhờ vào kết hôn.

## Khi Dữ Liệu Bảng Không thể Giúp bạn

Sử dụng dữ liệu bảng và mô hình hiệu ứng cố định là công cụ vô cùng hiệu quả cho suy luận nhân quả. Khi bạn không có dữ liệu ngẫu nhiên hoặc biến công cụ tốt, hiệu ứng cố định là phương tiện suy luận nhân quả thuyết phục với dữ liệu phi thực nghiệm. Tuy vậy nó không phải liều thuốc vạn năng. Có những tình huống mà ngay cả dữ liệu bảng không thể giúp bạn.

Trường hợp dễ thấy nhất là khi bạn có các biến nhiễu thay đổi theo thời gian. Hiệu Ứng Cố Định chỉ có thể loại bỏ thiên lệch từ các thuộc tính cố định theo cá nhân. Ví dụ, giả sử trí tuệ được tăng cường khi đọc nhiều sách và tiêu thụ nhiều chất béo tốt. Điều này giúp bạn kiếm được công việc tốt và lấy được vợ. Hiệu ứng cố định sẽ không thể loại bỏ  loại thiên lệch phát sinh từ nhiễu trí tuệ không được đo lường vì trong ví dụ này trí tuệ thay đổi theo thời gian.

Một trường hợp khác khó thấy hơn là khi hiệu ứng cố định không thể giúp khi bạn gặp phải **quan hệ nhân quả ngược**. Ví dụ, hãy nói rằng việc kết hôn không làm tăng thu nhập của bạn. Phải chăng thu nhập cao làm tăng khả năng kết hôn của bạn? Trong trường hợp này, bạn sẽ thấy mối tương quan cùng chiều nhưng mức thu nhập được định trước. Chúng sẽ cùng thay đổi theo thời gian và theo cùng một hướng, vì vậy hiệu ứng cố định sẽ không thể kiểm soát được điều này.

## Ý tưởng chủ đạo

Trong bài này chúng ta đã thấy cách sử dụng dữ liệu bảng, loại dữ liệu với nhiều kết quả đo cho cùng cá nhân tại các thời điểm khác nhau. Trong trường hợp này, chúng ta cần sử dụng mô hình hiệu ứng cố định kiểm soát các thuộc tính cố định theo thời gian của mỗi đối tượng. Đây là một phương pháp rất hiệu quả và thuyết phục để kiểm soát các biến nhiễu và có thể áp dụng tốt cho dữ liệu không ngẫu nhiên.

Cuối cùng, chúng ta đã thấy FE không phải liều thuốc vạn năng. Chúng ta đã nắm được hai tình huống mà phương pháp này ít tác dụng: khi chúng ta có quan hệ nhân quả ngược và khi các biến nhiễu không đo được thay đổi theo thời gian.

## Tài liệu tham khảo

Tôi muốn dành loạt bài viết này để vinh danh Joshua Angrist, Alberto Abadie and Christopher Walters vì khóa học Kinh tế lượng tuyệt cú mèo của họ. Phần lớn ý tưởng trong loạt bài này được lấy từ các bài giảng của họ được tổ chức bởi Hiệp hội Kinh tế Mĩ.  Theo dõi các bài giảng này là những gì tôi làm trong suốt năm 2020 khó nhằn.
* [Kinh tế lượng với dữ liệu chéo](https://www.aeaweb.org/conference/cont-ed/2017-webcasts)
* [Luyện chưởng Kinh tế lượng Gần như Vô hại](https://www.aeaweb.org/conference/cont-ed/2020-webcasts)

Tôi cũng muốn giới thiệu cuốn sách lý thú của Angrist. Chúng cho tôi thấy Kinh tế lượng, hoặc 'Lượng theo cách họ gọi không chỉ vô cùng hữu ích mà còn rất vui.

* [Kinh tế lượng Gần như Vô hại](https://www.mostlyharmlesseconometrics.com/)
* [Luyện chưởng 'Lượng](https://www.masteringmetrics.com/)

Tài liệu tham khảo cuối cùng của tôi là cuốn sách của Miguel Hernan and Jamie Robins. Nó là người bạn đồng hành tin cậy với tôi khi trả lời những câu hỏi nhân quả khó nhằn.

* [Sách Suy Luận Nhân Quả](https://www.hsph.harvard.edu/miguel-hernan/causal-inference-book/)

Cuối cùng, tôi cũng muốn tán dương Scott Cunningham và và sản phẩm xuất sắc của ông pha trộn Suy Luận Nhân Quả với lời thoại Rap:

* [Suy Luận Nhân Quả: Đĩa nhạc](https://www.scunning.com/mixtape.html)

# Bảng Từ Viết tắt
|Viết tắt| Tiếng Anh | Tiếng Việt |
| --- | --- | --- |
|FE|Fixed Effects|FE|

# Bảng Thuật ngữ
| Thuật ngữ | Tiếng Anh |
| --- | --- |
|biến|variable|
|biến công cụ|instrumental-variable, instrumental variable, instrument, instrument variable|
|biến giả|dummy, dummy variable|
|biến nhiễu|confounder, confounding variable|
|biễn nhiễu không được đo lường|unmeasured confounder|
|bảng|panel|
|cấu trúc bảng|panel structure|
|cố định|condition on|
|dữ liệu|data|
|dữ liệu bảng|panel data|
|dữ liệu không ngẫu nhiên|non-random data|
|dữ liệu ngẫu nhiên|random data|
|dữ liệu phi thực nghiệm|non experimental data|
|dữ liệu đã khử giá trị trung bình theo thời gian|time-demeaned data|
|dự đoán|predict, prediction|
|ghép cặp|matching|
|giả thiết|assumption|
|hiệu ứng cố định|fixed-effect, fixed effects|
|hệ số chặn|intercept|
|hồi quy|regression, regress|
|hồi quy tuyến tính|linear regression|
|khung dữ liệu|data frame|
|không nhiễu có điều kiện|conditional unconfoundedness|
|khử giá trị trung bình (theo thời gian)|deamean|
|khử giá trị trung bình theo thời gian|time-demeaned|
|kinh tế lượng|econometrics|
|kết quả|outcome|
|mô hình|model|
|mô hình fe|fe model|
|mô hình hiệu ứng cố định|fixed effects model, fixed effect model|
|mô hình hồi quy|regression model|
|mô hình hồi quy tuyến tính|linear regression model|
|mô hình tuyến tính|linear model|
|nhóm cụm|clustered|
|phần dư|error term, residual|
|quan hệ nhân quả ngược|reverse causality|
|quan sát|observe, observation|
|sai số chuẩn|standard error|
|suy luận nhân quả|causal inference, causal reasoning|
|tham số|parameter|
|tham số thực|true parameter, argument|
|thiên lệch|bias|
|thiên lệch thiếu biến|omitted variable bias|
|thuộc tính|feature|
|thư viện|library|
|tổ hợp tuyến tính|linear combination|
|véc-tơ|vector|
|điểm xu hướng|propensity score|
|đường hồi quy|fitted line, regression line|
|độ dốc|slope|
|độ lệch chuẩn|standard deviation|
|ước lượng hệ số|parameter estimate|


```python
#remove cell visibility
from IPython.display import HTML
tag = HTML('''
Toggle cell visibility <a href="javascript:code_toggle()">here</a>.''')
display(tag)
```

Toggle cell visibility here.

```python
%matplotlib inline
import control
import numpy
import sympy as sym
from IPython.display import display, Markdown
import ipywidgets as widgets
import matplotlib.pyplot as plt

#print a matrix latex-like
def bmatrix(a):
     """Returns a LaTeX bmatrix - by Damir Arbula (ICCT project)

     :a: numpy array
     :returns: LaTeX bmatrix as a string
     """
     if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
     lines = str(a).replace('[', '').replace(']', '').splitlines()
     rv = [r'\begin{bmatrix}']
     rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
     rv +=  [r'\end{bmatrix}']
     return '\n'.join(rv)

# Display formatted matrix:
def vmatrix(a):
    if len(a.shape) > 2:
         raise ValueError('bmatrix can at most display two dimensions')
    lines = str(a).replace('[', '').replace(']', '').splitlines()
    rv = [r'\begin{vmatrix}']
    rv += ['  ' + ' & '.join(l.split()) + r'\\' for l in lines]
    rv +=  [r'\end{vmatrix}']
    return '\n'.join(rv)

#matrixWidget is a matrix looking widget built with a VBox of HBox(es) that returns a numPy array as value !
class matrixWidget(widgets.VBox):
    def updateM(self,change):
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.M_[irow,icol] = self.children[irow].children[icol].value
                #print(self.M_[irow,icol])
        self.value = self.M_

    def dummychangecallback(self,change):
        pass

    def __init__(self,n,m):
        self.n = n
        self.m = m
        self.M_ = numpy.matrix(numpy.zeros((self.n,self.m)))
        self.value = self.M_
        widgets.VBox.__init__(self,
                             children = [
                                 widgets.HBox(children =
                                              [widgets.FloatText(value=0.0, layout=widgets.Layout(width='90px')) for i in range(m)]
                                             )
                                 for j in range(n)
                             ])

        #fill in widgets and tell interact to call updateM each time a children changes value
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
                self.children[irow].children[icol].observe(self.updateM, names='value')
        #value = Unicode('example@example.com', help="The email value.").tag(sync=True)
        self.observe(self.updateM, names='value', type= 'All')

    def setM(self, newM):
        #disable callbacks, change values, and reenable
        self.unobserve(self.updateM, names='value', type= 'All')
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].unobserve(self.updateM, names='value')
        self.M_ = newM
        self.value = self.M_
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].value = self.M_[irow,icol]
        for irow in range(0,self.n):
            for icol in range(0,self.m):
                self.children[irow].children[icol].observe(self.updateM, names='value')
        self.observe(self.updateM, names='value', type= 'All')

                #self.children[irow].children[icol].observe(self.updateM, names='value')

#overlaod class for state space systems that DO NOT remove "useless" states (what "professor" of automatic control would do this?)
class sss(control.StateSpace):
    def __init__(self,*args):
        #call base class init constructor
        control.StateSpace.__init__(self,*args)
    #disable function below in base class
    def _remove_useless_states(self):
        pass
```

## Autonomous Underwater Vehicle (AUV) depth control

Dynamics of the pitch angle $\theta$ of an Autonomous Underwater Vehicle (AUV) can be modelled as:
$$
J_y\ddot{\theta}=−D\dot{\theta}+CV\gamma,
$$
where $J_y=8.333$ kg$\text{m}^2$ is the vehicle moment of inertia around the pitch axis, $D = 30$ Nms the drag coefficient, $C=45$ Ns the tail fin effectiveness coefficient, $\gamma$ the tail fin angle (expressed in radians and to be kept within $\pm10°$), and $V$ current cruising speed in m/s.

The vertical (heave) velocity $w$ of an AUV can be modelled approximately as:
$$
w = V\theta.
$$

For safety reasons, the pitch angle should never exceed $\pm20°$ during operation. Pitch angle and depth ($z$) are measured by a Navigation Filter.

The dynamics equations in state space form are
\begin{cases}
    \dot{x} = \begin{bmatrix} -D/J_y & 0 & 0 \\ 1 & 0 & 0 \\ 0 & V & 0 \end{bmatrix}x + \begin{bmatrix} CV/J_y \\ 0 \\ 0 \end{bmatrix}u\\
    y = \begin{bmatrix} 0 & 1 & 0 \\ 0 & 0 & 1 \end{bmatrix}x,
\end{cases}

where $x=\begin{bmatrix} x_1 & x_2 & x_3 \end{bmatrix}^T=\begin{bmatrix} \dot{\theta} & \theta & z \end{bmatrix}^T$ and $u=\gamma$.

Considering $V=1$ m/s constant, we want to develop a regulator for $z$ which allows to have zero steady-state error in response to a step position request.

### Regulator design
#### Controller design

To meet the requirements we first add a new state:
$$
\dot{x_4} = z-y_d = x_3 - y_d
$$
The resulting augmented system is thus:
\begin{cases}
    \dot{x_a} = \begin{bmatrix} -D/J_y & 0 & 0 & 0 \\ 1 & 0 & 0 & 0 \\ 0 & V & 0 & 0 \\ 0 & 0 & 1 & 0 \end{bmatrix}x_a + \begin{bmatrix} CV/J_y & 0 \\ 0 & 0 \\ 0 & 0 \\ 0 & -1 \end{bmatrix} \begin{bmatrix} u \\ y_d \end{bmatrix}\\
    y = \begin{bmatrix} 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}x_a,
\end{cases}

where $x_a = \begin{bmatrix} x_1 & x_2 & x_3 & x_4 \end{bmatrix}^T = \begin{bmatrix} \dot{\theta} & \theta & z & x_4 \end{bmatrix}^T$.

With the first input $u$, since the system is controllable, we place the poles in order to stabilize the system and respect the limits on $u$ ($\pm10°$) and $\theta$ ($\pm20°$) when giving a step of $y_d=1$ m. A possible solution is to place the poles in $-5.9$, $-0.9$ and $-0.63\pm0.70i$.

#### Observer design

We have the measurements of $x_2$, $x_3$ and $x_4$ so we need only to estimate $x_1$. We can work with the subsystem
$x_l =\begin{bmatrix} x_1 & x_2 \end{bmatrix}^T$, $\dot{x_l}=A_lx_l + B_lu$ and $y_l = x_2 = C_lx_l$. Defining $L=\begin{bmatrix} l_1 & l_2 \end{bmatrix}^T$ we can calculate the eigenvalues $\lambda_1$ and $\lambda_2$ of $A_l-LC_l$ and solve for $l_1$ and $l_2$:
\begin{cases}
l_1 = \lambda_1\lambda_2 + D\lambda_1/J_y + D\lambda_2/J_y + D^2/J_y^2 \\
l_2 = -\lambda_1 - \lambda_2 - D/J_y \, .
\end{cases}

We choose $\lambda_1 = \lambda_2 = -15$.

### How to use this notebook?
- Try, changing the poles of the controller, to meet the requirements with $V = 0.75$ m/s.
- Verify the behaviour of the regulator in presence of an initial error in the estimate and try to improve its performance.

```python
# Preparatory cell

X0 = numpy.matrix('0.0')
K = numpy.matrix([0,0,0,0])
L = numpy.matrix([[0],[0]])

X0w = matrixWidget(1,1)
X0w.setM(X0)
Kw = matrixWidget(1,4)
Kw.setM(K)
Lw = matrixWidget(2,1)
Lw.setM(L)

eig1c = matrixWidget(1,1)
eig2c = matrixWidget(2,1)
eig3c = matrixWidget(1,1)
eig4c = matrixWidget(2,1)
eig1c.setM(numpy.matrix([-5.7]))
eig2c.setM(numpy.matrix([[-0.63],[-0.70]]))
eig3c.setM(numpy.matrix([-0.9]))
eig4c.setM(numpy.matrix([[-0.63],[-0.70]]))

eig1o = matrixWidget(1,1)
eig2o = matrixWidget(2,1)
eig1o.setM(numpy.matrix([-15.]))
eig2o.setM(numpy.matrix([[-15.],[0.]]))
```

```python
# Misc

#create dummy widget
DW = widgets.FloatText(layout=widgets.Layout(width='0px', height='0px'))

#create button widget
START = widgets.Button(
    description='Test',
    disabled=False,
    button_style='', # 'success', 'info', 'warning', 'danger' or ''
    tooltip='Test',
    icon='check'
)

def on_start_button_clicked(b):
    #This is a workaround to have intreactive_output call the callback:
    #   force the value of the dummy widget to change
    if DW.value> 0 :
        DW.value = -1
    else:
        DW.value = 1
    pass
START.on_click(on_start_button_clicked)

# Define type of method
selm = widgets.Dropdown(
    options= ['Set K and L', 'Set the eigenvalues'],
    value= 'Set the eigenvalues',
    description='',
    disabled=False
)

# Define the number of complex eigenvalues
selec = widgets.Dropdown(
    options= ['0 complex eigenvalues', '2 complex eigenvalues', '4 complex eigenvalues'],
    value= '2 complex eigenvalues',
    description='Eig controller:',
    disabled=False
)
seleo = widgets.Dropdown(
    options= ['0 complex eigenvalues', '2 complex eigenvalues'],
    value= '0 complex eigenvalues',
    description='Eig observer:',
    disabled=False
)

#define type of ipout
selu = widgets.Dropdown(
    options=['impulse', 'step', 'sinusoid', 'square wave'],
    value='step',
    description='Type of reference:',
    style = {'description_width': 'initial'},
    disabled=False
)
# Define the values of the input
u = widgets.FloatSlider(
    value=1,
    min=0,
    max=4,
    step=0.1,
    description='',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.1f',
)
v = widgets.FloatSlider(
    value=1,
    min=0.5,
    max=4,
    step=0.1,
    description=r'$V$:',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.1f',
)
period = widgets.FloatSlider(
    value=0.5,
    min=0.001,
    max=10,
    step=0.001,
    description='Period: ',
    disabled=False,
    continuous_update=False,
    orientation='horizontal',
    readout=True,
    readout_format='.2f',
)

simTime = widgets.FloatText(
    value=10,
    description='',
    disabled=False
)
```

```python
# Support functions

def eigen_choice(selec,seleo):
    if selec == '0 complex eigenvalues':
        eig1c.children[0].children[0].disabled = False
        eig2c.children[1].children[0].disabled = True
        eig3c.children[0].children[0].disabled = False
        eig4c.children[0].children[0].disabled = False
        eig4c.children[1].children[0].disabled = True
        eigc = 0
    if seleo == '0 complex eigenvalues':
        eig1o.children[0].children[0].disabled = False
        eig2o.children[1].children[0].disabled = True
        eigo = 0
    if selec == '2 complex eigenvalues':
        eig1c.children[0].children[0].disabled = False
        eig2c.children[1].children[0].disabled = False
        eig3c.children[0].children[0].disabled = False
        eig4c.children[0].children[0].disabled = True
        eig4c.children[1].children[0].disabled = True
        eigc = 2
    if seleo == '2 complex eigenvalues':
        eig1o.children[0].children[0].disabled = True
        eig2o.children[1].children[0].disabled = False
        eigo = 2
    if selec == '4 complex eigenvalues':
        eig1c.children[0].children[0].disabled = True
        eig2c.children[1].children[0].disabled = False
        eig3c.children[0].children[0].disabled = True
        eig4c.children[0].children[0].disabled = False
        eig4c.children[1].children[0].disabled = False
        eigc = 4
    return eigc, eigo

def method_choice(selm):
    if selm == 'Set K and L':
        method = 1
        selec.disabled = True
        seleo.disabled = True
    if selm == 'Set the eigenvalues':
        method = 2
        selec.disabled = False
        seleo.disabled = False
    return method
```

```python
D = 30
c = 45
V = 1
Jy = 8.333

A = numpy.matrix([[-D/Jy, 0, 0, 0],
                  [1, 0, 0, 0],
                  [0, V, 0, 0],
                  [0, 0, 1, 0]])
Bu = numpy.matrix([[c*V/Jy],[0],[0],[0]])
Bref = numpy.matrix([[0],[0],[0],[-1]])
C = numpy.matrix([0,0,1,0])

Al = numpy.matrix([[-D/Jy, 0],
                  [1, 0]])
Cl = numpy.matrix([0,1])

def main_callback2(v, X0w, K, L, eig1c, eig2c, eig3c, eig4c, eig1o, eig2o, u, period, selm, selec, seleo, selu, simTime, DW):
    eigc, eigo = eigen_choice(selec,seleo)
    method = method_choice(selm)

    A = numpy.matrix([[-D/Jy, 0, 0, 0],
                  [1, 0, 0, 0],
                  [0, v, 0, 0],
                  [0, 0, 1, 0]])
    Bu = numpy.matrix([[c*v/Jy],[0],[0],[0]])

    if method == 1:
        solc = numpy.linalg.eig(A-Bu*K)
        solo = numpy.linalg.eig(Al-L*Cl)
    if method == 2:
        if eig1c[0,0]==eig2c[0,0] or eig1c[0,0]==eig3c[0,0] or eig1c[0,0]==eig4c[0,0]:
            eig1c[0,0] -= 0.0001
        if eig2c[0,0]==eig3c[0,0] or eig2c[0,0]==eig4c[0,0]:
            eig3c[0,0] -= 0.0002
        if eigc == 0:
            K = control.acker(A, Bu, [eig1c[0,0], eig2c[0,0], eig3c[0,0], eig4c[0,0]])
            Kw.setM(K)
        if eigc == 2:
            K = control.acker(A, Bu, [eig3c[0,0],
                                      eig1c[0,0],
                                      numpy.complex(eig2c[0,0], eig2c[1,0]),
                                      numpy.complex(eig2c[0,0],-eig2c[1,0])])
            Kw.setM(K)
        if eigc == 4:
            K = control.acker(A, Bu, [numpy.complex(eig4c[0,0], eig4c[1,0]),
                                      numpy.complex(eig4c[0,0],-eig4c[1,0]),
                                      numpy.complex(eig2c[0,0], eig2c[1,0]),
                                      numpy.complex(eig2c[0,0],-eig2c[1,0])])
            Kw.setM(K)
        if eigo == 0:
            temp1 = eig1o[0,0]
            temp2 = eig2o[0,0]
            L = numpy.matrix([[temp1*temp2 + D*temp1/Jy + D*temp2/Jy + D**2/Jy**2], [-temp1 - temp2 - D/Jy]])
            Lw.setM(L)
        if eigo == 2:
            temp1 = numpy.complex(eig2o[0,0], eig2o[1,0])
            temp2 = numpy.complex(eig2o[0,0],-eig2o[1,0])
            L = numpy.matrix([[temp1*temp2 + D*temp1/Jy + D*temp2/Jy + D**2/Jy**2], [-temp1 - temp2 - D/Jy]])
            L = numpy.real(L)
            Lw.setM(L)

    sys = sss(A,numpy.hstack((Bu,Bref)),[[0,1,0,0],[0,0,1,0],[0,0,0,1]],[[0,0],[0,0],[0,0]])
    syse = sss(Al-L*Cl,numpy.hstack((Bu[0:2],L)),[1,0],[0,0])
    sysc = sss(0,[0,0,0,0],0,-K)
    sys_append = control.append(sys,syse,sysc)
    sys_CL = control.connect(sys_append,
                             [[1,5],[3,5],[4,1],[5,4],[6,1],[7,2],[8,3]],
                             [2],
                             [2,5])

    X0w1 = numpy.zeros((6,1))
    X0w1[4,0] = X0w
    if simTime != 0:
        T = numpy.linspace(0, simTime, 10000)
    else:
        T = numpy.linspace(0, 1, 10000)

    if selu == 'impulse': #selu
        U = [0 for t in range(0,len(T))]
        U[0] = u
        T, yout, xout = control.forced_response(sys_CL,T,U,X0w1)
    if selu == 'step':
        U = [u for t in range(0,len(T))]
        T, yout, xout = control.forced_response(sys_CL,T,U,X0w1)
    if selu == 'sinusoid':
        U = u*numpy.sin(2*numpy.pi/period*T)
        T, yout, xout = control.forced_response(sys_CL,T,U,X0w1)
    if selu == 'square wave':
        U = u*numpy.sign(numpy.sin(2*numpy.pi/period*T))
        T, yout, xout = control.forced_response(sys_CL,T,U,X0w1)

    try:
        step_info_dict = control.step_info(sys_CL[0,0],SettlingTimeThreshold=0.05,T=T)
        print('Step info: \n\tRise time =',step_info_dict['RiseTime'],'\n\tSettling time (5%) =',step_info_dict['SettlingTime'],'\n\tOvershoot (%)=',step_info_dict['Overshoot'])
        print('Max x_2 value (% of 20°)=', max(abs(xout[1]))/(numpy.pi/180*20)*100)
        print('Max u value (% of 10°)=', max(abs(yout[1]))/(numpy.pi/180*10)*100)
    except:
        print("Error in the calculation of step info.")

    fig = plt.figure(num='Simulation1', figsize=(14,12))

    fig.add_subplot(221)
    plt.title('Output response')
    plt.ylabel('Output')
    plt.plot(T,yout[0],T,U,'r--')
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.legend(['$y$','Reference'])
    plt.grid()

    fig.add_subplot(222)
    plt.title('Input')
    plt.ylabel('$u$')
    plt.plot(T,yout[1])
    plt.plot(T,[numpy.pi/180*10 for i in range(len(T))],'r--')
    plt.plot(T,[-numpy.pi/180*10 for i in range(len(T))],'r--')
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.grid()

    fig.add_subplot(223)
    plt.title('States response')
    plt.ylabel('States')
    plt.plot(T,xout[0],
             T,xout[1],
             T,xout[2],
             T,xout[3],
             T,[numpy.pi/180*20 for i in range(len(T))],'r--',
             T,[-numpy.pi/180*20 for i in range(len(T))],'r--')
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.legend(['$x_{1}$','$x_{2}$','$x_{3}$','$x_{4}$','+lim $x_2$','-lim $x_2$'])
    plt.grid()

    fig.add_subplot(224)
    plt.title('Estimated errors')
    plt.ylabel('Error')
    plt.plot(T,xout[4]-xout[0])
    plt.xlabel('$t$ [s]')
    plt.axvline(x=0,color='black',linewidth=0.8)
    plt.axhline(y=0,color='black',linewidth=0.8)
    plt.legend(['$e_{1}$'])
    plt.grid()
    #plt.tight_layout()

alltogether2 = widgets.VBox([widgets.HBox([selm,
                                          selec,
                                          seleo,
                                          selu]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([widgets.VBox([widgets.Label('K:',border=3), Kw,
                                                        widgets.Label('Eigenvalues:',border=3),
                                                        widgets.HBox([eig1c,
                                                                      eig2c,
                                                                      eig3c,
                                                                      eig4c])]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([widgets.VBox([widgets.HBox([widgets.Label('L:',border=3), Lw, widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('Eigenvalues:',border=3),
                                          eig1o,
                                          eig2o,
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                          widgets.Label('X0 est.:',border=3), X0w]),
                                          widgets.Label(' ',border=3),
                                          widgets.Label(' ',border=3),
                                                       widgets.HBox([
                                          widgets.VBox([widgets.Label('Simulation time [s]:',border=3)]),
                                          widgets.VBox([simTime])])])]),
                                          widgets.Label(' ',border=3)]),
                            widgets.Label(' ',border=3),
                            widgets.HBox([widgets.Label('Reference [m]:',border=3),u,
                                          v]),
                            widgets.HBox([period,START])])
out2 = widgets.interactive_output(main_callback2, {'v':v, 'X0w':X0w, 'K':Kw, 'L':Lw,
                                                   'eig1c':eig1c, 'eig2c':eig2c, 'eig3c':eig3c, 'eig4c':eig4c,
                                                   'eig1o':eig1o, 'eig2o':eig2o,
                                                   'u':u, 'period':period, 'selm':selm, 'selec':selec, 'seleo':seleo, 'selu':selu, 'simTime':simTime, 'DW':DW})
out2.layout.height = '880px'
display(out2, alltogether2)
```

    Output(layout=Layout(height='880px'))

    VBox(children=(HBox(children=(Dropdown(index=1, options=('Set K and L', 'Set the eigenvalues'), value='Set the…

```python

```