[JAVA] Objektorientierte numerische Berechnung

Einführung

Dieser Artikel ist der Artikel zum dritten Tag des Meiji University Advent Calender 2018. Dies ist der zweite Beitrag am dritten Tag. Fahrradbetrieb Adventskalender. Vielen Dank.

Ziel

** (Hinweis 1) Die verwendete Sprache ist Verarbeitung (fast Java). Der Unterschied zu Java besteht darin, dass die Zeichenfläche erheblich ist. ** ** ** ** (Achtung 2) Es gibt keine Möglichkeit, die Berechnung zu beschleunigen, da sie objektorientiert implementiert wird. Es gibt auch einen Abschnitt, in dem die Vor- und Nachteile beschrieben werden. ** ** **

Struktur dieses Artikels

Einführung der numerischen Berechnungsmethode (Euler-Methode)

Einführung in die Euler-Methode. Die Euler-Methode ist eines der Schemata (numerische Berechnungsmethoden). Zuerst werde ich es in einer allgemeinen Form vorstellen, und dann werde ich vorstellen, was passiert, wenn es sich um ein bestimmtes Problem handelt. Angenommen, das folgende Anfangswertproblem der normalen Differentialgleichung ist gegeben.

\frac{dx}{dt} = f(x, t), \quad x(0 ) = x_0

Wenn Sie es von Hand in Form von $ f $ lösen können, können Sie es tun, aber wenn Sie es nicht lösen können, ist es schnell, sich auf numerische Berechnungen zu verlassen. Die Euler-Methode entspricht der Unterscheidung der obigen stehenden Teilungsgleichung, um die folgende allmähliche Gleichung zu erstellen und zu lösen. Beachten Sie, dass $ dt $ die Zeitdifferenzbreite ist, die ein Parameter dafür ist, wie fein die numerische Berechnung durchgeführt wird.

x_{n + 1} = x_n + dt \cdot f(x_n, \ dt \cdot n)

Sie können $ x_n $ nacheinander nach dieser Abschlussformel finden.

Ein einfaches Beispiel ist das Malthus-Modell, eine normale Differentialgleichung, die das Bevölkerungswachstum darstellt.

\frac{dx}{dt} = ax, \quad x(0) = x_0

Dies kann leicht durch manuelle Berechnung als $ x (t) = x_0 e ^ {at} $ gelöst werden. Wenn jedoch eine numerische Berechnung nach der Euler-Methode durchgeführt wird, ist der Anfangswert $ x_0 $ und der folgende Wert nach und nach Denken Sie nur an die Formel.

x_{n + 1} = x_n + dt \cdot (ax_n)

Wenn Sie dies lösen, können Sie danach eine Antwort erhalten, die der tatsächlichen Lösung "irgendwie" nahe zu kommen scheint.

Objektorientierter Begriff

Lassen Sie uns einen kurzen Blick auf die Objektorientierung werfen. Objektorientiert ist grob gesagt die Idee, "Dinge" und "Konzepte" als eine "Klasse" zu sehen. Derjenige, der die Implementierung der erforderlichen Elemente in der "Klasse" anfordert, wird als "Schnittstelle" bezeichnet.

Ich werde es anhand eines allgemeinen Beispiels erklären. Angenommen, Sie möchten einen objektorientierten "Hund" implementieren. Was wir einen "Hund" fragen wollen, ist zum Beispiel das Bellen. Die "Hundeschnittstelle" hat also eine Rindenfunktion.

Selbst wenn Sie sagen, dass Sie beißen, bellt die Art und Weise, wie Sie bellen, je nach Hunderasse. Erstellen wir also eine "Pudelklasse", die die "Hundeschnittstelle" erbt und tatsächlich "bellt", wie man bellt.

Danach möchte ich es als Haustier behalten, also möchte ich jedem Pudel einen Namen geben. Wenn das passiert, geben Sie ihm einfach einen Namen. Einzelne Pudel werden als "Hundeklasseninstanzen" bezeichnet.

Die Implementierung dieser Idee in Java sieht folgendermaßen aus:

Inu.java


interface Dog {
    //Fordern Sie die Implementierung von geerbten Klassen an
    void bark();
}

class Poodle implements Dog {
    String name;
    
    Poodle (String name) {
        this.name = name;
    }

