Composants génériques de calcul scientifique



Documents pareils
Introduction à la programmation orientée objet, illustrée par le langage C++ Patrick Cégielski

Solution A La Gestion Des Objets Java Pour Des Systèmes Embarqués

Logiciel Libre Cours 3 Fondements: Génie Logiciel

basée sur le cours de Bertrand Legal, maître de conférences à l ENSEIRB Olivier Augereau Formation UML

Grandes lignes ASTRÉE. Logiciels critiques. Outils de certification classiques. Inspection manuelle. Definition. Test

Patrons de Conception (Design Patterns)

Bases de programmation. Cours 5. Structurer les données

Prénom : Matricule : Sigle et titre du cours Groupe Trimestre INF1101 Algorithmes et structures de données Tous H2004. Loc Jeudi 29/4/2004

Cours 1 : La compilation

Sciences de Gestion Spécialité : SYSTÈMES D INFORMATION DE GESTION

Suivant les langages de programmation, modules plus avancés : modules imbriqués modules paramétrés par des modules (foncteurs)

TP1 : Initiation à Java et Eclipse

Programmes des classes préparatoires aux Grandes Ecoles

Université du Québec à Chicoutimi. Département d informatique et de mathématique. Plan de cours. Titre : Élément de programmation.

Cycle de vie du logiciel. Unified Modeling Language UML. UML: définition. Développement Logiciel. Salima Hassas. Unified Modeling Language

UE Programmation Impérative Licence 2ème Année

Rapport de Synthèse. Création d un Générateur de modèle PADL pour le langage C++ Sébastien Colladon

La carte, le territoire et l'explorateur où est la visualisation? Jean-Daniel Fekete Equipe-projet AVIZ INRIA

Systèmes d information et bases de données (niveau 1)

PG208, Projet n 3 : Serveur HTTP évolué

Héritage presque multiple en Java (1/2)

Analyse de sécurité de logiciels système par typage statique

DE L ALGORITHME AU PROGRAMME INTRO AU LANGAGE C 51

Gé nié Logiciél Livré Blanc

GOL502 Industries de services

Éléments d informatique Cours 3 La programmation structurée en langage C L instruction de contrôle if

INTRODUCTION A JAVA. Fichier en langage machine Exécutable

Une proposition d extension de GML pour un modèle générique d intégration de données spatio-temporelles hétérogènes

Les diagrammes de modélisation

Annexe : La Programmation Informatique

Projet Active Object

Modélisation de Lignes de Produits en UML *

Formula Negator, Outil de négation de formule.

Vérification de programmes et de preuves Première partie. décrire des algorithmes

UE C avancé cours 1: introduction et révisions

Les algorithmes de base du graphisme

Cours d initiation à la programmation en C++ Johann Cuenin

Conception des systèmes répartis

Intégration de l interface graphique de Ptidej dans Eclipse

INITIATION AU LANGAGE C SUR PIC DE MICROSHIP

Les structures de données. Rajae El Ouazzani

Machines virtuelles Cours 1 : Introduction

IRL : Simulation distribuée pour les systèmes embarqués

Introduction à MATLAB R

Cours de Java. Sciences-U Lyon. Java - Introduction Java - Fondamentaux Java Avancé.

Quelques patterns pour la persistance des objets avec DAO DAO. Principe de base. Utilité des DTOs. Le modèle de conception DTO (Data Transfer Object)

Techniques d interaction dans la visualisation de l information Séminaire DIVA

Introduction au langage C

NT2 : Une bibliothèque haute-performance pour la vision artificielle. NT2 : A High-performance library for computer vision.

Évaluation et implémentation des langages

Le Futur de la Visualisation d Information. Jean-Daniel Fekete Projet in situ INRIA Futurs

Chapitre 1 : Introduction aux bases de données

Table des matières PRESENTATION DU LANGAGE DS2 ET DE SES APPLICATIONS. Introduction

Chapitre VI- La validation de la composition.

Arithmétique binaire. Chapitre. 5.1 Notions Bit Mot

UML (Diagramme de classes) Unified Modeling Language

IN Cours 1. 1 Informatique, calculateurs. 2 Un premier programme en C

Figure 3.1- Lancement du Gambit

CORBA haute performance

Projet L1, S2, 2015: Simulation de fourmis, Soutenance la semaine du 4 mai.


