# matplotlib und numpy importieren
%pylab nbagg

# Arbeiten mit Daten
import pandas as pd

# Zum Fitten
import lmfit  

# Fehlerrechnung
import uncertainties as uct
from uncertainties.umath import *

"""
Explanation: Auswertung (Uni Mainz PGP2 SS17 Blockpraktikum)
Es bietet sich an zuerst alle Pakete zu importieren, die zum Ausführen des Notebooks benötigt werden. Stellt man während der Arbeit am Notebook fest, dass zusätzliche Pakete benötigt werden, sollte die Liste hier ergänzt werden.
End of explanation
"""


def residual(userfcn):
    """
    Gibt für eine Modellfunktion die entsprechende Residuenfunktion 
    zurück, die für gewisse Parameter und x/y-Wertepaare die Residuen 
    (modell(x) - y) berechnet. Wird ein Fehler angegeben, dann werden 
    die Residuen noch mit dem Kehrwert des Fehlers gewichtet.
    
    Parameters
    ----------
    userfcn : callable
        The model function for which the residuals should be calculated. 
        This should take two arguments params and x, where params 
        denotes the parameters (lmfit.Parameters.parameter instance) to 
        be used in the model and x denotes the values for which the model 
        function should be evaluated. 
        
    Returns
    -------
    residualfunction : callable
        This function calculates the residuals . It takes the model parameters and
        x/y-values as arguments and optionally the uncertainties of the y-values
        as a keyword argument 'eps='.
        
    Note
    ----
    In Summary the userfcn should be a callable of the form
    
        model(params, x)
        
    which returns a ndarray with the y-values corresponding to the x-values and the
    model with the given parameters. The residual function which is returned is of 
    the form
    
        residual(params, x, y, eps=yerr)
        
    and returns a ndarray with the corresponding residuals for the parameters and the
    given x/y-value pairs.
    """
    def out(*args, **kwargs):
        p, x, y = args
        assert isinstance(p, lmfit.parameter.Parameters)
        if "eps" in kwargs:
            eps = kwargs.pop("eps")
        else:
            eps = None
            
        modell = userfcn(p.valuesdict(), x)
        if eps is None:
            residual = abs(modell-y)
        else:
            residual = abs((modell-y)/eps)
        return residual
    return out

"""
Explanation: Die Fitroutine minimiert das $\chi^2$, welches aus den Quadraten der Residuen (=Abweichung der Modellvorhersage von den Messwerten) berechnet wird. Die folgende Funktion bestimmt für ein beliebiges Modell die dazugehörige Residuenfunktion. Es ist nicht wichtig, diesen Quellcode zu verstehen. Im Zweifel reicht es, diese Zelle einfach auszuführen, damit die Funktion definiert ist.
End of explanation
"""


plt.rcParams['font.size'] = 10
plt.rcParams['axes.grid'] = True
plt.rcParams['figure.figsize'] = (4,3)
plt.rcParams['figure.dpi'] = 144

"""
Explanation: Zusätzlich ändern wir einige globale Optionen zur Formatierung der Plots. Es ist sinnvoll, das auch am Anfang des Notebooks zu tun, damit diese Änderungen für alle Plots wirksam sind.
End of explanation
"""


data = pd.read_csv('dat/a1-100ohm.csv')

"""
Explanation: Nun zur eigentlichen Auswertung. Wir beginnen damit, die Daten von Aufgabe 1 zu importieren.
Daten importieren
Wir importieren erst einmal die Daten. Dazu nutzen wir das Paket pandas (http://pandas.pydata.org/), welches exzellent ist, um mit Daten zu arbeiten. Wir haben es als pd importiert. Das heißt, immer, wenn wir pd schreiben, weiß der Python-Interpreter, dass eigentlich pandas gemeint ist. Wir verwenden den Befehl read_csv für den Datenimport.
End of explanation
"""


# Betrachte die ersten 4 (=Standardwert) Zeilen
data.head()

"""
Explanation: Jetzt sind die Daten importiert und wir schauen sie uns an.
End of explanation
"""


data.head(20)

"""
Explanation: Das sind nur Einstellungen von dem Oszilloskop, die brauchen wir gar nicht! wir betrachten noch ein paar mehr der ersten Zeilen, um herauszufinden, wo die Daten anfangen.
End of explanation
"""


data = pd.read_csv('dat/a1-100ohm.csv', skiprows=16)

