[JAVA] Calcul numérique orienté objet

introduction

Cet article est l'article du troisième jour du Calendrier de l'Avent de l'Université Meiji 2018. Ceci est le deuxième message du troisième jour. Opération de vélo Calendrier de l'Avent. Je vous remercie.

Cible

** (Note 1) Le langage utilisé est Processing (presque Java). La différence avec Java est que la surface de dessin est importante. ** ** ** (Attention 2) Il n'y a pas de vitesse de calcul plus rapide car elle est implémentée de manière orientée objet. Il existe également une section qui décrit les avantages et les inconvénients, alors reportez-vous à cela. ** **

Structure de cet article

--Introduction de la méthode de calcul numérique (méthode Euler): --Si vous connaissez la méthode Euler, sautez-la et ok

Introduction de la méthode de calcul numérique (méthode Euler)

Présentation de la méthode Euler. La méthode Euler est l'un des schémas (méthodes de calcul numérique). Tout d'abord, je vais l'introduire sous une forme générale, puis je présenterai ce qui se passe s'il s'agit d'un problème spécifique. Supposons que le problème de valeur initiale suivant d'une équation différentielle normale soit donné.

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

Si vous pouvez le résoudre à la main sous la forme de $ f $, vous pouvez le faire, mais si vous ne pouvez pas le résoudre, il est rapide de s'appuyer sur le calcul numérique. La méthode Euler équivaut à discriminer l'équation de division debout ci-dessus et à créer et résoudre l'équation graduelle suivante. Notez que $ dt $ est la largeur de la différence de temps, qui est un paramètre de la finesse du calcul numérique.

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

Vous pouvez trouver $ x_n $ séquentiellement par cette formule de graduation.

Un exemple simple est le modèle de Malthus, une équation différentielle normale qui représente la croissance démographique.

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

Cela peut être facilement résolu par un calcul manuel comme $ x (t) = x_0 e ^ {at} $, mais lors de l'exécution d'un calcul numérique par la méthode Euler, la valeur initiale est $ x_0 $ et la suivante progressivement Pensez simplement à la formule.

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

Après cela, si vous résolvez cela, vous pouvez obtenir une réponse qui semble être "en quelque sorte" proche de la vraie solution.

Terme orienté objet

Jetons un coup d'œil à l'orientation des objets. En gros, orienté objet est l'idée de voir les «choses» et les «concepts» comme une «classe». Celui qui demande l'implémentation des éléments nécessaires dans la «classe» est appelé «interface».

Je vais vous expliquer en utilisant un exemple courant. Supposons que vous souhaitiez implémenter un "chien" orienté objet. Ce que nous voulons demander à un «chien», c'est, par exemple, aboyer. Ainsi, "l'interface chien" a une fonction d'aboiement.

Cependant, même si vous dites que vous aboyez lors d'une morsure, la façon dont vous aboyez diffère selon la race de chien. Alors, créons une "classe Poodle" qui hérite de "l'interface chien" et "implémentons" comment aboyer.

Après cela, je veux le garder comme animal de compagnie, donc je veux donner un nom à chaque caniche. Si cela se produit, donnez-lui simplement un nom. Les caniches individuels sont appelés «instances de classe de chien».

La mise en œuvre de cette idée en Java ressemble à ceci:

Inu.java


interface Dog {
    //Demander une implémentation à partir des classes héritées
    void bark();
}

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

    //Implémenter toutes les fonctions d'interface
    void bark () {
        System.out.println("Kyan Kyan");
    }
}

//Je crée une instance de Poodle en tant que type de chien.
Dog alto = new Poodle("Alto");
alto.bark(); //Kyan Kyan

Calcul numérique orienté objet

Prenons maintenant la méthode d'Euler du modèle de Malthus comme exemple et voyons comment penser le sujet principal, le "calcul numérique orienté objet". Tout d'abord, l'image ressemble à ceci. Si vous ne comprenez pas après avoir lu cette section, veuillez revenir à la figure ci-dessous selon le cas. Screen Shot 2018-12-03 at 01.56.52.png