données en connaissance et en actions?

Méthodes d évolution de modèle produit dans les systèmes du type PLM

CARTOGRAPHIE EN LIGNE ET GÉNÉRALISATION

Quatrième partie IV. Test. Test 15 février / 71

Présentation du langage et premières fonctions

XML par la pratique Bases indispensables, concepts et cas pratiques (3ième édition)

WINDOWS SHAREPOINT SERVICES 2007

Cours en ligne Développement Java pour le web

Généralités sur le Langage Java et éléments syntaxiques.

Éléments de programmation et introduction à Java

PROJET ALGORITHMIQUE ET PROGRAMMATION II

Travaux pratiques. Compression en codage de Huffman Organisation d un projet de programmation

Introduction au datamining

Laboratoire 4 Développement d un système intelligent

Vérifier la qualité de vos applications logicielle de manière continue

Représentation d un entier en base b

EXTENSION D UN OUTIL DE VISUALISATION DE TRACES

Rappel. Analyse de Données Structurées - Cours 12. Un langage avec des déclaration locales. Exemple d'un programme

Cours d introduction à l informatique. Partie 2 : Comment écrire un algorithme? Qu est-ce qu une variable? Expressions et instructions

PRESENTATION DES RECOMMANDATIONS DE VANCOUVER

Programmer en JAVA. par Tama

EPREUVE OPTIONNELLE d INFORMATIQUE CORRIGE

Le Processus RUP. H. Kadima. Tester. Analyst. Performance Engineer. Database Administrator. Release Engineer. Project Leader. Designer / Developer

Pour signifier qu'une classe fille hérite d'une classe mère, on utilise le mot clé extends class fille extends mère

Programmation impérative

Tutoriel LabVIEW Des fonctions simples à l acquisition de données

Une bibliothèque de templates pour CUDA

Centre CPGE TSI - Safi 2010/2011. Algorithmique et programmation :

Le langage C++ est un langage de programmation puissant, polyvalent, on serait presque tenté de dire universel, massivement utilisé dans l'industrie

Evaluation des performances de programmes parallèles haut niveau à base de squelettes

Le développement d'applications informatiques

Pentaho Business Analytics Intégrer > Explorer > Prévoir

Julien MATHEVET Alexandre BOISSY GSID 4. Rapport RE09. Load Balancing et migration

Comment créer des rapports de test professionnels sous LabVIEW? NIDays 2002

Métriques de performance pour les algorithmes et programmes parallèles

TP 1. Prise en main du langage Python

Résolution d équations non linéaires

MIS 102 Initiation à l Informatique

Une méthode d apprentissage pour la composition de services web

Transcription:

Composants génériques de calcul scientifique T. Géraud et A. Duret-Lutz RAPPORT TECHNIQUE 9901 MARS 1999 Laboratoire de Recherche et Développement d EPITA 14-16, rue Voltaire 94276 Le Kremlin-Bicêtre cedex France Tél. +33 1 44 08 01 01 Fax +33 1 44 08 01 99

Composants génériques de calcul scientifique 2 Document Composants génériques de calcul scientifique T. Géraud et A. Duret-Lutz Rapport technique 9901 Mars 1999 Laboratoire de Recherche et Développement d EPITA 14-16, rue Voltaire 94276 Le Kremlin-Bicêtre cedex France Tél. +33 1 44 08 01 01 Fax +33 1 44 08 01 99 Résumé Dans le cadre de l écriture en C++ d une bibliothèque de traitements d images et d un atelier de programmation par composants d une chaîne de traitements, nous nous sommes fixés deux objectifs principaux : rendre les traitements génériques vis-à-vis du type de ses entrées sans entraîner de surcoût significatif à l exécution, et pouvoir exécuter un traitement lorsqu il a été introduit ultérieurement à la compilation de l atelier. Le premier objectif est atteint à l aide de programmation générique et de la traduction en polymorphisme statique de certains idiomes (design patterns) définis pour le polymorphisme dynamique. La problématique du second objectif est double. Tout d abord, nous devions réaliser la mise en correspondance d un traitement dont les entrées-sorties sont des types abstraits et de la routine générique, chargée du traitement, dont les paramètres sont des types concrets ; ensuite, nous devions pouvoir compiler et lier de nouveaux traitements à la volée, lors de l exécution de l atelier. Pour atteindre ce double objectif, nous utilisons de la programmation générative et nous pratiquons l instanciation paresseuse (lazy) de code générique. Les solutions que nous apportons permettent la gestion de composants réutilisables de calcul scientifique, ce qui, à notre connaissance, est original. Mots-clefs Calcul numérique, programmation générique, programmation par composants.