"""
Explanation: Offensichtlich fangen die Daten in Zeile 16 an. Wir können also getrost die ersten 16 Zeilen beim Import überspringen (die erste Zeile der Datei wird standardmäßig für die Spaltennamen verwendet).
End of explanation
"""


data.head()

"""
Explanation: Jetzt betrachten wir erneut die ersten Zeilen der importierten Daten.
End of explanation
"""


data = pd.read_csv('dat/a1-100ohm.csv', skiprows=16, usecols=[3, 4])
data.head()

"""
Explanation: Wir brauchen auch nur die vierte und fünfte Spalte, also importieren wir nur diese. Achtung! Python beginnt immer bei 0 mit Zählen, also hat die vierte Spalte den Index 3 und die fünfte Spalte den Index 4. Außerdem betrachten wir direkt die ersten Zeilen der importierten Daten.
End of explanation
"""


data = pd.read_csv('dat/a1-100ohm.csv', skiprows=16, usecols=[3, 4], names=["t", "U"])
data.head()

"""
Explanation: Jetzt ergänzen wir noch sinnvolle Spaltenüberschriften in unserem Import-Befehl und betrachten die Daten.
End of explanation
"""


data = pd.read_csv('dat/a1-100ohm.csv', skiprows=16, usecols=[3, 4], names=["t", "U"])

# Zeit von Sekunden in Nanosekunden umrechnen
data.t *= 1e9

# Daten plotten, um einen Überblick zu gewinnen
data.plot(x="t", y="U")

"""
Explanation: Das sieht gut aus, damit können wir arbeiten. Jetzt plotten wir die Daten. Es ist an dieser Stelle sinnvoll, die Zeitdaten direkt nach dem Import in Nanosekunden umzurechnen.
End of explanation
"""


# Fitmodell definieren
def aufladekurve_kondensator(params, t):
    try :
        p = params  # needed for fitting with lmfit
    except:
        p = params.valuesdict()  # needed later for plotting a fitresult
        
    return p["U0"] * (1 - np.exp(-(t - p["t0"]) / p["tau"])) + p["offset"]
modell = aufladekurve_kondensator
    
# Startparameter festlegen
params = lmfit.Parameters()
params.add("U0", value=11.6, vary=True)
params.add("tau", 500)
params.add("t0", -50)
params.add("offset", -12)

# Messwerte für das Fitten auswählen
# Die Daten für t<0 werden für den Fit hier ignoriert, da zu sehen ist,
# dass der Anstieg der Spannung nicht durch unser Modell beschrieben wird.
# Das ist hier auch nicht schlimm, da noch genügend Messdaten vorliegen.
# Es ist auch sinnvoll, da die Rechteckspannung an den Rändern nicht perfekt
# ist. 
fitdata = data[(data.t > 0)]  

# Fitdaten auswählen
X, Y = fitdata.t, fitdata.U

# Fehler der y-Werte definieren
Yerr = 0.03 * fitdata.U

"""
Explanation: Fitten
Diesen Datensatz wollen wir Fitten. Zum Fitten benötigt man immer ein Modell, Startparameter und Messwerte, für die der Fit durchgeführt werden soll. Wir definieren alles in der nächsten Zelle.
End of explanation
"""


# chi2 minimizer erstellen
minimizer = lmfit.Minimizer(userfcn=residual(modell), 
                            params=params, 
                            fcn_args=(X,Y), 
                            fcn_kws=dict(eps=Yerr),
                            nan_policy="omit"
                           )

# fit auch tatsächlich durchführen
result = minimizer.minimize()

# ergebnis ausgeben
print(lmfit.fit_report(result))

"""
Explanation: Jetzt erstellen wir einen $\chi^2$-Minimierer und führen den Fit durch. Definiert man das Modell immer in der Variable modell, die Parameter immer in params und die Messwerte in (X, Y, Yerr). Dann kann die nächste Zelle für jeden Fit 1zu1 übernommen werden.
End of explanation
"""


# plotten
fig, ax = plt.subplots()

data.plot(ax=ax, x="t", y="U", label="Messwerte")
ax.plot(data.t, modell(result.params, data.t), label="Fit", c="r")

ax.legend(loc="lower right", title='Kondensator ($R=100\,\Omega$)')

ax.set_ylim(-20,20)
ax.set_xlim(-500,None)

ax.set_xlabel('Zeit $t$ [ns]')
ax.set_ylabel('Spannung $U$ [V]')