    //Implementieren Sie alle Schnittstellenfunktionen
    void bark () {
        System.out.println("Kyan Kyan");
    }
}

//Ich erstelle eine Instanz von Pudel als Hundetyp.
Dog alto = new Poodle("Alt");
alto.bark(); //Kyan Kyan

Objektorientierte numerische Berechnung

Nehmen wir nun die Euler-Methode des Malthus-Modells als Beispiel und sehen, wie man über das Hauptthema "objektorientierte numerische Berechnung" nachdenkt. Erstens sieht das Bild so aus. Wenn Sie dies nach dem Lesen dieses Abschnitts nicht verstehen, kehren Sie bitte zu der folgenden Abbildung zurück. Screen Shot 2018-12-03 at 01.56.52.png

Gesamtbild

Die gesamte numerische Berechnung ist grob in einen "Berechnungsteil" und einen "Visualisierungsteil" unterteilt. Der "Berechnungsteil" umfasst ferner "Modelleinstellung" und "Schemaeinstellung". In diesem Fall wird das Malthus-Modell beispielsweise mit der Euler-Methode gelöst. Im Folgenden werden wir uns die Details ansehen und die ** Bottom-up-Methode ** erläutern, die schließlich als Ganzes zusammengefasst wird.

Malthus-Modell in normaler Differentialgleichung

Das Malthus-Modell ist ** eines der unzähligen Anfangswertprobleme gewöhnlicher Differentialgleichungen **. Mit anderen Worten, die Malthus-Modellklasse ** ist eine ** Implementierung ** der ** Schnittstelle ** des Anfangswertproblems gewöhnlicher Differentialgleichungen.

Erstellen wir also eine "ODE-Schnittstelle" als große Schnittstelle für das Anfangswertproblem gewöhnlicher Differentialgleichungen. Da gewöhnliche Differentialgleichungen im Allgemeinen mit $ \ frac {dx} {dt} = f (x, t), \ quad x (0) = x_0 $ multipliziert werden, implementiert jede eine "ODE-Schnittstelle" Fordern Sie "Implementierung von $ f $" für das Modell an.

Anschließend wird durch Implementierung der "ODE-Schnittstelle" ein konkretes Malthus-Modell erstellt. Da das Malthus-Modell unter Verwendung des Parameters $ a $, der die Bevölkerungswachstumsrate darstellt, als $ f (x, t) = ax $ definiert ist, sind der Parameter $ a $ und der Anfangswert Variablen.

Unter Berücksichtigung des oben Gesagten ist die Implementierung wie folgt.

public interface ODE {
    public float getInitialVal ();
    public float f (float x, float t);
}


public class Malthus implements ODE {
    //Parameter
    private float a;
    private float initialVal;
  
    public Malthus (float a, float initialVal) {
        this.a = a;
        this.initialVal = initialVal;
    }
    
    //ODE-Schnittstellenanforderung implementiert
    public float getInitialVal () {
        return this.initialVal;
    }

    public float f (float x, float t) {
        return this.a * x;
    }
}

Eulermethode im numerischen Berechnungsschema

Die Euler-Methode ist ** eines von vielen numerischen Berechnungsschemata **. Zum Beispiel gibt es ein anderes numerisches Berechnungsschema für gewöhnliche Differentialgleichungen, wie die Runge-Kutta-Methode. Dieses Mal habe ich unter ihnen die Euler-Methode gewählt. Objektorientiert ist die Euler-Methode ** Klasse ** eine ** Implementierung ** der ** Schnittstelle **, bei der es sich um das numerische Berechnungsschema für gewöhnliche Differentialgleichungen handelt.

Erstellen wir eine "Schema-Schnittstelle" als Schnittstelle für das numerische Berechnungsschema gewöhnlicher Differentialgleichungen. Betrachten Sie hier die Funktionen, die jedes Schema gemeinsam haben möchte. In Anbetracht dessen, dass das numerische Berechnungsschema der gewöhnlichen Differentialgleichung darin besteht, eine allmähliche Gleichung zu konstruieren, können die folgenden zwei Anforderungen berücksichtigt werden.

--calcNextX (ODE-Modell, jetzt $ x $, jetzt $ t $)

