[pyoperators] 02/04: Imported Upstream version 0.13

Ghislain Vaillant ghisvail-guest at moszumanska.debian.org
Wed Oct 15 22:43:32 UTC 2014


This is an automated email from the git hooks/post-receive script.

ghisvail-guest pushed a commit to branch master
in repository pyoperators.

commit 06451ceb130fa89416f995a547ff85139fc57611
Author: Ghislain Antony Vaillant <ghisvail at gmail.com>
Date:   Wed Oct 15 22:57:04 2014 +0100

    Imported Upstream version 0.13
---
 .gitignore                                  |  11 +
 LICENCE_FR                                  | 519 ++++++++++++++++++++++++++++
 LICENSE                                     | 515 +++++++++++++++++++++++++++
 MANIFEST.in                                 |   3 +
 PKG-INFO                                    | 121 -------
 README.rst                                  |   2 +
 hooks.py                                    | 411 ++++++++++++++--------
 pyoperators/{__init__.py => __init__.py.in} |   7 +-
 pyoperators/config.py                       |  36 +-
 pyoperators/core.py                         | 129 ++++---
 pyoperators/fft.py                          |   4 +-
 pyoperators/flags.py                        |  15 +-
 pyoperators/iterative/algorithms.py         |   2 +-
 pyoperators/iterative/cg.py                 |  11 +-
 pyoperators/iterative/core.py               |  18 +-
 pyoperators/linear.py                       |  56 +--
 pyoperators/memory.py                       |  32 +-
 pyoperators/nonlinear.py                    |   4 +-
 pyoperators/norms.py                        |  41 ---
 pyoperators/operators_pywt.py               |   8 +-
 pyoperators/rules.py                        |   9 +-
 pyoperators/utils/__init__.py               |   2 +-
 pyoperators/utils/cythonutils.pyx           | 132 +++++++
 pyoperators/utils/fake_MPI.py               |   9 +-
 pyoperators/utils/misc.py                   |  33 +-
 pyoperators/utils/mpi.py                    |   5 +-
 pyoperators/utils/testing.py                |   4 +-
 setup.py                                    |  22 +-
 test/__init__.py                            |   0
 test/common.py                              | 168 +++++++++
 test/test_broadcastingoperators.py          |   4 +-
 test/test_core.py                           |  23 +-
 test/test_delete.py                         |   5 +-
 test/test_linear.py                         |   6 +-
 test/test_memory.py                         |   3 +-
 test/test_nbytes.py                         |   5 +-
 test/test_proxy.py                          |   4 +-
 test/test_utils.py                          |  10 +-
 38 files changed, 1882 insertions(+), 507 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3def2b9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*.pyc