plt.tight_layout()
plt.savefig('a1-kondensator-100ohm.pdf')
plt.show()

"""
Explanation: Jetzt plotten wir das Resultat.
End of explanation
"""


# Daten importieren 
data = pd.read_csv('data/a1-aufladekurve-spule-100ohm.csv', skiprows=16, usecols=[3,4], names=["t", "U"])

# Zeit von Sekunden in millisekunden umrechnen
data.t = data.t * 1e6

# Fitmodell definieren
def aufladekurve_spule(params, t):
    try :
        p = params  # needed for fitting with lmfit
    except:
        p = params.valuesdict()  # needed later for plotting a fitresult
        
    return p["U0"] * (np.exp(-(t - p["t0"]) / p["tau"])) + p["offset"]
modell = aufladekurve_spule

# Startparameter festlegen
params = lmfit.Parameters()
params.add("U0", 22.5, vary='False')
params.add("tau", 100)
params.add("t0", -50)
params.add("offset", -12)

# Fitten
fitdata = data[(data.t > 10)]
X, Y = fitdata.t, fitdata.U
Yerr = 0.03 * fitdata.U
minimizer = lmfit.Minimizer(userfcn=residual(modell), 
                            params=params, 
                            fcn_args=(X,Y), 
                            fcn_kws=dict(eps=Yerr),
                            nan_policy="omit"
                           )
result = minimizer.minimize()
print(lmfit.fit_report(result))

# Plot erstellen
fig, ax = plt.subplots()
data.plot(ax=ax, x="t", y="U", label="Messwerte")
ax.plot(data.t, aufladekurve_spule(result.params, data.t), label="Fit", c="r")
ax.legend(loc="upper right", title='Kondensator ($R=100\,\Omega$)')
ax.set_xlim(-50,200)
ax.set_ylim(-5,25)
ax.set_xlabel('Zeit $t$ [$\mu$s]')
ax.set_ylabel('Spannung $U$ [V]')
plt.tight_layout()
plt.savefig('a1-spule-100ohm.pdf')
plt.show()

"""
Explanation: Jetzt fitten wir die Kurve für die Spule. Die Dokumentation wird abgekürzt.
End of explanation
"""


# Definiere Zeitkonstanten und Widerstand mit Unsicherheiten aus den Fits
tauC= uct.ufloat(242.68,1.27)
tauL = uct.ufloat(50.67, 0.25)
R = uct.ufloat(100, 100*0.05)

# Berechne Kapazität und Induktivität sowie Resonanzfrequenz 
C = tauC/R * 1e-9
L = tauL * R * 1e-6
omega = 1 / (sqrt(L*C))
nu = omega / (2*pi)

# Gebe Ergebnis aus
for i,j in zip(['C = ', 'L = ', 'omega = ', 'nu = '], [C, L, omega, nu]):
    print('{}{}'.format(i,j))

"""
Explanation: Mit Messunsicherheiten rechnen
Dazu benutzen wir das Paket uncertainties, das uns die Fehlerrechnung abnimmt.
End of explanation
"""


# Daten plotten
data = pd.read_csv('dat/a2-100ohm.csv', skiprows=15, usecols=[3,4], names=["t", "U"])
data.t *= 1e6

fig, ax = plt.subplots(dpi=144)
data.plot(ax=ax, x='t', legend=False)
ax.set_xlabel('Zeit $t$ [$\mu$s]')
ax.set_ylabel('Spannung $U$ [V]')
ax.set_ylim(-.3,.3)
plt.tight_layout()
plt.savefig('a2-100ohm.pdf')
plt.show()

"""
Explanation: Aufgabe 2
Wir plotten die Messung.
End of explanation
"""


# abgelesene Werte zweier Punkte gleicher Phase nach N Perioden
t1 = uct.ufloat(12.09, .5)
t2 = uct.ufloat(194.6, 1)
N = 8

# periodendauer
T = (t2 - t1) / N

# frequenz
f = 1/T

# kreisfrequenz
omega = 2*pi*f

# ausgabe der werte
for i,j in zip(['T', 'f', 'omega'], [T,f,omega]):
    print('{} = '.format(i), '{:f}'.format(j))

"""
Explanation: Bestimmung von Periodendauer, Frequenz und Kreisfrequenz.
End of explanation
"""


# Werte aus Graphen ablesen
U1 = uct.ufloat(0.25, 0.01)
U2 = uct.ufloat(0.05, 0.01)
N = 8