Vue d'ensemble

L'ensemble du calcul numérique est grossièrement divisé en une "partie calcul" et une "partie visualisation". La "partie de calcul" comprend en outre "l'établissement du modèle" et le "réglage du schéma". Par exemple, dans ce cas, le modèle de Malthus est résolu par la méthode Euler. Ci-dessous, nous examinerons les détails et expliquerons en utilisant la ** méthode ascendante **, qui sera éventuellement résumée dans son ensemble.

Modèle de Malthus en équation différentielle normale

Le modèle de Malthus est ** l'un des innombrables problèmes de valeur initiale des équations différentielles ordinaires **. En d'autres termes, le modèle de Malthus ** classe ** est une ** implémentation ** de ** l'interface ** du problème de valeur initiale des équations différentielles ordinaires.

Alors, créons une "interface ODE" comme une grande interface pour le problème de la valeur initiale des équations différentielles ordinaires. Puisque les équations différentielles ordinaires sont généralement multipliées par $ \ frac {dx} {dt} = f (x, t), \ quad x (0) = x_0 $, chacune qui implémente une "interface ODE" Demande "implémentation de $ f $" pour le modèle.

Ensuite, un modèle de Malthus concret est créé en implémentant "l'interface ODE". Puisque le modèle de Malthus est défini comme $ f (x, t) = ax $ en utilisant le paramètre $ a $ qui représente le taux de croissance de la population, le paramètre $ a $ et la valeur initiale sont des variables.

Compte tenu de ce qui précède, la mise en œuvre est la suivante.

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


public class Malthus implements ODE {
    //Paramètres
    private float a;
    private float initialVal;
  
    public Malthus (float a, float initialVal) {
        this.a = a;
        this.initialVal = initialVal;
    }
    
    //Requête d'interface ODE implémentée
    public float getInitialVal () {
        return this.initialVal;
    }

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

Méthode d'Euler dans le schéma de calcul numérique

La méthode Euler est ** l'un des nombreux schémas de calcul numérique **. Par exemple, il existe un autre schéma de calcul numérique pour les équations différentielles ordinaires, comme la méthode Runge-Kutta. Cette fois, j'ai choisi la méthode Euler parmi eux. En termes orientés objet, la méthode Euler ** class ** est une ** implémentation ** de ** l'interface **, qui est le schéma de calcul numérique des équations différentielles ordinaires.

Créons une "interface de schéma" comme interface pour le schéma de calcul numérique des équations différentielles ordinaires. Ici, considérez les fonctions que chaque schéma veut avoir en commun. Étant donné que le schéma de calcul numérique de l'équation différentielle ordinaire consiste à construire une équation graduelle, les deux conditions suivantes peuvent être prises en compte.

--calcNextX (modèle ODE, maintenant $ x $, maintenant $ t $) --Une fonction qui renvoie la valeur de $ x $ à la prochaine fois $ t + dt $ à partir du modèle donné et les $ x $ et $ t $ actuels. --getSolution (modèle ODE) --Une fonction qui trouve une solution approximative à tous les instants discrets en utilisant calcNextX séquentiellement

Après cela, vous pouvez créer une "classe de méthode Euler" qui implémente l '"interface Scheme". Avec $ dt $ comme décalage horaire, calcNextX peut implémenter une expression graduelle représentée par $ x_ {n + 1} = x_n + dt \ cdot f (x_n, n \ cdot dt) $.

Programmons en fait ce qui précède.

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;
    }
}

Intégrez ODE et Scheme

Avez-vous remarqué que vous avez créé indépendamment «l'interface ODE» et «l'interface Scheme»? Dans "Scheme", il y avait une fonction qui prenait "ODE" comme argument, mais je me fiche de la manière dont "ODE" est implémenté.

Afin de gérer collectivement "ODE" et "Scheme", il est nécessaire de créer un grand cadre appelé "classe de calcul numérique" comme une autre couche ci-dessus. C'est une classe qui a un rôle d'administrateur.