Composants génériques de calcul scientifique 3 1 Introduction Dans le domaine du traitement d images, ou plus généralement de la vision par ordinateur, aucune bibliothèque n a été à ce jour suffisamment fédératrice pour être adoptée par de nombreux laboratoires. Plusieurs efforts ont été menés dans ce sens mais tous se sont heurtés à une même difficulté : la grande diversité des problèmes abordés dans ce domaine nécessite une généricité totale des outils proposés. Nous devons nous affranchir de deux facteurs : le type des structures de données et le type-même des données. Suivant les domaines, les traitements doivent s appliquer à des images 2D ou 3D, isotropes ou non, à des séquences temporelles d images, à des pyramides multi-échelles ou multi-résolutions d images, à des régions ou des graphes d adjacences de régions, etc. Quant aux données, elles peuvent être scalaires ou booléennes, entières ou flottantes, complexes, vectorielles, ou encore composées (comme dans le cas de la couleur) et chaque composante peut avoir son propre codage, etc. Un véritable enjeu est donc de proposer un jeu d outils qui soit générique vis-à-vis du type des structures de données et du type des données, mais qui soit également performant et de taille raisonnable. En effet, l obtention de la généricité ne doit pas se traduire par un surcoût significatif lors de l exécution d un traitement ; de plus, il n est pas question de compiler l ensemble des triplets traitement structure de données données car sa cardinalité est trop élevée et, du point de vue d un groupe d utilisateurs, seul un petit sous-ensemble est intéressant. Dans cet article, nous présentons les solutions logicielles que nous avons développées. La section 2 décrit l obtention de routines de calcul par programmation générique et montre comment le polymorphisme dynamique de certains idiomes est traduit en polymorphisme statique. La section 3 décrit deux mécanismes : celui qui permet de mettre en correspondance un composant dont les entrées et sorties sont de type abstrait avec la routine de calcul dont les paramètres sont de types concrets, et celui qui permet de compiler à la volée une routine lorsqu elle n a pas encore été instanciée. Les solutions que nous avançons ne sont pas spécifiques à la problématique du traitement d images ; elles sont directement généralisables au domaine non exploré du calcul scientifique générique par composants. NOTA BENE : LE CODE PRÉSENTÉ DANS CE DOCUMENT EST VOLONTAIREMENT SIMPLIFIÉ POUR UNE PLUS GRANDE CLARTÉ DES EXEMPLES. 2 Généricité des routines de calcul Un traitement se traduit par une routine de calcul qui nécessite un certain nombre d outils. Les types des entrées et sorties du traitement conditionne non seulement la routine mais également les outils. 2.1 Exemple de traitement Pour illustrer nos propos, considérons un traitement d images classique, le filtre moyenneur. Sa description, donnée d ores et déjà sous une forme abstraite, peut s énoncer comme suit :