+*.so
+.coverage
+MANIFEST
+build
+dist
+_site
+htmlcov
+pyoperators/__init__.py
+pyoperators/utils/ufuncs.c
+__pycache__
diff --git a/LICENCE_FR b/LICENCE_FR
new file mode 100644
index 0000000..355f45e
--- /dev/null
+++ b/LICENCE_FR
@@ -0,0 +1,519 @@
+
+CONTRAT DE LICENCE DE LOGICIEL LIBRE CeCILL-B
+
+
+    Avertissement
+
+Ce contrat est une licence de logiciel libre issue d'une concertation
+entre ses auteurs afin que le respect de deux grands principes pr�side �
+sa r�daction:
+
+    * d'une part, le respect des principes de diffusion des logiciels
+      libres: acc�s au code source, droits �tendus conf�r�s aux
+      utilisateurs,
+    * d'autre part, la d�signation d'un droit applicable, le droit
+      fran�ais, auquel elle est conforme, tant au regard du droit de la
+      responsabilit� civile que du droit de la propri�t� intellectuelle
+      et de la protection qu'il offre aux auteurs et titulaires des
+      droits patrimoniaux sur un logiciel.
+
+Les auteurs de la licence CeCILL-B (pour Ce[a] C[nrs] I[nria] L[ogiciel]
+L[ibre]) sont:
+
+Commissariat � l'Energie Atomique - CEA, �tablissement public de
+recherche � caract�re scientifique, technique et industriel, dont le
+si�ge est situ� 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris.
+
+Centre National de la Recherche Scientifique - CNRS, �tablissement
+public � caract�re scientifique et technologique, dont le si�ge est
+situ� 3 rue Michel-Ange, 75794 Paris cedex 16.
+
+Institut National de Recherche en Informatique et en Automatique -
+INRIA, �tablissement public � caract�re scientifique et technologique,
+dont le si�ge est situ� Domaine de Voluceau, Rocquencourt, BP 105, 78153
+Le Chesnay cedex.
+
+
+    Pr�ambule
+
+Ce contrat est une licence de logiciel libre dont l'objectif est de
+conf�rer aux utilisateurs une tr�s large libert� de modification et de
+redistribution du logiciel r�gi par cette licence.
+
+L'exercice de cette libert� est assorti d'une obligation forte de
+citation � la charge de ceux qui distribueraient un logiciel incorporant
+un logiciel r�gi par la pr�sente licence afin d'assurer que les
+contributions de tous soient correctement identifi�es et reconnues.
+
+L'accessibilit� au code source et les droits de copie, de modification
+et de redistribution qui d�coulent de ce contrat ont pour contrepartie
+de n'offrir aux utilisateurs qu'une garantie limit�e et de ne faire
+peser sur l'auteur du logiciel, le titulaire des droits patrimoniaux et
+les conc�dants successifs qu'une responsabilit� restreinte.
+
+A cet �gard l'attention de l'utilisateur est attir�e sur les risques
+associ�s au chargement, � l'utilisation, � la modification et/ou au
+d�veloppement et � la reproduction du logiciel par l'utilisateur �tant
+donn� sa sp�cificit� de logiciel libre, qui peut le rendre complexe �
+manipuler et qui le r�serve donc � des d�veloppeurs ou des
+professionnels avertis poss�dant des connaissances informatiques
+approfondies. Les utilisateurs sont donc invit�s � charger et tester
+l'ad�quation du logiciel � leurs besoins dans des conditions permettant
+d'assurer la s�curit� de leurs syst�mes et/ou de leurs donn�es et, plus
+g�n�ralement, � l'utiliser et l'exploiter dans les m�mes conditions de
+s�curit�. Ce contrat peut �tre reproduit et diffus� librement, sous
+r�serve de le conserver en l'�tat, sans ajout ni suppression de clauses.
+
+Ce contrat est susceptible de s'appliquer � tout logiciel dont le
+titulaire des droits patrimoniaux d�cide de soumettre l'exploitation aux
+dispositions qu'il contient.
+
+
+    Article 1 - DEFINITIONS
+
+Dans ce contrat, les termes suivants, lorsqu'ils seront �crits avec une
+lettre capitale, auront la signification suivante:
+
+Contrat: d�signe le pr�sent contrat de licence, ses �ventuelles versions
+post�rieures et annexes.
+
+Logiciel: d�signe le logiciel sous sa forme de Code Objet et/ou de Code
+Source et le cas �ch�ant sa documentation, dans leur �tat au moment de
+l'acceptation du Contrat par le Licenci�.
+
+Logiciel Initial: d�signe le Logiciel sous sa forme de Code Source et
+�ventuellement de Code Objet et le cas �ch�ant sa documentation, dans
+leur �tat au moment de leur premi�re diffusion sous les termes du Contrat.
+
+Logiciel Modifi�: d�signe le Logiciel modifi� par au moins une
+Contribution.
+
+Code Source: d�signe l'ensemble des instructions et des lignes de
+programme du Logiciel et auquel l'acc�s est n�cessaire en vue de
+modifier le Logiciel.
+
+Code Objet: d�signe les fichiers binaires issus de la compilation du
+Code Source.
+
+Titulaire: d�signe le ou les d�tenteurs des droits patrimoniaux d'auteur
+sur le Logiciel Initial.
+
+Licenci�: d�signe le ou les utilisateurs du Logiciel ayant accept� le
+Contrat.
+
+Contributeur: d�signe le Licenci� auteur d'au moins une Contribution.
+
+Conc�dant: d�signe le Titulaire ou toute personne physique ou morale
+distribuant le Logiciel sous le Contrat.
+
+Contribution: d�signe l'ensemble des modifications, corrections,
+traductions, adaptations et/ou nouvelles fonctionnalit�s int�gr�es dans
+le Logiciel par tout Contributeur, ainsi que tout Module Interne.
+
+Module: d�signe un ensemble de fichiers sources y compris leur
+documentation qui permet de r�aliser des fonctionnalit�s ou services
+suppl�mentaires � ceux fournis par le Logiciel.
+
+Module Externe: d�signe tout Module, non d�riv� du Logiciel, tel que ce
+Module et le Logiciel s'ex�cutent dans des espaces d'adressage
+diff�rents, l'un appelant l'autre au moment de leur ex�cution.
+
+Module Interne: d�signe tout Module li� au Logiciel de telle sorte
+qu'ils s'ex�cutent dans le m�me espace d'adressage.
+
+Parties: d�signe collectivement le Licenci� et le Conc�dant.
+
+Ces termes s'entendent au singulier comme au pluriel.
+
+
+    Article 2 - OBJET
+
+Le Contrat a pour objet la concession par le Conc�dant au Licenci� d'une
+licence non exclusive, cessible et mondiale du Logiciel telle que
+d�finie ci-apr�s � l'article 5 pour toute la dur�e de protection des droits 
+portant sur ce Logiciel. 
+
+
+    Article 3 - ACCEPTATION
+
+3.1 L'acceptation par le Licenci� des termes du Contrat est r�put�e
+acquise du fait du premier des faits suivants:
+
+    * (i) le chargement du Logiciel par tout moyen notamment par
+      t�l�chargement � partir d'un serveur distant ou par chargement �
+      partir d'un support physique;
+    * (ii) le premier exercice par le Licenci� de l'un quelconque des
+      droits conc�d�s par le Contrat.
+
+3.2 Un exemplaire du Contrat, contenant notamment un avertissement
+relatif aux sp�cificit�s du Logiciel, � la restriction de garantie et �
+la limitation � un usage par des utilisateurs exp�riment�s a �t� mis �
+disposition du Licenci� pr�alablement � son acceptation telle que
+d�finie � l'article 3.1 ci dessus et le Licenci� reconna�t en avoir pris
+connaissance.
+
+
+    Article 4 - ENTREE EN VIGUEUR ET DUREE
+
+
+      4.1 ENTREE EN VIGUEUR
+
+Le Contrat entre en vigueur � la date de son acceptation par le Licenci�
+telle que d�finie en 3.1.
+
+
+      4.2 DUREE
+
+Le Contrat produira ses effets pendant toute la dur�e l�gale de
+protection des droits patrimoniaux portant sur le Logiciel.
+
+
+    Article 5 - ETENDUE DES DROITS CONCEDES
+
+Le Conc�dant conc�de au Licenci�, qui accepte, les droits suivants sur
+le Logiciel pour toutes destinations et pour la dur�e du Contrat dans
+les conditions ci-apr�s d�taill�es.
+
+Par ailleurs, si le Conc�dant d�tient ou venait � d�tenir un ou
+plusieurs brevets d'invention prot�geant tout ou partie des
+fonctionnalit�s du Logiciel ou de ses composants, il s'engage � ne pas
+opposer les �ventuels droits conf�r�s par ces brevets aux Licenci�s
+successifs qui utiliseraient, exploiteraient ou modifieraient le
+Logiciel. En cas de cession de ces brevets, le Conc�dant s'engage �
+faire reprendre les obligations du pr�sent alin�a aux cessionnaires.
+
+
+      5.1 DROIT D'UTILISATION
+
+Le Licenci� est autoris� � utiliser le Logiciel, sans restriction quant
+aux domaines d'application, �tant ci-apr�s pr�cis� que cela comporte:
+
+   1. la reproduction permanente ou provisoire du Logiciel en tout ou
+      partie par tout moyen et sous toute forme.
+
+   2. le chargement, l'affichage, l'ex�cution, ou le stockage du
+      Logiciel sur tout support.
+
+   3. la possibilit� d'en observer, d'en �tudier, ou d'en tester le
+      fonctionnement afin de d�terminer les id�es et principes qui sont
+      � la base de n'importe quel �l�ment de ce Logiciel; et ceci,
+      lorsque le Licenci� effectue toute op�ration de chargement,
+      d'affichage, d'ex�cution, de transmission ou de stockage du
+      Logiciel qu'il est en droit d'effectuer en vertu du Contrat.
+
+
+      5.2 DROIT D'APPORTER DES CONTRIBUTIONS
+
+Le droit d'apporter des Contributions comporte le droit de traduire,
+d'adapter, d'arranger ou d'apporter toute autre modification au Logiciel
+et le droit de reproduire le logiciel en r�sultant.
+
+Le Licenci� est autoris� � apporter toute Contribution au Logiciel sous
+r�serve de mentionner, de fa�on explicite, son nom en tant qu'auteur de
+cette Contribution et la date de cr�ation de celle-ci.
+
+
+      5.3 DROIT DE DISTRIBUTION
+
+Le droit de distribution comporte notamment le droit de diffuser, de
+transmettre et de communiquer le Logiciel au public sur tout support et
+par tout moyen ainsi que le droit de mettre sur le march� � titre
+on�reux ou gratuit, un ou des exemplaires du Logiciel par tout proc�d�.
+
+Le Licenci� est autoris� � distribuer des copies du Logiciel, modifi� ou
+non, � des tiers dans les conditions ci-apr�s d�taill�es.
+
+
+        5.3.1 DISTRIBUTION DU LOGICIEL SANS MODIFICATION
+
+Le Licenci� est autoris� � distribuer des copies conformes du Logiciel,
+sous forme de Code Source ou de Code Objet, � condition que cette
+distribution respecte les dispositions du Contrat dans leur totalit� et
+soit accompagn�e:
+
+   1. d'un exemplaire du Contrat,
+
+   2. d'un avertissement relatif � la restriction de garantie et de
+      responsabilit� du Conc�dant telle que pr�vue aux articles 8
+      et 9,
+
+et que, dans le cas o� seul le Code Objet du Logiciel est redistribu�,
+le Licenci� permette un acc�s effectif au Code Source complet du
+Logiciel pendant au moins toute la dur�e de sa distribution du Logiciel,
+�tant entendu que le co�t additionnel d'acquisition du Code Source ne
+devra pas exc�der le simple co�t de transfert des donn�es.
+
+
+        5.3.2 DISTRIBUTION DU LOGICIEL MODIFIE
+
+Lorsque le Licenci� apporte une Contribution au Logiciel, le Logiciel
+Modifi� peut �tre distribu� sous un contrat de licence autre que le
+pr�sent Contrat sous r�serve du respect des dispositions de l'article
+5.3.4.
+
+
+        5.3.3 DISTRIBUTION DES MODULES EXTERNES
+
+Lorsque le Licenci� a d�velopp� un Module Externe les conditions du
+Contrat ne s'appliquent pas � ce Module Externe, qui peut �tre distribu�
+sous un contrat de licence diff�rent.
+
+
+        5.3.4 CITATIONS
+
+Le Licenci� qui distribue un Logiciel Modifi� s'engage express�ment:
+
+   1. � indiquer dans sa documentation qu'il a �t� r�alis� � partir du
+      Logiciel r�gi par le Contrat, en reproduisant les mentions de
+      propri�t� intellectuelle du Logiciel,
+
+   2. � faire en sorte que l'utilisation du Logiciel, ses mentions de
+      propri�t� intellectuelle et le fait qu'il est r�gi par le Contrat
+      soient indiqu�s dans un texte facilement accessible depuis
+      l'interface du Logiciel Modifi�,
+
+   3. � mentionner, sur un site Web librement accessible d�crivant le
+      Logiciel Modifi�, et pendant au moins toute la dur�e de sa
+      distribution, qu'il a �t� r�alis� � partir du Logiciel r�gi par le
+      Contrat, en reproduisant les mentions de propri�t� intellectuelle
+      du Logiciel,
+
+   4. lorsqu'il le distribue � un tiers susceptible de distribuer
+      lui-m�me un Logiciel Modifi�, sans avoir � en distribuer le code
+      source, � faire ses meilleurs efforts pour que les obligations du
+      pr�sent article 5.3.4 soient reprises par le dit tiers.
+
+Lorsque le Logiciel modifi� ou non est distribu� avec un Module Externe
+qui a �t� con�u pour l'utiliser, le Licenci� doit soumettre le dit
+Module Externe aux obligations pr�c�dentes.
+
+
+        5.3.5 COMPATIBILITE AVEC LES LICENCES CeCILL et CeCILL-C
+
+Lorsqu'un Logiciel Modifi� contient une Contribution soumise au contrat
+de licence CeCILL, les stipulations pr�vues � l'article 5.3.4 sont 
+facultatives.
+
+Un Logiciel Modifi� peut �tre distribu� sous le contrat de licence
+CeCILL-C. Les stipulations pr�vues � l'article 5.3.4 sont alors 
+facultatives.
+
+
+    Article 6 - PROPRIETE INTELLECTUELLE
+
+
+      6.1 SUR LE LOGICIEL INITIAL
+
+Le Titulaire est d�tenteur des droits patrimoniaux sur le Logiciel
+Initial. Toute utilisation du Logiciel Initial est soumise au respect
+des conditions dans lesquelles le Titulaire a choisi de diffuser son
+oeuvre et nul autre n'a la facult� de modifier les conditions de
+diffusion de ce Logiciel Initial.
+
+Le Titulaire s'engage � ce que le Logiciel Initial reste au moins r�gi
+par le Contrat et ce, pour la dur�e vis�e � l'article 4.2.
+
+
+      6.2 SUR LES CONTRIBUTIONS
+
+Le Licenci� qui a d�velopp� une Contribution est titulaire sur celle-ci
+des droits de propri�t� intellectuelle dans les conditions d�finies par
+la l�gislation applicable.
+
+
+      6.3 SUR LES MODULES EXTERNES
+
+Le Licenci� qui a d�velopp� un Module Externe est titulaire sur celui-ci
+des droits de propri�t� intellectuelle dans les conditions d�finies par
+la l�gislation applicable et reste libre du choix du contrat r�gissant
+sa diffusion.
+
+
+      6.4 DISPOSITIONS COMMUNES
+
+Le Licenci� s'engage express�ment:
+
+   1. � ne pas supprimer ou modifier de quelque mani�re que ce soit les
+      mentions de propri�t� intellectuelle appos�es sur le Logiciel;
+
+   2. � reproduire � l'identique lesdites mentions de propri�t�
+      intellectuelle sur les copies du Logiciel modifi� ou non.
+
+Le Licenci� s'engage � ne pas porter atteinte, directement ou
+indirectement, aux droits de propri�t� intellectuelle du Titulaire et/ou
+des Contributeurs sur le Logiciel et � prendre, le cas �ch�ant, �
+l'�gard de son personnel toutes les mesures n�cessaires pour assurer le
+respect des dits droits de propri�t� intellectuelle du Titulaire et/ou
+des Contributeurs.
+
+
+    Article 7 - SERVICES ASSOCIES
+
+7.1 Le Contrat n'oblige en aucun cas le Conc�dant � la r�alisation de
+prestations d'assistance technique ou de maintenance du Logiciel.
+
+Cependant le Conc�dant reste libre de proposer ce type de services. Les
+termes et conditions d'une telle assistance technique et/ou d'une telle
+maintenance seront alors d�termin�s dans un acte s�par�. Ces actes de
+maintenance et/ou assistance technique n'engageront que la seule
+responsabilit� du Conc�dant qui les propose.
+
+7.2 De m�me, tout Conc�dant est libre de proposer, sous sa seule
+responsabilit�, � ses licenci�s une garantie, qui n'engagera que lui,
+lors de la redistribution du Logiciel et/ou du Logiciel Modifi� et ce,
+dans les conditions qu'il souhaite. Cette garantie et les modalit�s
+financi�res de son application feront l'objet d'un acte s�par� entre le
+Conc�dant et le Licenci�.
+
+
+    Article 8 - RESPONSABILITE
+
+8.1 Sous r�serve des dispositions de l'article 8.2, le Licenci� a la 
+facult�, sous r�serve de prouver la faute du Conc�dant concern�, de
+solliciter la r�paration du pr�judice direct qu'il subirait du fait du
+Logiciel et dont il apportera la preuve.
+
+8.2 La responsabilit� du Conc�dant est limit�e aux engagements pris en
+application du Contrat et ne saurait �tre engag�e en raison notamment:
+(i) des dommages dus � l'inex�cution, totale ou partielle, de ses
+obligations par le Licenci�, (ii) des dommages directs ou indirects
+d�coulant de l'utilisation ou des performances du Logiciel subis par le
+Licenci� et (iii) plus g�n�ralement d'un quelconque dommage indirect. En
+particulier, les Parties conviennent express�ment que tout pr�judice
+financier ou commercial (par exemple perte de donn�es, perte de
+b�n�fices, perte d'exploitation, perte de client�le ou de commandes,
+manque � gagner, trouble commercial quelconque) ou toute action dirig�e
+contre le Licenci� par un tiers, constitue un dommage indirect et
+n'ouvre pas droit � r�paration par le Conc�dant.
+
+
+    Article 9 - GARANTIE
+
+9.1 Le Licenci� reconna�t que l'�tat actuel des connaissances
+scientifiques et techniques au moment de la mise en circulation du
+Logiciel ne permet pas d'en tester et d'en v�rifier toutes les
+utilisations ni de d�tecter l'existence d'�ventuels d�fauts. L'attention
+du Licenci� a �t� attir�e sur ce point sur les risques associ�s au
+chargement, � l'utilisation, la modification et/ou au d�veloppement et �
+la reproduction du Logiciel qui sont r�serv�s � des utilisateurs avertis.
+
+Il rel�ve de la responsabilit� du Licenci� de contr�ler, par tous
+moyens, l'ad�quation du produit � ses besoins, son bon fonctionnement et
+de s'assurer qu'il ne causera pas de dommages aux personnes et aux biens.
+
+9.2 Le Conc�dant d�clare de bonne foi �tre en droit de conc�der
+l'ensemble des droits attach�s au Logiciel (comprenant notamment les
+droits vis�s � l'article 5).
+
+9.3 Le Licenci� reconna�t que le Logiciel est fourni "en l'�tat" par le
+Conc�dant sans autre garantie, expresse ou tacite, que celle pr�vue �
+l'article 9.2 et notamment sans aucune garantie sur sa valeur commerciale,
+son caract�re s�curis�, innovant ou pertinent.
+
+En particulier, le Conc�dant ne garantit pas que le Logiciel est exempt
+d'erreur, qu'il fonctionnera sans interruption, qu'il sera compatible
+avec l'�quipement du Licenci� et sa configuration logicielle ni qu'il
+remplira les besoins du Licenci�.
+
+9.4 Le Conc�dant ne garantit pas, de mani�re expresse ou tacite, que le
+Logiciel ne porte pas atteinte � un quelconque droit de propri�t�
+intellectuelle d'un tiers portant sur un brevet, un logiciel ou sur tout
+autre droit de propri�t�. Ainsi, le Conc�dant exclut toute garantie au
+profit du Licenci� contre les actions en contrefa�on qui pourraient �tre
+diligent�es au titre de l'utilisation, de la modification, et de la
+redistribution du Logiciel. N�anmoins, si de telles actions sont
+exerc�es contre le Licenci�, le Conc�dant lui apportera son aide
+technique et juridique pour sa d�fense. Cette aide technique et
+juridique est d�termin�e au cas par cas entre le Conc�dant concern� et
+le Licenci� dans le cadre d'un protocole d'accord. Le Conc�dant d�gage
+toute responsabilit� quant � l'utilisation de la d�nomination du
+Logiciel par le Licenci�. Aucune garantie n'est apport�e quant �
+l'existence de droits ant�rieurs sur le nom du Logiciel et sur
+l'existence d'une marque.
+
+
+    Article 10 - RESILIATION
+
+10.1 En cas de manquement par le Licenci� aux obligations mises � sa
+charge par le Contrat, le Conc�dant pourra r�silier de plein droit le
+Contrat trente (30) jours apr�s notification adress�e au Licenci� et
+rest�e sans effet.
+
+10.2 Le Licenci� dont le Contrat est r�sili� n'est plus autoris� �
+utiliser, modifier ou distribuer le Logiciel. Cependant, toutes les
+licences qu'il aura conc�d�es ant�rieurement � la r�siliation du Contrat
+resteront valides sous r�serve qu'elles aient �t� effectu�es en
+conformit� avec le Contrat.
+
+
+    Article 11 - DISPOSITIONS DIVERSES
+
+
+      11.1 CAUSE EXTERIEURE
+
+Aucune des Parties ne sera responsable d'un retard ou d'une d�faillance
+d'ex�cution du Contrat qui serait d� � un cas de force majeure, un cas
+fortuit ou une cause ext�rieure, telle que, notamment, le mauvais
+fonctionnement ou les interruptions du r�seau �lectrique ou de
+t�l�communication, la paralysie du r�seau li�e � une attaque
+informatique, l'intervention des autorit�s gouvernementales, les
+catastrophes naturelles, les d�g�ts des eaux, les tremblements de terre,
+le feu, les explosions, les gr�ves et les conflits sociaux, l'�tat de
+guerre...
+
+11.2 Le fait, par l'une ou l'autre des Parties, d'omettre en une ou
+plusieurs occasions de se pr�valoir d'une ou plusieurs dispositions du
+Contrat, ne pourra en aucun cas impliquer renonciation par la Partie
+int�ress�e � s'en pr�valoir ult�rieurement.
+
+11.3 Le Contrat annule et remplace toute convention ant�rieure, �crite
+ou orale, entre les Parties sur le m�me objet et constitue l'accord
+entier entre les Parties sur cet objet. Aucune addition ou modification
+aux termes du Contrat n'aura d'effet � l'�gard des Parties � moins
+d'�tre faite par �crit et sign�e par leurs repr�sentants d�ment habilit�s.
+
+11.4 Dans l'hypoth�se o� une ou plusieurs des dispositions du Contrat
+s'av�rerait contraire � une loi ou � un texte applicable, existants ou
+futurs, cette loi ou ce texte pr�vaudrait, et les Parties feraient les
+amendements n�cessaires pour se conformer � cette loi ou � ce texte.
+Toutes les autres dispositions resteront en vigueur. De m�me, la
+nullit�, pour quelque raison que ce soit, d'une des dispositions du
+Contrat ne saurait entra�ner la nullit� de l'ensemble du Contrat.
+
+
+      11.5 LANGUE
+
+Le Contrat est r�dig� en langue fran�aise et en langue anglaise, ces
+deux versions faisant �galement foi.
+
+
+    Article 12 - NOUVELLES VERSIONS DU CONTRAT
+
+12.1 Toute personne est autoris�e � copier et distribuer des copies de
+ce Contrat.
+
+12.2 Afin d'en pr�server la coh�rence, le texte du Contrat est prot�g�
+et ne peut �tre modifi� que par les auteurs de la licence, lesquels se
+r�servent le droit de publier p�riodiquement des mises � jour ou de
+nouvelles versions du Contrat, qui poss�deront chacune un num�ro
+distinct. Ces versions ult�rieures seront susceptibles de prendre en
+compte de nouvelles probl�matiques rencontr�es par les logiciels libres.
+
+12.3 Tout Logiciel diffus� sous une version donn�e du Contrat ne pourra
+faire l'objet d'une diffusion ult�rieure que sous la m�me version du
+Contrat ou une version post�rieure.
+
+
+    Article 13 - LOI APPLICABLE ET COMPETENCE TERRITORIALE
+
+13.1 Le Contrat est r�gi par la loi fran�aise. Les Parties conviennent
+de tenter de r�gler � l'amiable les diff�rends ou litiges qui
+viendraient � se produire par suite ou � l'occasion du Contrat.
+
+13.2 A d�faut d'accord amiable dans un d�lai de deux (2) mois � compter
+de leur survenance et sauf situation relevant d'une proc�dure d'urgence,
+les diff�rends ou litiges seront port�s par la Partie la plus diligente
+devant les Tribunaux comp�tents de Paris.
+
+
+Version 1.0 du 2006-09-05.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..3ad4dea
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,515 @@
+
+CeCILL-B FREE SOFTWARE LICENSE AGREEMENT
+
+
+    Notice
+
+This Agreement is a Free Software license agreement that is the result
+of discussions between its authors in order to ensure compliance with
+the two main principles guiding its drafting:
+
+    * firstly, compliance with the principles governing the distribution
+      of Free Software: access to source code, broad rights granted to
+      users,
+    * secondly, the election of a governing law, French law, with which
+      it is conformant, both as regards the law of torts and
+      intellectual property law, and the protection that it offers to
+      both authors and holders of the economic rights over software.
+
+The authors of the CeCILL-B (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre])
+license are: 
+
+Commissariat � l'Energie Atomique - CEA, a public scientific, technical
+and industrial research establishment, having its principal place of
+business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France.
+
+Centre National de la Recherche Scientifique - CNRS, a public scientific
+and technological establishment, having its principal place of business
+at 3 rue Michel-Ange, 75794 Paris cedex 16, France.
+
+Institut National de Recherche en Informatique et en Automatique -
+INRIA, a public scientific and technological establishment, having its
+principal place of business at Domaine de Voluceau, Rocquencourt, BP
+105, 78153 Le Chesnay cedex, France.
+
+
+    Preamble
+
+This Agreement is an open source software license intended to give users
+significant freedom to modify and redistribute the software licensed
+hereunder.
+
+The exercising of this freedom is conditional upon a strong obligation
+of giving credits for everybody that distributes a software
+incorporating a software ruled by the current license so as all
+contributions to be properly identified and acknowledged.
+
+In consideration of access to the source code and the rights to copy,
+modify and redistribute granted by the license, users are provided only
+with a limited warranty and the software's author, the holder of the
+economic rights, and the successive licensors only have limited liability.
+
+In this respect, the risks associated with loading, using, modifying
+and/or developing or reproducing the software by the user are brought to
+the user's attention, given its Free Software status, which may make it
+complicated to use, with the result that its use is reserved for
+developers and experienced professionals having in-depth computer
+knowledge. Users are therefore encouraged to load and test the
+suitability of the software as regards their requirements in conditions
+enabling the security of their systems and/or data to be ensured and,
+more generally, to use and operate it in the same conditions of
+security. This Agreement may be freely reproduced and published,
+provided it is not altered, and that no provisions are either added or
+removed herefrom.
+
+This Agreement may apply to any or all software for which the holder of
+the economic rights decides to submit the use thereof to its provisions.
+
+
+    Article 1 - DEFINITIONS
+
+For the purpose of this Agreement, when the following expressions
+commence with a capital letter, they shall have the following meaning:
+
+Agreement: means this license agreement, and its possible subsequent
+versions and annexes.
+
+Software: means the software in its Object Code and/or Source Code form
+and, where applicable, its documentation, "as is" when the Licensee
+accepts the Agreement.
+
+Initial Software: means the Software in its Source Code and possibly its
+Object Code form and, where applicable, its documentation, "as is" when
+it is first distributed under the terms and conditions of the Agreement.
+
+Modified Software: means the Software modified by at least one
+Contribution.
+
+Source Code: means all the Software's instructions and program lines to
+which access is required so as to modify the Software.
+
+Object Code: means the binary files originating from the compilation of
+the Source Code.
+
+Holder: means the holder(s) of the economic rights over the Initial
+Software.
+
+Licensee: means the Software user(s) having accepted the Agreement.
+
+Contributor: means a Licensee having made at least one Contribution.
+
+Licensor: means the Holder, or any other individual or legal entity, who
+distributes the Software under the Agreement.
+
+Contribution: means any or all modifications, corrections, translations,
+adaptations and/or new functions integrated into the Software by any or
+all Contributors, as well as any or all Internal Modules.
+
+Module: means a set of sources files including their documentation that
+enables supplementary functions or services in addition to those offered
+by the Software.
+
+External Module: means any or all Modules, not derived from the
+Software, so that this Module and the Software run in separate address
+spaces, with one calling the other when they are run.
+
+Internal Module: means any or all Module, connected to the Software so
+that they both execute in the same address space.
+
+Parties: mean both the Licensee and the Licensor.
+
+These expressions may be used both in singular and plural form.
+
+
+    Article 2 - PURPOSE
+
+The purpose of the Agreement is the grant by the Licensor to the
+Licensee of a non-exclusive, transferable and worldwide license for the
+Software as set forth in Article 5 hereinafter for the whole term of the
+protection granted by the rights over said Software.
+
+
+    Article 3 - ACCEPTANCE
+
+3.1 The Licensee shall be deemed as having accepted the terms and
+conditions of this Agreement upon the occurrence of the first of the
+following events:
+
+    * (i) loading the Software by any or all means, notably, by
+      downloading from a remote server, or by loading from a physical
+      medium;
+    * (ii) the first time the Licensee exercises any of the rights
+      granted hereunder.
+
+3.2 One copy of the Agreement, containing a notice relating to the
+characteristics of the Software, to the limited warranty, and to the
+fact that its use is restricted to experienced users has been provided
+to the Licensee prior to its acceptance as set forth in Article 3.1
+hereinabove, and the Licensee hereby acknowledges that it has read and
+understood it.
+
+
+    Article 4 - EFFECTIVE DATE AND TERM
+
+
+      4.1 EFFECTIVE DATE
+
+The Agreement shall become effective on the date when it is accepted by
+the Licensee as set forth in Article 3.1.
+
+
+      4.2 TERM
+
+The Agreement shall remain in force for the entire legal term of
+protection of the economic rights over the Software.
+
+
+    Article 5 - SCOPE OF RIGHTS GRANTED
+
+The Licensor hereby grants to the Licensee, who accepts, the following
+rights over the Software for any or all use, and for the term of the
+Agreement, on the basis of the terms and conditions set forth hereinafter.
+
+Besides, if the Licensor owns or comes to own one or more patents
+protecting all or part of the functions of the Software or of its
+components, the Licensor undertakes not to enforce the rights granted by
+these patents against successive Licensees using, exploiting or
+modifying the Software. If these patents are transferred, the Licensor
+undertakes to have the transferees subscribe to the obligations set
+forth in this paragraph.
+
+
+      5.1 RIGHT OF USE
+
+The Licensee is authorized to use the Software, without any limitation
+as to its fields of application, with it being hereinafter specified
+that this comprises:
+
+   1. permanent or temporary reproduction of all or part of the Software
+      by any or all means and in any or all form.
+
+   2. loading, displaying, running, or storing the Software on any or
+      all medium.
+
+   3. entitlement to observe, study or test its operation so as to
+      determine the ideas and principles behind any or all constituent
+      elements of said Software. This shall apply when the Licensee
+      carries out any or all loading, displaying, running, transmission
+      or storage operation as regards the Software, that it is entitled
+      to carry out hereunder.
+
+
+      5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS
+
+The right to make Contributions includes the right to translate, adapt,
+arrange, or make any or all modifications to the Software, and the right
+to reproduce the resulting software.
+
+The Licensee is authorized to make any or all Contributions to the
+Software provided that it includes an explicit notice that it is the
+author of said Contribution and indicates the date of the creation thereof.
+
+
+      5.3 RIGHT OF DISTRIBUTION
+
+In particular, the right of distribution includes the right to publish,
+transmit and communicate the Software to the general public on any or
+all medium, and by any or all means, and the right to market, either in
+consideration of a fee, or free of charge, one or more copies of the
+Software by any means.
+
+The Licensee is further authorized to distribute copies of the modified
+or unmodified Software to third parties according to the terms and
+conditions set forth hereinafter.
+
+
+        5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION
+
+The Licensee is authorized to distribute true copies of the Software in
+Source Code or Object Code form, provided that said distribution
+complies with all the provisions of the Agreement and is accompanied by:
+
+   1. a copy of the Agreement,
+
+   2. a notice relating to the limitation of both the Licensor's
+      warranty and liability as set forth in Articles 8 and 9,
+
+and that, in the event that only the Object Code of the Software is
+redistributed, the Licensee allows effective access to the full Source
+Code of the Software at a minimum during the entire period of its
+distribution of the Software, it being understood that the additional
+cost of acquiring the Source Code shall not exceed the cost of
+transferring the data.
+
+
+        5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE
+
+If the Licensee makes any Contribution to the Software, the resulting
+Modified Software may be distributed under a license agreement other
+than this Agreement subject to compliance with the provisions of Article
+5.3.4.
+
+
+        5.3.3 DISTRIBUTION OF EXTERNAL MODULES
+
+When the Licensee has developed an External Module, the terms and
+conditions of this Agreement do not apply to said External Module, that
+may be distributed under a separate license agreement.
+
+
+        5.3.4 CREDITS
+
+Any Licensee who may distribute a Modified Software hereby expressly
+agrees to:
+
+   1. indicate in the related documentation that it is based on the
+      Software licensed hereunder, and reproduce the intellectual
+      property notice for the Software,
+
+   2. ensure that written indications of the Software intended use,
+      intellectual property notice and license hereunder are included in
+      easily accessible format from the Modified Software interface,
+
+   3. mention, on a freely accessible website describing the Modified
+      Software, at least throughout the distribution term thereof, that
+      it is based on the Software licensed hereunder, and reproduce the
+      Software intellectual property notice,
+
+   4. where it is distributed to a third party that may distribute a
+      Modified Software without having to make its source code
+      available, make its best efforts to ensure that said third party
+      agrees to comply with the obligations set forth in this Article .
+
+If the Software, whether or not modified, is distributed with an
+External Module designed for use in connection with the Software, the
+Licensee shall submit said External Module to the foregoing obligations.
+
+
+        5.3.5 COMPATIBILITY WITH THE CeCILL AND CeCILL-C LICENSES
+
+Where a Modified Software contains a Contribution subject to the CeCILL
+license, the provisions set forth in Article 5.3.4 shall be optional.
+
+A Modified Software may be distributed under the CeCILL-C license. In
+such a case the provisions set forth in Article 5.3.4 shall be optional.
+
+
+    Article 6 - INTELLECTUAL PROPERTY
+
+
+      6.1 OVER THE INITIAL SOFTWARE
+
+The Holder owns the economic rights over the Initial Software. Any or
+all use of the Initial Software is subject to compliance with the terms
+and conditions under which the Holder has elected to distribute its work
+and no one shall be entitled to modify the terms and conditions for the
+distribution of said Initial Software.
+
+The Holder undertakes that the Initial Software will remain ruled at
+least by this Agreement, for the duration set forth in Article 4.2.
+
+
+      6.2 OVER THE CONTRIBUTIONS
+
+The Licensee who develops a Contribution is the owner of the
+intellectual property rights over this Contribution as defined by
+applicable law.
+
+
+      6.3 OVER THE EXTERNAL MODULES
+
+The Licensee who develops an External Module is the owner of the
+intellectual property rights over this External Module as defined by
+applicable law and is free to choose the type of agreement that shall
+govern its distribution.
+
+
+      6.4 JOINT PROVISIONS
+
+The Licensee expressly undertakes:
+
+   1. not to remove, or modify, in any manner, the intellectual property
+      notices attached to the Software;
+
+   2. to reproduce said notices, in an identical manner, in the copies
+      of the Software modified or not.
+
+The Licensee undertakes not to directly or indirectly infringe the
+intellectual property rights of the Holder and/or Contributors on the
+Software and to take, where applicable, vis-�-vis its staff, any and all
+measures required to ensure respect of said intellectual property rights
+of the Holder and/or Contributors.
+
+
+    Article 7 - RELATED SERVICES
+
+7.1 Under no circumstances shall the Agreement oblige the Licensor to
+provide technical assistance or maintenance services for the Software.
+
+However, the Licensor is entitled to offer this type of services. The
+terms and conditions of such technical assistance, and/or such
+maintenance, shall be set forth in a separate instrument. Only the
+Licensor offering said maintenance and/or technical assistance services
+shall incur liability therefor.
+
+7.2 Similarly, any Licensor is entitled to offer to its licensees, under
+its sole responsibility, a warranty, that shall only be binding upon
+itself, for the redistribution of the Software and/or the Modified
+Software, under terms and conditions that it is free to decide. Said
+warranty, and the financial terms and conditions of its application,
+shall be subject of a separate instrument executed between the Licensor
+and the Licensee.
+
+
+    Article 8 - LIABILITY
+
+8.1 Subject to the provisions of Article 8.2, the Licensee shall be
+entitled to claim compensation for any direct loss it may have suffered
+from the Software as a result of a fault on the part of the relevant
+Licensor, subject to providing evidence thereof.
+
+8.2 The Licensor's liability is limited to the commitments made under
+this Agreement and shall not be incurred as a result of in particular:
+(i) loss due the Licensee's total or partial failure to fulfill its
+obligations, (ii) direct or consequential loss that is suffered by the
+Licensee due to the use or performance of the Software, and (iii) more
+generally, any consequential loss. In particular the Parties expressly
+agree that any or all pecuniary or business loss (i.e. loss of data,
+loss of profits, operating loss, loss of customers or orders,
+opportunity cost, any disturbance to business activities) or any or all
+legal proceedings instituted against the Licensee by a third party,
+shall constitute consequential loss and shall not provide entitlement to
+any or all compensation from the Licensor.
+
+
+    Article 9 - WARRANTY
+
+9.1 The Licensee acknowledges that the scientific and technical
+state-of-the-art when the Software was distributed did not enable all
+possible uses to be tested and verified, nor for the presence of
+possible defects to be detected. In this respect, the Licensee's
+attention has been drawn to the risks associated with loading, using,
+modifying and/or developing and reproducing the Software which are
+reserved for experienced users.
+
+The Licensee shall be responsible for verifying, by any or all means,
+the suitability of the product for its requirements, its good working
+order, and for ensuring that it shall not cause damage to either persons
+or properties.
+
+9.2 The Licensor hereby represents, in good faith, that it is entitled
+to grant all the rights over the Software (including in particular the
+rights set forth in Article 5).
+
+9.3 The Licensee acknowledges that the Software is supplied "as is" by
+the Licensor without any other express or tacit warranty, other than
+that provided for in Article 9.2 and, in particular, without any warranty 
+as to its commercial value, its secured, safe, innovative or relevant 
+nature.
+
+Specifically, the Licensor does not warrant that the Software is free
+from any error, that it will operate without interruption, that it will
+be compatible with the Licensee's own equipment and software
+configuration, nor that it will meet the Licensee's requirements.
+
+9.4 The Licensor does not either expressly or tacitly warrant that the
+Software does not infringe any third party intellectual property right
+relating to a patent, software or any other property right. Therefore,
+the Licensor disclaims any and all liability towards the Licensee
+arising out of any or all proceedings for infringement that may be
+instituted in respect of the use, modification and redistribution of the
+Software. Nevertheless, should such proceedings be instituted against
+the Licensee, the Licensor shall provide it with technical and legal
+assistance for its defense. Such technical and legal assistance shall be
+decided on a case-by-case basis between the relevant Licensor and the
+Licensee pursuant to a memorandum of understanding. The Licensor
+disclaims any and all liability as regards the Licensee's use of the
+name of the Software. No warranty is given as regards the existence of
+prior rights over the name of the Software or as regards the existence
+of a trademark.
+
+
+    Article 10 - TERMINATION
+
+10.1 In the event of a breach by the Licensee of its obligations
+hereunder, the Licensor may automatically terminate this Agreement
+thirty (30) days after notice has been sent to the Licensee and has
+remained ineffective.
+
+10.2 A Licensee whose Agreement is terminated shall no longer be
+authorized to use, modify or distribute the Software. However, any
+licenses that it may have granted prior to termination of the Agreement
+shall remain valid subject to their having been granted in compliance
+with the terms and conditions hereof.
+
+
+    Article 11 - MISCELLANEOUS
+
+
+      11.1 EXCUSABLE EVENTS
+
+Neither Party shall be liable for any or all delay, or failure to
+perform the Agreement, that may be attributable to an event of force
+majeure, an act of God or an outside cause, such as defective
+functioning or interruptions of the electricity or telecommunications
+networks, network paralysis following a virus attack, intervention by
+government authorities, natural disasters, water damage, earthquakes,
+fire, explosions, strikes and labor unrest, war, etc.
+
+11.2 Any failure by either Party, on one or more occasions, to invoke
+one or more of the provisions hereof, shall under no circumstances be
+interpreted as being a waiver by the interested Party of its right to
+invoke said provision(s) subsequently.
+
+11.3 The Agreement cancels and replaces any or all previous agreements,
+whether written or oral, between the Parties and having the same
+purpose, and constitutes the entirety of the agreement between said
+Parties concerning said purpose. No supplement or modification to the
+terms and conditions hereof shall be effective as between the Parties
+unless it is made in writing and signed by their duly authorized
+representatives.
+
+11.4 In the event that one or more of the provisions hereof were to
+conflict with a current or future applicable act or legislative text,
+said act or legislative text shall prevail, and the Parties shall make
+the necessary amendments so as to comply with said act or legislative
+text. All other provisions shall remain effective. Similarly, invalidity
+of a provision of the Agreement, for any reason whatsoever, shall not
+cause the Agreement as a whole to be invalid.
+
+
+      11.5 LANGUAGE
+
+The Agreement is drafted in both French and English and both versions
+are deemed authentic.
+
+
+    Article 12 - NEW VERSIONS OF THE AGREEMENT
+
+12.1 Any person is authorized to duplicate and distribute copies of this
+Agreement.
+
+12.2 So as to ensure coherence, the wording of this Agreement is
+protected and may only be modified by the authors of the License, who
+reserve the right to periodically publish updates or new versions of the
+Agreement, each with a separate number. These subsequent versions may
+address new issues encountered by Free Software.
+
+12.3 Any Software distributed under a given version of the Agreement may
+only be subsequently distributed under the same version of the Agreement
+or a subsequent version.
+
+
+    Article 13 - GOVERNING LAW AND JURISDICTION
+
+13.1 The Agreement is governed by French law. The Parties agree to
+endeavor to seek an amicable solution to any disagreements or disputes
+that may arise during the performance of the Agreement.
+
+13.2 Failing an amicable solution within two (2) months as from their
+occurrence, and unless emergency proceedings are necessary, the
+disagreements or disputes shall be referred to the Paris Courts having
+jurisdiction, by the more diligent Party.
+
+
+Version 1.0 dated 2006-09-05.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..b1e91db
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,3 @@
+include .coveragerc
+include hooks.py
+include README.rst
diff --git a/PKG-INFO b/PKG-INFO
deleted file mode 100644
index 1ef7c7a..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,121 +0,0 @@
-Metadata-Version: 1.1
-Name: pyoperators
-Version: 0.12.13
-Summary: Operators and solvers for high-performance computing.
-Home-page: http://pchanial.github.com/pyoperators
-Author: Pierre Chanial
-Author-email: pierre.chanial at gmail.com
-License: CeCILL-B
-Description: ===========
-        PyOperators
-        ===========
-        
-        The PyOperators package defines operators and solvers for high-performance computing. These operators are multi-dimensional functions with optimised and controlled memory management. If linear, they behave like matrices with a sparse storage footprint.
-        
-        Getting started
-        ===============
-        
-        To define an operator, one needs to define a direct function
-        which will replace the usual matrix-vector operation:
-        
-        >>> def f(x, out):
-        ...     out[...] = 2 * x
-        
-        Then, you can instantiate an ``Operator``:
-        
-        >>> A = pyoperators.Operator(direct=f, flags='symmetric')
-        
-        An alternative way to define an operator is to define a subclass:
-        
-        >>> from pyoperators import flags, Operator
-        ... @flags.symmetric
-        ... class MyOperator(Operator):
-        ...     def direct(x, out):
-        ...         out[...] = 2 * x
-        ...
-        ... A = MyOperator()
-        
-        This operator does not have an explicit shape, it can handle inputs of any shape:
-        
-        >>> A(np.ones(5))
-        array([ 2.,  2.,  2.,  2.,  2.])
-        >>> A(np.ones((2,3)))
-        array([[ 2.,  2.,  2.],
-               [ 2.,  2.,  2.]])
-        
-        By setting the ``symmetric`` flag, we ensure that A's transpose is A:
-        
-        >>> A.T is A
-        True
-        
-        For non-explicit shape operators, we get the corresponding dense matrix by specifying the input shape:
-        
-        >>> A.todense(shapein=2)
-        array([[2, 0],
-               [0, 2]])
-        
-        Operators do not have to be linear. Many operators are already `predefined <http://pchanial.github.io/pyoperators/2000/doc-operators/#list>`_, such as the ``IdentityOperator``, the ``DiagonalOperator`` or the nonlinear ``ClipOperator``.
-        
-        The previous ``A`` matrix could be defined more easily like this:
-        
-        >>> from pyoperators import I
-        >>> A = 2 * I
-        
-        where ``I`` is the identity operator with no explicit shape.
-        
-        Operators can be combined together by addition, element-wise multiplication or composition. Note that the operator ``*`` stands for matrix multiplication if the two operators are linear, or for element-wise multiplication otherwise:
-        
-        >>> from pyoperators import I, DiagonalOperator
-        >>> B = 2 * I + DiagonalOperator(range(3))
-        >>> B.todense()
-        array([[2, 0, 0],
-               [0, 3, 0],
-               [0, 0, 4]])
-        
-        Algebraic rules can easily be attached to operators. They are used to simplify expressions to speed up their execution. The ``B`` Operator has been reduced to:
-        
-        >>> B
-        DiagonalOperator(array([2, ..., 4], dtype=int64), broadcast='disabled', dtype=int64, shapein=3, shapeout=3)
-        
-        Many simplifications are available. For instance:
-        
-        >>> from pyoperators import Operator
-        >>> C = Operator(flags='idempotent,linear')
-        >>> C * C is C
-        True
-        >>> D = Operator(flags='involutary')
-        >>> D(D)
-        IdentityOperator()
-        
-        
-        Requirements
-        ============
-        
-        List of requirements:
-        
-        - python 2.6
-        - numpy >= 1.6
-        - scipy >= 0.9
-        
-        Optional requirements:
-        
-        - numexpr (>= 2.0 is better)
-        - PyWavelets : wavelet transforms
-        
-Keywords: scientific computing
-Platform: MacOS X
-Platform: Linux
-Platform: Solaris
-Platform: Unix
-Platform: Windows
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2 :: Only
-Classifier: Programming Language :: C
-Classifier: Programming Language :: Cython
-Classifier: Development Status :: 4 - Beta
-Classifier: Intended Audience :: Science/Research
-Classifier: Operating System :: OS Independent
-Classifier: Topic :: Scientific/Engineering
-Requires: numpy(>=1.6)
-Requires: scipy(>=0.9)
-Requires: pyfftw
diff --git a/README.rst b/README.rst
index 4b12ff1..1e225e6 100644
--- a/README.rst
+++ b/README.rst
@@ -4,6 +4,8 @@ PyOperators
 
 The PyOperators package defines operators and solvers for high-performance computing. These operators are multi-dimensional functions with optimised and controlled memory management. If linear, they behave like matrices with a sparse storage footprint.
 