Le contenu est extrêmement simple, il suffit d'avoir un "ODE" et un "Scheme", puis d'avoir une solution approximative obtenue en utilisant les "ODE" et "Scheme" donnés.

La mise en œuvre est la suivante.

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);
  }
}

Visualisation

Quoi qu'il en soit, je veux visualiser le résultat du calcul numérique. Puisqu'il s'agit d'un style d'écriture particulier à Processing d'ici, une explication détaillée est omise, mais seule la partie orientée objet sera expliquée.

La visualisation étant également un concept, créez une "classe graphique". C'est une classe qui interprète les résultats obtenus par calcul numérique et dessine.

Il convient de noter ici que, du point de vue de la "classe graphique", ** elle ne dessine que le tableau flottant donné, pas le résultat du calcul numérique. ** En orientation objet, la partie calcul numérique et la partie visualisation doivent être complètement séparées.

Comment mettre une classe de visualisation assez pauvre pour le moment. Tout ce que vous avez à faire est de dessiner l'axe et de tracer la trajectoire de la solution approchée. Si vous pouvez utiliser une bibliothèque graphique, vous devez absolument l'utiliser.

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);
        }
    }
}

Intégration du calcul numérique et de la visualisation

La "classe de calcul numérique" était une classe qui utilisait les "ODE" et "Scheme" donnés pour enregistrer les résultats des calculs numériques. De plus, la "classe de visualisation" était comme dessiner un tableau de flottants. Il est nécessaire de les intégrer et de créer une classe qui transmet le résultat du calcul numérique obtenu par la "classe de calcul numérique" à la "classe de visualisation".

La «classe générale» joue ce rôle. Le contenu est simple et relie la "classe de calcul numérique" et la "classe de visualisation" de sorte que le processus du calcul numérique à la visualisation puisse être effectué en appelant simplement une fonction (fonction doAll).

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());
    }
}

Courir

Maintenant qu'il est terminé, il ne vous reste plus qu'à l'exécuter. Si vous lancez une instance du modèle Malthus et une instance de la méthode Euler vers Overall et que vous l'exécutez avec doAll, le résultat du calcul numérique sera visualisé.

void setup () est une manière d'écrire spécifique au traitement, mais vous pouvez la considérer comme une fonction principale.

void setup() {
    //Taille de l'écran
    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();
}

Résultat d'exécution Screen Shot 2018-12-03 at 04.19.26.png

