/* Entwicklung einer Funktion in ein Lagrange Polynom (mit ausgelagerter 'LagrangePoly'-Klasse, inline-Member Funktion f(x) und Member Ausgabefunktion)
 * Mittels der Methode der Lagrange Polynome entwickelt man eine Funktion ( hier speziell f(x)=1/x )  
 * durch Angabe von N+1 vorgegebener Punkte in ein Lagrange Polynom vom Grade N. 
 * Hier speziell 7 Punkte
 * Ausgabe zum Plotten (Gnuplot oder Python) mittels: "./a.out > Lagrange_Polynom_Klasse.dat" */
#include <iostream>                                                    // Ein- und Ausgabebibliothek
#include <iomanip>                                                     // Formatierung von Ein- und Ausgaben (z.B. setw, setprecision)
#include <vector>                                                      // Vector-Container der Standardbibliothek
#include <functional>                                                  // Funktionen in der Argumentenliste von Funktionen
using namespace std;                                                   // Benutze den Namensraum std

//Definition der Klasse 'LagrangePoly'
class LagrangePoly {
    // Private Instanzvariablen (Daten-Member) der Klasse
    vector<double> points;                                             // Vector-Container der Stützstellen
    function<double(double)> f;                                        // Allgemeine Funktion f(x)


    // Private Member-Funktionen der Klasse zur Berechnung des approximierten Polynomwertes an der Stelle x
    double p(double x) {
        double Pfp = 0;                                                // Deklaration und Initialisierung des Funktionswertes des approximierten Polynoms
        double Lk = 0;                                                 // Deklaration und Initialisierungeiner Zusatzvariable
        for(size_t k = 0; k < points.size(); ++k){                     // For-Schleife der Summation in der Lagrange Polynom Methode
            Lk=1;                                                      // Initialisierung der Produktvariable Lk mit 1
            for(size_t i = 0; i < points.size(); ++i){                 // For-Schleife der Produktbildung in der Lagrange Polynom Methode
                if(i != k){                                            // Die Produktbildung soll nur fuer (i ungleich k) erfolgen
                    Lk *= (x - points[i])/(points[k] - points[i]);     // Berechnung der Lk-Werte in der Lagrange Polynom Methode
                }                                                      // Ende if-Bedingung
            }                                                          // Ende for-Schleife der Produktbildung
            Pfp += f(points[k])*Lk;                                    // Kern-Gleichung in der Lagrange Polynom Methode
        }                                                              // Ende for-Schleife der Summenbildung
        return Pfp;                                                    // Rueckgabe des berechneten, approximierten Polynomwertes
    }                                                                  // Ende der Member-Funktion p(double x)
    
    // Oeffentliche Konstruktoren und Member-Funktionen der Klasse
    public:
        // Konstruktor mit zwei Argumenten
        LagrangePoly(const vector<double>& set_points, function<double(double)> set_f) : points{set_points}, f{set_f}{}

        // Öffentliche Member-Funktion zur Ausgabe der Ergebnisse im x-Intervall [a,b] mit N_xp Punkten
        void printOutput(double a, double b, unsigned int N_xp) {
            double dx = (b - a)/N_xp;                                          // Abstand dx zwischen den aequidistanten Punkten des x-Intervalls
            double x;                                                          // Aktueller x-Wert

            cout << "# x-Werte der " << setw(3) << points.size() << " Stuetzstellen-Punkte: \n";
            for(size_t k = 0; k < points.size(); ++k){                         // For-Schleife der Ausgabe der Stuetzstellen x-Werte
                cout << setw(10) << fixed << setprecision(5) << points[k];      // Ausgabe der Stuetzpunkte
            }                                                                  // Ende for-Schleife
            cout << "\n";                                                      // Zeilenumbruch
            cout << "# 0: Index j \n# 1: x-Wert \n# 2: f(x)-Wert \n";          // Beschreibung der ausgegebenen Groessen
            cout << "# 3: Approximierter Wert des Lagrange Polynoms P(x) \n";  // Beschreibung der ausgegebenen Groessen
            cout << "# 4: Fehler zum wirklichen Wert f(x)-P(x) \n";            // Beschreibung der ausgegebenen Groessen

            // For-Schleife die ueber die einzelnen Punkte des x-Intervalls [a,b] geht
            for(unsigned int j = 0; j <= N_xp; ++j){
                x = a + j*dx;                                                  // Aktueller x-Wert
                cout << setw(3) << j << " " << fixed << setprecision(10)       // Ausgabe der berechneten Werte
                     << setw(14) << x << " " << setw(14) << f(x) << " "        // Ausgabe der berechneten Werte
                     << setw(14) << p(x) << " " << setw(14) << (f(x) - p(x))   // Ausgabe der berechneten Werte
                     << " \n";
            }                                                                  // Ende for-Schleife der Ausgabe
        }                                                                      // Ende der Member-Funktion zur Ausgabe
};                                                                             // Ende der Klassendefinition

// Definition der Funktion f(x)
double f(double x){
    double wert;
    wert = 1.0/x;                                                      // Eigentliche Definition der Funktion
    return wert;                                                       // Rueckgabewert der Funktion f(x)
}                                                                      // Ende der Funktion f(x)

int main(){                                                                    // Hauptfunktion
    vector<double> points = { 1, 1.5, 2, 2.5, 3, 5, 7 };                       // Deklaration und Initialisierung der Punkte als Vector-Container
    
    LagrangePoly Poly1 {points, f};                                            // Aufruf des Konstruktors der Klasse LagrangePoly (Erzeugung des Objektes 'Poly1')
    Poly1.printOutput(0.5, 7.5, 300);                                          // Aufruf der Ausgabe-Member-Funktion des Objektes 'Poly1'
}                                                                              // Ende der Hauptfunktion