Composants génériques de calcul scientifique 4 pour chaque site d un agrégat d entrée, on calcule la moyenne des valeurs sur le voisinage de ce site puis on affecte cette valeur au site correspondant de l agrégat de sortie. Dans les bibliothèques de traitement d images, le paradigme le plus avancé que l on puisse trouver est la généricité vis-à-vis des types de données [1] ; l écriture de la routine de calcul, dans le cas d images bidimensionnelles, est alors similaire à celui qui suit [6, 8]. template< typename T > void mean( Image2D<T>& imagein, Image2D<T>& imageout ) for ( int irow = 0; irow < imagein.nrows; ++irow ) for ( int icolumn = 0; icolumn < imagein.ncolumns; ++icolumn ) T sum = 0; for ( ineighbor = 0; ineighbor < 4; ++ineighbor ) sum += imagein.data[ irow + C4[iNeighbor].deltaRow ] [ icolumn + C4[iNeighbor].deltaColumn ]; imageout.data[irow][icolumn] = sum / 4; Afin d obtenir en plus une généricité vis-à-vis des structures de données, nous pouvons introduire des idiomes [5]. 2.2 Ecriture d une routine de calcul par idiomes Afin de prendre en charge le balayage des sites d un agrégat et d abstraire les détails d implantation liés à chaque type de structures, nous pouvons utiliser des itérateurs. Ces derniers sont ici les outils de la routine de calcul : un itérateur exhaustif pour parcourir tous les sites d un agrégat, un itérateur de voisinage qui parcourt dans un agrégat les sites voisins du site courant d un itérateur de référence, et un itérateur suiveur qui se positionne dans un agrégat de façon équivalente à un itérateur de référence défini sur un autre agrégat. Deux premières classes abstraites sont définies : Aggregate<T>, qui représente un agrégat d éléments de type T, et Iterator<T>, qui représente un itérateur sur cet agrégat et déclare un jeu de méthodes abstraites pour une manipulation uniforme de tout itérateur. De la classe Iterator<T> dérivent les classes abstraites ExhaustiveIterator<T> et NeighborhoodIterator<T> ainsi que la classe concrète FollowerIterator<T>. La classe Aggregate<T> joue le rôle d une usine abstraite pour produire les itérateurs à l aide de méthodes ad hoc. Ainsi, par spécialisation, la classe Image2D<T> produit des outils ExhaustiveIterator Image2D<T> et NeighborhoodIterator Image2D<T>. Avec cette modélisation, l écriture du traitement ne fait pas intervenir de types particuliers à un type concret d agrégat :

Composants génériques de calcul scientifique 5 approche polymorphisme polymorphisme impérative dynamique statique 3.5 s 11.4 s 3.9 s TAB. 1: Performance de différentes techniques de programmation. template< typename T > void mean( Aggregate<T>& inputaggregate, Aggregate<T>& outputaggregate ) Iterator<T>& input = inputaggregate.createexhaustiveiterator(), Iterator<T>& neighbor = input.createneighboriterator(); FollowerIterator<T> output( outputaggregate, input ); for_each( input ) T sum = 0; for_each( neighbor ) sum += neighbor.getvalue(); output.setvalue( sum / neighbor.getcard() ); Les solutions fondées sur le polymorphisme dynamique présentent cependant quatre inconvénients énumérés dans [4] : ce paradigme renforce le couplage à travers la relation d héritage [7], il induit un surcoût en mémoire pour chaque objet (pointeur vers la table de fonctions virtuelles [3]), il ne permet pas de vérifications très sévères du typage fort à la compilation, et enfin, il ajoute une indirection lors de chaque appel à une fonction polymorphe. Pour une problématique de calculs intensifs, ce dernier inconvénient est rédhibitoire : la répétitivité des indirections peut être très élevée ce qui se traduit par un surcoût important en temps d exécution par rapport à l écriture impérative de la section 2.1. La table 1 donne, pour notre exemple, les temps d exécution obtenus avec un PC à 333MHz sous Linux, l image traitée possédant huit millions de points. L approche classique du paradigme objet étant inadaptée au cadre du calcul scientifique, nous avons utilisé un autre paradigme, apparu récemment [2], et nous allons voir qu il ne s agit que de traduire l écriture des idiomes dans ce nouveau paradigme. 2.3 Traduction statique des idiomes Avec l adoption en 1994 par le comité de normalisation du C++ de la Standard Template Library (STL) [9], bibliothèque de conteneurs et d algorithmes associés, une nouvelle approche de la programmation d algorithmes génériques s est révélée : le polymorphisme statique ; l idée clef est de paramétrer les algorithmes non pas par le type des données mais par le type des structures. Deux bibliothèques récentes utilisent cette technique, CGAL [4] et Blitz++ [10], respectivement pour du calcul géométrique et pour du calcul algébrique. La traduction statique d une usine abstraite utilise la déduction de types. En effet, les types des outils d une routine algorithmique se déduisent du type des struc-