# log Dekrement und Güte berechen
delta = 1 / N * log(U1 / U2)
q = pi / delta
tau = q / omega

# Ausgabe der Werte
for i,j in zip(['delta', 'q', 'tau'], [delta, q, tau]):
    print('{} = '.format(i), '{:f}'.format(j))

"""
Explanation: Bestimmung des logarithmischen Dekrements und der Güte
End of explanation
"""


# Plot erstellen
data1 = pd.read_csv('dat/a3-a-kleinefrequenz-100ohm.csv', skiprows=16, usecols=[3,4], names=["t", "U"])
data1.t *= 1e6

data2 = pd.read_csv('dat/a3-a-resonanzfrequenz-100ohm.csv', skiprows=16, usecols=[3,4], names=["t", "U"])
data2.t *= 1e6


fig, (ax1, ax2) = plt.subplots(2)
data1.plot(ax=ax1, x='t', title='$\\omega \\ll \\omega_0$', legend=False)
data2.plot(ax=ax2, x='t', title='$\\omega = \\omega_0$', legend=False)
for ax in (ax1, ax2):
    ax.set_xlabel('Zeit $t$ [$\mu$s]')
    ax.set_ylabel('Spannung $U$ [V]')
ax1.set_yticks([-2,-1,0,1,2])
ax2.set_yticks([-40,-20,0,20,40])
plt.tight_layout()
plt.savefig('a3-a-100ohm.pdf')
plt.show()

"""
Explanation: Aufgabe 3
End of explanation
"""


# Werte ablesen
Uklein = uct.ufloat(1.87, 0.1)
Ures = uct.ufloat(32.5, 1.2)

# Güte bestimmen
q = Ures / Uklein

# Ausgabe
print("q = ", q)

"""
Explanation: Resonanzüberhöhung bzw. Güten berechnen
End of explanation
"""


# Plot
data = pd.read_csv('dat/a3-b-resonanz.csv')
data.columns = ['t', 'U']

fig, ax = plt.subplots()
data.plot(ax=ax, x='t', legend=False)
ax.set_xlabel('Zeit $t$ [$\mu$s]')
ax.set_ylabel('Spannung $U$ [V]')
plt.tight_layout()
plt.savefig('a3-b-resonanz.pdf')
plt.show()

# Ablesen der Werte
Umax = Ures
U1 = uct.ufloat(4, 2)
U2 = uct.ufloat(30,2)
N = 14

t1 = uct.ufloat(44e-6,1e-6)
t2 = uct.ufloat(469e-6, 1e-6)
Nperioden = 19
T = (t2 - t1) / Nperioden
omega = 2 * pi / T

# Berechnung der Zeitkonstanten
delta_anschwing = 1 / N * log((Umax - U1) / (Umax - U2))
q = pi / delta_anschwing
tau = pi / (delta_anschwing * omega)

# Ausgabe
for i,j, in zip(['delta_anschwing', 'q', 'T', 'omega', 'tau'], [delta_anschwing, q, T, omega, tau]):
    print('{} = {}'.format(i,j))

"""
Explanation: Zeitkonstante des Anschwingens bestimmen
End of explanation
"""


data1 = pd.read_csv('dat/a3-b-schwebung+5kHz-100ohm.csv', skiprows=16, usecols=[3,4], names=['t', 'U'])
data2 = pd.read_csv('dat/a3-b-schwebung-5kHz-100ohm.csv', skiprows=16, usecols=[3,4], names=['t', 'U'])
data3 = pd.read_csv('dat/a3-b-schwebung+10kHz-100ohm.csv', skiprows=16, usecols=[3,4], names=['t', 'U'])
data4 = pd.read_csv('dat/a3-b-schwebung-10kHz-100ohm.csv', skiprows=16, usecols=[3,4], names=['t', 'U'])

for data in [data1, data2, data3, data4]:
    data.t *= 1e6

fig, ((ax1, ax3), (ax2, ax4)) = plt.subplots(2,2, figsize=(8,6), dpi=100)
for data, ax, title in zip(
    [data1, data2, data3, data4], 
    [ax1, ax2, ax3, ax4], 
    ['$+5$ kHz', '$-5$ kHz', '$+10$ kHz', '$-10$ kHz']
):
    data.plot(ax=ax, x='t', legend=False, title=title)
    
for ax in (ax1, ax2, ax3, ax4):
    ax.set_xlim(0,400)
    ax.set_ylim(-12, 12)