Danach können Sie eine "Euler-Methodenklasse" erstellen, die die "Schema-Schnittstelle" implementiert. Mit $ dt $ als Zeitdifferenz kann calcNextX einen allmählichen Ausdruck implementieren, der durch $ x_ {n + 1} = x_n + dt \ cdot f (x_n, n \ cdot dt) $ dargestellt wird.

Lassen Sie uns das oben genannte programmieren.

public interface Scheme {
    public float calcNextX(ODE sys, float currentX, float currentT);
    public float[] getSolution (ODE sys);
}

public class Euler implements Scheme {
    private float dt;
    private int timeStep;
  
    public Euler (float dt, int timeStep) {
        this.dt = dt;
        this.timeStep = timeStep;
    }
    
    public float calcNextX(ODE sys, float currentX, float currentT) {
        return currentX + sys.f(currentX, currentT) * this.dt;
    }
  
    public float[] getSolution(ODE sys) {
        float[] xs = new float[this.timeStep];
        xs[0] = sys.getInitialVal();
    
        for (int i = 1; i < this.timeStep ; i++) {
             xs[i] = calcNextX(sys, xs[i - 1], (float)i * this.dt);
        }
    
        return xs;
    }
}

ODE und Schema integrieren

Haben Sie bemerkt, dass Sie die "ODE-Schnittstelle" und die "Schema-Schnittstelle" unabhängig voneinander erstellt haben? In "Scheme" gab es eine Funktion, die "ODE" als Argument verwendete, aber es ist mir egal, wie "ODE" implementiert wird.

Um "ODE" und "Scheme" gemeinsam zu behandeln, muss ein großes Framework namens "numerische Berechnungsklasse" als weitere Ebene darüber erstellt werden. Es ist eine Klasse, die eine Rolle wie ein Administrator hat.

Der Inhalt ist extrem einfach, Sie müssen nur eine "ODE" und ein "Schema" haben und dann eine ungefähre Lösung erhalten, die unter Verwendung der angegebenen "ODE" und "Schema" erhalten wird.

Die Implementierung ist wie folgt.

public class NumericalAnalysis {
  private ODE sys;
  private Scheme scheme;
  private float[] solution;
  
  public NumericalAnalysis (ODE sys, Scheme scheme) {
    this.sys = sys;
    this.scheme = scheme;
    this.solution = this.getResult();
  }
  
  private float[] getResult() {
    return this.scheme.getSolution(this.sys);
  }
}

Visualisierung

Wie auch immer, ich möchte das Ergebnis der numerischen Berechnung visualisieren. Da es sich um einen Schreibstil handelt, der der Verarbeitung von hier aus eigen ist, wird eine ausführliche Erklärung weggelassen, aber nur der objektorientierte Teil wird erklärt.

Da Visualisierung auch ein Konzept ist, erstellen Sie eine "Grafikklasse". Dies ist eine Klasse, die die durch numerische Berechnung erhaltenen Ergebnisse interpretiert und zeichnet.

Hierbei ist zu beachten, dass vom Standpunkt der "Grafikklasse" ** nur das angegebene Float-Array gezeichnet wird, nicht das numerische Berechnungsergebnis. ** Bei der Objektorientierung sollten der numerische Berechnungsteil und der Visualisierungsteil vollständig getrennt sein.

Wie man vorerst eine ziemlich schlechte Visualisierungsklasse setzt. Sie müssen lediglich die Achse und die Flugbahn der Näherungslösung zeichnen. Wenn Sie eine Grafikbibliothek verwenden können, sollten Sie sie unbedingt verwenden.

public class Graphic {
    public void drawAxis () {
       background(255);
       stroke(0);
       // x-axis
       line(30, 30, 30, 470);
       // t-axis
       line(30, 470, 470, 470);
    }
  
    public void drawGraph (float[] solution) {
        drawAxis();
    
        float maxVal = max(solution);
        float minVal = min(solution);
        float dt = 440 / (float)solution.length;
    
        for (int i = 0; i < solution.length - 1; i++) {
            float now = map(solution[i], minVal, maxVal, 465, 35);
            float next = map(solution[i + 1], minVal, maxVal, 465, 35);
            line(dt * i + 30, now, dt * (i + 1) + 30, next);
        }
    }
}

Integration von numerischer Berechnung und Visualisierung