+More documentation can be found here: http://pchanial.github.io/pyoperators.
+
 Getting started
 ===============
 
diff --git a/hooks.py b/hooks.py
index cc9ebde..31bcb6e 100644
--- a/hooks.py
+++ b/hooks.py
@@ -2,8 +2,9 @@
 The version number is obtained from git tags, branch and commit identifier.
 It has been designed for the following workflow:
 
-- git checkout master
-- modify, commit, commit
+- git init
+- create setup.py commit
+- more commit
 - set version 0.1 in setup.py -> 0.1.dev03
 - modify, commit              -> 0.1.dev04
 - git checkout -b v0.1        -> 0.1.dev04
@@ -17,18 +18,45 @@ It has been designed for the following workflow:
 - git checkout master         -> 0.1.dev04
 - set version=0.2 in setup.py -> 0.2.dev01
 - modify, commit              -> 0.2.dev02
+- git tag 0.2                 -> 0.2
+- set version=0.3 in setup.py -> 0.3.dev01
+
 
 When working on the master branch, the dev number is the number of commits
-since the last branch of name "v[0-9.]+"
+since the last release branch (by default of name "v[0-9.]+", but it is
+configurable) or the last tag.
 
 """
-import os
+
+# These variables can be changed by the hooks importer
+ABBREV = 5
+F2PY_TABLE = {'integer': {'int8': 'char',
+                          'int16': 'short',
+                          'int32': 'int',
+                          'int64': 'long_long'},
+              'real': {'real32': 'float',
+                       'real64': 'double'},
+              'complex': {'real32': 'complex_float',
+                          'real64': 'complex_double'}}
+REGEX_RELEASE = '^v(?P<name>[0-9.]+)$'
+try:
+    import os
+    from Cython.Build import cythonize
+    USE_CYTHON = bool(int(os.getenv('SETUPHOOKS_USE_CYTHON', '1') or '0'))
+except ImportError:
+    USE_CYTHON = False
+
+import numpy
 import re
+import shutil
 import sys
+from distutils.command.clean import clean
 from numpy.distutils.command.build import build
 from numpy.distutils.command.build_ext import build_ext
+from numpy.distutils.command.build_src import build_src
 from numpy.distutils.command.sdist import sdist
 from numpy.distutils.core import Command
+from numpy.distutils.misc_util import has_f_sources
 from subprocess import call, Popen, PIPE
 from warnings import filterwarnings
 
@@ -36,116 +64,286 @@ try:
     root = os.path.dirname(os.path.abspath(__file__))
 except NameError:
     root = os.path.dirname(os.path.abspath(sys.argv[0]))
-ABBREV = 5
-BRANCH_REGEX = '^refs/(heads|remotes/origin)/v[0-9.]+$'
+
+# monkey patch to allow pure and elemental routines in preprocessed
+# Fortran libraries
+numpy.distutils.from_template.routine_start_re = re.compile(
+    r'(\n|\A)((     (\$|\*))|)\s*((im)?pure\s+|elemental\s+)*(subroutine|funct'
+    r'ion)\b', re.I)
+numpy.distutils.from_template.function_start_re = re.compile(
+    r'\n     (\$|\*)\s*((im)?pure\s+|elemental\s+)*function\b', re.I)
+
+
+def get_cmdclass():
+
+    class BuildCommand(build):
+        def run(self):
+            _write_version(self.distribution.get_name(),
+                           self.distribution.get_version())
+            build.run(self)
+
+    class BuildExtCommand(build_ext):
+        def run(self):
+            has_fortran = False
+            has_cython = False
+            for ext in self.extensions:
+                has_fortran = has_fortran or has_f_sources(ext.sources)
+                for isource, source in enumerate(ext.sources):
+                    if source.endswith('.pyx'):
+                        if USE_CYTHON:
+                            has_cython = True
+                        else:
+                            ext.sources[isource] = source[:-3] + 'c'
+            if has_cython:
+                self.extensions = cythonize(self.extensions, force=True)
+            if has_fortran:
+                with open(os.path.join(root, '.f2py_f2cmap'), 'w') as f:
+                    f.write(repr(F2PY_TABLE))
+            build_ext.run(self)
+
+    class BuildSrcCommand(build_src):
+        def initialize_options(self):
+            build_src.initialize_options(self)
+            self.f2py_opts = '--quiet'
+
+    class SDistCommand(sdist):
+        def make_release_tree(self, base_dir, files):
+            _write_version(self.distribution.get_name(),
+                           self.distribution.get_version())
+            initfile = os.path.join(self.distribution.get_name(),
+                                    '__init__.py')
+            if initfile not in files:
+                files.append(initfile)
+            sdist.make_release_tree(self, base_dir, files)
+
+    class CleanCommand(clean):
+        def run(self):
+            clean.run(self)
+            try:
+                print(run_git('clean -fdX' + ('n' if self.dry_run else '')))
+                return
+            except RuntimeError:
+                pass
+
+            extensions = '.o', '.pyc', 'pyd', 'pyo', '.so'
+            for root_, dirs, files in os.walk(root):
+                for f in files:
+                    if os.path.splitext(f)[-1] in extensions:
+                        self.__delete(os.path.join(root_, f))
+                for d in dirs:
+                    if d in ('build', '__pycache__'):
+                        self.__delete(os.path.join(root_, d), dir=True)
+            files = (
+                'MANIFEST',
+                os.path.join(self.distribution.get_name(), '__init__.py'))
+            for f in files:
+                if os.path.exists(f):
+                    self.__delete(f)
+
+        def __delete(self, file_, dir=False):
+            msg = 'would remove' if self.dry_run else 'removing'
+            try:
+                if not self.dry_run:
+                    if dir:
+                        shutil.rmtree(file_)
+                    else:
+                        os.unlink(file_)
+            except OSError:
+                msg = 'problem removing'
+            print(msg + ' {!r}'.format(file_))
+
+    class CoverageCommand(Command):
+        description = "run the package coverage"
+        user_options = [('file=', 'f', 'restrict coverage to a specific file'),
+                        ('erase', None,
+                         'erase previously collected coverage before run'),
+                        ('html-dir=', None,
+                         'Produce HTML coverage information in dir')]
+
+        def run(self):
+            cmd = [sys.executable, '-mnose', '--with-coverage', '--cover-html',
+                   '--cover-package=' + self.distribution.get_name(),
+                   '--cover-html-dir=' + self.html_dir]
+            if self.erase:
+                cmd.append('--cover-erase')
+            call(cmd + [self.file])
+
+        def initialize_options(self):
+            self.file = 'test'
+            self.erase = 0
+            self.html_dir = 'htmlcov'
+
+        def finalize_options(self):
+            pass
+
+    class TestCommand(Command):
+        description = "run the test suite"
+        user_options = [('file=', 'f', 'restrict test to a specific file')]
+
+        def run(self):
+            call([sys.executable, '-mnose', self.file])
+
+        def initialize_options(self):
+            self.file = 'test'
+
+        def finalize_options(self):
+            pass
+
+    return {'build': BuildCommand,
+            'build_ext': BuildExtCommand,
+            'build_src': BuildSrcCommand,
+            'clean': CleanCommand,
+            'coverage': CoverageCommand,
+            'sdist': SDistCommand,
+            'test': TestCommand}
 
 
 def get_version(name, default):
-    version = get_version_git(default)
-    if version != '':
-        return version
-    return get_version_init_file(name) or default
-
-
-def get_version_git(default):
-    def run(cmd, cwd=root):
-        git = "git"
-        if sys.platform == "win32":
-            git = "git.cmd"
-        process = Popen([git] + cmd, cwd=cwd, stdout=PIPE, stderr=PIPE)
-        stdout, stderr = process.communicate()
+    return _get_version_git(default) or _get_version_init_file(name) or default
+
+
+def run_git(cmd, cwd=root):
+    git = 'git'
+    if sys.platform == 'win32':
+        git = 'git.cmd'
+    cmd = git + ' ' + cmd
+    process = Popen(cmd.split(), cwd=cwd, stdout=PIPE, stderr=PIPE)
+    stdout, stderr = process.communicate()
+    if process.returncode != 0:
         if stderr != '':
-            raise RuntimeError(stderr)
-        if process.returncode != 0:
-            raise RuntimeError('Error code: {0}.'.format(process.returncode))
-        return stdout.strip()
+            stderr = '\n' + stderr.decode('utf-8')
+        raise RuntimeError(
+            'Command failed (error {}): {}{}'.format(
+                process.returncode, cmd, stderr))
+    return stdout.strip().decode('utf-8')
+
+
+def _get_version_git(default):
+    INF = 2147483647
 
     def get_branches():
-        return run(['for-each-ref', '--sort=-committerdate', '--format=%(ref'
-                    'name)', 'refs/heads', 'refs/remotes/origin']).split('\n')
+        return run_git('for-each-ref --sort=-committerdate --format=%(refname)'
+                       ' refs/heads refs/remotes/origin').splitlines()
 
     def get_branch_name():
-        return run(['rev-parse', '--abbrev-ref', 'HEAD'])
+        branch = run_git('rev-parse --abbrev-ref HEAD')
+        if branch != 'HEAD':
+            return branch
+        branch = run_git('branch --no-color --contains HEAD').splitlines()
+        return branch[min(1, len(branch)-1)].strip()
 
     def get_description():
+        branch = get_branch_name()
         try:
-            description = run([
-                'describe', '--tags', '--abbrev={0}'.format(ABBREV)])
+            description = run_git('describe --abbrev={} --tags'.format(ABBREV))
         except RuntimeError:
-            description = run([
-                'describe', '--tags', '--abbrev={0}'.format(ABBREV),
-                '--always']).split('-')
-            return '', '', description[0], '-' + description[1]
+            description = run_git(
+                'describe --abbrev={} --always'.format(ABBREV))
+            regex = r"""^
+            (?P<commit>.*?)
+            (?P<dirty>(-dirty)?)
+            $"""
+            m = re.match(regex, description, re.VERBOSE)
+            commit, dirty = (m.group(_) for _ in 'commit,dirty'.split(','))
+            return branch, '', INF, commit, dirty
+
         regex = r"""^
         (?P<tag>.*?)
         (?:-
             (?P<rev>\d+)-g
             (?P<commit>[0-9a-f]{5,40})
         )?
