Δημοσιεύτηκε: 05 Δεκ 2010, 16:33
από Dimitris
Metanumerics

Στο παρελθόν είχα αναφερθεί σε κάποιο άλλο νήμα για μεταπρογραμματισμό (metaprogramming) και στο παρόν νήμα μιας και το θέμα είναι αριθμητικές μέθοδοι θα ήθελα να αναφερθώ στον όρο μετααριθμητική (metanumerics). Metanumerics είναι ένας σχετικά νέος κλάδος των εφαρμοσμένων μαθηματικών και της επιστημής των υπολογιστών που ασχολείται, κατ'αναλογία με το μεταπρογραμματισμό, με κώδικα που γράφει κώδικα. Στην περίπτωση αυτή βέβαια έχει να κάνει με κώδικα αριθμητικών μεθόδων. Ένα παράδειγμα metanumerics είχα επίσης παρουσιάσει σε ένα τεύχος του περιοδικού ubuntistas, τεύχος 9, για την αλγοριθμική ή αυτόματη παραγώγιση (algorithmic or automatic differentiation) και το πακέτο ADOL-C. Εδώ θα ήθελα να παρουσιάσω με περισσότερη λεπτομέρεια το θέμα metanumerics.

Ως metanumerics θα μπορούσαμε να ορίσουμε τον κώδικα που γράφει κώδικα αριθμητικών μεθόδων. Λίγο ασαφής ορισμός γι'αυτό ας δούμε ένα παράδειγμα. Έχουμε έναν κώδικα γραμμένο στη γλώσσα maxima το οποίο υπολογίζει το ολοκλήρωμα μιας συνάρτησης.
Κώδικας: Επιλογή όλων
integrate(x/(x^3 + 1), x);

Τίποτε δύσκολο. Ας υποθέσουμε ότι αυτό θέλουμε να το ενσωματώσουμε στον κώδικα C με τον οποίο δουλεύουμε. Η πιο απλή λύση είναι να προγραμματίσουμε τη λύση της ολοκλήρωσης σε μια συνάρτηση C. Το πρόβλημα που δημιουργείται είναι αν αλλάξουμε τη συνάρτηση ολοκλήρωσης στον κώδικα maxima, θα πρέπει να επαναπρογραμματίσουμε τη συνάρτηση C. Φυσικά στο απλό αυτό πρόβλημα θα μπορούσαμε να υλοποιήσουμε εναν αλγόριθμο αριθμητικής ολοκληρωσης κατ'ευθειαν στη C. Αλλα στη γενική περίπτωση θα μπορούσαμε να γράψουμε ένα script (θα παραμείνω στη χρήση του script γιατί είναι πιο εύκολο να γίνει σε μια interpreted γλώσσα) το οποίο θα παίρνει το αποτέλεσμα του maxima και θα δημιουργεί αυτόματα τον κώδικα C που χρειαζόμαστε, θα τον κάνει compile και έπειτα link στο C πρόγραμμά μας.

Το ADOL-C δέχεται ως δεδομένα εισόδου υπάρχωντα κώδικα τον οποίο τον επεξεργάζεται και δημιουργεί νέο κώδικα ο οποίος υπολογίζει την παράγωγο μιας μεταβλητής ως προς κάποια άλλη. Έπειτα ο νέος κώδικας μεταγλωττίζεται και συνδέεται στο πρόγραμμα όπου χρειάζεται η παράγωγος. Συνήθως είναι κάποιος κώδικας βελτιστοποίησης.

Ένα άλλο παράδειγμα προγράμματος που χρησιμοποιεί αυτή την τεχνική είναι το FEniCS. Το FEniCS είναι ένα υπερπακέτο, για python ή C++, το οποίο μπορεί να λύσει εξισώσεις στη variational form (βλ. calculus of variations = λογισμός των μεταβολών, μου λείπει η μετάφραση). Το FEniCS δημιουργεί κατά το runtime C++ κώδικα, ειδικό για το συγκεκριμένο πρόβλημα, ο οποίος μετά μεταγλωττίζεται, δημιουργείται η διεπιφάνεια για την python και τέλος εκτελείται.

Στο σημείο αυτό ίσως κάποιος πει ότι δημιουργείται ένα overhead και κάτι τέτοιο αληθεύει. Αλλά σε μεγάλα προβλήματα η γενικότητα του κώδικα δε συγκρίνεται με τη μικρή μείωση ταχύτητας. Μη ξεχνάμε ότι το κόστος του προγραμματιστή είναι πολύ μεγαλύτερο από το κόστος λειτουργίας του υπολογιστή. Να σημειώσω ότι το FEniCS λειτουργεί και παράλληλα με τη χρήση βιβλιοθηκών MPI.

Περνώντας πάλι στη γενική διατύπωση του ορισμού, μπορούμε να δώσουμε την εξής λύση σε πολλά προβλήματα. Ας υποθέσουμε ότι υπάρχει C/FORTRAN legacy code ο οποίος λύνει μια συγκεκριμένη κατηγορία προβλημάτων. Θα μπορούσε κάποιος να χρησιμοποιήσει μια interpreted γλώσσα προγραμματισμού για να δημιουργήσει case specific κώδικα C/FORTRAN ο οποίος θα μεταγλωττιστεί και θα ενσωματωθεί στο υπάρχων πρόγραμμα. Αυτό κάνουν οι βιβλιοθήκες boost στην python, οι lex/yacc για parsing, και πολλά άλλα εργαλεία.

Φυσικά ο προγραμματισμός αυτών των εργαλείων ίσως να απαιτήσει το χειρισμό strings (το οποίο για μένα είναι το χειρότερό μου) αλλά το τελικό αποτέλεσμα αξίζει τον κόπο.

Αλλάζοντας ελαφρώς θέμα θα ήθελα να αναφερθώ στις domain specific languages (DSL). Σε αντίθεση με τις γενικής χρήσης γλώσσες προγραμματισμού είναι γλώσσες που επιλύουν ένα συγκεκριμένο πρόβλημα και δεν ενδείκνυνται για προγραμματισμό όλων των τύπων προγραμμάτων. Για παράδειγμα το make αυτοματοποιεί τη μεταγλώττιση και σύνδεση των αρχείων ενός προγράμματος, το configure αυτοματοποιεί τη δημιουργεία των Makefiles. Συνήθως στόχος των DSL είναι η δημιουργία μιας γλώσσας με απλό συντακτικό.

Θα μπορούσαμε για παράδειγμα να γράψουμε ένα εργαλείο που θα έχει την εξής απλή σύνταξη:
Κώδικας: Επιλογή όλων
program {
  a -> b -> c;
  a: d -> e;
  b: f -> g;
  c: e -> g;
  f: d-> d;
}

το οποίο θα δημιουργεί τον εξής κώδικα C:
Κώδικας: Επιλογή όλων
/* main.c */
#include "file1.h"
#include "file2.h"
int main(){
a();
b();
c();
}

/* file1.c */
void a(){
d();
e();
}

void b(){
f();
g();
}

void c(){
e();
g();
}

/* file2.h */
void d(){
}

void e(){
}

void f(){
d();
d();
}

void g(){
}


και τα αντίστοιχα header files.

Αυτά για σήμερα. Καλό brainstorming και καλή διασκέδαση!!!