Die "numerische Berechnungsklasse" war eine Klasse, die die numerischen Berechnungsergebnisse unter Verwendung der angegebenen "ODE" und "Schema" speicherte. Die "Visualisierungsklasse" war auch wie das Zeichnen einer Reihe von Floats. Diese müssen integriert und eine Klasse erstellt werden, die das von der "numerischen Berechnungsklasse" erhaltene numerische Berechnungsergebnis an die "Visualisierungsklasse" übergibt.

Die "Gesamtklasse" spielt diese Rolle. Der Inhalt ist einfach und verbindet die "numerische Berechnungsklasse" und die "Visualisierungsklasse", sodass der Prozess von der numerischen Berechnung bis zur Visualisierung durch Aufrufen nur einer Funktion (doAll-Funktion) durchgeführt werden kann.

public class Overall {
    private NumericalAnalysis na;
    private Graphic g;
  
    public Overall (ODE sys, Scheme scheme) {
        this.na = new NumericalAnalysis (sys, scheme);
        this.g = new Graphic();
    }
  
    public void doAll () {
        g.drawGraph(this.na.getResult());
    }
}

Lauf

Jetzt, da es fertig ist, müssen Sie es nur noch ausführen. Wenn Sie eine Instanz des Malthus-Modells und eine Instanz der Euler-Methode auf Overall werfen und mit doAll ausführen, wird das numerische Berechnungsergebnis visualisiert.

void setup () ist eine verarbeitungsspezifische Schreibweise, die Sie sich jedoch als Hauptfunktion vorstellen können.

void setup() {
    //Bildschirmgröße
    size(500, 500);
    
    ODE sys = new Malthus(1.0, 1.0);
    Scheme scheme = new Euler(0.01, 300);
    Overall oa = new Overall (sys, scheme);
  
    oa.doAll();
}

Ausführungsergebnis Screen Shot 2018-12-03 at 04.19.26.png

Es scheint, dass das Ergebnis wie eine Exponentialfunktion richtig erhalten wird. (Bitte verzeihen Sie, dass sich auf der Achse keine Skala oder Beschriftung befindet.)

Verdienst und Fehler

Wie war die objektorientierte numerische Berechnung? Die Behauptung, dass es einfacher wäre, ohne Objektorientierung zu schreiben, ist richtig. Es kann etwas kompliziert aussehen, da das Programm aus Abstraktion und Abstraktion besteht.

Durch das Erstellen einer Schnittstelle, die ODE und Schema auf diese Weise abstrahiert, sollte es jedoch einfacher sein, ein anderes Modell oder Schema zu erstellen, das nicht das Malthus-Modell oder die Euler-Methode ist. Wenn Sie beispielsweise die Runge-Kutta-Methode 4. Ordnung anstelle der Euler-Methode implementieren möchten, können Sie sie bereits visualisieren, indem Sie die "Runge-Kutta-Klasse" über die "Scheme-Schnittstelle" implementieren und eine Instanz davon erstellen ** Es wird enden. Mit anderen Worten, ** Sie können ein anderes Programm schreiben, indem Sie nur einen Teil ersetzen.

Sich entwickelnde Themen

Andere als ODE (PDE usw.)

Dieses Mal habe ich mich auf das Anfangswertproblem gewöhnlicher Differentialgleichungen und ihre numerischen Berechnungsschemata konzentriert, aber es gibt andere partielle Differentialgleichungen und verschiedene numerische Berechnungsmethoden, die sie begleiten. Die Verallgemeinerung ist jedoch nicht so schwierig, da ein beträchtlicher Rahmen durch denselben Rahmen wie diesmal erklärt werden kann.

Schema nach Singleton

In diesem Artikel habe ich das Schema als gewöhnliche Klasse geschrieben. Dies bedeutet, dass Sie in einem Programm mehrere Instanzen desselben Schemas erstellen können. Das ist schlecht. Das Schema ist nur eine Methode, daher sollte es nicht mehr als eine derselben geben.