+        (?P<dirty>(-dirty)?)
         $"""
         m = re.match(regex, description, re.VERBOSE)
-        tag, rev, commit = (m.group(_) for _ in 'tag,rev,commit'.split(','))
-        rev = int(rev)
-        return tag, rev, commit
+        tag, rev, commit, dirty = (m.group(_)
+                                   for _ in 'tag,rev,commit,dirty'.split(','))
+        if rev is None:
+            rev = 0
+            commit = ''
+        else:
+            rev = int(rev)
+        return branch, tag, rev, commit, dirty
 
     def get_rev_since_branch(branch):
-        common = run(['merge-base', 'HEAD', branch])
-        return int(run(['rev-list', '--count', 'HEAD', '^' + common]))
+        try:
+            # get best common ancestor
+            common = run_git('merge-base HEAD ' + branch)
+        except RuntimeError:
+            return INF  # no common ancestor, the branch is dangling
+        return int(run_git('rev-list --count HEAD ^' + common))
 
-    def get_dirty():
-        return '-dirty' if run(['diff-index', 'HEAD']) else ''
+    def get_rev_since_any_branch():
+        if REGEX_RELEASE.startswith('^'):
+            regex = REGEX_RELEASE[1:]
+        else:
+            regex = '.*' + REGEX_RELEASE
+        regex = '^refs/(heads|remotes/origin)/' + regex
 
-    def get_master_rev(default):
         branches = get_branches()
         for branch in branches:
             # filter branches according to BRANCH_REGEX
-            if not re.match(BRANCH_REGEX, branch):
+            if not re.match(regex, branch):
                 continue
             rev = get_rev_since_branch(branch)
             if rev > 0:
                 return rev
-        return int(run(['rev-list', '--count', 'HEAD']))
+        # no branch has been created from an ancestor
+        return INF
 
     try:
-        run(['rev-parse', '--is-inside-work-tree'])
+        run_git('rev-parse --is-inside-work-tree')
     except (OSError, RuntimeError):
         return ''
 
-    dirty = get_dirty()
+    branch, tag, rev_tag, commit, dirty = get_description()
 
-    # check if HEAD is tagged
-    try:
-        return run(['describe', '--tags', '--candidates=0']) + dirty
-    except RuntimeError:
-        pass
-
-    # if the current branch is master, look up the last release branch
-    # to get the dev number
-    branch = get_branch_name()
-    if get_branch_name() == 'master':
-        rev = get_master_rev(default)
-        commit = run(['rev-parse', '--short={}'.format(ABBREV), 'HEAD'])
-        if default != '':
-            return '{}.dev{:02}-g{}{}'.format(default, rev, commit, dirty)
-        return str(rev) + dirty
-
-    isrelease = re.match('^v[0-9.]+$', branch) is not None
-    rev_master = get_rev_since_branch('master')
-    tag, rev_tag, commit = get_description()
-    if isrelease:
-        version = tag
+    # check if the commit is tagged
+    if rev_tag == 0:
+        return tag + dirty
+
+    # if the current branch is master, look up the closest tag or the closest
+    # release branch rev to get the dev number otherwise, look up the closest
+    # tag or the closest master rev.
+    suffix = 'dev'
+    if branch == 'master':
+        rev_branch = get_rev_since_any_branch()
+        name = default
+        is_branch_release = False
     else:
-        version = branch
-    if rev_tag > 0:
-        if rev_master < rev_tag:
-            version += '.pre{:02}'.format(rev_master)
-        else:
-            version += '.post{:02}'.format(rev_tag)
-        version += '-g' + commit
-    return version + dirty
+        rev_branch = get_rev_since_branch('master')
+        name = branch
+        m = re.match(REGEX_RELEASE, branch)
+        is_branch_release = m is not None
+        if is_branch_release:
+            try:
+                name = m.group('name')
+            except IndexError:
+                pass
+        elif rev_tag == rev_branch:
+            tag = branch
+
+    if rev_branch == rev_tag == INF:
+        # no branch and no tag from ancestors, counting from root
+        rev = int(run_git('rev-list --count HEAD'))
+        if branch != 'master':
+            suffix = 'rev'
+    elif rev_tag <= rev_branch:
+        rev = rev_tag
+        if branch != 'master':
+            name = tag
+        if is_branch_release:
+            suffix = 'post'
+    else:
+        rev = rev_branch
+        if is_branch_release:
+            suffix = 'pre'
+    if name != '':
+        name += '.'
+    return '{}{}{:02}-g{}{}'.format(name, suffix, rev, commit, dirty)
 
 
-def get_version_init_file(name):
+def _get_version_init_file(name):
     try:
         f = open(os.path.join(name, '__init__.py')).read()
     except IOError:
@@ -156,7 +354,7 @@ def get_version_init_file(name):
     return m.groups()[0]
 
 
-def write_version(name, version):
+def _write_version(name, version):
     try:
         init = open(os.path.join(root, name, '__init__.py.in')).readlines()
     except IOError:
@@ -165,67 +363,4 @@ def write_version(name, version):
     open(os.path.join(root, name, '__init__.py'), 'w').writelines(init)
 
 
-class BuildCommand(build):
-    def run(self):
-        write_version(self.distribution.get_name(),
-                      self.distribution.get_version())
-        build.run(self)
-
-
-class SDistCommand(sdist):
-    def make_release_tree(self, base_dir, files):
-        write_version(self.distribution.get_name(),
-                      self.distribution.get_version())
-        initfile = os.path.join(self.distribution.get_name(), '__init__.py')
-        if initfile not in files:
-            files.append(initfile)
-        sdist.make_release_tree(self, base_dir, files)
-
-
-class CoverageCommand(Command):
-    description = "run the package coverage"
-    user_options = [('file=', 'f', 'restrict coverage to a specific file'),
-                    ('erase', None,
-                     'erase previously collected coverage before run'),
-                    ('html-dir=', None,
-                     'Produce HTML coverage information in dir')]
-
-    def run(self):
-        cmd = ['nosetests', '--with-coverage', '--cover-html',
-               '--cover-package=' + self.distribution.get_name(),
-               '--cover-html-dir=' + self.html_dir]
-        if self.erase:
-            cmd.append('--cover-erase')
-        call(cmd + [self.file])
-
-    def initialize_options(self):
-        self.file = 'test'
-        self.erase = 0
-        self.html_dir = 'htmlcov'
-
-    def finalize_options(self):
-        pass
-
-
-class TestCommand(Command):
-    description = "run the test suite"
-    user_options = [('file=', 'f', 'restrict test to a specific file')]
-
-    def run(self):
-        call(['nosetests', self.file])
-
-    def initialize_options(self):
-        self.file = 'test'
-
-    def finalize_options(self):
-        pass
-
-
-def get_cmdclass():
-    return {'build': BuildCommand,
-            'build_ext': build_ext,
-            'coverage': CoverageCommand,
-            'sdist': SDistCommand,
-            'test': TestCommand}
-
 filterwarnings('ignore', "Unknown distribution option: 'install_requires'")
diff --git a/pyoperators/__init__.py b/pyoperators/__init__.py.in
similarity index 94%
rename from pyoperators/__init__.py
rename to pyoperators/__init__.py.in
index 4f8c422..4765304 100644
--- a/pyoperators/__init__.py
+++ b/pyoperators/__init__.py.in
@@ -29,15 +29,16 @@ try:
 except(ImportError):
     pass
 
+import sys
 import types
 __all__ = [f for f in dir() if f[0] != '_' and not isinstance(eval(f),
            types.ModuleType)]
 
-del f  #XXX not necessary with Python3
+if sys.version_info.major == 2:
+    del f
+del sys
 del types
 
 I = IdentityOperator()
 O = ZeroOperator()
 X = Variable('X')
-
-__version__ = '0.12.13'
diff --git a/pyoperators/config.py b/pyoperators/config.py
index b7efa01..2f38535 100644
--- a/pyoperators/config.py
+++ b/pyoperators/config.py
@@ -2,18 +2,24 @@ import os
 import site
 from .warnings import warn, PyOperatorsWarning
 
-def getenv(key):
+
+def getenv(key, default, cls):
     val = os.getenv(key, '').strip()
     if len(val) == 0:
-        return False
+        return cls(default)
     try:
-        val = int(val)
+        if cls is bool:
+            val = int(val)
+        val = cls(val)
     except ValueError:
-        warn("Invalid environment variable {0}='{1}'".format(key, val))
-        return False
-    return bool(val)
+        warn("Invalid environment variable {0}='{1}'".format(key, val),
+             PyOperatorsWarning)
+        return cls(default)
+    return val
+
 
-LOCAL_PATH = os.getenv('PYOPERATORSPATH')
+# PyOperators local path, used for example to store the FFTW wisdom files
+LOCAL_PATH = os.getenv('PYOPERATORS_PATH')
 if LOCAL_PATH is None:
     LOCAL_PATH = os.path.join(site.USER_BASE, 'share', 'pyoperators')
 if not os.path.exists(LOCAL_PATH):
@@ -26,7 +32,17 @@ elif not os.access(LOCAL_PATH, os.W_OK):
     warn("User path '{0}' is not writable.".format(LOCAL_PATH),
          PyOperatorsWarning)
 
-PYOPERATORS_NO_MPI = getenv('PYOPERATORS_NO_MPI')
-PYOPERATORS_VERBOSE = getenv('PYOPERATORS_VERBOSE')
+# force garbage collection when deleted operators' nbytes exceed this
+# threshold.
+GC_NBYTES_THRESHOLD = getenv('PYOPERATORS_GC_NBYTES_THRESHOLD', 1e8, float)
+
+MEMORY_ALIGNMENT = getenv('PYOPERATORS_MEMORY_ALIGNMENT', 32, int)
+
+# We allow reuse of pool variables only if they do not exceed 20% of
+# the requested size
+MEMORY_TOLERANCE = getenv('PYOPERATORS_MEMORY_TOLERANCE', 1.2, float)
+
+NO_MPI = getenv('PYOPERATORS_NO_MPI', False, bool)
+VERBOSE = getenv('PYOPERATORS_VERBOSE', False, bool)
 
-del os, site, PyOperatorsWarning, warn, getenv
+#del os, site, PyOperatorsWarning, warn, getenv
diff --git a/pyoperators/core.py b/pyoperators/core.py
index 36f20d0..22ffa17 100644
--- a/pyoperators/core.py
+++ b/pyoperators/core.py
@@ -6,25 +6,27 @@ Operator docstring for more information.
 """
 
 from __future__ import absolute_import, division, print_function