Il semble que le résultat comme une fonction exponentielle soit obtenu correctement. (Veuillez pardonner qu'il n'y a pas d'échelle ou d'étiquette sur l'axe.)

mérite et démérite

Comment était le calcul numérique orienté objet? L'affirmation selon laquelle il serait plus facile d'écrire sans orientation objet, c'est vrai. Cela peut paraître un peu compliqué car le programme a été construit en superposant l'abstraction à l'abstraction.

Cependant, suite à la création d'une interface qui résume l'ODE et le schéma de cette manière, il devrait être plus facile de créer un autre modèle ou schéma autre que le modèle Malthus ou la méthode Euler. Par exemple, si vous souhaitez implémenter avec la méthode Runge-Kutta du 4ème ordre au lieu de la méthode Euler, vous pouvez déjà la visualiser en implémentant la "classe Runge-Kutta" à partir de "l'interface Scheme" et en en créant une instance ** Cela finira. En d'autres termes, ** vous pouvez écrire un autre programme simplement en remplaçant une partie.

Sujets en évolution

Autre que ODE (PDE, etc.)

Cette fois, je me suis concentré sur le problème de la valeur initiale des équations différentielles ordinaires et de leurs schémas de calcul numérique, mais il existe d'autres équations différentielles partielles et diverses méthodes de calcul numérique qui les accompagnent. Cependant, la généralisation n'est pas si difficile car un cadre considérable peut être expliqué par le même cadre que cette fois.

Scheme to Singleton

Dans cet article, j'ai écrit le schéma comme une classe ordinaire. Cela signifie que vous pouvez créer plusieurs instances du même schéma dans un programme. C'est mauvais. Le schéma est juste une méthode, il ne devrait donc pas y en avoir plus d'une.

Il existe une idée appelée Singleton pour le résoudre. En termes simples, c'est une idée de garantir qu'une seule instance de cette classe est créée dans votre programme. Pour plus d'informations, voir [Design Patterns (lien vers 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 Voir% 82% A7% E3% 82% A2)).

Il devrait s'agir de Singleton à titre d'exemple, et si vous regardez le modèle de conception lié, vous pouvez voir quel modèle de conception est utilisé dans le programme de cet article et lequel doit être utilisé. ..

MVC (modèle / vue / contrôleur)

Quelqu'un s'est-il demandé pourquoi il était divisé en une "classe de calcul numérique" et une "classe de visualisation"? Dans la section visualisation, j'ai souligné que la "classe de visualisation" ne traduit et dessine que le tableau flottant, pas les résultats des calculs numériques.

Derrière ce mot se cache le concept de MVC. MVC est une abréviation de Model View Controller. Ce concept est facile à comprendre si vous prenez un exemple de jeu. Le modèle est le matériel et le logiciel du jeu, la vue est l'écran du jeu et le contrôleur est le contrôleur. Ceux-ci sont connectés les uns aux autres par les humains et les signaux. Sur la base des informations affichées à l'écran, elles sont entrées dans le contrôleur, le matériel interprète les informations d'entrée et le résultat est transmis à l'écran. Ils ne partagent pas tous des informations au même niveau les uns que les autres, mais traduisent et échangent des informations. Bien qu'il s'agisse d'un cycle de livraison, c'est l'extérieur comme les humains et les câbles qui les relient.

Dans ce programme de calcul numérique, il n'y avait pas de contrôleur, mais la classe de calcul numérique était Model et la classe de visualisation était View. Par conséquent, il a dit que la classe de visualisation dessine uniquement un tableau flottant.

Polymorphisme

Le polymorphisme est traduit par polymorphisme en japonais. Cette fois, nous avons préparé Scheme comme interface et préparé un cadre dans lequel la méthode Euler et la méthode Runge-Kutta sont implémentées. Dans celui-ci, j'ai créé une instance de méthode Euler comme suit.

Scheme euler = new Euler(0.01, 1.0);

En faisant cela, il était possible de créer une variable qui a le comportement de la méthode Euler même s'il s'agit d'un type Scheme. De cette manière, une idée qui peut être considérée comme un type concret dans la réalité sous un type abstrait est appelée polymorphisme. Le polymorphisme a pour rôle de combiner les cas de programmes en un seul.

Encapsulation

Pour faire simple, l'encapsulation est une boxe noire. De l'extérieur, c'est un concept qui vous permet d'utiliser la classe sans vous soucier de la méthode d'implémentation compliquée à l'intérieur. Par exemple, cette fois, en utilisant la classe Overall qui rassemble tout, tout a été exécuté avec une seule fonction doAll. L'utilisateur peut définir divers paramètres du modèle Malthus et de la méthode Euler et faire tout pour obtenir le résultat sans en connaître le contenu.

Résumé

J'ai créé un programme qui effectue le calcul numérique d'équations différentielles ordinaires en utilisant l'orientation objet. En utilisant ce programme, j'ai compris ce qu'est l'orientation objet.

Recommended Posts

Calcul numérique orienté objet
Calcul numérique conditionnel
WebAssembly est-il vraiment rapide? [Calcul numérique]
Résumé orienté objet
Programmation orientée objet
Coopération Ruby / Rust (3) Calcul numérique avec FFI
[Java] Orienté objet
Liaison Ruby / Rust (4) Calcul numérique avec Rutie
Coopération Ruby / Rust (5) Calcul numérique avec Rutie ② Veggie