Es gibt eine Idee namens Singleton, um sie zu lösen. Einfach ausgedrückt ist es eine Idee sicherzustellen, dass nur eine Instanz dieser Klasse in Ihrem Programm erstellt wird. Weitere Informationen finden Sie unter [Entwurfsmuster (Link zu Wikipedia)](https://ja.wikipedia.org/wiki/%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3% E3% 83% 91% E3% 82% BF% E3% 83% BC% E3% 83% B3_ (% E3% 82% BD% E3% 83% 95% E3% 83% 88% E3% 82% A6% E3 Siehe% 82% A7% E3% 82% A2)).

Es sollte Singleton als Beispiel sein, und wenn Sie sich das verknüpfte Entwurfsmuster ansehen, können Sie sehen, welches Entwurfsmuster im Programm dieses Artikels verwendet wird und welches verwendet werden sollte. ..

MVC (Modell / Ansicht / Controller)

Hat sich jemand gefragt, warum es in eine "numerische Berechnungsklasse" und eine "Visualisierungsklasse" unterteilt wurde? Im Abschnitt Visualisierung habe ich betont, dass die "Visualisierungsklasse" nur das Float-Array übersetzt und zeichnet, nicht die numerischen Berechnungsergebnisse.

Hinter diesem Wort steht das Konzept von MVC. MVC ist eine Abkürzung für Model View Controller. Dieses Konzept ist leicht zu verstehen, wenn Sie ein Spiel als Beispiel nehmen. Modell ist die Hardware und Software des Spiels, Ansicht ist der Bildschirm des Spiels und Controller ist der Controller. Diese sind durch Menschen und Signale miteinander verbunden. Basierend auf den auf dem Bildschirm angezeigten Informationen werden diese in die Steuerung eingegeben, die Hardware interpretiert die Eingabeinformationen und das Ergebnis wird an den Bildschirm übergeben. Sie teilen nicht alle Informationen auf derselben Ebene, sondern übersetzen und tauschen Informationen aus. Obwohl es sich um einen Lieferzyklus handelt, ist es das Äußere wie Menschen und Kabel, die sie verbinden.

In diesem numerischen Berechnungsprogramm gab es keinen Controller, aber die numerische Berechnungsklasse war Modell und die Visualisierungsklasse war Ansicht. Daher sagte er, dass die Visualisierungsklasse nur ein Float-Array zeichnet.

Polymorphismus

Polymorphismus wird im Japanischen als Polymorphismus übersetzt. Dieses Mal haben wir das Schema als Schnittstelle vorbereitet und ein Framework vorbereitet, in dem die Euler-Methode und die Runge-Kutta-Methode implementiert sind. Darin habe ich eine Euler-Methodeninstanz wie folgt erstellt.

Scheme euler = new Euler(0.01, 1.0);

Auf diese Weise war es möglich, eine Variable zu erstellen, die das Verhalten der Euler-Methode aufweist, obwohl es sich um einen Schematyp handelt. Auf diese Weise wird eine Idee, die in der Realität unter einem abstrakten Typ als konkreter Typ angesehen werden kann, als Polymorphismus bezeichnet. Polymorphismus hat die Aufgabe, die Fälle von Programmen zu einem zu kombinieren.

Verkapselung

Um es einfach auszudrücken: Kapselung ist Black Boxing. Von außen ist es ein Konzept, mit dem Sie die Klasse verwenden können, ohne sich über die komplizierte Implementierungsmethode im Inneren Gedanken machen zu müssen. Diesmal wurde beispielsweise mit der Overall-Klasse, die alles zusammenfügt, alles mit nur einer doAll-Funktion ausgeführt. Der Benutzer kann verschiedene Parameter des Malthus-Modells und der Euler-Methode sowie von doAll festlegen, um das Ergebnis zu erhalten, ohne den Inhalt zu kennen.

Zusammenfassung

Ich habe ein Programm erstellt, das die numerische Berechnung gewöhnlicher Differentialgleichungen unter Verwendung der Objektorientierung durchführt. Mit diesem Programm habe ich verstanden, was Objektorientierung ist.

Recommended Posts

Objektorientierte numerische Berechnung
Bedingte numerische Berechnung
Ist WebAssembly wirklich schnell? [Numerische Berechnung]
Objektorientierte Zusammenfassung
Objekt orientierte Programmierung
Ruby / Rust-Kooperation (3) Numerische Berechnung mit FFI
[Java] Objektorientiert
Ruby / Rust-Verknüpfung (4) Numerische Berechnung mit Rutie
Ruby / Rust-Kooperation (5) Numerische Berechnung mit Rutie ② Veggie