Composants génériques de calcul scientifique 6 tures sur lesquelles la routine s applique. Ces déductions sont réalisées par des défini-tions de types dans les classes d agrégats : template< typename T > class Image2D : public Aggregate<T> public: typedef T typedef ExhaustiveIterator_Image2D<T> typedef NeighborhoodIterator_Image2D<T> //... ; DataType; ExhaustiveIterator; NeighborIterator; Lorsqu un module n est pas polymorphe, il doit avoir autant de paramètres qu il manipulerait de classes abstraites avec une implantation traditionnelle. C est le cas de l itérateur suiveur ; ainsi, il est paramétré par le type de l agrégat sur lequel il itère et par le type de l itérateur qu il suit : template< typename Aggr, typename Iter > class FollowerIterator //... ; Au final, la routine de calcul a pour écriture statique : template< typename Aggr > void mean( Aggr& inputaggregate, Aggr& outputaggregate ) typename Aggr::ExhaustiveIterator input( inputaggregate ); typename Aggr::NeighborIterator neighbor( input ); FollowerIterator< Aggr, Aggr::ExhaustiveIterator > output( outputaggregate, input ); for_each( input ) typename Aggr::DataType::CumulType sum = 0; for_each( neighbor ) sum += neighbor.getvalue(); output.setvalue( sum / neighbor.getcard() ); Grâce à la traduction statique, le coût de l écriture dynamique de l idiome itérateur disparaît en majeure partie ; le temps d exécution est alors seulement légèrement supérieure à celui d une écriture impérative (voir table 1). Nous sommes actuellement en train de formaliser les règles d écriture de logiciels en programmation générique avec polymorphisme statique, et en particulier, d établir les règles d écriture d un certain nombre d idiomes dans ce nouveau paradigme. 3 Mise en correspondance des composants et des routines génériques de calcul Posons tout d abord la problématique d une plateforme basée sur des composants de calcul numérique.

Composants génériques de calcul scientifique 7 3.1 Problématique des composants de calcul scientifique Dans la plateforme de traitements d images que nous sommes en train de réaliser, les fonctionnalités que nous souhaitons offrir à l utilisateur sont : l appel d un traitement à partir d un programme écrit en C++, l exécution d un traitement à partir d une ligne de commande textuelle, l exécution d un traitement à partir d une interface graphique (commande graphique), la programmation visuelle d une chaîne de traitements, l augmentation de la bibliothèque (par tout nouveau type de données, type de structures de données ou routine de traitement). Un traitement se traduit par un composant qui peut être soit composite (c està-dire un ensemble de composants) soit atomique. Dans un composite, les entrées et sorties des composants qu il comporte sont raccordés par des liens. Les liens sont le support du flux des données et peuvent être considérés comme les arcs typés du graphe d exécution, les nœuds du graphe correspondant aux composants. Une chaîne de traitements est donc un composite, ce qui permet à l utilisateur de l insérer au sein d autres chaînes de traitements. La description d un composant, donnée par un fichier texte, permet de définir ses entrées et sorties, ses éventuelles contraintes ou déductions concernant le typage des entrées et sorties, et l appel à la routine de calcul. Cette description permet d interpréter la ligne de commande texte, de définir l interface de la commande graphique, de gérer l affichage du composant dans l atelier, et de typer un graphe d exécution. Les entrées et sorties des composants sont du type statique abstrait Data (la classe Aggregate<T> qui apparaît dans le code de la section 2.3 dérive de la classe Data). Afin de pouvoir résoudre à l exécution l appel à des routines de calcul numérique à partir d un composant, il faut donc être capable de connaître explicitement les types dynamiques concrets des entrées et sorties. 3.2 Résolution des types A partir de la description d un traitement, nous générons automatiquement le code des procédures qui traduisent les types statiques des entrées et sorties en types dynamiques, et qui se chargent alors d appeler la routine générique de calcul. Quant au nom d une de ces procédures, il est formé avec le nom du traitement et le nom des types paramètres. La génération de code (generative programming [11]) est donc totalement factorisable. Pour reprendre notre exemple, lorsque l entrée et la sortie sont de type Image2D<Float>, la procédure suivante aura été générée ou devra l être : void mean Image2D_Float_( Component& component ) Image2D<Float>& input = dynamic_cast< Image2D<Float>& >( component.entry( "input" ) ); Image2D<Float>& output = dynamic_cast< Image2D<Float>& >( component.entry( "output" ) ); mean( input, output );

