#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# Aufgabenkontrolle11.py

import numpy as np


def Aufgabe42(testfunktion):
    '''Aufgabe40 testet automatisch, ob die übergebene Funktion "testfunktion"
    die Anforderungen aus Aufgabe 42 erfüllt.
    Dazu löst es mit "testfunktion" einige lineare Gleichungssysteme
    verschiedener Größe und überprüft das Residuum.
    '''
    print('')
    print('Ihre Funktion wird jetzt mit 4 Gleichungssystemen getestet.');
    print('Wenn die Norm des Residuums klein ist (<10^-8), ist der Test bestanden.')
    print('')
    
    AsUndBs = [
        (np.array([
            [ 0, 1, 2 ],
            [ 1, 2, 5 ],
            [ 2, 3, 4 ],
        ], dtype = float), np.random.rand(3, 1)),
        _generierteZufallsmatrixMitGuterKonditionUndVektor(5, float),
        _generierteZufallsmatrixMitGuterKonditionUndVektor(100, int),
        _generierteZufallsmatrixMitGuterKonditionUndVektor(100, float),
    ]
    texte = [
        "der Matrix\n{0}",
        "einer reellen {0.shape[0]} x {0.shape[1]} Matrix",
        "einer ganzzahligen {0.shape[0]} x {0.shape[1]} Matrix",
        "einer reellen {0.shape[0]} x {0.shape[1]} Matrix",
    ]
    
    tb = 0
    for (A, b), text in zip(AsUndBs , texte):
        text = text.format(A)
        print(f"Teste mit {text}.")
        x = testfunktion(A.copy(), b)
        residuum = A @ x - b
        resNorm = np.linalg.norm(residuum)
        print(f"Die Norm des Residuums ist {resNorm}.\n")
        if resNorm < 1e-8:
            tb += 1
    
    print(f'{tb} von {len(AsUndBs)} Tests erfolgreich')


def _generierteZufallsmatrixMitGuterKonditionUndVektor(N, typ):
    """
    Generiert eine Zufallsmatrix vorgegebener Größe und vorgegebenem Typs
    so, dass die Konditionszahl der Matrix gut ist.
    """
    while True:
        # Wir raten so lange Matrizen, bis die Kondition der geratenen Matrix
        # gut ist.
        A = 200 * ((np.random.rand(N, N) - 1/2) * 2)
        A = A.astype(typ)
        if typ == complex:
            # Rate zusätzlichen Imaginärteil (brauchen wir hier nicht)
            A += 1j * 200 * ((np.random.rand(N, N) - 1/2) * 2)
        if np.linalg.cond(A) <= N:
            break
    b = np.random.rand(N, 1)
    return A, b
    
    
def TestLR_Bruch(testfunktion):
    m = 10
    print(f'Ihre Funktion wird jetzt mit 3 ({m}x{m})-Matrizen getestet...');
    A = _zufallsmatINT(m)
    B = _zufallsmatBRUCH(m)
    C1 = _zufallsmatINT(m)
    C = _zufallsmatBRUCH(m)
    for i in range(m):
        for j in range(m):
            zufallsindex = np.random.randint(0,2)
            if zufallsindex == 0:
                C[i, j] = C1[i, j]
    texte = ['Matrix mit Integer-Einträgen',\
             'Matrix mit Bruch-Einträgen',\
             'Matrix mit gemischten Einträgen']
    tb = 0
    for M, text in zip([A, B, C], texte):
        print('Teste ' + text + ':')
        p, MLR = testfunktion(M)
        R = np.triu(MLR)
        L = np.eye(MLR.shape[0], dtype=int) + np.tril(MLR, -1)
        erg = np.equal(M[p,:], L@R).all()
        print(f'Das Ergebnis ist {"richtig" if erg else "falsch"}.');
        if erg:
            tb += 1;
    
    print(f'{tb} von 3 Tests erfolgreich.');

def _zufallsmatINT(m, Null = True):
    mat = np.random.randint(-20,20, m**2)
    if Null == False:
        mat[mat == 0] += 1
    return mat.reshape(m,m)

def _zufallsmatBRUCH(m):
    ZufINT1 = _zufallsmatINT(m)
    ZufINT2 = _zufallsmatINT(m, Null = False)
    MatBRUCH = np.zeros((m,m), dtype=Bruch)
    for i in range(m):
        for j in range(m):
            MatBRUCH[i,j] = Bruch(int(ZufINT1[i,j]), int(ZufINT2[i,j]))
    return MatBRUCH
