|
Publicité ' | |||||||||||||||||||||||
|
|
#1 | ||||||||||||||||||||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Bonjour,
Je souhaiterais vous présenter un projet que j'ai commencé il y a peu. On pourrait dire qu'il ne s'agit d'une simple calculatrice en ligne de commande : ça ne serait pas faux en soit, juste incomplet. En effet, il s'agit d'un interpréteur extrêmement simple (la classe "State", moins de 900 lignes de code pour le moment, header inclus) qui permet de gérer des opérations entre des symboles. Il est, dans sa conception, relativement générique et permet l'ajout de nouveaux types sans modifier le code de base de l’interpréteur. Ainsi, il est muni par défaut de trois types fondamentaux :
Pour montrer que l'ajout de nouveaux types est possible simplement, j'ai mis également à disposition un type supplémentaire : Vector. Ressemblant beaucoup au std::vector du C++, le type Vector est un conteneur unidimensionnel homogène (il contient N valeurs de types identiques). Il n'y a strictement aucune restriction sur le type de valeur contenue, ainsi il est possible de créer des Vector de Vector, et toute autre combinaison qui vous passe par la tête. Il possède deux opérateurs généraux : l'addition (+) et la soustraction (-), plus la multiplication par un "Scalar" (commutative). Les 3-Vectors (vectors à 3 composantes) disposent également, pour le fun, du produit vectoriel (^). Enfin, on peut utiliser l'opérateur '[]' pour accéder aux composantes du vector, en utilisant soit la notation "canonique" vector[]index, soit la notation C++ vector[index]. La syntaxe de construction d'un vector est la suivante : <elt1, elt2, ...> (l'utilisation des parenthèses étant réservée à leur sens mathématique premier, qui permet de grouper les expressions). L'interpréteur peut fonctionner soit en ligne de commande, soit en lisant les commandes directement à partir d'un fichier. Voici donc une suite d'exemples d'utilisation commentés, qui valent mieux qu'on long discours. Vous trouverez les binaires pour Windows, ainsi que les sources (tout à fait portables), à la fin du message. On commence par créer deux symboles 'a' et 'b' en une seule commande. Le symbole ';' permet, comme en C++, de signaler à l'interpréteur la fin d'une instruction. Code :
Code :
Code :
Code :
Code :
Tout est ok ! On peut alors commencer sereinement. On affecte d'abord quelques valeurs numériques : Code :
Il est possible de rappeler le dernier résultat avec la commande '_' : Code :
Code :
Code :
Code :
Si vous avez envie de crier à l'arnaque, vous en avez le droit : pour le moment, pas de calcul formel au menu... Mais c'est bien entendu sur la ToDo list ! Le but principal de ce programme n'est pas de concurrencer Mathematica/Mapple/etc, mais de proposer une solution gratuite, open source et légère permettant de faire de l'algèbre linéaire de base (pas de matrices pour le moment, mais rien ne m'en empêche) et du calcul tensoriel formel (et accessoirement, numérique). Comme vous pouvez le voir sur mon profil, je suis étudiant en physique (Master 2 actuellement), et il m'arrive bien souvent d'avoir à calculer des expressions tensorielles relativement pénibles, dont j'aimerais vérifier le résultat rapidement par ordinateur. C'est de là qu'est partie l'envie de programmer ce petit bout de programme (plus ma passion un peu malsaine de ré-inventage de roue). Voici donc comme promis les binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
Pour compiler, vous devrez d'abord compiler la bibliothèque "Utils" qui se trouve dans le sous dossier du même nom. Elle n'a aucune dépendance ni configuration, et est donc très facile à compiler (les projets Code::Block sont fournis et prêts à l'emploi, d'ailleurs). La documentation Doxygen de cette bibliothèque est fournie ici : [7z] (262 Ko), [zip] (1329 Ko). |
||||||||||||||||||||
|
|
10
|
|
|
#2 | ||||||||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Le programme peut maintenant faire se propager les références :
Code :
La prochaine étape pour le calcul formel sera donc de proposer des fonctions pour modifier le contenu d'une référence (développement, factorisation, etc). Dans cette optique, la version 002 apporte également le support des fonctions, qui peuvent avoir un nombre arbitraire d'arguments. La recherche se fait d'abord par le nom de la fonction, puis si celle-ci existe, on évalue les arguments un par uns pour voir si un prototype correspond aux arguments fournis : Code :
Code :
Ainsi on aura par exemple : Code c++ :
On voit alors que celles-ci doivent avoir le prototype suivant : Code :
s_bool (State& s, const s_ctnr<s_refptr<Symbol>>& args, s_ctnr<s_refptr<Symbol>>& res, s_str& e) L'emploi de 'static_cast' n'est pas problématique, car la classe State s'assure déjà en amont que les types sont corrects. Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources : |
||||||||
|
|
10
|
|
|
#3 | ||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Voici la version 003, qui contient quelques ajouts mineurs :
Petits exemples de code : Code :
Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources : |
||
|
|
00
|
|
|
#4 | ||||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Et voilà tranquillement la version 004 qui arrive :
Et maintenant quelques exemples pour étayer le tout. On a l'algèbre de base des nombres complexes : Code :
Les fonctions usuelles ont été étendues sur l'ensemble des complexes (quand c'est possible) : Code :
Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources : |
||||
|
|
00
|
|
|
#5 |
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Après pas mal d'inactivité sur ce projet, j'ai décidé de le reprendre à zéro sur de meilleures bases. Le précédent code était relativement stable (je l'utilise depuis le début comme calculatrice sur tout mes PCs !), mais j'avais du mal à le faire évoluer, tant le parser était fouillis et la conception parfois bancale.
Je vous présente donc la toute neuve v005, avec au menu (en vrac) :
Je pense avoir fait le tour ! La compilation est un peu plus ardue que précédemment, vu qu'il faut avoir boost d'installé (j'utilise la version 1.50), ainsi que MPFR (mpfr::real est header only et fourni avec mes sources) qui lui-même dépend de GMP. Autant boost::spirit est header only (rien à compiler donc), autant ce n'est pas le cas de GMP et MPFR qui sont des bibliothèques C. Ceci dit, elles compilent très facilement sous Windows avec MSYS et MinGW (./configure, make, un café, et c'est plié). Sous linux, je pense qu'il existe des paquets pré-compilés pour votre distribution (il faut au minimum MPFR 3). La bibliothèque "Utils" a quant à elle disparu. Ce qu'il reste à faire :
J'ai un peu laissé tombé le côté tenseur pour le moment. À voir si ça reviendra au devant de la scène plus tard Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources :
__________________
Mes programmes : éditeur de sous-titres, générateur de code C++, calcul formel en ligne de commande, wrapper C++ pour Lua, bibliothèque de GUI, utilitaire pour la physique en C++11. |
|
|
00
|
|
|
#6 |
|
Membre chevronné
![]() ![]() Yann CaronIngénieur développement logiciels Inscription : mai 2008 Messages : 377 ![]() |
Rhaaa les nombres complexes et tout ça, je ne suis pas allez assez loin dans mes études générales pour connaitre.
En revanche, les parseurs, tout ça, je suis un peu plus à l'aise. Concernant ton problème d'optimisation (1+2) a vs (a+1) +2 tu devrais utiliser les AST (Abstract Syntax Tree) Le principe est le suivant, tu pré-compile ton source dans un arbre. Ensuite pour résoudre ton arbre, il te suffit de faire un parcour en profondeur. Regarde du coté du design pattern Interpreter du GoF. Sinon un très bon bouquin sur le parsing et la compilation : http://pragprog.com/book/tpdsl/langu...ation-patterns Autre petit point, j'ai cru comprendre en lisant tes exemples que ton programme ne respecte pas la précédence des opérateurs. http://en.wikipedia.org/wiki/Order_of_operations (cf partie "programming languages") Ce qui sous entend que 1 + 2 * 3 = 1 + (2 * 3) <> (1 + 2) * 3 La précédende insique que le signe * est traité "avant" le signe +, et du coup respecte l'ecriture arithmétique que nous connaissons. Mais je peu me tromper, tu l'a peu-t-être fait. Derniere chose, c'est dommage de ne pas avoir écrit ton parseur toi même. C'est un domaine vraiment très intéréssant. Mais ça demande ennormément de temps. En tous cas bonne continuation.
__________________
Mon petit projet en cours, Algoid sur developpez.net, 100% naturel java, sans conservateur et sans parabène. Mon site vouèbeu Orange Head by CyaNn, avec 45% de matière 3D dedans. |
|
|
00
|
|
|
#7 | |||||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Citation:
Ca sert beaucoup en physique théorique et en math, mais aussi en optique, en électronique, et plus généralement tous les domaines où l'on retrouve des phénomènes périodiques (c'est donc vaste)... Dans la vie de tout le jours, c'est complètement inutile Citation:
Ce qui me manque, c'est l'algorithme de simplification, et aussi que je rende mon code conscient du fait que tel opérateur est associatif ou non (i.e. que (1+2)+3 == 1+(2+3), et ce pour l'addition et la multiplication seulement ! Ce n'est pas vrai pour la soustraction, la division ou le modulo par exemple). Citation:
Code c++ :
Ceci dit, depuis le début (donc dans les deux codes), si deux opérateurs de précédence égale sont situés l'un à côté de l'autre, l'évaluation se fait de gauche à droite. C'est peut être ça qui t'a induit en erreur ? En rédigeant ce message (en vérifiant que mon programme donnait bien i*i == -1 Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources :
__________________
Mes programmes : éditeur de sous-titres, générateur de code C++, calcul formel en ligne de commande, wrapper C++ pour Lua, bibliothèque de GUI, utilitaire pour la physique en C++11. |
|||||
|
|
00
|
|
|
#8 | |||||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Citation:
Cependant, il faut partir sur de bonnes bases (ce qui n'était pas mon cas) et être rigoureux sur toute la ligne (là encore, ce n'était pas vraiment mon cas non plus) si on veut avoir un code clair et maintenable. En utilisant boost::spirit ici, je n'ai besoin que de 240 lignes pour avoir un parseur fonctionnel et très simple à faire évoluer, à comparer aux 800 lignes de mon code perso. Sinon, le problème de simplification des parenthèses inutiles est réglé. Il m'a suffit d'attribuer une priorité à chaque symbole, de sorte que les littéraux, fonctions, variables et vecteurs soient de priorité 0 (la plus élevée), les opérateurs unaires "-" et "+" aient respectivement une priorité de 1 et 2, et les opérateurs binaires "%", "/", "*", "-", "+" aient respectivement une priorité de 3, 4, 5, 6 et 7 (en gros). Alors, lorsque je sérialise une expression, je regarde si la priorité de l'opérateur courant est inférieure à celle des symboles sur lesquels il agit : si c'est le cas, on enveloppe ces symboles dans des parenthèses, sinon c'est inutile. On a donc : Code :
Code :
Il me faut en plus faire un choix : attribuer les propriétés d'associativité et de commutativité aux opérateurs seuls, ou également prendre en compte le type des opérandes... (i.e. : "*" peut être commutatif pour les scalaires et complexes, mais pas pour les matrices par exemple). Le premier cas simplifie le processus de simplification (hum...) car on a pas besoin de connaître le type des variables qui entrent en jeu dans l'expression. En revanche, si je veux introduire la multiplication matricielle par la suite, je devrai introduire un nouvel opérateur, par exemple "**", spécialement dédié à cette fonction et qui lui ne serait pas commutatif. En soit ce n'est pas trop grave, mais ça peut être déroutant pour l'utilisateur qui serait habitué à utiliser "*". Le second cas est plus pratique pour l'utilisateur a priori, mais complexifie la simplification (hum... bis). Pour pouvoir simplifier une expression comme celle donnée en exemple plus haut, il faudra donner des informations supplémentaires au programme (comme par exemple dire "simplifier cette expression en considérant que la variable 'a' est un scalaire" : le programme peut alors utiliser la commutativité et tout roule). Il me faut y réfléchir. Quoi qu'il en soit, voici la dernière version (j'en ai profité pour nettoyer le code et régler un bug avec les opérateurs unaires n'agissant pas sur des littéraux) : Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources :
__________________
Mes programmes : éditeur de sous-titres, générateur de code C++, calcul formel en ligne de commande, wrapper C++ pour Lua, bibliothèque de GUI, utilitaire pour la physique en C++11. |
|||||
|
|
00
|
|
|
#9 | ||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Décidément, ça bouillonne...
J'ai repéré un bug avec mon code de simplification des parenthèses : Clairement ces parenthèses là sont inutiles. C'est maintenant de l'histoire ancienne : j'ai pris en compte que certains opérateurs peuvent avoir la même précédence (par exemple ici "+" et "-"), et que si l'on se retrouve avec une expression faisant intervenir deux opérateurs qui sont dans ce cas là (comme l'exemple ci-dessus) seules les parenthèses à droite peuvent être utiles. J'avais aussi oublié l'opérateur "^" (puissance) dans ma liste de priorité, je l'ai rajouté. Sinon, j'ai introduis un nouveau type d'affectation, ":=", qui fonctionne comme "=" mais qui n'évalue pas l'expression à droite. Enfin, j'ai implémenté un détecteur de dépendance circulaire : Code :
Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].
PS : j'ai pu tester la compilation sous Ubuntu 12.04. Avec les paquets libboost-dev (1.48), libgmp-dev (5.0.2) et libmpfr-dev (3.1.0) fournis dans les dépôts par défaut, ça passe sans encombre. Bien sûr, il faut avoir gcc 4.7 (disponible dans ce PPA, ou dans la prochaine version 12.10).
__________________
Mes programmes : éditeur de sous-titres, générateur de code C++, calcul formel en ligne de commande, wrapper C++ pour Lua, bibliothèque de GUI, utilitaire pour la physique en C++11. |
||
|
|
00
|
|
|
#10 | ||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
À chaque jour sa version...
Au menu d'aujourd'hui :
Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources :
__________________
Mes programmes : éditeur de sous-titres, générateur de code C++, calcul formel en ligne de commande, wrapper C++ pour Lua, bibliothèque de GUI, utilitaire pour la physique en C++11. |
||
|
|
00
|
|
|
#11 | ||||||||||||||||||||||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Voici une nouvelle version bien chargée...
Voilà pour les petits changements. J'ai également implémenté quelque chose d'un peu plus conséquent : les chaînes de caractères (ou string) :
Le tout tient en 3000 lignes de code (commentaires et espaces compris), je trouve ça assez hallucinant... Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources :
__________________
Mes programmes : éditeur de sous-titres, générateur de code C++, calcul formel en ligne de commande, wrapper C++ pour Lua, bibliothèque de GUI, utilitaire pour la physique en C++11. |
||||||||||||||||||||||
|
|
00
|
|
|
#12 | ||||||||
|
Membre éprouvé
![]() ![]() Doctorant en astrophysique Inscription : juin 2007 Messages : 314 ![]() |
Voici la version 0.11, avec au menu de gros changements au niveau de la gestion des vecteurs. En effet, les vecteurs ((1, 5, 13, ...)) ne peuvent maintenant plus être hétérogènes, car ils sont ils sont maintenant implémentés comme des vecteurs C++ (ou comme des tableaux en C), c'est à dire que les différentes valeurs sont arrangées de manières continue en mémoire. Cela permet d'avoir de bien meilleures performances (également parce qu'on ne doit vérifier le type du contenu qu'une seule fois, au lieu de le faire pour chaque valeur comme avant).
J'ai du coup introduit une nouvelle notation avec des accolades ({"abc", 1, 12i, (5,6,7)}) qui permet de construire une "collection". Comme vous pouvez le voir, les collections peuvent vraiment contenir n'importe quoi, contrairement aux vecteurs. Elles sont moins performantes (il vaut mieux utiliser un vecteur quand c'est possible), mais peuvent aussi être bien pratiques pour faire transiter des données facilement. Ces deux types de conteneurs coexistent au sein du programme, et sont utilisés dans des situations différentes. En particulier, toutes les fonctions mathématiques (cos, etc.) sont surchargées pour prendre un vecteur de scalaire ou complexe en argument. Également, une bonne partie des fonctions de gestion des chaines de caractères (trim, etc.) sont aussi surchargées pour prendre un vecteur de chaine de caractères en argument. Mais il n'y a pas de règle automatique : pour qu'une fonction puisse être appelée sur un vecteur, il faut qu'une surcharge explicite existe. Ce n'est pas le cas des collections : si le premier argument d'une fonction est une collection (et qu'aucune surcharge explicite correspondante n'existe), alors la fonction est appelée sur chacun des éléments de la collection. Enfin, dans le cas de retours multiples (par exemple avec evaluate), les valeurs de retour sont stockées dans une collection, et non un vecteur. Outre ces différences, il est maintenant possible de créer des vecteurs à plusieurs dimensions (je ne montre pas d'exemple, mais tout ce qui suit est aussi valable pour les collections) : Code :
... il s'agit bel et bien d'un vecteur multidimensionnel ! L'organisation en mémoire est strictement la même que celle d'un vecteur monodimensionnel, dans le sens où tous les éléments sont contigus en mémoire. On peut donc tout à fait parcourir ce vecteur comme un vecteur normal (on voit alors comment les différentes dimensions sont arrangées en mémoire) : Code :
Autre nouveauté, on peut demander à extraire un "intervalle" plutôt qu'une seule valeur en utilisant la notation a:b, où a représente la borne inférieure et b la borne supérieure (toutes deux inclusives) : Et ça marche aussi avec les chaînes de caractères : Un intervalle est un type à part entière, et il est possible de l'utiliser comme argument de certaines fonctions. On notera par exemple la fonction indgen qui génère une suite d'entiers consécutifs et stocke le tout dans un vecteur : Code :
Ces intervalles sont de mini collections : on peut appliquer des opérations dessus, accéder à chacune des bornes via (5:12)[0] et (5:12)[1], etc. On peut créer un intervalle hétérogène, même si ça n'a pas beaucoup d'intérêt à l'heure actuelle ("abc":12). Pour en finir sur les vecteurs, j'ai aussi ajouté une fonction replicate qui, comme son nom l'indique, créé un certain nombre de copie de son argument et en fait un vecteur : Code :
Quelques autres changements moins importants :
Parmi les choses que j'aimerai ajouter pour la suite, les deux plus importantes sont :
Binaires : Note : L'édition des messages anciens de plus de 3 jours est désactivée sur Developpez. Il est donc presque certain que les versions ci-dessous ne soient pas les plus récentes. Merci de visiter cette page pour être sûr d'avoir la dernière version : [click].... et sources :
__________________
Mes programmes : éditeur de sous-titres, générateur de code C++, calcul formel en ligne de commande, wrapper C++ pour Lua, bibliothèque de GUI, utilitaire pour la physique en C++11. |
||||||||
|
|
00
|
Copyright © 2000-2013 - www.developpez.com