Composants génériques de calcul scientifique 8 L appel d un composant à une procédure ne fait plus intervenir, du point de vue du langage, les types concrets des entrées et sorties. Afin d effectuer dynamiquement l appel à la procédure ad hoc, nous maintenons un tableau associatif qui associe au nom du traitement et à la chaîne de caractères caractéristique des types dynamiques des paramètres de la routine l adresse de la procédure. Nous aurons ainsi en mémoire l association : Call::func["mean"]["Image2D_Float_"] == & mean Image2D_Float_ L exécution du code de calcul numérique d un composant est alors possible car chaque donnée sait renvoyer son nom de type dynamique (Data déclare une mé-thode polymorphe pour cela) et car la description d un composant comporte la règle de nommage de sa routine. Le dernier problème que nous avons résolu est l instanciation à la volée d une routine générique. 3.3 Instanciation paresseuse Dans une bibliothèque générique, les triplets traitement structure de données données, comme par exemple ( mean, Image2D, Float ), peuvent atteindre rapidement un nombre considérable. Pour un groupe d utilisateurs qui partagent essentiellement les mêmes besoins, il est donc inutile de forcer l instanciation de l ensemble des routines. Aussi, nous avons adopté une attitude paresseuse pour l instanciation des routines ; la plateforme peut ainsi être totalement utilisable sans qu un seul traitement ne soit compilé. Lorsqu un composant de traitement est appelé par l utilisateur et lorsque les entrées du composant sont renseignées, la vérification d éventuelles contraintes de typage, fournies par la description du composant, est effectuée. Ensuite, un test réalisé sur le tableau associatif permet de savoir si la procédure ad hoc est disponible. Si elle est disponible en mémoire, elle est exécutée ; si elle est disponible sous forme d objet partagé (fichier mean Image2D Float.so pour la routine du même nom), elle est chargée auparavant et le tableau associatif est mis à jour ; si la procédure n est pas disponible, son code est généré puis compilé ce qui nous replace dans le cas précédent. De plus, l ajout d une entité, que ce soit un traitement, un type de structure de données ou un type de données, est réalisé dynamiquement dans l atelier afin d accéder immédiatement à de nouvelles fonctionnalités par simple réutilisation de l existant. Enfin, l utilisateur qui connaît ses besoins peut demander explicitement la compilation de triplets. 4 Conclusion Depuis quelques années, l utilisation de la généricité ne se limite plus à des classes utilitaires mais constitue un nouveau paradigme de programmation. Du fait du regain d intérêt récent vis-à-vis de la programmation générique, aucun ensemble de règles d écriture n a véritablement été formalisé à ce jour. Nous avons mis en évidence deux premières règles (soulignées en section 2.3) dont la première est

Composants génériques de calcul scientifique 9 une traduction statique de l idiome fabrique abstraite ; néanmoins, il reste encore à effectuer un important travail de formalisation. Nous avons aussi montré comment mettre en correspondance les notions de composant et de généricité. Si notre travail s inscrit dans le cadre de la réalisation d une plateforme de calcul scientifique, il est applicable aux multiples domaines que la programmation générique promet de couvrir. Un composant peut véritablement être générique. Références [1] M.R. Dobie and P.H. Lewis, Data structures for image processing in C, Pattern Recognition Letters, vol. 12, n o 8, pp. 457-466, 1991. [2] G. Dos Reis, Vers une approche du calcul scientifique en C++, Rapport de recherche n o 3362, INRIA, 1998. [3] M.A. Ellis et B. Stroustrup, The annotated C++ reference manual, Addison- Wesley, 1990. [4] A. Fabri et al., On the design of CGAL, the computational geometry algorithms library, Rapport de recherche n o 3407, INRIA, 1998. [5] E. Gamma et al., Design Patterns Elements of reusable object-oriented software, Addison-Wesley, 1994. [6] C. Kohl and J. Mundy, The development of the Image Understanding Environment, in proc. Int. Conf. on Computer Vision and Pattern Recognition, pp. 443-447, 1994. [7] J. Lakos, Large scale C++ software design, Addison-Wesley, 1996. [8] G.X. Ritter, J.N. Wilson and J.L. Davidson, Image Algebra : an overview, Computer Vision, Graphics, and Image Processing, vol. 49, n o 3, pp. 297-331, 1990. [9] A. Stepanov et M. Lee, The standard template library, Rapport, Hewlett- Packard, 1994. [10] T.L. Veldhuizen, Blitz++, http://monet.uwaterloo.ca/blitz/, 1996. [11] T.L. Veldhuizen et D. Gannon, Active Libraries : rethinking the roles of compilers and libraries, in Proceedings of the SIAM Workshop on Object Oriented Methods for Inter-operable Scientific and Engineering Computing, Yorktown Heights, New York, 1998.