-import copy
 import inspect
 import numpy as np
 import operator
 import pyoperators as po
 import scipy.sparse as sp
+import sys
 import types
 from collections import MutableMapping, MutableSequence, MutableSet
-from itertools import groupby, izip
+from itertools import groupby
+from . import config
 from .flags import (
     Flags, idempotent, inplace, involutary, linear, real,
     square, symmetric, update_output)
 from .memory import (
-    empty, garbage_collect, iscompatible, zeros, MemoryPool, MEMORY_ALIGNMENT)
+    empty, garbage_collect, iscompatible, zeros, MemoryPool)
 from .utils import (
     all_eq, first_is_not, inspect_special_values, isalias, isclassattr,
     isscalarlike, merge_none, ndarraywrap, operation_assignment, product,
     renumerate, strenum, strplural, strshape, Timer, tointtuple)
 from .utils.mpi import MPI
+import collections
 
 __all__ = [
     'Operator',
@@ -246,7 +248,7 @@ class Operator(object):
         of the new class.
         """
         if None not in (self.classout, cls) and self.classout is not cls:
-            for a in attr.keys():
+            for a in list(attr.keys()):
                 if isclassattr(a, cls) and not isclassattr(a, self.classout):
                     del attr[a]
         if 'shape_global' in attr:
@@ -428,14 +430,14 @@ class Operator(object):
         if not inplace or not self.flags.inplace:
             v = zeros(n, dtype)
             if not self.flags.aligned_output:
-                for i in xrange(n):
+                for i in range(n):
                     v[i] = 1
                     o = d[i, :].reshape(shapeout)
                     self.direct(v.reshape(shapein), o)
                     v[i] = 0
             else:
                 o = empty(shapeout, dtype)
-                for i in xrange(n):
+                for i in range(n):
                     v[i] = 1
                     self.direct(v.reshape(shapein), o)
                     d[i, :] = o.ravel()
@@ -446,7 +448,7 @@ class Operator(object):
         u = empty(max(m, n), dtype)
         v = u[:n]
         w = u[:m]
-        for i in xrange(n):
+        for i in range(n):
             v[:] = 0
             v[i] = 1
             self.direct(v.reshape(shapein), w.reshape(shapeout))
@@ -625,9 +627,19 @@ class Operator(object):
             self._generate_associated_operators()
         return self._I
 
-    def copy(self):
-        """ Return a copy of the operator. """
-        return copy.copy(self)
+    def copy(self, target=None):
+        """ Return a shallow copy of the operator. """
+        class Target(object):
+            pass
+        if target is None:
+            target = Target()
+        target.__class__ = self.__class__
+        for k, v in self.__dict__.items():
+            if isinstance(v, types.MethodType) and v.__self__ is self:
+                target.__dict__[k] = types.MethodType(v.__func__, target)
+            else:
+                target.__dict__[k] = v
+        return target
 
     @staticmethod
     def _find_common_type(dtypes):
@@ -994,23 +1006,24 @@ class Operator(object):
         if validateout is not None:
             self.validateout = validateout
 
-        shapein = tointtuple(shapein)
-        shapeout = tointtuple(shapeout)
-        self.shapein = shapein
-        self.shapeout = shapeout
-        if shapein is not None:
-            shapeout = tointtuple(self.reshapein(shapein))
+        self.shapein = tointtuple(shapein)
+        self.shapeout = tointtuple(shapeout)
+        if self.shapein is not None:
+            shapeout = tointtuple(self.reshapein(self.shapein))
             if self.shapeout is None:
                 self.shapeout = shapeout
-        if shapeout is not None:
-            shapein = tointtuple(self.reshapeout(shapeout))
+            else:
+                self.validateout(shapeout)
+        if self.shapeout is not None:
+            shapein = tointtuple(self.reshapeout(self.shapeout))
             if self.shapein is None:
                 self.shapein = shapein
-
-        if shapein is not None:
-            self.validatein(shapein)
-        if shapeout is not None:
-            self.validateout(shapeout)
+            else:
+                self.validatein(shapein)
+        if self.shapein is not None:
+            self.validatein(self.shapein)
+        if self.shapeout is not None:
+            self.validateout(self.shapeout)
 
         if self.shapein is not None and self.shapeout is not None:
             self._set_flags(square=self.shapein == self.shapeout)
@@ -1022,13 +1035,8 @@ class Operator(object):
                 self.shapeout = self.shapein
             self.reshapein = lambda x: x
             self.reshapeout = self.reshapein
-            self.validatein = self.validatein or self.validateout
+            self.toshapeout = self.toshapein
             self.validateout = self.validatein
-            if self.toshapein.im_func is Operator.toshapein.im_func and \
-               self.toshapeout.im_func is not Operator.toshapeout.im_func:
-                self.toshapein = self.toshapeout
-            else:
-                self.toshapeout = self.toshapein
 
         if self.shapein is not None:
             try:
@@ -1049,17 +1057,12 @@ class Operator(object):
             else 'unconstrained'
         self._set_flags(shape_input=flag_is, shape_output=flag_os)
 
-        if flag_is == 'explicit':
+        if self.flags.shape_input == 'explicit':
             self.reshapeout = Operator.reshapeout.__get__(self, type(self))
             self.validatein = Operator.validatein.__get__(self, type(self))
-        if flag_os == 'explicit':
-            if self.flags.square:
-                self.reshapein = self.reshapeout
-                self.validateout = self.validatein
-            else:
-                self.reshapein = Operator.reshapein.__get__(self, type(self))
-                self.validateout = Operator.validateout.__get__(
-                    self, type(self))
+        if self.flags.shape_output == 'explicit':
+            self.reshapein = Operator.reshapein.__get__(self, type(self))
+            self.validateout = Operator.validateout.__get__(self, type(self))
 
     def _init_name(self, name):
         """ Set operator's __name__ attribute. """
@@ -1100,15 +1103,10 @@ class Operator(object):
             self.flags = flags
             return
         flags = self.validate_flags(flags, **keywords)
-        true_flags = [k for k, v in flags.items() if v is True]
-        if any(_ in true_flags
-               for _ in ['hermitian', 'involutary', 'orthogonal', 'symmetric',
-                         'unitary']):
-            if true_flags != ('involutary',):
+        for flag in ('hermitian', 'orthogonal', 'symmetric', 'unitary'):
+            if flags.get(flag, False):
                 flags['linear'] = True
-            # custom reshapein override the square flag
-            if self.reshapein == Operator.reshapein.__get__(self, type(self)):
-                flags['square'] = True
+                break
         self.flags = self.flags._replace(**flags)
 
     def _validate_arguments(self, input, output):
@@ -1447,6 +1445,7 @@ class DeletedOperator(Operator):
 
 
 @real
+ at square
 @symmetric
 @idempotent
 @involutary
@@ -1543,9 +1542,9 @@ class CompositeOperator(Operator):
         if isinstance(self, AdditionOperator):
             op = ' + '
         elif isinstance(self, MultiplicationOperator):
-            op = u' {0} '.format(u'\u00d7').encode('utf-8')
+            op = u' \u00d7 '
         elif isinstance(self, (BlockDiagonalOperator, BlockSliceOperator)):
-            op = u' {0} '.format(u'\u2295').encode('utf-8')
+            op = u' \u2295 '
         else:
             op = ' * '
 
@@ -1563,7 +1562,10 @@ class CompositeOperator(Operator):
             if self.operands[0].data == -1:
                 operands[0] += '1'
 
-        return op.join(operands)
+        op = op.join(operands)
+        if sys.version_info.major == 2:
+            op = op.encode('utf-8')
+        return op
 
     def __repr__(self):
         r = self.__name__ + '(['
@@ -1587,8 +1589,7 @@ class CommutativeCompositeOperator(CompositeOperator):
         keywords = self._get_attributes(operands, **keywords)
         operands = self._apply_rules(operands)
         if len(operands) == 1 and self.morph_single_operand:
-            self.__class__ = operands[0].__class__
-            self.__dict__ = operands[0].__dict__.copy()
+            operands[0].copy(self)
             self._reset(**keywords)
             return
         CompositeOperator.__init__(self, operands, **keywords)
@@ -2034,8 +2035,8 @@ class CompositionOperator(NonCommutativeCompositeOperator):
         operands = self._validate_operands(operands)
         operands = self._apply_rules(operands)
         if len(operands) == 1 and self.morph_single_operand:
-            self.__class__ = operands[0].__class__
-            self.__dict__ = operands[0].__dict__.copy()
+            operands[0].copy(self)
+            self._reset(**keywords)
             return
         keywords = self._get_attributes(operands, **keywords)
         self._info = {}
@@ -2249,9 +2250,9 @@ class CompositionOperator(NonCommutativeCompositeOperator):
         dtypein = input.dtype
         dtypeout = output.dtype
         alignedin = input.__array_interface__['data'][0] \
-            % MEMORY_ALIGNMENT == 0
+            % config.MEMORY_ALIGNMENT == 0
         alignedout = output.__array_interface__['data'][0] \
-            % MEMORY_ALIGNMENT == 0
+            % config.MEMORY_ALIGNMENT == 0
         contiguousin = input.flags.contiguous
         contiguousout = output.flags.contiguous
 
@@ -2269,7 +2270,7 @@ class CompositionOperator(NonCommutativeCompositeOperator):
                 "The composition of an unconstrained input shape operator by a"
                 "n unconstrained output shape operator is ambiguous.")
         dtypes = self._get_dtypes(input.dtype)
-        sizes = [product(s) * d.itemsize for s, d in izip(shapes, dtypes)]
+        sizes = [product(s) * d.itemsize for s, d in zip(shapes, dtypes)]
 
         ninplaces, aligneds, contiguouss = self._get_requirements()
 
@@ -2583,8 +2584,8 @@ class BlockOperator(NonCommutativeCompositeOperator):
 
         operands = self._validate_operands(operands)
         if len(operands) == 1:
-            self.__class__ = operands[0].__class__
-            self.__dict__ = operands[0].__dict__.copy()
+            operands[0].copy(self)
+            self._reset(**keywords)
             return
 
         if not isinstance(self, BlockRowOperator) and axisout is None and \
@@ -3357,7 +3358,7 @@ class BlockRowOperator(BlockOperator):
         with _pool.get_if(self._need_temporary, output.shape, output.dtype,
                           self.__name__) as buf:
 
-            for op, sin in zip(self.operands, sins)[1:]:
+            for op, sin in zip(self.operands[1:], sins[1:]):
                 i = input[sin]
                 with _pool.copy_if(i, op.flags.aligned_input,
                                    op.flags.contiguous_input) as i:
@@ -3402,7 +3403,8 @@ class ReshapeOperator(Operator):
             return
         Operator.__init__(self, shapein=shapein, shapeout=shapeout, **keywords)
         self.set_rule('T', lambda s: ReshapeOperator(s.shapeout, s.shapein))