for ax in (ax1, ax2):
    ax.set_ylabel('Spannung $U$ [V]')
for ax in (ax2, ax4):
    ax.set_xlabel('Zeit $t$ [$\mu$s]')
for ax in (ax1, ax3):
    ax.set_xlabel('')
plt.tight_layout()
plt.savefig('a3-b-schwebungen.pdf')
plt.show()

"""
Explanation: Plotten der übrigen Anschwingkurven
End of explanation
"""


# Daten einlesen
data = pd.read_csv('dat/a4-100ohm.csv', comment='#')

# div's in Spannungen umrechnen
data.U0 = data.U0 * data.skal
data.U1 = data.U1 * data.skal

# alpha berechnen
data['alpha'] = arcsin(np.sqrt(data.U1 / data.U0)) / pi
for i in arange(14,28):
    data.ix[i,'alpha'] = 1 - data.ix[i,'alpha']
data

# fehler für alpha und für f berechnen
deltaU = 0.2 * data.skal
data['alpha_err'] = np.sqrt(
    deltaU**2 / data.U0**2 / (1 - (data.U1**2 / data.U0**2)) \
    + deltaU**2 * data.U1**2 / data.U0**4 / (1 - (data.U1**2 / data.U0**2))
)
data['f_err'] = 0.1

# Plotten
fig, ax = plt.subplots()
data.plot(ax=ax, kind='scatter', x='f',y='alpha', yerr='alpha_err', legend=False, edgecolor='none', s=10)
ax.set_xlabel('Frequenz $f$ [kHz]')
ax.set_ylabel('Phasenverschiebung $\\alpha$')
ax.set_yticks([0,0.25,0.5,0.75,1])
ax.set_yticklabels(['$0$', '$\\frac{\\pi}{4}$', '$\\frac{\\pi}{2}$', '$\\frac{3\\pi}{4}$', '$\\pi$'])
ax.set_ylim(0,1)
plt.tight_layout()
plt.savefig('a4-100ohm.pdf')
plt.show()

data.U0.max()/sqrt(2)
fres = uct.ufloat(44.70, 0.01)

# fmin und fmax aus graph ablesen
fmin = uct.ufloat(42.70, 0.01)
fmax = uct.ufloat(46.65, 0.01)

q = fres / (fmax - fmin)
tau = q / fres

for i, j in zip(['fres', 'fmin', 'fmax', 'q', 'tau'], [fres, fmin, fmax, q, tau]):
    print('{} = {}'.format(i,j))

"""
Explanation: Aufgabe 4
Hier wurde die Datei 'a4-data-100ohm' selbst erstellt, deshalb sieht der Import-Befehl anders aus.
End of explanation
"""


data = pd.read_csv('dat/a5-100ohm.csv', skiprows=16, usecols=[3,4,10], names=['t', 'CH1', 'CH2'])

# Bestimmung der Frequenzskalierung
# ---------------------------------

# Diese Frequenzen habt ihr (theoretisch) mit einer Rechteckspannung am Wobblegenerator bestimmt
fmin = uct.ufloat(10, 2)  
fmax = uct.ufloat(83, 2)

# Den Wert hättet ihr (theoretisch) beim Anlegen der Rechteckspannung ablesen sollen.
# Er war aber bei jeder Praktikumsgruppe identisch mit dem der Dreieckspannung
Urechteck = uct.ufloat(40, 0.5)  

# Hier muss natürlich die zweifache Amplitude der Dreieckspannung abgelesen werden,
# da die Frequenzen ja auch vom Minimum bis zum Maximum der Dreieckspannung durch-
# laufen werden
Udreieck = uct.ufloat(40, 0.5)

# Der Umrechnungsfaktor zwischen Rechteck und Dreieckspannung, da die Grenzfrequenzen
# (eigentlich) bei Rechteckspannung bestimmt werden, aber die Messung bei Dreieckspannung
# durchgeführt wird
gamma = Urechteck / Udreieck

# Die Resonanzspannung von CH1 ist eigentlich nur wichtig, um die Höhe bei dem 
# 1/sqrt(2)-fachen einzuzeichnen
Ures = uct.ufloat(12.33, 0.5)

# Die Anzahl der Kästchen auf der x-Achse 
smin = uct.ufloat(0.069, 0.001)
smax = uct.ufloat(0.717, 0.001)
sres = uct.ufloat(0.393, 0.001)
s = smax - smin