-        self.set_rule((type(self), '.'), self._rule_reshape,
+        self.set_rule(('.', ReshapeOperator),
+                      lambda s, o: ReshapeOperator(o.shapein, s.shapeout),
                       CompositionOperator)
 
     def direct(self, input, output):
@@ -3410,10 +3412,6 @@ class ReshapeOperator(Operator):
             pass
         output.ravel()[:] = input.ravel()
 
-    @staticmethod
-    def _rule_reshape(other, self):
-        return ReshapeOperator(self.shapein, other.shapeout)
-
     def __str__(self):
         return strshape(self.shapeout) + '←' + strshape(self.shapein)
 
@@ -3592,6 +3590,7 @@ class BroadcastingBase(Operator):
         return np.lib.stride_tricks.as_strided(self.data, shape, strides)
 
 
+ at square
 @symmetric
 class DiagonalBase(BroadcastingBase):
     """
@@ -4249,7 +4248,7 @@ def asoperator(x, constant=False, **keywords):
                         shapein=x.shape[1], shapeout=x.shape[0],
                         dtype=x.dtype, **keywords)
 
-    if callable(x):
+    if isinstance(x, collections.Callable):
         def direct(input, output):
             output[...] = x(input)
         keywords['flags'] = Operator.validate_flags(keywords.get('flags', {}),
diff --git a/pyoperators/fft.py b/pyoperators/fft.py
index a19522d..f79a91e 100644
--- a/pyoperators/fft.py
+++ b/pyoperators/fft.py
@@ -417,10 +417,10 @@ def _load_wisdom():
 
     def load(filename):
         try:
-            with open(filename) as f:
+            with open(filename, 'rb') as f:
                 wisdom = f.read()
         except IOError:
-            wisdom = ''
+            wisdom = b''
         return wisdom
 
     wisdom = [load(f) for f in FFTW_WISDOM_FILES]
diff --git a/pyoperators/flags.py b/pyoperators/flags.py
index 3f3c8e2..196b452 100644
--- a/pyoperators/flags.py
+++ b/pyoperators/flags.py
@@ -89,7 +89,8 @@ def symmetric(cls):
     """
     Decorator for symmetric operators, i.e. operators that are equal to their
     transpose.
-    It sets the 'linear' and 'symmetric' flags.
+    It sets the 'linear' and 'symmetric' flags. Note that implicit shape
+    symmetric operators do not have to be square.
 
     """
     return flags(cls, 'symmetric')
@@ -99,7 +100,8 @@ def hermitian(cls):
     """
     Decorator for hermitian operators, i.e. operators that are equal to their
     adjoint.
-    It sets the 'linear' and 'hermitian' flags.
+    It sets the 'linear' and 'hermitian' flags. Note that implicit shape
+    hermitian operators do not have to be square.
 
     """
     return flags(cls, 'hermitian')
@@ -119,7 +121,8 @@ def involutary(cls):
     """
     Decorator for involutary operators, i.e. operators whose composition
     by themselves is equal to the identity.
-    It sets the 'involutary' flag.
+    It sets the 'involutary' flag. Note that implicit shape involutary
+    operators do not have to be square.
 
     """
     return flags(cls, 'involutary')
@@ -129,7 +132,8 @@ def orthogonal(cls):
     """
     Decorator for orthogonal operators, i.e. real operators whose composition
     by their transpose is equal to the identity.
-    It sets the 'real', 'linear' and 'orthogonal' flags.
+    It sets the 'real', 'linear' and 'orthogonal' flags. Note that implicit
+    shape orthogonal operators do not have to be square.
 
     """
     return flags(cls, 'orthogonal')
@@ -139,7 +143,8 @@ def unitary(cls):
     """
     Decorator for orthogonal operators, i.e. operators whose composition
     by their adjoint is equal to the identity.
-    It sets the 'linear' and 'unitary' flags.
+    It sets the 'linear' and 'unitary' flags. Note that implicit shape
+    unitary operators do not have to be square.
 
     """
     return flags(cls, 'unitary')
diff --git a/pyoperators/iterative/algorithms.py b/pyoperators/iterative/algorithms.py
index e456597..8317285 100644
--- a/pyoperators/iterative/algorithms.py
+++ b/pyoperators/iterative/algorithms.py
@@ -58,7 +58,7 @@ class Algorithm(object):
         """
         Perform n iterations and return current solution.
         """
-        for i in xrange(n):
+        for i in range(n):
             self.iter_ += 1
             self.callback(self)
         return self.current_solution
diff --git a/pyoperators/iterative/cg.py b/pyoperators/iterative/cg.py
index 8a913e5..53666c2 100644
--- a/pyoperators/iterative/cg.py
+++ b/pyoperators/iterative/cg.py
@@ -143,6 +143,9 @@ class PCGAlgorithm(IterativeAlgorithm):
 def pcg(A, b, x0=None, tol=1.e-5, maxiter=300, M=None, disp=False,
         callback=None, reuse_initial_state=False):
     """
+    output = pcg(A, b, [x0, tol, maxiter, M, disp, callback,
+                 reuse_initial_state])
+
     Parameters
     ----------
     A : {Operator, sparse matrix, dense matrix}
@@ -178,6 +181,11 @@ def pcg(A, b, x0=None, tol=1.e-5, maxiter=300, M=None, disp=False,
         'x' : the converged solution.
         'success' : boolean indicating success
         'message' : string indicating cause of failure
+        'nit' : number of completed iterations
+        'error' : normalized residual ||Ax-b|| / ||b||
+        'time' : elapsed time in solver
+        'algorithm' : the PCGAlgorithm instance (the callback function has
+                      access to it and can store information in it)
 
     """
     time0 = time.time()
@@ -197,7 +205,8 @@ def pcg(A, b, x0=None, tol=1.e-5, maxiter=300, M=None, disp=False,
             'message': message,
             'nit': algo.niterations,
             'error': algo.error,
-            'time': time.time() - time0}
+            'time': time.time() - time0,
+            'algorithm': algo}
 
 
 def _norm2(x, comm):
diff --git a/pyoperators/iterative/core.py b/pyoperators/iterative/core.py
index b2a6668..14feaf4 100644
--- a/pyoperators/iterative/core.py
+++ b/pyoperators/iterative/core.py
@@ -9,9 +9,16 @@ import numpy as np
 import re
 
 from ..utils import strenum, uninterruptible_if
+from ..utils.mpi import MPI
 from ..memory import empty
 from .stopconditions import NoStopCondition
 
+# Python 2 backward compatibility
+try:
+    range = xrange
+except NameError:
+    pass
+
 __all__ = ['AbnormalStopIteration', 'IterativeAlgorithm']
 
 NO_STOP_CONDITION = NoStopCondition()
@@ -132,7 +139,7 @@ class IterativeAlgorithm(object):
 
         """
         self.clean_interrupt = clean_interrupt
-        self.disp = disp
+        self.disp = False if MPI.COMM_WORLD.rank > 0 else disp
         self._set_buffer_handling(inplace_recursion, allocate_new_state,
                                   reuse_initial_state)
         self._set_order(keywords)
@@ -212,7 +219,7 @@ class IterativeAlgorithm(object):
         """ Perform n iterations and return current solution. """
         if self.niterations == self.order - 1:
             self.initialize()
-        for i in xrange(n):
+        for i in range(n):
             with uninterruptible_if(self.clean_interrupt):
                 self._check_stop_conditions()
                 self.niterations += 1
@@ -228,6 +235,9 @@ class IterativeAlgorithm(object):
                 self._update_variables()
         return self.finalize()
 
+    def __next__(self):
+        return self.next()
+
     def iteration(self):
         """
         Algorithm actual iteration, It defines the new state from the previous
@@ -311,7 +321,7 @@ class IterativeAlgorithm(object):
         """ Set the callback function, if specified. """
         if callback is None:
             return
-        if not callable(callback):
+        if not isinstance(callback, collections.Callable):
             raise TypeError('The callback function is not callable.')
         self.callback = callback
 
@@ -383,7 +393,7 @@ class IterativeAlgorithm(object):
 
     def _set_stop_conditions(self, normal_stop, abnormal_stop):
         """ Set the stop conditions. """
-        if not callable(normal_stop) or not callable(abnormal_stop):
+        if not isinstance(normal_stop, collections.Callable) or not isinstance(abnormal_stop, collections.Callable):
             raise TypeError('The stop conditions must be callable.')
         self.normal_stop_condition = normal_stop
         self.abnormal_stop_condition = abnormal_stop
diff --git a/pyoperators/linear.py b/pyoperators/linear.py
index b98a743..88a0dbf 100644
--- a/pyoperators/linear.py
+++ b/pyoperators/linear.py
@@ -8,8 +8,9 @@ try:
 except:
     pass
 import scipy.sparse as sp
+import scipy.sparse.sparsetools as sps
 import sys
-from itertools import izip
+
 from scipy.sparse.linalg import eigsh
 from .core import (
     BlockRowOperator, BroadcastingBase, CompositionOperator, ConstantOperator,
@@ -324,25 +325,11 @@ class DenseOperator(DenseBlockDiagonalOperator):
 @contiguous
 @update_output
 class SparseBase(Operator):
-    def __init__(self, matrix, dtype=None, shapein=None, shapeout=None,
-                 **keywords):
+    def __init__(self, matrix, dtype=None, **keywords):
         if dtype is None:
             dtype = matrix.dtype
-        if shapein is None:
-            shapein = matrix.shape[1]
-        elif product(shapein) != matrix.shape[1]:
-            raise ValueError(
-                "The input shape '{0}' is incompatible with the sparse matrix "
-                "shape {1}.".format(shapein, matrix.shape))
-        if shapeout is None:
-            shapeout = matrix.shape[0]
-        elif product(shapeout) != matrix.shape[0]:
-            raise ValueError(
-                "The output shape '{0}' is incompatible with the sparse matrix"
-                " shape {1}.".format(shapeout, matrix.shape))
         self.matrix = matrix
-        Operator.__init__(self, shapein=shapein, shapeout=shapeout,
-                          dtype=dtype, **keywords)
+        Operator.__init__(self, dtype=dtype, **keywords)
 
     @property
     def nbytes(self):
@@ -358,7 +345,7 @@ class SparseBase(Operator):
         if isinstance(m, sp.dok_matrix):
             sizeoftuple = sys.getsizeof(())
             return (24 * m.ndim + m.dtype.itemsize +
-                    2 * sizeoftuple + 24) * len(m.viewitems())
+                    2 * sizeoftuple + 24) * len(m.items())
         try:
             return m.data.nbytes
         except AttributeError:
@@ -406,6 +393,18 @@ class SparseOperator(SparseBase):
         if isinstance(matrix, sp.lil_matrix):
             raise TypeError('The LIL format is not suited for arithmetic opera'
                             'tions.')
+        if shapein is None:
+            shapein = matrix.shape[1]
+        elif product(shapein) != matrix.shape[1]:
+            raise ValueError(
+                "The input shape '{0}' is incompatible with the sparse matrix "
+                "shape {1}.".format(shapein, matrix.shape))
+        if shapeout is None:
+            shapeout = matrix.shape[0]
+        elif product(shapeout) != matrix.shape[0]:
+            raise ValueError(
+                "The output shape '{0}' is incompatible with the sparse matrix"
+                " shape {1}.".format(shapeout, matrix.shape))
         SparseBase.__init__(self, matrix, dtype=dtype, shapein=shapein,
                             shapeout=shapeout, **keywords)
         self.set_rule('T', lambda s: SparseOperator(s.matrix.transpose()))
@@ -421,11 +420,11 @@ class SparseOperator(SparseBase):
             raise ValueError('Invalid reduction operation.')
         m = self.matrix
         if isinstance(m, sp.dok_matrix):
-            for (i, j), v in m.iteritems():
+            for (i, j), v in m.items():
                 output[i] += v * input[j]
             return
         M, N = m.shape
-        fn = getattr(sp.sparsetools, m.format + '_matvec')
+        fn = getattr(sps, m.format + '_matvec')
         if isinstance(m, (sp.csr_matrix, sp.csc_matrix)):
             fn(M, N, m.indptr, m.indices, m.data, input, output)
         elif isinstance(m, sp.coo_matrix):
@@ -1208,10 +1207,10 @@ class BandOperator(Operator):
         # diag
         out[:] = self.ab[self.ku] * x
         # upper part
-        for i in xrange(self.ku):
+        for i in range(self.ku):
             j = self.ku - i
             out[:-j] += self.ab[i, j:] * x[j:]
-        for i in xrange(self.ku, self.kl + self.ku):
+        for i in range(self.ku, self.kl + self.ku):
             # lower part
             out[i:] += self.ab[i + 1, :-i] * x[:-i]
 
@@ -1221,10 +1220,10 @@ class BandOperator(Operator):
         # diag
         out = self.rab[self.ku] * x
         # upper part
-        for i in xrange(rku):
+        for i in range(rku):
             j = rku - i
             out[:-j] += rab[i, j:] * x[j:]
-        for i in xrange(rku, rkl + rku):
+        for i in range(rku, rkl + rku):
             # lower part
             out[i:] += rab[i + 1, :-i] * x[:-i]
 
@@ -1248,7 +1247,7 @@ class BandOperator(Operator):
         kl, ku = self.kl, self.ku
         rku, rkl = kl, ku
         rab = np.zeros(ab.shape, dtype=ab.dtype)
-        for i in xrange(- kl, ku + 1):
+        for i in range(- kl, ku + 1):
             rab[_band_diag(rku, -i)] = self.diag(i)
         return rab
 
@@ -1288,6 +1287,7 @@ class UpperTriangularOperator(BandOperator):
         BandOperator.__init__(self, ab, kl, ku, **kwargs)
 
 
+ at square
 @symmetric
 class SymmetricBandOperator(Operator):
     """
@@ -1308,7 +1308,7 @@ class SymmetricBandOperator(Operator):
 
     def direct(self, x, out):
         out[:] = self.ab[0] * x
-        for i in xrange(1, self.ab.shape[0]):
+        for i in range(1, self.ab.shape[0]):
             # upper part
             out[:-i] += self.ab[i, :-i] * x[i:]
             # lower part
@@ -1362,6 +1362,7 @@ class SymmetricBandOperator(Operator):
 
 @real
 @linear
+ at square
 @symmetric
 @inplace
 class SymmetricBandToeplitzOperator(Operator):
@@ -1451,7 +1452,7 @@ class SymmetricBandToeplitzOperator(Operator):
                     dtype):
         firstrow = firstrow.reshape((-1, ncorr + 1))
         kernel = empty((firstrow.shape[0], fftsize // 2 + 1), dtype)
-        for f, k in izip(firstrow, kernel):
+        for f, k in zip(firstrow, kernel):
             rbuffer[:ncorr+1] = f
             rbuffer[ncorr+1:-ncorr] = 0
             rbuffer[-ncorr:] = f[:0:-1]
@@ -1494,6 +1495,7 @@ class DifferenceOperator(Operator):
         return tuple(shape)
 
 
+ at square
 @symmetric
 class EigendecompositionOperator(CompositionOperator):
     """
diff --git a/pyoperators/memory.py b/pyoperators/memory.py
index 626b6ae..2db3f49 100644
--- a/pyoperators/memory.py
+++ b/pyoperators/memory.py
@@ -19,16 +19,6 @@ from .utils import ifirst, product, strshape, tointtuple
 
 __all__ = ['empty', 'ones', 'zeros']
 
-# force garbage collection when deleted operators' nbytes exceed this
-# threshold.
-GC_NBYTES_THRESHOLD = 1e8
-
-MEMORY_ALIGNMENT = 32
-
-# We allow reuse of pool variables only if they do not exceed 20% of
-# the requested size
-MEMORY_TOLERANCE = 1.2
-
 _gc_nbytes_counter = 0
 
 
@@ -41,7 +31,7 @@ def empty(shape, dtype=np.float, order='c', description=None, verbose=None):
     shape = tointtuple(shape)
     dtype = np.dtype(dtype)
     if verbose is None:
-        verbose = config.PYOPERATORS_VERBOSE
+        verbose = config.VERBOSE
 
     requested = product(shape) * dtype.itemsize
     if requested == 0:
@@ -64,14 +54,16 @@ def empty(shape, dtype=np.float, order='c', description=None, verbose=None):
         print(utils.strinfo('Allocating ' + strshape(shape) +
               ' ' + (str(dtype) if dtype.kind != 'V' else 'elements') +
               ' = ' + utils.strnbytes(requested) + ' ' + description))
+
+    alignment = config.MEMORY_ALIGNMENT
     try:
-        buf = np.empty(requested + MEMORY_ALIGNMENT, np.int8)
+        buf = np.empty(requested + alignment, np.int8)
     except MemoryError:
         gc.collect()
-        buf = np.empty(requested + MEMORY_ALIGNMENT, np.int8)
+        buf = np.empty(requested + alignment, np.int8)
 
     address = buf.__array_interface__['data'][0]
-    offset = MEMORY_ALIGNMENT - address % MEMORY_ALIGNMENT
+    offset = alignment - address % alignment
 
     return np.frombuffer(buf.data, np.int8, count=requested, offset=offset) \
              .view(dtype).reshape(shape, order=order)
@@ -109,7 +101,7 @@ def iscompatible(array, shape, dtype, aligned=False, contiguous=False,
     shape = tointtuple(shape)
     dtype = np.dtype(dtype)
     if aligned and \
-       array.__array_interface__['data'][0] % MEMORY_ALIGNMENT != 0:
+       array.__array_interface__['data'][0] % config.MEMORY_ALIGNMENT != 0:
         return False
     if not array.flags.contiguous:
         if contiguous:
@@ -162,7 +154,7 @@ class MemoryPool(object):
         """
         if not isinstance(v, np.ndarray):
             raise TypeError('The input is not an ndarray.')
-        alignment = MEMORY_ALIGNMENT if aligned else 1
+        alignment = config.MEMORY_ALIGNMENT if aligned else 1
         if v.__array_interface__['data'][0] % alignment != 0 or \
            contiguous and not v.flags.contiguous:
             with self.get(v.shape, v.dtype) as buf:
@@ -181,8 +173,8 @@ class MemoryPool(object):
         """
         shape = tointtuple(shape)
         dtype = np.dtype(dtype)
-        compatible = lambda x: iscompatible(x, shape, dtype, aligned,
-                                            contiguous, MEMORY_TOLERANCE)
+        compatible = lambda x: iscompatible(
+            x, shape, dtype, aligned, contiguous, config.MEMORY_TOLERANCE)
         try:
             i = ifirst(self._buffers, compatible)
             v = self._buffers.pop(i)
@@ -319,8 +311,8 @@ class MemoryPool(object):
 def garbage_collect(nbytes=None):
     global _gc_nbytes_counter
     if nbytes is None:
-        nbytes = GC_NBYTES_THRESHOLD
+        nbytes = config.GC_NBYTES_THRESHOLD
     _gc_nbytes_counter += nbytes
-    if _gc_nbytes_counter >= GC_NBYTES_THRESHOLD:
+    if _gc_nbytes_counter >= config.GC_NBYTES_THRESHOLD:
         gc.collect()
         _gc_nbytes_counter = 0
diff --git a/pyoperators/nonlinear.py b/pyoperators/nonlinear.py
index 77885ea..a695c57 100644
--- a/pyoperators/nonlinear.py
+++ b/pyoperators/nonlinear.py
@@ -1,8 +1,8 @@
 #coding: utf-8
 from __future__ import absolute_import, division, print_function
 import numexpr
-if numexpr.__version__ < 2.0:
-    raise ImportError('Please update numexpr to a newer version > 2.0.')
+if numexpr.__version__ < '2.0':
+    raise ImportError('Please update numexpr to a newer version >= 2.0.')
 
 import numpy as np
 import pyoperators as po
diff --git a/pyoperators/norms.py b/pyoperators/norms.py
deleted file mode 100644
index 45d7ef4..0000000
--- a/pyoperators/norms.py
+++ /dev/null
@@ -1,41 +0,0 @@
-from __future__ import absolute_import, division, print_function
-import numpy as np
-from .core import (
-    BlockOperator, BlockRowOperator, CompositionOperator, Operator)
-from .flags import real
-from .utils import MPI
-
-# these function may be overridden
-sum = np.sum
-dot = np.dot
-
-
- at real
-class NormOperator(Operator):
-    commin = None
-    commout = MPI.COMM_SELF
-    shapeout = ()
-    operation = 'sum'
-
-    def __init__(self, **keywords):
-        Operator.__init__(self, **keywords)
-        self.set_rule(('.', BlockOperator), lambda s, b: s._rule_block(s, b),
-                      CompositionOperator)
-
-    @staticmethod
-    def _rule_block(self, b):
-        if b.partitionout is None:
-            return
-        s = self.copy()
-        s.commin = None
-        return BlockRowOperator(len(b.partitionout) * s, operation='sum',
-                                partitionin=b.partitionout, axisin=b.axisout,
-                                new_axisin=b.new_axisout, commin=b.commout,
-                                commout=self.commout) * b
-
-
-class Norm2Operator(NormOperator):
-    def direct(self, input, output):
-        output[...] = dot(input, input)
-        if self.commin is not None:
-            self.commin.Allreduce(MPI.IN_PLACE, output)
diff --git a/pyoperators/operators_pywt.py b/pyoperators/operators_pywt.py
index c328304..a33b960 100644
--- a/pyoperators/operators_pywt.py
+++ b/pyoperators/operators_pywt.py
@@ -82,7 +82,7 @@ class WaveletOperator(Operator):
 
     def _vect2coeffs(self, vect):
         return [vect[self.cumsizes[i]:self.cumsizes[i + 1]]
-                for i in xrange(len(self.sizes))]
+                for i in range(len(self.sizes))]
 
 
 @real
@@ -127,7 +127,7 @@ class Wavelet2dOperator(Operator):
         approx = coeffs[0]
         details = coeffs[1:]
         self.shapes = [approx.shape]
-        self.shapes += [d[i].shape for d in details for i in xrange(3)]
+        self.shapes += [d[i].shape for d in details for i in range(3)]
         self.sizes = [np.prod(s) for s in self.shapes]
         self.cumsizes = np.zeros(len(self.sizes) + 1)
         np.cumsum(self.sizes, out=self.cumsizes[1:])
@@ -153,7 +153,7 @@ class Wavelet2dOperator(Operator):
         details = coeffs[1:]
         # transform 2d arrays into vectors
         vect_coeffs = [approx.ravel()]
-        vect_coeffs += [d[i].ravel() for d in details for i in xrange(3)]
+        vect_coeffs += [d[i].ravel() for d in details for i in range(3)]
         # put everything into a single coefficient
         return np.concatenate(vect_coeffs)
 
@@ -161,5 +161,5 @@ class Wavelet2dOperator(Operator):
         cs = self.cumsizes
         approx = [vect[:self.sizes[0]].reshape(self.shapes[0])]
         details = [[vect[cs[i + j]:cs[i + j + 1]].reshape(self.shapes[i + j])
-                    for j in xrange(3)] for i in xrange(1, len(self.sizes), 3)]
+                    for j in range(3)] for i in range(1, len(self.sizes), 3)]
         return approx + details
diff --git a/pyoperators/rules.py b/pyoperators/rules.py
index 941a099..703e4ae 100644
--- a/pyoperators/rules.py
+++ b/pyoperators/rules.py
@@ -6,6 +6,7 @@ import os
 from . import config
 from .core import HomothetyOperator, IdentityOperator, Operator, ZeroOperator
 from .warnings import warn, PyOperatorsWarning
+import collections
 
 __all__ = ['rule_manager']
 _triggers = {}
@@ -67,7 +68,7 @@ class Rule(object):
         if isinstance(self.predicate, types.FunctionType):
             if type(self.predicate) is not type(other.predicate):
                 return False
-            return self.predicate.func_code is other.predicate.func_code
+            return self.predicate.__code__ is other.predicate.__code__
         if isinstance(self.predicate, str):
             return self.predicate == other.predicate
         return self.predicate is other.predicate
@@ -155,7 +156,7 @@ class UnaryRule(Rule):
             raise ValueError('This is not a unary rule.')
         if self.subjects[0] == '.':
             raise ValueError('The subject cannot be the operator itself.')
-        if callable(predicate) or predicate in ('.', '1'):
+        if isinstance(predicate, collections.Callable) or predicate in ('.', '1'):
             return
         raise ValueError("Invalid predicate: '{0}'.".format(predicate))
 
@@ -163,7 +164,7 @@ class UnaryRule(Rule):
         predicate = self._symbol2operator(reference, self.predicate)
         if predicate is None:
             return None
-        if not isinstance(predicate, Operator) and callable(predicate):
+        if not isinstance(predicate, Operator) and isinstance(predicate, collections.Callable):
             predicate = predicate(reference)
         if not isinstance(predicate, Operator):
             raise TypeError('The predicate is not an operator.')
@@ -236,7 +237,7 @@ class BinaryRule(Rule):
         if predicate is None:
             return None
 
-        if not isinstance(predicate, Operator) and callable(predicate):
+        if not isinstance(predicate, Operator) and isinstance(predicate, collections.Callable):
             predicate = predicate(o1, o2)
         if predicate is None:
             return None
diff --git a/pyoperators/utils/__init__.py b/pyoperators/utils/__init__.py
index 1c2b790..8a96051 100644
--- a/pyoperators/utils/__init__.py
+++ b/pyoperators/utils/__init__.py
@@ -1,4 +1,4 @@
-import ufuncs
+from . import ufuncs
 from . import mpi
 from . import testing
 from .cythonutils import *
diff --git a/pyoperators/utils/cythonutils.pyx b/pyoperators/utils/cythonutils.pyx
new file mode 100644
index 0000000..cacb7f9
--- /dev/null
+++ b/pyoperators/utils/cythonutils.pyx
@@ -0,0 +1,132 @@
+from __future__ import division
+
+import numpy as np
+cimport numpy as np
+cimport cython
+
+__all__ = []
+
+ at cython.boundscheck(False)
+def inspect_special_values_bool8(np.ndarray[np.uint8_t, ndim=1] v):
+    cdef int nzeros = 0
+    cdef unsigned int n = v.size
+    cdef unsigned int i
+
+    for i in range(n):
+        if v[i] == 0:
+            nzeros += 1
+    return 0, nzeros, n - nzeros, False, nzeros in (0, n)
+
+ at cython.boundscheck(False)
+def inspect_special_values_uint64(np.ndarray[np.uint64_t, ndim=1] v):
+    cdef int nones = 0
+    cdef int nzeros = 0
+    cdef unsigned int n = v.size
+    cdef unsigned int i
+    cdef np.uint64_t value, value0 = v[0]
+    cdef int same = 1
+    cdef int other = 0
+
+    for i in range(n):
+        value = v[i]
+        if value == 0:
+            nzeros += 1
+        elif value == 1:
+            nones += 1
+        else:
+            other = 1
+        if same == 1 and value != value0:
+            same = 0
+        if same == 0 and other == 1:
+            return 0, 0, 0, True, False
+    if other == 1:
+        return 0, 0, 0, True, True
+    return 0, nzeros, nones, False, same == 1
+
+ at cython.boundscheck(False)
+def inspect_special_values_int64(np.ndarray[np.int64_t, ndim=1] v):
+    cdef int nones = 0
+    cdef int nzeros = 0
+    cdef int nmones = 0
+    cdef unsigned int n = v.size
+    cdef unsigned int i
+    cdef np.int64_t value, value0 = v[0]
+    cdef int same = 1
+    cdef int other = 0
+
+    for i in range(n):
+        value = v[i]
+        if value == 0:
+            nzeros += 1
+        elif value == 1:
+            nones += 1
+        elif value == -1:
+            nmones += 1
+        else:
+            other = 1
+        if same == 1 and value != value0:
+            same = 0
+        if same == 0 and other == 1:
+            return 0, 0, 0, True, False
+    if other == 1:
+        return 0, 0, 0, True, True
+    return nmones, nzeros, nones, False, same == 1
+
+ at cython.boundscheck(False)
+def inspect_special_values_float64(np.ndarray[np.float64_t, ndim=1] v):
+    cdef int nones = 0
+    cdef int nzeros = 0
+    cdef int nmones = 0
+    cdef unsigned int n = v.size
+    cdef unsigned int i
+    cdef np.float64_t value, value0 = v[0]
+    cdef int same = 1
+    cdef int other = 0
+
+    for i in range(n):
+        value = v[i]
+        if value == 0:
+            nzeros += 1
+        elif value == 1:
+            nones += 1
+        elif value == -1:
+            nmones += 1
+        else:
+            other = 1
+        if same == 1 and value != value0:
+            same = 0
+        if same == 0 and other == 1:
+            return 0, 0, 0, True, False
+    if other == 1:
+        return 0, 0, 0, True, True
+    return nmones, nzeros, nones, False, same == 1
+
+ at cython.boundscheck(False)
+def inspect_special_values_complex128(np.ndarray[np.complex128_t, ndim=1] v):
+    cdef int nones = 0
+    cdef int nzeros = 0
+    cdef int nmones = 0
+    cdef unsigned int n = v.size
+    cdef unsigned int i
+    cdef np.complex128_t value, value0 = v[0]
+    cdef int same = 1
+    cdef int other = 0
+
+    for i in range(n):
+        value = v[i]
+        if value == 0:
+            nzeros += 1
+        elif value == 1:
+            nones += 1
+        elif value == -1:
+            nmones += 1
+        else:
+            other = 1
+        if same == 1 and value != value0:
+            same = 0
+        if same == 0 and other == 1:
+            return 0, 0, 0, True, False
+    if other == 1:
+        return 0, 0, 0, True, True
+    return nmones, nzeros, nones, False, same == 1
+
diff --git a/pyoperators/utils/fake_MPI.py b/pyoperators/utils/fake_MPI.py
index 4023a4a..1e13cc5 100644
--- a/pyoperators/utils/fake_MPI.py
+++ b/pyoperators/utils/fake_MPI.py
@@ -1,7 +1,10 @@
 """
 MPI-wrapper module for non-MPI enabled platforms.
 """
-import __builtin__
+try:
+    import builtins
+except ImportError:
+    import __builtin__ as builtins
 
 _g = globals()
 _constants = ('SUM', 'MIN', 'MAX', 'PROD', 'BAND', 'BOR', 'BXOR',
@@ -146,9 +149,9 @@ COMM_SELF = Comm(0, 1)
 COMM_WORLD = Comm(0, 1)
 
 
-class Exception(__builtin__.Exception):
+class Exception(builtins.Exception):
     """ Exception.__init__(self, int ierr=0) """
     def __init__(self, ierr=0):
         pass
 
-del _g, _constants, __builtin__
+del _g, _constants, builtins
diff --git a/pyoperators/utils/misc.py b/pyoperators/utils/misc.py
index 8a6d920..74ca54a 100644
--- a/pyoperators/utils/misc.py
+++ b/pyoperators/utils/misc.py
@@ -11,11 +11,13 @@ import os
 import signal
 import timeit
 import types
-
+import sys
 from contextlib import contextmanager
-from itertools import izip
 from . import cythonutils as cu
 from ..warnings import warn, PyOperatorsDeprecationWarning
+# Python 2 backward compatibility
+if sys.version_info.major == 2:
+    zip = itertools.izip
 
 __all__ = ['all_eq',
            'broadcast_shapes',
@@ -102,7 +104,7 @@ def all_eq(a, b):
             if not all_eq(a[k], b[k]):
                 return False
         return True
-    if isinstance(a, (str, unicode)):
+    if isinstance(a, str):
         if type(a) is not type(b):
             return False
         return a == b
@@ -114,18 +116,18 @@ def all_eq(a, b):
             return False
         if len(a) != len(b):
             return False
-        for a_, b_ in izip(a, b):
+        for a_, b_ in zip(a, b):
             if not all_eq(a_, b_):
                 return False
         return True
     if isinstance(a, types.MethodType):
         if type(a) is not type(b):
             return False
-        return a.im_class is b.im_class and a.im_func is b.im_func
+        return a.__self__.__class__ is b.__self__.__class__ and a.__func__ is b.__func__
     if isinstance(a, types.LambdaType):
         if type(a) is not type(b):
             return False
-        return a.func_code is b.func_code
+        return a.__code__ is b.__code__
     return a == b
 
 
@@ -298,8 +300,9 @@ def groupbykey(iterable, key):
     value of key.
 
     """
-    iterator = izip(iterable, key)
+    iterator = zip(iterable, key)
     i, value = next(iterator)
+
     l = [i]
     for i, k in iterator:
         if k == value:
@@ -335,7 +338,7 @@ def ifirst(l, match):
 
     """
     try:
-        if not callable(match):
+        if not isinstance(match, collections.Callable):
             return next((i for i, _ in enumerate(l) if _ == match))
         return next((i for i, _ in enumerate(l) if match(_)))
     except StopIteration:
@@ -514,7 +517,7 @@ def izip_broadcast(*args):
         return a
     if any(not hasattr(a, '__len__') or len(a) != 1 for a in args):
         args = [wrap(arg) for arg in args]
-    return izip(*args)
+    return zip(*args)
 
 
 def last(l, f):
@@ -578,7 +581,7 @@ def least_greater_multiple(a, l, out=None):
     slices = [slice(0, m+1) for m in max_power]
     powers = np.ogrid[slices]
     values = 1
-    for v, p in izip(l, powers):
+    for v, p in zip(l, powers):
         values = values * v**p
     for v, o in it:
         if np.__version__ < '2':
@@ -613,9 +616,9 @@ def merge_none(a, b):
         return None
     if len(a) != len(b):
         raise ValueError('The input sequences do not have the same length.')
-    if any(p != q for p, q in izip(a, b) if None not in (p, q)):
+    if any(p != q for p, q in zip(a, b) if None not in (p, q)):
         raise ValueError('The input sequences have incompatible values.')
-    return tuple(p if p is not None else q for p, q in izip(a, b))
+    return tuple(p if p is not None else q for p, q in zip(a, b))
 
 
 class ndarraywrap(np.ndarray):
@@ -645,7 +648,7 @@ operation_symbol = {
     operator.iadd: '+',
     operator.isub: '-',
     operator.imul: '*',
-    operator.idiv: '/',
+    operator.itruediv: '/',
 }
 
 
@@ -697,7 +700,9 @@ def product(a):
 
 def renumerate(l):
     """ Reversed enumerate. """
-    return izip(xrange(len(l)-1, -1, -1), reversed(l))
+    if isinstance(l, collections.Iterable):
+        l = list(l)
+    return zip(range(len(l)-1, -1, -1), reversed(l))
 
 
 def reshape_broadcast(x, shape):
diff --git a/pyoperators/utils/mpi.py b/pyoperators/utils/mpi.py
index 132e2ed..f4fa9b4 100644
--- a/pyoperators/utils/mpi.py
+++ b/pyoperators/utils/mpi.py
@@ -1,12 +1,13 @@
-from __future__ import absolute_import, division, print_function
+
 import contextlib
 import numpy as np
 import operator
 import os
 from .. import config
+from functools import reduce
 
 try:
-    if config.PYOPERATORS_NO_MPI:
+    if config.NO_MPI:
         raise ImportError()
     from mpi4py import MPI
 except ImportError:
diff --git a/pyoperators/utils/testing.py b/pyoperators/utils/testing.py
index ef295eb..ad4a379 100644
--- a/pyoperators/utils/testing.py
+++ b/pyoperators/utils/testing.py
@@ -2,7 +2,7 @@ import collections
 import functools
 import numpy as np
 from collections import Container, Mapping
-from itertools import izip
+
 from nose.plugins.skip import SkipTest
 from numpy.testing import assert_equal, assert_allclose
 
@@ -135,7 +135,7 @@ def assert_eq(a, b, msg=''):
     if isinstance(a, Container) and not isinstance(a, (set, str)) and \
        isinstance(b, Container) and not isinstance(b, (set, str)):
         assert_equal(len(a), len(b), msg)
-        for a_, b_ in izip(a, b):
+        for a_, b_ in zip(a, b):
             assert_eq(a_, b_, msg)
         return
 
diff --git a/setup.py b/setup.py
index 4ed72ff..5b60b0a 100644
--- a/setup.py
+++ b/setup.py
@@ -1,21 +1,24 @@
 #!/usr/bin/env python
 import numpy as np
-from distutils.extension import Extension
-from numpy.distutils.core import setup
+import sys
 from hooks import get_cmdclass, get_version
+from numpy.distutils.core import setup
+from numpy.distutils.extension import Extension
 
-VERSION = '0.12'
+VERSION = '0.13'
 
 name = 'pyoperators'
 long_description = open('README.rst').read()
 keywords = 'scientific computing'
 platforms = 'MacOS X,Linux,Solaris,Unix,Windows'
+define_macros = [] if sys.version_info.major == 2 else [('NPY_PY3K', None)]
 
 ext_modules = [Extension("pyoperators.utils.cythonutils",
-                         sources=["pyoperators/utils/cythonutils.c"],
-                         include_dirs=['.', np.get_include()]),
+                         sources=["pyoperators/utils/cythonutils.pyx"],
+                         include_dirs=[np.get_include()]),
                Extension("pyoperators.utils.ufuncs",
-                         sources=["pyoperators/utils/ufuncs.c.src"])]
+                         sources=["pyoperators/utils/ufuncs.c.src"],
+                         define_macros=define_macros)]
 
 setup(name=name,
       version=get_version(name, VERSION),
@@ -38,7 +41,12 @@ setup(name=name,
       license='CeCILL-B',
       classifiers=[
           'Programming Language :: Python',
-          'Programming Language :: Python :: 2 :: Only',
+          'Programming Language :: Python :: 2',
+          'Programming Language :: Python :: 2.6',
+          'Programming Language :: Python :: 2.7',
+          'Programming Language :: Python :: 3',
+          'Programming Language :: Python :: 3.3',
+          'Programming Language :: Python :: 3.4',
           'Programming Language :: C',
           'Programming Language :: Cython',
           'Development Status :: 4 - Beta',
diff --git a/test/__init__.py b/test/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/test/common.py b/test/common.py
new file mode 100644
index 0000000..ed1d049
--- /dev/null
+++ b/test/common.py
@@ -0,0 +1,168 @@
+import numpy as np
+import pyoperators
+
+from pyoperators import Operator, flags
+from pyoperators.utils.testing import assert_eq
+
+DTYPES = [np.dtype(t) for t in (np.uint8, np.int8, np.uint16, np.int16,
+          np.uint32, np.int32, np.uint64, np.int64,
+          np.float16, np.float32, np.float64, np.float128,
+          np.complex64, np.complex128, np.complex256)]
+COMPLEX_DTYPES = [np.dtype(t) for t in (np.complex64, np.complex128,
+                  np.complex256)]
+
+
+class ndarray1(np.ndarray):
+    pass
+
+
+class ndarray2(np.ndarray):
+    pass
+
+attr1 = {'attr1': True, 'attr2': True}
+attr2 = {'attr1': False, 'attr3': False}
+
+
+ at flags.linear
+class ExplExpl(Operator):
+    def __init__(self, shapein=3, shapeout=4, **keywords):
+        Operator.__init__(self, shapein=shapein, shapeout=shapeout,
+                          classout=ndarray1, attrout=attr1, **keywords)
+
+    def direct(self, input, output):
+        output[0:3] = input
+        output[3] = 10.
+
+
+ at flags.linear
+class UncoExpl(Operator):
+    def __init__(self, shapein=3, **keywords):
+        Operator.__init__(self, shapein=shapein, classout=ndarray1,
+                          attrout=attr1, **keywords)
+
+    def direct(self, input, output):
+        output[0:3] = 2*input
+        output[3:] = 20
+
+
+ at flags.linear
+class ImplImpl(Operator):
+    def __init__(self, **keywords):
+        Operator.__init__(self, classout=ndarray1, attrout=attr1, **keywords)
+
+    def direct(self, input, output):
+        output[0:input.size] = 3*input
+        output[-1] = 30
+
+    def reshapein(self, shapein):
+        return (shapein[0] + 1,)
+
+    def reshapeout(self, shapeout):
+        return (shapeout[0] - 1,)
+
+
+ at flags.linear
+class UncoImpl(Operator):
+    def __init__(self, **keywords):
+        Operator.__init__(self, classout=ndarray1, attrout=attr1, **keywords)
+
+    def direct(self, input, output):
+        output[0:output.size-1] = 4*input
+        output[-1] = 40
+
+    def reshapeout(self, shapeout):
+        return (shapeout[0] - 1,)
+
+
+ at flags.linear
+class ExplUnco(Operator):
+    def __init__(self, shapeout=4, **keywords):
+        Operator.__init__(self, shapeout=shapeout, classout=ndarray1,
+                          attrout=attr1, **keywords)
+
+    def direct(self, input, output):
+        output[0:3] = 5*input[0:3]
+        output[3] = 50
+
+
+ at flags.linear
+class ImplUnco(Operator):
+    def __init__(self, **keywords):
+        Operator.__init__(self, classout=ndarray1, attrout=attr1, **keywords)
+
+    def direct(self, input, output):
+        output[0:input.size] = 6*input
+        output[-1] = 60
+
+    def reshapein(self, shapein):
+        return (shapein[0] + 1,)
+
+
+ at flags.linear
+class UncoUnco(Operator):
+    def __init__(self, **keywords):
+        Operator.__init__(self, classout=ndarray1, attrout=attr1, **keywords)
+
+    def direct(self, input, output):
+        output[0:3] = 7*input[0:3]
+        output[3:] = 70
+
+OPS = ExplExpl, UncoExpl, ImplImpl, UncoImpl, ExplUnco, ImplUnco, UncoUnco
+
+ALL_OPS = [eval('pyoperators.' + op) for op in dir(pyoperators) if op.endswith(
+           'Operator')]
+
+
+ at flags.linear
+ at flags.square
+class IdentityOutplaceOperator(Operator):
+    def direct(self, input, output):
+        output[...] = input
+
+
+ at flags.linear
+ at flags.real
+ at flags.square
+ at flags.symmetric
+class HomothetyOutplaceOperator(Operator):
+    def __init__(self, value, **keywords):
+        Operator.__init__(self, **keywords)
+        self.value = value
+
+    def direct(self, input, output):
+        output[...] = self.value * input
+
+
+ at flags.linear
+class Stretch(Operator):
+    """ Stretch input array by replicating it by a factor of 2. """
+    def __init__(self, axis, **keywords):
+        self.axis = axis
+        if self.axis < 0:
+            self.slice = [Ellipsis] + (-self.axis) * [slice(None)]
+        else:
+            self.slice = (self.axis+1) * [slice(None)] + [Ellipsis]
+        Operator.__init__(self, **keywords)
+
+    def direct(self, input, output):
+        self.slice[self.axis] = slice(0, None, 2)
+        output[self.slice] = input
+        self.slice[self.axis] = slice(1, None, 2)
+        output[self.slice] = input
+
+    def reshapein(self, shape):
+        shape_ = list(shape)
+        shape_[self.axis] *= 2
+        return shape_
+
+    def reshapeout(self, shape):
+        shape_ = list(shape)
+        shape_[self.axis] //= 2
+        return shape_
+
+
+def assert_inplace_outplace(op, v, expected):
+    w = op(v)
+    assert_eq(w, expected)
+    op(v, out=w)
+    assert_eq(w, expected)
diff --git a/test/test_broadcastingoperators.py b/test/test_broadcastingoperators.py
index 35eab1a..c11dd87 100644
--- a/test/test_broadcastingoperators.py
+++ b/test/test_broadcastingoperators.py
@@ -426,14 +426,14 @@ def test_partition():
             shape = tuple(range(2, 2 + ndims))
 
             def sfunc1(ndim):
-                s = range(2, ndim + 2)
+                s = list(range(2, ndim + 2))
                 data = np.arange(product(s)).reshape(s) + 2
                 if cls is MaskOperator:
                     data = (data % 2).astype(bool)
                 return data
 
             def sfunc2(ndim):
-                s = range(2 + ndims - ndim, 2 + ndims)
+                s = list(range(2 + ndims - ndim, 2 + ndims))
                 data = np.arange(product(s)).reshape(s) + 2
                 if cls is MaskOperator:
                     data = (data % 2).astype(bool)
diff --git a/test/test_core.py b/test/test_core.py
index 637076e..ddf4b01 100644
--- a/test/test_core.py
+++ b/test/test_core.py
@@ -8,7 +8,7 @@ import sys
 from nose import with_setup
 from nose.plugins.skip import SkipTest
 from numpy.testing import assert_equal
-from pyoperators import memory, flags
+from pyoperators import config, flags
 from pyoperators import (
     Operator, AdditionOperator, BlockColumnOperator, BlockDiagonalOperator,
     BlockRowOperator, BlockSliceOperator, CompositionOperator,  GroupOperator,
@@ -61,10 +61,6 @@ def assert_is_inttuple(shape, msg=''):
 def assert_square(op, msg=''):
     assert_flags(op, 'square', msg)
     assert_eq(op.shapein, op.shapeout)
-    assert_eq(op.reshapein, op.reshapeout)
-    assert_eq(op.validatein, op.validateout)
-    if op.shapein is None:
-        assert_eq(op.toshapein, op.toshapeout)
 
 SHAPES = (None, (), (1,), (3,), (2, 3))
 
@@ -151,6 +147,7 @@ def test_flags():
 def test_symmetric():
     mat = np.matrix([[2, 1], [1, 2]])
 
+    @flags.square
     @flags.symmetric
     class Op(Operator):
         def __init__(self):
@@ -345,12 +342,12 @@ def test_times_mul_or_comp():
         return _.flags.linear
 
     def func(x, y):
-        if np.__version__ < '1.9' and isinstance(x, np.ndarray):
+        if isinstance(x, np.ndarray):
             if isinstance(x, np.matrix):
                 x = DenseOperator(x)
             elif x.ndim > 0:
                 x = DiagonalOperator(x)
-        if scipy.__version__ < '0.14' and isinstance(x, csc_matrix):
+        if isinstance(x, csc_matrix):
             x = SparseOperator(x)
         if x is X.T and (y is np.sqrt or isinstance(y, SquareOperator)) or \
            y is X.T and not isscalarlike(x) and \
@@ -1358,16 +1355,16 @@ def test_inplace1():
 
 def setup_memory():
     global old_memory_tolerance, old_memory_verbose
-    old_memory_tolerance = memory.MEMORY_TOLERANCE
-    old_memory_verbose = memory.verbose
+    old_memory_tolerance = config.MEMORY_TOLERANCE
+    old_memory_verbose = config.VERBOSE
     # ensure buffers in the pool are always used
-    memory.MEMORY_TOLERANCE = np.inf
-    memory.verbose = True
+    config.MEMORY_TOLERANCE = np.inf
+    config.VERBOSE = True
 
 
 def teardown_memory():
-    memory.MEMORY_TOLERANCE = old_memory_tolerance
-    memory.verbose = old_memory_verbose
+    config.MEMORY_TOLERANCE = old_memory_tolerance
+    config.VERBOSE = old_memory_verbose
 
 
 @skiptest
diff --git a/test/test_delete.py b/test/test_delete.py
index 429feb1..243cd1c 100644
--- a/test/test_delete.py
+++ b/test/test_delete.py
@@ -1,7 +1,8 @@
 from __future__ import division
 import numpy as np
 from numpy.testing import assert_raises
-from pyoperators import DiagonalOperator, HomothetyOperator, Operator, memory
+from pyoperators import (
+    config, DiagonalOperator, HomothetyOperator, memory, Operator)
 from pyoperators.utils import setting
 from pyoperators.core import DeletedOperator
 
@@ -27,7 +28,7 @@ def test_collection_reset():
 
 
 def test_collection():
-    with setting(memory, 'GC_NBYTES_THRESHOLD', 8000):
+    with setting(config, 'GC_NBYTES_THRESHOLD', 8000):
         memory.garbage_collect()
         counter = 0
         for i in range(10):
diff --git a/test/test_linear.py b/test/test_linear.py
index 2004a56..79b2c6e 100644
--- a/test/test_linear.py
+++ b/test/test_linear.py
@@ -97,7 +97,7 @@ def test_integration_trapeze():
         def direct(self, input, output):
             output[...] = self.x ** (np.arange(input.size) + input)
 
-    value = range(3)
+    value = list(range(3))
     x = [0.5, 1, 2, 4]
     func_op = BlockColumnOperator([Op(_) for _ in x], new_axisout=0)
     eval_ = func_op(value)
@@ -246,8 +246,8 @@ def test_symmetric_band_toeplitz_operator():
                 [totoeplitz(n_, f_) for f_ in firstrow], new_axisin=0)
         ncorr = len(firstrow) - 1
         dense = np.zeros((n, n))
-        for i in xrange(n):
-            for j in xrange(n):
+        for i in range(n):
+            for j in range(n):
                 if abs(i-j) <= ncorr:
                     dense[i, j] = firstrow[abs(i-j)]
         return DenseOperator(dense, shapein=dense.shape[1])
diff --git a/test/test_memory.py b/test/test_memory.py
index 73ce545..3174436 100644
--- a/test/test_memory.py
+++ b/test/test_memory.py
@@ -3,7 +3,8 @@ from __future__ import division
 import itertools
 import numpy as np
 from numpy.testing import assert_equal
-from pyoperators.memory import MemoryPool, empty, MEMORY_ALIGNMENT
+from pyoperators.config import MEMORY_ALIGNMENT
+from pyoperators.memory import MemoryPool, empty
 from pyoperators.utils import tointtuple
 
 buffers = [empty(10), empty((5, 2)), empty(20)[::2], empty(11)[1:],
diff --git a/test/test_nbytes.py b/test/test_nbytes.py
index e2b19e3..570b49b 100644
--- a/test/test_nbytes.py
+++ b/test/test_nbytes.py
@@ -1,6 +1,7 @@
 from __future__ import division
 import numpy as np
 import scipy.sparse as sp
+import sys
 from numpy.testing import assert_equal
 from pyoperators import (
     AdditionOperator, BlockColumnOperator, BlockDiagonalOperator,
@@ -9,7 +10,7 @@ from pyoperators import (
 
 COMPOSITES = (AdditionOperator, BlockColumnOperator, BlockDiagonalOperator,
               BlockRowOperator, CompositionOperator, MultiplicationOperator)
-
+PYTHON2 = sys.version_info.major == 2
 
 class Op1(Operator):
     nbytes = 4
@@ -23,7 +24,7 @@ def test_sparse():
     D = np.arange(15, dtype=float).reshape(3, 5)
     matrices = (sp.coo_matrix, sp.bsr_matrix, sp.csc_matrix, sp.csr_matrix,
                 sp.dia_matrix, sp.dok_matrix)
-    expecteds = 224, 184, 192, 184, 308, 2688
+    expecteds = 224, 184, 192, 184, 308, 2688 if PYTHON2 else 2464
 
     def func(matrix, expected):
         op = SparseOperator(matrix(D))
diff --git a/test/test_proxy.py b/test/test_proxy.py
index 388e8a2..04a656a 100644
--- a/test/test_proxy.py
+++ b/test/test_proxy.py
@@ -99,9 +99,9 @@ def test_composite():
     global counter
     counter = 0
     proxy_lists = [get_operator(proxy_list, attr)
-                   for attr in '', 'C', 'T', 'H', 'I', 'IC', 'IT', 'IH']
+                   for attr in ('', 'C', 'T', 'H', 'I', 'IC', 'IT', 'IH')]
     ref_lists = [get_operator(ref_list, attr)
-                 for attr in '', 'C', 'T', 'H', 'I', 'IC', 'IT', 'IH']
+                 for attr in ('', 'C', 'T', 'H', 'I', 'IC', 'IT', 'IH')]
 
     op = AdditionOperator(CompositionOperator(_) for _ in zip(*proxy_lists))
     ref = AdditionOperator(CompositionOperator(_) for _ in zip(*ref_lists))
diff --git a/test/test_utils.py b/test/test_utils.py
index 56c1797..79f1a08 100644
--- a/test/test_utils.py
+++ b/test/test_utils.py
@@ -227,7 +227,7 @@ def test_interruptible():
 def test_is_scalar():
     def func(x):
         assert isscalarlike(x)
-    for x in (True, 1, 1., 'lkj', u'jj', np.array(1)):
+    for x in (True, 1, 1., 'lkj', 'jj', np.array(1)):
         yield func, x
 
 
@@ -307,9 +307,9 @@ def test_one_pi_zero():
 def test_pool_threading():
     try:
         import mkl
+        mkl_nthreads = mkl.get_max_threads()
     except ImportError:
         mkl = None
-    mkl_nthreads = mkl.get_max_threads()
     counter = None
 
     def func_thread(i):
@@ -331,15 +331,15 @@ def test_pool_threading():
     def func(env):
         global counter
         with env:
-            omp_num_threads = os.getenv('OMP_NUM_THREADS')
+            nthreads = os.getenv('OMP_NUM_THREADS')
             expected = omp_num_threads()
             with pool_threading() as pool:
                 assert_equal(int(os.environ['OMP_NUM_THREADS']), 1)
                 if mkl is not None:
                     assert_equal(mkl.get_max_threads(), 1)
                 counter = 0
-                pool.map(func_thread, xrange(pool._processes))
-            assert_equal(os.getenv('OMP_NUM_THREADS'), omp_num_threads)
+                pool.map(func_thread, range(pool._processes))
+            assert_equal(os.getenv('OMP_NUM_THREADS'), nthreads)
             if mkl is not None:
                 assert_equal(mkl.get_max_threads(), mkl_nthreads)
             assert_equal(counter, expected)

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/debian-science/packages/pyoperators.git



More information about the debian-science-commits mailing list