# Skalierung berechnen
skal = (fmax * gamma - fmin) / s

# Bestimmung von delta omega
Ured = Ures / sqrt(2)

# sklein und sgross können natürlich erst mithilfe des unteren Plots abgelesen werden, indem
# die axhline bei y=Ures/sqrt(2) hinzugefügt wird
sklein = uct.ufloat(0.382, 0.001)
sgross = uct.ufloat(0.405, 0.001)

# Güte bestimmen
delta_omega = (sgross - sklein) * skal
omega = fmin + (sres - smin) * skal
q = omega / delta_omega

for i,j in zip(['gamma', 's', 'skal', 'delta_omega', 'omega', 'q'], [gamma, s, skal, delta_omega, omega, q]):
    print('{} = {}'.format(i,j))

# Plot
fig, ax = plt.subplots()

data.plot(ax=ax, x='t', y="CH2", color="g", legend=False)

ax2 = ax.twinx()
data.plot(ax=ax2, x='t', y="CH1", color="b", legend=False)
ax2.set_ylabel("Amplitude CH1 [div]")


ax.set_xlabel('Frequenzachse [div]')
ax.set_ylabel('Amplitude CH2 [div]')

ax.set_xlim(0.,0.8)
ax.set_ylim(-21,21)

ax.axhline(y=Ures.nominal_value / sqrt(2), xmin=0.2, xmax=0.8, c='r', linewidth=1.)
ax.axvline(x=smin.nominal_value, c='g', linewidth=1., alpha=0.5)
ax.axvline(x=smax.nominal_value, c='g', linewidth=1., alpha=0.5)
ax.axvline(x=sklein.nominal_value, c='y', linewidth=1., alpha=0.5)
ax.axvline(x=sgross.nominal_value, c='y', linewidth=1., alpha=0.5)
ax.axvline(x=sres.nominal_value, c='m', linewidth=1., alpha=0.5)

ax.set_title('Resonanzkurve, 100$\Omega$')
ax.annotate('$\\frac{U_{\\rm{res}}}{\\sqrt{2}}$', 
            xy=(0.655, Ures.nominal_value / sqrt(2)), 
            xycoords='data', 
            ha='left', va='center', 
            color="r")

handles, labels = ax.get_legend_handles_labels()
hndls2, lbls2 = ax2.get_legend_handles_labels()

handles.append(hndls2[0])
labels.append(lbls2[0])

ax.legend(reversed(handles), reversed(labels), loc="upper left", fontsize="small")

plt.grid()
plt.tight_layout()
plt.savefig('a5-100ohm.pdf')
plt.show()

"""
Explanation: Aufgabe 5
End of explanation
"""


# Plot
fig, ax = plt.subplots()

data.plot(ax=ax, x='t', y="CH2", color="g", legend=False)

ax2 = ax.twinx()
data.plot(ax=ax2, x='t', y="CH1", color="b", legend=False)
ax2.set_ylabel("Amplitude CH1 [div]")


ax.set_xlabel('Frequenzachse [div]')
ax.set_ylabel('Amplitude CH2 [div]')

ax.set_xlim(0.3,0.5)
ax.set_ylim(-21,21)

ax.axhline(y=Ures.nominal_value / sqrt(2), xmin=0.2, xmax=0.8, c='r', linewidth=1.)
ax.axvline(x=sklein.nominal_value, c='y', linewidth=1., alpha=0.5)
ax.axvline(x=sgross.nominal_value, c='y', linewidth=1., alpha=0.5)
ax.axvline(x=sres.nominal_value, c='m', linewidth=1., alpha=0.5)

ax.set_title('Resonanzkurve (vergrößert), 100$\Omega$')
ax.annotate('$\\frac{U_{\\rm{res}}}{\\sqrt{2}}$', 
            xy=(0.655, Ures.nominal_value / sqrt(2)), 
            xycoords='data', 
            ha='left', va='center', 
            color="r")

handles, labels = ax.get_legend_handles_labels()
hndls2, lbls2 = ax2.get_legend_handles_labels()

handles.append(hndls2[0])
labels.append(lbls2[0])

ax.legend(reversed(handles), reversed(labels), loc="upper left", fontsize="small")

plt.grid()
plt.tight_layout()
plt.savefig('a5-100ohm.pdf')
plt.show()

"""
Explanation: Vergrößerter Plot um die Resonanz
End of explanation
"""


