Index: tools/testase.py
===================================================================
--- tools/testase.py	(revision 0)
+++ tools/testase.py	(revision 1)
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+from sys import argv
+display = (len(argv) != 2 or argv[1] != '--no-display')
+from ase.test import test
+test(2, display=display)

Property changes on: tools/testase.py
___________________________________________________________________
Added: svn:executable
   + 

Index: tools/ag
===================================================================
--- tools/ag	(revision 0)
+++ tools/ag	(revision 1)
@@ -0,0 +1,5 @@
+#!/usr/bin/env python
+
+from ase.gui.ag import main
+
+main()

Property changes on: tools/ag
___________________________________________________________________
Added: svn:executable
   + 

Index: tools/ASE2ase.py
===================================================================
--- tools/ASE2ase.py	(revision 0)
+++ tools/ASE2ase.py	(revision 1)
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+import sys
+
+def convert(filename):
+    lines = open(filename).readlines()
+    t1 = ''.join(lines)
+
+    first = True
+    for i in range(len(lines)):
+        line = lines[i]
+        if line.startswith('from ASE'):
+            if first:
+                lines[i] = 'from ase.all import *\n'
+                first = False
+            else:
+                lines[i] = ''
+
+    t = ''.join(lines)
+
+    for old, new in [('GetCartesianPositions', 'get_positions'),
+                     ('SetCartesianPositions', 'set_positions'),
+                     ('GetPotentialEnergy', 'get_potential_energy'),
+                     ('SetCalculator', 'set_calculator'),
+                     ('GetScaledPositions', 'get_scaled_positions'),
+                     ('SetScaledPositions', 'set_scaled_positions'),
+                     ('SetUnitCell', 'set_cell'),
+                     ('GetUnitCell', 'get_cell'),
+                     ('GetBoundaryConditions', 'get_pbc'),
+                     ('GetCartesianForces', 'get_forces'),
+                     ('GetCartesianVelocities', 'get_velocities'),
+                     ('SetCartesianVelocities', 'set_velocities'),
+                     ('GetCartesianMomenta', 'get_momenta'),
+                     ('SetCartesianMomenta', 'set_momenta'),
+                     ('ListOfAtoms', 'Atoms'),
+                     ('periodic', 'pbc'),
+                     ('pbcity', 'periodicity'),
+                     ('.Converge(', '.run('),
+                     ('Repeat', 'repeat'),
+                     ('Numeric', 'numpy'),
+                     ('numpyal', 'Numerical'),
+                     ('GetAtomicNumber()', 'number'),
+                     ('GetChemicalSymbol()', 'symbol'),
+                     ('GetCartesianPosition()', 'position'),
+                     ('GetTag()', 'tag'),
+                     ('GetCharge()', 'charge'),
+                     ('GetMass()', 'mass'),
+                     ('GetCartesianMomentum()', 'momentum'),
+                     ('GetMagneticMoment()', 'magmom'),
+                     ]:
+        t = t.replace(old, new)
+
+    t2 = ''
+    while 1:
+        i = t.find('.')
+        i2 = t.find('def ')
+        if 0 <= i < i2:
+            n = 1
+        elif i2 != -1:
+            n = 4
+            i = i2
+        else:
+            break
+        t2 += t[:i + n]
+        t = t[i + n:]
+        if t[0].isupper() and t[1].islower():
+            j = t.find('(')
+            if j != -1 and t[2: j].isalpha():
+                for k in range(j):
+                    if t[k].isupper() and k > 0:
+                        t2 += '_'
+                    t2 += t[k].lower()
+                t = t[j:]
+
+    t2 += t
+
+    if t2 != t1:
+        print filename, len(t1) - len(t2)
+        open(filename + '.bak', 'w').write(t1)
+        open(filename, 'w').write(t2)
+
+for filename in sys.argv[1:]:
+    convert(filename)

Property changes on: tools/ASE2ase.py
___________________________________________________________________
Added: svn:executable
   + 

Index: setup.py
===================================================================
--- setup.py	(revision 0)
+++ setup.py	(revision 1)
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2007  CAMP
+# Please see the accompanying LICENSE file for further information.
+
+from distutils.core import setup
+from glob import glob
+from os.path import join
+
+import os
+import sys
+
+long_description = """\
+ASE is a python package providing an open source Atomic Simulation
+Environment in the python scripting language."""
+
+
+if sys.version_info < (2, 3, 0, 'final', 0):
+    raise SystemExit, 'Python 2.3 or later is required!'
+
+packages = ['ase',
+            'ase.io',
+            'ase.md',
+            'ase.dft',
+            'ase.gui',
+            'ase.gui.languages',
+            'ase.data',
+            'ase.test',
+            'ase.utils',
+            'ase.lattice',
+            'ase.lattice.spacegroup',
+            'ase.examples',
+            'ase.optimize',
+            'ase.optimize.test',
+            'ase.visualize',
+            'ase.visualize.vtk',
+            'ase.transport',
+            'ase.calculators',
+            'ase.calculators.jacapo']
+
+package_dir={'ase': 'ase'}
+
+package_data={'ase': ['lattice/spacegroup/spacegroup.dat']}
+
+# Get the current version number:
+execfile('ase/svnversion_io.py')  # write ase/svnversion.py and get svnversion
+execfile('ase/version.py')        # get version_base
+if svnversion:
+    version = version_base + '.' + svnversion
+else:
+    version = version_base
+
+setup(name = 'python-ase',
+      version=version,
+      description='Atomic Simulation Environment',
+      url='https://wiki.fysik.dtu.dk/ase',
+      maintainer='CAMd',
+      maintainer_email='camd@fysik.dtu.dk',
+      license='LGPLv2.1+',
+      platforms=['linux'],
+      packages=packages,
+      package_dir=package_dir,
+      package_data=package_data,
+      scripts=['tools/ag', 'tools/ASE2ase.py', 'tools/testase.py'],
+      long_description=long_description)

Property changes on: setup.py
___________________________________________________________________
Added: svn:executable
   + 

Index: ase/test/n2.py
===================================================================
--- ase/test/n2.py	(revision 0)
+++ ase/test/n2.py	(revision 1)
@@ -0,0 +1,8 @@
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.optimize import QuasiNewton
+
+n2 = Atoms('N2', positions=[(0, 0, 0), (0, 0, 1.1)],
+           calculator=EMT())
+QuasiNewton(n2).run(0.01)
+print n2.get_distance(0, 1), n2.get_potential_energy()
Index: ase/test/vib.py
===================================================================
--- ase/test/vib.py	(revision 0)
+++ ase/test/vib.py	(revision 1)
@@ -0,0 +1,21 @@
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.optimize import QuasiNewton
+from ase.vibrations import Vibrations
+from ase.thermochemistry import IdealGasThermo
+
+n2 = Atoms('N2',
+           positions=[(0, 0, 0), (0, 0, 1.1)],
+           calculator=EMT())
+QuasiNewton(n2).run(fmax=0.01)
+vib = Vibrations(n2)
+vib.run()
+print vib.get_frequencies()
+vib.summary()
+print vib.get_mode(-1)
+vib.write_mode(-1, nimages=20)
+vib_energies = vib.get_energies()
+
+thermo = IdealGasThermo(vib_energies=vib_energies, geometry='linear', 
+                        atoms=n2, symmetrynumber=2, spin=0)
+thermo.get_free_energy(temperature=298.15, pressure=2*101325.)
Index: ase/test/center.py
===================================================================
--- ase/test/center.py	(revision 0)
+++ ase/test/center.py	(revision 1)
@@ -0,0 +1,82 @@
+"Test that atoms.center() works when adding vacuum ()"
+
+import numpy as np
+from math import pi, sqrt, cos
+from ase import data
+from ase.lattice.cubic import FaceCenteredCubic
+
+def checkang(a, b, phi):
+    "Check the angle between two vectors."
+    cosphi = np.dot(a,b) / sqrt(np.dot(a,a) * np.dot(b,b))
+    assert np.abs(cosphi - cos(phi)) < 1e-10
+
+symb = "Cu"
+Z = data.atomic_numbers[symb]
+a0 = data.reference_states[Z]['a']
+
+# (100) oriented block
+atoms = FaceCenteredCubic(size=(5,5,5), symbol="Cu", pbc=(1,1,0))
+assert len(atoms) == 5*5*5*4
+c = atoms.get_cell()
+checkang(c[0], c[1], pi/2)
+checkang(c[0], c[2], pi/2)
+checkang(c[1], c[2], pi/2)
+assert np.abs(5 * a0 - c[2,2]) < 1e-10
+
+# Add vacuum in one direction
+vac = 10.0
+atoms.center(axis=2, vacuum=vac)
+c = atoms.get_cell()
+checkang(c[0], c[1], pi/2)
+checkang(c[0], c[2], pi/2)
+checkang(c[1], c[2], pi/2)
+assert np.abs(4.5 * a0 + 2* vac - c[2,2]) < 1e-10
+
+# Add vacuum in all directions
+vac = 4.0
+atoms.center(vacuum=vac)
+c = atoms.get_cell()
+checkang(c[0], c[1], pi/2)
+checkang(c[0], c[2], pi/2)
+checkang(c[1], c[2], pi/2)
+assert np.abs(4.5 * a0 + 2* vac - c[0,0]) < 1e-10
+assert np.abs(4.5 * a0 + 2* vac - c[1,1]) < 1e-10
+assert np.abs(4.5 * a0 + 2* vac - c[2,2]) < 1e-10
+
+# Now a general unit cell
+atoms = FaceCenteredCubic(size=(5,5,5), directions=[[1,0,0], [0,1,0], [1,0,1]],
+                          symbol="Cu", pbc=(1,1,0))
+assert len(atoms) == 5*5*5*2
+c = atoms.get_cell()
+checkang(c[0], c[1], pi/2)
+checkang(c[0], c[2], pi/4)
+checkang(c[1], c[2], pi/2)
+assert np.abs(2.5 * a0 - c[2,2]) < 1e-10
+
+# Add vacuum in one direction
+vac = 10.0
+atoms.center(axis=2, vacuum=vac)
+c = atoms.get_cell()
+checkang(c[0], c[1], pi/2)
+checkang(c[0], c[2], pi/4)
+checkang(c[1], c[2], pi/2)
+assert np.abs(2 * a0 + 2* vac - c[2,2]) < 1e-10
+
+# Recenter without specifying vacuum
+atoms.center()
+c = atoms.get_cell()
+checkang(c[0], c[1], pi/2)
+checkang(c[0], c[2], pi/4)
+checkang(c[1], c[2], pi/2)
+assert np.abs(2 * a0 + 2* vac - c[2,2]) < 1e-10
+
+# Add vacuum in all directions
+vac = 4.0
+atoms.center(vacuum=vac)
+c = atoms.get_cell()
+checkang(c[0], c[1], pi/2)
+checkang(c[0], c[2], pi/4)
+checkang(c[1], c[2], pi/2)
+assert np.abs(4.5 * a0 + 2* vac - c[1,1]) < 1e-10
+assert np.abs(2 * a0 + 2* vac - c[2,2]) < 1e-10
+
Index: ase/test/crystal.py
===================================================================
--- ase/test/crystal.py	(revision 0)
+++ ase/test/crystal.py	(revision 1)
@@ -0,0 +1,63 @@
+import numpy as np
+
+from ase.lattice.spacegroup import crystal
+
+
+# A diamond unit cell
+diamond = crystal('C', [(0,0,0)], spacegroup=227, 
+                  cellpar=[3.57, 3.57, 3.57, 90, 90, 90])
+
+assert len(diamond) == 8
+correct_pos = np.array([[ 0.  ,  0.  ,  0.  ],
+                        [ 0.  ,  0.5 ,  0.5 ],
+                        [ 0.5 ,  0.5 ,  0.  ],
+                        [ 0.5 ,  0.  ,  0.5 ],
+                        [ 0.75,  0.25,  0.75],
+                        [ 0.25,  0.25,  0.25],
+                        [ 0.25,  0.75,  0.75],
+                        [ 0.75,  0.75,  0.25]])
+assert np.allclose(diamond.get_scaled_positions(), correct_pos)
+
+
+
+# A CoSb3 skutterudite unit cell containing 32 atoms
+skutterudite = crystal(('Co', 'Sb'), 
+    basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)], 
+    spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90])
+
+assert len(skutterudite) == 32
+
+correct_pos = np.array([[ 0.25 ,  0.25 ,  0.25 ],
+                        [ 0.75 ,  0.75 ,  0.25 ],
+                        [ 0.75 ,  0.25 ,  0.75 ],
+                        [ 0.25 ,  0.75 ,  0.75 ],
+                        [ 0.75 ,  0.75 ,  0.75 ],
+                        [ 0.25 ,  0.25 ,  0.75 ],
+                        [ 0.25 ,  0.75 ,  0.25 ],
+                        [ 0.75 ,  0.25 ,  0.25 ],
+                        [ 0.   ,  0.335,  0.158],
+                        [ 0.   ,  0.665,  0.158],
+                        [ 0.   ,  0.335,  0.842],
+                        [ 0.   ,  0.665,  0.842],
+                        [ 0.158,  0.   ,  0.335],
+                        [ 0.158,  0.   ,  0.665],
+                        [ 0.842,  0.   ,  0.335],
+                        [ 0.842,  0.   ,  0.665],
+                        [ 0.335,  0.158,  0.   ],
+                        [ 0.665,  0.158,  0.   ],
+                        [ 0.335,  0.842,  0.   ],
+                        [ 0.665,  0.842,  0.   ],
+                        [ 0.5  ,  0.835,  0.658],
+                        [ 0.5  ,  0.165,  0.658],
+                        [ 0.5  ,  0.835,  0.342],
+                        [ 0.5  ,  0.165,  0.342],
+                        [ 0.658,  0.5  ,  0.835],
+                        [ 0.658,  0.5  ,  0.165],
+                        [ 0.342,  0.5  ,  0.835],
+                        [ 0.342,  0.5  ,  0.165],
+                        [ 0.835,  0.658,  0.5  ],
+                        [ 0.165,  0.658,  0.5  ],
+                        [ 0.835,  0.342,  0.5  ],
+                        [ 0.165,  0.342,  0.5  ]])
+
+assert np.allclose(skutterudite.get_scaled_positions(), correct_pos)
Index: ase/test/unitcellfilter.py
===================================================================
--- ase/test/unitcellfilter.py	(revision 0)
+++ ase/test/unitcellfilter.py	(revision 1)
@@ -0,0 +1,36 @@
+from math import sqrt
+from ase import Atoms
+from ase.optimize.lbfgs import LBFGS
+from ase.constraints import StrainFilter, UnitCellFilter
+from ase.io import PickleTrajectory
+from ase.optimize.lbfgs import LBFGS
+from ase.optimize.mdmin import MDMin
+try:
+    from asap3 import EMT
+except ImportError:
+    pass
+else:
+    a = 3.6
+    b = a / 2
+    cu = Atoms('Cu', cell=[(0,b,b),(b,0,b),(b,b,0)], pbc=1) * (6, 6, 6)
+    cu.set_calculator(EMT())
+    f = UnitCellFilter(cu, [1, 1, 1, 0, 0, 0])
+    opt = LBFGS(f)
+    t = PickleTrajectory('Cu-fcc.traj', 'w', cu)
+    opt.attach(t)
+    opt.run(5.0)
+
+    # HCP:
+    from ase.lattice.surface import hcp0001
+    cu = hcp0001('Cu', (1, 1, 2), a=a / sqrt(2))
+    cu.cell[1,0] += 0.05
+    cu *= (6, 6, 3)
+    cu.set_calculator(EMT())
+    print cu.get_forces()
+    print cu.get_stress()
+    f = UnitCellFilter(cu)
+    opt = MDMin(f,dt=0.01)
+    t = PickleTrajectory('Cu-hcp.traj', 'w', cu)
+    opt.attach(t)
+    opt.run(0.2)
+
Index: ase/test/trajectory.py
===================================================================
--- ase/test/trajectory.py	(revision 0)
+++ ase/test/trajectory.py	(revision 1)
@@ -0,0 +1,36 @@
+import os
+from ase import Atom, Atoms
+from ase.io import PickleTrajectory
+
+co = Atoms([Atom('C', (0, 0, 0)),
+            Atom('O', (0, 0, 1.2))])
+traj = PickleTrajectory('1.traj', 'w', co)
+for i in range(5):
+    co.positions[:, 2] += 0.1
+    traj.write()
+del traj
+traj = PickleTrajectory('1.traj', 'a')
+co = traj[-1]
+print co.positions
+co.positions[:] += 1
+traj.write(co)
+del traj
+t = PickleTrajectory('1.traj', 'a')
+print t[-1].positions
+print '.--------'
+for a in t:
+    print 1, a.positions[-1,2]
+co.positions[:] += 1
+t.write(co)
+for a in t:
+    print 2, a.positions[-1,2]
+assert len(t) == 7
+
+# append to a nonexisting file
+fname = '2.traj'
+if os.path.isfile(fname):
+    os.remove(fname)
+t = PickleTrajectory(fname, 'a', co)
+del(t)
+os.remove(fname)
+
Index: ase/test/vtk_pipeline.py
===================================================================
--- ase/test/vtk_pipeline.py	(revision 0)
+++ ase/test/vtk_pipeline.py	(revision 1)
@@ -0,0 +1,452 @@
+#!/usr/bin/env python
+
+from ase.visualize.vtk import requirevtk
+
+requirevtk()
+
+import sys, unittest
+from ase.test import CustomTestCase, CustomTextTestRunner
+
+from vtk import vtkContourFilter, vtkPolyDataNormals, \
+                vtkLinearSubdivisionFilter, vtkPolyDataMapper
+from ase.visualize.vtk.pipeline import vtkPolyDataPipeline
+
+# -------------------------------------------------------------------
+
+class UTPipeline(CustomTestCase):
+    """
+    Abstract test case class - TODO."""
+
+    def assertConnected(self, vtk_one, vtk_two, port=0):
+        self.assertEqual(vtk_two.GetNumberOfInputConnections(port), 1)
+        self.assertEqual(vtk_one.GetOutputPort(),
+                         vtk_two.GetInputConnection(port, 0))
+
+    def assertNotConnected(self, vtk_one, vtk_two, port=0):
+        self.assertEqual(vtk_two.GetNumberOfInputConnections(port), 0)
+
+
+# -------------------------------------------------------------------
+
+class UTPolyDataPipeline(UTPipeline):
+    """
+    TODO"""
+
+    def setUp(self):
+        self.vtk_iso = vtkContourFilter()
+        #self.vtk_iso.SetInput(...)
+
+        self.vtk_dnorm = vtkPolyDataNormals()
+        self.vtk_subdiv = vtkLinearSubdivisionFilter()
+        self.vtk_dmap = vtkPolyDataMapper()
+
+    def tearDown(self):
+        del self.vtk_dmap
+        del self.vtk_subdiv
+        del self.vtk_dnorm
+        del self.vtk_iso
+
+# -------------------------------------------------------------------
+
+class UTPolyDataPipeline_PureVTK(UTPolyDataPipeline):
+    """
+    Consistency tests for pure-VTK objects with the purpose
+    of ultimately mapping the polygonal data within the pipeline."""
+
+    # =================================
+
+    def test_consistent_data_oninit(self):
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+
+        self.assertTrue(pipeline.hasfilters())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_data_direct(self):
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.set_data(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+
+        self.assertTrue(pipeline.hasfilters())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_data_postponed(self):
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertTrue(pipeline.hasfilters())
+        self.assertNotConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.set_data(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertTrue(pipeline.hasdata())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_data_onclose(self):
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertTrue(pipeline.hasfilters())
+        self.assertNotConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertTrue(pipeline.isclosed())
+
+        pipeline.set_data(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+    # =================================
+
+    def test_failure_duplicate_data(self):
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        self.assertRaises(ValueError, pipeline.append, self.vtk_iso)
+
+    def test_failure_duplicate_mixed(self):
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        pipeline.append(self.vtk_dnorm)
+        self.assertRaises(ValueError, pipeline.append, self.vtk_iso)
+
+    def test_failure_duplicate_cyclic(self):
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        pipeline.append(self.vtk_dnorm)
+        pipeline.append(self.vtk_subdiv)
+        self.assertRaises(ValueError, pipeline.append, self.vtk_dnorm)
+
+    def test_failure_duplicate_filter(self):
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        pipeline.append(self.vtk_dnorm)
+        self.assertRaises(ValueError, pipeline.append, self.vtk_dnorm)
+
+    # =================================
+
+    def test_failure_output_missing(self):
+        pipeline = vtkPolyDataPipeline()
+        self.assertRaises(RuntimeError, pipeline.get_output_port)
+
+    def test_failure_output_closed(self):
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        pipeline.append(self.vtk_dmap)
+        self.assertRaises(RuntimeError, pipeline.append, self.vtk_dnorm)
+
+# -------------------------------------------------------------------
+
+class UTPolyDataPipeline_PipelineVTK(UTPolyDataPipeline):
+    """
+    Consistency tests for mixed VTK objects and pipelines with the purpose
+    of ultimately mapping the polygonal data through embedded pipelines."""
+
+    # =================================
+
+    def get_consistent_datapipe(self):
+        datapipe = vtkPolyDataPipeline(self.vtk_iso)
+        self.assertTrue(datapipe.hasdata())
+        self.assertTrue(self.vtk_iso in datapipe)
+        self.assertEqual(datapipe.vtkish_data, self.vtk_iso)
+        self.assertFalse(datapipe.hasfilters())
+        self.assertFalse(datapipe.isclosed())
+
+        return datapipe
+
+    def test_consistent_datapipe_oninit(self):
+        datapipe = self.get_consistent_datapipe()
+
+        pipeline = vtkPolyDataPipeline(datapipe)
+        self.assertTrue(pipeline.hasdata())
+        self.assertTrue(datapipe in pipeline)
+        self.assertTrue(self.vtk_iso in pipeline)
+        self.assertEqual(pipeline.vtkish_data, datapipe)
+        self.assertRaises(RuntimeError, datapipe.isclosed)
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+
+        self.assertTrue(pipeline.hasfilters())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_datapipe_direct(self):
+        datapipe = self.get_consistent_datapipe()
+
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(datapipe in pipeline)
+        self.assertFalse(self.vtk_iso in pipeline)
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.set_data(datapipe)
+        self.assertTrue(pipeline.hasdata())
+        self.assertTrue(datapipe in pipeline)
+        self.assertTrue(self.vtk_iso in pipeline)
+        self.assertEqual(pipeline.vtkish_data, datapipe)
+        self.assertRaises(RuntimeError, datapipe.isclosed)
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+
+        self.assertTrue(pipeline.hasfilters())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_datapipe_postponed(self):
+        datapipe = self.get_consistent_datapipe()
+
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(datapipe in pipeline)
+        self.assertFalse(self.vtk_iso in pipeline)
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertTrue(pipeline.hasfilters())
+        self.assertNotConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.set_data(datapipe)
+        self.assertTrue(pipeline.hasdata())
+        self.assertTrue(datapipe in pipeline)
+        self.assertTrue(self.vtk_iso in pipeline)
+        self.assertEqual(pipeline.vtkish_data, datapipe)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+        self.assertTrue(datapipe.isclosed())
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertTrue(pipeline.hasdata())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_datapipe_onclose(self):
+        datapipe = self.get_consistent_datapipe()
+
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(datapipe in pipeline)
+        self.assertFalse(self.vtk_iso in pipeline)
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dnorm)
+        self.assertTrue(pipeline.hasfilters())
+        self.assertNotConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertTrue(pipeline.isclosed())
+
+        pipeline.set_data(datapipe)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, datapipe)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+        self.assertTrue(datapipe.isclosed())
+
+    # =================================
+
+    def get_consistent_filterpipe(self):
+        filterpipe = vtkPolyDataPipeline()
+        self.assertFalse(filterpipe.hasdata())
+        self.assertFalse(self.vtk_iso in filterpipe)
+        self.assertFalse(filterpipe.hasfilters())
+        self.assertFalse(filterpipe.isclosed())
+
+        filterpipe.append(self.vtk_dnorm)
+        self.assertTrue(filterpipe.hasfilters())
+        self.assertNotConnected(self.vtk_iso, self.vtk_dnorm)
+
+        filterpipe.append(self.vtk_subdiv)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        return filterpipe
+
+    def test_consistent_filterpipe_oninit(self):
+        filterpipe = self.get_consistent_filterpipe()
+
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(filterpipe)
+        self.assertRaises(RuntimeError, filterpipe.isclosed)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dmap)
+        self.assertTrue(filterpipe.isclosed())
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertTrue(pipeline.hasfilters())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_filterpipe_direct(self):
+        filterpipe = self.get_consistent_filterpipe()
+
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.set_data(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+
+        pipeline.append(filterpipe)
+        self.assertRaises(RuntimeError, filterpipe.isclosed)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(self.vtk_dmap)
+        self.assertTrue(filterpipe.isclosed())
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertTrue(pipeline.hasfilters())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_filterpipe_postponed(self):
+        filterpipe = self.get_consistent_filterpipe()
+
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(filterpipe)
+        self.assertRaises(RuntimeError, filterpipe.isclosed)
+        self.assertNotConnected(self.vtk_iso, self.vtk_dnorm)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.set_data(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertTrue(filterpipe.isclosed())
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertTrue(pipeline.hasdata())
+        self.assertTrue(pipeline.isclosed())
+
+    def test_consistent_filterdata_onclose(self):
+        filterpipe = self.get_consistent_filterpipe()
+
+        pipeline = vtkPolyDataPipeline()
+        self.assertFalse(pipeline.hasdata())
+        self.assertFalse(pipeline.hasfilters())
+        self.assertFalse(pipeline.isclosed())
+
+        pipeline.append(filterpipe)
+        self.assertRaises(RuntimeError, filterpipe.isclosed)
+        self.assertNotConnected(self.vtk_iso, self.vtk_dnorm)
+        self.assertConnected(self.vtk_dnorm, self.vtk_subdiv)
+
+        pipeline.append(self.vtk_dmap)
+        self.assertTrue(filterpipe.isclosed())
+        self.assertConnected(self.vtk_subdiv, self.vtk_dmap)
+        self.assertFalse(pipeline.hasdata())
+        self.assertTrue(pipeline.isclosed())
+
+        pipeline.set_data(self.vtk_iso)
+        self.assertTrue(pipeline.hasdata())
+        self.assertEqual(pipeline.vtkish_data, self.vtk_iso)
+        self.assertConnected(self.vtk_iso, self.vtk_dnorm)
+
+    # =================================
+
+    def test_failure_recursive_data(self):
+        pipeline = vtkPolyDataPipeline()
+        self.assertRaises(ValueError, pipeline.set_data, pipeline)
+
+    def test_failure_recursive_mixed(self):
+        pipeline = vtkPolyDataPipeline(self.vtk_iso)
+        pipeline.append(self.vtk_dnorm)
+        self.assertRaises(ValueError, pipeline.append, pipeline)
+
+    def test_failure_recursive_filter(self):
+        pipeline = vtkPolyDataPipeline()
+        self.assertRaises(ValueError, pipeline.append, pipeline)
+
+
+# -------------------------------------------------------------------
+
+if __name__ in ['__main__', '__builtin__']:
+    # We may have been imported by test.py, if so we should redirect to logfile
+    if __name__ == '__builtin__':
+        testrunner = CustomTextTestRunner('vtk_pipeline.log', verbosity=2)
+    else:
+        testrunner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2)
+
+    testcases = [UTPolyDataPipeline_PureVTK, UTPolyDataPipeline_PipelineVTK]
+
+    for test in testcases:
+        info = '\n' + test.__name__ + '\n' + test.__doc__.strip('\n') + '\n'
+        testsuite = unittest.defaultTestLoader.loadTestsFromTestCase(test)
+        testrunner.stream.writeln(info)
+        testresult = testrunner.run(testsuite)
+        # Provide feedback on failed tests if imported by test.py
+        if __name__ == '__builtin__' and not testresult.wasSuccessful():
+            raise SystemExit('Test failed. Check vtk_pipeline.log for details.')
+

Property changes on: ase/test/vtk_pipeline.py
___________________________________________________________________
Added: svn:executable
   + 

Index: ase/test/vasp_co.py
===================================================================
--- ase/test/vasp_co.py	(revision 0)
+++ ase/test/vasp_co.py	(revision 1)
@@ -0,0 +1,59 @@
+#!/usr/bin/python
+
+"""
+Run some VASP tests to ensure that the VASP calculator works. This
+is conditional on the existence of the VASP_COMMAND or VASP_SCRIPT
+environment variables
+
+"""
+
+from ase.test import NotAvailable
+import os
+
+vcmd = os.getenv('VASP_COMMAND')
+vscr = os.getenv('VASP_SCRIPT')
+if vcmd == None and vscr == None:
+    raise NotAvailable('Neither VASP_COMMAND nor VASP_SCRIPT defined')
+
+from ase import Atoms
+from ase.calculators.vasp import Vasp
+import numpy as np
+
+def array_almost_equal(a1, a2, tol=np.finfo(type(1.0)).eps):
+    """Replacement for old numpy.testing.utils.array_almost_equal."""
+    return (np.abs(a1 - a2) < tol).all()
+
+d = 1.14
+co = Atoms('CO', positions=[(0, 0, 0), (0, 0, d)],
+              pbc=True)
+co.center(vacuum=5.)
+
+calc = Vasp(
+            xc = 'PBE',
+            prec = 'Low',
+            algo = 'Fast',
+            ismear= 0,
+            sigma = 1.,
+            lwave = False,
+            lcharg = False)
+
+co.set_calculator(calc)
+en = co.get_potential_energy()
+assert abs(en + 14.918933) < 1e-4
+
+# Secondly, check that restart from the previously created VASP output works
+
+calc2 = Vasp(restart=True)
+co2 = calc2.get_atoms()
+
+# Need tolerance of 1e-14 because VASP itself changes coordinates
+# slightly between reading POSCAR and writing CONTCAR even if no ionic
+# steps are made.
+assert array_almost_equal(co.positions, co2.positions, 1e-14)
+
+assert en - co2.get_potential_energy() == 0.
+assert array_almost_equal(calc.get_stress(co), calc2.get_stress(co2))
+assert array_almost_equal(calc.get_forces(co), calc2.get_forces(co2))
+assert array_almost_equal(calc.get_eigenvalues(), calc2.get_eigenvalues())
+assert calc.get_number_of_bands() == calc2.get_number_of_bands()
+assert calc.get_xc_functional() == calc2.get_xc_functional()
Index: ase/test/example.py
===================================================================
--- ase/test/example.py	(revision 0)
+++ ase/test/example.py	(revision 1)
@@ -0,0 +1,25 @@
+from ase.all import *
+
+atoms = Atoms('H7',
+              positions=[(0, 0, 0),
+                         (1, 0, 0),
+                         (0, 1, 0),
+                         (1, 1, 0),
+                         (0, 2, 0),
+                         (1, 2, 0),
+                         (0.5, 0.5, 1)],
+              constraint=[FixAtoms(range(6))],
+              calculator=LennardJones())
+
+traj = PickleTrajectory('H.traj', 'w', atoms)
+dyn = QuasiNewton(atoms, maxstep=0.2)
+dyn.attach(traj.write)
+dyn.run(fmax=0.01, steps=100)
+
+try:
+    del atoms[-1]
+except RuntimeError:
+    pass
+else:
+    raise RuntimeError
+
Index: ase/test/things.py
===================================================================
--- ase/test/things.py	(revision 0)
+++ ase/test/things.py	(revision 1)
@@ -0,0 +1,8 @@
+from ase.dft import monkhorst_pack
+
+assert [0, 0, 0] in  monkhorst_pack((1, 3, 5)).tolist()
+assert [0, 0, 0] not in  monkhorst_pack((1, 3, 6)).tolist()
+assert len(monkhorst_pack((3, 4, 6))) == 3 * 4 * 6
+
+from ase.units import Hartree, Bohr, kJ, mol, kcal, kB, fs
+print Hartree, Bohr, kJ/mol, kcal/mol, kB*300, fs, 1/fs
Index: ase/test/vtk_data.py
===================================================================
--- ase/test/vtk_data.py	(revision 0)
+++ ase/test/vtk_data.py	(revision 1)
@@ -0,0 +1,481 @@
+#!/usr/bin/env python
+
+from ase.visualize.vtk import requirevtk, probe_vtk_kilobyte
+
+requirevtk()
+vtk_kilobyte = probe_vtk_kilobyte(1024)
+
+import numpy as np
+
+import sys, unittest, gc
+from ase.test import CustomTestCase, CustomTextTestRunner
+from ase.utils.memory import MemoryStatistics, MemorySingleton, shapeopt
+
+from vtk import vtkDataArray
+from ase.visualize.vtk.data import vtkFloatArrayFromNumPyArray, \
+                                   vtkDoubleArrayFromNumPyArray, \
+                                   vtkFloatArrayFromNumPyMultiArray, \
+                                   vtkDoubleArrayFromNumPyMultiArray
+
+# -------------------------------------------------------------------
+
+class UTConversionDataArrayNumPy(CustomTestCase):
+    """
+    Abstract class with test cases for VTK/NumPy data conversion.
+
+    Leak tests the six possible permutations of deletion order for the
+    objects involved in conversion between VTK and NumPy data formats.
+
+    Objects:
+
+    conv: instance of vtkDataArrayFromNumPyBuffer of subclass thereof
+        The object in charge of the conversion
+    data: NumPy array
+        NumPy data with a specific memory footprint
+    vtk_da: instance of vtkDataArray of subclass thereof
+        VTK data array with a similar memory footprint
+
+    Permutations:
+
+    Case A: 012 i.e. deletion order is conv, data, vtk_da
+    Case B: 021 i.e. deletion order is conv, vtk_da, data
+    Case C: 102 i.e. deletion order is data, conv, vtk_da
+    Case D: 120 i.e. deletion order is data, vtk_da, conv
+    Case E: 201 i.e. deletion order is vtk_da, conv, data
+    Case F: 210 i.e. deletion order is vtk_da, data, conv
+    """
+    footprint = 100*1024**2
+    dtype = None
+    verbose = 0
+    gc_threshold = (300,5,5) #default is (700,10,10)
+    gc_flags = gc.DEBUG_LEAK # | gc.DEBUG_STATS
+    ctol = -7 #10MB
+    etol = -7 #10MB
+
+    def setUp(self):
+        self.mem_ini = MemorySingleton(self.verbose-1)
+        self.mem_ref = MemoryStatistics(self.verbose-1)
+        self.mem_cur = self.mem_ref.copy()
+
+        self.gc_threshold_old = gc.get_threshold()
+        self.gc_flags_old = gc.get_debug()
+        gc.set_threshold(*self.gc_threshold)
+        gc.set_debug(self.gc_flags)
+
+        # Try to obtain a clean slate
+        gc.collect()
+        self.gc_count = len(gc.garbage)
+        del gc.garbage[:]
+
+    def tearDown(self):
+        gc.collect()
+        self.assertEqual(len(gc.garbage), self.gc_count)
+        if len(gc.garbage)>0:
+            if self.verbose>1: print gc.get_objects()
+            #TODO be pedantic and fail?
+        del gc.garbage[:]
+        gc.set_threshold(*self.gc_threshold_old)
+        gc.set_debug(self.gc_flags_old)
+
+    def assertAlmostConsumed(self, bytes, digits=0, key='VmSize'):
+        self.mem_cur.update()
+        dm = self.mem_cur-self.mem_ref
+        self.assertAlmostEqual(dm[key], bytes, digits)
+
+    def assertAlmostExceeded(self, bytes, digits=0, key='VmPeak'):
+        self.mem_cur.update()
+        dm = self.mem_cur-self.mem_ini
+        #self.assertAlmostEqual(dm[key], bytes, digits) #TODO what really?
+        #self.assertAlmostEqual(max(0, dm[key]-bytes), 0, digits) #TODO ???
+        #dm = 200 MB, bytes = 100MB     ok
+        #dm = 101 MB, bytes = 100MB     ok
+        #dm = 0 MB, bytes = 100MB       bad
+
+    def convert_to_vtk_array(self, data):
+        """Convert an ndarray to a VTK data array.
+
+         data: NumPy array
+            NumPy data with a specific memory footprint
+        """
+
+        raise RuntimeError('Virtual member function.')
+
+    def get_leaktest_scenario(self):
+        """Construct the necessary conversion objects for leak testing.
+
+        Returns tuple of the form (conv, data, vtk_da,) where:
+
+        conv: instance of vtkDataArrayFromNumPyBuffer of subclass thereof
+            The object in charge of the conversion
+        data: NumPy array
+            NumPy data with a specific memory footprint
+        vtk_da: instance of vtkDataArray of subclass thereof
+            VTK data array with a similar memory footprint
+        """
+
+        raise RuntimeError('Virtual member function.')
+
+    # =================================
+
+    def test_deletion_case_a(self):
+        # Case A: 012 i.e. deletion order is conv, data, vtk_da
+        (conv, data, vtk_da,) = self.get_leaktest_scenario()
+
+        # Conversion cleanup
+        del conv
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion cleanup=', self.mem_cur-self.mem_ref
+
+        # NumPy cleanup
+        del data
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy cleanup=', self.mem_cur-self.mem_ref
+
+        # VTK cleanup
+        del vtk_da
+        self.assertAlmostConsumed(0, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'VTK cleanup=', self.mem_cur-self.mem_ref
+
+    def test_deletion_case_b(self):
+        # Case B: 021 i.e. deletion order is conv, vtk_da, data
+        (conv, data, vtk_da,) = self.get_leaktest_scenario()
+
+        # Conversion cleanup
+        del conv
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion cleanup=', self.mem_cur-self.mem_ref
+
+        # VTK cleanup
+        del vtk_da
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'VTK cleanup=', self.mem_cur-self.mem_ref
+
+        # Numpy cleanup
+        del data
+        self.assertAlmostConsumed(0, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy cleanup=', self.mem_cur-self.mem_ref
+
+    def test_deletion_case_c(self):
+        # Case C: 102 i.e. deletion order is data, conv, vtk_da
+        (conv, data, vtk_da,) = self.get_leaktest_scenario()
+
+        # NumPy cleanup
+        del data
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy cleanup=', self.mem_cur-self.mem_ref
+
+        # Conversion cleanup
+        del conv
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion cleanup=', self.mem_cur-self.mem_ref
+
+        # VTK cleanup
+        del vtk_da
+        self.assertAlmostConsumed(0, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'VTK cleanup=', self.mem_cur-self.mem_ref
+
+    def test_deletion_case_d(self):
+        # Case D: 120 i.e. deletion order is data, vtk_da, conv
+        (conv, data, vtk_da,) = self.get_leaktest_scenario()
+
+        # NumPy cleanup
+        del data
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy cleanup=', self.mem_cur-self.mem_ref
+
+        # VTK cleanup
+        del vtk_da
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'VTK cleanup=', self.mem_cur-self.mem_ref
+
+        # Conversion cleanup
+        del conv
+        self.assertAlmostConsumed(0, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion cleanup=', self.mem_cur-self.mem_ref
+
+    def test_deletion_case_e(self):
+        # Case E: 201 i.e. deletion order is vtk_da, conv, data
+        (conv, data, vtk_da,) = self.get_leaktest_scenario()
+
+        # VTK cleanup
+        del vtk_da
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'VTK cleanup=', self.mem_cur-self.mem_ref
+
+        # Conversion cleanup
+        del conv
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion cleanup=', self.mem_cur-self.mem_ref
+
+        # NumPy cleanup
+        del data
+        self.assertAlmostConsumed(0, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy cleanup=', self.mem_cur-self.mem_ref
+
+    def test_deletion_case_f(self):
+        # Case F: 210 i.e. deletion order is vtk_da, data, conv
+        (conv, data, vtk_da,) = self.get_leaktest_scenario()
+
+        # VTK cleanup
+        del vtk_da
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'VTK cleanup=', self.mem_cur-self.mem_ref
+
+        # NumPy cleanup
+        del data
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy cleanup=', self.mem_cur-self.mem_ref
+
+        # Conversion cleanup
+        del conv
+        self.assertAlmostConsumed(0, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion cleanup=', self.mem_cur-self.mem_ref
+
+# -------------------------------------------------------------------
+
+# class UTDataArrayFromNumPyBuffer(...): TODO
+
+# -------------------------------------------------------------------
+
+class UTDataArrayFromNumPyArray_Scalar(UTConversionDataArrayNumPy):
+    """
+    Test cases for memory leaks during VTK/NumPy data conversion.
+    Conversion of 1D NumPy array to VTK data array using buffers."""
+
+    def setUp(self):
+        UTConversionDataArrayNumPy.setUp(self)
+        self.shape = self.footprint//np.nbytes[self.dtype]
+
+    def get_leaktest_scenario(self):
+        self.assertAlmostEqual(np.prod(self.shape)*np.nbytes[self.dtype], \
+                               self.footprint, -2) #100B
+
+        # Update memory reference
+        self.mem_ref.update()
+
+        # NumPy allocation
+        data = np.empty(self.shape, self.dtype)
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy allocation=', self.mem_cur-self.mem_ref
+
+        # NumPy to VTK conversion
+        np2da = self.convert_to_vtk_array(data)
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion=', self.mem_cur-self.mem_ref
+
+        # VTK retrieval
+        vtk_da = np2da.get_output()
+        self.assertTrue(isinstance(vtk_da, vtkDataArray))
+        self.assertAlmostEqual(vtk_da.GetActualMemorySize()*vtk_kilobyte, \
+                               self.footprint, -3) #1kB
+        if self.verbose>=1: print 'VTK retrieval=', self.mem_cur-self.mem_ref
+
+        return (np2da, data, vtk_da,)
+
+class UTFloatArrayFromNumPyArray_Scalar(UTDataArrayFromNumPyArray_Scalar):
+    __doc__ = UTDataArrayFromNumPyArray_Scalar.__doc__
+    dtype = np.float32
+    convert_to_vtk_array = vtkFloatArrayFromNumPyArray
+
+class UTDoubleArrayFromNumPyArray_Scalar(UTDataArrayFromNumPyArray_Scalar):
+    __doc__ = UTDataArrayFromNumPyArray_Scalar.__doc__
+    dtype = np.float64
+    convert_to_vtk_array = vtkDoubleArrayFromNumPyArray
+
+class UTDataArrayFromNumPyArray_Vector(UTConversionDataArrayNumPy):
+    """
+    Test cases for memory leaks during VTK/NumPy data conversion.
+    Conversion of 2D NumPy array to VTK data array using buffers."""
+
+    def setUp(self):
+        UTConversionDataArrayNumPy.setUp(self)
+        size = self.footprint//np.nbytes[self.dtype]
+        self.shape = (size//3, 3)
+
+    def get_leaktest_scenario(self):
+        self.assertAlmostEqual(np.prod(self.shape)*np.nbytes[self.dtype], \
+                               self.footprint, -2) #100B
+
+        # Update memory reference
+        self.mem_ref.update()
+
+        # NumPy allocation
+        data = np.empty(self.shape, self.dtype)
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy allocation=', self.mem_cur-self.mem_ref
+
+        # NumPy to VTK conversion
+        np2da = self.convert_to_vtk_array(data)
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion=', self.mem_cur-self.mem_ref
+
+        # VTK retrieval
+        vtk_da = np2da.get_output()
+        self.assertTrue(isinstance(vtk_da, vtkDataArray))
+        self.assertAlmostEqual(vtk_da.GetActualMemorySize()*vtk_kilobyte, \
+                               self.footprint, -3) #1kB
+        if self.verbose>=1: print 'VTK retrieval=', self.mem_cur-self.mem_ref
+
+        return (np2da, data, vtk_da,)
+
+class UTFloatArrayFromNumPyArray_Vector(UTDataArrayFromNumPyArray_Vector):
+    __doc__ = UTDataArrayFromNumPyArray_Vector.__doc__
+    dtype = np.float32
+    convert_to_vtk_array = vtkFloatArrayFromNumPyArray
+
+class UTDoubleArrayFromNumPyArray_Vector(UTDataArrayFromNumPyArray_Vector):
+    __doc__ = UTDataArrayFromNumPyArray_Vector.__doc__
+    dtype = np.float64
+    convert_to_vtk_array = vtkDoubleArrayFromNumPyArray
+
+# -------------------------------------------------------------------
+
+class UTDataArrayFromNumPyMultiArray_Scalar(UTConversionDataArrayNumPy):
+    """
+    Test cases for memory leaks during VTK/NumPy data conversion.
+    Conversion of NumPy grid scalars to VTK data array using buffers."""
+
+    def setUp(self):
+        UTConversionDataArrayNumPy.setUp(self)
+        size = self.footprint//np.nbytes[self.dtype]
+        digits, shape = shapeopt(1000, size, ndims=3, ecc=0.3)
+        if self.verbose>=1: print 'digits=%8.3f, shape=%s' % (digits,shape)
+        self.shape = shape + (1,)
+        self.assertAlmostEqual(np.prod(self.shape)*np.nbytes[self.dtype], \
+                               self.footprint, -4) #10kB
+
+    def get_leaktest_scenario(self):
+
+        # Update memory reference
+        self.mem_ref.update()
+
+        # NumPy allocation
+        data = np.empty(self.shape, self.dtype)
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy allocation=', self.mem_cur-self.mem_ref
+
+        # NumPy to VTK conversion
+        np2da = self.convert_to_vtk_array(data)
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion=', self.mem_cur-self.mem_ref
+
+        # VTK retrieval
+        vtk_da = np2da.get_output()
+        self.assertTrue(isinstance(vtk_da, vtkDataArray))
+        self.assertAlmostEqual(vtk_da.GetActualMemorySize()*vtk_kilobyte, \
+                               self.footprint, -4) #10kB
+        if self.verbose>=1: print 'VTK retrieval=', self.mem_cur-self.mem_ref
+
+        return (np2da, data, vtk_da,)
+
+class UTFloatArrayFromNumPyMultiArray_Scalar(UTDataArrayFromNumPyMultiArray_Scalar):
+    __doc__ = UTDataArrayFromNumPyMultiArray_Scalar.__doc__
+    dtype = np.float32
+    convert_to_vtk_array = vtkFloatArrayFromNumPyMultiArray
+
+class UTDoubleArrayFromNumPyMultiArray_Scalar(UTDataArrayFromNumPyMultiArray_Scalar):
+    __doc__ = UTDataArrayFromNumPyMultiArray_Scalar.__doc__
+    dtype = np.float64
+    convert_to_vtk_array = vtkDoubleArrayFromNumPyMultiArray
+
+class UTDataArrayFromNumPyMultiArray_Vector(UTConversionDataArrayNumPy):
+    """
+    Test cases for memory leaks during VTK/NumPy data conversion.
+    Conversion of NumPy grid vectors to VTK data array using buffers."""
+
+    def setUp(self):
+        UTConversionDataArrayNumPy.setUp(self)
+        size = self.footprint//np.nbytes[self.dtype]
+        digits, shape = shapeopt(1000, size//3, ndims=3, ecc=0.3)
+        if self.verbose>=1: print 'digits=%8.3f, shape=%s' % (digits,shape)
+        self.shape = shape + (3,)
+        self.assertAlmostEqual(np.prod(self.shape)*np.nbytes[self.dtype], \
+                               self.footprint, -4) #10kB
+
+    def get_leaktest_scenario(self):
+
+        # Update memory reference
+        self.mem_ref.update()
+
+        # NumPy allocation
+        data = np.empty(self.shape, self.dtype)
+        self.assertAlmostConsumed(self.footprint, self.ctol)
+        self.assertAlmostExceeded(self.footprint, self.etol)
+        if self.verbose>=1: print 'NumPy allocation=', self.mem_cur-self.mem_ref
+
+        # NumPy to VTK conversion
+        np2da = self.convert_to_vtk_array(data)
+        self.assertAlmostConsumed(2*self.footprint, self.ctol)
+        self.assertAlmostExceeded(2*self.footprint, self.etol)
+        if self.verbose>=1: print 'Conversion=', self.mem_cur-self.mem_ref
+
+        # VTK retrieval
+        vtk_da = np2da.get_output()
+        self.assertTrue(isinstance(vtk_da, vtkDataArray))
+        self.assertAlmostEqual(vtk_da.GetActualMemorySize()*vtk_kilobyte, \
+                               self.footprint, -4) #10kB
+        if self.verbose>=1: print 'VTK retrieval=', self.mem_cur-self.mem_ref
+
+        return (np2da, data, vtk_da,)
+
+class UTFloatArrayFromNumPyMultiArray_Vector(UTDataArrayFromNumPyMultiArray_Vector):
+    __doc__ = UTDataArrayFromNumPyMultiArray_Vector.__doc__
+    dtype = np.float32
+    convert_to_vtk_array = vtkFloatArrayFromNumPyMultiArray
+
+class UTDoubleArrayFromNumPyMultiArray_Vector(UTDataArrayFromNumPyMultiArray_Vector):
+    __doc__ = UTDataArrayFromNumPyMultiArray_Vector.__doc__
+    dtype = np.float64
+    convert_to_vtk_array = vtkDoubleArrayFromNumPyMultiArray
+
+# -------------------------------------------------------------------
+
+if __name__ in ['__main__', '__builtin__']:
+    # We may have been imported by test.py, if so we should redirect to logfile
+    if __name__ == '__builtin__':
+        testrunner = CustomTextTestRunner('vtk_data.log', verbosity=2)
+    else:
+        testrunner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2)
+
+    testcases = [UTFloatArrayFromNumPyArray_Scalar, \
+                 UTDoubleArrayFromNumPyArray_Scalar, \
+                 UTFloatArrayFromNumPyArray_Vector, \
+                 UTDoubleArrayFromNumPyArray_Vector, \
+                 UTFloatArrayFromNumPyMultiArray_Scalar, \
+                 UTDoubleArrayFromNumPyMultiArray_Scalar, \
+                 UTFloatArrayFromNumPyMultiArray_Vector, \
+                 UTDoubleArrayFromNumPyMultiArray_Vector]
+
+    for test in testcases:
+        info = '\n' + test.__name__ + '\n' + test.__doc__.strip('\n') + '\n'
+        testsuite = unittest.defaultTestLoader.loadTestsFromTestCase(test)
+        testrunner.stream.writeln(info)
+        testresult = testrunner.run(testsuite)
+        # Provide feedback on failed tests if imported by test.py
+        if __name__ == '__builtin__' and not testresult.wasSuccessful():
+            raise SystemExit('Test failed. Check vtk_data.log for details.')
+

Property changes on: ase/test/vtk_data.py
___________________________________________________________________
Added: svn:executable
   + 

Index: ase/test/maxwellboltzmann.py
===================================================================
--- ase/test/maxwellboltzmann.py	(revision 0)
+++ ase/test/maxwellboltzmann.py	(revision 1)
@@ -0,0 +1,10 @@
+from ase.md.velocitydistribution import MaxwellBoltzmannDistribution
+from ase.lattice.cubic import FaceCenteredCubic
+
+atoms = FaceCenteredCubic(size=(50,50,50), symbol="Cu", pbc=False)
+print "Number of atoms:", len(atoms)
+MaxwellBoltzmannDistribution(atoms, 0.1)
+temp = atoms.get_kinetic_energy() / (1.5 * len(atoms))
+
+print "Temperature", temp, " (should be 0.1)"
+assert abs(temp - 0.1) < 1e-3
Index: ase/test/rotate.py
===================================================================
--- ase/test/rotate.py	(revision 0)
+++ ase/test/rotate.py	(revision 1)
@@ -0,0 +1,17 @@
+from ase.utils import rotate, irotate
+
+def test(xyz):
+    a = rotate(xyz)
+    ixyz = '%sx,%sy,%sz' % irotate(a)
+    a2 = rotate(ixyz)
+    print xyz
+    print ixyz
+    #print np.around(a-a2, 5)
+    assert abs(a-a2).max() < 1e-10
+
+test('10z')
+test('155x,43y,190z')
+test('55x,90y,190z')
+test('180x,-90y,45z')
+test('-180y')
+test('40z,50x')
Index: ase/test/__init__.py
===================================================================
--- ase/test/__init__.py	(revision 0)
+++ ase/test/__init__.py	(revision 1)
@@ -0,0 +1,89 @@
+import sys
+import unittest
+from glob import glob
+
+class NotAvailable(SystemExit):
+    def __init__(self, msg, code=0):
+        SystemExit.__init__(self, (msg,code,))
+        self.msg = msg
+        self.code = code
+
+# -------------------------------------------------------------------
+
+# Custom test case/suite for embedding unittests in the test scripts
+
+if sys.version_info < (2, 4, 0, 'final', 0):
+    class CustomTestCase(unittest.TestCase):
+        assertTrue = unittest.TestCase.failUnless
+        assertFalse = unittest.TestCase.failIf
+else:
+    from unittest import TestCase as CustomTestCase
+
+from ase.parallel import paropen
+
+class CustomTextTestRunner(unittest.TextTestRunner):
+    def __init__(self, logname, descriptions=1, verbosity=1):
+        self.f = paropen(logname, 'w')
+        unittest.TextTestRunner.__init__(self, self.f, descriptions, verbosity)
+
+    def run(self, test):
+        stderr_old = sys.stderr
+        try:
+            sys.stderr = self.f
+            testresult = unittest.TextTestRunner.run(self, test)
+        finally:
+            sys.stderr = stderr_old
+        return testresult
+
+# -------------------------------------------------------------------
+
+class ScriptTestCase(unittest.TestCase):
+    def __init__(self, methodname='testfile', filename=None, display=True):
+        unittest.TestCase.__init__(self, methodname)
+        self.filename = filename
+        self.display = display
+        
+    def testfile(self):
+        try:
+            execfile(self.filename, {'display': self.display})
+        except KeyboardInterrupt:
+            raise RuntimeError('Keyboard interrupt')
+        except NotAvailable, err:
+            # Only non-zero error codes are failures
+            if err.code:
+                raise
+
+    def id(self):
+        return self.filename
+
+    def __str__(self):
+        return '%s (ScriptTestCase)' % self.filename.split('/')[-1]
+
+    def __repr__(self):
+        return "ScriptTestCase(filename='%s')" % self.filename
+
+
+def test(verbosity=1, dir=None, display=True, stream=sys.stdout):
+    ts = unittest.TestSuite()
+    if dir is None:
+        dir = __path__[0]
+    tests = glob(dir + '/*.py')
+    tests.sort()
+    for test in tests:
+        if test.endswith('__init__.py'):
+            continue
+        if test.endswith('COCu111.py'):
+            lasttest = test
+            continue
+        ts.addTest(ScriptTestCase(filename=test, display=display))
+    ts.addTest(ScriptTestCase(filename=lasttest, display=display))
+
+    from ase.utils import devnull
+    sys.stdout = devnull
+    
+    ttr = unittest.TextTestRunner(verbosity=verbosity, stream=stream)
+    results = ttr.run(ts)
+
+    sys.stdout = sys.__stdout__
+
+    return results
Index: ase/test/strain.py
===================================================================
--- ase/test/strain.py	(revision 0)
+++ ase/test/strain.py	(revision 1)
@@ -0,0 +1,34 @@
+from math import sqrt
+from ase import Atoms
+from ase.constraints import StrainFilter
+from ase.optimize.mdmin import MDMin
+from ase.io import PickleTrajectory
+try:
+    from asap3 import EMT
+except ImportError:
+    pass
+else:
+    a = 3.6
+    b = a / 2
+    cu = Atoms('Cu', cell=[(0,b,b),(b,0,b),(b,b,0)], pbc=1) * (6, 6, 6)
+
+    cu.set_calculator(EMT())
+    f = StrainFilter(cu, [1, 1, 1, 0, 0, 0])
+    opt = MDMin(f, dt=0.01)
+    t = PickleTrajectory('Cu.traj', 'w', cu)
+    opt.attach(t)
+    opt.run(0.001)
+
+# HCP:
+    from ase.lattice.surface import hcp0001
+    cu = hcp0001('Cu', (1, 1, 2), a=a / sqrt(2))
+    cu.cell[1,0] += 0.05
+    cu *= (6, 6, 3)
+
+    cu.set_calculator(EMT())
+    f = StrainFilter(cu)
+    opt = MDMin(f, dt=0.01)
+    t = PickleTrajectory('Cu.traj', 'w', cu)
+    opt.attach(t)
+    opt.run(0.01)
+
Index: ase/test/verlet.py
===================================================================
--- ase/test/verlet.py	(revision 0)
+++ ase/test/verlet.py	(revision 1)
@@ -0,0 +1,29 @@
+import numpy as np
+from ase import Atoms
+from ase.units import fs
+from ase.calculators.test import TestPotential
+from ase.calculators.emt import EMT
+from ase.md import VelocityVerlet
+from ase.io import PickleTrajectory, read
+from ase.optimize import QuasiNewton
+
+np.seterr(all='raise')
+a = Atoms('4X',
+          masses=[1, 2, 3, 4],
+          positions=[(0, 0, 0),
+                     (1, 0, 0),
+                     (0, 1, 0),
+                     (0.1, 0.2, 0.7)],
+          calculator=TestPotential())
+print a.get_forces()
+md = VelocityVerlet(a, dt=0.5 * fs, logfile='-', loginterval=500)
+traj = PickleTrajectory('4N.traj', 'w', a)
+md.attach(traj.write, 100)
+e0 = a.get_total_energy()
+md.run(steps=10000)
+del traj
+assert abs(read('4N.traj').get_total_energy() - e0) < 0.0001
+
+qn = QuasiNewton(a)
+qn.run(0.001)
+assert abs(a.get_potential_energy() - 1.0) < 0.000002
Index: ase/test/scaled_positions.py
===================================================================
--- ase/test/scaled_positions.py	(revision 0)
+++ ase/test/scaled_positions.py	(revision 1)
@@ -0,0 +1,2 @@
+from ase import Atoms
+assert Atoms('X', [(-1e-35, 0, 0)], pbc=True).get_scaled_positions()[0, 0] < 1
Index: ase/test/netcdf.py
===================================================================
--- ase/test/netcdf.py	(revision 0)
+++ ase/test/netcdf.py	(revision 1)
@@ -0,0 +1,23 @@
+import numpy as np
+from ase.io.pupynere import NetCDFFile
+
+# Write array
+a1 = np.random.rand(5, 5)
+a2 = a1 * 2 - 5
+nc = NetCDFFile('test.nc', 'w')
+nc.createDimension('dimx', a1.shape[0])
+nc.createDimension('dimy', a1.shape[1])
+nc.createVariable('matrix1', 'd', ('dimx', 'dimy'))[:] = a1
+nc.createVariable('matrix2', 'd', ('dimx', 'dimy'))[:] = a2
+nc.sync()
+nc.close()
+
+# Read array
+nc = NetCDFFile('test.nc', 'r')
+b1 = nc.variables['matrix1'][:]
+b2 = nc.variables['matrix2'][:]
+
+assert np.all(a1 == b1) and np.all(a2 == b2)
+
+import os
+os.remove('test.nc')
Index: ase/test/stm.py
===================================================================
--- ase/test/stm.py	(revision 0)
+++ ase/test/stm.py	(revision 1)
@@ -0,0 +1,7 @@
+from ase.calculators.test import make_test_dft_calculation
+from ase.dft.stm import STM
+
+atoms = make_test_dft_calculation()
+stm = STM(atoms, [0, 1, 2])
+c = stm.get_averaged_current(4.5)
+h = stm.scan(c)
Index: ase/test/emt1.py
===================================================================
--- ase/test/emt1.py	(revision 0)
+++ ase/test/emt1.py	(revision 1)
@@ -0,0 +1,25 @@
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.constraints import FixBondLength
+from ase.io import PickleTrajectory
+from ase.optimize import BFGS
+
+a = 3.6
+b = a / 2
+cu = Atoms('Cu2Ag',
+           positions=[(0, 0, 0),
+                      (b, b, 0),
+                      (a, a, b)],
+           calculator=EMT())
+e0 = cu.get_potential_energy()
+print e0
+
+d0 = cu.get_distance(0, 1)
+cu.set_constraint(FixBondLength(0, 1))
+t = PickleTrajectory('cu2ag.traj', 'w', cu)
+qn = BFGS(cu)
+qn.attach(t.write)
+def f(): print cu.get_distance(0,1)
+qn.attach(f)
+qn.run(fmax=0.01)
+assert abs(cu.get_distance(0, 1) - d0) < 1e-14
Index: ase/test/io.py
===================================================================
--- ase/test/io.py	(revision 0)
+++ ase/test/io.py	(revision 1)
@@ -0,0 +1,48 @@
+import numpy as np
+from ase import Atoms
+from ase.io import write, read
+
+a = 5.0
+d = 1.9
+c = a / 2
+atoms = Atoms('AuH',
+              positions=[(c, c, 0), (c, c, d)],
+              cell=(a, a, 2 * d),
+              pbc=(0, 0, 1))
+extra = np.array([ 2.3, 4.2 ])
+atoms.set_array("extra", extra)
+atoms *= (1, 1, 2)
+images = [atoms.copy(), atoms.copy()]
+r = ['xyz', 'traj', 'cube', 'pdb', 'cfg', 'struct']
+w = r + ['xsf']
+try:
+    import matplotlib
+except ImportError:
+    pass
+else:
+    w += ['png', 'eps']
+    
+for format in w:
+    print format, 'O',
+    fname1 = 'io-test.1.' + format
+    fname2 = 'io-test.2.' + format
+    write(fname1, atoms, format=format)
+    if format not in ['cube', 'png', 'eps', 'cfg', 'struct']:
+        write(fname2, images, format=format)
+
+    if format in r:
+        print 'I'
+        a1 = read(fname1)
+        assert np.all(np.abs(a1.get_positions() -
+                             atoms.get_positions()) < 1e-6)
+        if format in ['traj', 'cube', 'cfg', 'struct']:
+            assert np.all(np.abs(a1.get_cell() - atoms.get_cell()) < 1e-6)
+        if format in ['cfg']:
+            assert np.all(np.abs(a1.get_array('extra') -
+                                 atoms.get_array('extra')) < 1e-6)
+        if format not in ['cube', 'png', 'eps', 'cfg', 'struct']:
+            a2 = read(fname2)
+            a3 = read(fname2, index=0)
+            a4 = read(fname2, index=slice(None))
+    else:
+        print
Index: ase/test/exciting.py
===================================================================
--- ase/test/exciting.py	(revision 0)
+++ ase/test/exciting.py	(revision 1)
@@ -0,0 +1,31 @@
+import os
+from ase import Atoms
+from ase.io import read, write
+from ase.calculators import Exciting
+from ase.units import Bohr, Hartree
+from ase.test import NotAvailable
+
+try:
+    import lxml
+except ImportError:
+    raise NotAvailable('This test need lxml module.')
+
+a = Atoms('N3O',
+          [(0, 0, 0), (1, 0, 0), (0, 0, 1), (0.5, 0.5, 0.5)],
+          pbc=True)
+
+raise NotAvailable('Problem with lxml module.')
+
+write('geo.exi', a)
+b = read('geo.exi')
+
+print a
+print a.get_positions()
+print b
+print b.get_positions()
+
+calculator = Exciting(dir='excitingtestfiles',
+                      kpts=(4, 4, 3),
+                      maxscl=3,
+                      #bin='/fshome/chm/git/exciting/bin/excitingser'
+                      )
Index: ase/test/COCu111.py
===================================================================
--- ase/test/COCu111.py	(revision 0)
+++ ase/test/COCu111.py	(revision 1)
@@ -0,0 +1,79 @@
+from math import sqrt
+from ase import Atoms, Atom
+from ase.calculators.emt import EMT
+from ase.constraints import FixAtoms
+from ase.optimize import QuasiNewton
+from ase.neb import NEB
+
+# Distance between Cu atoms on a (111) surface:
+a = 3.6
+d = a / sqrt(2)
+fcc111 = Atoms(symbols='Cu',
+               cell=[(d, 0, 0),
+                     (d / 2, d * sqrt(3) / 2, 0),
+                     (d / 2, d * sqrt(3) / 6, -a / sqrt(3))],
+               pbc=True)
+slab = fcc111 * (2, 2, 4)
+slab.set_cell([2 * d, d * sqrt(3), 1])
+slab.set_pbc((1, 1, 0))
+slab.set_calculator(EMT())
+Z = slab.get_positions()[:, 2]
+indices = [i for i, z in enumerate(Z) if z < Z.mean()]
+constraint = FixAtoms(indices=indices)
+slab.set_constraint(constraint)
+dyn = QuasiNewton(slab)
+dyn.run(fmax=0.05)
+Z = slab.get_positions()[:, 2]
+print Z[0] - Z[1]
+print Z[1] - Z[2]
+print Z[2] - Z[3]
+
+b = 1.2
+h = 1.5
+slab += Atom('C', (d / 2, -b / 2, h))
+slab += Atom('O', (d / 2, +b / 2, h))
+s = slab.copy()
+dyn = QuasiNewton(slab)
+dyn.run(fmax=0.05)
+#view(slab)
+
+# Make band:
+images = [slab]
+for i in range(6):
+    image = slab.copy()
+    image.set_constraint(constraint)
+    image.set_calculator(EMT())
+    images.append(image)
+image[-2].position = image[-1].position
+image[-1].x = d
+image[-1].y = d / sqrt(3)
+dyn = QuasiNewton(images[-1])
+dyn.run(fmax=0.05)
+neb = NEB(images, climb=not True)
+
+# Set constraints and calculator:
+
+# Displace last image:
+
+# Relax height of Ag atom for initial and final states:
+
+# Interpolate positions between initial and final states:
+neb.interpolate()
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
+
+#dyn = MDMin(neb, dt=0.4)
+#dyn = FIRE(neb, dt=0.01)
+dyn = QuasiNewton(neb, maxstep=0.04, trajectory='mep.traj')
+#from ase.optimize.oldqn import GoodOldQuasiNewton
+#dyn = GoodOldQuasiNewton(neb)
+dyn.run(fmax=0.05)
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
+
+if display:
+    import os
+    error = os.system('ag mep.traj@-7:')
+    assert error == 0
Index: ase/test/distmom.py
===================================================================
--- ase/test/distmom.py	(revision 0)
+++ ase/test/distmom.py	(revision 1)
@@ -0,0 +1,20 @@
+from ase.dft import get_distribution_moment
+import numpy as np
+
+precision = 1E-8
+
+x = np.linspace(-50., 50., 1000)
+y = np.exp(-x**2 / 2.)
+area, center, mom2 = get_distribution_moment(x, y, (0, 1, 2))
+assert sum((abs(area - np.sqrt(2. * np.pi)), abs(center), abs(mom2 - 1.))) < precision
+
+x = np.linspace(-1., 1., 100000)
+for order in range(0, 9):
+    y = x**order
+    area = get_distribution_moment(x, y)
+    assert abs(area - (1. - (-1.)**(order + 1)) / (order + 1.)) < precision
+
+x = np.linspace(-50., 50., 100)
+y = np.exp(-2. * (x - 7.)**2 / 10.) + np.exp(-2. * (x + 5.)**2 / 10.)
+center=get_distribution_moment(x, y, 1)
+assert abs(center - 1.) < precision 
Index: ase/test/Ag-Cu100.py
===================================================================
--- ase/test/Ag-Cu100.py	(revision 0)
+++ ase/test/Ag-Cu100.py	(revision 1)
@@ -0,0 +1,64 @@
+from math import sqrt
+from ase import Atom, Atoms
+from ase.neb import NEB
+from ase.constraints import FixAtoms
+from ase.vibrations import Vibrations
+from ase.calculators.emt import EMT
+from ase.optimize import QuasiNewton
+
+# Distance between Cu atoms on a (100) surface:
+d = 3.6 / sqrt(2)
+initial = Atoms('Cu',
+                positions=[(0, 0, 0)],
+                cell=(d, d, 1.0),
+                pbc=(True, True, False))
+initial *= (2, 2, 1)  # 2x2 (100) surface-cell
+
+# Approximate height of Ag atom on Cu(100) surfece:
+h0 = 2.0
+initial += Atom('Ag', (d / 2, d / 2, h0))
+
+if 0:
+    view(initial)
+
+# Make band:
+images = [initial.copy() for i in range(6)]
+neb = NEB(images, climb=True)
+
+# Set constraints and calculator:
+constraint = FixAtoms(range(len(initial) - 1))
+for image in images:
+    image.set_calculator(EMT())
+    image.set_constraint(constraint)
+
+# Displace last image:
+images[-1].positions[-1] += (d, 0, 0)
+#images[-1].positions[-1] += (d, d, 0)
+
+# Relax height of Ag atom for initial and final states:
+dyn1 = QuasiNewton(images[0])
+dyn1.run(fmax=0.01)
+dyn2 = QuasiNewton(images[-1])
+dyn2.run(fmax=0.01)
+
+# Interpolate positions between initial and final states:
+neb.interpolate()
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
+
+#dyn = MDMin(neb, dt=0.4)
+#dyn = FIRE(neb, dt=0.4)
+dyn = QuasiNewton(neb, trajectory='mep.traj')
+dyn.run(fmax=0.05)
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
+
+a = images[0]
+vib = Vibrations(a, [4])
+vib.run()
+print vib.get_frequencies()
+vib.summary()
+print vib.get_mode(-1)
+vib.write_mode(-1, nimages=20)
Index: ase/test/vacancy.py
===================================================================
--- ase/test/vacancy.py	(revision 0)
+++ ase/test/vacancy.py	(revision 1)
@@ -0,0 +1,40 @@
+from ase import Atoms
+from ase.optimize import QuasiNewton
+from ase.neb import NEB
+from ase.optimize.mdmin import MDMin
+try:
+    from asap3 import EMT
+except ImportError:
+    pass
+else:
+
+    a = 3.6
+    b = a / 2
+    initial = Atoms('Cu4',
+                    positions=[(0, 0, 0),
+                               (0, b, b),
+                               (b, 0, b),
+                               (b, b, 0)],
+                    cell=(a, a, a),
+                    pbc=True)
+    initial *= (4, 4, 4)
+    del initial[0]
+    images = [initial] + [initial.copy() for i in range(6)]
+    images[-1].positions[0] = (0, 0, 0)
+    for image in images:
+        image.set_calculator(EMT())
+        #image.set_calculator(ASAP())
+
+    for image in [images[0], images[-1]]:
+        QuasiNewton(image).run(fmax=0.01)
+    neb = NEB(images)
+    neb.interpolate()
+
+    for a in images:
+        print a.positions[0], a.get_potential_energy()
+
+    dyn = MDMin(neb, dt=0.1, trajectory='mep1.traj')
+    #dyn = QuasiNewton(neb)
+    print dyn.run(fmax=0.01, steps=25)
+    for a in images:
+        print a.positions[0], a.get_potential_energy()
Index: ase/test/emt.py
===================================================================
--- ase/test/emt.py	(revision 0)
+++ ase/test/emt.py	(revision 1)
@@ -0,0 +1,64 @@
+import numpy as np
+from ase.calculators.emt import EMT
+from ase import Atoms
+
+a = 3.60
+b = a / 2
+cu = Atoms('Cu',
+           positions=[(0, 0, 0)],
+           cell=[(0, b, b),
+                 (b, 0, b),
+                 (b, b, 0)],
+           pbc=1,
+           calculator=EMT())
+e0 = cu.get_potential_energy()
+print e0
+
+cu.set_cell(cu.get_cell() * 1.001, scale_atoms=True)
+e1 = cu.get_potential_energy()
+V = a**3 / 4
+B = 2 * (e1 - e0) / 0.003**2 / V * 160.2
+print B
+
+for i in range(4):
+    x = 0.001 * i
+    A = np.array([(x, b, b+x),
+                  (b, 0, b),
+                  (b, b, 0)])
+    cu.set_cell(A, scale_atoms=True)
+    e = cu.get_potential_energy() - e0
+    if i == 0:
+        print i, e
+    else:
+        print i, e, e / x**2
+
+A = np.array([(0, b, b),
+              (b, 0, b),
+              (6*b, 6*b, 0)])
+R = np.zeros((2, 3))
+for i in range(1, 2):
+    R[i] = i * A[2] / 6
+print (Atoms('Cu2', positions=R,
+             pbc=1, cell=A,
+             calculator=EMT()).get_potential_energy() - 2 * e0) / 2
+
+A = np.array([(0, b, b),
+              (b, 0, b),
+              (10*b, 10*b, 0)])
+R = np.zeros((3, 3))
+for i in range(1, 3):
+    R[i] = i * A[2] / 10
+print (Atoms('Cu3', positions=R,
+             pbc=1, cell=A,
+             calculator=EMT()).get_potential_energy() - 3 * e0) / 2
+
+A = np.array([(0, b, b),
+              (b, 0, b),
+              (b, b, 0)])
+R = np.zeros((3, 3))
+for i in range(1, 3):
+    R[i] = i * A[2]
+print (Atoms('Cu3', positions=R,
+             pbc=(1, 1, 0), cell=A,
+             calculator=EMT()).get_potential_energy() - 3 * e0) / 2
+
Index: ase/test/rotate_euler.py
===================================================================
--- ase/test/rotate_euler.py	(revision 0)
+++ ase/test/rotate_euler.py	(revision 1)
@@ -0,0 +1,12 @@
+from math import pi, sqrt
+from ase import Atoms, Atom
+
+d = 1.14
+a = Atoms([Atom('C', (0, 0, 0)), Atom('O', (d, 0, 0))])
+a.rotate_euler(phi=pi/2, theta=pi/4, psi=pi)
+for p in a[0].position:
+    assert p == 0.0
+assert abs(a[1].position[0]) < 1e-15
+d2 = d / sqrt(2)
+assert abs(a[1].position[1] - d2) < 1e-15
+assert abs(a[1].position[2] - d2) < 1e-15
Index: ase/test/coverage.py
===================================================================
--- ase/test/coverage.py	(revision 0)
+++ ase/test/coverage.py	(revision 1)
@@ -0,0 +1,4 @@
+from ase import Atoms
+print Atoms()
+print Atoms('H2O')
+#...
Index: ase/test/geometry.py
===================================================================
--- ase/test/geometry.py	(revision 0)
+++ ase/test/geometry.py	(revision 1)
@@ -0,0 +1,102 @@
+"Test the ase.utils.geometry module"
+
+import numpy as np
+
+import ase
+from ase.lattice.spacegroup import crystal
+from ase.utils.geometry import get_layers, cut, stack
+
+np.set_printoptions(suppress=True)
+
+al = crystal('Al', [(0,0,0)], spacegroup=225, cellpar=4.05)
+
+
+# Cut out slab of 5 Al(001) layers
+al001 = cut(al, nlayers=5)
+correct_pos = np.array([[ 0. ,  0. ,  0. ],
+                        [ 0. ,  0.5,  0.2],
+                        [ 0.5,  0. ,  0.2],
+                        [ 0.5,  0.5,  0. ],
+                        [ 0. ,  0. ,  0.4],
+                        [ 0. ,  0.5,  0.6],
+                        [ 0.5,  0. ,  0.6],
+                        [ 0.5,  0.5,  0.4],
+                        [ 0. ,  0. ,  0.8],
+                        [ 0.5,  0.5,  0.8]])
+assert np.allclose(correct_pos, al001.get_scaled_positions())
+
+# Check layers along 001
+tags, levels = get_layers(al001, (0, 0, 1))
+assert np.allclose(tags, [0, 1, 1, 0, 2, 3, 3, 2, 4, 4])
+assert np.allclose(levels, [ 0., 2.025, 4.05, 6.075, 8.1])
+
+# Check layers along 101
+tags, levels = get_layers(al001, (1, 0, 1))
+assert np.allclose(tags, [0, 1, 5, 3, 2, 4, 8, 7, 6, 9])
+assert np.allclose(levels, [0.000, 0.752, 1.504, 1.880, 2.256,
+                            2.632, 3.008, 3.384, 4.136, 4.888], atol=0.001)
+
+# Check layers along 111
+tags, levels = get_layers(al001, (1, 1, 1))
+assert np.allclose(tags, [0, 2, 2, 4, 1, 5, 5, 6, 3, 7])
+assert np.allclose(levels, [0.000, 1.102, 1.929, 2.205, 
+                            2.756, 3.031, 3.858, 4.960], atol=0.001)
+
+
+# Cut out slab of three Al(111) layers
+al111 = cut(al, (1,-1,0), (0,1,-1), nlayers=3)
+correct_pos = np.array([[ 0.        ,  0.5       ,  0.        ],
+                        [ 0.5       ,  0.5       ,  0.        ],
+                        [ 2/3.      ,  5/6.      ,  1/3.      ],
+                        [ 0.5       ,  0.        ,  0.        ],
+                        [ 0.        ,  0.        ,  0.        ],
+                        [ 1/6.      ,  1/3.      ,  1/3.      ],
+                        [ 2/3.      ,  1/3.      ,  1/3.      ],
+                        [ 1/3.      ,  1/6.      ,  2/3.      ],
+                        [ 5/6.      ,  1/6.      ,  2/3.      ],
+                        [ 5/6.      ,  2/3.      ,  2/3.      ],
+                        [ 1/6.      ,  5/6.      ,  1/3.      ],
+                        [ 1/3.      ,  2/3.      ,  2/3.      ]])
+assert np.allclose(correct_pos, al111.get_scaled_positions())
+
+
+# Cut out cell including all corner and edge atoms (non-periodic structure)
+al = cut(al, extend=1.1)
+correct_pos = np.array([[ 0. ,  0. ,  0. ],
+                        [ 0. ,  0.5,  0.5],
+                        [ 0.5,  0. ,  0.5],
+                        [ 0.5,  0.5,  0. ],
+                        [ 0. ,  0. ,  0. ],
+                        [ 0. ,  0.5,  0.5],
+                        [ 0. ,  0. ,  0. ],
+                        [ 0.5,  0. ,  0.5],
+                        [ 0. ,  0. ,  0. ],
+                        [ 0. ,  0. ,  0. ],
+                        [ 0.5,  0.5,  0. ],
+                        [ 0. ,  0. ,  0. ],
+                        [ 0. ,  0. ,  0. ],
+                        [ 0. ,  0. ,  0. ]])
+assert np.allclose(correct_pos, al.get_scaled_positions())
+
+
+# Create an Ag(111)/Si(111) interface
+ag = crystal(['Ag'], basis=[(0,0,0)], spacegroup=225, cellpar=4.09)
+si = crystal(['Si'], basis=[(0,0,0)], spacegroup=227, cellpar=5.43)
+
+ag111 = cut(ag, a=(4, -4, 0), b=(4, 4, -8), nlayers=5)
+si111 = cut(si, a=(3, -3, 0), b=(3, 3, -6), nlayers=5)
+
+interface = stack(ag111, si111)
+assert len(interface) == 1000
+assert np.allclose(interface.positions[::100], 
+                   [[ 28.56875   ,  -2.040625  , -26.528125  ],
+                    [ 20.40916667,   6.12479167, -22.44395833],
+                    [ 24.49916667,  12.25541667, -20.39458333],
+                    [ 24.49041667,  -6.11895833, -14.28145833],
+                    [ 10.20895833,  10.20895833, -12.23791667],
+                    [ 24.49625   ,   2.049375  , -14.275625  ],
+                    [ 16.33375   ,  -4.0725    ,   0.00875   ],
+                    [ 31.30027778,  12.25444444, -17.67472222],
+                    [ 25.85861111,  14.97527778, -14.95388889],
+                    [ 23.13777778,   4.09194444,  -1.34972222]])
+
Index: ase/test/CO2_Au111.py
===================================================================
--- ase/test/CO2_Au111.py	(revision 0)
+++ ase/test/CO2_Au111.py	(revision 1)
@@ -0,0 +1,31 @@
+from math import sqrt, pi
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.constraints import FixBondLengths
+from ase.optimize import BFGS, QuasiNewton
+from ase.neb import SingleCalculatorNEB
+from ase.lattice.surface import fcc111, add_adsorbate
+from math import sqrt, cos, sin
+
+
+zpos = cos(134.3/2.0*pi/180.0)*1.197
+xpos = sin(134.3/2.0*pi/180.0)*1.19
+co2 = Atoms('COO', positions=[(-xpos+1.2,0,-zpos),
+                              (-xpos+1.2,-1.1,-zpos),
+                              (-xpos+1.2,1.1,-zpos)])
+
+slab = fcc111('Au', size=(2, 2, 4), vacuum=2*5, orthogonal=True)
+slab.center()
+add_adsorbate(slab,co2,1.5,'bridge')
+slab.set_pbc((True,True,False))
+d0 = co2.get_distance(-3, -2)
+d1 = co2.get_distance(-3, -1)
+
+calc = EMT()
+slab.set_calculator(calc)
+constraint = FixBondLengths([[-3,-2],[-3,-1]])
+slab.set_constraint(constraint)
+dyn = BFGS(slab, trajectory='relax.traj')
+dyn.run(fmax=0.05)
+assert abs(co2.get_distance(-3, -2) - d0) < 1e-14
+assert abs(co2.get_distance(-3, -1) - d1) < 1e-14
Index: ase/test/replay.py
===================================================================
--- ase/test/replay.py	(revision 0)
+++ ase/test/replay.py	(revision 1)
@@ -0,0 +1,35 @@
+from math import sqrt
+from ase import Atoms, Atom
+from ase.constraints import FixAtoms
+from ase.calculators.emt import EMT
+from ase.optimize import QuasiNewton
+from ase.io import read
+from ase.vibrations import Vibrations
+
+# Distance between Cu atoms on a (100) surface:
+d = 3.6 / sqrt(2)
+a = Atoms('Cu',
+          positions=[(0, 0, 0)],
+          cell=(d, d, 1.0),
+          pbc=(True, True, False))
+a *= (2, 2, 1)  # 2x2 (100) surface-cell
+
+# Approximate height of Ag atom on Cu(100) surfece:
+h0 = 2.0
+a += Atom('Ag', (d / 2, d / 2, h0))
+
+if 0:
+    view(a)
+
+constraint = FixAtoms(range(len(a) - 1))
+a.set_calculator(EMT())
+a.set_constraint(constraint)
+dyn1 = QuasiNewton(a, trajectory='AgCu1.traj', logfile='AgCu1.log')
+dyn1.run(fmax=0.1)
+
+a = read('AgCu1.traj')
+a.set_calculator(EMT())
+print a.constraints
+dyn2 = QuasiNewton(a, trajectory='AgCu2.traj', logfile='AgCu2.log')
+dyn2.replay_trajectory('AgCu1.traj')
+dyn2.run(fmax=0.01)
Index: ase/test/properties.py
===================================================================
--- ase/test/properties.py	(revision 0)
+++ ase/test/properties.py	(revision 1)
@@ -0,0 +1,13 @@
+from ase import Atoms
+a = Atoms('H2', positions=[(0, 0, 0), (0, 0, 1.1)])
+
+a.pbc[0] = 1
+assert a.pbc.any()
+assert not a.pbc.all()
+a.pbc = 1
+assert a.pbc.all()
+
+a.cell = (1, 2, 3)
+a.cell *= 2
+a.cell[0, 0] = 3
+assert not (a.cell.diagonal() - (3, 4, 6)).any()
Index: ase/test/basin.py
===================================================================
--- ase/test/basin.py	(revision 0)
+++ ase/test/basin.py	(revision 1)
@@ -0,0 +1,35 @@
+import numpy as np
+from math import pi, sqrt
+from ase import Atoms
+from ase.calculators.lj import LennardJones
+from ase.optimize.basin import BasinHopping
+from ase.io import PickleTrajectory, read
+from ase.units import kB
+
+N = 7
+R = N**(1./3.)
+pos = np.random.uniform(-R, R, (N, 3))
+s = Atoms('He' + str(N),
+          positions = pos)
+s.set_calculator(LennardJones())
+
+ftraj = 'lowest.traj'
+traj = PickleTrajectory(ftraj, 'w', s)
+bh = BasinHopping(s, 
+                  temperature=100 * kB, dr=0.5, 
+                  optimizer_logfile=None)
+bh.attach(traj)
+bh.run(10)
+
+Emin, smin = bh.get_minimum()
+
+# recalc energy
+smin.set_calculator(LennardJones())
+E = smin.get_potential_energy()
+assert abs(E - Emin) < 1e-15
+traj.close()
+smim = read(ftraj)
+E = smin.get_potential_energy()
+assert abs(E - Emin) < 1e-15
+
+#view(smin)
Index: ase/test/neighbor.py
===================================================================
--- ase/test/neighbor.py	(revision 0)
+++ ase/test/neighbor.py	(revision 1)
@@ -0,0 +1,60 @@
+import numpy.random as random
+import numpy as np
+from ase import Atoms
+from ase.calculators.neighborlist import NeighborList
+
+atoms = Atoms(numbers=range(10),
+              cell=[(0.2, 1.2, 1.4),
+                    (1.4, 0.1, 1.6),
+                    (1.3, 2.0, -0.1)])
+atoms.set_scaled_positions(3 * random.random((10, 3)) - 1)
+
+def count(nl, atoms):
+    c = np.zeros(len(atoms), int)
+    R = atoms.get_positions()
+    cell = atoms.get_cell()
+    d = 0.0
+    for a in range(len(atoms)):
+        i, offsets = nl.get_neighbors(a)
+        for j in i:
+            c[j] += 1
+        c[a] += len(i)
+        d += (((R[i] + np.dot(offsets, cell) - R[a])**2).sum(1)**0.5).sum()
+    return d, c
+
+for sorted in [False, True]:
+    for p1 in range(2):
+        for p2 in range(2):
+            for p3 in range(2):
+                print p1, p2, p3
+                atoms.set_pbc((p1, p2, p3))
+                nl = NeighborList(atoms.numbers * 0.2 + 0.5,
+                                  skin=0.0, sorted=sorted)
+                nl.update(atoms)
+                d, c = count(nl, atoms)
+                atoms2 = atoms.repeat((p1 + 1, p2 + 1, p3 + 1))
+                nl2 = NeighborList(atoms2.numbers * 0.2 + 0.5,
+                                   skin=0.0, sorted=sorted)
+                nl2.update(atoms2)
+                d2, c2 = count(nl2, atoms2)
+                c2.shape = (-1, 10)
+                dd = d * (p1 + 1) * (p2 + 1) * (p3 + 1) - d2
+                print dd
+                print c2 - c
+                assert abs(dd) < 1e-10
+                assert not (c2 - c).any()
+
+h2 = Atoms('H2', positions=[(0, 0, 0), (0, 0, 1)])
+nl = NeighborList([0.5, 0.5], skin=0.1, sorted=True, self_interaction=False)
+assert nl.update(h2)
+assert not nl.update(h2)
+assert (nl.get_neighbors(0)[0] == [1]).all()
+
+h2[1].z += 0.09
+assert not nl.update(h2)
+assert (nl.get_neighbors(0)[0] == [1]).all()
+
+h2[1].z += 0.09
+assert nl.update(h2)
+assert (nl.get_neighbors(0)[0] == []).all()
+assert nl.nupdates == 2
Index: ase/test/COCu111_2.py
===================================================================
--- ase/test/COCu111_2.py	(revision 0)
+++ ase/test/COCu111_2.py	(revision 1)
@@ -0,0 +1,72 @@
+from math import sqrt
+from ase import Atoms, Atom
+from ase.constraints import FixAtoms
+from ase.optimize import FIRE, QuasiNewton
+from ase.neb import SingleCalculatorNEB
+from ase.calculators.emt import EMT
+
+Optimizer=FIRE
+Optimizer=QuasiNewton
+
+# Distance between Cu atoms on a (111) surface:
+a = 3.6
+d = a / sqrt(2)
+fcc111 = Atoms(symbols='Cu',
+               cell=[(d, 0, 0),
+                     (d / 2, d * sqrt(3) / 2, 0),
+                     (d / 2, d * sqrt(3) / 6, -a / sqrt(3))],
+               pbc=True)
+initial = fcc111 * (2, 2, 4)
+initial.set_cell([2 * d, d * sqrt(3), 1])
+initial.set_pbc((1, 1, 0))
+initial.set_calculator(EMT())
+Z = initial.get_positions()[:, 2]
+indices = [i for i, z in enumerate(Z) if z < Z.mean()]
+constraint = FixAtoms(indices=indices)
+initial.set_constraint(constraint)
+dyn = Optimizer(initial)
+dyn.run(fmax=0.05)
+Z = initial.get_positions()[:, 2]
+print Z[0] - Z[1]
+print Z[1] - Z[2]
+print Z[2] - Z[3]
+
+b = 1.2
+h = 1.5
+initial += Atom('C', (d / 2, -b / 2, h))
+initial += Atom('O', (d / 2, +b / 2, h))
+s = initial.copy()
+dyn = Optimizer(initial)
+dyn.run(fmax=0.05)
+#view(initial)
+
+# create final
+final = initial.copy()
+final.set_calculator(EMT())
+final.set_constraint(constraint)
+final[-2].position = final[-1].position
+final[-1].x = d
+final[-1].y = d / sqrt(3)
+dyn = Optimizer(final)
+dyn.run(fmax=0.1)
+#view(final)
+
+# create 2 intermediate step neb
+neb = SingleCalculatorNEB([initial, final])
+neb.refine(2)
+neb.set_calculators(EMT())
+assert(neb.n() == 4)
+
+dyn = Optimizer(neb, maxstep=0.04, trajectory='mep_2coarse.traj')
+dyn.run(fmax=0.1)
+#dyn.run(fmax=39.1)
+
+# read from the trajectory
+neb = SingleCalculatorNEB('mep_2coarse.traj@-4:')
+
+# refine in the important region
+neb.refine(2, 1, 3)
+neb.set_calculators(EMT())
+dyn = Optimizer(neb, maxstep=0.04, trajectory='mep_2fine.traj')
+dyn.run(fmax=0.1)
+assert(len(neb.images) == 8)
Index: ase/test/jacapo.py
===================================================================
--- ase/test/jacapo.py	(revision 0)
+++ ase/test/jacapo.py	(revision 1)
@@ -0,0 +1,40 @@
+# do some tests here before we import
+# Right version of Scientific?
+from ase.test import NotAvailable
+import string, os
+try:
+    import Scientific
+    version = string.split(Scientific.__version__,".")
+    print 'Found ScientifPython version: ',Scientific.__version__
+    if map(int,version) < [2,8]:
+        print 'ScientificPython 2.8 or greater required for numpy support in NetCDF'
+        raise NotAvailable('ScientificPython version 2.8 or greater is required')
+except:
+    print "No Scientific python found. Check your PYTHONPATH"
+    raise NotAvailable('ScientificPython version 2.8 or greater is required')
+
+if not (os.system('which dacapo.run') == 0):
+    print "No Dacapo Fortran executable (dacapo.run) found. Check your path settings."
+    raise NotAvailable('dacapo.run is not installed on this machine or not in the path')
+
+# Now Scientific 2.8 and dacapo.run should both be available
+
+from ase import Atoms, Atom
+from ase.calculators.jacapo import Jacapo
+
+atoms = Atoms([Atom('H',[0,0,0])],
+            cell=(2,2,2))
+
+calc = Jacapo('Jacapo-test.nc',
+              pw=200,
+              nbands=2,
+              kpts=(1,1,1),
+              spinpol=False,
+              dipole=False,
+              symmetry=False,
+              ft=0.01)
+
+atoms.set_calculator(calc)
+
+print atoms.get_potential_energy()
+os.system('rm -f Jacapo-test.nc Jacapo-test.txt')
Index: ase/test/build.py
===================================================================
--- ase/test/build.py	(revision 0)
+++ ase/test/build.py	(revision 1)
@@ -0,0 +1,21 @@
+import numpy as np
+from ase import Atoms, Atom
+
+a = Atoms([Atom('Cu')])
+a.positions[:] += 1.0
+print a.get_positions(), a.positions
+a=a+a
+a+=a
+a.append(Atom('C'))
+a += Atoms([])
+a += Atom('H', magmom=1)
+print a.get_initial_magnetic_moments()
+print a[0].number
+print a[[0,1]].get_atomic_numbers()
+print a[np.array([1,1,0,0,1], bool)].get_atomic_numbers()
+print a[::2].get_atomic_numbers()
+print a.get_chemical_symbols()
+del a[2]
+print a.get_chemical_symbols()
+del a[-2:]
+print a.get_chemical_symbols()
Index: ase/test/dimer.py
===================================================================
--- ase/test/dimer.py	(revision 0)
+++ ase/test/dimer.py	(revision 1)
@@ -0,0 +1,19 @@
+from ase import Atom, Atoms
+from ase.calculators.lj import LennardJones
+from ase.constraints import FixBondLength
+
+dimer = Atoms([Atom('X', (0, 0, 0)),
+               Atom('X', (0, 0, 1))],
+              calculator=LennardJones(),
+              constraint=FixBondLength(0, 1))
+print dimer.get_forces()
+print dimer.positions
+dimer.positions[:] += 0.1
+print dimer.positions
+dimer.positions[:, 2] += 5.1
+print dimer.positions
+dimer.positions[:] = [(1,2,3),(4,5,6)]
+print dimer.positions
+dimer.set_positions([(1,2,3),(4,5,6.2)])
+print dimer.positions
+
Index: ase/test/md.py
===================================================================
--- ase/test/md.py	(revision 0)
+++ ase/test/md.py	(revision 1)
@@ -0,0 +1,21 @@
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.md import VelocityVerlet
+from ase.io import PickleTrajectory
+
+a = 3.6
+b = a / 2
+fcc = Atoms('Cu', positions=[(0, 0, 0)],
+            cell=[(0, b, b), (b, 0, b), (b, b, 0)],
+            pbc=1)
+fcc *= (2, 1, 1)
+fcc.set_calculator(EMT())
+fcc.set_momenta([(0.9, 0.0, 0.0), (-0.9, 0, 0)])
+md = VelocityVerlet(fcc, dt=0.1)
+def f():
+    print fcc.get_potential_energy(), fcc.get_total_energy()
+md.attach(f)
+md.attach(PickleTrajectory('Cu2.traj', 'w', fcc).write, interval=3)
+md.run(steps=20)
+fcc2 = PickleTrajectory('Cu2.traj', 'r')[-1]
+
Index: ase/test/atom.py
===================================================================
--- ase/test/atom.py	(revision 0)
+++ ase/test/atom.py	(revision 1)
@@ -0,0 +1,30 @@
+from ase import Atom, Atoms
+
+m = Atoms('H2')
+a = m[0]
+b = Atom('H')
+for c in [a, b]:
+    assert c.x == 0
+    c.z = 24.0
+    assert c.position[2] == 24.0
+    assert c.symbol == 'H'
+    c.number = 92
+    assert c.symbol == 'U'
+    c.symbol = 'Fe'
+    assert c.number == 26
+    c.tag = 42
+    assert c.tag == 42
+    c.momentum = (1,2,3)
+assert m[0].tag == 42
+momenta = m.get_momenta()
+m = Atoms('LiH')
+for a in m:
+    print a.symbol
+for a in m:  
+    if a.symbol == 'H':
+        a.z = 0.75
+assert m.get_distance(0, 1) == 0.75
+a = m.pop()
+m += a
+del m[:1]
+print m
Index: ase/test/vasp_Al_volrelax.py
===================================================================
--- ase/test/vasp_Al_volrelax.py	(revision 0)
+++ ase/test/vasp_Al_volrelax.py	(revision 1)
@@ -0,0 +1,75 @@
+#!/usr/bin/python
+
+"""
+Run VASP tests to ensure that relaxation with the VASP calculator works.
+This is conditional on the existence of the VASP_COMMAND or VASP_SCRIPT
+environment variables.
+
+"""
+
+from ase.test import NotAvailable
+import os
+
+vcmd = os.getenv('VASP_COMMAND')
+vscr = os.getenv('VASP_SCRIPT')
+if vcmd == None and vscr == None:
+    raise NotAvailable('Neither VASP_COMMAND nor VASP_SCRIPT defined')
+
+import numpy as np
+from ase import io
+from ase.optimize import QuasiNewton
+from ase.constraints import StrainFilter
+from ase.structure import bulk
+from ase.calculators.vasp import Vasp
+
+# -- Perform Volume relaxation within Vasp
+def vasp_vol_relax():
+    Al = bulk('Al', 'fcc', a=4.5, cubic=True)
+    calc = Vasp(xc='LDA', isif=7, nsw=5,
+                ibrion=1, ediffg=-1e-3, lwave=False, lcharg=False)
+    calc.calculate(Al)
+
+    # Explicitly parse atomic position output file from Vasp
+    CONTCAR_Al = io.read('CONTCAR', format='vasp')
+
+    print 'Stress after relaxation:\n', calc.read_stress()
+
+    print 'Al cell post relaxation from calc:\n', calc.get_atoms().get_cell()
+    print 'Al cell post relaxation from atoms:\n', Al.get_cell()
+    print 'Al cell post relaxation from CONTCAR:\n', CONTCAR_Al.get_cell()
+
+    # All the cells should be the same.
+    assert (calc.get_atoms().get_cell() == CONTCAR_Al.get_cell()).all()
+    assert (Al.get_cell() == CONTCAR_Al.get_cell()).all()
+
+    return Al
+
+# -- Perform Volume relaxation using ASE with Vasp as force/stress calculator
+def ase_vol_relax():
+    Al = bulk('Al', 'fcc', a=4.5, cubic=True)
+    calc = Vasp(xc='LDA')
+    Al.set_calculator(calc)
+
+    from ase.constraints import StrainFilter
+    sf = StrainFilter(Al)
+    qn = QuasiNewton(sf, logfile='relaxation.log')
+    qn.run(fmax=0.1, steps=5)
+
+    print 'Stress:\n', calc.read_stress()
+    print 'Al post ASE volume relaxation\n', calc.get_atoms().get_cell()
+
+    return Al
+
+# Test function for comparing two cells
+def cells_almost_equal(cellA, cellB, tol=0.01):
+    return  (np.abs(cellA - cellB) < tol).all()
+
+# Correct LDA relaxed cell
+a_rel = 4.18
+LDA_cell = np.diag([a_rel, a_rel, a_rel])
+
+Al_vasp = vasp_vol_relax()
+Al_ase = ase_vol_relax()
+
+assert cells_almost_equal(LDA_cell, Al_vasp.get_cell())
+assert cells_almost_equal(LDA_cell, Al_ase.get_cell())
Index: ase/test/neb.py
===================================================================
--- ase/test/neb.py	(revision 0)
+++ ase/test/neb.py	(revision 1)
@@ -0,0 +1,23 @@
+from ase.io import PickleTrajectory
+from ase.neb import NEB
+from ase.calculators.lj import LennardJones
+from ase.optimize import QuasiNewton
+
+print [a.get_potential_energy() for a in PickleTrajectory('H.traj')]
+images = [PickleTrajectory('H.traj')[-1]]
+for i in range(4):
+    images.append(images[0].copy())
+images[-1].positions[6, 1] = 2 - images[0].positions[6, 1]
+neb = NEB(images)
+neb.interpolate()
+
+for image in images:
+    image.set_calculator(LennardJones())
+
+for a in neb.images:
+    print a.positions[-1], a.get_potential_energy()
+
+dyn = QuasiNewton(neb, trajectory='mep.traj')
+print dyn.run(fmax=0.01, steps=25)
+for a in neb.images:
+    print a.positions[-1], a.get_potential_energy()
Index: ase/test/h2.py
===================================================================
--- ase/test/h2.py	(revision 0)
+++ ase/test/h2.py	(revision 1)
@@ -0,0 +1,8 @@
+from ase import Atoms
+from ase.calculators.emt import EMT
+
+h2 = Atoms('H2', positions=[(0, 0, 0), (0, 0, 1.1)],
+           calculator=EMT())
+print h2.calc.get_numeric_forces(h2)
+print h2.get_forces()
+
Index: ase/test/hcp.py
===================================================================
--- ase/test/hcp.py	(revision 0)
+++ ase/test/hcp.py	(revision 1)
@@ -0,0 +1,36 @@
+try:
+    import scipy
+except ImportError:
+    from ase.test import NotAvailable
+    raise NotAvailable('This test needs scipy module.')
+
+import numpy as np
+from ase.io import read, PickleTrajectory
+from ase.structure import bulk
+from ase.calculators.emt import EMT
+
+a0 = 3.52 / np.sqrt(2)
+c0 = np.sqrt(8 / 3.0) * a0
+print '%.4f %.3f' % (a0, c0 / a0)
+for i in range(3):
+    traj = PickleTrajectory('Ni.traj', 'w')
+    eps = 0.01
+    for a in a0 * np.linspace(1 - eps, 1 + eps, 4):
+        for c in c0 * np.linspace(1 - eps, 1 + eps, 4):
+            ni = bulk('Ni', 'hcp', a=a, covera=c / a)
+            ni.set_calculator(EMT())
+            ni.get_potential_energy()
+            traj.write(ni)
+
+    configs = read('Ni.traj@:')
+    energies = [config.get_potential_energy() for config in configs]
+    ac = [(config.cell[0, 0], config.cell[2, 2]) for config in configs]
+    from ase.optimize import polyfit
+    p = polyfit(ac, energies)
+    from scipy.optimize import fmin_bfgs
+    a0, c0 = fmin_bfgs(p, (a0, c0))
+    print '%.4f %.3f' % (a0, c0 / a0)
+assert abs(a0 - 2.469) < 0.001
+assert abs(c0 / a0 - 1.624) < 0.005
+
+
Index: ase/test/pull.py
===================================================================
--- ase/test/pull.py	(revision 0)
+++ ase/test/pull.py	(revision 1)
@@ -0,0 +1,10 @@
+import numpy as np
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.io import PickleTrajectory
+
+Cu = Atoms('Cu', pbc=(1, 0, 0), calculator=EMT())
+traj = PickleTrajectory('Cu.traj', 'w')
+for a in np.linspace(2.0, 4.0, 20):
+    Cu.set_cell([a, 1, 1], scale_atoms=True)
+    traj.write(Cu)
Index: ase/test/bader.py
===================================================================
--- ase/test/bader.py	(revision 0)
+++ ase/test/bader.py	(revision 1)
@@ -0,0 +1,28 @@
+import os
+from ase.data.molecules import molecule
+from ase.io.bader import attach_charges
+
+fname = 'ACF.dat'
+f = open(fname, 'w')
+print >> f, """
+   #         X           Y           Z        CHARGE     MIN DIST
+ ----------------------------------------------------------------
+   1      7.0865      8.5038      9.0672      9.0852      1.3250
+   2      7.0865      9.9461      7.9403      0.4574      0.3159
+   3      7.0865      7.0615      7.9403      0.4574      0.3159
+ ----------------------------------------------------------------
+  NUMBER OF ELECTRONS:        9.99999
+"""
+f.close()
+
+atoms = molecule('H2O')
+atoms.set_cell([7.5, 9, 9])
+atoms.center()
+
+attach_charges(atoms)
+attach_charges(atoms, fname)
+os.remove(fname)
+
+for atom in atoms:
+    print 'Atom', atom.symbol, 'Bader charge', atom.charge 
+
Index: ase/test/magmom.py
===================================================================
--- ase/test/magmom.py	(revision 0)
+++ ase/test/magmom.py	(revision 1)
@@ -0,0 +1,9 @@
+from ase import Atoms
+from ase.io import read, write
+
+atoms = Atoms('HH', [[.0,.0,.0], [.0,.0,.74]], pbc=True, cell=[5, 5, 5])
+atoms.set_initial_magnetic_moments([1, -1])
+moms = atoms.get_initial_magnetic_moments()
+write('test.traj',atoms)
+atoms = read('test.traj')
+assert (atoms.get_initial_magnetic_moments() == moms).all()
Index: ase/infrared.py
===================================================================
--- ase/infrared.py	(revision 0)
+++ ase/infrared.py	(revision 1)
@@ -0,0 +1,300 @@
+
+# -*- coding: utf-8 -*-
+
+"""Infrared intensities"""
+
+import pickle
+from math import sin, pi, sqrt, exp, log
+from os import remove
+from os.path import isfile
+
+import numpy as np
+
+import ase.units as units
+from ase.io.trajectory import PickleTrajectory
+from ase.parallel import rank, barrier, parprint
+from ase.vibrations import Vibrations
+
+
+class InfraRed(Vibrations):
+    """Class for calculating vibrational modes and infrared intensities
+    using finite difference.
+
+    The vibrational modes are calculated from a finite difference
+    approximation of the Dynamical matrix and the IR intensities from
+    a finite difference approximation of the gradient of the dipole
+    moment. The method is described in:
+
+      D. Porezag, M. R. Peterson:
+      "Infrared intensities and Raman-scattering activities within
+      density-functional theory",
+      Phys. Rev. B 54, 7830 (1996)
+
+    The calculator object (calc) linked to the Atoms object (atoms) must 
+    have the attribute:
+    
+    >>> calc.get_dipole_moment(atoms)
+
+    In addition to the methods included in the ``Vibrations`` class
+    the ``InfraRed`` class introduces two new methods;
+    *get_spectrum()* and *write_spectra()*. The *summary()*, *get_energies()*, 
+    *get_frequencies()*, *get_spectrum()* and *write_spectra()*
+    methods all take an optional *method* keyword.  Use
+    method='Frederiksen' to use the method described in:
+
+      T. Frederiksen, M. Paulsson, M. Brandbyge, A. P. Jauho:
+      "Inelastic transport theory from first-principles: methodology
+      and applications for nanoscale devices", 
+      Phys. Rev. B 75, 205413 (2007) 
+
+    atoms: Atoms object
+        The atoms to work on.
+    indices: list of int
+        List of indices of atoms to vibrate.  Default behavior is
+        to vibrate all atoms.
+    name: str
+        Name to use for files.
+    delta: float
+        Magnitude of displacements.
+    nfree: int
+        Number of displacements per degree of freedom, 2 or 4 are
+        supported. Default is 2 which will displace each atom +delta
+        and -delta in each cartesian direction.
+    directions: list of int
+        Cartesian coordinates to calculate the gradient of the dipole moment in. 
+        For example directions = 2 only dipole moment in the z-direction will
+        be considered, whereas for directions = [0, 1] only the dipole
+        moment in the xy-plane will be considered. Default behavior is to
+        use the dipole moment in all directions.
+
+    Example:
+    
+    >>> from ase.io import read
+    >>> from ase.calculators.vasp import Vasp
+    >>> from ase.infrared import InfraRed
+    >>> water = read('water.traj')  # read pre-relaxed structure of water molecule
+    >>> calc = Vasp(prec='Accurate',
+    ...             ediff=1E-8,
+    ...             isym=0,
+    ...             idipol=4,       # calculate the total dipole moment
+    ...             dipol=water.get_center_of_mass(scaled=True),
+    ...             ldipol=True)
+    >>> water.set_calculator(calc)
+    >>> ir = InfraRed(water)
+    >>> ir.run()
+    >>> ir.summary()
+    -------------------------------------
+    Mode    Frequency        Intensity
+    #    meV     cm^-1   (D/Å)^2 amu^-1
+    -------------------------------------
+    0   16.9i    136.2i     1.6108
+    1   10.5i     84.9i     2.1682
+    2    5.1i     41.1i     1.7327
+    3    0.3i      2.2i     0.0080
+    4    2.4      19.0      0.1186
+    5   15.3     123.5      1.4956
+    6  195.5    1576.7      1.6437
+    7  458.9    3701.3      0.0284
+    8  473.0    3814.6      1.1812
+    -------------------------------------
+    Zero-point energy: 0.573 eV
+    Static dipole moment: 1.833 D
+    Maximum force on atom in `eqiulibrium`: 0.0026 eV/Å
+
+
+
+    This interface now also works for calculator 'siesta', 
+    (added get_dipole_moment for siesta).
+
+    Example:
+
+    >>> #!/usr/bin/env python
+
+    >>> from ase.io import read
+    >>> from ase.calculators.siesta import Siesta
+    >>> from ase.infrared import InfraRed
+
+    >>> bud = read('bud1.xyz')
+
+    >>> calc = Siesta(label='bud',
+    ...       meshcutoff=250 * Ry,
+    ...       basis='DZP',
+    ...       kpts=[1, 1, 1])
+
+    >>> calc.set_fdf('DM.MixingWeight', 0.08)
+    >>> calc.set_fdf('DM.NumberPulay', 3)
+    >>> calc.set_fdf('DM.NumberKick', 20)
+    >>> calc.set_fdf('DM.KickMixingWeight', 0.15)
+    >>> calc.set_fdf('SolutionMethod',      'Diagon')
+    >>> calc.set_fdf('MaxSCFIterations', 500)
+    >>> calc.set_fdf('PAO.BasisType',  'split')
+    >>> #50 meV = 0.003674931 * Ry
+    >>> calc.set_fdf('PAO.EnergyShift', 0.003674931 * Ry )
+    >>> calc.set_fdf('LatticeConstant', 1.000000 * Ang)
+    >>> calc.set_fdf('WriteCoorXmol',       'T')
+
+    >>> bud.set_calculator(calc)
+
+    >>> ir = InfraRed(bud)
+    >>> ir.run()
+    >>> ir.summary()
+
+
+
+
+    """
+    def __init__(self, atoms, indices=None, name='ir', delta=0.01, nfree=2, directions=None):
+        assert nfree in [2, 4]
+        self.atoms = atoms
+        if atoms.constraints:
+            print "WARNING! \n Your Atoms object is constrained. Some forces may be unintended set to zero. \n"
+        self.calc = atoms.get_calculator()
+        if indices is None:
+            indices = range(len(atoms))
+        self.indices = np.asarray(indices)
+        self.nfree = nfree
+        self.name = name+'-d%.3f' % delta
+        self.delta = delta
+        self.H = None
+        if directions is None:
+            self.directions = np.asarray([0, 1, 2])
+        else:
+            self.directions = np.asarray(directions)
+        self.ir = True
+
+    def read(self, method='standard', direction='central'):
+        self.method = method.lower()
+        self.direction = direction.lower()
+        assert self.method in ['standard', 'frederiksen']
+        if direction != 'central':
+            raise NotImplementedError('Only central difference is implemented at the moment.')
+
+        # Get "static" dipole moment and forces
+        name = '%s.eq.pckl' % self.name
+        [forces_zero, dipole_zero] = pickle.load(open(name))
+        self.dipole_zero = (sum(dipole_zero**2)**0.5)*units.Debye
+        self.force_zero = max([sum((forces_zero[j])**2)**0.5 for j in self.indices])
+
+        ndof = 3 * len(self.indices)
+        H = np.empty((ndof, ndof))
+        dpdx = np.empty((ndof, 3))
+        r = 0
+        for a in self.indices:
+            for i in 'xyz':
+                name = '%s.%d%s' % (self.name, a, i)
+                [fminus, dminus] = pickle.load(open(name + '-.pckl'))
+                [fplus, dplus] = pickle.load(open(name + '+.pckl'))
+                if self.nfree == 4:
+                    [fminusminus, dminusminus] = pickle.load(open(name + '--.pckl'))
+                    [fplusplus, dplusplus] = pickle.load(open(name + '++.pckl'))
+                if self.method == 'frederiksen':
+                    fminus[a] += -fminus.sum(0)
+                    fplus[a] += -fplus.sum(0)
+                    if self.nfree == 4:
+                        fminusminus[a] += -fminus.sum(0)
+                        fplusplus[a] += -fplus.sum(0)
+                if self.nfree == 2:
+                    H[r] = (fminus - fplus)[self.indices].ravel() / 2.0
+                    dpdx[r] = (dminus - dplus)
+                if self.nfree == 4:
+                    H[r] = (-fminusminus+8*fminus-8*fplus+fplusplus)[self.indices].ravel() / 12.0
+                    dpdx[r] = (-dplusplus + 8*dplus - 8*dminus +dminusminus) / 6.0
+                H[r] /= 2 * self.delta
+                dpdx[r] /= 2 * self.delta
+                for n in range(3):
+                    if n not in self.directions:
+                        dpdx[r][n] = 0
+                        dpdx[r][n] = 0
+                r += 1
+        # Calculate eigenfrequencies and eigenvectors
+        m = self.atoms.get_masses()
+        H += H.copy().T
+        self.H = H
+        m = self.atoms.get_masses()
+        self.im = np.repeat(m[self.indices]**-0.5, 3)
+        omega2, modes = np.linalg.eigh(self.im[:, None] * H * self.im)
+        self.modes = modes.T.copy()
+
+        # Calculate intensities
+        dpdq = np.array([dpdx[j]/sqrt(m[self.indices[j/3]]*units._amu/units._me) for j in range(ndof)])
+        dpdQ = np.dot(dpdq.T, modes)
+        dpdQ = dpdQ.T
+        intensities = np.array([sum(dpdQ[j]**2) for j in range(ndof)])
+        # Conversion factor:
+        s = units._hbar * 1e10 / sqrt(units._e * units._amu)
+        self.hnu = s * omega2.astype(complex)**0.5
+        # Conversion factor from atomic units to (D/Angstrom)^2/amu.
+        conv = units.Debye**2*units._amu/units._me
+        self.intensities = intensities*conv
+
+    def summary(self, method='standard', direction='central'):
+        hnu = self.get_energies(method, direction)
+        s = 0.01 * units._e / units._c / units._hplanck
+        parprint('-------------------------------------')
+        parprint(' Mode    Frequency        Intensity')
+        parprint('  #    meV     cm^-1   (D/Å)^2 amu^-1')
+        parprint('-------------------------------------')
+        for n, e in enumerate(hnu):
+            if e.imag != 0:
+                c = 'i'
+                e = e.imag
+            else:
+                c = ' '
+            parprint('%3d %6.1f%s  %7.1f%s  %9.4f' % 
+                     (n, 1000 * e, c, s * e, c, self.intensities[n]))
+        parprint('-------------------------------------')
+        parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy())
+        parprint('Static dipole moment: %.3f D' % self.dipole_zero)
+        parprint('Maximum force on atom in `eqiulibrium`: %.4f eV/Å' % 
+                  self.force_zero)
+        parprint()
+
+    def get_spectrum(self, start=800, end=4000, npts=None, width=4, type='Gaussian', method='standard', direction='central'):
+        """Get infrared spectrum.
+
+        The method returns wavenumbers in cm^-1 with corresonding absolute infrared intensity.
+        Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1."""
+
+        self.type = type.lower()
+        assert self.type in ['gaussian', 'lorentzian']
+        if not npts: 
+            npts = (end-start)/width*10+1
+        frequencies = self.get_frequencies(method, direction).real
+        intensities=self.intensities
+        if type == 'lorentzian':
+            intensities = intensities*width*pi/2.
+        else:
+            sigma = width/2./sqrt(2.*log(2.))
+        #Make array with spectrum data
+        spectrum = np.empty(npts,np.float)
+        energies = np.empty(npts,np.float)
+        ediff = (end-start)/float(npts-1)
+        energies = np.arange(start, end+ediff/2, ediff)
+        for i, energy in enumerate(energies):
+            energies[i] = energy
+            if type == 'lorentzian':
+                spectrum[i] = (intensities*0.5*width/pi/((frequencies-energy)**2+0.25*width**2)).sum()
+            else:
+                spectrum[i] = (intensities*np.exp(-(frequencies - energy)**2/2./sigma**2)).sum()
+        return [energies, spectrum]
+
+    def write_spectra(self, out='ir-spectra.dat', start=800, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'):
+        """Write out infrared spectrum to file.
+
+        First column is the wavenumber in cm^-1, the second column the absolute infrared intensities, and
+        the third column the absorbance scaled so that data runs from 1 to 0. Start and end 
+        point, and width of the Gaussian/Lorentzian should be given in cm^-1."""
+        energies, spectrum = self.get_spectrum(start, end, npts, width, type, method, direction)
+
+        #Write out spectrum in file. First column is absolute intensities. 
+        #Second column is absorbance scaled so that data runs from 1 to 0
+        spectrum2 = 1. - spectrum/spectrum.max()
+        outdata = np.empty([len(energies), 3])
+        outdata.T[0] = energies
+        outdata.T[1] = spectrum
+        outdata.T[2] = spectrum2
+        fd = open(out, 'w')
+        for row in outdata:
+            fd.write('%.3f  %15.5e  %15.5e \n' % (row[0], row[1], row[2]) )
+        fd.close()
+        #np.savetxt(out, outdata, fmt='%.3f  %15.5e  %15.5e')
Index: ase/parallel.py
===================================================================
--- ase/parallel.py	(revision 0)
+++ ase/parallel.py	(revision 1)
@@ -0,0 +1,95 @@
+import sys
+import time
+import atexit
+
+
+def paropen(name, mode='r', buffering=0):
+    """MPI-safe version of open function.
+
+    In read mode, the file is opened on all nodes.  In write and
+    append mode, the file is opened on the master only, and /dev/null
+    is opened on all other nodes.
+    """
+    if rank > 0 and mode[0] != 'r':
+        name = '/dev/null'
+    return open(name, mode, buffering)
+
+
+def parprint(*args, **kwargs):
+    """MPI save print - prints only from master.
+
+    Tries to adopt python 3 behaviour.
+    """
+    if rank > 0:
+        return
+    defaults = { 'end' : '\n',
+                 'file' : sys.stdout }
+    for key in defaults:
+        if not key in kwargs:
+            kwargs[key] = defaults[key]
+
+    for arg in args[:-1]:
+        print >> kwargs['file'], arg,
+    if len(args):
+        last = args[-1]
+    else:
+        last = ''
+    if kwargs['end'] == '\n':
+        print last
+    else:
+        print last,
+
+# Check for special MPI-enabled Python interpreters:
+if '_gpaw' in sys.modules:
+    # http://wiki.fysik.dtu.dk/gpaw
+    from gpaw.mpi import world
+    rank = world.rank
+    size = world.size
+    barrier = world.barrier
+elif 'asapparallel3' in sys.modules:
+    # http://wiki.fysik.dtu.dk/Asap
+    # We cannot import asap3.mpi here, as that creates an import deadlock
+    #from asap3.mpi import world
+    import asapparallel3
+    world = asapparallel3.Communicator()
+    rank = world.rank
+    size = world.size
+    barrier = world.barrier    
+elif 'Scientific_mpi' in sys.modules:
+    # 
+    from Scientific.MPI import world
+    rank = world.rank
+    size = world.size
+    barrier = world.barrier
+else:
+    # This is a standard Python interpreter:
+    rank = 0
+    size = 1
+    world = None
+    def barrier():
+        pass
+
+
+
+
+def register_parallel_cleanup_function():
+    """Call MPI_Abort if python crashes.
+
+    This will terminate the processes on the other nodes."""
+        
+    if size == 1:
+        return
+
+    def cleanup(sys=sys, time=time, world=world):
+        error = getattr(sys, 'last_type', None)
+        if error:
+            sys.stdout.flush()
+            sys.stderr.write(('ASE CLEANUP (node %d): %s occurred.  ' +
+                              'Calling MPI_Abort!\n') % (world.rank, error))
+            sys.stderr.flush()
+            # Give other nodes a moment to crash by themselves (perhaps
+            # producing helpful error messages):
+            time.sleep(3)
+            world.abort(42)
+
+    atexit.register(cleanup)
Index: ase/gui/status.py
===================================================================
--- ase/gui/status.py	(revision 0)
+++ ase/gui/status.py	(revision 1)
@@ -0,0 +1,111 @@
+# -*- coding: utf-8 -*-
+
+from math import sqrt, pi, acos
+
+import gtk
+import numpy as np
+
+from ase.data import chemical_symbols as symbols
+from ase.data import atomic_names as names
+from ase.gui.widgets import pack
+from ase.gui.languages import translate as _
+
+def formula(Z):
+    hist = {}
+    for z in Z:
+        if z in hist:
+            hist[z] += 1
+        else:
+            hist[z] = 1
+    text = ''
+    Z = hist.keys()
+    Z.sort()
+    for z in Z:
+        text += symbols[z]
+        n = hist[z]
+        if n > 1:
+            text += '<sub>%d</sub>' % n
+    return text
+
+class Status:
+    def __init__(self, vbox):
+        self.eventbox = gtk.EventBox()
+        self.label = gtk.Label()
+        self.eventbox.add(self.label)
+        self.label.show()
+        if gtk.pygtk_version < (2, 12):
+            self.set_tip(self.eventbox, _('Tip for status box ...'))
+        else:
+            self.eventbox.set_tooltip_text(_('Tip for status box ...'))
+        pack(vbox, self.eventbox)
+        self.ordered_indices = []
+
+    def status(self):
+        # use where here:  XXX
+        indices = np.arange(self.images.natoms)[self.images.selected]
+        ordered_indices = self.images.selected_ordered
+        n = len(indices)
+        self.nselected = n
+        
+        if n == 0:
+            self.label.set_text('')
+            return
+
+        Z = self.images.Z[indices]
+        R = self.R[indices]
+
+        if n == 1:
+            tag = self.images.T[indices][0]
+            mom = self.images.M[self.frame][indices]
+            text = (u' #%d %s (%s): %.3f Å, %.3f Å, %.3f Å ' %
+                    ((indices[0], names[Z[0]], symbols[Z[0]]) + tuple(R[0])))
+        
+            text+=' tag=%s mom=%1.2f' % (tag, mom)        
+        elif n == 2:
+            D = R[0] - R[1]
+            d = sqrt(np.dot(D, D))
+            text = u' %s-%s: %.3f Å' % (symbols[Z[0]], symbols[Z[1]], d)
+        elif n == 3:
+            d = []
+            for c in range(3):
+                D = R[c] - R[(c + 1) % 3]
+                d.append(np.dot(D, D))
+            a = []
+            for c in range(3):
+                t1 = 0.5 * (d[c] + d[(c + 1) % 3] - d[(c + 2) % 3])
+                t2 = sqrt(d[c] * d[(c + 1) % 3])
+                try:
+                    t3 = acos(t1 / t2)
+                except ValueError:
+                    if t1 > 0:
+                        t3 = 0
+                    else:
+                        t3 = pi
+                a.append(t3 * 180 / pi)
+            text = (u' %s-%s-%s: %.1f°, %.1f°, %.1f°' %
+                    tuple([symbols[z] for z in Z] + a))
+        elif len(ordered_indices) == 4:
+            R = self.R[ordered_indices]
+            Z = self.images.Z[ordered_indices]
+            a    = R[1]-R[0]
+            b    = R[2]-R[1]
+            c    = R[3]-R[2]
+            bxa  = np.cross(b,a)
+            bxa /= np.sqrt(np.vdot(bxa,bxa))
+            cxb  = np.cross(c,b)
+            cxb /= np.sqrt(np.vdot(cxb,cxb))
+            angle = np.vdot(bxa,cxb)
+            if angle < -1: angle = -1
+            if angle >  1: angle =  1
+            angle = np.arccos(angle)
+            if (np.vdot(bxa,c)) > 0: angle = 2*np.pi-angle
+            angle = angle*180.0/np.pi
+            text = ( u'dihedral %s->%s->%s->%s: %.1f°' % tuple([symbols[z] for z in Z]+[angle]))
+        else:
+            text = ' ' + formula(Z)
+            
+        self.label.set_markup(text)
+        
+if __name__ == '__main__':
+    import os
+    os.system('python gui.py')
Index: ase/gui/rot_tools.py
===================================================================
--- ase/gui/rot_tools.py	(revision 0)
+++ ase/gui/rot_tools.py	(revision 1)
@@ -0,0 +1,48 @@
+# Gives the rotation matrix which rotates theta degrees about
+# vecU
+
+#  Generates the rotation matrix that rotate theta degrees about the vecU
+def rotate_about_vec(vecU, theta):
+    import numpy as np
+    vecU = np.array(vecU)
+    vecU = vecU / (sum(vecU ** 2) ** 0.5)
+    ux, uy, uz = vecU
+    st = np.sin(theta)
+    ct = np.cos(theta)
+    mat = np.array([[ux ** 2 + ct * (1 - ux ** 2), 
+                     ux * uy * (1 - ct) - uz * st, 
+                     uz * ux * (1 - ct) + uy * st],
+                    [ux * uy * (1 - ct) + uz * st, 
+                     uy ** 2 + ct * (1 - uy ** 2),
+                     uy * uz * (1 - ct) - ux * st],
+                    [uz * ux * (1 - ct) - uy * st,
+                     uy * uz * (1 - ct) + ux * st,
+                     uz ** 2 + ct * (1 - uz **2)]])
+    return (mat)
+
+# Generates the rotation matrix which rotates aVec into intoVec
+def rotate_vec_into_newvec(aVec, intoVec):
+    def length(v):
+        return((sum(v ** 2)) ** 0.5)
+
+    import numpy as np
+    from math import acos
+    fac = 1.0
+    aVec = np.array(aVec)
+    intoVec = np.array(intoVec)
+    nor = np.cross(aVec, intoVec)
+    if length(nor) == 0:
+        nor = np.array([1, 0, 0])
+    nor = nor / length(nor)
+    theta = acos(np.dot(aVec, intoVec) / (length(aVec) * length(intoVec)))
+    if np.dot(aVec, intoVec) < 0:
+        theta = theta + np.pi
+        fac = -1
+    return(fac * rotate_about_vec(nor, theta))
+
+# Applies the rotation matrix to the vector and returns the rotated vector
+def rotate_vec (rot_mat, vec):
+    import numpy as np
+    rot_vec = np.dot(rot_mat, vec)
+
+    return (rot_vec)
Index: ase/gui/simulation.py
===================================================================
--- ase/gui/simulation.py	(revision 0)
+++ ase/gui/simulation.py	(revision 1)
@@ -0,0 +1,129 @@
+"Base class for simulation windows"
+
+import gtk
+from ase.gui.widgets import oops, pack
+from ase import Atoms
+
+class Simulation(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        self.gui = gui
+
+    def packtext(self, vbox, text, label=None):
+        "Pack an text frame into the window."
+        pack(vbox, gtk.Label(""))
+        txtframe = gtk.Frame(label)
+        txtlbl = gtk.Label(text)
+        txtframe.add(txtlbl)
+        txtlbl.show()
+        pack(vbox, txtframe)
+        pack(vbox, gtk.Label(""))
+
+    def packimageselection(self, outerbox, txt1=" (rerun simulation)",
+                           txt2=" (continue simulation)"):
+        "Make the frame for selecting starting config if more than one."
+        self.startframe = gtk.Frame("Select starting configuration:")
+        pack(outerbox, [self.startframe])
+        vbox = gtk.VBox()
+        self.startframe.add(vbox)
+        vbox.show()
+        self.numconfig_format = "There are currently %i configurations loaded."
+        self.numconfig_label = gtk.Label("")
+        pack(vbox, [self.numconfig_label])
+        lbl = gtk.Label("Choose which one to use as the initial configuration")
+        pack(vbox, [lbl])
+        self.start_radio_first = gtk.RadioButton(
+            None, "The first configuration"+txt1+".")
+        pack(vbox, [self.start_radio_first])
+        self.start_radio_nth = gtk.RadioButton(self.start_radio_first,
+                                               "Configuration number ")
+        self.start_nth_adj = gtk.Adjustment(0, 0, 1, 1)
+        self.start_nth_spin = gtk.SpinButton(self.start_nth_adj, 0, 0)
+        self.start_nth_spin.set_sensitive(False)
+        pack(vbox, [self.start_radio_nth, self.start_nth_spin])
+        self.start_radio_last = gtk.RadioButton(self.start_radio_first,
+            "The last configuration"+txt2+".")
+        self.start_radio_last.set_active(True)
+        pack(vbox, self.start_radio_last)
+        self.start_radio_nth.connect("toggled", self.start_radio_nth_toggled)
+        self.setupimageselection()
+        
+    def start_radio_nth_toggled(self, widget):
+        self.start_nth_spin.set_sensitive(self.start_radio_nth.get_active())
+
+    def setupimageselection(self):
+        "Decide if the start image selection frame should be shown."
+        n = self.gui.images.nimages
+        if n <= 1:
+            self.startframe.hide()
+        else:
+            self.startframe.show()
+            if self.start_nth_adj.value >= n:
+                self.start_nth_adj.value = n-1
+            self.start_nth_adj.upper = n-1
+            self.numconfig_label.set_text(self.numconfig_format % (n,))
+
+    def getimagenumber(self):
+        "Get the image number selected in the start image frame."
+        nmax = self.gui.images.nimages
+        if nmax <= 1:
+            return 0
+        elif self.start_radio_first.get_active():
+            return 0
+        elif self.start_radio_nth.get_active():
+            return self.start_nth_adj.value
+        else:
+            assert self.start_radio_last.get_active()
+            return nmax-1
+
+    def makebutbox(self, vbox):
+        self.buttons = gtk.HButtonBox()
+        runbut = gtk.Button("Run")
+        runbut.connect('clicked', self.run)
+        closebut = gtk.Button(stock=gtk.STOCK_CLOSE)
+        closebut.connect('clicked', lambda x: self.destroy())
+        for w in (runbut, closebut):
+            self.buttons.pack_start(w, 0, 0)
+            w.show()
+        pack(vbox, [self.buttons], end=True, bottom=True)
+
+    def setup_atoms(self):
+        self.atoms = self.get_atoms()
+        if self.atoms is None:
+            return False
+        try:
+            self.calculator = self.gui.simulation['calc']
+        except KeyError:
+            oops("No calculator: Use Calculate/Set Calculator on the menu.")
+            return False
+        self.atoms.set_calculator(self.calculator())
+        return True
+    
+    def get_atoms(self):
+        "Make an atoms object from the active image"
+        images = self.gui.images
+        if images.natoms < 1:
+            oops("No atoms present")
+            return None
+        n = self.getimagenumber()
+        return Atoms(positions=images.P[n], symbols=images.Z,
+                     cell=images.A[n], pbc=images.pbc)
+
+    def begin(self, **kwargs):
+        if self.gui.simulation.has_key('progress'):
+            self.gui.simulation['progress'].begin(**kwargs)
+
+    def end(self):
+        if self.gui.simulation.has_key('progress'):
+            self.gui.simulation['progress'].end()
+
+    def prepare_store_atoms(self):
+        "Informs the gui that the next configuration should be the first."
+        self.gui.prepare_new_atoms()
+        self.count_steps = 0
+        
+    def store_atoms(self):
+        "Observes the minimization and stores the atoms in the gui."
+        self.gui.append_atoms(self.atoms)
+        self.count_steps += 1
+
Index: ase/gui/pybutton.py
===================================================================
--- ase/gui/pybutton.py	(revision 0)
+++ ase/gui/pybutton.py	(revision 1)
@@ -0,0 +1,79 @@
+
+"""pybutton.py - a button for displaying Python code.
+
+This module defines two classes, together they implement a button that
+a module can use to display Python.
+
+PyButton
+--------
+
+PyButton is a gkt.Button with the label 'Python'.  When pressed, it
+opens a PyWindow displaying some Python code, or an error message if
+no Python code is ready.
+
+The script is stored in the attribute .python, it is the
+responsability of the owning object to keep this attribute up to date:
+when pressing the Apply button would result in a sensible
+configuration being created, the python attribute must be set to a
+string creating this code.  When pressing Apply would cause an error,
+the python attribute must be set to None.
+
+PyWindow
+--------
+
+Displays the Python code.  This object is created by the PyButton
+object when needed.
+"""
+
+import gtk
+import time
+from ase.gui.widgets import oops, pack
+
+
+class PyButton(gtk.Button):
+    "A button for displaying Python code."
+    def __init__(self, title):
+        gtk.Button.__init__(self, "Python")
+        self.title = title
+        self.python = None
+        self.connect_after('clicked', self.run)
+
+    def run(self, *args):
+        "The method called when the button is click."
+        if self.python:
+            now = time.ctime()
+            win = PyWindow(self.title, now, self.python)
+        else:
+            oops("No Python code",
+                 "You have not (yet) specified a consistent set of parameters.")
+
+fr1_template = """
+Title: %s
+Time: %s
+"""
+
+class PyWindow(gtk.Window):
+    "A window displaying Python code."
+    def __init__(self, title, time, code):
+        gtk.Window.__init__(self)
+        self.set_title("ag: Python code")
+        vbox = gtk.VBox()
+        lbl = gtk.Label(fr1_template % (title, time))
+        lbl.set_alignment(0.0, 0.5)
+        fr = gtk.Frame("Information:")
+        fr.add(lbl)
+        pack(vbox, fr)
+        txtbuf = gtk.TextBuffer()
+        txtbuf.set_text(code)
+        txtview = gtk.TextView(txtbuf)
+        txtview.set_editable(False)
+        fr = gtk.Frame("Python code:")
+        fr.add(txtview)
+        fr.set_label_align(0.0, 0.5)
+        pack(vbox, fr)
+        but =  gtk.Button(stock=gtk.STOCK_OK)
+        but.connect('clicked', lambda x: self.destroy())
+        pack(vbox, [but], end=True, bottom=True)
+        self.add(vbox)
+        self.show_all()
+        
Index: ase/gui/debug.py
===================================================================
--- ase/gui/debug.py	(revision 0)
+++ ase/gui/debug.py	(revision 1)
@@ -0,0 +1,20 @@
+import sys
+
+import gtk
+
+
+class Debug(gtk.Window):
+    def __init__(self, gui):
+        self.gui = gui
+        gtk.Window.__init__(self)
+        self.set_title('Debug')
+        entry = gtk.Entry(200)
+        self.add(entry)
+        entry.connect('activate', self.enter, entry)
+        entry.show()
+        self.show()
+
+    def enter(self, widget, entry):
+        g = self.gui
+        print >> sys.stderr, eval(entry.get_text())
+    
Index: ase/gui/setupwindow.py
===================================================================
--- ase/gui/setupwindow.py	(revision 0)
+++ ase/gui/setupwindow.py	(revision 1)
@@ -0,0 +1,64 @@
+# encoding: utf-8
+"""setupwindow.py - Window base class for setup modules.
+"""
+
+import gtk
+from ase.gui.widgets import pack, cancel_apply_ok, oops
+import ase
+
+class SetupWindow(gtk.Window):
+    "Base class for ase.gui setup windows."
+    # __init__ inherited from gtk.Window
+
+    def packtext(self, vbox, text, label=None):
+        "Pack an text frame into the window."
+        pack(vbox, gtk.Label(""))
+        txtframe = gtk.Frame(label)
+        txtlbl = gtk.Label(text)
+        txtframe.add(txtlbl)
+        txtlbl.show()
+        pack(vbox, txtframe)
+        pack(vbox, gtk.Label(""))
+
+    def update_element(self, *args):
+        "Called when a new element may have been entered."
+        # Assumes the element widget is self.element and that a label
+        # to keep updated is self.elementinfo.  The chemical symbol is
+        # placed in self.legalelement - or None if the element is
+        # invalid.
+        elem = self.element.get_text()
+        if not elem:
+            self.invalid_element("  No element specified!")
+            return False
+        try:
+            z = int(elem)
+        except ValueError:
+            # Probably a symbol
+            try:
+                z = ase.data.atomic_numbers[elem]
+            except KeyError:
+                self.invalid_element()
+                return False
+        try:
+            symb = ase.data.chemical_symbols[z]
+        except KeyError:
+            self.invalid_element()
+            return False
+        name = ase.data.atomic_names[z]
+        ref = ase.data.reference_states[z]
+        if ref is None:
+            struct = "No crystal structure data"
+        else:
+            struct = ref['symmetry'].lower()
+            if struct == 'fcc' or struct == 'bcc':
+                struct = "%s (a=%.3f Å)" % (struct, ref['a'])
+        
+        txt = "  %s: %s, Z=%i, %s" % (name, symb, z, struct)
+        self.elementinfo.set_text(txt)
+        self.legal_element = symb
+        return True
+        
+    def invalid_element(self, txt="  ERROR: Invalid element!"):
+        self.legal_element = False
+        self.elementinfo.set_text(txt)
+
Index: ase/gui/graphene.py
===================================================================
--- ase/gui/graphene.py	(revision 0)
+++ ase/gui/graphene.py	(revision 1)
@@ -0,0 +1,237 @@
+# encoding: utf-8
+"""nanotube.py - Window for setting up Graphene sheets and ribbons.
+"""
+
+
+import gtk
+from ase.gui.widgets import pack, cancel_apply_ok, oops
+from ase.gui.setupwindow import SetupWindow
+from ase.gui.pybutton import PyButton
+from ase.structure import graphene_nanoribbon
+import ase
+import numpy as np
+
+introtext = """\
+Set up a graphene sheet or a graphene nanoribbon.  A nanoribbon may
+optionally be saturated with hydrogen (or another element).\
+"""
+
+py_template = """
+from ase.structure import nanotube
+
+atoms = nanotube(%(n)i, %(m)i, length=%(length)i, bond=%(bl).3f, symbol=%(symb)s)
+"""
+
+class SetupGraphene(SetupWindow):
+    "Window for setting up a graphene sheet or nanoribbon."
+    def __init__(self, gui):
+        SetupWindow.__init__(self)
+        self.set_title("Nanotube")
+        vbox = gtk.VBox()
+
+        # Intoductory text
+        self.packtext(vbox, introtext)
+
+        # Choose structure
+        label = gtk.Label("Structure: ")
+        self.struct = gtk.combo_box_new_text()
+        for s in ("Infinite sheet", "Unsaturated ribbon", "Saturated ribbon"):
+            self.struct.append_text(s)
+        self.struct.set_active(0)
+        self.struct.connect('changed', self.update_gui)
+        pack(vbox, [label, self.struct])
+
+        # Orientation
+        label = gtk.Label("Orientation: ")
+        self.orient = gtk.combo_box_new_text()
+        self.orient_text = []
+        for s in ("zigzag", "armchair"):
+            self.orient.append_text(s)
+            self.orient_text.append(s)
+        self.orient.set_active(0)
+        self.orient.connect('changed', self.update_gui)
+        pack(vbox, [label, self.orient])
+        pack(vbox, gtk.Label(""))
+
+
+        # Choose the element and bond length
+        label1 = gtk.Label("Element: ")
+        #label.set_alignment(0.0, 0.2)
+        self.element = gtk.Entry(max=3)
+        self.element.set_text("C")
+        self.element.connect('activate', self.update_element)
+        self.bondlength = gtk.Adjustment(1.42, 0.0, 1000.0, 0.01)
+        label2 = gtk.Label("  Bond length: ")
+        label3 = gtk.Label("Å")
+        bond_box = gtk.SpinButton(self.bondlength, 10.0, 3)
+        pack(vbox, [label1, self.element, label2, bond_box, label3])
+
+        # Choose the saturation element and bond length
+        self.sat_label1 = gtk.Label("Saturation: ")
+        #label.set_alignment(0.0, 0.2)
+        self.element2 = gtk.Entry(max=3)
+        self.element2.set_text("H")
+        self.element2.connect('activate', self.update_element)
+        self.bondlength2 = gtk.Adjustment(1.12, 0.0, 1000.0, 0.01)
+        self.sat_label2 = gtk.Label("  Bond length: ")
+        self.sat_label3 = gtk.Label("Å")
+        self.bond_box = gtk.SpinButton(self.bondlength2, 10.0, 3)
+        pack(vbox, [self.sat_label1, self.element2, self.sat_label2,
+                    self.bond_box, self.sat_label3])
+
+        self.elementinfo = gtk.Label("")
+        self.elementinfo.modify_fg(gtk.STATE_NORMAL,
+                                   gtk.gdk.color_parse('#FF0000'))
+        pack(vbox, [self.elementinfo])
+        pack(vbox, gtk.Label(""))
+
+        # Size
+        label1 = gtk.Label("Width: ")
+        label2 = gtk.Label("  Length: ")
+        self.n = gtk.Adjustment(1, 1, 100, 1)
+        self.m = gtk.Adjustment(1, 1, 100, 1)
+        spinn = gtk.SpinButton(self.n, 0, 0)
+        spinm = gtk.SpinButton(self.m, 0, 0)
+        pack(vbox, [label1, spinn, label2, spinm])
+
+        # Vacuum
+        label1 = gtk.Label("Vacuum: ")
+        self.vacuum = gtk.Adjustment(5.0, 0.0, 1000.0, 0.1)
+        label2 = gtk.Label("Å")
+        vac_box = gtk.SpinButton(self.vacuum, 10.0, 2)
+        pack(vbox, [label1, vac_box, label2])
+        pack(vbox, gtk.Label(""))
+
+
+        # Buttons
+        #self.pybut = PyButton("Creating a nanoparticle.")
+        #self.pybut.connect('clicked', self.makeatoms)
+        buts = cancel_apply_ok(cancel=lambda widget: self.destroy(),
+                               apply=self.apply,
+                               ok=self.ok)
+        pack(vbox, [buts], end=True, bottom=True)
+
+        # Finalize setup
+        self.update_gui()
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+    def update_element(self, *args):
+        "Called when a new element may have been entered."
+        # Assumes the element widget is self.element and that a label
+        # for errors is self.elementinfo.  The chemical symbol is
+        # placed in self.legalelement - or None if the element is
+        # invalid.
+        symb = []
+        if self.struct.get_active() == 2:
+            # Saturated nanoribbon
+            elements = (self.element.get_text(), self.element2.get_text())
+        else:
+            elements = (self.element.get_text(), )
+            
+        for elem in elements:
+            if not elem:
+                self.invalid_element("  No element specified!")
+                return False
+            try:
+                z = int(elem)
+            except ValueError:
+                # Probably a symbol
+                try:
+                    z = ase.data.atomic_numbers[elem]
+                except KeyError:
+                    self.invalid_element()
+                    return False
+            try:
+                symb.append(ase.data.chemical_symbols[z])
+            except KeyError:
+                self.invalid_element()
+                return False
+        self.elementinfo.set_text("")
+        self.legal_element = symb[0]
+        if len(symb) == 2:
+            self.legal_element2 = symb[1]
+        else:
+            self.legal_element2 = None
+        return True
+        
+    def update_gui(self, *args):
+        # Saturation element is only relevant for saturated nanoribbons
+        satur = self.struct.get_active() == 2
+        for w in (self.element2, self.bond_box):
+            w.set_sensitive(satur)
+        # Infinite zigzag sheets must have even width
+        if self.struct.get_active() == 0 and self.orient.get_active() == 0:
+            if self.n.value % 2 == 1:
+                self.n.value += 1
+            self.n.lower = 2
+            self.n.step_increment = 2
+        else:
+            self.n.lower = 1
+            self.n.step_increment = 1
+        
+    def makeatoms(self, *args):
+        self.update_element()
+        if self.legal_element is None or (self.struct.get_active() == 2 and
+                                          self.legal_element2 is None):
+            self.atoms = None
+            self.pybut.python = None
+        else:
+            n = int(self.n.value)
+            m = int(self.m.value)
+            CC = self.bondlength.value
+            vacuum = self.vacuum.value
+            orient = self.orient_text[self.orient.get_active()]
+            elem = self.legal_element
+            if self.struct.get_active() == 0:
+                # Extended sheet
+                self.atoms = graphene_nanoribbon(n, m, type=orient, C_C=CC,
+                                                 vacc=vacuum, sheet=True,
+                                                 main_element=elem)
+            elif self.struct.get_active() == 1:
+                # Unsaturated nanoribbon
+                self.atoms = graphene_nanoribbon(n, m, type=orient, C_C=CC,
+                                                 vacc=vacuum,
+                                                 main_element=elem)
+            elif self.struct.get_active() == 2:
+                # Saturated nanoribbon
+                elem2 = self.legal_element2
+                self.atoms = graphene_nanoribbon(n, m, type=orient, C_C=CC,
+                                                 C_H=self.bondlength2.value,
+                                                 vacc=vacuum,
+                                                 saturated=True,
+                                                 main_element=elem,
+                                                 satur_element=elem2)
+            else:
+                raise RuntimeError("Unknown structure in SetupGraphene!")
+        # Now, rotate into the xy plane (ase.gui's default view plane)
+        pos = self.atoms.get_positions()
+        cell = self.atoms.get_cell()
+        pbc = self.atoms.get_pbc()
+        cell[1,1], cell[2,2] = cell[2,2], cell[1,1]
+        x = pos[:,1].copy()
+        z = pos[:,2].copy()
+        pos[:,1] = z
+        pos[:,2] = x
+        self.atoms.set_cell(cell)
+        self.atoms.set_positions(pos)
+        self.atoms.set_pbc([pbc[0], pbc[2], pbc[1]])
+
+    def apply(self, *args):
+        self.makeatoms()
+        if self.atoms is not None:
+            self.gui.new_atoms(self.atoms)
+            return True
+        else:
+            oops("No valid atoms.",
+                 "You have not (yet) specified a consistent set of parameters.")
+            return False
+
+    def ok(self, *args):
+        if self.apply():
+            self.destroy()
+            
+            
+                                                 
Index: ase/gui/surfaceslab.py
===================================================================
--- ase/gui/surfaceslab.py	(revision 0)
+++ ase/gui/surfaceslab.py	(revision 1)
@@ -0,0 +1,240 @@
+# encoding: utf-8
+"""surfaceslab.py - Window for setting up surfaces
+"""
+
+import gtk
+from ase.gui.widgets import pack, cancel_apply_ok, oops
+from ase.gui.pybutton import PyButton
+from ase.gui.setupwindow import SetupWindow
+import ase.lattice.surface as _surf
+import ase
+import numpy as np
+
+introtext = """\
+  Use this dialog to create surface slabs.  Select the element by
+writing the chemical symbol or the atomic number in the box.  Then
+select the desired surface structure.  Note that some structures can
+be created with an othogonal or a non-orthogonal unit cell, in these
+cases the non-orthogonal unit cell will contain fewer atoms.
+
+  If the structure matches the experimental crystal structure, you can
+look up the lattice constant, otherwise you have to specify it
+yourself."""
+
+# Name, structure, orthogonal, support-nonorthogonal, function
+surfaces = [('FCC(100)', 'fcc', True, False, _surf.fcc100),
+            ('FCC(110)', 'fcc', True, False, _surf.fcc110),
+            ('FCC(111) non-orthogonal', 'fcc', False, True, _surf.fcc111),
+            ('FCC(111) orthogonal', 'fcc', True, True, _surf.fcc111),
+            ('BCC(100)', 'bcc', True, False, _surf.bcc100),
+            ('BCC(110) non-orthogonal', 'bcc', False, True, _surf.bcc110),
+            ('BCC(110) orthogonal', 'bcc', True, True, _surf.bcc110),
+            ('BCC(111) non-orthogonal', 'bcc', False, True, _surf.bcc111),
+            ('BCC(111) orthogonal', 'bcc', True, True, _surf.bcc111),
+            ('HCP(0001) non-orthogonal', 'hcp', False, True, _surf.hcp0001),
+            ('HCP(0001) orthogonal', 'hcp', True, True, _surf.hcp0001),
+            ]
+
+py_template = """
+from ase.lattice.surface import %(func)s
+
+atoms = %(func)s(symbol='%(symbol)s', size=%(size)s,
+    a=%(a).3f, vacuum=%(vacuum).3f%(orthoarg)s)
+"""
+
+class SetupSurfaceSlab(SetupWindow):
+    """Window for setting up a surface."""
+    def __init__(self, gui):
+        SetupWindow.__init__(self)
+        self.set_title("Surface")
+        self.atoms = None
+
+        vbox = gtk.VBox()
+
+        # Intoductory text
+        self.packtext(vbox, introtext)
+             
+        # Choose the element
+        label = gtk.Label("Element: ")
+        element = gtk.Entry(max=3)
+        self.element = element
+        self.elementinfo = gtk.Label("")
+        pack(vbox, [label, element, self.elementinfo])
+        self.element.connect('activate', self.update)
+        self.legal_element = False
+        
+        # Choose the surface structure
+        label = gtk.Label("Structure: ")
+        self.structchoice = gtk.combo_box_new_text()
+        self.surfinfo = {}
+        for s in surfaces:
+            assert len(s) == 5
+            self.structchoice.append_text(s[0])
+            self.surfinfo[s[0]] = s
+        pack(vbox, [label, self.structchoice])
+        self.structchoice.connect('changed', self.update)
+
+        # Choose the lattice constant
+        tbl = gtk.Table(2, 3)
+        label = gtk.Label("Lattice constant: ")
+        tbl.attach(label, 0, 1, 0, 1)
+        vbox2 = gtk.VBox()          # For the non-HCP stuff
+        self.vbox_hcp = gtk.VBox()  # For the HCP stuff.
+        self.lattice_const = gtk.Adjustment(3.0, 0.0, 1000.0, 0.01)
+        lattice_box = gtk.SpinButton(self.lattice_const, 10.0, 3)
+        lattice_box.numeric = True
+        pack(vbox2, [gtk.Label("a:"), lattice_box, gtk.Label("Å")])
+        tbl.attach(vbox2, 1, 2, 0, 1)
+        lattice_button = gtk.Button("Get from database")
+        tbl.attach(lattice_button, 2, 3, 0, 1)
+        # HCP stuff
+        self.hcp_ideal = (8.0/3)**(1.0/3)
+        self.lattice_const_c = gtk.Adjustment(self.lattice_const.value * self.hcp_ideal,
+                                              0.0, 1000.0, 0.01)
+        lattice_box_c = gtk.SpinButton(self.lattice_const_c, 10.0, 3)
+        lattice_box_c.numeric = True
+        pack(self.vbox_hcp, [gtk.Label("c:"), lattice_box_c, gtk.Label("Å")])
+        self.hcp_c_over_a_format = "c/a: %.3f (%.1f %% of ideal)"
+        self.hcp_c_over_a_label = gtk.Label(self.hcp_c_over_a_format % (self.hcp_ideal,
+                                                                        100.0))
+        pack(self.vbox_hcp, [self.hcp_c_over_a_label])
+        tbl.attach(self.vbox_hcp, 1, 2, 1, 2)
+        tbl.show_all()
+        pack(vbox, [tbl])
+        self.lattice_const.connect('value-changed', self.update)
+        self.lattice_const_c.connect('value-changed', self.update)
+        lattice_button.connect('clicked', self.get_lattice_const)
+        pack(vbox, gtk.Label(""))
+
+        # System size
+        self.size = [gtk.Adjustment(1, 1, 100, 1) for i in range(3)]
+        buttons = [gtk.SpinButton(s, 0, 0) for s in self.size]
+        self.vacuum = gtk.Adjustment(10.0, 0, 100.0, 0.1)
+        vacuum_box = gtk.SpinButton(self.vacuum, 0.0, 1)
+        pack(vbox, [gtk.Label("Size: \tx: "), buttons[0],
+                    gtk.Label(" unit cells")])
+        pack(vbox, [gtk.Label("\t\ty: "), buttons[1],
+                    gtk.Label(" unit cells")])
+        pack(vbox, [gtk.Label("      \t\tz: "), buttons[2],
+                    gtk.Label(" layers,  "),
+                    vacuum_box, gtk.Label(" Å vacuum")])
+        self.nosize = "\t\tNo size information yet."
+        self.sizelabel = gtk.Label(self.nosize)
+        pack(vbox, [self.sizelabel])
+        for s in self.size:
+            s.connect('value-changed', self.update)
+        self.vacuum.connect('value-changed', self.update)
+        pack(vbox, gtk.Label(""))
+
+        # Buttons
+        self.pybut = PyButton("Creating a surface slab.")
+        self.pybut.connect('clicked', self.update)
+        buts = cancel_apply_ok(cancel=lambda widget: self.destroy(),
+                               apply=self.apply,
+                               ok=self.ok)
+        pack(vbox, [self.pybut, buts], end=True, bottom=True)
+        
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+        # Hide the HCP stuff to begin with.
+        self.vbox_hcp.hide_all()
+
+    # update_element inherited from SetupWindow
+
+    def update(self, *args):
+        "Called when something has changed."
+        struct = self.structchoice.get_active_text()
+        if struct:
+            structinfo = self.surfinfo[struct]
+            if structinfo[1] == 'hcp':
+                self.vbox_hcp.show_all()
+                ca = self.lattice_const_c.value / self.lattice_const.value
+                self.hcp_c_over_a_label.set_text(self.hcp_c_over_a_format %
+                                                 (ca, 100 * ca / self.hcp_ideal))
+            else:
+                self.vbox_hcp.hide_all()
+        # Abort if element or structure is invalid
+        if not (self.update_element() and struct):
+            self.sizelabel.set_text(self.nosize)
+            self.atoms = None
+            self.pybut.python = None
+            return False
+        # Make the atoms
+        assert self.legal_element
+        kw = {}
+        kw2 = {}
+        if structinfo[3]:  # Support othogonal keyword?
+            kw['orthogonal'] = structinfo[2]
+            kw2['orthoarg'] = ', orthogonal='+str(kw['orthogonal'])
+        else:
+            kw2['orthoarg'] = ''
+        kw2['func'] = structinfo[4].__name__
+        kw['symbol'] = self.legal_element
+        kw['size'] = [int(s.value) for s in self.size]
+        kw['a'] = self.lattice_const.value
+        kw['vacuum'] = self.vacuum.value
+        # Now create the atoms
+        try:
+            self.atoms = structinfo[4](**kw)
+        except ValueError, e:
+            # The values were illegal - for example some size
+            # constants must be even for some structures.
+            self.pybut.python = None
+            self.atoms = None
+            self.sizelabel.set_text(str(e).replace(".  ", ".\n"))
+            return False
+        kw2.update(kw)
+        self.pybut.python = py_template % kw2
+        # Find the heights of the unit cell
+        h = np.zeros(3)
+        uc = self.atoms.get_cell()
+        for i in range(3):
+            norm = np.cross(uc[i-1], uc[i-2])
+            norm /= np.sqrt(np.dot(norm, norm))
+            h[i] = np.abs(np.dot(norm, uc[i]))
+        natoms = len(self.atoms)
+        txt = ("\t\t%.2f Å x %.2f Å x %.2f Å,  %i atoms."
+               % (h[0], h[1], h[2], natoms))
+        self.sizelabel.set_text(txt)
+        return True
+    
+    def get_lattice_const(self, *args):
+        if not self.update_element():
+            oops("Invalid element.")
+            return
+        z = ase.atomic_numbers[self.legal_element]
+        ref = ase.data.reference_states[z]
+        surface = self.structchoice.get_active_text()
+        if not surface:
+            oops("No structure specified!")
+            return
+        struct = self.surfinfo[surface][1]
+        if ref is None or ref['symmetry'].lower() != struct:
+            oops(struct.upper() + " lattice constant unknown for "
+                      + self.legal_element + ".")
+            return
+        a = ref['a']
+        self.lattice_const.set_value(a)
+        if struct == 'hcp':
+            c = ref['c/a'] * a
+            self.lattice_const_c.set_value(c)
+
+    def apply(self, *args):
+        self.update()
+        if self.atoms is not None:
+            self.gui.new_atoms(self.atoms)
+            return True
+        else:
+            oops("No valid atoms.",
+                 "You have not (yet) specified a consistent set of parameters.")
+            return False
+
+    def ok(self, *args):
+        if self.apply():
+            self.destroy()
+            
+
+        
Index: ase/gui/energyforces.py
===================================================================
--- ase/gui/energyforces.py	(revision 0)
+++ ase/gui/energyforces.py	(revision 1)
@@ -0,0 +1,91 @@
+# encoding: utf-8
+
+"Module for calculating energies and forces."
+
+import gtk
+from ase.gui.simulation import Simulation
+from ase.gui.widgets import oops, pack
+
+class OutputFieldMixin:
+    def makeoutputfield(self, box, label="Output:"):
+        frame = gtk.Frame(label)
+        if box is not None:
+            box.pack_start(frame, True, True, 0)
+        box2 = gtk.VBox()
+        frame.add(box2)
+        #pack(box, [gtk.Label("Output:")])
+        scrwin = gtk.ScrolledWindow()
+        scrwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        self.output = gtk.TextBuffer()
+        txtview = gtk.TextView(self.output)
+        txtview.set_editable(False)
+        scrwin.add(txtview)
+        scrwin.show_all()
+        box2.pack_start(scrwin, True, True, 0)
+        self.savebutton = gtk.Button(stock=gtk.STOCK_SAVE)
+        self.savebutton.connect('clicked', self.saveoutput)
+        self.savebutton.set_sensitive(False)
+        pack(box2, [self.savebutton])
+        box2.show()
+        frame.show()
+        return frame
+    
+    def activate_output(self):
+        self.savebutton.set_sensitive(True)
+
+    def saveoutput(self, widget):
+        chooser = gtk.FileChooserDialog(
+            'Save output', None, gtk.FILE_CHOOSER_ACTION_SAVE,
+            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+        ok = chooser.run()
+        if ok == gtk.RESPONSE_OK:
+            filename = chooser.get_filename()
+            txt = self.output.get_text(self.output.get_start_iter(),
+                                       self.output.get_end_iter())
+            f = open(filename, "w")
+            f.write(txt)
+            f.close()
+        chooser.destroy()
+        
+class EnergyForces(Simulation, OutputFieldMixin):
+    def __init__(self, gui):
+        Simulation.__init__(self, gui)
+
+        self.set_default_size(-1, 300)
+        vbox = gtk.VBox()
+        self.packtext(vbox,
+                      "Calculate potential energy and the force on all atoms")
+        self.packimageselection(vbox)
+        pack(vbox, gtk.Label(""))
+        self.forces = gtk.CheckButton("Write forces on the atoms")
+        self.forces.set_active(True)
+        pack(vbox, [self.forces])
+        pack(vbox, [gtk.Label("")])
+        self.makeoutputfield(vbox)
+        pack(vbox, gtk.Label(""))
+        self.makebutbox(vbox)
+        vbox.show()
+        self.add(vbox)
+        self.show()
+        self.gui.register_vulnerable(self)
+        
+    def run(self, *args):
+        if not self.setup_atoms():
+            return
+        self.begin()
+        e = self.atoms.get_potential_energy()
+        txt = "Potential Energy:\n"
+        txt += "  %8.3f eV\n\n" % (e,)
+        if self.forces.get_active():
+            txt +="Forces:\n"
+            forces = self.atoms.get_forces()
+            for f in forces:
+                txt += "  %8.3f, %8.3f, %8.3f eV/Å\n" % tuple(f)
+        self.output.set_text(txt)
+        self.activate_output()
+        self.end()
+                
+    def notify_atoms_changed(self):
+        "When atoms have changed, check for the number of images."
+        self.setupimageselection()
Index: ase/gui/execute.py
===================================================================
--- ase/gui/execute.py	(revision 0)
+++ ase/gui/execute.py	(revision 1)
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+import __future__
+import gtk
+
+import numpy as np
+
+from ase.gui.languages import translate as _
+from ase.gui.widgets import pack
+
+
+class Execute(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        #self.window.set_position(gtk.WIN_POS_CENTER)
+        #self.window.connect("destroy", lambda w: gtk.main_quit())
+        #self.window.connect('delete_event', self.exit)
+        self.set_title('Execute')
+        vbox = gtk.VBox()
+        pack(vbox, gtk.Label('Global: Use n, N, R, A, S:'))
+        self.cmd1 = pack(vbox, gtk.Entry(43))
+        pack(vbox, gtk.Label('Atoms: Use a, x, y, z, s, Z'))
+        self.cmd2 = pack(vbox, gtk.Entry(43))
+        #self.cmd1, self.cmd2 = pack(vbox, [gtk.Entry(23),gtk.Entry(23)])
+        self.cmd1.connect('activate', self.execute)
+        self.cmd2.connect('activate', self.execute)
+        self.selected = gtk.CheckButton('Only selected atoms')
+        pack(vbox, self.selected)
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+    def execute(self, widget=None):
+        cmd1 = self.cmd1.get_text()
+        cmd2 = self.cmd2.get_text()
+        if cmd1:
+            code1 = compile(cmd1 + '\n', 'execute.py', 'single',
+                            __future__.CO_FUTURE_DIVISION)
+        if cmd2:
+            code2 = compile(cmd2 + '\n', 'execute.py', 'single',
+                            __future__.CO_FUTURE_DIVISION)
+
+        gui = self.gui
+        images = gui.images
+
+        N = images.nimages
+        n = images.natoms
+        S = images.selected
+        if self.selected.get_active():
+            indices = np.where(S)[0]
+        else:
+            indices = range(n)
+
+        for i in range(N):
+            R = images.P[i]
+            A = images.A[i]
+            #fmax = sqrt(max((F**2).sum(1))
+
+            if cmd1:
+                exec code1
+
+            if not cmd2:
+                continue
+            
+            for a in indices:
+                x, y, z = R[a]
+                s = S[a]
+                Z = images.Z[a]
+                #f =
+                exec code2
+                S[a] = s
+                R[a] = x, y, z
+
+        gui.set_frame(gui.frame)
+        
+    python = execute
Index: ase/gui/neb.py
===================================================================
--- ase/gui/neb.py	(revision 0)
+++ ase/gui/neb.py	(revision 1)
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+from math import sqrt
+
+import numpy as np
+
+from ase.neb import fit0
+
+def NudgedElasticBand(images):
+    N = images.repeat.prod()
+    natoms = images.natoms // N
+
+    R = images.P[:, :natoms]
+    E = images.E
+    F = images.F[:, :natoms]
+
+    s, E, Sfit, Efit, lines = fit0(E, F, R)
+    import pylab
+    import matplotlib
+    #matplotlib.use('GTK')
+    pylab.ion()
+    x = 2.95
+    pylab.figure(figsize=(x * 2.5**0.5, x))
+    pylab.plot(s, E, 'o')
+    for x, y in lines:
+        pylab.plot(x, y, '-g')
+    pylab.plot(Sfit, Efit, 'k-')
+    pylab.xlabel(u'path [Å]')
+    pylab.ylabel(u'energy [eV]')
+    pylab.title('Maximum: %.3f eV' % max(Efit))
+    pylab.show()
Index: ase/gui/progress.py
===================================================================
--- ase/gui/progress.py	(revision 0)
+++ ase/gui/progress.py	(revision 1)
@@ -0,0 +1,348 @@
+# encoding: utf-8
+
+import gtk
+import numpy as np
+from ase.gui.widgets import pack, oops, AseGuiCancelException
+import sys
+import re
+import time
+
+
+class DummyProgressIndicator:
+    def begin(self, **kwargs):
+        pass
+
+    def end(self):
+        pass
+
+class DefaultProgressIndicator(gtk.Window):
+    "Window for reporting progress."
+    waittime = 3  # Time (in sec) after which a progress bar appears.
+    def __init__(self):
+        gtk.Window.__init__(self)
+        self.set_title("Progress")
+        self.globalbox = gtk.VBox()
+
+        # Scaling deformation progress frame
+        self.scalebox = gtk.VBox()
+        self.scaleframe = gtk.Frame("Scaling deformation:")
+        vbox = gtk.VBox()
+        self.scaleframe.add(vbox)
+        pack(self.scalebox, [self.scaleframe])
+        pack(self.scalebox, gtk.Label(""))
+
+        self.label_scale_stepno_format = "Step number %s of %s."
+        self.label_scale_stepno = gtk.Label(
+            self.label_scale_stepno_format % ("-" , "-"))
+        pack(vbox, [self.label_scale_stepno])
+        self.scale_progress = gtk.ProgressBar()
+        self.scale_progress.modify_bg(gtk.STATE_PRELIGHT,
+                                      gtk.gdk.color_parse('#00AA00'))
+        pack(vbox, [self.scale_progress])
+
+        vbox.show()
+        self.scaleframe.show()
+        self.globalbox.pack_start(self.scalebox)
+        
+        # Minimization progress frame
+        self.minbox = gtk.VBox()  # Box containing frame and spacing
+        self.minframe = gtk.Frame("Energy minimization:")
+        vbox = gtk.VBox()         # Box containing the frames content.
+        self.minframe.add(vbox)
+        pack(self.minbox, [self.minframe])
+        pack(self.minbox, gtk.Label(""))
+        
+        self.label_min_stepno = gtk.Label("-")
+        pack(vbox, [gtk.Label("Step number: "), self.label_min_stepno])
+        lbl = gtk.Label()
+        lbl.set_markup("F<sub>max</sub>: ")
+        self.minimize_progress = gtk.ProgressBar()
+        pack(vbox, [lbl, self.minimize_progress])
+        self.label_min_fmax = gtk.Label("-")
+        lbl = gtk.Label()
+        lbl.set_markup("Convergence criterion: F<sub>max</sub> = ")
+        pack(vbox, [lbl, self.label_min_fmax])
+        self.label_min_maxsteps = gtk.Label("-")
+        pack(vbox, [gtk.Label("Max. number of steps: "),
+                    self.label_min_maxsteps])
+        
+        vbox.show()
+        self.minframe.show()
+        self.globalbox.pack_start(self.minbox)
+        self.globalbox.show()
+        self.add(self.globalbox)
+
+        # Make the cancel button
+        self.cancelbut = gtk.Button(stock=gtk.STOCK_CANCEL)
+        self.cancelbut.connect('clicked', self.cancel)
+        pack(self.globalbox, [self.cancelbut], end=True, bottom=True)
+        
+    def begin(self, mode=None, algo=None, fmax=None, steps=None,
+              scalesteps=None):
+        self.mode = mode
+        # Hide all mode-specific boxes
+        self.scalebox.hide()
+        self.minbox.hide()
+        # Activate any relevant box
+        if mode == "scale" or mode == "scale/min":
+            self.scalesteps = int(scalesteps)
+            self.scalebox.show()
+            self.set_scale_progress(0, init=True)
+        if mode == "min" or mode == "scale/min":
+            # It is a minimization.
+            self.minbox.show()
+            self.label_min_stepno.set_text("-")
+            self.label_min_fmax.set_text("%.3f" % (fmax,))
+            self.label_min_maxsteps.set_text(str(steps))
+            self.minimize_progress.set_fraction(0)
+            self.minimize_progress.set_text("unknown")
+        # Record starting time
+        self.starttime = time.time()
+        self.active = None  # Becoming active
+        self.raisecancelexception = False
+        
+    def end(self):
+        self.hide()
+        self.active = False
+
+    def activity(self):
+        "Register that activity occurred."
+        if self.active is None and time.time() > self.starttime + self.waittime:
+            # This has taken so long that a progress bar is needed.
+            self.show()
+            self.active = True
+        # Allow GTK to update display
+        while gtk.events_pending():
+            gtk.main_iteration()
+        if self.raisecancelexception:
+            self.cancelbut.set_sensitive(True)
+            raise AseGuiCancelException
+
+    def cancel(self, widget):
+        print "CANCEL pressed."
+        # We cannot raise the exception here, as this function is
+        # called by the GTK main loop.
+        self.raisecancelexception = True
+        self.cancelbut.set_sensitive(False)
+
+    def set_scale_progress(self, step, init=False):
+        "Set the step number in scaling deformation."
+        self.label_scale_stepno.set_text(
+            self.label_scale_stepno_format % (step, self.scalesteps))
+        percent = 1.0 * step / self.scalesteps
+        self.scale_progress.set_fraction(percent)
+        self.scale_progress.set_text("%i%%" % (round(100*percent),))
+        if not init:
+            self.activity()
+        
+    def logger_write(self, line):
+        if self.mode == "min" or self.mode == "scale/min":
+            # Update the minimization progress bar.
+            w = line.split()
+            fmax = float(w[-1])
+            step = w[1]
+            self.minimize_progress.set_fraction(fmax / 1.0)
+            self.minimize_progress.set_text(w[-1])
+            self.label_min_stepno.set_text(step)
+        else:
+            raise RuntimeError(
+                "ProgressIndicator.logger_write called unexpectedly")
+        self.activity()
+            
+    def get_logger_stream(self):
+        return LoggerStream(self)
+
+
+class GpawProgressIndicator(DefaultProgressIndicator):
+    "Window for reporting GPAW progress."
+
+    def __init__(self):
+        DefaultProgressIndicator.__init__(self)
+
+        # GPAW progress frame
+        self.gpawframe = gtk.Frame("GPAW progress:")
+        vbox = self.gpawvbox = gtk.VBox()
+        self.gpawframe.add(vbox)
+        self.table = gtk.Table(1, 2)
+        self.tablerows = 0
+        pack(vbox, self.table)
+        self.status = gtk.Label("-")
+        self.tablepack([gtk.Label("Status: "), self.status])
+        self.iteration = gtk.Label("-")
+        self.tablepack([gtk.Label("Iteration: "), self.iteration])
+        self.tablepack([gtk.Label("")])
+        lbl = gtk.Label()
+        lbl.set_markup("log<sub>10</sub>(change):")
+        self.tablepack([gtk.Label(""), lbl])
+        self.wfs_progress = gtk.ProgressBar()
+        self.tablepack([gtk.Label("Wave functions: "), self.wfs_progress])
+        self.dens_progress = gtk.ProgressBar()
+        self.tablepack([gtk.Label("Density: "), self.dens_progress])
+        self.energy_progress = gtk.ProgressBar()
+        self.tablepack([gtk.Label("Energy: "), self.energy_progress])
+        self.tablepack([gtk.Label("")])
+        self.versionlabel = gtk.Label("")
+        self.tablepack([gtk.Label("GPAW version: "), self.versionlabel])
+        self.natomslabel = gtk.Label("")
+        self.tablepack([gtk.Label("Number of atoms: "), self.natomslabel])
+        self.memorylabel = gtk.Label("N/A")
+        self.tablepack([gtk.Label("Memory estimate: "), self.memorylabel])
+        self.globalbox.pack_start(self.gpawframe)
+        self.gpawframe.show()
+
+        vbox.show()
+        self.active = False
+
+    def tablepack(self, widgets):
+        self.tablerows += 1
+        self.table.resize(self.tablerows, 2)
+        for i, w in enumerate(widgets):
+            self.table.attach(w, i, i+1, self.tablerows-1, self.tablerows)
+            if hasattr(w, "set_alignment"):
+                w.set_alignment(0, 0.5)
+            w.show()
+            
+    def begin(self, **kwargs):
+        DefaultProgressIndicator.begin(self, **kwargs)
+        # Set GPAW specific stuff.
+        self.active = True
+        self.oldenergy = None
+        self.poscount = None
+        self.reset_gpaw_bars()
+        # With GPAW, all calculations are slow: Show progress window
+        # immediately.
+        self.show()
+        while gtk.events_pending():
+            gtk.main_iteration()
+
+    def reset_gpaw_bars(self):
+        for lbl in (self.status, self.iteration):
+            lbl.set_text("-")
+        for bar in (self.wfs_progress, self.dens_progress,
+                    self.energy_progress):
+            bar.set_fraction(0.0)
+            bar.set_text("No info")
+
+    def gpaw_write(self, txt):
+        #if not self.active:
+        #    self.begin()
+        sys.stdout.write(txt)
+        versearch = re.search("\|[ |_.]+([0-9]+\.[0-9]+\.[0-9]+)", txt)
+        if versearch:
+            # Starting a gpaw calculation.
+            self.versionlabel.set_text(versearch.group(1))
+            self.status.set_text("Initializing")
+        elif txt.startswith("Positions:"):
+            # Start counting atoms
+            self.poscount = True
+            self.reset_gpaw_bars()
+            self.status.set_text("Starting calculation")
+            self.oldenergy = None
+        elif txt.strip() == "":
+            # Stop counting atoms
+            self.poscount = False
+        elif self.poscount:
+            # Count atoms.
+            w = txt.split()
+            assert(len(w) == 5)
+            self.natoms = int(w[0]) + 1
+            self.natomslabel.set_text(str(self.natoms))
+        elif txt.startswith("iter:"):
+            # Found iteration line.
+            wfs = txt[self.wfs_idx:self.density_idx].strip()
+            dens = txt[self.density_idx:self.energy_idx].strip()
+            energy = txt[self.energy_idx:self.fermi_idx].strip()
+            if wfs:
+                p = fraction(float(wfs), -9.0)
+                self.wfs_progress.set_fraction(p)
+                self.wfs_progress.set_text(wfs)
+            if dens:
+                p = fraction(float(dens), -4.0)
+                self.dens_progress.set_fraction(p)
+                self.dens_progress.set_text(dens)
+            if energy:
+                if self.oldenergy is None:
+                    self.oldenergy = float(energy)
+                else:
+                    de = abs(self.oldenergy - float(energy))
+                    self.oldenergy = float(energy)
+                    if de > 1e-10:
+                        de = np.log10(de/self.natoms)
+                        p = fraction(de, -3.0)
+                        self.energy_progress.set_fraction(p)
+                        self.energy_progress.set_text("%.1f" % de)
+                    else:
+                        self.energy_progress.set_fraction(1)
+                        self.energy_progress.set_text("unchanged")
+            words = txt.split()
+            self.iteration.set_text(words[1])
+        elif (-1 < txt.find("WFS") < txt.find("Density") < txt.find("Energy")
+               < txt.find("Fermi")):
+            # Found header of convergence table
+            self.wfs_idx = txt.find("WFS")
+            self.density_idx = txt.find("Density")
+            self.energy_idx = txt.find("Energy")
+            self.fermi_idx = txt.find("Fermi")
+            self.status.set_text("Self-consistency loop")
+            self.iteration.set_text("0")
+        elif txt.find("Converged After") != -1:
+            # SCF loop has converged.
+            words = txt.split()
+            self.status.set_text("Calculating forces")
+            self.iteration.set_text(words[2]+" (converged)")
+        elif -1 < txt.find("Calculator") < txt.find("MiB"):
+            # Memory estimate
+            words = txt.split()
+            self.memorylabel.set_text(words[1]+" "+words[2])
+        self.activity()
+
+    def get_gpaw_stream(self):
+        return GpawStream(self)
+
+        
+
+class LoggerStream:
+    "A file-like object feeding minimizer logs to GpawProgressWindow."
+    def __init__(self, progresswindow):
+        self.window = progresswindow
+        
+    def write(self, txt):
+        self.window.logger_write(txt)
+
+    def flush(self):
+        pass
+    
+        
+class GpawStream:
+    "A file-like object feeding GPAWs txt file to GpawProgressWindow."
+    def __init__(self, progresswindow):
+        self.window = progresswindow
+        
+    def write(self, txt):
+        if txt == "":
+            return
+        endline = txt[-1] == '\n'
+        if endline:
+            txt = txt[:-1]
+        lines = txt.split("\n")
+        if endline:
+            for l in lines:
+                self.window.gpaw_write(l+'\n')
+        else:
+            for l in lines[:-1]:
+                self.window.gpaw_write(l+'\n')
+            self.window.gpaw_write(lines[-1])
+
+    def flush(self):
+        pass
+
+def fraction(value, maximum):
+    p = value/maximum
+    if p < 0.0:
+        return 0.0
+    elif p > 1.0:
+        return 1.0
+    else:
+        return p
+    
+    
Index: ase/gui/bulk_modulus.py
===================================================================
--- ase/gui/bulk_modulus.py	(revision 0)
+++ ase/gui/bulk_modulus.py	(revision 1)
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+from math import sqrt
+
+import numpy as np
+
+from ase.units import kJ
+from ase.utils.eos import EquationOfState
+
+
+def BulkModulus(images):
+    v = np.array([abs(np.linalg.det(A)) for A in images.A])
+    #import matplotlib.pyplot as plt
+    import pylab as plt
+    plt.ion()
+    EquationOfState(v, images.E).plot()
+
+"""
+    fit = np.poly1d(np.polyfit(v**-(1.0 / 3), images.E, 3))
+    fit1 = np.polyder(fit, 1)
+    fit2 = np.polyder(fit1, 1)
+    for t in np.roots(fit1):
+        if t > 0 and fit2(t) > 0:
+            break
+    v0 = t**-3
+    e0 = fit(t)
+    B = t**5 * fit2(t) / 9 / kJ * 1.0e24  # Gpa
+
+    import pylab
+    import matplotlib
+    #matplotlib.use('GTK')
+
+    pylab.ion()
+    x = 3.95
+    pylab.figure(figsize=(x * 2.5**0.5, x))
+
+    pylab.plot(v, images.E, 'o')
+
+    x = np.linspace(min(v), max(v), 100)
+    pylab.plot(x, fit(x**-(1.0 / 3)), '-r')
+    pylab.xlabel(u'volume [Å^3]')
+    pylab.ylabel(u'energy [eV]')
+    pylab.title(u'E: %.3f eV, V: %.3f Å^3, B: %.3f GPa' % (e0, v0, B))
+    pylab.show()
+"""
Index: ase/gui/images.py
===================================================================
--- ase/gui/images.py	(revision 0)
+++ ase/gui/images.py	(revision 1)
@@ -0,0 +1,370 @@
+from math import sqrt
+
+import numpy as np
+
+from ase.data import covalent_radii
+from ase.atoms import Atoms
+from ase.calculators.singlepoint import SinglePointCalculator
+from ase.io import read, write, string2index
+from ase.constraints import FixAtoms
+
+
+class Images:
+    def __init__(self, images=None):
+
+        if images is not None:
+            self.initialize(images)
+    
+    def initialize(self, images, filenames=None, init_magmom=False):
+        
+        self.natoms = len(images[0])
+
+        self.nimages = len(images)
+        if filenames is None:
+            filenames = [None] * self.nimages
+        self.filenames = filenames
+        self.P = np.empty((self.nimages, self.natoms, 3))
+        self.E = np.empty(self.nimages)
+        self.K = np.empty(self.nimages)
+        self.F = np.empty((self.nimages, self.natoms, 3))
+        self.M = np.empty((self.nimages, self.natoms))
+        self.T = np.empty((self.natoms))
+        self.A = np.empty((self.nimages, 3, 3))
+        self.Z = images[0].get_atomic_numbers()
+        self.pbc = images[0].get_pbc()
+        warning = False
+        for i, atoms in enumerate(images):
+            natomsi = len(atoms)
+            if (natomsi != self.natoms or
+                (atoms.get_atomic_numbers() != self.Z).any()):
+                raise RuntimeError('Can not handle different images with ' +
+                                   'different numbers of atoms or different ' +
+                                   'kinds of atoms!')
+            self.P[i] = atoms.get_positions()
+            self.A[i] = atoms.get_cell()
+            if (atoms.get_pbc() != self.pbc).any():
+                warning = True
+            try:
+                self.E[i] = atoms.get_potential_energy()
+            except RuntimeError:
+                self.E[i] = np.nan
+            self.K[i] = atoms.get_kinetic_energy()
+            try:
+                self.F[i] = atoms.get_forces(apply_constraint=False)
+            except RuntimeError:
+                self.F[i] = np.nan
+            try:
+                if init_magmom:
+                    self.M[i] = atoms.get_initial_magnetic_moments()
+                else:
+                  self.M[i] = atoms.get_magnetic_moments()
+            except (RuntimeError, AttributeError):
+                self.M[i] = 0.0
+                
+            # added support for tags
+            try:
+                self.T = atoms.get_tags()
+            except RuntimeError:
+                self.T = np.nan
+                
+
+        if warning:
+            print('WARNING: Not all images have the same bondary conditions!')
+            
+        self.selected = np.zeros(self.natoms, bool)
+        self.selected_ordered  = []
+        self.atoms_to_rotate_0 = np.zeros(self.natoms, bool)
+        self.visible = np.ones(self.natoms, bool)
+        self.nselected = 0
+        self.set_dynamic(constraints = images[0].constraints)
+        self.repeat = np.ones(3, int)
+        self.set_radii(0.89)
+        
+    def prepare_new_atoms(self):
+        "Marks that the next call to append_atoms should clear the images."
+        self.next_append_clears = True
+        
+    def append_atoms(self, atoms, filename=None):
+        "Append an atoms object to the images already stored."
+        assert len(atoms) == self.natoms
+        if self.next_append_clears:
+            i = 0
+        else:
+            i = self.nimages
+        for name in ('P', 'E', 'K', 'F', 'M', 'A'):
+            a = getattr(self, name)
+            newa = np.empty( (i+1,) + a.shape[1:] )
+            if not self.next_append_clears:
+                newa[:-1] = a
+            setattr(self, name, newa)
+        self.next_append_clears = False
+        self.P[i] = atoms.get_positions()
+        self.A[i] = atoms.get_cell()
+        try:
+            self.E[i] = atoms.get_potential_energy()
+        except RuntimeError:
+            self.E[i] = np.nan
+        self.K[i] = atoms.get_kinetic_energy()
+        try:
+            self.F[i] = atoms.get_forces(apply_constraint=False)
+        except RuntimeError:
+            self.F[i] = np.nan
+        try:
+            self.M[i] = atoms.get_magnetic_moments()
+        except (RuntimeError, AttributeError):
+            self.M[i] = np.nan
+        self.nimages = i + 1
+        self.filenames.append(filename)
+        self.set_dynamic()
+        return self.nimages
+        
+    def set_radii(self, scale):
+        self.r = covalent_radii[self.Z] * scale
+                
+    def read(self, filenames, index=-1):
+        images = []
+        names = []
+        for filename in filenames:
+            i = read(filename, index)
+            
+            if not isinstance(i, list):
+                i = [i]
+            images.extend(i)
+            names.extend([filename] * len(i))
+            
+        self.initialize(images, names)
+    
+    def import_atoms(self, filename, cur_frame):
+        if filename:
+            filename = filename[0]
+            old_a = self.get_atoms(cur_frame)
+            imp_a = read(filename, -1)
+            new_a = old_a + imp_a
+            self.initialize([new_a], [filename])
+    
+    def repeat_images(self, repeat):
+        n = self.repeat.prod()
+        repeat = np.array(repeat)
+        self.repeat = repeat
+        N = repeat.prod()
+        natoms = self.natoms // n
+        P = np.empty((self.nimages, natoms * N, 3))
+        M = np.empty((self.nimages, natoms * N))
+        T = np.empty(natoms * N, int)
+        F = np.empty((self.nimages, natoms * N, 3))
+        Z = np.empty(natoms * N, int)
+        r = np.empty(natoms * N)
+        dynamic = np.empty(natoms * N, bool)
+        a0 = 0
+        for i0 in range(repeat[0]):
+            for i1 in range(repeat[1]):
+                for i2 in range(repeat[2]):
+                    a1 = a0 + natoms
+                    for i in range(self.nimages):
+                        P[i, a0:a1] = (self.P[i, :natoms] +
+                                       np.dot((i0, i1, i2), self.A[i]))
+                    F[:, a0:a1] = self.F[:, :natoms]
+                    M[:, a0:a1] = self.M[:, :natoms]
+                    T[a0:a1] = self.T[:natoms]
+                    Z[a0:a1] = self.Z[:natoms]
+                    r[a0:a1] = self.r[:natoms]
+                    dynamic[a0:a1] = self.dynamic[:natoms]
+                    a0 = a1
+        self.P = P
+        self.F = F
+        self.Z = Z
+        self.T = T
+        self.M = M
+        self.r = r
+        self.dynamic = dynamic
+        self.natoms = natoms * N
+        self.selected = np.zeros(natoms * N, bool)
+        self.atoms_to_rotate_0 = np.zeros(self.natoms, bool)
+        self.visible = np.ones(natoms * N, bool)
+        self.nselected = 0
+        
+    def graph(self, expr):
+        import ase.units as units
+        code = compile(expr + ',', 'atoms.py', 'eval')
+
+        n = self.nimages
+        def d(n1, n2):
+            return sqrt(((R[n1] - R[n2])**2).sum())
+        def a(n1, n2, n3):
+            v1 = R[n1]-R[n2]
+            v2 = R[n3]-R[n2]
+            arg = np.vdot(v1,v2)/(sqrt((v1**2).sum()*(v2**2).sum()))
+            if arg > 1.0: arg = 1.0
+            if arg < -1.0: arg = -1.0
+            return 180.0*np.arccos(arg)/np.pi
+        def dih(n1, n2, n3, n4):
+            # vector 0->1, 1->2, 2->3 and their normalized cross products:
+            a    = R[n2]-R[n1]
+            b    = R[n3]-R[n2]
+            c    = R[n4]-R[n3]
+            bxa  = np.cross(b,a)
+            bxa /= np.sqrt(np.vdot(bxa,bxa))
+            cxb  = np.cross(c,b)
+            cxb /= np.sqrt(np.vdot(cxb,cxb))
+            angle = np.vdot(bxa,cxb)
+            # check for numerical trouble due to finite precision:
+            if angle < -1: angle = -1
+            if angle >  1: angle =  1
+            angle = np.arccos(angle)
+            if (np.vdot(bxa,c)) > 0: angle = 2*np.pi-angle
+            return angle*180.0/np.pi
+        # get number of mobile atoms for temperature calculation
+        ndynamic = 0
+        for dyn in self.dynamic: 
+            if dyn: ndynamic += 1
+        S = self.selected
+        D = self.dynamic[:, np.newaxis]
+        E = self.E
+        s = 0.0
+        data = []
+        for i in range(n):
+            R = self.P[i]
+            F = self.F[i]
+            A = self.A[i]
+            f = ((F * D)**2).sum(1)**.5
+            fmax = max(f)
+            fave = f.mean()
+            epot = E[i]
+            ekin = self.K[i]
+            e = epot + ekin
+            T = 2.0 * ekin / (3.0 * ndynamic * units.kB)
+            data = eval(code)
+            if i == 0:
+                m = len(data)
+                xy = np.empty((m, n))
+            xy[:, i] = data
+            if i + 1 < n:
+                s += sqrt(((self.P[i + 1] - R)**2).sum())
+        return xy
+
+    def set_dynamic(self, constraints = None):
+        if self.nimages == 1:
+            self.dynamic = np.ones(self.natoms, bool)
+        else:
+            self.dynamic = np.zeros(self.natoms, bool)
+            R0 = self.P[0]
+            for R in self.P[1:]:
+                self.dynamic |= (np.abs(R - R0) > 1.0e-10).any(1)
+        if constraints is not None:
+            for con in constraints: 
+                if isinstance(con,FixAtoms):
+                    self.dynamic[con.index] = False
+
+    def write(self, filename, rotations='', show_unit_cell=False, bbox=None):
+        indices = range(self.nimages)
+        p = filename.rfind('@')
+        if p != -1:
+            try:
+                slice = string2index(filename[p + 1:])
+            except ValueError:
+                pass
+            else:
+                indices = indices[slice]
+                filename = filename[:p]
+                if isinstance(indices, int):
+                    indices = [indices]
+
+        images = [self.get_atoms(i) for i in indices]
+        if len(filename) > 4 and filename[-4:] in ['.eps', '.png', '.pov']:
+            write(filename, images, 
+                  rotation=rotations, show_unit_cell=show_unit_cell,
+                  bbox=bbox)
+        else:
+            write(filename, images)
+
+    def get_atoms(self, frame):
+        atoms = Atoms(positions=self.P[frame],
+                      numbers=self.Z,
+                      magmoms=self.M[0],
+                      tags=self.T,
+                      cell=self.A[frame],
+                      pbc=self.pbc)
+        
+        # check for constrained atoms and add them accordingly:
+        if not self.dynamic.all():
+            atoms.set_constraint(FixAtoms(mask=1-self.dynamic))
+        
+        atoms.set_calculator(SinglePointCalculator(self.E[frame],
+                                                   self.F[frame],
+                                                   None, None, atoms))
+        return atoms
+                           
+    def delete(self, i):
+        self.nimages -= 1
+        P = np.empty((self.nimages, self.natoms, 3))
+        F = np.empty((self.nimages, self.natoms, 3))
+        A = np.empty((self.nimages, 3, 3))
+        E = np.empty(self.nimages)
+        P[:i] = self.P[:i]
+        P[i:] = self.P[i + 1:]
+        self.P = P
+        F[:i] = self.F[:i]
+        F[i:] = self.F[i + 1:]
+        self.F = F
+        A[:i] = self.A[:i]
+        A[i:] = self.A[i + 1:]
+        self.A = A
+        E[:i] = self.E[:i]
+        E[i:] = self.E[i + 1:]
+        self.E = E
+        del self.filenames[i]
+
+    def aneb(self):
+        n = self.nimages
+        assert n % 5 == 0
+        levels = n // 5
+        n = self.nimages = 2 * levels + 3
+        P = np.empty((self.nimages, self.natoms, 3))
+        F = np.empty((self.nimages, self.natoms, 3))
+        E = np.empty(self.nimages)
+        for L in range(levels):
+            P[L] = self.P[L * 5]
+            P[n - L - 1] = self.P[L * 5 + 4]
+            F[L] = self.F[L * 5]
+            F[n - L - 1] = self.F[L * 5 + 4]
+            E[L] = self.E[L * 5]
+            E[n - L - 1] = self.E[L * 5 + 4]
+        for i in range(3):
+            P[levels + i] = self.P[levels * 5 - 4 + i]
+            F[levels + i] = self.F[levels * 5 - 4 + i]
+            E[levels + i] = self.E[levels * 5 - 4 + i]
+        self.P = P
+        self.F = F
+        self.E = E
+
+    def interpolate(self, m):
+        assert self.nimages == 2
+        self.nimages = 2 + m
+        P = np.empty((self.nimages, self.natoms, 3))
+        F = np.empty((self.nimages, self.natoms, 3))
+        A = np.empty((self.nimages, 3, 3))
+        E = np.empty(self.nimages)
+        P[0] = self.P[0]
+        F[0] = self.F[0]
+        A[0] = self.A[0]
+        E[0] = self.E[0]
+        for i in range(1, m + 1):
+            x = i / (m + 1.0)
+            y = 1 - x
+            P[i] = y * self.P[0] + x * self.P[1]
+            F[i] = y * self.F[0] + x * self.F[1]
+            A[i] = y * self.A[0] + x * self.A[1]
+            E[i] = y * self.E[0] + x * self.E[1]
+        P[-1] = self.P[1]
+        F[-1] = self.F[1]
+        A[-1] = self.A[1]
+        E[-1] = self.E[1]
+        self.P = P
+        self.F = F
+        self.A = A
+        self.E = E
+        self.filenames[1:1] = [None] * m
+
+if __name__ == '__main__':
+    import os
+    os.system('python gui.py')
Index: ase/gui/nanotube.py
===================================================================
--- ase/gui/nanotube.py	(revision 0)
+++ ase/gui/nanotube.py	(revision 1)
@@ -0,0 +1,160 @@
+# encoding: utf-8
+"""nanotube.py - Window for setting up Carbon nanotubes and similar tubes.
+"""
+
+import gtk
+from ase.gui.widgets import pack, cancel_apply_ok, oops
+from ase.gui.setupwindow import SetupWindow
+from ase.gui.pybutton import PyButton
+from ase.structure import nanotube
+import ase
+import numpy as np
+
+introtext = """\
+Set up a Carbon nanotube by specifying the (n,m) roll-up vector.
+Please note that m <= n.
+
+Nanotubes of other elements can be made by specifying the element
+and bond length.\
+"""
+
+py_template = """
+from ase.structure import nanotube
+
+atoms = nanotube(%(n)i, %(m)i, length=%(length)i, bond=%(bl).3f, symbol=%(symb)s)
+"""
+
+
+class SetupNanotube(SetupWindow):
+    "Window for setting up a (Carbon) nanotube."
+    def __init__(self, gui):
+        SetupWindow.__init__(self)
+        self.set_title("Nanotube")
+        vbox = gtk.VBox()
+
+        # Intoductory text
+        self.packtext(vbox, introtext)
+           
+        # Choose the element and bond length
+        label1 = gtk.Label("Element: ")
+        #label.set_alignment(0.0, 0.2)
+        self.element = gtk.Entry(max=3)
+        self.element.set_text("C")
+        self.element.connect('activate', self.update_element)
+        self.bondlength = gtk.Adjustment(1.42, 0.0, 1000.0, 0.01)
+        label2 = gtk.Label("  Bond length: ")
+        label3 = gtk.Label("Å")
+        bond_box = gtk.SpinButton(self.bondlength, 10.0, 3)
+        pack(vbox, [label1, self.element, label2, bond_box, label3])
+        self.elementinfo = gtk.Label("")
+        self.elementinfo.modify_fg(gtk.STATE_NORMAL,
+                                   gtk.gdk.color_parse('#FF0000'))
+        pack(vbox, [self.elementinfo])
+        pack(vbox, gtk.Label(""))
+
+        # Choose the structure.
+        pack(vbox, [gtk.Label("Select roll-up vector (n,m) and tube length:")])
+        label1 = gtk.Label("n: ")
+        label2 = gtk.Label("  m: ")
+        self.n = gtk.Adjustment(6, 1, 100, 1)
+        self.m = gtk.Adjustment(0, 0, 100, 1)
+        spinn = gtk.SpinButton(self.n, 0, 0)
+        spinm = gtk.SpinButton(self.m, 0, 0)
+        label3 = gtk.Label("  Length: ")
+        self.length = gtk.Adjustment(1, 1, 100, 1)
+        spinl = gtk.SpinButton(self.length, 0, 0)
+        pack(vbox, [label1, spinn, label2, spinm, label3, spinl])
+        self.err = gtk.Label("")
+        self.err.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse('#FF0000'))
+        pack(vbox, [self.err])
+        pack(vbox, gtk.Label(""))
+        self.n.connect('value-changed', self.update_n)
+        self.m.connect('value-changed', self.update_m)
+
+        # Buttons
+        self.pybut = PyButton("Creating a nanoparticle.")
+        self.pybut.connect('clicked', self.makeatoms)
+        buts = cancel_apply_ok(cancel=lambda widget: self.destroy(),
+                               apply=self.apply,
+                               ok=self.ok)
+        pack(vbox, [self.pybut, buts], end=True, bottom=True)
+
+        # Finalize setup
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+    def update_n(self, *args):
+        if self.m.value > self.n.value:
+            self.m.value = self.n.value
+            self.err.set_text("m decreased! (m may not be larger than n.)")
+        else:
+            self.err.set_text("")
+            
+    def update_m(self, *args):
+        if self.m.value > self.n.value:
+            self.n.value = self.m.value
+            self.err.set_text("n increased! (m may not be larger than n.)")
+        else:
+            self.err.set_text("")
+
+    def update_element(self, *args):
+        "Called when a new element may have been entered."
+        # Assumes the element widget is self.element and that a label
+        # for errors is self.elementinfo.  The chemical symbol is
+        # placed in self.legalelement - or None if the element is
+        # invalid.
+        elem = self.element.get_text()
+        if not elem:
+            self.invalid_element("  No element specified!")
+            return False
+        try:
+            z = int(elem)
+        except ValueError:
+            # Probably a symbol
+            try:
+                z = ase.data.atomic_numbers[elem]
+            except KeyError:
+                self.invalid_element()
+                return False
+        try:
+            symb = ase.data.chemical_symbols[z]
+        except KeyError:
+            self.invalid_element()
+            return False
+        self.elementinfo.set_text("")
+        self.legal_element = symb
+        return True
+        
+    def makeatoms(self, *args):
+        self.update_element()
+        if self.legal_element is None:
+            self.atoms = None
+            self.pybut.python = None
+        else:
+            n = int(self.n.value)
+            m = int(self.m.value)
+            symb = self.legal_element
+            length = int(self.length.value)
+            bl = self.bondlength.value
+            self.atoms = nanotube(n, m, length=length, bond=bl, symbol=symb)
+            self.pybut.python = py_template % {'n': n, 'm':m, 'length':length,
+                                               'symb':symb, 'bl':bl}
+        
+
+    def apply(self, *args):
+        self.makeatoms()
+        if self.atoms is not None:
+            self.gui.new_atoms(self.atoms)
+            return True
+        else:
+            oops("No valid atoms.",
+                 "You have not (yet) specified a consistent set of parameters.")
+            return False
+
+    def ok(self, *args):
+        if self.apply():
+            self.destroy()
+            
+
Index: ase/gui/dft.py
===================================================================
--- ase/gui/dft.py	(revision 0)
+++ ase/gui/dft.py	(revision 1)
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+from math import sqrt
+
+import gtk
+
+from ase.gui.languages import translate as _
+from ase.gui.widgets import pack, Help
+
+
+class DFT(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        self.set_title(_('DFT'))
+        vbox = gtk.VBox()
+        combo = gtk.combo_box_new_text()
+        self.xcfuncs = 'None LDA PBE revPBE RPBE PW91 EXX PBE0'.split()
+        for xc in self.xcfuncs:
+            combo.append_text(xc)
+        pack(vbox, [gtk.Label(_('XC-functional: ')), combo])
+
+        button=radio(None,monkhorstpack)
+        button=radio(button, special)
+        pack(vbox, gtk.Label(_('Repeat atoms:')))
+        self.kpts = [gtk.Adjustment(r, 1, 99, 1) for r in gui.atoms.repeat]
+        pack(vbox, [gtk.SpinButton(r, 0, 0) for r in self.repeat])
+        for r in self.repeat:
+            r.connect('value-changed', self.change)
+
+        close = pack(vbox, gtk.Button(_('Close')))
+        close.connect('clicked', lambda widget: self.destroy())
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+        xc = gui.atoms.dft.get('xc', 'None')
+        combo.set_active(self.xcfuncs.index(xc))
+
+    def selected(self, button):
+        self.gui.atoms.dynamic = ~self.gui.atoms.selected
+        self.gui.draw()
+
+    def immobile(self, button):
+        self.gui.atoms.set_dynamic()
+        self.gui.draw()
+
+    def clear(self, button):
+        self.gui.atoms.dynamic[:] = True
+        self.gui.draw()
+
Index: ase/gui/languages/__init__.py
===================================================================
--- ase/gui/languages/__init__.py	(revision 0)
+++ ase/gui/languages/__init__.py	(revision 1)
@@ -0,0 +1,27 @@
+import locale
+
+from ase.gui.languages.en import translation as default_translation
+
+
+def translate(text):
+    return default_translation.get(text, text)
+
+language_code = locale.getdefaultlocale()[0]
+if language_code is None:
+    language_code = 'en'
+else:
+    language_code = language_code[:2]
+
+if language_code != 'en':
+    try:
+        module = __import__(language_code, globals(), locals())
+    except ImportError:
+        pass
+    else:
+        translation = module.translation
+        def translate(text):
+            translated_text = translation.get(text)
+            if translated_text is None:
+                return default_translation.get(text, text)
+            else:
+                return translated_text
Index: ase/gui/languages/en.py
===================================================================
--- ase/gui/languages/en.py	(revision 0)
+++ ase/gui/languages/en.py	(revision 1)
@@ -0,0 +1,35 @@
+translation = {
+'Tip for status box ...':
+"""Show information about selected atoms (type, position, bondlengths and angles).  Use left mouse button to select atoms - possibly combined with the [ctrl] key.""",
+
+'Help for plot ...':
+"""Symbols:
+<c>e</c>:\t\t\ttotal energy
+<c>epot</c>:\t\tpotential energy
+<c>ekin</c>:\t\tkinetic energy
+<c>fmax</c>:\t\tmaximum force
+<c>fave</c>:\t\taverage force
+<c>R[n,0-2]</c>:\t\tposition of atom number <c>n</c> 
+<c>d(n<sub>1</sub>,n<sub>2</sub>)</c>:\t\tdistance between two atoms n1 and n2
+<c>i</c>:\t\t\tcurrent image number
+<c>E[i]</c>:\t\t\tenergy of image number <c>i</c>
+<c>F[n,0-2]</c>:\t\tforce on atom number <c>n</c>
+<c>M[n]</c>:\t\tmagnetic moment of atom number <c>n</c> 
+<c>A[0-2,0-2]</c>:\tunit-cell basis vectors 
+<c>s</c>:\t\t\tpath length
+<c>a(n1,n2,n3)</c>:\tangle between atoms n1, n2 and n3, centered on n2
+<c>dih(n1,n2,n3,n4)</c>:\tdihedral angle between n1, n2, n3, and n4
+<c>T</c>:\t\t\tTemperature (K)""",
+
+'Help for slice ...':
+"""Use Python slice syntax: "start:stop:step" or "start:stop":
+
+<c>2:5</c>\tsecond, third and fourth
+<c>:3</c>\tfirst three
+<c>-3:</c>\tlast three
+<c>::3</c>\tevery third
+<c>0</c>\tfirst
+<c>6</c>\tseventh
+<c>-1</c>\tlast
+"""
+}
Index: ase/gui/languages/da.py
===================================================================
--- ase/gui/languages/da.py	(revision 0)
+++ ase/gui/languages/da.py	(revision 1)
@@ -0,0 +1,42 @@
+# -*- coding: utf-8 -*-
+"""Danish translation."""
+
+translation = {
+'Select': u'Vælg',
+'Invert selection': 'Inverter valg',
+'Select all': u'Vælg alle',
+'View': 'Vis',
+'Focus': u'Fokusér',
+'Settings': 'Indstillinger',
+'Plugins': 'Indstik',
+'Help': u'Hjælp',
+'Edit': u'Redigér',
+'File': 'Fil',
+'Exit': 'Afslut',
+'Open': u'Åben',
+'Play': 'Afspil',
+'Adjust play time.': 'Indstil afspilningstid',
+'Image number:': 'Billede nummer:',
+
+'Tip for status box ...':
+"""Vis informationer om valgte atomer (type, position, bindingslængder
+og vinkler) ved at vælge atomer med musen (venstre knap) evt. i
+kombination med [ctrl] tasten""",
+
+'Help for plot ...':
+u"""Symboler:
+<c>e</c>:\t\ttotal energi
+<c>epot</c>:\tpotentiel energi
+<c>ekin</c>:\tkinetisk energi
+<c>fmax</c>:\tmaximum kræft
+<c>fave</c>:\tmiddel kræft
+<c>R[n,0-2]</c>:\tposition af atom nummer <c>n</c> 
+<c>d(n<sub>1</sub>,n<sub>1</sub>)</c>:\tafstand mellem to atomer
+<c>i</c>:\t\tnuværende billede nummer
+<c>E[i]</c>:\t\tenergi af billede nummer <c>i</c>
+<c>F[n,0-2]</c>:\tkræft på atom nummer <c>n</c> 
+<c>A[0-2,0-2]</c>:\tEnhedscelle vektorer
+<c>s</c>:\t\tvejlængde""",
+
+'Slice: ': 'Udsnit: '
+}
Index: ase/gui/nanoparticle.py
===================================================================
--- ase/gui/nanoparticle.py	(revision 0)
+++ ase/gui/nanoparticle.py	(revision 1)
@@ -0,0 +1,468 @@
+# encoding: utf-8
+"""nanoparticle.py - Window for setting up crystalline nanoparticles.
+"""
+
+import gtk
+from ase.gui.widgets import pack, cancel_apply_ok, oops
+from ase.gui.setupwindow import SetupWindow
+from ase.gui.pybutton import PyButton
+import ase
+import numpy as np
+# Delayed imports:
+# ase.cluster.data
+
+introtext = """\
+You specify the size of the particle by specifying the number of atomic layers
+in the different low-index crystal directions.  Often, the number of layers is
+specified for a family of directions, but they can be given individually.
+
+When the particle is created, the actual numbers of layers are printed, they
+may be less than specified if a surface is cut of by other surfaces."""
+
+py_template = """
+from ase.cluster import Cluster
+import ase
+
+layers = %(layers)s
+atoms = Cluster(symbol='%(element)s', layers=layers, latticeconstant=%(a).5f,
+                symmetry='%(structure)s')
+
+# OPTIONAL: Cast to ase.Atoms object, discarding extra information:
+# atoms = ase.Atoms(atoms)
+"""
+
+class attribute_collection:
+    pass
+
+class SetupNanoparticle(SetupWindow):
+    "Window for setting up a nanoparticle."
+    families = {'fcc': [(0,0,1), (0,1,1), (1,1,1)]}
+    defaults = {'fcc': [6, 9, 5]}
+    
+    def __init__(self, gui):
+        SetupWindow.__init__(self)
+        self.set_title("Nanoparticle")
+        self.atoms = None
+        import ase.cluster.data
+        self.data_module = ase.cluster.data
+        import ase.cluster
+        self.Cluster = ase.cluster.Cluster
+        self.wulffconstruction = ase.cluster.wulff_construction
+        self.no_update = True
+        
+        vbox = gtk.VBox()
+
+        # Intoductory text
+        self.packtext(vbox, introtext)
+           
+        # Choose the element
+        label = gtk.Label("Element: ")
+        label.set_alignment(0.0, 0.2)
+        element = gtk.Entry(max=3)
+        self.element = element
+        lattice_button = gtk.Button("Get structure")
+        lattice_button.connect('clicked', self.get_structure)
+        self.elementinfo = gtk.Label(" ")
+        pack(vbox, [label, element, self.elementinfo, lattice_button], end=True)
+        self.element.connect('activate', self.update)
+        self.legal_element = False
+
+        # The structure and lattice constant
+        label = gtk.Label("Structure: ")
+        self.structure = gtk.combo_box_new_text()
+        self.allowed_structures = ('fcc',)
+        for struct in self.allowed_structures:
+            self.structure.append_text(struct)
+        self.structure.set_active(0)
+        self.structure.connect('changed', self.update)
+        
+        label2 = gtk.Label("   Lattice constant: ")
+        self.lattice_const = gtk.Adjustment(3.0, 0.0, 1000.0, 0.01)
+        lattice_box = gtk.SpinButton(self.lattice_const, 10.0, 3)
+        lattice_box.numeric = True
+        pack(vbox, [label, self.structure, label2, lattice_box])
+        self.lattice_const.connect('value-changed', self.update)
+
+        # Choose specification method
+        label = gtk.Label("Method: ")
+        self.method = gtk.combo_box_new_text()
+        for meth in ("Layer specification", "Wulff construction"):
+            self.method.append_text(meth)
+        self.method.set_active(0)
+        self.method.connect('changed', self.update_gui)
+        pack(vbox, [label, self.method])
+        pack(vbox, gtk.Label(""))
+        
+        # The number of layers
+        self.layerbox = gtk.VBox()
+        self.layerdata = attribute_collection()
+        pack(vbox, self.layerbox)
+        self.make_layer_gui(self.layerbox, self.layerdata, 0)
+
+        # The Wulff construction
+        self.wulffbox = gtk.VBox()
+        self.wulffdata = attribute_collection()
+        pack(vbox, self.wulffbox)
+        self.make_layer_gui(self.wulffbox, self.wulffdata, 1)
+        label = gtk.Label("Particle size: ")
+        self.size_n_radio = gtk.RadioButton(None, "Number of atoms: ")
+        self.size_n_radio.set_active(True)
+        self.size_n_adj = gtk.Adjustment(100, 1, 100000, 1)
+        self.size_n_spin = gtk.SpinButton(self.size_n_adj, 0, 0)
+        self.size_dia_radio = gtk.RadioButton(self.size_n_radio,
+                                              "Volume: ")
+        self.size_dia_adj = gtk.Adjustment(1.0, 0, 100.0, 0.1)
+        self.size_dia_spin = gtk.SpinButton(self.size_dia_adj, 10.0, 2)
+        pack(self.wulffbox, [label, self.size_n_radio, self.size_n_spin,
+                    gtk.Label("   "), self.size_dia_radio, self.size_dia_spin,
+                    gtk.Label("Å³")])
+        self.size_n_radio.connect("toggled", self.update_gui)
+        self.size_dia_radio.connect("toggled", self.update_gui)
+        self.size_n_adj.connect("value-changed", self.update_size_n)
+        self.size_dia_adj.connect("value-changed", self.update_size_dia)
+        self.update_size_n()
+        label = gtk.Label(
+            "Rounding: If exact size is not possible, choose the size")
+        pack(self.wulffbox, [label])
+        self.round_above = gtk.RadioButton(None, "above  ")
+        self.round_below = gtk.RadioButton(self.round_above, "below  ")
+        self.round_closest = gtk.RadioButton(self.round_above, "closest  ")
+        self.round_closest.set_active(True)
+        buts = [self.round_above, self.round_below, self.round_closest]
+        pack(self.wulffbox, buts)
+        for b in buts:
+            b.connect("toggled", self.update)
+        
+        # Information
+        pack(vbox, gtk.Label(""))
+        infobox = gtk.VBox()
+        label1 = gtk.Label("Number of atoms: ")
+        self.natoms_label = gtk.Label("-")
+        label2 = gtk.Label("   Approx. diameter: ")
+        self.dia1_label = gtk.Label("-")
+        pack(infobox, [label1, self.natoms_label, label2, self.dia1_label])
+        pack(infobox, gtk.Label(""))
+        infoframe = gtk.Frame("Information about the created cluster:")
+        infoframe.add(infobox)
+        infobox.show()
+        pack(vbox, infoframe)
+        
+        # Buttons
+        self.pybut = PyButton("Creating a nanoparticle.")
+        self.pybut.connect('clicked', self.makeatoms)
+        buts = cancel_apply_ok(cancel=lambda widget: self.destroy(),
+                               apply=self.apply,
+                               ok=self.ok)
+        pack(vbox, [self.pybut, buts], end=True, bottom=True)
+        self.auto = gtk.CheckButton("Automatic Apply")
+        fr = gtk.Frame()
+        fr.add(self.auto)
+        fr.show_all()
+        pack(vbox, [fr], end=True, bottom=True)
+        
+        # Finalize setup
+        self.update_gui()
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+        self.no_update = False
+
+    def update_gui(self, widget=None):
+        method = self.method.get_active()
+        if method == 0:
+            self.wulffbox.hide()
+            self.layerbox.show()
+        elif method == 1:
+            self.layerbox.hide()
+            self.wulffbox.show()
+            self.size_n_spin.set_sensitive(self.size_n_radio.get_active())
+            self.size_dia_spin.set_sensitive(self.size_dia_radio.get_active())
+
+    def update_size_n(self, widget=None):
+        if not self.size_n_radio.get_active():
+            return
+        at_vol = self.get_atomic_volume()
+        dia = 2.0 * (3 * self.size_n_adj.value * at_vol / (4 * np.pi))**(1.0/3)
+        self.size_dia_adj.value = dia
+        self.update()
+
+    def update_size_dia(self, widget=None):
+        if not self.size_dia_radio.get_active():
+            return
+        at_vol = self.get_atomic_volume()
+        n = round(np.pi / 6 * self.size_dia_adj.value**3 / at_vol)
+        self.size_n_adj.value = n
+        self.update()
+                
+    def update(self, *args):
+        if self.no_update:
+            return
+        self.update_gui()
+        self.update_element()
+        if self.auto.get_active():
+            self.makeatoms()
+            if self.atoms is not None:
+                self.gui.new_atoms(self.atoms)
+        else:
+            self.clearatoms()
+        self.makeinfo()
+
+    def get_structure(self, *args):
+        if not self.update_element():
+            oops("Invalid element.")
+            return
+        z = ase.atomic_numbers[self.legal_element]
+        ref = ase.data.reference_states[z]
+        if ref is None:
+            structure = None
+        else:
+            structure = ref['symmetry'].lower()
+                
+        if ref is None or not structure in self.allowed_structures:
+            oops("Unsupported or unknown structure",
+                 "Element = %s,  structure = %s" % (self.legal_element,
+                                                    structure))
+            return
+        for i, s in enumerate(self.allowed_structures):
+            if structure == s:
+                self.structure.set_active(i)
+        a = ref['a']
+        self.lattice_const.set_value(a)
+
+    def make_layer_gui(self, box, data, method):
+        "Make the part of the gui specifying the layers of the particle"
+        assert method in (0,1)
+        
+        # Clear the box
+        children = box.get_children()
+        for c in children:
+            box.remove(c)
+        del children
+
+        # Make the label
+        if method == 0:
+            txt = "Number of layers:"
+        else:
+            txt = "Surface energies (unit: energy/area, i.e. J/m<sup>2</sup> or eV/nm<sup>2</sup>, <i>not</i> eV/atom):"
+        label = gtk.Label()
+        label.set_markup(txt)
+        pack(box, [label])
+
+        # Get the crystal structure
+        struct = self.structure.get_active_text()
+        # Get the surfaces in the order the ase.cluster module expects
+        surfaces = self.data_module.lattice[struct]['surface_names']
+        # Get the surface families
+        families = self.families[struct]
+        if method == 0:
+            defaults = self.defaults[struct]
+        else:
+            defaults = [1.0] * len(self.defaults[struct])
+        
+        # Empty array for the gtk.Adjustments for the layer numbers
+        data.layers = [None] * len(surfaces)
+        data.layer_lbl = [None] * len(surfaces)
+        data.layer_spin = [None] * len(surfaces)
+        data.layer_owner = [None] * len(surfaces)
+        data.layer_label = [None] * len(surfaces)
+        data.famlayers = [None] * len(families)
+        data.infamily = [None] * len(families)
+        data.family_label = [None] * len(families)
+        
+        # Now, make a box for each family of surfaces
+        frames = []
+        for i in range(len(families)):
+            family = families[i]
+            default = defaults[i]
+            frames.append(self.make_layer_family(data, i, family, surfaces,
+                                                 default, method))
+        for a in data.layers:
+            assert a is not None
+
+        pack(box, frames)
+        box.show_all()
+
+    def make_layer_family(self, data, n, family, surfaces, default, method):
+        """Make a frame box for a single family of surfaces.
+
+        The layout is a frame containing a table.  For example
+
+        {0,0,1}, SPIN, EMPTY, EMPTY
+        -- empty line --
+        (0,0,1), SPIN, Label(actual), Checkbox
+        ...
+        """
+        tbl = gtk.Table(2, 4)
+        lbl = gtk.Label("{%i,%i,%i}: " % family)
+        lbl.set_alignment(1, 0.5)
+        tbl.attach(lbl, 0, 1, 0, 1)
+        if method == 0:
+            famlayers = gtk.Adjustment(default, 1, 100, 1)
+            sbut = gtk.SpinButton(famlayers, 0, 0)
+        else:
+            flimit = 1000.0
+            famlayers = gtk.Adjustment(default, 0.0, flimit, 0.1)
+            sbut = gtk.SpinButton(famlayers, 10.0, 3)
+        tbl.attach(sbut, 2, 3, 0, 1)
+        tbl.attach(gtk.Label(" "), 0, 1, 1, 2)
+        assert data.famlayers[n] is None
+        data.famlayers[n] = famlayers
+        data.infamily[n] = []
+        data.family_label[n] = gtk.Label("")
+        tbl.attach(data.family_label[n], 1, 2, 0, 1)
+        row = 2
+        myspin = []
+        for i, s in enumerate(surfaces):
+            s2 = [abs(x) for x in s]
+            s2.sort()
+            if tuple(s2) == family:
+                data.infamily[n].append(i)
+                tbl.resize(row+1, 4)
+                lbl = gtk.Label("(%i,%i,%i): " % s)
+                lbl.set_alignment(1, 0.5)
+                tbl.attach(lbl, 0, 1, row, row+1)
+                label = gtk.Label("    ")
+                tbl.attach(label, 1, 2, row, row+1)
+                data.layer_label[i] = label
+                if method == 0:
+                    lay = gtk.Adjustment(default, -100, 100, 1)
+                    spin = gtk.SpinButton(lay, 0, 0)
+                else:
+                    lay = gtk.Adjustment(default, -flimit, flimit, 0.1)
+                    spin = gtk.SpinButton(lay, 10.0, 3)
+                lay.connect('value-changed', self.update)
+                spin.set_sensitive(False)
+                tbl.attach(spin, 2, 3, row, row+1)
+                assert data.layers[i] is None
+                data.layers[i] = lay
+                data.layer_lbl[i] = lbl
+                data.layer_spin[i] = spin
+                data.layer_owner[i] = n
+                myspin.append(spin)
+                chkbut = gtk.CheckButton()
+                tbl.attach(chkbut, 3, 4, row, row+1)
+                chkbut.connect("toggled", self.toggle_surface, i)
+                row += 1
+        famlayers.connect('value-changed', self.changed_family_layers, myspin)
+        vbox = gtk.VBox()
+        vbox.pack_start(tbl, False, False, 0)
+        fr = gtk.Frame()
+        fr.add(vbox)
+        fr.show_all()
+        return fr
+
+    def toggle_surface(self, widget, number):
+        "Toggle whether a layer in a family can be specified."
+        active = widget.get_active()
+        data = self.get_data()
+        data.layer_spin[number].set_sensitive(active)
+        if not active:
+            data.layers[number].value = \
+                data.famlayers[data.layer_owner[number]].value
+        
+    def changed_family_layers(self, widget, myspin):
+        "Change the number of layers in inactive members of a family."
+        self.no_update = True
+        x = widget.value
+        for s in myspin:
+            if s.state == gtk.STATE_INSENSITIVE:
+                adj = s.get_adjustment()
+                if adj.value != x:
+                    adj.value = x
+        self.no_update = False
+        self.update()
+
+    def get_data(self):
+        return self.layerdata
+    
+    def makeatoms(self, *args):
+        "Make the atoms according to the current specification."
+        if not self.update_element():
+            self.clearatoms()
+            self.makeinfo()
+            return False
+        assert self.legal_element is not None
+        struct = self.structure.get_active_text()
+        lc = self.lattice_const.value
+        if self.method.get_active() == 0:
+            # Layer-by-layer specification
+            layers = [int(x.value) for x in self.layerdata.layers]
+            self.atoms = self.Cluster(self.legal_element, layers=layers,
+                                      latticeconstant=lc, symmetry=struct)
+            self.pybut.python = py_template % {'element': self.legal_element,
+                                               'layers': str(layers),
+                                               'structure': struct,
+                                               'a': lc}
+        else:
+            # Wulff construction
+            assert self.method.get_active() == 1
+            surfaceenergies = [x.value for x in self.wulffdata.layers]
+            self.update_size_dia()
+            if self.round_above.get_active():
+                rounding = "above"
+            elif self.round_below.get_active():
+                rounding = "below"
+            elif self.round_closest.get_active():
+                rounding = "closest"
+            else:
+                raise RuntimeError("No rounding!")
+            self.atoms = self.wulffconstruction(self.legal_element,
+                                                surfaceenergies,
+                                                self.size_n_adj.value,
+                                                rounding, struct, lc)
+                                   
+        self.makeinfo()
+
+    def clearatoms(self):
+        self.atoms = None
+        self.pybut.python = None
+
+    def get_atomic_volume(self):
+        s = self.structure.get_active_text()
+        a = self.lattice_const.value
+        if s == 'fcc':
+            return a**3 / 4
+        else:
+            raise RuntimeError("Unknown structure: "+s)
+
+    def makeinfo(self):
+        "Fill in information field about the atoms."
+        #data = self.get_data()
+        if self.atoms is None:
+            self.natoms_label.set_label("-")
+            self.dia1_label.set_label("-")
+            for d in (self.layerdata, self.wulffdata):
+                for label in d.layer_label+d.family_label:
+                    label.set_text("    ")
+        else:
+            self.natoms_label.set_label(str(len(self.atoms)))
+            at_vol = self.get_atomic_volume()
+            dia = 2 * (3 * len(self.atoms) * at_vol / (4 * np.pi))**(1.0/3.0)
+            self.dia1_label.set_label("%.1f Å" % (dia,))
+            actual = self.atoms.get_layers()
+            for i, a in enumerate(actual):
+                self.layerdata.layer_label[i].set_text("%2i " % (a,))
+                self.wulffdata.layer_label[i].set_text("%2i " % (a,))
+            for d in (self.layerdata, self.wulffdata):
+                for i, label in enumerate(d.family_label):
+                    relevant = actual[d.infamily[i]]
+                    if relevant.min() == relevant.max():
+                        label.set_text("%2i " % (relevant[0]))
+                    else:
+                        label.set_text("-- ")
+            
+    def apply(self, *args):
+        self.makeatoms()
+        if self.atoms is not None:
+            self.gui.new_atoms(self.atoms)
+            return True
+        else:
+            oops("No valid atoms.",
+                 "You have not (yet) specified a consistent set of parameters.")
+            return False
+
+    def ok(self, *args):
+        if self.apply():
+            self.destroy()
+            
Index: ase/gui/minimize.py
===================================================================
--- ase/gui/minimize.py	(revision 0)
+++ ase/gui/minimize.py	(revision 1)
@@ -0,0 +1,117 @@
+# encoding: utf-8
+
+"Module for performing energy minimization."
+
+import gtk
+from ase.gui.simulation import Simulation
+from ase.gui.widgets import oops, pack, AseGuiCancelException
+import ase
+import numpy as np
+
+class MinimizeMixin:
+    minimizers = ('BFGS', 'BFGSLineSearch', 'LBFGS', 'LBFGSLineSearch', 'MDMin', 'FIRE')
+    def make_minimize_gui(self, box):
+        self.algo = gtk.combo_box_new_text()
+        for m in self.minimizers:
+            self.algo.append_text(m)
+        self.algo.set_active(0)
+        pack(box, [gtk.Label("Algorithm: "), self.algo])
+        
+        self.fmax = gtk.Adjustment(0.05, 0.00, 10.0, 0.01)
+        self.fmax_spin = gtk.SpinButton(self.fmax, 0, 3)
+        lbl = gtk.Label()
+        lbl.set_markup("Convergence criterion: F<sub>max</sub> = ")
+        pack(box, [lbl, self.fmax_spin])
+
+        self.steps = gtk.Adjustment(100, 1, 1000000, 1)
+        self.steps_spin = gtk.SpinButton(self.steps, 0, 0)
+        pack(box, [gtk.Label("Max. number of steps: "), self.steps_spin])
+
+class Minimize(Simulation, MinimizeMixin):
+    "Window for performing energy minimization."
+    
+    def __init__(self, gui):
+        Simulation.__init__(self, gui)
+        self.set_title("Energy minimization")
+        
+        vbox = gtk.VBox()
+        self.packtext(vbox,
+                      "Minimize the energy with respect to the positions.")
+        self.packimageselection(vbox)
+        pack(vbox, gtk.Label(""))
+
+        self.make_minimize_gui(vbox)
+        
+        pack(vbox, gtk.Label(""))
+        self.status_label = gtk.Label("")
+        pack(vbox, [self.status_label])
+        self.makebutbox(vbox)
+        vbox.show()
+        self.add(vbox)
+        self.show()
+        self.gui.register_vulnerable(self)
+
+    def run(self, *args):
+        "User has pressed [Run]: run the minimization."
+        if not self.setup_atoms():
+            return
+        fmax = self.fmax.value
+        steps = self.steps.value
+        mininame = self.minimizers[self.algo.get_active()]
+        self.begin(mode="min", algo=mininame, fmax=fmax, steps=steps)
+        algo = getattr(ase.optimize, mininame)
+        try:
+            logger_func = self.gui.simulation['progress'].get_logger_stream
+        except (KeyError, AttributeError):
+            logger = None
+        else:
+            logger = logger_func()  # Don't catch errors in the function.
+
+        # Display status message
+        self.status_label.set_text("Running ...")
+        self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                    gtk.gdk.color_parse('#AA0000'))
+        while gtk.events_pending():
+            gtk.main_iteration()
+
+        self.prepare_store_atoms()
+        minimizer = algo(self.atoms, logfile=logger)
+        minimizer.attach(self.store_atoms)
+        try:
+            minimizer.run(fmax=fmax, steps=steps)
+        except AseGuiCancelException:
+            # Update display to reflect cancellation of simulation.
+            self.status_label.set_text("Minimization CANCELLED after %i steps."
+                                       % (self.count_steps,))
+            self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                        gtk.gdk.color_parse('#AA4000'))
+        except MemoryError:
+            self.status_label.set_text("Out of memory, consider using LBFGS instead")
+            self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                        gtk.gdk.color_parse('#AA4000'))
+            
+        else:
+            # Update display to reflect succesful end of simulation.
+            self.status_label.set_text("Minimization completed in %i steps."
+                                       % (self.count_steps,))
+            self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                        gtk.gdk.color_parse('#007700'))
+            
+        self.end()
+        if self.count_steps:
+            # Notify other windows that atoms have changed.
+            # This also notifies this window!
+            self.gui.notify_vulnerable()
+
+        # Open movie window and energy graph
+        if self.gui.images.nimages > 1:
+            self.gui.movie()
+            assert not np.isnan(self.gui.images.E[0])
+            if not self.gui.plot_graphs_newatoms():
+                expr = 'i, e - E[-1]'            
+                self.gui.plot_graphs(expr=expr)
+
+    def notify_atoms_changed(self):
+        "When atoms have changed, check for the number of images."
+        self.setupimageselection()
+        
Index: ase/gui/graphs.py
===================================================================
--- ase/gui/graphs.py	(revision 0)
+++ ase/gui/graphs.py	(revision 1)
@@ -0,0 +1,116 @@
+#!/usr/bin/env python
+from math import sqrt
+
+import gtk
+
+from ase.gui.languages import translate as _
+from ase.gui.widgets import pack, help
+
+
+class Graphs(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        #self.window.set_position(gtk.WIN_POS_CENTER)
+        #self.window.connect("destroy", lambda w: gtk.main_quit())
+        #self.window.connect('delete_event', self.exit)
+        self.set_title('Graphs')
+        vbox = gtk.VBox()
+        self.expr = pack(vbox, [gtk.Entry(40),
+                                help('Help for plot ...')])[0]
+        self.expr.connect('activate', self.plot)
+
+        completion = gtk.EntryCompletion()
+        self.liststore = gtk.ListStore(str)
+        for s in ['fmax', 's, e-E[0]', 'i, d(0,1)']:
+            self.liststore.append([s])
+        completion.set_model(self.liststore)
+        self.expr.set_completion(completion)
+        completion.set_text_column(0)
+
+        button = pack(vbox, [gtk.Button('Plot'),
+                             gtk.Label(' x, y1, y2, ...')])[0]
+        button.connect('clicked', self.plot, 'xy')
+        button = pack(vbox, [gtk.Button('Plot'),
+                             gtk.Label(' y1, y2, ...')])[0]
+        button.connect('clicked', self.plot, 'y')
+        save_button = gtk.Button(stock=gtk.STOCK_SAVE)
+        save_button.connect('clicked',self.save)
+        clear_button = gtk.Button(_('clear'))
+        clear_button.connect('clicked', self.clear)
+        pack(vbox, [save_button,clear_button])
+        
+
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+    def plot(self, button=None, type=None, expr=None):
+        if expr is None:
+            expr = self.expr.get_text()
+        else:
+            self.expr.set_text(expr)
+
+        if expr not in [row[0] for row in self.liststore]:
+            self.liststore.append([expr])
+
+        data = self.gui.images.graph(expr)
+        
+        import matplotlib
+        matplotlib.interactive(True)
+        matplotlib.use('GTK')
+        #matplotlib.use('GTK', warn=False)# Not avail. in 0.91 (it is in 0.98)
+        import pylab
+        pylab.ion()
+
+        x = 2.5
+        self.gui.graphs.append(pylab.figure(figsize=(x * 2.5**0.5, x)))
+        i = self.gui.frame
+        m = len(data)
+
+        if type is None:
+            if m == 1:
+                type = 'y'
+            else:
+                type = 'xy'
+                
+        if type == 'y':
+            for j in range(m):
+                pylab.plot(data[j])
+                pylab.plot([i], [data[j, i]], 'o')
+        else:
+            for j in range(1, m):
+                pylab.plot(data[0], data[j])
+                pylab.plot([data[0, i]], [data[j, i]], 'o')
+        pylab.title(expr)
+        #pylab.show()
+
+    python = plot
+
+    def save(self, filename):
+        chooser = gtk.FileChooserDialog(
+            'Save data to file ... ', None, gtk.FILE_CHOOSER_ACTION_SAVE,
+            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+        save = chooser.run()
+        if save == gtk.RESPONSE_OK:
+            filename = chooser.get_filename()
+            expr = self.expr.get_text()
+            data = self.gui.images.graph(expr)
+            expr = '# '+expr
+            fd = open(filename,'w')
+            fd.write("%s \n" % (expr))
+            for s in range(len(data[0])):
+                for i in range(len(data)):
+                    val = data[i,s]
+                    fd.write("%12.8e\t" % (val))
+                fd.write("\n")
+            fd.close()
+        chooser.destroy()
+
+    def clear(self, button):
+        import pylab
+        for graph in self.gui.graphs:
+            pylab.close(graph)
+        self.gui.graphs = []
+        
Index: ase/gui/gui.py
===================================================================
--- ase/gui/gui.py	(revision 0)
+++ ase/gui/gui.py	(revision 1)
@@ -0,0 +1,1132 @@
+# husk:
+# Exit*2?  remove pylab.show()
+# close button
+# DFT
+# ADOS
+# grey-out stuff after one second: vmd, rasmol, ...
+# Show with ....
+# rasmol: set same rotation as ag
+# Graphs: save, Python, 3D
+# start from python (interactive mode?)
+# ascii-art option (colored)|
+# option -o (output) and -f (force overwrite)
+# surfacebuilder
+# screen-dump
+# icon
+# ag-community-server
+# translate option: record all translations, 
+# and check for missing translations.
+
+#TODO: Add possible way of choosing orinetations. \
+#TODO: Two atoms defines a direction, three atoms their normal does
+#TODO: Align orientations chosen in Rot_selected v unselcted
+#TODO: Get the atoms_rotate_0 thing string
+#TODO: Use set atoms instead og the get atoms
+#TODO: Arrow keys will decide how the orientation changes
+#TODO: Undo redo que should be implemented
+#TODO: Update should have possibility to change positions
+#TODO: Window for rotation modes and move moves which can be chosen
+#TODO: WHen rotate and move / hide the movie menu
+
+import os
+import weakref
+import numpy as np
+
+import gtk
+from ase.gui.view import View
+from ase.gui.status import Status
+from ase.gui.widgets import pack, help, Help, oops
+from ase.gui.languages import translate as _
+
+from ase.gui.settings import Settings
+from ase.gui.surfaceslab import SetupSurfaceSlab
+from ase.gui.nanoparticle import SetupNanoparticle
+from ase.gui.nanotube import SetupNanotube
+from ase.gui.graphene import SetupGraphene
+from ase.gui.calculator import SetCalculator
+from ase.gui.energyforces import EnergyForces
+from ase.gui.minimize import Minimize
+from ase.gui.scaling import HomogeneousDeformation
+
+ui_info = """\
+<ui>
+  <menubar name='MenuBar'>
+    <menu action='FileMenu'>
+      <menuitem action='Open'/>
+      <menuitem action='New'/>
+      <menuitem action='Save'/>
+      <separator/>
+      <menuitem action='Quit'/>
+    </menu>
+    <menu action='EditMenu'>
+      <menuitem action='SelectAll'/>
+      <menuitem action='Invert'/>
+      <menuitem action='SelectConstrained'/>
+      <separator/>
+      <menuitem action='Modify'/>
+      <menuitem action='AddAtoms'/>
+      <menuitem action='DeleteAtoms'/>
+      <separator/>      
+      <menuitem action='First'/>
+      <menuitem action='Previous'/>
+      <menuitem action='Next'/>
+      <menuitem action='Last'/>
+    </menu>
+    <menu action='ViewMenu'>
+      <menuitem action='ShowUnitCell'/>
+      <menuitem action='ShowAxes'/>
+      <menuitem action='ShowBonds'/>
+      <separator/>
+      <menuitem action='Repeat'/>
+      <menuitem action='Rotate'/>
+      <menuitem action='Focus'/>
+      <menuitem action='ZoomIn'/>
+      <menuitem action='ZoomOut'/>
+      <menuitem action='Settings'/>
+      <menuitem action='VMD'/>
+      <menuitem action='RasMol'/>
+      <menuitem action='XMakeMol'/>
+      <menuitem action='Avogadro'/>
+    </menu>
+    <menu action='ToolsMenu'>
+      <menuitem action='Graphs'/>
+      <menuitem action='Movie'/>
+      <menuitem action='EModify'/>
+      <menuitem action='Constraints'/>
+      <menuitem action='MoveAtoms'/>
+      <menuitem action='RotateAtoms'/>
+      <menuitem action='OrientAtoms'/>
+      <menuitem action='DFT'/>
+      <menuitem action='NEB'/>
+      <menuitem action='BulkModulus'/>
+    </menu>
+    <menu action='SetupMenu'>
+      <menuitem action='Surface'/>
+      <menuitem action='Nanoparticle'/>
+      <menuitem action='Graphene'/>
+      <menuitem action='Nanotube'/>
+    </menu>
+    <menu action='CalculateMenu'>
+      <menuitem action='SetCalculator'/>
+      <separator/>
+      <menuitem action='EnergyForces'/>
+      <menuitem action='Minimize'/>
+      <menuitem action='Scaling'/>
+    </menu>
+    <menu action='HelpMenu'>
+      <menuitem action='About'/>
+      <menuitem action='Webpage'/>
+      <menuitem action='Debug'/>
+    </menu>
+  </menubar>
+</ui>"""
+
+class GUI(View, Status):
+    def __init__(self, images, rotations='', show_unit_cell=True,
+                 show_bonds=False):
+        self.images = images
+        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+        #self.window.set_icon(gtk.gdk.pixbuf_new_from_file('guiase.png'))
+        self.window.set_position(gtk.WIN_POS_CENTER)
+        #self.window.connect("destroy", lambda w: gtk.main_quit())
+        self.window.connect('delete_event', self.exit)
+        vbox = gtk.VBox()
+        self.window.add(vbox)
+        if gtk.pygtk_version < (2, 12):
+            self.set_tip = gtk.Tooltips().set_tip
+
+        actions = gtk.ActionGroup("Actions")
+        actions.add_actions([
+            ('FileMenu', None, '_File'),
+            ('EditMenu', None, '_Edit'),
+            ('ViewMenu', None, '_View'  ),
+            ('ToolsMenu', None, '_Tools'),
+            ('SetupMenu', None, '_Setup'),
+            ('CalculateMenu', None, '_Calculate'),
+            ('HelpMenu', None, '_Help'),
+            ('Open', gtk.STOCK_OPEN, '_Open', '<control>O',
+             'Create a new file',
+             self.open),
+             ('New', gtk.STOCK_NEW, '_New', '<control>N',
+             'New ase.gui window',
+             lambda widget: os.system('ag &')),
+            ('Save', gtk.STOCK_SAVE, '_Save', '<control>S',
+             'Save current file',
+             self.save),
+            ('Quit', gtk.STOCK_QUIT, '_Quit', '<control>Q',
+             'Quit',
+             self.exit),
+            ('SelectAll', None, 'Select _all', None,
+             '',
+             self.select_all),
+            ('Invert', None, '_Invert selection', None,
+             '',
+             self.invert_selection),
+            ('SelectConstrained', None, 'Select _constrained atoms', None,
+             '',
+             self.select_constrained_atoms),
+             ('Modify', None, '_Modify', '<control>Y',
+              'Change tags, moments and atom types of the selected atoms',
+              self.modify_atoms),
+             ('AddAtoms', None, '_Add atoms', '<control>A',
+              'Insert or import atoms and molecules',
+              self.add_atoms),
+             ('DeleteAtoms', None, '_Delete selected atoms', 'BackSpace',
+              'Deletes the selected atoms',
+              self.delete_selected_atoms),             
+            ('First', gtk.STOCK_GOTO_FIRST, '_First image', 'Home',
+             '',
+             self.step),
+            ('Previous', gtk.STOCK_GO_BACK, '_Previous image', 'Page_Up',
+             '',
+             self.step),
+            ('Next', gtk.STOCK_GO_FORWARD, '_Next image', 'Page_Down',
+             '',
+             self.step),
+            ('Last', gtk.STOCK_GOTO_LAST, '_Last image', 'End',
+             '',
+             self.step),
+            ('Repeat', None, 'Repeat ...', None,
+             '',
+             self.repeat_window),
+            ('Rotate', None, 'Rotate ...', None,
+             '',
+             self.rotate_window),
+            ('Focus', gtk.STOCK_ZOOM_FIT, 'Focus', 'F',
+             '',
+             self.focus),
+            ('ZoomIn', gtk.STOCK_ZOOM_IN, 'Zoom in', 'plus',
+             '',
+             self.zoom),
+            ('ZoomOut', gtk.STOCK_ZOOM_OUT, 'Zoom out', 'minus',
+             '',
+             self.zoom),
+            ('Settings', gtk.STOCK_PREFERENCES, 'Settings ...', None,
+             '',
+             self.settings),
+            ('VMD', None, 'VMD', None,
+             '',
+             self.external_viewer),
+            ('RasMol', None, 'RasMol', None,
+             '',
+             self.external_viewer),
+            ('XMakeMol', None, 'xmakemol', None,
+             '',
+             self.external_viewer),
+            ('Avogadro', None, 'avogadro', None,
+             '',
+             self.external_viewer),
+            ('Graphs', None, 'Graphs ...', None,
+             '',
+             self.plot_graphs),
+            ('Movie', None, 'Movie ...', None,
+             '',
+             self.movie),
+            ('EModify', None, 'Expert modify ...', None,
+             '',
+             self.execute),
+            ('Constraints', None, 'Constraints ...', None,
+             '',
+             self.constraints_window),
+            ('DFT', None, 'DFT ...', None,
+             '',
+             self.dft_window),
+            ('NEB', None, 'NE_B', None,
+             '',
+             self.NEB),
+            ('BulkModulus', None, 'B_ulk Modulus', None,
+             '',
+             self.bulk_modulus),
+            ('Bulk', None, '_Bulk Crystal', None,
+             "Create a bulk crystal with arbitrary orientation",
+             self.bulk_window),
+            ('Surface', None, '_Surface slab', None,
+             "Create the most common surfaces",
+             self.surface_window),
+            ('Nanoparticle', None, '_Nanoparticle', None,
+             "Create a crystalline nanoparticle",
+             self.nanoparticle_window),
+            ('Nanotube', None, 'Nano_tube', None,
+             "Create a nanotube",
+             self.nanotube_window),
+            ('Graphene', None, 'Graphene', None,
+             "Create a graphene sheet or nanoribbon",
+             self.graphene_window),
+            ('SetCalculator', None, 'Set _Calculator', None,
+             "Set a calculator used in all calculation modules",
+             self.calculator_window),
+            ('EnergyForces', None, '_Energy and Forces', None,
+             "Calculate energy and forces",
+             self.energy_window),
+            ('Minimize', None, 'Energy _Minimization', None,
+             "Minimize the energy",
+             self.energy_minimize_window),
+            ('Scaling', None, 'Scale system', None,
+             "Deform system by scaling it",
+             self.scaling_window),
+            ('About', None, '_About', None,
+             None,
+             self.about),
+            ('Webpage', gtk.STOCK_HELP, 'Webpage ...', None, None, webpage),
+            ('Debug', None, 'Debug ...', None, None, self.debug)])
+        actions.add_toggle_actions([
+            ('ShowUnitCell', None, 'Show _unit cell', '<control>U',
+             'Bold',
+             self.toggle_show_unit_cell,
+             show_unit_cell > 0),
+            ('ShowAxes', None, 'Show _axes', None,
+             'Bold',
+             self.toggle_show_axes,
+             True),
+            ('ShowBonds', None, 'Show _bonds', '<control>B',
+             'Bold',
+             self.toggle_show_bonds,
+             show_bonds),
+            ('MoveAtoms', None, '_Move atoms', '<control>M',
+             'Bold',
+             self.toggle_move_mode,
+             False),
+            ('RotateAtoms', None, '_Rotate atoms', '<control>R',
+             'Bold',
+             self.toggle_rotate_mode,
+             False),
+            ('OrientAtoms', None, 'Orien_t atoms', '<control>T',
+             'Bold',
+             self.toggle_orient_mode,
+             False)             
+            ])
+        self.ui = ui = gtk.UIManager()
+        ui.insert_action_group(actions, 0)
+        self.window.add_accel_group(ui.get_accel_group())
+
+        try:
+            mergeid = ui.add_ui_from_string(ui_info)
+        except gobject.GError, msg:
+            print 'building menus failed: %s' % msg
+
+        vbox.pack_start(ui.get_widget('/MenuBar'), False, False, 0)
+        
+        View.__init__(self, vbox, rotations)
+        Status.__init__(self, vbox)
+        vbox.show()
+        #self.window.set_events(gtk.gdk.BUTTON_PRESS_MASK)
+        self.window.connect('key-press-event', self.scroll)
+        self.window.connect('scroll_event', self.scroll_event)
+        self.window.show()
+        self.graphs = []       # List of open pylab windows
+        self.graph_wref = []   # List of weakrefs to Graph objects
+        self.movie_window = None
+        self.vulnerable_windows = []
+        self.simulation = {}   # Used by modules on Calculate menu.
+        self.module_state = {} # Used by modules to store their state.
+
+    def run(self, expr=None):
+        self.set_colors()
+        self.set_coordinates(self.images.nimages - 1, focus=True)
+
+        if self.images.nimages > 1:
+            self.movie()
+
+        if expr is None and not np.isnan(self.images.E[0]):
+            expr = 'i, e - E[-1]'
+            
+        if expr is not None and expr != '' and self.images.nimages > 1:
+            self.plot_graphs(expr=expr)
+
+        gtk.main()
+    
+            
+    def step(self, action):
+        d = {'First': -10000000,
+             'Previous': -1,
+             'Next': 1,
+             'Last': 10000000}[action.get_name()]
+        i = max(0, min(self.images.nimages - 1, self.frame + d))
+        self.set_frame(i)
+        if self.movie_window is not None:
+            self.movie_window.frame_number.value = i
+            
+    def _do_zoom(self, x):
+        """Utility method for zooming"""
+        self.scale *= x
+        self.draw()
+        
+    def zoom(self, action):
+        """Zoom in/out on keypress or clicking menu item"""
+        x = {'ZoomIn': 1.2, 'ZoomOut':1 / 1.2}[action.get_name()]
+        self._do_zoom(x)
+
+    def scroll_event(self, window, event):
+        """Zoom in/out when using mouse wheel"""
+        x = 1.0
+        if event.direction == gtk.gdk.SCROLL_UP:
+            x = 1.2
+        elif event.direction == gtk.gdk.SCROLL_DOWN:
+            x = 1.0 / 1.2
+        self._do_zoom(x)
+
+    def settings(self, menuitem):
+        Settings(self)
+        
+    def scroll(self, window, event):
+        from copy import copy    
+        CTRL = event.state == gtk.gdk.CONTROL_MASK
+        SHIFT = event.state == gtk.gdk.SHIFT_MASK
+        dxdydz = {gtk.keysyms.KP_Add: ('zoom', 1.2, 0),
+                gtk.keysyms.KP_Subtract: ('zoom', 1 / 1.2, 0),
+                gtk.keysyms.Up:    ( 0, +1 - CTRL, +CTRL),
+                gtk.keysyms.Down:  ( 0, -1 + CTRL, -CTRL),
+                gtk.keysyms.Right: (+1,  0, 0),
+                gtk.keysyms.Left:  (-1,  0, 0)}.get(event.keyval, None)
+        try:
+            inch = chr(event.keyval)
+        except:
+            inch = None
+            
+        sel = []
+
+        atom_move = self.ui.get_widget('/MenuBar/ToolsMenu/MoveAtoms'
+                                    ).get_active()
+        atom_rotate = self.ui.get_widget('/MenuBar/ToolsMenu/RotateAtoms'
+                                      ).get_active()
+        atom_orient = self.ui.get_widget('/MenuBar/ToolsMenu/OrientAtoms'
+                                      ).get_active()
+        if dxdydz is None:
+            return
+        dx, dy, dz = dxdydz
+        if dx == 'zoom':
+            self._do_zoom(dy)
+            return
+        if dx == 'zoom':
+            self._do_zoom(dy)
+            return
+        d = self.scale * 0.1
+        tvec = np.array([dx, dy, dz])
+
+        dir_vec = np.dot(self.axes, tvec)
+        if (atom_move):
+            rotmat = self.axes
+            s = 0.1
+            if SHIFT: 
+                s = 0.01
+            add = s * dir_vec
+            for i in range(len(self.R)):
+                if self.atoms_to_rotate_0[i]: 
+                    self.R[i] += add
+                    for jx in range(self.images.nimages):
+                        self.images.P[jx][i] += add
+        elif atom_rotate:
+            from rot_tools import rotate_about_vec, \
+                                  rotate_vec
+            sel = self.images.selected
+            if sum(sel) == 0:
+                sel = self.atoms_to_rotate_0
+            nsel = sum(sel)
+            # this is the first one to get instatiated
+            if nsel != 2: 
+                self.rot_vec = dir_vec
+                
+            change = False
+            z_axis = np.dot(self.axes, np.array([0, 0, 1]))
+            if self.atoms_to_rotate == None:
+                change = True 
+                self.z_axis_old = z_axis.copy()
+                self.dx_change = [0, 0]
+                self.atoms_to_rotate = self.atoms_to_rotate_0.copy()
+                self.atoms_selected = sel.copy()
+                self.rot_vec = dir_vec
+                    
+            if nsel != 2 or sum(self.atoms_to_rotate) == 2:
+                self.dx_change = [0, 0]
+                
+            for i in range(len(sel)):
+                if sel[i] != self.atoms_selected[i]: 
+                    change = True
+            cz = [dx, dy+dz]      
+            
+            if cz[0] or cz[1]: 
+                change = False
+            if not(cz[0] * (self.dx_change[1])):
+                change = True 
+            for i in range(2):
+                if cz[i] and self.dx_change[i]:
+                    self.rot_vec = self.rot_vec * cz[i] * self.dx_change[i]
+                    if cz[1]:
+                        change = False
+                
+            if np.prod(self.z_axis_old != z_axis):
+                change = True
+            self.z_axis_old = z_axis.copy()           
+            self.dx_change = copy(cz)
+            dihedral_rotation = len(self.images.selected_ordered) == 4
+
+            if change:
+                self.atoms_selected = sel.copy()
+
+                if nsel == 2 and sum(self.atoms_to_rotate) != 2:
+                    asel = []
+                    for i, j in enumerate(sel):
+                        if j: 
+                            asel.append(i)
+                    a1, a2 = asel
+
+                    rvx = self.images.P[self.frame][a1] - \
+                          self.images.P[self.frame][a2]
+                        
+                    rvy = np.cross(rvx,
+                                   np.dot(self.axes,
+                                   np.array([0, 0, 1])))     
+                    self.rot_vec = rvx * dx + rvy * (dy + dz)
+                    self.dx_change = [dx, dy+dz]
+                    
+                # dihedral rotation?
+                if dihedral_rotation:
+                    sel = self.images.selected_ordered
+                    self.rot_vec = (dx+dy+dz)*(self.R[sel[2]]-self.R[sel[1]])
+
+            rot_cen = np.array([0.0, 0.0, 0.0])
+            if dihedral_rotation:
+                sel = self.images.selected_ordered
+                rot_cen = self.R[sel[1]].copy()
+            elif nsel: 
+                for i, b in enumerate( sel):
+                    if b: 
+                        rot_cen += self.R[i]
+                rot_cen /= float(nsel)     
+
+            degrees = 5 * (1 - SHIFT) + SHIFT
+            degrees = abs(sum(dxdydz)) * 3.1415 / 360.0 * degrees
+            rotmat = rotate_about_vec(self.rot_vec, degrees)
+            
+            # now rotate the atoms that are to be rotated            
+            for i in range(len(self.R)):
+                if self.atoms_to_rotate[i]: 
+                    self.R[i] -= rot_cen
+                    for jx in range(self.images.nimages):
+                        self.images.P[jx][i] -= rot_cen
+                    
+                    self.R[i] = rotate_vec(rotmat, self.R[i])
+                    for jx in range(self.images.nimages):
+                        self.images.P[jx][i] = rotate_vec(rotmat, self.images.P[jx][i])
+                    
+                    self.R[i] += rot_cen
+                    for jx in range(self.images.nimages):
+                        self.images.P[jx][i] += rot_cen
+        elif atom_orient:
+            to_vec  = np.array([dx, dy, dz])
+
+            from rot_tools import rotate_vec_into_newvec
+            rot_mat = rotate_vec_into_newvec(self.orient_normal, to_vec)
+            self.axes = rot_mat
+            
+            self.set_coordinates()
+        else:
+            self.center -= (dx * 0.1 * self.axes[:, 0] -
+                            dy * 0.1 * self.axes[:, 1])
+        self.draw()
+    
+        
+    def add_atoms(self, widget, data=None):
+        """
+        Presents a dialogbox to the user, that allows him to add atoms/molecule to the current slab.
+        
+        The molecule/atom is rotated using the current rotation of the coordinate system.
+        
+        The molecule/atom can be added at a specified position - if the keyword auto+Z is used,
+        the COM of the selected atoms will be used as COM for the moleculed. The COM is furthermore
+        translated Z ang towards the user.
+        
+        If no molecules are selected, the COM of all the atoms will be used for the x-y components of the
+        active coordinate system, while the z-direction will be chosen from the nearest atom position 
+        along this direction.
+        
+        Note: If this option is used, all frames except the active one are deleted.
+        """
+        if data:
+            if data == 'load':
+                chooser = gtk.FileChooserDialog(
+                            _('Open ...'), None, gtk.FILE_CHOOSER_ACTION_OPEN,
+                            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                             gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+                ok = chooser.run()
+                if ok == gtk.RESPONSE_OK:
+                    filename = chooser.get_filename()
+                chooser.destroy()
+                if not ok:
+                    return
+
+            if data == 'OK' or data == 'load':
+                import ase
+                if data == 'load':
+                    molecule = filename
+                else:
+                    molecule = self.add_entries[1].get_text()
+                tag = self.add_entries[2].get_text()
+                mom = self.add_entries[3].get_text()
+                pos = self.add_entries[4].get_text().lower()
+
+                a = None
+                try:
+                    a = ase.Atoms([ase.Atom(molecule)])
+                except:      
+                    try:
+                        a = ase.molecule(molecule)
+                    except:
+                        try:
+                            a = ase.read(molecule, -1)
+                        except:
+                            self.add_entries[1].set_text('?' + molecule) 
+                            return ()
+                        
+                directions = np.transpose(self.axes)
+                if a != None:
+                    for i in a:
+                        try: 
+                            i.set_tag(int(tag))
+                        except: 
+                            self.add_entries[2].set_text('?' + tag) 
+                            return ()
+                        try: 
+                            i.magmom = float(mom)
+                        except: 
+                            self.add_entries[3].set_text('?' + mom) 
+                            return ()
+                  # apply the current rotation matrix to A
+                    for i in a:
+                        i.position = np.dot(self.axes, i.position)       
+                  # find the extent of the molecule in the local coordinate system
+                    a_cen_pos = np.array([0.0, 0.0, 0.0])
+                    m_cen_pos = 0.0
+                    for i in a.positions:
+                        a_cen_pos[0] += np.dot(directions[0], i)
+                        a_cen_pos[1] += np.dot(directions[1], i)
+                        a_cen_pos[2] += np.dot(directions[2], i)
+
+                        m_cen_pos = max(np.dot(-directions[2], i), m_cen_pos)
+                    a_cen_pos[0] /= len(a.positions)      
+                    a_cen_pos[1] /= len(a.positions)      
+                    a_cen_pos[2] /= len(a.positions)
+                    a_cen_pos[2] -= m_cen_pos
+          
+                  # now find the position
+                    cen_pos = np.array([0.0, 0.0, 0.0])
+                    if sum(self.images.selected) > 0:
+                        for i in range(len(self.R)):
+                            if self.images.selected[i]:
+                                cen_pos += self.R[i]
+                        cen_pos /= sum(self.images.selected)   
+                    elif len(self.R) > 0:
+                        px = 0.0
+                        py = 0.0
+                        pz = -1e6
+
+                        for i in range(len(self.R)):
+                            px += np.dot(directions[0], self.R[i])
+                            py += np.dot(directions[1], self.R[i])
+                            pz = max(np.dot(directions[2], self.R[i]), pz)
+                        px = (px/float(len(self.R)))
+                        py = (py/float(len(self.R)))
+                        cen_pos = directions[0] * px + \
+                                  directions[1] * py + \
+                                  directions[2] * pz
+              
+                    if 'auto' in pos:
+                        pos = pos.replace('auto', '')
+                        import re
+                        pos = re.sub('\s', '', pos)
+                        if '(' in pos:
+                            sign = eval('%s1' % pos[0])
+                            a_cen_pos -= sign * np.array(eval(pos[1:]), float)
+                        else:
+                            a_cen_pos -= float(pos) * directions[2]
+                    else:
+                        cen_pos = np.array(eval(pos))
+                    for i in a:
+                        i.position += cen_pos - a_cen_pos      
+  
+                  # and them to the molecule
+                    atoms = self.images.get_atoms(self.frame)
+                    atoms = atoms + a
+                    self.new_atoms(atoms, init_magmom=True)
+                    
+                  # and finally select the new molecule for easy moving and rotation
+                    for i in range(len(a)):
+                        self.images.selected[len(atoms) - i - 1] = True
+
+                    self.draw()    
+            self.add_entries[0].destroy()
+        if data == None:
+            molecule = ''
+            tag = '0'
+            mom = '0'
+            pos = 'auto+1'
+            self.add_entries = []
+            window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+            self.add_entries.append(window)
+            window.set_title('Add atoms')
+
+            vbox = gtk.VBox(False, 0)
+            window.add(vbox)
+            vbox.show()
+            pack = False
+            for i, j in [['Insert atom or molecule', molecule],
+                         ['Tag', tag],
+                         ['Moment', mom],
+                         ['Position', pos]]:
+
+                label = gtk.Label(i)
+                if not pack:
+                    vbox.pack_start(label, True, True, 0)
+                else: 
+                    pack = True
+                    vbox.add(label)    
+                label.show()
+  
+                entry = gtk.Entry()
+                entry.set_text(j)
+                self.add_entries.append(entry)
+                entry.set_max_length(50)
+                entry.show()
+    
+                vbox.add(entry)
+
+            button = gtk.Button('_Load molecule')
+            button.connect('clicked', self.add_atoms, 'load')
+            button.show()
+            vbox.add(button)
+            button = gtk.Button('_OK')
+            button.connect('clicked', self.add_atoms, 'OK')
+            button.show()
+            vbox.add(button)
+            button = gtk.Button('_Cancel')
+            button.connect('clicked', self.add_atoms, 'Cancel')
+            button.show()
+            vbox.add(button)
+ 
+            window.show()
+        
+    def modify_atoms(self, widget, data=None):
+        """
+        Presents a dialog box where the user is able to change the atomic type, the magnetic
+        moment and tags of the selected atoms. An item marked with X will not be changed.
+        """
+        if data:
+            if data == 'OK':
+                import ase
+                symbol = self.add_entries[1].get_text()
+                tag = self.add_entries[2].get_text()
+                mom = self.add_entries[3].get_text()
+                a = None
+                if symbol != 'X': 
+                    try:
+                        a = ase.Atoms([ase.Atom(symbol)])  
+                    except:
+                        self.add_entries[1].set_text('?' + symbol)
+                        return ()
+          
+                y = self.images.selected.copy()
+                    # and them to the molecule
+                atoms = self.images.get_atoms(self.frame)
+                for i in range(len(atoms)):
+                    if self.images.selected[i]:  
+                        if a: 
+                            atoms[i].symbol = symbol
+                        try: 
+                            if tag != 'X': 
+                                atoms[i].tag = int(tag)
+                        except:
+                            self.add_entries[2].set_text('?' + tag)
+                            return ()
+                        try: 
+                            if mom != 'X': 
+                                atoms[i].magmom = float(mom)
+                        except: 
+                            self.add_entries[3].set_text('?' + mom)
+                            return ()
+                self.new_atoms(atoms, init_magmom=True)
+
+                # and finally select the new molecule for easy moving and rotation
+                self.images.selected = y
+                self.draw()    
+          
+            self.add_entries[0].destroy()          
+        if data == None and sum(self.images.selected):
+            atoms = self.images.get_atoms(self.frame)
+            s_tag = ''
+            s_mom = ''  
+            s_symbol = ''
+          # Get the tags, moments and symbols of the selected atomsa  
+            for i in range(len(atoms)):
+                if self.images.selected[i]:
+                    if not(s_tag): 
+                        s_tag = str(atoms[i].tag)
+                    elif s_tag != str(atoms[i].tag): 
+                        s_tag = 'X'
+                    if not(s_mom): 
+                        s_mom = ("%2.2f" % (atoms[i].magmom))
+                    elif s_mom != ("%2.2f" % (atoms[i].magmom)): 
+                        s_mom = 'X'
+                    if not(s_symbol): 
+                        s_symbol = str(atoms[i].symbol)       
+                    elif s_symbol != str(atoms[i].symbol): 
+                        s_symbol = 'X'
+  
+            self.add_entries = []
+            window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+            self.add_entries.append(window)
+            window.set_title('Modify')
+
+            vbox = gtk.VBox(False, 0)
+            window.add(vbox)
+            vbox.show()
+            pack = False
+            for i, j in [['Atom', s_symbol],
+                        ['Tag', s_tag],
+                        ['Moment', s_mom]]:
+                label = gtk.Label(i)
+                if not pack:
+                    vbox.pack_start(label, True, True, 0)
+                else: 
+                    pack = True
+                    vbox.add(label)    
+                label.show()
+
+                entry = gtk.Entry()
+                entry.set_text(j)
+                self.add_entries.append(entry)
+                entry.set_max_length(50)
+                entry.show()
+                vbox.add(entry)
+            button = gtk.Button('_OK')
+            button.connect('clicked', self.modify_atoms, 'OK')
+            button.show()
+            vbox.add(button)
+            button = gtk.Button('_Cancel')
+            button.connect('clicked', self.modify_atoms, 'Cancel')
+            button.show()
+            vbox.add(button)
+ 
+            window.show()
+        
+    def delete_selected_atoms(self, widget, data=None):
+        if data == 'OK':
+            atoms = self.images.get_atoms(self.frame)
+            lena = len(atoms)
+            for i in range(len(atoms)):
+                li = lena-1-i
+                if self.images.selected[li]:
+                    del(atoms[li])
+            self.new_atoms(atoms)
+         
+            self.draw()    
+        if data:      
+            self.window.destroy()
+             
+        if not(data) and sum(self.images.selected):
+            more = '' + (sum(self.images.selected) > 1) * 's'
+            self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
+            self.window.set_title('Confirmation')
+            self.window.set_border_width(10)
+            self.box1 = gtk.HBox(False, 0)
+            self.window.add(self.box1)
+            self.button1 = gtk.Button('Delete selected atom%s?' % more)
+            self.button1.connect("clicked", self.delete_selected_atoms, "OK")
+            self.box1.pack_start(self.button1, True, True, 0)
+            self.button1.show()
+
+            self.button2 = gtk.Button("Cancel")
+            self.button2.connect("clicked", self.delete_selected_atoms, "Cancel")
+            self.box1.pack_start(self.button2, True, True, 0)
+            self.button2.show()
+
+            self.box1.show()
+            self.window.show()
+                
+    def debug(self, x):
+        from ase.gui.debug import Debug
+        Debug(self)
+
+    def execute(self, widget=None):
+        from ase.gui.execute import Execute
+        Execute(self)
+        
+    def constraints_window(self, widget=None):
+        from ase.gui.constraints import Constraints
+        Constraints(self)
+
+    def dft_window(self, widget=None):
+        from ase.gui.dft import DFT
+        DFT(self)
+
+    def select_all(self, widget):
+        self.images.selected[:] = True
+        self.draw()
+        
+    def invert_selection(self, widget):
+        self.images.selected[:] = ~self.images.selected
+        self.draw()
+        
+    def select_constrained_atoms(self, widget):
+        self.images.selected[:] = ~self.images.dynamic
+        self.draw()
+        
+    def movie(self, widget=None):
+        from ase.gui.movie import Movie
+        self.movie_window = Movie(self)
+        
+    def plot_graphs(self, x=None, expr=None):
+        from ase.gui.graphs import Graphs
+        g = Graphs(self)
+        if expr is not None:
+            g.plot(expr=expr)
+        self.graph_wref.append(weakref.ref(g))
+
+    def plot_graphs_newatoms(self):
+        "Notify any Graph objects that they should make new plots."
+        new_wref = []
+        found = 0
+        for wref in self.graph_wref:
+            ref = wref()
+            if ref is not None:
+                ref.plot()
+                new_wref.append(wref)  # Preserve weakrefs that still work.
+                found += 1
+        self.graph_wref = new_wref
+        return found
+        
+    def NEB(self, action):
+        from ase.gui.neb import NudgedElasticBand
+        NudgedElasticBand(self.images)
+        
+    def bulk_modulus(self, action):
+        from ase.gui.bulk_modulus import BulkModulus
+        BulkModulus(self.images)
+        
+    def open(self, button=None, filenames=None):
+        if filenames == None:
+            chooser = gtk.FileChooserDialog(
+                _('Open ...'), None, gtk.FILE_CHOOSER_ACTION_OPEN,
+                (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                 gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+            ok = chooser.run() == gtk.RESPONSE_OK
+            if ok:
+                filenames = [chooser.get_filename()]
+            chooser.destroy()
+
+            if not ok:
+                return
+            
+        self.reset_tools_modes()     
+        self.images.read(filenames, slice(None))
+        self.set_colors()
+        self.set_coordinates(self.images.nimages - 1, focus=True)
+
+    def import_atoms (self, button=None, filenames=None):
+        if filenames == None:
+            chooser = gtk.FileChooserDialog(
+                _('Open ...'), None, gtk.FILE_CHOOSER_ACTION_OPEN,
+                (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                 gtk.STOCK_OPEN, gtk.RESPONSE_OK))
+            ok = chooser.run()
+            if ok == gtk.RESPONSE_OK:
+                filenames = [chooser.get_filename()]
+            chooser.destroy()
+
+            if not ok:
+                return
+            
+            
+        self.images.import_atoms(filenames, self.frame)
+        self.set_colors()
+        self.set_coordinates(self.images.nimages - 1, focus=True)
+
+    def save(self, action):
+        chooser = gtk.FileChooserDialog(
+            _('Save ...'), None, gtk.FILE_CHOOSER_ACTION_SAVE,
+            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+
+        # Add a file type filter
+        types = []
+        name_to_suffix = {}
+        for name, suffix in [('Automatic', None),
+                             ('XYZ file', 'xyz'),
+                             ('ASE trajectory', 'traj'),
+                             ('PDB file', 'pdb'),
+                             ('Gaussian cube file', 'cube'),
+                             ('Python script', 'py'),
+                             ('VNL file', 'vnl'),
+                             ('Portable Network Graphics', 'png'),
+                             ('Persistance of Vision', 'pov'),
+                             ('Encapsulated PostScript', 'eps'),
+                             ('FHI-aims geometry input', 'in'),
+                             ('VASP geometry input', 'POSCAR')]:
+            if suffix is None:
+                name = _(name)
+            else:
+                name = '%s (%s)' % (_(name), suffix)
+            filt = gtk.FileFilter()
+            filt.set_name(name)
+            if suffix is None:
+                filt.add_pattern('*')
+            elif suffix == 'POSCAR':
+                filt.add_pattern('*POSCAR*')
+            else:
+                filt.add_pattern('*.'+suffix)
+            if suffix is not None:
+                types.append(suffix)
+                name_to_suffix[name] = suffix
+                
+            chooser.add_filter(filt)
+
+        if self.images.nimages > 1:
+            img_vbox = gtk.VBox()
+            button = gtk.CheckButton('Save current image only (#%d)' %
+                                     self.frame)
+            pack(img_vbox, button)
+            entry = gtk.Entry(10)
+            pack(img_vbox, [gtk.Label(_('Slice: ')), entry,
+                                        help('Help for slice ...')])
+            entry.set_text('0:%d' % self.images.nimages)
+            img_vbox.show()
+            chooser.set_extra_widget(img_vbox)
+
+        while True:
+            # Loop until the user selects a proper file name, or cancels.
+            response = chooser.run()
+            if response == gtk.RESPONSE_CANCEL:
+                chooser.destroy()
+                return
+            elif response != gtk.RESPONSE_OK:
+                print >>sys.stderr, "AG INTERNAL ERROR: strange response in Save,", response
+                chooser.destroy()
+                return
+                
+            filename = chooser.get_filename()
+
+            suffix = os.path.splitext(filename)[1][1:]
+            if 'POSCAR' in filename:
+                suffix = 'POSCAR'
+            if suffix == '':
+                # No suffix given.  If the user has chosen a special
+                # file type, use the corresponding suffix.
+                filt = chooser.get_filter().get_name()
+                suffix = name_to_suffix[filt]
+                filename = filename + '.' + suffix
+                
+            if suffix not in types:
+                oops(message='Unknown output format!',
+                     message2='Use one of: ' + ', '.join(types[1:]))
+                continue
+                
+            if self.images.nimages > 1:
+                if button.get_active():
+                    filename += '@%d' % self.frame
+                else:
+                    filename += '@' + entry.get_text()
+
+            break
+        
+        chooser.destroy()
+
+        bbox = np.empty(4)
+        size = np.array([self.width, self.height]) / self.scale
+        bbox[0:2] = np.dot(self.center, self.axes[:, :2]) - size / 2
+        bbox[2:] = bbox[:2] + size
+        suc = self.ui.get_widget('/MenuBar/ViewMenu/ShowUnitCell').get_active()
+        self.images.write(filename, self.axes, show_unit_cell=suc, bbox=bbox)
+        
+    def bulk_window(self, menuitem):
+        SetupBulkCrystal(self)
+
+    def surface_window(self, menuitem):
+        SetupSurfaceSlab(self)
+
+    def nanoparticle_window(self, menuitem):
+        SetupNanoparticle(self)
+        
+    def graphene_window(self, menuitem):
+        SetupGraphene(self)
+
+    def nanotube_window(self, menuitem):
+        SetupNanotube(self)
+
+    def calculator_window(self, menuitem):
+        SetCalculator(self)
+        
+    def energy_window(self, menuitem):
+        EnergyForces(self)
+        
+    def energy_minimize_window(self, menuitem):
+        Minimize(self)
+
+    def scaling_window(self, menuitem):
+        HomogeneousDeformation(self)
+        
+    def new_atoms(self, atoms, init_magmom=False):
+        "Set a new atoms object."
+        self.notify_vulnerable()
+        self.reset_tools_modes()
+        
+        rpt = getattr(self.images, 'repeat', None)
+        self.images.repeat_images(np.ones(3, int))
+        self.images.initialize([atoms], init_magmom=init_magmom)
+        self.frame = 0   # Prevent crashes
+        self.images.repeat_images(rpt)
+        self.set_colors()
+        self.set_coordinates(frame=0, focus=True)
+
+    def prepare_new_atoms(self):
+        "Marks that the next call to append_atoms should clear the images."
+        self.images.prepare_new_atoms()
+        
+    def append_atoms(self, atoms):
+        "Set a new atoms object."
+        #self.notify_vulnerable()   # Do this manually after last frame.
+        frame = self.images.append_atoms(atoms)
+        self.set_coordinates(frame=frame-1, focus=True)
+
+    def notify_vulnerable(self):
+        """Notify windows that would break when new_atoms is called.
+
+        The notified windows may adapt to the new atoms.  If that is not
+        possible, they should delete themselves.
+        """
+        new_vul = []  # Keep weakrefs to objects that still exist.
+        for wref in self.vulnerable_windows:
+            ref = wref()
+            if ref is not None:
+                new_vul.append(wref)
+                ref.notify_atoms_changed()
+        self.vulnerable_windows = new_vul
+
+    def register_vulnerable(self, obj):
+        """Register windows that are vulnerable to changing the images.
+
+        Some windows will break if the atoms (and in particular the
+        number of images) are changed.  They can register themselves
+        and be closed when that happens.
+        """
+        self.vulnerable_windows.append(weakref.ref(obj))
+
+    def exit(self, button, event=None):
+        gtk.main_quit()
+        return True
+
+    def xxx(self, x=None,
+            message1='Not implemented!',
+            message2='do you really need it?'):
+        oops(message1, message2)
+        
+    def about(self, action):
+        try:
+            dialog = gtk.AboutDialog()
+        except AttributeError:
+            self.xxx()
+        else:
+            dialog.run()
+
+def webpage(widget):
+    import webbrowser
+    webbrowser.open('https://wiki.fysik.dtu.dk/ase/ase/gui.html')
+
Index: ase/gui/ag.py
===================================================================
--- ase/gui/ag.py	(revision 0)
+++ ase/gui/ag.py	(revision 1)
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+
+# Copyright 2008, 2009
+# CAMd (see accompanying license files for details).
+ 
+from optparse import OptionParser, SUPPRESS_HELP
+import ase.gui.gtkexcepthook
+#ase.gui.gtkexcepthook.feedback = "schiotz@fysik.dtu.dk"
+#ase.gui.gtkexcepthook.smtphost = "mail.fysik.dtu.dk"
+
+def build_parser():
+    parser = OptionParser(usage='%prog [options] [file[, file2, ...]]',
+                          version='%prog 0.1',
+                          description='See the online manual ' +
+                          '(https://wiki.fysik.dtu.dk/ase/ase/gui.html) ' +
+                          'for more information.')
+    parser.add_option('-n', '--image-number',
+                      default=':', metavar='NUMBER',
+                      help='Pick image(s) from trajectory.  NUMBER can be a '
+                      'single number (use a negative number to count from '
+                      'the back) or a range: start:stop:step, where the '
+                      '":step" part can be left out - default values are '
+                      '0:nimages:1.')
+    parser.add_option('-u', '--show-unit-cell', type='int',
+                      default=1, metavar='I',
+                      help="0: Don't show unit cell.  1: Show unit cell.  "
+                      '2: Show all of unit cell.')
+    parser.add_option('-r', '--repeat',
+                      default='1',
+                      help='Repeat unit cell.  Use "-r 2" or "-r 2,3,1".')
+    parser.add_option('-R', '--rotations', default='',
+                      help='Examples: "-R -90x", "-R 90z,-30x".')
+    parser.add_option('-o', '--output', metavar='FILE',
+                      help='Write configurations to FILE.')
+    parser.add_option('-g', '--graph',
+                      metavar='EXPR',
+                      help='Plot x,y1,y2,... graph from configurations or '
+                      'write data to sdtout in terminal mode.  Use the '
+                      'symbols: i, s, d, fmax, e, ekin, A, R, E and F.  See '
+                      'https://wiki.fysik.dtu.dk/ase/ase/gui.html#plotting-data '
+                      'for more details.')
+    parser.add_option('-t', '--terminal',
+                      action='store_true',
+                      default=False,
+                      help='Run in terminal window - no GUI.')
+    parser.add_option('--aneb',
+                      action='store_true',
+                      default=False,
+                      help='Read ANEB data.')
+    parser.add_option('--interpolate',
+                      type='int', metavar='N',
+                      help='Interpolate N images between 2 given images.')
+    parser.add_option('-b', '--bonds',
+                      action='store_true',
+                      default=False,
+                      help='Draw bonds between atoms.')
+    #parser.add_option('--read-pickled-data-from-file',
+    #                  type='string', help=SUPPRESS_HELP)
+    return parser
+
+def main():
+    parser = build_parser()
+    opt, args = parser.parse_args()
+
+    try:
+        import ase
+    except ImportError:
+        import sys
+        from os.path import dirname, join, pardir
+        sys.path.append(join(dirname(__file__), pardir))
+
+    from ase.gui.images import Images
+    from ase.atoms import Atoms
+
+    def run(opt, args):
+        images = Images()
+
+        if opt.aneb:
+            opt.image_number = '-1'
+
+        if len(args) > 0:
+            from ase.io import string2index
+            images.read(args, string2index(opt.image_number))
+        else:
+            images.initialize([Atoms()])
+
+        if opt.interpolate:
+            images.interpolate(opt.interpolate)
+
+        if opt.aneb:
+            images.aneb()
+
+        if opt.repeat != '1':
+            r = opt.repeat.split(',')
+            if len(r) == 1:
+                r = 3 * r
+            images.repeat_images([int(c) for c in r])
+
+        if opt.output is not None:
+            images.write(opt.output, rotations=opt.rotations,
+                        show_unit_cell=opt.show_unit_cell)
+            opt.terminal = True
+
+        if opt.terminal:
+            if opt.graph is not None:
+                data = images.graph(opt.graph)
+                for line in data.T:
+                    for x in line:
+                        print x,
+                    print
+        else:
+            from ase.gui.gui import GUI
+            gui = GUI(images, opt.rotations, opt.show_unit_cell, opt.bonds)
+            gui.run(opt.graph)
+
+    import traceback
+
+    try:
+        run(opt, args)
+    except KeyboardInterrupt:
+        pass
+    except Exception:
+        traceback.print_exc()
+        print """
+An exception occurred!  Please report the issue to
+ase-developers@listserv.fysik.dtu.dk - thanks!  Please also report this if
+it was a user error, so that a better error message can be provided
+next time."""
Index: ase/gui/calculator.py
===================================================================
--- ase/gui/calculator.py	(revision 0)
+++ ase/gui/calculator.py	(revision 1)
@@ -0,0 +1,1687 @@
+# encoding: utf-8
+"calculator.py - module for choosing a calculator."
+
+import gtk
+import os
+import numpy as np
+from copy import copy
+from ase.gui.setupwindow import SetupWindow
+from ase.gui.progress import DefaultProgressIndicator, GpawProgressIndicator
+from ase.gui.widgets import pack, oops, cancel_apply_ok
+from ase import Atoms
+from ase.data import chemical_symbols
+import ase
+
+# Asap and GPAW may be imported if selected.
+
+introtext = """\
+To make most calculations on the atoms, a Calculator object must first
+be associated with it.  ASE supports a number of calculators, supporting
+different elements, and implementing different physical models for the
+interatomic interactions.\
+"""
+
+# Informational text about the calculators
+lj_info_txt = """\
+The Lennard-Jones pair potential is one of the simplest
+possible models for interatomic interactions, mostly
+suitable for noble gasses and model systems.
+
+Interactions are described by an interaction length and an
+interaction strength.\
+"""
+
+emt_info_txt = """\
+The EMT potential is a many-body potential, giving a
+good description of the late transition metals crystalling
+in the FCC crystal structure.  The elements described by the
+main set of EMT parameters are Al, Ni, Cu, Pd, Ag, Pt, and
+Au, the Al potential is however not suitable for materials
+science application, as the stacking fault energy is wrong.
+
+A number of parameter sets are provided.
+
+<b>Default parameters:</b>
+
+The default EMT parameters, as published in K. W. Jacobsen,
+P. Stoltze and J. K. Nørskov, <i>Surf. Sci.</i> <b>366</b>, 394 (1996).
+
+<b>Alternative Cu, Ag and Au:</b>
+
+An alternative set of parameters for Cu, Ag and Au,
+reoptimized to experimental data including the stacking
+fault energies by Torben Rasmussen (partly unpublished).
+
+<b>Ruthenium:</b>
+
+Parameters for Ruthenium, as published in J. Gavnholt and
+J. Schiøtz, <i>Phys. Rev. B</i> <b>77</b>, 035404 (2008).
+
+<b>Metallic glasses:</b>
+
+Parameters for MgCu and CuZr metallic glasses. MgCu
+parameters are in N. P. Bailey, J. Schiøtz and
+K. W. Jacobsen, <i>Phys. Rev. B</i> <b>69</b>, 144205 (2004).
+CuZr in A. Paduraru, A. Kenoufi, N. P. Bailey and
+J. Schiøtz, <i>Adv. Eng. Mater.</i> <b>9</b>, 505 (2007).
+"""
+
+aseemt_info_txt = """\
+The EMT potential is a many-body potential, giving a
+good description of the late transition metals crystalling
+in the FCC crystal structure.  The elements described by the
+main set of EMT parameters are Al, Ni, Cu, Pd, Ag, Pt, and
+Au.  In addition, this implementation allows for the use of
+H, N, O and C adatoms, although the description of these is
+most likely not very good.
+
+<b>This is the ASE implementation of EMT.</b> For large
+simulations the ASAP implementation is more suitable; this
+implementation is mainly to make EMT available when ASAP is
+not installed.
+"""
+
+brenner_info_txt = """\
+The Brenner potential is a reactive bond-order potential for
+carbon and hydrocarbons.  As a bond-order potential, it takes
+into account that carbon orbitals can hybridize in different
+ways, and that carbon can form single, double and triple
+bonds.  That the potential is reactive means that it can
+handle gradual changes in the bond order as chemical bonds
+are formed or broken.
+
+The Brenner potential is implemented in Asap, based on a
+C implentation published at http://www.rahul.net/pcm/brenner/ .
+
+The potential is documented here:
+  Donald W Brenner, Olga A Shenderova, Judith A Harrison,
+  Steven J Stuart, Boris Ni and Susan B Sinnott:
+  "A second-generation reactive empirical bond order (REBO)
+  potential energy expression for hydrocarbons",
+  J. Phys.: Condens. Matter 14 (2002) 783-802.
+  doi: 10.1088/0953-8984/14/4/312
+"""
+
+
+gpaw_info_txt = """\
+GPAW implements Density Functional Theory using a
+<b>G</b>rid-based real-space representation of the wave
+functions, and the <b>P</b>rojector <b>A</b>ugmented <b>W</b>ave
+method for handling the core regions.  
+"""
+
+aims_info_txt = """\
+FHI-aims is an external package implementing density 
+functional theory and quantum chemical methods using 
+all-electron methods and a numeric local orbital basis set. 
+For full details, see http://www.fhi-berlin.mpg.de/aims/ 
+or Comp. Phys. Comm. v180 2175 (2009). The ASE 
+documentation contains information on the keywords and 
+functionalities available within this interface. 
+"""
+
+aims_pbc_warning_text = """\
+WARNING:
+Your system seems to have more than zero but less than 
+three periodic dimensions. Please check that this is 
+really what you want to compute. Assuming full 
+3D periodicity for this calculator."""
+
+vasp_info_txt = """\
+VASP is an external package implementing density 
+functional functional theory using pseudopotentials 
+or the projector-augmented wave method together 
+with a plane wave basis set. For full details, see
+http://cms.mpi.univie.ac.at/vasp/vasp/
+"""
+
+emt_parameters = (
+    ("Default (Al, Ni, Cu, Pd, Ag, Pt, Au)", None),
+    ("Alternative Cu, Ag and Au", "EMTRasmussenParameters"),
+    ("Ruthenium", "EMThcpParameters"),
+    ("CuMg and CuZr metallic glass", "EMTMetalGlassParameters")
+    )
+
+class SetCalculator(SetupWindow):
+    "Window for selecting a calculator."
+
+    # List the names of the radio button attributes
+    radios = ("none", "lj", "emt", "aseemt", "brenner", "gpaw", "aims", "vasp")
+    # List the names of the parameter dictionaries
+    paramdicts = ("lj_parameters",)
+    # The name used to store parameters on the gui object
+    classname = "SetCalculator"
+    
+    def __init__(self, gui):
+        SetupWindow.__init__(self)
+        self.set_title("Select calculator")
+        vbox = gtk.VBox()
+        
+        # Intoductory text
+        self.packtext(vbox, introtext)
+        
+        pack(vbox, [gtk.Label("Calculator:")])
+
+        # No calculator (the default)
+        self.none_radio = gtk.RadioButton(None, "None")
+        pack(vbox, [self.none_radio])
+
+        # Lennard-Jones
+        self.lj_radio = gtk.RadioButton(self.none_radio, "Lennard-Jones (ASAP)")
+        self.lj_setup = gtk.Button("Setup")
+        self.lj_info = InfoButton(lj_info_txt)
+        self.lj_setup.connect("clicked", self.lj_setup_window)
+        self.pack_line(vbox, self.lj_radio, self.lj_setup, self.lj_info)
+
+        # EMT
+        self.emt_radio = gtk.RadioButton(
+            self.none_radio, "EMT - Effective Medium Theory (ASAP)")
+        self.emt_setup = gtk.combo_box_new_text()
+        self.emt_param_info = {}
+        for p in emt_parameters:
+            self.emt_setup.append_text(p[0])
+            self.emt_param_info[p[0]] = p[1]
+        self.emt_setup.set_active(0)
+        self.emt_info = InfoButton(emt_info_txt)
+        self.pack_line(vbox, self.emt_radio, self.emt_setup, self.emt_info)
+
+        # EMT (ASE implementation)
+        self.aseemt_radio = gtk.RadioButton(
+            self.none_radio, "EMT - Effective Medium Theory (ASE)")
+        self.aseemt_info = InfoButton(aseemt_info_txt)
+        self.pack_line(vbox, self.aseemt_radio, None, self.aseemt_info)
+
+        # Brenner potential
+        self.brenner_radio = gtk.RadioButton(
+            self.none_radio, "Brenner Potential (ASAP)")
+        self.brenner_info = InfoButton(brenner_info_txt)
+        self.pack_line(vbox, self.brenner_radio, None, self.brenner_info)
+        
+        # GPAW
+        self.gpaw_radio = gtk.RadioButton(self.none_radio,
+                                          "Density Functional Theory (GPAW)")
+        self.gpaw_setup = gtk.Button("Setup")
+        self.gpaw_info = InfoButton(gpaw_info_txt)
+        self.gpaw_setup.connect("clicked", self.gpaw_setup_window)
+        self.pack_line(vbox, self.gpaw_radio, self.gpaw_setup, self.gpaw_info)
+        
+        # FHI-aims
+        self.aims_radio = gtk.RadioButton(self.none_radio, 
+                                          "Density Functional Theory (FHI-aims)")
+        self.aims_setup = gtk.Button("Setup")
+        self.aims_info = InfoButton(aims_info_txt)
+        self.aims_setup.connect("clicked", self.aims_setup_window)
+        self.pack_line(vbox, self.aims_radio, self.aims_setup, self.aims_info)
+        
+        # VASP
+        self.vasp_radio = gtk.RadioButton(self.none_radio, 
+                                          "Density Functional Theory (VASP)")
+        self.vasp_setup = gtk.Button("Setup")
+        self.vasp_info = InfoButton(vasp_info_txt)
+        self.vasp_setup.connect("clicked", self.vasp_setup_window)
+        self.pack_line(vbox, self.vasp_radio, self.vasp_setup, self.vasp_info)
+
+        # Buttons etc.
+        pack(vbox, gtk.Label(""))
+        buts = cancel_apply_ok(cancel=lambda widget: self.destroy(),
+                               apply=self.apply,
+                               ok=self.ok)
+        pack(vbox, [buts], end=True, bottom=True)
+        self.check = gtk.CheckButton("Check that the calculator is reasonable.")
+        self.check.set_active(True)
+        fr = gtk.Frame()
+        fr.add(self.check)
+        fr.show_all()
+        pack(vbox, [fr], end=True, bottom=True)
+        
+        # Finalize setup
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+        self.load_state()
+        
+    def pack_line(self, box, radio, setup, info):
+        hbox = gtk.HBox()
+        hbox.pack_start(radio, 0, 0)
+        hbox.pack_start(gtk.Label("  "), 0, 0)
+        hbox.pack_end(info, 0, 0)
+        if setup is not None:
+            radio.connect("toggled", self.radio_toggled, setup)
+            setup.set_sensitive(False)
+            hbox.pack_end(setup, 0, 0)
+        hbox.show_all()
+        box.pack_start(hbox, 0, 0)
+
+    def radio_toggled(self, radio, button):
+        button.set_sensitive(radio.get_active())
+
+    def lj_setup_window(self, widget):
+        if not self.get_atoms():
+            return
+        lj_param = getattr(self, "lj_parameters", None)
+        LJ_Window(self, lj_param, "lj_parameters")
+        # When control is retuned, self.lj_parameters has been set.
+        
+    def gpaw_setup_window(self, widget):
+        if not self.get_atoms():
+            return
+        gpaw_param = getattr(self, "gpaw_parameters", None)
+        GPAW_Window(self, gpaw_param, "gpaw_parameters")
+        # When control is retuned, self.gpaw_parameters has been set.
+        
+    def aims_setup_window(self, widget):
+        if not self.get_atoms():
+            return
+        aims_param = getattr(self, "aims_parameters", None)
+        AIMS_Window(self, aims_param, "aims_parameters")        
+        # When control is retuned, self.aims_parameters has been set.
+
+    def vasp_setup_window(self, widget):
+        if not self.get_atoms():
+            return
+        vasp_param = getattr(self, "vasp_parameters", None)
+        VASP_Window(self, vasp_param, "vasp_parameters")
+        # When control is retuned, self.vasp_parameters has been set.
+
+    def get_atoms(self):
+        "Make an atoms object from the active image"
+        images = self.gui.images
+        if images.natoms < 1:
+            oops("No atoms present")
+            return False
+        self.atoms = Atoms(positions=images.P[0], symbols=images.Z,
+                           cell=images.A[0], pbc=images.pbc)
+        if not images.dynamic.all(): 
+            from ase.constraints import FixAtoms
+            self.atoms.set_constraint(FixAtoms(mask=1-images.dynamic))
+        return True
+
+    def apply(self, *widget):
+        if self.do_apply():
+            self.save_state()
+            return True
+        else:
+            return False
+        
+    def do_apply(self):
+        nochk = not self.check.get_active()
+        self.gui.simulation["progress"] = DefaultProgressIndicator()
+        if self.none_radio.get_active():
+            self.gui.simulation['calc'] = None
+            return True
+        elif self.lj_radio.get_active():
+            if nochk or self.lj_check():
+                self.choose_lj()
+                return True
+        elif self.emt_radio.get_active():
+            if nochk or self.emt_check():
+                self.choose_emt()
+                return True
+        elif self.aseemt_radio.get_active():
+            if nochk or self.aseemt_check():
+                self.choose_aseemt()
+                return True
+        elif self.brenner_radio.get_active():
+            if nochk or self.brenner_check():
+                self.choose_brenner()
+                return True
+        elif self.gpaw_radio.get_active():
+            if nochk or self.gpaw_check():
+                self.choose_gpaw()
+                return True
+        elif self.aims_radio.get_active():
+            if nochk or self.aims_check():
+                self.choose_aims()
+                return True
+        elif self.vasp_radio.get_active():
+            if nochk or self.vasp_check():
+                self.choose_vasp()
+                return True  
+        return False
+
+    def ok(self, *widget):
+        if self.apply():
+            self.destroy()
+
+    def save_state(self):
+        state = {}
+        for r in self.radios:
+            radiobutton = getattr(self, r+"_radio")
+            if radiobutton.get_active():
+                state["radio"] = r
+        state["emtsetup"] = self.emt_setup.get_active()
+        state["check"] = self.check.get_active()
+        for p in self.paramdicts:
+            if hasattr(self, p):
+                state[p] = getattr(self, p)
+        self.gui.module_state[self.classname] = state
+
+    def load_state(self):
+        try:
+            state = self.gui.module_state[self.classname]
+        except KeyError:
+            return
+        r = state["radio"]
+        radiobutton = getattr(self, r+"_radio")
+        radiobutton.set_active(True)
+        self.emt_setup.set_active(state["emtsetup"])
+        self.check.set_active(state["check"])
+        for p in self.paramdicts:
+            if state.has_key(p):
+                setattr(self, p, state[p])
+            
+    def lj_check(self):
+        try:
+            import asap3
+        except ImportError:
+            oops("ASAP is not installed. (Failed to import asap3)")
+            return False
+        if not hasattr(self, "lj_parameters"):
+            oops("You must set up the Lennard-Jones parameters")
+            return False
+        try:
+            self.atoms.set_calculator(asap3.LennardJones(**self.lj_parameters))
+        except (asap3.AsapError, TypeError, ValueError), e:
+            oops("Could not create useful Lennard-Jones calculator.",
+                 str(e))
+            return False
+        return True
+
+    def choose_lj(self):
+        # Define a function on the fly!
+        import asap3
+        def lj_factory(p=self.lj_parameters, lj=asap3.LennardJones):
+            return lj(**p)
+        self.gui.simulation["calc"] = lj_factory
+
+    def emt_get(self):
+        import asap3
+        provider_name = self.emt_setup.get_active_text()
+        provider =  self.emt_param_info[provider_name]
+        if provider is not None:
+            provider = getattr(asap3, provider)
+        return (asap3.EMT, provider, asap3)
+                                      
+    def emt_check(self):
+        if not self.get_atoms():
+            return False
+        try:
+            emt, provider, asap3 = self.emt_get()
+        except ImportError:
+            oops("ASAP is not installed. (Failed to import asap3)")
+            return False
+        try:
+            if provider is not None:
+                self.atoms.set_calculator(emt(provider()))
+            else:
+                self.atoms.set_calculator(emt())
+        except (asap3.AsapError, TypeError, ValueError), e:
+            oops("Could not attach EMT calculator to the atoms.",
+                 str(e))
+            return False
+        return True
+
+    def choose_emt(self):
+        emt, provider, asap3 = self.emt_get()
+        if provider is None:
+            emt_factory = emt
+        else:
+            def emt_factory(emt=emt, prov=provider):
+                return emt(prov())
+        self.gui.simulation["calc"] = emt_factory
+
+    def aseemt_check(self):
+        return self.element_check("ASE EMT", ['H', 'Al', 'Cu', 'Ag', 'Au',
+                                              'Ni', 'Pd', 'Pt', 'C', 'N', 'O'])
+
+    def choose_aseemt(self):
+        self.gui.simulation["calc"] = ase.EMT
+        ase.EMT.disabled = False  # In case Asap has been imported.
+
+    def brenner_check(self):
+        try:
+            import asap3
+        except ImportError:
+            oops("ASAP is not installed. (Failed to import asap3)")
+            return False
+        return self.element_check("Brenner potential", ['H', 'C', 'Si'])
+
+    def choose_brenner(self):
+        import asap3
+        self.gui.simulation["calc"] = asap3.BrennerPotential
+
+    def choose_aseemt(self):
+        self.gui.simulation["calc"] = ase.EMT
+        ase.EMT.disabled = False  # In case Asap has been imported.
+
+    def gpaw_check(self):
+        try:
+            import gpaw
+        except ImportError:
+            oops("GPAW is not installed. (Failed to import gpaw)")
+            return False
+        if not hasattr(self, "gpaw_parameters"):
+            oops("You must set up the GPAW parameters")
+            return False
+        return True
+
+    def choose_gpaw(self):
+        # This reuses the same GPAW object.
+        try:
+            import gpaw
+        except ImportError:
+            oops("GPAW is not installed. (Failed to import gpaw)")
+            return False
+        p = self.gpaw_parameters
+        use = ["xc", "kpts", "mode"]
+        if p["use_h"]:
+            use.append("h")
+        else:
+            use.append("gpts")
+        if p["mode"] == "lcao":
+            use.append("basis")
+        gpaw_param = {}
+        for s in use:
+            gpaw_param[s] = p[s]
+        if p["use mixer"]:
+            mx = getattr(gpaw, p["mixer"])
+            mx_args = {}
+            mx_arg_n = ["beta", "nmaxold", "weight"]
+            if p["mixer"] == "MixerDiff":
+                mx_arg_n.extend(["beta_m", "nmaxold_m", "weight_m"])
+            for s in mx_arg_n:
+                mx_args[s] = p[s]
+            gpaw_param["mixer"] = mx(**mx_args)
+        progress = GpawProgressIndicator()
+        self.gui.simulation["progress"] = progress
+        gpaw_param["txt"] = progress.get_gpaw_stream()
+        gpaw_calc = gpaw.GPAW(**gpaw_param)
+        def gpaw_factory(calc = gpaw_calc):
+            return calc
+        self.gui.simulation["calc"] = gpaw_factory
+                
+    def aims_check(self):
+        if not hasattr(self, "aims_parameters"):
+            oops("You must set up the FHI-aims parameters")
+            return False
+        return True
+
+    def choose_aims(self):
+        param = self.aims_parameters
+        from ase.calculators.aims import Aims
+        calc_aims = Aims(**param)
+        def aims_factory(calc = calc_aims):
+            return calc
+        self.gui.simulation["calc"] = aims_factory
+
+    def vasp_check(self):
+        if not hasattr(self, "vasp_parameters"):
+            oops("You must set up the VASP parameters")
+            return False
+        return True
+
+    def choose_vasp(self):
+        param = self.vasp_parameters
+        from ase.calculators.vasp import Vasp
+        calc_vasp = Vasp(**param)
+        def vasp_factory(calc = calc_vasp):
+            return calc
+        self.gui.simulation["calc"] = vasp_factory
+
+    def element_check(self, name, elements):
+        "Check that all atoms are allowed"
+        elements = [ase.data.atomic_numbers[s] for s in elements]
+        elements_dict = {}
+        for e in elements:
+            elements_dict[e] = True
+        if not self.get_atoms():
+            return False
+        try:
+            for e in self.atoms.get_atomic_numbers():
+                elements_dict[e]
+        except KeyError:
+            oops("Element %s is not allowed by the '%s' calculator"
+                 % (ase.data.chemical_symbols[e], name))
+            return False
+        return True
+    
+class InfoButton(gtk.Button):
+    def __init__(self, txt):
+        gtk.Button.__init__(self, "Info")
+        self.txt = txt
+        self.connect('clicked', self.run)
+
+    def run(self, widget):
+        dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
+                                   type=gtk.MESSAGE_INFO,
+                                   buttons=gtk.BUTTONS_CLOSE)
+        dialog.set_markup(self.txt)
+        dialog.connect('response', lambda x, y: dialog.destroy())
+        dialog.show()
+
+
+class LJ_Window(gtk.Window):
+    def __init__(self, owner, param, attrname):
+        gtk.Window.__init__(self)
+        self.set_title("Lennard-Jones parameters")
+        self.owner = owner
+        self.attrname = attrname
+        atoms = owner.atoms
+        atnos = atoms.get_atomic_numbers()
+        found = {}
+        for z in atnos:
+            found[z] = True
+        self.present = found.keys()
+        self.present.sort()  # Sorted list of atomic numbers
+        nelem = len(self.present)
+        vbox = gtk.VBox()
+        label = gtk.Label("Specify the Lennard-Jones parameters here")
+        pack(vbox, [label])
+        pack(vbox, gtk.Label(""))
+        pack(vbox, [gtk.Label("Epsilon (eV):")])
+        tbl, self.epsilon_adj = self.makematrix(self.present)
+        pack(vbox, [tbl])
+        pack(vbox, gtk.Label(""))
+        pack(vbox, [gtk.Label("Sigma (Å):")])
+        tbl, self.sigma_adj = self.makematrix(self.present)
+        pack(vbox, [tbl])
+        self.modif = gtk.CheckButton("Shift to make smooth at cutoff")
+        self.modif.set_active(True)
+        pack(vbox, gtk.Label(""))
+        pack(vbox, self.modif)
+        pack(vbox, gtk.Label(""))
+        butbox = gtk.HButtonBox()
+        cancel_but = gtk.Button(stock=gtk.STOCK_CANCEL)
+        cancel_but.connect('clicked', lambda widget: self.destroy())
+        ok_but = gtk.Button(stock=gtk.STOCK_OK)
+        ok_but.connect('clicked', self.ok)
+        butbox.pack_start(cancel_but, 0, 0)
+        butbox.pack_start(ok_but, 0, 0)
+        butbox.show_all()
+        pack(vbox, [butbox], end=True, bottom=True)
+        vbox.show()
+        self.add(vbox)
+
+        # Now, set the parameters
+        if param and param['elements'] == self.present:
+            self.set_param(self.epsilon_adj, param["epsilon"], nelem)
+            self.set_param(self.sigma_adj, param["sigma"], nelem)
+            self.modif.set_active(param["modified"])
+
+        self.show()
+        self.grab_add()  # Lock all other windows
+        
+    def makematrix(self, present):
+        nelem = len(present)
+        adjdict = {}
+        tbl = gtk.Table(2+nelem, 2+nelem)
+        for i in range(nelem):
+            s = chemical_symbols[present[i]]
+            tbl.attach(gtk.Label(" " + str(present[i])), 0, 1, i, i+1)
+            tbl.attach(gtk.Label("  "+s+" "), 1, 2, i, i+1)
+            tbl.attach(gtk.Label(str(present[i])), i+2, i+3, 1+nelem, 2+nelem)
+            tbl.attach(gtk.Label(s), i+2, i+3, nelem, 1+nelem)
+            for j in range(i+1):
+                adj = gtk.Adjustment(1.0, 0.0, 100.0, 0.1)
+                spin = gtk.SpinButton(adj, 0.1, 3)
+                tbl.attach(spin, 2+j, 3+j, i, i+1)
+                adjdict[(i,j)] = adj
+        tbl.show_all()
+        return tbl, adjdict
+    
+    def set_param(self, adj, params, n):
+        for i in range(n):
+            for j in range(n):
+                if j <= i:
+                    adj[(i,j)].value = params[i,j]
+
+    def get_param(self, adj, params, n):
+        for i in range(n):
+            for j in range(n):
+                if j <= i:
+                    params[i,j] = params[j,i] = adj[(i,j)].value
+
+
+    def destroy(self):
+        self.grab_remove()
+        gtk.Window.destroy(self)
+
+    def ok(self, *args):
+        params = {}
+        params["elements"] = copy(self.present)
+        n = len(self.present)
+        eps = np.zeros((n,n))
+        self.get_param(self.epsilon_adj, eps, n)
+        sigma = np.zeros((n,n))
+        self.get_param(self.sigma_adj, sigma, n)
+        params["epsilon"] = eps
+        params["sigma"] = sigma
+        params["modified"] = self.modif.get_active()
+        setattr(self.owner, self.attrname, params)
+        self.destroy()
+
+
+class GPAW_Window(gtk.Window):
+    gpaw_xc_list = ['LDA', 'PBE', 'RPBE', 'revPBE']
+    gpaw_xc_default = 'PBE'
+    def __init__(self, owner, param, attrname):
+        gtk.Window.__init__(self)
+        self.set_title("GPAW parameters")
+        self.owner = owner
+        self.attrname = attrname
+        atoms = owner.atoms
+        self.ucell = atoms.get_cell()
+        self.size = tuple([self.ucell[i,i] for i in range(3)])
+        self.pbc = atoms.get_pbc()
+        self.orthogonal = self.isorthogonal(self.ucell)
+        self.natoms = len(atoms)
+        
+        vbox = gtk.VBox()
+        #label = gtk.Label("Specify the GPAW parameters here")
+        #pack(vbox, [label])
+
+        # Print some info
+        txt = "%i atoms.\n" % (self.natoms,)
+        if self.orthogonal:
+            txt += "Orthogonal unit cell: %.2f x %.2f x %.2f Å." % self.size
+        else:
+            txt += "Non-orthogonal unit cell:\n"
+            txt += str(self.ucell)
+        pack(vbox, [gtk.Label(txt)])
+        
+        # XC potential
+        self.xc = gtk.combo_box_new_text()
+        for i, x in enumerate(self.gpaw_xc_list):
+            self.xc.append_text(x)
+            if x == self.gpaw_xc_default:
+                self.xc.set_active(i)
+        pack(vbox, [gtk.Label("Exchange-correlation functional: "),
+                    self.xc])
+        
+        # Grid spacing
+        self.radio_h = gtk.RadioButton(None, "Grid spacing")
+        self.h = gtk.Adjustment(0.18, 0.0, 1.0, 0.01)
+        self.h_spin = gtk.SpinButton(self.h, 0, 2)
+        pack(vbox, [self.radio_h, gtk.Label(" h = "), self.h_spin,
+                    gtk.Label("Å")])
+        self.radio_gpts = gtk.RadioButton(self.radio_h, "Grid points")
+        self.gpts = []
+        self.gpts_spin = []
+        for i in range(3):
+            g = gtk.Adjustment(4, 4, 1000, 4)
+            s = gtk.SpinButton(g, 0, 0)
+            self.gpts.append(g)
+            self.gpts_spin.append(s)
+        self.gpts_hlabel = gtk.Label("")
+        self.gpts_hlabel_format = "h<sub>eff</sub> = (%.3f, %.3f, %.3f) Å"
+        pack(vbox, [self.radio_gpts, gtk.Label(" gpts = ("), self.gpts_spin[0],
+                    gtk.Label(", "), self.gpts_spin[1], gtk.Label(", "),
+                    self.gpts_spin[2], gtk.Label(")  "), self.gpts_hlabel])
+        self.radio_h.connect("toggled", self.radio_grid_toggled)
+        self.radio_gpts.connect("toggled", self.radio_grid_toggled)
+        self.radio_grid_toggled(None)
+        for g in self.gpts:
+            g.connect("value-changed", self.gpts_changed)
+        self.h.connect("value-changed", self.h_changed)
+        
+        # K-points
+        self.kpts = []
+        self.kpts_spin = []
+        for i in range(3):
+            if self.pbc[i] and self.orthogonal:
+                default = np.ceil(20.0 / self.size[i])
+            else:
+                default = 1
+            g = gtk.Adjustment(default, 1, 100, 1)
+            s = gtk.SpinButton(g, 0, 0)
+            self.kpts.append(g)
+            self.kpts_spin.append(s)
+            if not self.pbc[i]:
+                s.set_sensitive(False)
+            g.connect("value-changed", self.k_changed)
+        pack(vbox, [gtk.Label("k-points  k = ("), self.kpts_spin[0],
+                    gtk.Label(", "), self.kpts_spin[1], gtk.Label(", "),
+                    self.kpts_spin[2], gtk.Label(")")])
+        self.kpts_label = gtk.Label("")
+        self.kpts_label_format = "k-points x size:  (%.1f, %.1f, %.1f) Å"
+        pack(vbox, [self.kpts_label])
+        self.k_changed()
+        
+        # Spin polarized
+        self.spinpol = gtk.CheckButton("Spin polarized")
+        pack(vbox, [self.spinpol])
+        pack(vbox, gtk.Label(""))
+
+        # Mode and basis functions
+        self.mode = gtk.combo_box_new_text()
+        self.mode.append_text("FD - Finite Difference (grid) mode")
+        self.mode.append_text("LCAO - Linear Combination of Atomic Orbitals")
+        self.mode.set_active(0)
+        pack(vbox, [gtk.Label("Mode: "), self.mode])
+        self.basis = gtk.combo_box_new_text()
+        self.basis.append_text("sz - Single Zeta")
+        self.basis.append_text("szp - Single Zeta polarized")
+        self.basis.append_text("dzp - Double Zeta polarized")
+        self.basis.set_active(2) # dzp
+        pack(vbox, [gtk.Label("Basis functions: "), self.basis])
+        pack(vbox, gtk.Label(""))
+        self.mode.connect("changed", self.mode_changed)
+        self.mode_changed()
+        
+        # Mixer
+        self.use_mixer = gtk.CheckButton("Non-standard mixer parameters")
+        pack(vbox, [self.use_mixer])
+        self.radio_mixer = gtk.RadioButton(None, "Mixer   ")
+        self.radio_mixersum = gtk.RadioButton(self.radio_mixer, "MixerSum   ")
+        self.radio_mixerdiff = gtk.RadioButton(self.radio_mixer, "MixerDiff")
+        pack(vbox, [self.radio_mixer, self.radio_mixersum,
+                    self.radio_mixerdiff])
+        self.beta_adj = gtk.Adjustment(0.25, 0.0, 1.0, 0.05)
+        self.beta_spin = gtk.SpinButton(self.beta_adj, 0, 2)
+        self.nmaxold_adj = gtk.Adjustment(3, 1, 10, 1)
+        self.nmaxold_spin = gtk.SpinButton(self.nmaxold_adj, 0, 0)
+        self.weight_adj = gtk.Adjustment(50, 1, 500, 1)
+        self.weight_spin = gtk.SpinButton(self.weight_adj, 0, 0)
+        pack(vbox, [gtk.Label("beta = "), self.beta_spin,
+                    gtk.Label("  nmaxold = "), self.nmaxold_spin,
+                    gtk.Label("  weight = "), self.weight_spin])
+        self.beta_m_adj = gtk.Adjustment(0.70, 0.0, 1.0, 0.05)
+        self.beta_m_spin = gtk.SpinButton(self.beta_m_adj, 0, 2)
+        self.nmaxold_m_adj = gtk.Adjustment(2, 1, 10, 1)
+        self.nmaxold_m_spin = gtk.SpinButton(self.nmaxold_m_adj, 0, 0)
+        self.weight_m_adj = gtk.Adjustment(10, 1, 500, 1)
+        self.weight_m_spin = gtk.SpinButton(self.weight_m_adj, 0, 0)
+        pack(vbox, [gtk.Label("beta_m = "), self.beta_m_spin,
+                    gtk.Label("  nmaxold_m = "), self.nmaxold_m_spin,
+                    gtk.Label("  weight_m = "), self.weight_m_spin])
+        for but in (self.spinpol, self.use_mixer, self.radio_mixer,
+                    self.radio_mixersum, self.radio_mixerdiff):
+            but.connect("clicked", self.mixer_changed)
+        self.mixer_changed()
+        
+        # Eigensolver
+        # Poisson-solver
+        
+        vbox.show()
+        self.add(vbox)
+
+        # Buttons at the bottom
+        pack(vbox, gtk.Label(""))
+        butbox = gtk.HButtonBox()
+        cancel_but = gtk.Button(stock=gtk.STOCK_CANCEL)
+        cancel_but.connect('clicked', lambda widget: self.destroy())
+        ok_but = gtk.Button(stock=gtk.STOCK_OK)
+        ok_but.connect('clicked', self.ok)
+        butbox.pack_start(cancel_but, 0, 0)
+        butbox.pack_start(ok_but, 0, 0)
+        butbox.show_all()
+        pack(vbox, [butbox], end=True, bottom=True)
+
+        # Set stored parameters
+        if param:
+            self.xc.set_active(param["xc#"])
+            if param["use_h"]:
+                self.radio_h.set_active(True)
+            else:
+                self.radio_gpts.set_active(True)
+            for i in range(3):
+                self.gpts[i].value = param["gpts"][i]
+                self.kpts[i].value = param["kpts"][i]
+            self.spinpol.set_active(param["spinpol"])
+            self.mode.set_active(param["mode#"])
+            self.basis.set_active(param["basis#"])
+            self.use_mixer.set_active(param["use mixer"])
+            getattr(self, "radio_"+param["mixer"].lower()).set_active(True)
+            for t in ("beta", "nmaxold", "weight", "beta_m", "nmaxold_m",
+                      "weight_m"):                    
+                getattr(self, t+"_adj").value = param[t]
+
+        self.show()
+        self.grab_add()  # Lock all other windows
+
+    def radio_grid_toggled(self, widget):
+        hmode = self.radio_h.get_active()
+        self.h_spin.set_sensitive(hmode)
+        for s in self.gpts_spin:
+            s.set_sensitive(not hmode)
+        self.gpts_changed()
+
+    def gpts_changed(self, *args):
+        if self.radio_gpts.get_active():
+            g = np.array([int(g.value) for g in self.gpts])
+            size = np.array([self.ucell[i,i] for i in range(3)])
+            txt = self.gpts_hlabel_format % tuple(size / g)
+            self.gpts_hlabel.set_markup(txt)
+        else:
+            self.gpts_hlabel.set_markup("")
+
+    def h_changed(self, *args):
+        h = self.h.value
+        for i in range(3):
+            g = 4 * round(self.ucell[i,i] / (4*h))
+            self.gpts[i].value = g
+
+    def k_changed(self, *args):
+        if self.orthogonal:
+            size = [self.kpts[i].value * self.size[i] for i in range(3)]
+        self.kpts_label.set_text(self.kpts_label_format % tuple(size))
+
+    def mode_changed(self, *args):
+        self.basis.set_sensitive(self.mode.get_active() == 1)
+
+    def mixer_changed(self, *args):
+        radios = (self.radio_mixer, self.radio_mixersum, self.radio_mixerdiff)
+        spin1 = (self.beta_spin, self.nmaxold_spin, self.weight_spin)
+        spin2 = (self.beta_m_spin, self.nmaxold_m_spin, self.weight_m_spin)
+        if self.use_mixer.get_active():
+            # Mixer parameters can be specified.
+            if self.spinpol.get_active():
+                self.radio_mixer.set_sensitive(False)
+                self.radio_mixersum.set_sensitive(True)
+                self.radio_mixerdiff.set_sensitive(True)
+                if self.radio_mixer.get_active():
+                    self.radio_mixersum.set_active(True)
+            else:
+                self.radio_mixer.set_sensitive(True)
+                self.radio_mixersum.set_sensitive(False)
+                self.radio_mixerdiff.set_sensitive(False)
+                self.radio_mixer.set_active(True)
+            if self.radio_mixerdiff.get_active():
+                active = spin1 + spin2
+                passive = ()
+            else:
+                active = spin1
+                passive = spin2
+            for widget in active:
+                widget.set_sensitive(True)
+            for widget in passive:
+                widget.set_sensitive(False)
+        else:
+            # No mixer parameters
+            for widget in radios + spin1 + spin2:
+                widget.set_sensitive(False)
+                
+    def isorthogonal(self, matrix):
+        ortho = True
+        for i in range(3):
+            for j in range(3):
+                if i != j and matrix[i][j] != 0.0:
+                    ortho = False
+        return ortho
+
+    def ok(self, *args):
+        param = {}
+        param["xc"] = self.xc.get_active_text()
+        param["xc#"] = self.xc.get_active()
+        param["use_h"] = self.radio_h.get_active()
+        param["h"] = self.h.value
+        param["gpts"] = [int(g.value) for g in self.gpts]
+        param["kpts"] = [int(k.value) for k in self.kpts]
+        param["spinpol"] = self.spinpol.get_active()
+        param["mode"] = self.mode.get_active_text().split()[0].lower()
+        param["mode#"] = self.mode.get_active()
+        param["basis"] = self.basis.get_active_text().split()[0].lower()
+        param["basis#"] = self.basis.get_active()
+        param["use mixer"] = self.use_mixer.get_active()
+        if self.radio_mixer.get_active():
+            m = "Mixer"
+        elif self.radio_mixersum.get_active():
+            m = "MixerSum"
+        else:
+            assert self.radio_mixerdiff.get_active()
+            m = "MixerDiff"
+        param["mixer"] = m
+        for t in ("beta", "nmaxold", "weight", "beta_m", "nmaxold_m",
+                  "weight_m"):
+            param[t] = getattr(self, t+"_adj").value
+        setattr(self.owner, self.attrname, param)
+        self.destroy()
+
+class AIMS_Window(gtk.Window):
+    aims_xc_cluster = ['pw-lda','pz-lda','pbe','pbesol','rpbe','revpbe',
+                    'blyp','am05','b3lyp','hse03','hse06','pbe0','pbesol0',
+                    'hf','mp2']
+    aims_xc_periodic = ['pw-lda','pz-lda','pbe','pbesol','rpbe','revpbe',
+                        'blyp','am05']
+    aims_xc_default = 'pbe'
+    aims_relativity_list = ['none','atomic_zora','zora']
+    aims_keyword_gui_list = ['xc','vdw_correction_hirshfeld','k_grid','spin','charge','relativistic',
+                             'sc_accuracy_etot','sc_accuracy_eev','sc_accuracy_rho','sc_accuracy_forces',
+                             'compute_forces','run_command','species_dir']
+    def __init__(self, owner, param, attrname):
+        self.owner = owner
+        self.attrname = attrname
+        atoms = owner.atoms
+        self.periodic = atoms.get_pbc().all()
+        if not self.periodic and atoms.get_pbc().any():
+            aims_periodic_warning = True
+            self.periodic = True 
+        else:
+            aims_periodic_warning = False
+        from ase.calculators.aims import float_keys,exp_keys,string_keys,int_keys,bool_keys,list_keys,input_keys
+        self.aims_keyword_list =float_keys+exp_keys+string_keys+int_keys+bool_keys+list_keys+input_keys
+        self.expert_keywords = []
+
+        natoms = len(atoms)
+        gtk.Window.__init__(self)
+        self.set_title("FHI-aims parameters")
+        vbox = gtk.VBox()
+        # Print some info
+        txt = "%i atoms.\n" % (natoms)
+        if self.periodic:
+            self.ucell = atoms.get_cell()
+            txt += "Periodic geometry, unit cell is: \n"
+            for i in range(3):
+                txt += "(%8.3f %8.3f %8.3f)\n" % (self.ucell[i][0], self.ucell[i][1], self.ucell[i][2])
+            self.xc_list = self.aims_xc_periodic
+        else:
+            txt += "Non-periodic geometry. \n"
+            self.xc_list = self.aims_xc_cluster
+        pack(vbox, [gtk.Label(txt)])
+
+        # XC functional & dispersion correction
+        self.xc = gtk.combo_box_new_text()
+        self.xc_setup = False
+        self.TS = gtk.CheckButton("Hirshfeld-based dispersion correction")
+        pack(vbox, [gtk.Label("Exchange-correlation functional: "),self.xc])
+        pack(vbox, [self.TS])
+        pack(vbox, [gtk.Label("")])
+        
+        # k-grid?
+        if self.periodic:
+            self.kpts = []
+            self.kpts_spin = []
+            for i in range(3):
+                default = np.ceil(20.0 / np.sqrt(np.vdot(self.ucell[i],self.ucell[i])))
+                g = gtk.Adjustment(default, 1, 100, 1)
+                s = gtk.SpinButton(g, 0, 0)
+                self.kpts.append(g)
+                self.kpts_spin.append(s)
+                g.connect("value-changed", self.k_changed)
+            pack(vbox, [gtk.Label("k-points  k = ("), self.kpts_spin[0],
+                        gtk.Label(", "), self.kpts_spin[1], gtk.Label(", "),
+                        self.kpts_spin[2], gtk.Label(")")])
+            self.kpts_label = gtk.Label("")
+            self.kpts_label_format = "k-points x size:  (%.1f, %.1f, %.1f) Å"
+            pack(vbox, [self.kpts_label])
+            self.k_changed()
+            pack(vbox, gtk.Label(""))
+
+        # Spin polarized, charge, relativity
+        self.spinpol = gtk.CheckButton("Spin polarized")
+        self.charge  = gtk.Adjustment(0,-100,100,0.1)
+        self.charge_spin = gtk.SpinButton(self.charge, 0, 0)
+        self.charge_spin.set_digits(2)
+        self.relativity_type = gtk.combo_box_new_text()
+        for i, x in enumerate(self.aims_relativity_list):
+            self.relativity_type.append_text(x)
+        self.relativity_type.connect('changed',self.relativity_changed)
+        self.relativity_threshold = gtk.Entry(max=8)
+        self.relativity_threshold.set_text('1.00e-12')
+        self.relativity_threshold.set_sensitive(False)
+        pack(vbox, [self.spinpol, 
+                    gtk.Label("   Charge"), 
+                    self.charge_spin, 
+                    gtk.Label("   Relativity"),
+                    self.relativity_type,
+                    gtk.Label(" Threshold"),
+                    self.relativity_threshold])
+        pack(vbox, gtk.Label(""))
+
+        # self-consistency criteria
+        pack(vbox,[gtk.Label("Self-consistency convergence:")])
+        self.sc_tot_energy      = gtk.Adjustment(1e-6, 1e-6, 1e0, 1e-6)
+        self.sc_tot_energy_spin = gtk.SpinButton(self.sc_tot_energy, 0, 0)
+        self.sc_tot_energy_spin.set_digits(6)
+        self.sc_tot_energy_spin.set_numeric(True)
+        self.sc_sum_eigenvalue      = gtk.Adjustment(1e-3, 1e-6, 1e0, 1e-6)
+        self.sc_sum_eigenvalue_spin = gtk.SpinButton(self.sc_sum_eigenvalue, 0, 0)
+        self.sc_sum_eigenvalue_spin.set_digits(6)
+        self.sc_sum_eigenvalue_spin.set_numeric(True)
+        self.sc_density      = gtk.Adjustment(1e-4, 1e-6, 1e0, 1e-6)
+        self.sc_density_spin = gtk.SpinButton(self.sc_density, 0, 0)
+        self.sc_density_spin.set_digits(6)
+        self.sc_density_spin.set_numeric(True)
+        self.compute_forces = gtk.CheckButton("Compute forces")
+        self.compute_forces.set_active(True)
+        self.compute_forces.connect("toggled", self.compute_forces_toggled,"")
+        self.sc_forces      = gtk.Adjustment(1e-4, 1e-6, 1e0, 1e-6)
+        self.sc_forces_spin = gtk.SpinButton(self.sc_forces, 0, 0)
+        self.sc_forces_spin.set_numeric(True)
+        self.sc_forces_spin.set_digits(6)
+        pack(vbox, [gtk.Label("Energy:                 "),
+                    self.sc_tot_energy_spin, 
+                    gtk.Label(" eV   Sum of eigenvalues:  "),
+                    self.sc_sum_eigenvalue_spin,
+                    gtk.Label(" eV")])
+        pack(vbox, [gtk.Label("Electron density: "),
+                    self.sc_density_spin,
+                    gtk.Label("        Force convergence:  "),
+                    self.sc_forces_spin,
+                    gtk.Label(" eV/Ang  ")])
+
+        pack(vbox, [self.compute_forces])
+        pack(vbox, gtk.Label(""))
+
+        self.expert_keyword_set = gtk.Entry(max = 55)
+        self.expert_keyword_add = gtk.Button(stock = gtk.STOCK_ADD)
+        self.expert_keyword_add.connect("clicked", self.expert_keyword_import)
+        self.expert_keyword_set.connect("activate", self.expert_keyword_import)
+        pack(vbox,[gtk.Label("Additional keywords: "),
+                   self.expert_keyword_set, 
+                   self.expert_keyword_add])
+        self.expert_vbox = gtk.VBox()
+        pack(vbox, self.expert_vbox)
+        pack(vbox, gtk.Label(""))
+
+        # run command and species defaults:
+        pack(vbox, gtk.Label('FHI-aims execution command: '))
+        self.run_command = pack(vbox, gtk.Entry(max=0))
+        pack(vbox, gtk.Label('Directory for species defaults: '))
+        self.species_defaults = pack(vbox, gtk.Entry(max=0))
+
+        # set defaults from previous instance of the calculator, if applicable:
+        if param is not None:
+            self.set_param(param)
+        else:
+            self.set_defaults()
+
+        # Buttons at the bottom
+        pack(vbox, gtk.Label(""))
+        butbox = gtk.HButtonBox()
+        default_but = gtk.Button("Set Defaults")
+        default_but.connect("clicked",self.set_defaults)
+        import_control_but = gtk.Button("Import control.in")
+        import_control_but.connect("clicked",self.import_control)
+        export_control_but = gtk.Button("Export control.in")
+        export_control_but.connect("clicked", self.export_control)
+        cancel_but = gtk.Button(stock=gtk.STOCK_CANCEL)
+        cancel_but.connect('clicked', lambda widget: self.destroy())
+        ok_but = gtk.Button(stock=gtk.STOCK_OK)
+        ok_but.connect('clicked', self.ok)
+        butbox.pack_start(default_but, 0, 0)
+        butbox.pack_start(import_control_but, 0, 0)
+        butbox.pack_start(export_control_but, 0, 0)
+        butbox.pack_start(cancel_but, 0, 0)
+        butbox.pack_start(ok_but, 0, 0)
+        butbox.show_all()
+        pack(vbox, [butbox], end=True, bottom=True)
+        self.expert_vbox.show()
+        vbox.show()
+        self.add(vbox)
+        self.show()
+        self.grab_add() 
+        if aims_periodic_warning:
+            oops(aims_pbc_warning_text)
+
+    def set_defaults(self, *args):
+        atoms = self.owner.atoms.copy()
+        if not self.xc_setup:
+            self.xc_setup = True 
+            for i, x in enumerate(self.xc_list):
+                self.xc.append_text(x)
+        for i, x in enumerate(self.xc_list):
+            if x == self.aims_xc_default:
+                self.xc.set_active(i)
+        self.TS.set_active(False)
+        if self.periodic:
+            self.ucell = atoms.get_cell()
+            for i in range(3):
+                default = np.ceil(20.0 / np.sqrt(np.vdot(self.ucell[i],self.ucell[i])))
+                self.kpts_spin[i].set_value(default)
+        self.spinpol.set_active(False)
+        self.charge.set_value(0)
+        aims_relativity_default = 'none'
+        for a in atoms:
+            if a.get_atomic_number() > 20: 
+                aims_relativity_default = 'atomic_zora'
+        for i, x in enumerate(self.aims_relativity_list):
+            if x == aims_relativity_default:
+                self.relativity_type.set_active(i)
+        self.sc_tot_energy.set_value(1e-6)
+        self.sc_sum_eigenvalue.set_value(1e-3)
+        self.sc_density.set_value(1e-4)
+        self.sc_forces.set_value(1e-4)
+        for key in self.expert_keywords:
+            key[0].destroy()
+            key[1].destroy()
+            key[2].destroy()
+            key[3] = False
+        if os.environ.has_key('AIMS_COMMAND'):
+            text = os.environ['AIMS_COMMAND']
+        else:
+            text = ""
+        self.run_command.set_text(text)
+        if os.environ.has_key('AIMS_SPECIES_DIR'):
+            text = os.environ['AIMS_SPECIES_DIR']
+        else:
+            text = ""
+        self.species_defaults.set_text(text)
+
+    def set_attributes(self, *args):
+        param = {}
+        param["xc"] = self.xc.get_active_text()
+        if self.periodic:
+            param["k_grid"] = (int(self.kpts[0].value),
+                               int(self.kpts[1].value),
+                               int(self.kpts[2].value))
+        if self.spinpol.get_active():
+            param["spin"] = "collinear"
+        else:
+            param["spin"] = "none"
+        param["vdw_correction_hirshfeld"] = self.TS.get_active()
+        param["charge"]             = self.charge.value
+        param["relativistic"]       = self.relativity_type.get_active_text()
+        if param["relativistic"] == 'atomic_zora':
+            param["relativistic"] += " scalar "
+        if param["relativistic"] == 'zora':
+            param["relativistic"] += " scalar "+self.relativity_threshold.get_text() 
+        param["sc_accuracy_etot"]   = self.sc_tot_energy.value
+        param["sc_accuracy_eev"]    = self.sc_sum_eigenvalue.value
+        param["sc_accuracy_rho"]    = self.sc_density.value
+        param["compute_forces"]     = self.compute_forces.get_active()
+        param["sc_accuracy_forces"] = self.sc_forces.value
+        param["run_command"]        = self.run_command.get_text()
+        param["species_dir"]        = self.species_defaults.get_text()
+        from ase.calculators.aims import float_keys,exp_keys,string_keys,int_keys,bool_keys,list_keys,input_keys
+        for option in self.expert_keywords:
+            if option[3]:   # set type of parameter accoding to which list it is in
+                key = option[0].get_text().strip()
+                val = option[1].get_text().strip()
+                if key == 'output':
+                    if param.has_key('output'): 
+                        param[key] += [val]
+                    else:
+                        param[key] = [val]
+                elif key in float_keys or key in exp_keys:
+                    param[key] = float(val)
+                elif key in list_keys or key in string_keys or key in input_keys:
+                    param[key] = val
+                elif key in int_keys:
+                    param[key] = int(val)
+                elif key in bool_keys:
+                    param[key] = bool(val)
+        setattr(self.owner, self.attrname, param)
+
+    def set_param(self, param):
+        if param["xc"] is not None:
+            for i, x in enumerate(self.xc_list):
+                if x == param["xc"]:
+                    self.xc.set_active(i)
+        if isinstance(param["vdw_correction_hirshfeld"],bool):
+            self.TS.set_active(param["vdw_correction_hirshfeld"])
+        if self.periodic and param["k_grid"] is not None:
+            self.kpts[0].value = int(param["k_grid"][0])
+            self.kpts[1].value = int(param["k_grid"][1])
+            self.kpts[2].value = int(param["k_grid"][2])
+        if param["spin"] is not None:
+            self.spinpol.set_active(param["spin"] == "collinear")
+        if param["charge"] is not None:
+            self.charge.value = param["charge"]
+        if param["relativistic"] is not None:
+            if isinstance(param["relativistic"],(tuple,list)):
+                rel = param["relativistic"]
+            else:
+                rel = param["relativistic"].split()
+            for i, x in enumerate(self.aims_relativity_list):
+                if x == rel[0]:
+                    self.relativity_type.set_active(i)
+                    if x == 'zora':
+                        self.relativity_threshold.set_text(rel[2])
+                        self.relativity_threshold.set_sensitive(True)
+        if param["sc_accuracy_etot"] is not None:
+            self.sc_tot_energy.value     = param["sc_accuracy_etot"]
+        if param["sc_accuracy_eev"] is not None:
+            self.sc_sum_eigenvalue.value = param["sc_accuracy_eev"]
+        if param["sc_accuracy_rho"] is not None:
+            self.sc_density.value        = param["sc_accuracy_rho"]
+        if param["compute_forces"] is not None:
+            if param["compute_forces"]:
+                if param["sc_accuracy_forces"] is not None:
+                    self.sc_forces.value = param["sc_accuracy_forces"]
+                self.compute_forces.set_active(param["compute_forces"])
+            else: 
+                self.compute_forces.set_active(False)
+        if param["run_command"] is not None:
+            self.run_command.set_text(param["run_command"])
+        if param["species_dir"] is not None:
+            self.species_defaults.set_text(param["species_dir"])
+        for (key,val) in param.items():
+            if key in self.aims_keyword_list and key not in self.aims_keyword_gui_list:
+                if val is not None:  # = existing "expert keyword"
+                    if key == 'output': # 'output' can be used more than once
+                        options = val
+                        if isinstance(options,str): options = [options]
+                        for arg in options:
+                            self.expert_keyword_create([key]+[arg])
+                    else:
+                        if isinstance(val,str):
+                            arg = [key]+val.split()
+                        elif isinstance(val,(tuple,list)):
+                            arg = [key]+[str(a) for a in val]
+                        else:
+                            arg = [key]+[str(val)]
+                        self.expert_keyword_create(arg)
+
+    def ok(self, *args):
+        self.set_attributes(*args)
+        self.destroy()
+
+    def export_control(self, *args):
+        filename = "control.in"
+        chooser = gtk.FileChooserDialog(
+            'Export parameters ... ', None, gtk.FILE_CHOOSER_ACTION_SAVE,
+            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+        chooser.set_filename(filename)
+        save = chooser.run()
+        if save == gtk.RESPONSE_OK or save == gtk.RESPONSE_SAVE:
+            filename = chooser.get_filename()
+            self.set_attributes(*args)
+            param = getattr(self.owner, "aims_parameters")
+            from ase.calculators.aims import Aims
+            calc_temp = Aims(**param)
+            atoms_temp = self.owner.atoms.copy()
+            atoms_temp.set_calculator(calc_temp)
+            atoms_temp.calc.write_control(file = filename)
+            atoms_temp.calc.write_species(file = filename)
+        chooser.destroy()
+
+    def import_control(self, *args):
+        filename = "control.in"
+        chooser = gtk.FileChooserDialog(
+            'Import control.in file ... ', None, gtk.FILE_CHOOSER_ACTION_SAVE,
+            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+        chooser.set_filename(filename)
+        save = chooser.run()
+        if save == gtk.RESPONSE_OK:
+            self.set_defaults()
+            filename = chooser.get_filename()
+            control = open(filename,'r')
+            while True:
+                line = control.readline()
+                if not line:
+                    break
+                if "List of parameters used to initialize the calculator:" in line:
+                    control.readline()
+                    from ase.io.aims import read_aims_calculator
+                    calc = read_aims_calculator(control)
+                    found_aims_calculator = True
+            control.close()
+            if found_aims_calculator:
+                param = calc.float_params
+                for key in calc.exp_params:
+                    param[key] = calc.exp_params[key]
+                for key in calc.string_params:
+                    param[key] = calc.string_params[key]
+                for key in calc.int_params:
+                    param[key] = calc.int_params[key]
+                for key in calc.bool_params:
+                    param[key] = calc.bool_params[key]
+                for key in calc.list_params:
+                    param[key] = calc.list_params[key]
+                for key in calc.input_parameters:
+                    param[key] = calc.input_parameters[key]
+                self.set_defaults()
+                self.set_param(param)
+        chooser.destroy()
+
+    def k_changed(self, *args):
+        size = [self.kpts[i].value * np.sqrt(np.vdot(self.ucell[i],self.ucell[i])) for i in range(3)]
+        self.kpts_label.set_text(self.kpts_label_format % tuple(size))
+
+    def compute_forces_toggled(self, *args):
+        self.sc_forces_spin.set_sensitive(self.compute_forces.get_active())
+
+    def relativity_changed(self, *args):
+        self.relativity_threshold.set_sensitive(self.relativity_type.get_active() == 2)
+
+    def expert_keyword_import(self, *args):
+        command = self.expert_keyword_set.get_text().split()
+        if len(command) > 0 and command[0] in self.aims_keyword_list and not command[0] in self.aims_keyword_gui_list:
+            self.expert_keyword_create(command)
+        elif command[0] in self.aims_keyword_gui_list:
+            oops("Please use the facilities provided in this window to manipulate "+
+                 "the keyword:" + command[0] + "!")
+        else:
+            oops("Don't know this keyword:" + command[0] 
+                 + "\nPlease check!\n\n" 
+                 + "If you really think it should be available, "
+                 + "please add it to the top of ase/calculators/aims.py.")
+        self.expert_keyword_set.set_text("")
+
+    def expert_keyword_create(self, command):
+        key = command[0]
+        argument = command[1]
+        if len(command) > 2:
+            for a in command[2:]:
+                argument += ' '+a
+        index = len(self.expert_keywords) 
+        self.expert_keywords += [[gtk.Label("    " +key+"  "),
+                                  gtk.Entry(max=45),
+                                  ExpertDeleteButton(index),
+                                  True]]
+        self.expert_keywords[index][1].set_text(argument)
+        self.expert_keywords[index][2].connect('clicked',self.expert_keyword_delete)
+        pack(self.expert_vbox, [self.expert_keywords[index][0],
+                                self.expert_keywords[index][1],
+                                self.expert_keywords[index][2]])
+
+    def expert_keyword_delete(self, button, *args):
+        index = button.index   # which one to kill 
+        for i in [0,1,2]:
+            self.expert_keywords[index][i].destroy()
+        self.expert_keywords[index][3] = False
+
+
+class ExpertDeleteButton(gtk.Button):
+    def __init__(self, index):
+        gtk.Button.__init__(self, stock=gtk.STOCK_DELETE)
+        alignment = self.get_children()[0]
+        hbox = alignment.get_children()[0]
+        image, label = hbox.get_children()
+        if image is not None: 
+            label.set_text('')
+        self.index = index
+
+
+class VASP_Window(gtk.Window):
+    vasp_xc_list = ['PW91', 'PBE', 'LDA']
+    vasp_xc_default = 'PBE'
+    vasp_prec_default = 'Normal'
+    def __init__(self, owner, param, attrname):
+        self.owner = owner
+        self.attrname = attrname
+        atoms = owner.atoms
+        self.periodic = atoms.get_pbc().all()
+        self.vasp_keyword_gui_list = ['ediff','encut', 'ismear', 'ispin', 'prec', 'sigma']
+        from ase.calculators.vasp import float_keys,exp_keys,string_keys,int_keys,bool_keys,list_keys,special_keys
+        self.vasp_keyword_list = float_keys+exp_keys+string_keys+int_keys+bool_keys+list_keys+special_keys
+        self.expert_keywords = []
+        natoms = len(atoms)
+        gtk.Window.__init__(self)
+        self.set_title("VASP parameters")
+        vbox = gtk.VBox()
+        # Print some info
+        txt = "%i atoms.\n" % (natoms)
+        self.ucell = atoms.get_cell()
+        txt += "Periodic geometry, unit cell is: \n"
+        for i in range(3):
+            txt += "(%8.3f %8.3f %8.3f)\n" % (self.ucell[i][0], self.ucell[i][1], self.ucell[i][2])
+        pack(vbox, [gtk.Label(txt)])
+
+        # XC functional ()
+        self.xc = gtk.combo_box_new_text()
+        for i, x in enumerate(self.vasp_xc_list):
+            self.xc.append_text(x)
+            if x == self.vasp_xc_default:
+                self.xc.set_active(i)
+
+        # Spin polarized
+        self.spinpol = gtk.CheckButton("Spin polarized")
+
+        pack(vbox, [gtk.Label("Exchange-correlation functional: "),
+                    self.xc,
+                    gtk.Label("    "),
+                    self.spinpol])
+        pack(vbox, gtk.Label(""))
+
+        # k-grid
+        self.kpts = []
+        self.kpts_spin = []
+        for i in range(3):
+            default = np.ceil(20.0 / np.sqrt(np.vdot(self.ucell[i],self.ucell[i])))
+            g = gtk.Adjustment(default, 1, 100, 1)
+            s = gtk.SpinButton(g, 0, 0)
+            self.kpts.append(g)
+            self.kpts_spin.append(s)
+            g.connect("value-changed", self.k_changed)
+
+        # Precision of calculation
+        self.prec = gtk.combo_box_new_text()
+        for i, x in enumerate(['Low', 'Normal', 'Accurate']):
+            self.prec.append_text(x)
+            if x == self.vasp_prec_default:
+                self.prec.set_active(i)
+
+        # cutoff energy
+        if os.environ.has_key('VASP_PP_PATH'):
+            self.encut_min_default, self.encut_max_default = self.get_min_max_cutoff()
+        else:
+            self.encut_max_default = 400.0
+            self.encut_min_default = 100.0
+        self.encut = gtk.Adjustment(self.encut_max_default, 0, 9999, 10)
+        self.encut_spin = gtk.SpinButton(self.encut, 0, 0)
+        self.encut_spin.set_digits(2)
+        self.encut_spin.connect("value-changed",self.check_encut_warning)
+        self.encut_warning = gtk.Label("")
+
+        pack(vbox, [gtk.Label("k-points  k = ("), self.kpts_spin[0],
+                    gtk.Label(", "), self.kpts_spin[1], gtk.Label(", "),
+                    self.kpts_spin[2], 
+                    gtk.Label(")    Cutoff: "),self.encut_spin,
+                    gtk.Label("    Precision: "),self.prec])
+        self.kpts_label = gtk.Label("")
+        self.kpts_label_format = "k-points x size:  (%.1f, %.1f, %.1f) Å       "
+        pack(vbox, [self.kpts_label, self.encut_warning])
+        self.k_changed()
+        pack(vbox, gtk.Label(""))
+
+        self.ismear = gtk.combo_box_new_text()
+        for x in ['Fermi', 'Gauss', 'Methfessel-Paxton']:
+            self.ismear.append_text(x)
+        self.ismear.set_active(2)
+        self.smearing_order = gtk.Adjustment(2,0,9,1)
+        self.smearing_order_spin = gtk.SpinButton(self.smearing_order,0,0)
+        self.smearing_order_spin.set_digits(0)
+        self.ismear.connect("changed", self.check_ismear_changed)
+        self.sigma = gtk.Adjustment(0.1, 0.001, 9.0, 0.1)
+        self.sigma_spin = gtk.SpinButton(self.sigma,0,0)
+        self.sigma_spin.set_digits(3)
+        pack(vbox, [gtk.Label("Smearing: "),
+                    self.ismear,
+                    gtk.Label(" order: "),
+                    self.smearing_order_spin,
+                    gtk.Label(" width: "),
+                    self.sigma_spin])
+        pack(vbox, gtk.Label(""))
+        
+        self.ediff = gtk.Adjustment(1e-4, 1e-6, 1e0, 1e-4)
+        self.ediff_spin = gtk.SpinButton(self.ediff, 0, 0)
+        self.ediff_spin.set_digits(6)
+        pack(vbox,[gtk.Label("Self-consistency convergence: "),
+                   self.ediff_spin,
+                   gtk.Label(" eV")])
+        pack(vbox,gtk.Label(""))
+
+        self.expert_keyword_set = gtk.Entry(max = 55)
+        self.expert_keyword_add = gtk.Button(stock = gtk.STOCK_ADD)
+        self.expert_keyword_add.connect("clicked", self.expert_keyword_import)
+        self.expert_keyword_set.connect("activate", self.expert_keyword_import)
+        pack(vbox,[gtk.Label("Additional keywords: "),
+                   self.expert_keyword_set, 
+                   self.expert_keyword_add])
+        self.expert_vbox = gtk.VBox()
+        pack(vbox, self.expert_vbox)
+        pack(vbox, gtk.Label(""))
+
+        # run command and location of POTCAR files:
+        pack(vbox, gtk.Label('VASP execution command: '))
+        self.run_command = pack(vbox, gtk.Entry(max=0))
+        if os.environ.has_key('VASP_COMMAND'):
+            self.run_command.set_text(os.environ['VASP_COMMAND'])
+        pack(vbox, gtk.Label('Directory for species defaults: '))
+        self.pp_path = pack(vbox, gtk.Entry(max=0))
+        if os.environ.has_key('VASP_PP_PATH'):
+            self.pp_path.set_text(os.environ['VASP_PP_PATH'])
+
+        # Buttons at the bottom
+        pack(vbox, gtk.Label(""))
+        butbox = gtk.HButtonBox()
+        export_vasp_but = gtk.Button("Export VASP files")
+        export_vasp_but.connect("clicked", self.export_vasp_files)
+        cancel_but = gtk.Button(stock=gtk.STOCK_CANCEL)
+        cancel_but.connect('clicked', lambda widget: self.destroy())
+        ok_but = gtk.Button(stock=gtk.STOCK_OK)
+        ok_but.connect('clicked', self.ok)
+        butbox.pack_start(export_vasp_but, 0, 0)
+        butbox.pack_start(cancel_but, 0, 0)
+        butbox.pack_start(ok_but, 0, 0)
+        butbox.show_all()
+        pack(vbox, [butbox], end=True, bottom=True)
+        vbox.show()
+        self.add(vbox)
+        self.show()
+        self.grab_add()  # Lock all other windows
+
+    def set_attributes(self, *args):
+        self.param = {}
+        self.param["xc"] = self.xc.get_active_text()
+        self.param["prec"] = self.prec.get_active_text()
+        self.param["kpts"] = (int(self.kpts[0].value),
+                              int(self.kpts[1].value),
+                              int(self.kpts[2].value))
+        self.param["encut"] = self.encut.value
+        self.param["ediff"] = self.ediff.value
+        self.param["ismear"] = self.get_ismear()
+        self.param["sigma"] = self.sigma.value
+        if self.spinpol.get_active(): 
+            self.param["ispin"] = 2
+        else:
+            self.param["ispin"] = 1
+        from ase.calculators.vasp import float_keys,exp_keys,string_keys,int_keys,bool_keys,list_keys,special_keys
+        for option in self.expert_keywords:
+            if option[3]:   # set type of parameter accoding to which list it is in
+                key = option[0].get_text().strip()
+                val = option[1].get_text().strip()
+                if key in float_keys or key in exp_keys:
+                    self.param[key] = float(val)
+                elif key in list_keys or key in string_keys:
+                    self.param[key] = val
+                elif key in int_keys:
+                    self.param[key] = int(val)
+                elif key in bool_keys:
+                    self.param[key] = bool(val)
+        setattr(self.owner, self.attrname, self.param)
+        os.environ['VASP_COMMAND'] = self.run_command.get_text()
+        os.environ['VASP_PP_PATH'] = self.pp_path.get_text()
+        
+    def ok(self, *args):
+        self.set_attributes(*args)
+        self.destroy()
+
+    def get_min_max_cutoff(self, *args):
+        # determine the recommended energy cutoff limits 
+        from ase.calculators.vasp import Vasp
+        calc_temp = Vasp()
+        atoms_temp = self.owner.atoms.copy()
+        calc_temp.initialize(atoms_temp)
+        calc_temp.write_potcar(suffix = '.check_energy_cutoff')
+        enmin = -1e6
+        enmax = -1e6
+        for line in open("POTCAR.check_energy_cutoff",'r').readlines():
+            if "ENMIN" in line:
+                enmax = max(enmax,float(line.split()[2].split(';')[0]))
+                enmin = max(enmin,float(line.split()[5]))
+        from os import system
+        system("rm POTCAR.check_energy_cutoff")
+        return enmin, enmax
+
+    def k_changed(self, *args):
+        size = [self.kpts[i].value * np.sqrt(np.vdot(self.ucell[i],self.ucell[i])) for i in range(3)]
+        self.kpts_label.set_text(self.kpts_label_format % tuple(size))
+
+    def check_encut_warning(self,*args):
+        if self.encut.value < self.encut_min_default:
+            self.encut_warning.set_markup("<b>WARNING:</b> cutoff energy is lower than recommended minimum!")
+        else:
+            self.encut_warning.set_markup("")
+
+    def check_ismear_changed(self,*args):
+        if self.ismear.get_active_text() == 'Methfessel-Paxton':
+            self.smearing_order_spin.set_sensitive(True)
+        else:
+            self.smearing_order_spin.set_sensitive(False)
+
+    def get_ismear(self,*args):
+        type = self.ismear.get_active_text()
+        if type == 'Methfessel-Paxton':
+            ismear_value = self.smearing_order.value
+        elif type == 'Fermi':
+            ismear_value = -1
+        else:
+            ismear_value = 0
+        return ismear_value
+
+    def destroy(self):
+        self.grab_remove()
+        gtk.Window.destroy(self)
+
+    def export_vasp_files(self, *args):
+        filename = ""
+        chooser = gtk.FileChooserDialog(
+            'Export VASP input files: choose directory ... ', 
+            None, gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
+            (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+             gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+        chooser.set_filename(filename)
+        save = chooser.run()
+        if save == gtk.RESPONSE_OK or save == gtk.RESPONSE_SAVE:
+            filename = chooser.get_filename()
+            from os import chdir
+            chdir(filename)
+            self.set_attributes(*args)
+            param = getattr(self.owner, "vasp_parameters")
+            from ase.calculators.vasp import Vasp
+            calc_temp = Vasp(**param)
+            atoms_temp = self.owner.atoms.copy()
+            atoms_temp.set_calculator(calc_temp)
+            calc_temp.initialize(atoms_temp)
+            calc_temp.write_incar(atoms_temp)
+            calc_temp.write_potcar()
+            calc_temp.write_kpoints()
+            calc_temp.write_sort_file()
+            from ase.io.vasp import write_vasp
+            write_vasp('POSCAR', calc_temp.atoms_sorted, symbol_count = calc_temp.symbol_count)
+        chooser.destroy()
+
+    def expert_keyword_import(self, *args):
+        command = self.expert_keyword_set.get_text().split()
+        if len(command) > 0 and command[0] in self.vasp_keyword_list and not command[0] in self.vasp_keyword_gui_list:
+            self.expert_keyword_create(command)
+        elif command[0] in self.vasp_keyword_gui_list:
+            oops("Please use the facilities provided in this window to manipulate "+
+                 "the keyword: " + command[0] + "!")
+        else:
+            oops("Don't know this keyword: " + command[0] 
+                 + "\nPlease check!\n\n" 
+                 + "If you really think it should be available, "
+                 + "please add it to the top of ase/calculators/vasp.py.")
+        self.expert_keyword_set.set_text("")
+
+    def expert_keyword_create(self, command):
+        key = command[0]
+        if command[1] == "=":
+            command.remove("=")
+        argument = command[1]
+        if len(command) > 2:
+            for a in command[2:]:
+                argument += ' '+a
+        index = len(self.expert_keywords) 
+        self.expert_keywords += [[gtk.Label("    " +key+" = "),
+                                  gtk.Entry(max=45),
+                                  ExpertDeleteButton(index),
+                                  True]]
+        self.expert_keywords[index][1].set_text(argument)
+        self.expert_keywords[index][2].connect('clicked',self.expert_keyword_delete)
+        pack(self.expert_vbox, [self.expert_keywords[index][0],
+                                self.expert_keywords[index][1],
+                                self.expert_keywords[index][2]])
+
+    def expert_keyword_delete(self, button, *args):
+        index = button.index   # which one to kill 
+        for i in [0,1,2]:
+            self.expert_keywords[index][i].destroy()
+        self.expert_keywords[index][3] = False
Index: ase/gui/rotate.py
===================================================================
--- ase/gui/rotate.py	(revision 0)
+++ ase/gui/rotate.py	(revision 1)
@@ -0,0 +1,46 @@
+import numpy as np
+import gtk
+
+from ase.gui.widgets import pack
+from ase.utils import rotate, irotate
+
+
+class Rotate(gtk.Window):
+    update = True
+    
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        angles = irotate(gui.axes)
+        self.set_title('Rotate')
+        vbox = gtk.VBox()
+        pack(vbox, gtk.Label('Rotation angles:'))
+        self.rotate = [gtk.Adjustment(value=a, lower=-360, upper=360,
+                                      step_incr=1, page_incr=10)
+                       for a in angles]
+        pack(vbox, [gtk.SpinButton(a, climb_rate=0, digits=1)
+                    for a in self.rotate])
+        for r in self.rotate:
+            r.connect('value-changed', self.change)
+        button = pack(vbox, gtk.Button('Update'))
+        button.connect('clicked', self.update_angles)
+        pack(vbox, gtk.Label('Note:\nYou can rotate freely\n'
+                             'with the mouse, by holding\n'
+                             'down mouse botton 2.'))
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+    def change(self, adjustment):
+        if self.update:
+            x, y, z = [float(a.value) for a in self.rotate]
+            self.gui.axes = rotate('%fx,%fy,%fz' % (x, y, z))
+            self.gui.set_coordinates()
+        return True
+        
+    def update_angles(self, button):
+        angles = irotate(self.gui.axes)
+        self.update = False
+        for r, a in zip(self.rotate, angles):
+            r.value = a
+        self.update = True
Index: ase/gui/__init__.py
===================================================================
Index: ase/gui/settings.py
===================================================================
--- ase/gui/settings.py	(revision 0)
+++ ase/gui/settings.py	(revision 1)
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+import gtk
+from ase.gui.widgets import pack
+from ase.gui.languages import translate as _
+
+class Settings(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        self.set_title('Settings')
+        self.gui = gui
+        vbox = gtk.VBox()
+
+        # Constraints
+        a = pack(vbox, gtk.Label())
+        a.set_markup('<span size="larger" underline="single">'
+                     'Constraints:</span>')
+        a, b = pack(vbox, [gtk.Button(_('Constrain')),
+                           gtk.Label('/'),
+                           gtk.Button(_('release')),                        
+                           gtk.Label(_(' selected atoms'))])[::2]
+        a.connect('clicked', self.constrain_selected)
+        b.connect('clicked', self.release_selected)
+        a = pack(vbox, gtk.Button(_('Constrain immobile atoms')))
+        a.connect('clicked', self.immobile)
+        a = pack(vbox, gtk.Button(_('Clear all constraints')))
+        a.connect('clicked', self.clear_constraints)
+
+        # Visibility
+        a = pack(vbox, gtk.Label())
+        a.set_markup('\n<span size="larger" underline="single">'
+                     'Visibility:</span>')
+        a, b = pack(vbox, [gtk.Button(_('Hide')),
+                           gtk.Label('/'),
+                           gtk.Button(_('show')),
+                           gtk.Label(_(' selected atoms'))])[::2]
+        a.connect('clicked', self.hide_selected)
+        b.connect('clicked', self.show_selected)
+        a = pack(vbox, gtk.Button(_('View all atoms')))
+        a.connect('clicked', self.view_all)
+
+        # Miscellaneous
+        a = pack(vbox, gtk.Label())
+        a.set_markup('\n<span size="larger" underline="single">'
+                     'Miscellaneous:</span>')
+        self.scale = gtk.Adjustment(value=.89, lower=0.2, upper=2.0,
+                                    step_incr=0.1, page_incr=0.5)
+        pack(vbox, [gtk.Label(_('Scale atomic radii:')),
+                    gtk.SpinButton(self.scale, climb_rate=0, digits=2)])
+        self.scale.connect('value-changed', self.scale_radii)
+
+        # A close button
+        pack(vbox, gtk.Label(_('\n')))
+        close = pack(vbox, gtk.Button(_('Close')))
+        close.connect('clicked', lambda widget: self.destroy())
+
+        # Add elements and show frame
+        self.add(vbox)
+        vbox.show()
+        self.show()
+
+    def scale_radii(self, adjustment):
+        self.gui.images.set_radii(float(self.scale.value))
+        self.gui.draw()
+        return True
+
+    def hide_selected(self, button):
+        self.gui.images.visible[self.gui.images.selected] = False
+        self.gui.draw()
+
+    def show_selected(self, button):
+        self.gui.images.visible[self.gui.images.selected] = True
+        self.gui.draw()
+
+    def view_all(self, button):
+        self.gui.images.visible[:] = True
+        self.gui.draw()
+
+    def constrain_selected(self, button):
+        self.gui.images.dynamic[self.gui.images.selected] = False
+        self.gui.draw()
+
+    def release_selected(self, button):
+        self.gui.images.dynamic[self.gui.images.selected] = True
+        self.gui.draw()
+
+    def immobile(self, button):
+        self.gui.images.set_dynamic()
+        self.gui.draw()
+
+    def clear_constraints(self, button):
+        self.gui.images.dynamic[:] = True
+        self.gui.draw()
Index: ase/gui/widgets.py
===================================================================
--- ase/gui/widgets.py	(revision 0)
+++ ase/gui/widgets.py	(revision 1)
@@ -0,0 +1,131 @@
+import gtk
+
+from ase.gui.languages import translate as _
+
+
+class Number(gtk.SpinButton):
+    def __init__(self, value=0,
+                 lower=0, upper=10000,
+                 step_incr=1, page_incr=10,
+                 climb_rate=0.5, digits=0):
+        self.adj = gtk.Adjustment(value, lower, upper, step_incr, page_incr, 0)
+        gtk.SpinButton.__init__(self, self.adj, climb_rate, digits)
+
+    def connect(self, *args):
+        return self.adj.connect(*args)
+
+
+class Menu:
+    def __init__(self, menubar, name, items):
+        self.items = {}
+        menu = gtk.Menu()
+        for data in items:
+            text = data[0]
+            callback = data[1]
+            args = data[2:]
+            menuitem = gtk.MenuItem(_(text))
+            menu.append(menuitem)
+            menuitem.connect('activate', callback, *args)
+            menuitem.show()
+            self.items[text] = menuitem
+        menuitem = gtk.MenuItem(_(name))
+        menubar.append(menuitem)
+        menuitem.set_submenu(menu)
+        menuitem.show()
+
+
+class Help(gtk.Window):
+    def __init__(self, text):
+        gtk.Window.__init__(self)
+        vbox = gtk.VBox()
+        self.add(vbox)
+        label = pack(vbox, gtk.Label())
+        label.set_line_wrap(True)
+        text = _(text).replace('<c>', '<span foreground="blue">')
+        text = text.replace('</c>', '</span>')
+        label.set_markup(text)
+        close = pack(vbox, gtk.Button(_('Close')))
+        close.connect('clicked', lambda widget: self.destroy())
+        self.show_all()
+
+def help(text):
+    button = gtk.Button(_('Help'))
+    button.connect('clicked', lambda widget, text=text: Help(text))
+    return button
+
+
+class Window(gtk.Window):
+    def __init__(self, gui):
+        self.gui = gui
+        gtk.Window.__init__(self)
+        self.set_title(_('Constraints'))
+        vbox = gtk.VBox()
+        b = pack(vbox, [gtk.Button(_('Constrain')),
+                        gtk.Label(_(' selected atoms'))])[0]
+        b.connect('clicked', self.selected)
+        b = pack(vbox, [gtk.Button(_('Constrain')),
+                        gtk.Label(_(' immobile atoms:'))])[0]
+        b.connect('clicked', self.immobile)
+        b = pack(vbox, gtk.Button('Clear constraint'))
+        b.connect('clicked', self.clear)
+        close = pack(vbox, gtk.Button(_('Close')))
+        close.connect('clicked', lambda widget: self.destroy())
+        self.add(vbox)
+        vbox.show()
+        self.show()
+
+def pack(vbox, widgets, end=False, bottom=False):
+    if not isinstance(widgets, list):
+        widgets.show()
+        if bottom:
+            vbox.pack_end(widgets, 0, 0)
+        else:
+            vbox.pack_start(widgets, 0, 0)
+        return widgets
+    hbox = gtk.HBox(0, 0)
+    hbox.show()
+    if bottom:
+        vbox.pack_end(hbox, 0, 0)
+    else:
+        vbox.pack_start(hbox, 0, 0)
+    for widget in widgets:
+        if type(widget) is gtk.Entry:
+            widget.set_size_request(widget.get_max_length() * 9, 24)
+        widget.show()
+        if end and widget is widgets[-1]:
+            hbox.pack_end(widget, 0, 0)
+        else:
+            hbox.pack_start(widget, 0, 0)
+    return widgets
+
+class cancel_apply_ok(gtk.HButtonBox):
+    "Widget with Cancel, Apply and OK buttons.  The arguments are callbacks."
+    def __init__(self, cancel, apply, ok):
+        gtk.HButtonBox.__init__(self)
+        cancel_but = gtk.Button(stock=gtk.STOCK_CANCEL)
+        cancel_but.connect('clicked', cancel)
+        apply_but = gtk.Button(stock=gtk.STOCK_APPLY)
+        apply_but.connect('clicked', apply)
+        ok_but = gtk.Button(stock=gtk.STOCK_OK)
+        ok_but.connect('clicked', ok)
+        for w in (cancel_but, apply_but, ok_but):
+            self.pack_start(w, 0, 0)
+            w.show()
+        #self.show_all()
+        
+def oops(message, message2=None):
+    dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
+                               type=gtk.MESSAGE_WARNING,
+                               buttons=gtk.BUTTONS_CLOSE,
+                               message_format=message)
+    try:
+        dialog.format_secondary_text(message2)
+    except AttributeError:
+        print >>sys.stderr, message
+        print >>sys.stderr, message2
+    dialog.connect('response', lambda x, y, dialog=dialog: dialog.destroy())
+    dialog.show()
+
+class AseGuiCancelException(Exception):
+    pass
+        
Index: ase/gui/constraints.py
===================================================================
--- ase/gui/constraints.py	(revision 0)
+++ ase/gui/constraints.py	(revision 1)
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+from math import sqrt
+
+import gtk
+
+from ase.gui.languages import translate as _
+from ase.gui.widgets import pack, Help
+
+
+class Constraints(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        self.set_title(_('Constraints'))
+        vbox = gtk.VBox()
+        b = pack(vbox, [gtk.Button(_('Constrain')),
+                        gtk.Label(_(' selected atoms'))])[0]
+        b.connect('clicked', self.selected)
+        b = pack(vbox, [gtk.Button(_('Constrain')),
+                        gtk.Label(_(' immobile atoms:'))])[0]
+        b.connect('clicked', self.immobile)
+        b = pack(vbox, gtk.Button('Clear constraints'))
+        b.connect('clicked', self.clear)
+        close = pack(vbox, gtk.Button(_('Close')))
+        close.connect('clicked', lambda widget: self.destroy())
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+    def selected(self, button):
+        self.gui.images.dynamic = ~self.gui.images.selected
+        self.gui.draw()
+
+    def immobile(self, button):
+        self.gui.images.set_dynamic()
+        self.gui.draw()
+
+    def clear(self, button):
+        self.gui.images.dynamic[:] = True
+        self.gui.draw()
+
Index: ase/gui/view.py
===================================================================
--- ase/gui/view.py	(revision 0)
+++ ase/gui/view.py	(revision 1)
@@ -0,0 +1,597 @@
+#!/usr/bin/env python
+
+# Emacs: treat this as -*- python -*-
+
+import os
+import gtk
+import tempfile
+from math import cos, sin, sqrt
+
+import numpy as np
+
+from ase.data.colors import jmol_colors
+from ase.gui.repeat import Repeat
+from ase.gui.rotate import Rotate
+from ase.utils import rotate
+
+
+class View:
+    def __init__(self, vbox, rotations):
+        self.colors = [None] * (len(jmol_colors) + 1)
+        self.nselected = 0
+        self.light_green_markings = 0
+        self.axes = rotate(rotations)
+        # this is a hack, in order to be able to toggle menu actions off/on
+        # without getting into an infinte loop
+        self.menu_change = 0
+    
+        self.atoms_to_rotate = None
+        
+        self.drawing_area = gtk.DrawingArea()
+        self.drawing_area.set_size_request(450, 450)
+        self.drawing_area.connect('button_press_event', self.press)
+        self.drawing_area.connect('button_release_event', self.release)
+        self.drawing_area.connect('motion-notify-event', self.move)
+        # Signals used to handle backing pixmap:
+        self.drawing_area.connect('expose_event', self.expose_event)
+        self.drawing_area.connect('configure_event', self.configure_event)
+        self.drawing_area.set_events(gtk.gdk.BUTTON_PRESS_MASK |
+                                     gtk.gdk.BUTTON_RELEASE_MASK |
+                                     gtk.gdk.BUTTON_MOTION_MASK |
+                                     gtk.gdk.POINTER_MOTION_HINT_MASK)
+        vbox.pack_start(self.drawing_area)
+        self.drawing_area.show()
+        self.configured = False
+        self.frame = None
+        
+    def set_coordinates(self, frame=None, focus=None):
+        if frame is None:
+            frame = self.frame
+        self.make_box()
+        self.bind(frame)
+        n = self.images.natoms
+        self.X = np.empty((n + len(self.B1) + len(self.bonds), 3))
+        #self.X[n:] = np.dot(self.B1, self.images.A[frame])
+        #self.B = np.dot(self.B2, self.images.A[frame])
+        self.set_frame(frame, focus=focus, init=True)
+
+    def set_frame(self, frame=None, focus=False, init=False):
+        if frame is None:
+            frame = self.frame
+
+        n = self.images.natoms
+
+        if init or frame != self.frame:
+            A = self.images.A
+            nc = len(self.B1)
+            nb = len(self.bonds)
+            
+            if init or (A[frame] != A[self.frame]).any():
+                self.X[n:n + nc] = np.dot(self.B1, A[frame])
+                self.B = np.empty((nc + nb, 3))
+                self.B[:nc] = np.dot(self.B2, A[frame])
+
+            if nb > 0:
+                P = self.images.P[frame]
+                Af = self.images.repeat[:, np.newaxis] * A[frame]
+                a = P[self.bonds[:, 0]]
+                b = P[self.bonds[:, 1]] + np.dot(self.bonds[:, 2:], Af) - a
+                d = (b**2).sum(1)**0.5
+                r = 0.65 * self.images.r
+                x0 = (r[self.bonds[:, 0]] / d).reshape((-1, 1))
+                x1 = (r[self.bonds[:, 1]] / d).reshape((-1, 1))
+                self.X[n + nc:] = a + b * x0
+                b *= 1.0 - x0 - x1
+                b[self.bonds[:, 2:].any(1)] *= 0.5
+                self.B[nc:] = self.X[n + nc:] + b
+
+            filenames = self.images.filenames
+            filename = filenames[frame]
+            if self.frame is None or filename != filenames[self.frame]:
+                if filename is None:
+                    filename = 'ase.gui'
+                self.window.set_title(filename)
+                
+        self.frame = frame
+
+        self.X[:n] = self.images.P[frame]
+        self.R = self.X[:n]
+        if focus:
+            self.focus()
+        else:
+            self.draw()
+        
+    def set_colors(self):
+        new = self.drawing_area.window.new_gc
+        alloc = self.colormap.alloc_color
+        for z in self.images.Z:
+            if self.colors[z] is None:
+                c, p, k = jmol_colors[z]
+                self.colors[z] = new(alloc(int(65535 * c),
+                                           int(65535 * p),
+                                           int(65535 * k)))
+            
+    def plot_cell(self):
+        V = self.images.A[0]
+        R1 = []
+        R2 = []
+        for c in range(3):
+            v = V[c]
+            d = sqrt(np.dot(v, v))
+            n = max(2, int(d / 0.3))
+            h = v / (2 * n - 1)
+            R = np.arange(n)[:, None] * (2 * h)
+            for i, j in [(0, 0), (0, 1), (1, 0), (1, 1)]:
+                R1.append(R + i * V[(c + 1) % 3] + j * V[(c + 2) % 3])
+                R2.append(R1[-1] + h)
+        return np.concatenate(R1), np.concatenate(R2)
+
+    def make_box(self):
+        if not self.ui.get_widget('/MenuBar/ViewMenu/ShowUnitCell'
+                                  ).get_active():
+            self.B1 = self.B2 = np.zeros((0, 3))
+            return
+        
+        V = self.images.A[0]
+        nn = []
+        for c in range(3):
+            v = V[c]
+            d = sqrt(np.dot(v, v))
+            n = max(2, int(d / 0.3))
+            nn.append(n)
+        self.B1 = np.zeros((2, 2, sum(nn), 3))
+        self.B2 = np.zeros((2, 2, sum(nn), 3))
+        n1 = 0
+        for c, n in enumerate(nn):
+            n2 = n1 + n
+            h = 1.0 / (2 * n - 1)
+            R = np.arange(n) * (2 * h)
+
+            for i, j in [(0, 0), (0, 1), (1, 0), (1, 1)]:
+                self.B1[i, j, n1:n2, c] = R
+                self.B1[i, j, n1:n2, (c + 1) % 3] = i
+                self.B1[i, j, n1:n2, (c + 2) % 3] = j
+            self.B2[:, :, n1:n2] = self.B1[:, :, n1:n2]
+            self.B2[:, :, n1:n2, c] += h
+            n1 = n2
+        self.B1.shape = (-1, 3)
+        self.B2.shape = (-1, 3)
+
+    def bind(self, frame):
+        if not self.ui.get_widget('/MenuBar/ViewMenu/ShowBonds'
+                                  ).get_active():
+            self.bonds = np.empty((0, 5), int)
+            return
+        
+        from ase.atoms import Atoms
+        from ase.calculators.neighborlist import NeighborList
+        nl = NeighborList(self.images.r * 1.5, skin=0, self_interaction=False)
+        nl.update(Atoms(positions=self.images.P[frame],
+                        cell=(self.images.repeat[:, np.newaxis] *
+                              self.images.A[frame]),
+                        pbc=self.images.pbc))
+        nb = nl.nneighbors + nl.npbcneighbors
+        self.bonds = np.empty((nb, 5), int)
+        if nb == 0:
+            return
+        
+        n1 = 0
+        for a in range(self.images.natoms):
+            indices, offsets = nl.get_neighbors(a)
+            n2 = n1 + len(indices)
+            self.bonds[n1:n2, 0] = a
+            self.bonds[n1:n2, 1] = indices
+            self.bonds[n1:n2, 2:] = offsets
+            n1 = n2
+
+        i = self.bonds[:n2, 2:].any(1)
+        self.bonds[n2:, 0] = self.bonds[i, 1]
+        self.bonds[n2:, 1] = self.bonds[i, 0]
+        self.bonds[n2:, 2:] = -self.bonds[i, 2:]
+
+    def toggle_show_unit_cell(self, action):
+        self.set_coordinates()
+        
+    def reset_tools_modes(self):
+        dummy = self.menu_change
+        self.menu_change = 1
+        self.atoms_to_rotate = None
+        for c_mode in ['Rotate', 'Orient', 'Move']:
+              self.ui.get_widget('/MenuBar/ToolsMenu/%sAtoms' % c_mode).set_active(False)
+        self.light_green_markings = 0
+        self.menu_change = 0        
+        self.draw()
+        
+                      
+    def toggle_mode(self, mode):
+        self.menu_change = 1
+        i_sum = 0
+        for c_mode in ['Rotate', 'Orient', 'Move']:
+            i_sum += self.ui.get_widget('/MenuBar/ToolsMenu/%sAtoms' % c_mode).get_active() 
+        if i_sum == 0 or (i_sum == 1 and sum(self.images.selected) == 0):
+            self.reset_tools_modes()
+            return()
+            
+        if i_sum == 2:
+            try:
+                self.images.selected = self.atoms_to_rotate_0.copy()
+            except:
+                self.atoms_to_rotate_0 = self.images.selected.copy()
+        if i_sum == 1:
+            self.atoms_to_rotate_0 = self.images.selected.copy()
+
+        for c_mode in ['Rotate', 'Orient', 'Move']:
+            if c_mode != mode:
+                  self.ui.get_widget('/MenuBar/ToolsMenu/%sAtoms' % c_mode).set_active(False) 
+        
+        if self.ui.get_widget('/MenuBar/ToolsMenu/%sAtoms' % mode).get_active():
+            self.atoms_to_rotate_0 = self.images.selected.copy()
+            for i in range(len(self.images.selected)):
+               self.images.selected[i] = False
+            self.light_green_markings = 1
+        else:
+            try: 
+                atr = self.atoms_to_rotate_0
+                for i in range(len(self.images.selected)):
+                    self.images.selected[i] = atr[i]
+            except:
+                pass                
+                
+        self.menu_change = 0
+        self.draw()
+                      
+    def toggle_move_mode(self, action):
+        """
+        Toggles the move mode, where the selected atoms can be moved with the arrow
+        keys and pg up/dn. If the shift key is pressed, the movement will be reduced.
+        
+        The movement will be relative to the current rotation of the coordinate system.
+        
+        The implementation of the move mode is found in the gui.scroll
+        """
+        if not (self.menu_change):
+            self.toggle_mode('Move')
+
+    def toggle_rotate_mode(self, action):
+        """
+        Toggles the rotate mode, where the selected atoms can be rotated with the arrow keys
+        and pg up/dn. If the shift key is pressed, the rotation angle will be reduced.
+        
+        The atoms to be rotated will be marked with light green - and the COM of the selected
+        atoms will be used as the COM of the rotation. This can be changed while rotating the
+        selected atoms.
+        
+        If only two atoms are seleceted, and the number of atoms to be rotated is different from
+        two, the selected atoms will define the axis of rotation.
+        
+        The implementation of the rotate mode is found in the gui.scroll
+        """
+        if not (self.menu_change):
+            self.toggle_mode('Rotate')
+                
+    def toggle_orient_mode(self, action):
+        """
+        Toggle the orientation mode - the orientation of the atoms will be changed
+        according to the arrow keys selected.
+        
+        If nothing is selected, standard directions are x, y and z
+        if two atoms are selected, the standard directions are along their displacement vector
+        if three atoms are selected, the orientation is changed according to the normal of these
+        three vectors.
+        """
+        if not (self.menu_change):
+            self.toggle_mode('Orient')
+        self.orient_normal = np.array([1.0, 0.0, 0.0])
+        sel_pos = []
+        for i, j in enumerate(self.atoms_to_rotate_0):
+            if j: 
+                sel_pos.append(self.R[i])
+        if len(sel_pos) == 2:
+            self.orient_normal = sel_pos[0] - sel_pos[1]
+        if len(sel_pos) == 3:
+            v1 = sel_pos[1] - sel_pos[0]
+            v2 = sel_pos[1] - sel_pos[2]
+            self.orient_normal = np.cross(v1, v2)
+        self.orient_normal /= sum(self.orient_normal ** 2) ** 0.5
+            
+    def toggle_show_axes(self, action):
+        self.draw()
+
+    def toggle_show_bonds(self, action):
+        self.set_coordinates()
+
+    def repeat_window(self, menuitem):
+        self.reset_tools_modes()
+        Repeat(self)
+
+    def rotate_window(self, menuitem):
+        Rotate(self)
+        
+    def focus(self, x=None):
+        if (self.images.natoms == 0 and not
+            self.ui.get_widget('/MenuBar/ViewMenu/ShowUnitCell').get_active()):
+            self.scale = 1.0
+            self.center = np.zeros(3)
+            self.draw()
+            return
+        
+        P = np.dot(self.X, self.axes)
+        n = self.images.natoms
+        P[:n] -= self.images.r[:, None]
+        P1 = P.min(0) 
+        P[:n] += 2 * self.images.r[:, None]
+        P2 = P.max(0)
+        self.center = np.dot(self.axes, (P1 + P2) / 2)
+        S = 1.3 * (P2 - P1)
+        if S[0] * self.height < S[1] * self.width:
+            self.scale = self.height / S[1]
+        else:
+            self.scale = self.width / S[0]
+        self.draw()
+
+    def draw(self, status=True):
+        self.pixmap.draw_rectangle(self.white_gc, True, 0, 0,
+                                   self.width, self.height)
+        axes = self.scale * self.axes * (1, -1, 1)
+        offset = (np.dot(self.center, axes) -
+                  (0.5 * self.width, 0.5 * self.height, 0))
+        X = np.dot(self.X, axes) - offset
+        n = self.images.natoms
+        self.indices = X[:, 2].argsort()
+        P = self.P = X[:n, :2]
+        X1 = X[n:, :2].round().astype(int)
+        X2 = (np.dot(self.B, axes) - offset).round().astype(int)
+
+        if self.ui.get_widget('/MenuBar/ViewMenu/ShowBonds').get_active():
+            r = self.images.r * (0.65 * self.scale)
+        else:
+            r = self.images.r * self.scale
+        A = (P - r[:, None]).round().astype(int)
+        d = (2 * r).round().astype(int)
+        selected_gc = self.selected_gc
+
+        colors = self.colors
+        Z = self.images.Z
+        arc = self.pixmap.draw_arc
+        line = self.pixmap.draw_line
+        black_gc = self.black_gc
+        dynamic = self.images.dynamic
+        selected = self.images.selected
+        visible = self.images.visible
+        for a in self.indices:
+            if a < n:
+                ra = d[a]
+                if visible[a]:
+                    arc(colors[Z[a]], True, A[a, 0], A[a, 1], ra, ra, 0, 23040)
+                if  self.light_green_markings and self.atoms_to_rotate_0[a]:
+                    arc(self.green, False, A[a, 0] + 2, A[a, 1] + 2,
+                        ra - 4, ra - 4, 0, 23040)
+
+                if not dynamic[a]:
+                    R1 = int(0.14644 * ra)
+                    R2 = int(0.85355 * ra)
+                    line(black_gc,
+                         A[a, 0] + R1, A[a, 1] + R1,
+                         A[a, 0] + R2, A[a, 1] + R2)
+                    line(black_gc,
+                         A[a, 0] + R2, A[a, 1] + R1,
+                         A[a, 0] + R1, A[a, 1] + R2)
+                if selected[a]:
+                    arc(selected_gc, False, A[a, 0], A[a, 1], ra, ra, 0, 23040)
+                elif visible[a]:
+                    arc(black_gc, False, A[a, 0], A[a, 1], ra, ra, 0, 23040)
+            else:
+                a -= n
+                line(black_gc, X1[a, 0], X1[a, 1], X2[a, 0], X2[a, 1])
+
+        if self.ui.get_widget('/MenuBar/ViewMenu/ShowAxes').get_active():
+            self.draw_axes()
+
+        if self.images.nimages > 1:
+            self.draw_frame_number()
+            
+        self.drawing_area.window.draw_drawable(self.white_gc, self.pixmap,
+                                               0, 0, 0, 0,
+                                               self.width, self.height)
+
+        if status:
+            self.status()
+
+    def draw_axes(self):
+        L = np.zeros((10, 2, 3))
+        L[:3, 1] = self.axes * 15
+        L[3:5] = self.axes[0] * 20
+        L[5:7] = self.axes[1] * 20
+        L[7:] = self.axes[2] * 20
+        L[3:, :, :2] += (((-4, -5), (4,  5)), ((-4,  5), ( 4, -5)), 
+                         ((-4,  5), (0,  0)), ((-4, -5), ( 4,  5)), 
+                         ((-4,  5), (4,  5)), (( 4,  5), (-4, -5)), 
+                         ((-4, -5), (4, -5)))
+        L = L.round().astype(int)
+        L[:, :, 0] += 20
+        L[:, :, 1] = self.height - 20 - L[:, :, 1]
+        line = self.pixmap.draw_line
+        colors = ([self.black_gc] * 3 +
+                  [self.red] * 2 + [self.green] * 2 + [self.blue] * 3)
+        for i in L[:, 1, 2].argsort():
+            (a, b), (c, d) = L[i, :, :2]
+            line(colors[i], a, b, c, d)
+
+    digits = np.array(((1, 1, 1, 1, 1, 1, 0),
+                       (0, 1, 1, 0, 0, 0, 0),
+                       (1, 0, 1, 1, 0, 1, 1),
+                       (1, 1, 1, 1, 0, 0, 1),
+                       (0, 1, 1, 0, 1, 0, 1),
+                       (1, 1, 0, 1, 1, 0, 1),
+                       (1, 1, 0, 1, 1, 1, 1),
+                       (0, 1, 1, 1, 0, 0, 0),
+                       (1, 1, 1, 1, 1, 1, 1),
+                       (0, 1, 1, 1, 1, 0, 1)), bool)
+
+    bars = np.array(((0, 2, 1, 2),
+                     (1, 2, 1, 1),
+                     (1, 1, 1, 0),
+                     (1, 0, 0, 0),
+                     (0, 0, 0, 1),
+                     (0, 1, 0, 2),
+                     (0, 1, 1, 1))) * 5
+    
+    def draw_frame_number(self):
+        n = str(self.frame)
+        x = self.width - 3 - 8 * len(n)
+        y = self.height - 27
+        color = self.black_gc
+        line = self.pixmap.draw_line
+        for c in n:
+            bars = View.bars[View.digits[int(c)]]
+            for a, b, c, d in bars:
+                line(color, a + x, b + y, c + x, d + y)
+            x += 8
+        
+    def release(self, drawing_area, event):
+        if event.button != 1:
+            return
+
+        selected = self.images.selected
+        selected_ordered = self.images.selected_ordered
+
+        if event.time < self.t0 + 200:  # 200 ms
+            d = self.P - self.xy
+            hit = np.less((d**2).sum(1), (self.scale * self.images.r)**2)
+            for a in self.indices[::-1]:
+                if a < self.images.natoms and hit[a]:
+                    if event.state & gtk.gdk.CONTROL_MASK:
+                        selected[a] = not selected[a]
+                        if selected[a]: 
+                            selected_ordered += [a]
+                        elif len(selected_ordered) > 0:
+                            if selected_ordered[-1] == a:
+                                selected_ordered = selected_ordered[:-1]
+                            else:
+                                selected_ordered = []
+                    else:
+                        selected[:] = False
+                        selected[a] = True
+                        selected_ordered = [a]
+                    break
+            else:
+                selected[:] = False
+                selected_ordered = []
+            self.draw()
+        else:
+            A = (event.x, event.y)
+            C1 = np.minimum(A, self.xy)
+            C2 = np.maximum(A, self.xy)
+            hit = np.logical_and(self.P > C1, self.P < C2)
+            indices = np.compress(hit.prod(1), np.arange(len(hit)))
+            if not (event.state & gtk.gdk.CONTROL_MASK):
+                selected[:] = False
+            selected[indices] = True
+            if len(indices) == 1 and indices[0] not in self.images.selected_ordered: 
+                selected_ordered += [indices[0]]
+            elif len(indices) > 1:
+                selected_ordered = []
+            self.draw()
+
+        indices = np.arange(self.images.natoms)[self.images.selected]
+        if len(indices) != len(selected_ordered):
+            selected_ordered = []
+        self.images.selected_ordered = selected_ordered
+
+    def press(self, drawing_area, event):
+        self.button = event.button
+        self.xy = (event.x, event.y)
+        self.t0 = event.time
+        self.axes0 = self.axes
+        self.center0 = self.center
+        
+    def move(self, drawing_area, event):
+             
+        x, y, state = event.window.get_pointer()
+        x0, y0 = self.xy
+        if self.button == 1:
+            window = self.drawing_area.window
+            window.draw_drawable(self.white_gc, self.pixmap,
+                                 0, 0, 0, 0,
+                                 self.width, self.height)
+            x0 = int(round(x0))
+            y0 = int(round(y0))
+            window.draw_rectangle(self.selected_gc, False,
+                                  min(x, x0), min(y, y0),
+                                  abs(x - x0), abs(y - y0))
+            return
+        if self.button == 2:
+            return
+        if state & gtk.gdk.SHIFT_MASK:
+            self.center = (self.center0 -
+                           np.dot(self.axes, (x - x0, y0 - y, 0)) / self.scale)
+        else:
+            # Snap mode: the a-b angle and t should multipla of 15 degrees ???
+            a = x - x0
+            b = y0 - y
+            t = sqrt(a * a + b * b)
+            if t > 0:
+                a /= t
+                b /= t
+            else:
+                a = 1.0
+                b = 0.0
+            c = cos(0.01 * t)
+            s = -sin(0.01 * t)
+            rotation = np.array([(c * a * a + b * b, (c - 1) * b * a, s * a),
+                                 ((c - 1) * a * b, c * b * b + a * a, s * b),
+                                 (-s * a, -s * b, c)])
+            self.axes = np.dot(self.axes0, rotation)
+            if self.images.natoms > 0:
+                com = self.X[:self.images.natoms].mean(0) 
+            else:
+                com = self.images.A[self.frame].mean(0)
+            self.center = com - np.dot(com - self.center0,
+                                       np.dot(self.axes0, self.axes.T))
+        self.draw(status=False)
+        
+    # Create a new backing pixmap of the appropriate size
+    def configure_event(self, drawing_area, event):
+        if self.configured:
+            w = self.width
+            h = self.height
+        else:
+            self.colormap = self.drawing_area.get_colormap()
+            self.black_gc = self.drawing_area.get_style().black_gc
+            self.white_gc = self.drawing_area.get_style().white_gc
+            self.red = self.drawing_area.window.new_gc(
+                self.colormap.alloc_color(62345, 0, 0), line_width=2)
+            self.green = self.drawing_area.window.new_gc(
+                self.colormap.alloc_color(0, 54456, 0), line_width=2)
+            self.blue = self.drawing_area.window.new_gc(
+                self.colormap.alloc_color(0, 0, 54456), line_width=2)
+            self.selected_gc = self.drawing_area.window.new_gc(
+                self.colormap.alloc_color(0, 16456, 0),
+                line_width=3)
+            
+        x, y, self.width, self.height = drawing_area.get_allocation()
+        self.pixmap = gtk.gdk.Pixmap(drawing_area.window,
+                                     self.width, self.height)
+        if self.configured:
+            self.scale *= sqrt(1.0 * self.width * self.height / (w * h))
+            self.draw()
+        self.configured = True
+        
+    # Redraw the screen from the backing pixmap
+    def expose_event(self, drawing_area, event):
+        x , y, width, height = event.area
+        gc = self.white_gc
+        drawing_area.window.draw_drawable(gc, self.pixmap,
+                                          x, y, x, y, width, height)
+
+    def external_viewer(self, action):
+        name = action.get_name()
+        command = {'Avogadro' : 'avogadro',
+                   'XMakeMol': 'xmakemol -f',
+                   'RasMol':'rasmol -xyz',
+                   'VMD': 'vmd'}[name]
+        fd, filename = tempfile.mkstemp('.xyz', 'ase.gui-')
+        os.close(fd)
+        self.images.write(filename)
+        os.system('(%s %s &); (sleep 60; rm %s) &' %
+                  (command, filename, filename))
Index: ase/gui/gtkexcepthook.py
===================================================================
--- ase/gui/gtkexcepthook.py	(revision 0)
+++ ase/gui/gtkexcepthook.py	(revision 1)
@@ -0,0 +1,224 @@
+# vim: sw=4 ts=4:
+#
+# (c) 2003 Gustavo J A M Carneiro gjc at inescporto.pt
+# 	2004-2005 Filip Van Raemdonck
+#
+# http://www.daa.com.au/pipermail/pygtk/2003-August/005775.html
+# Message-ID: <1062087716.1196.5.camel@emperor.homelinux.net>
+# 	"The license is whatever you want."
+#
+# This file was downloaded from http://www.sysfs.be/downloads/
+# Minor adaptions 2009 by Martin Renold:
+# - let KeyboardInterrupt through
+# - print traceback to stderr before showing the dialog
+# - nonzero exit code when hitting the "quit" button
+# - suppress more dialogs while one is already active
+# see also http://faq.pygtk.org/index.py?req=show&file=faq20.010.htp
+# (The license is still whatever you want.)
+
+import inspect, linecache, pydoc, sys, traceback
+from cStringIO import StringIO
+from gettext import gettext as _
+from smtplib import SMTP
+
+import pygtk
+pygtk.require ('2.0')
+import gtk, pango
+
+def analyse_simple (exctyp, value, tb):
+	trace = StringIO()
+	traceback.print_exception (exctyp, value, tb, None, trace)
+	return trace
+
+def lookup (name, frame, lcls):
+	'''Find the value for a given name in the given frame'''
+	if name in lcls:
+		return 'local', lcls[name]
+	elif name in frame.f_globals:
+		return 'global', frame.f_globals[name]
+	elif '__builtins__' in frame.f_globals:
+		builtins = frame.f_globals['__builtins__']
+		if type (builtins) is dict:
+			if name in builtins:
+				return 'builtin', builtins[name]
+		else:
+			if hasattr (builtins, name):
+				return 'builtin', getattr (builtins, name)
+	return None, []
+
+def analyse (exctyp, value, tb):
+	import tokenize, keyword
+
+	trace = StringIO()
+	nlines = 3
+	frecs = inspect.getinnerframes (tb, nlines)
+	trace.write ('Traceback (most recent call last):\n')
+	for frame, fname, lineno, funcname, context, cindex in frecs:
+		trace.write ('  File "%s", line %d, ' % (fname, lineno))
+		args, varargs, varkw, lcls = inspect.getargvalues (frame)
+
+		def readline (lno=[lineno], *args):
+			if args: print args
+			try: return linecache.getline (fname, lno[0])
+			finally: lno[0] += 1
+		all, prev, name, scope = {}, None, '', None
+		for ttype, tstr, stup, etup, line in tokenize.generate_tokens (readline):
+			if ttype == tokenize.NAME and tstr not in keyword.kwlist:
+				if name:
+					if name[-1] == '.':
+						try:
+							val = getattr (prev, tstr)
+						except AttributeError:
+							# XXX skip the rest of this identifier only
+							break
+						name += tstr
+				else:
+					assert not name and not scope
+					scope, val = lookup (tstr, frame, lcls)
+					name = tstr
+				if hasattr(val, "shape") and len(val):
+					prev = val
+				elif val:
+					prev = val
+				#print '  found', scope, 'name', name, 'val', val, 'in', prev, 'for token', tstr
+			elif tstr == '.':
+				if prev:
+					name += '.'
+			else:
+				if name:
+					all[name] = (scope, prev)
+				prev, name, scope = None, '', None
+				if ttype == tokenize.NEWLINE:
+					break
+
+		trace.write (funcname +
+		  inspect.formatargvalues (args, varargs, varkw, lcls, formatvalue=lambda v: '=' + pydoc.text.repr (v)) + '\n')
+		trace.write (''.join (['    ' + x.replace ('\t', '  ') for x in filter (lambda a: a.strip(), context)]))
+		if len (all):
+			trace.write ('  variables: %s\n' % str (all))
+
+	trace.write ('%s: %s' % (exctyp.__name__, value))
+	return trace
+
+def _info (exctyp, value, tb):
+	global exception_dialog_active
+	if exctyp is KeyboardInterrupt:
+		return original_excepthook(exctyp, value, tb)
+	sys.stderr.write(analyse_simple (exctyp, value, tb).getvalue())
+	if exception_dialog_active:
+		return
+
+	gtk.gdk.pointer_ungrab()
+	gtk.gdk.keyboard_ungrab()
+
+	exception_dialog_active = True
+	trace = None
+	dialog = gtk.MessageDialog (parent=None, flags=0, type=gtk.MESSAGE_WARNING, buttons=gtk.BUTTONS_NONE)
+	dialog.set_title (_("Bug Detected"))
+	if gtk.check_version (2, 4, 0) is not None:
+		dialog.set_has_separator (False)
+
+	primary = _("<big><b>A programming error has been detected.</b></big>")
+	secondary = _("It probably isn't fatal, but the details should be reported to the developers nonetheless.")
+
+	try:
+		setsec = dialog.format_secondary_text
+	except AttributeError:
+		raise
+		dialog.vbox.get_children()[0].get_children()[1].set_markup ('%s\n\n%s' % (primary, secondary))
+		#lbl.set_property ("use-markup", True)
+	else:
+		del setsec
+		dialog.set_markup (primary)
+		dialog.format_secondary_text (secondary)
+
+	try:
+		email = feedback
+		dialog.add_button (_("Report..."), 3)
+	except NameError:
+		# could ask for an email address instead...
+		pass
+	dialog.add_button (_("Details..."), 2)
+	dialog.add_button (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
+	dialog.add_button (gtk.STOCK_QUIT, 1)
+
+	while True:
+		resp = dialog.run()
+		if resp == 3:
+			if trace == None:
+				trace = analyse (exctyp, value, tb)
+
+			# TODO: prettyprint, deal with problems in sending feedback, &tc
+			try:
+				server = smtphost
+			except NameError:
+				server = 'localhost'
+
+			message = 'From: buggy_application"\nTo: bad_programmer\nSubject: Exception feedback\n\n%s' % trace.getvalue()
+
+			s = SMTP()
+			s.connect (server)
+			s.sendmail (email, (email,), message)
+			s.quit()
+			break
+
+		elif resp == 2:
+			if trace == None:
+				trace = analyse (exctyp, value, tb)
+
+			# Show details...
+			details = gtk.Dialog (_("Bug Details"), dialog,
+			  gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
+			  (gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE, ))
+			details.set_property ("has-separator", False)
+
+			textview = gtk.TextView(); textview.show()
+			textview.set_editable (False)
+			textview.modify_font (pango.FontDescription ("Monospace"))
+
+			sw = gtk.ScrolledWindow(); sw.show()
+			sw.set_policy (gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+			sw.add (textview)
+			details.vbox.add (sw)
+			textbuffer = textview.get_buffer()
+			textbuffer.set_text (trace.getvalue())
+
+			monitor = gtk.gdk.screen_get_default ().get_monitor_at_window (dialog.window)
+			area = gtk.gdk.screen_get_default ().get_monitor_geometry (monitor)
+			try:
+				w = area.width // 1.6
+				h = area.height // 1.6
+			except SyntaxError:
+				# python < 2.2
+				w = area.width / 1.6
+				h = area.height / 1.6
+			details.set_default_size (int (w), int (h))
+
+			details.run()
+			details.destroy()
+
+		elif resp == 1 and gtk.main_level() > 0:
+			#gtk.main_quit() - why...? Exit code 0 is bad for IDEs.
+			sys.exit(1)
+			break
+		else:
+			break
+
+	dialog.destroy()
+	exception_dialog_active = False
+
+original_excepthook = sys.excepthook
+sys.excepthook = _info
+exception_dialog_active = False
+
+if __name__ == '__main__':
+	class X (object):
+		pass
+	x = X()
+	x.y = 'Test'
+	x.z = x
+	w = ' e'
+	#feedback = 'developer@bigcorp.comp'
+	#smtphost = 'mx.bigcorp.comp'
+	1, x.z.y, f, w
+	raise Exception (x.z.y + w)
Index: ase/gui/movie.py
===================================================================
--- ase/gui/movie.py	(revision 0)
+++ ase/gui/movie.py	(revision 1)
@@ -0,0 +1,118 @@
+#!/usr/bin/env python
+import gtk
+import gobject
+
+from ase.gui.languages import translate as _
+from ase.gui.widgets import pack
+
+
+class Movie(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        self.set_position(gtk.WIN_POS_NONE)
+        self.connect('destroy', self.close)
+        #self.connect('delete_event', self.exit2)
+        self.set_title('Movie')
+        vbox = gtk.VBox()
+        pack(vbox, gtk.Label(_('Image number:')))
+        self.frame_number = gtk.Adjustment(gui.frame, 0,
+                                           gui.images.nimages - 1,
+                                           1.0, 5.0)
+        self.frame_number.connect('value-changed', self.new_frame)
+
+        hscale = pack(vbox, gtk.HScale(self.frame_number))
+        hscale.set_update_policy(gtk.UPDATE_CONTINUOUS)
+        hscale.set_digits(0)
+
+        buttons = [gtk.Button(stock=gtk.STOCK_GOTO_FIRST),
+                   gtk.Button(stock=gtk.STOCK_GO_BACK),
+                   gtk.Button(stock=gtk.STOCK_GO_FORWARD),
+                   gtk.Button(stock=gtk.STOCK_GOTO_LAST)]
+
+        buttons[0].connect('clicked', self.click, -10000000)
+        buttons[1].connect('clicked', self.click, -1)
+        buttons[2].connect('clicked', self.click, 1)
+        buttons[3].connect('clicked', self.click, 10000000)
+
+        pack(vbox, buttons)
+
+        play, stop = pack(vbox, [gtk.Button(_('Play')),
+                                 gtk.Button('Stop')])
+        play.connect('clicked', self.play)
+        stop.connect('clicked', self.stop)
+
+        self.rock = pack(vbox, gtk.CheckButton('Rock'))
+
+        self.time = gtk.Adjustment(2.0, 0.5, 9.0, 0.2)
+        hscale = pack(vbox, gtk.HScale(self.time))
+        hscale.set_update_policy(gtk.UPDATE_DISCONTINUOUS)
+            
+        self.time.connect('value-changed', self.new_time)
+
+        self.add(vbox)
+
+        if gtk.pygtk_version < (2, 12):
+            self.set_tip = gtk.Tooltips().set_tip
+            self.set_tip(hscale, _('Adjust play time.'))
+        else:
+            hscale.set_tooltip_text(_('Adjust play time.'))
+        vbox.show()
+        self.show()
+        self.gui = gui
+        #gui.m=self
+        self.direction = 1
+        self.id = None
+        gui.register_vulnerable(self)
+
+    def notify_atoms_changed(self):
+        "Called by gui object when the atoms have changed."
+        self.destroy()
+        
+    def close(self, event):
+        self.stop()
+
+    def click(self, button, step):
+        i = max(0, min(self.gui.images.nimages - 1, self.gui.frame + step))
+        self.gui.set_frame(i)
+        self.frame_number.value = i
+        self.direction = cmp(step, 0)
+        
+    def new_frame(self, widget):
+        self.gui.set_frame(int(self.frame_number.value))
+
+    def play(self, widget=None):
+        if self.id is not None:
+            gobject.source_remove(self.id)
+
+        t = int(1000 * self.time.value / (self.gui.images.nimages - 1))
+        self.id = gobject.timeout_add(t, self.step)
+
+    def stop(self, widget=None):
+        if self.id is not None:
+            gobject.source_remove(self.id)
+            self.id = None
+
+    def step(self):
+        i = self.gui.frame
+        nimages = self.gui.images.nimages
+        
+        if self.rock.get_active():
+            if i == 0:
+                self.direction = 1
+            elif i == nimages - 1:
+                self.direction = -1
+            i += self.direction
+        else:
+            i = (i + self.direction + nimages) % nimages
+            
+        self.gui.set_frame(i)
+        self.frame_number.value = i
+        return True
+
+    def new_time(self, widget):
+        if self.id is not None:
+            self.play()
+
+if __name__ == '__main__':
+    import os
+    os.system('python gui.py')
Index: ase/gui/scaling.py
===================================================================
--- ase/gui/scaling.py	(revision 0)
+++ ase/gui/scaling.py	(revision 1)
@@ -0,0 +1,361 @@
+# encoding: utf-8
+
+"Module for homogeneous deformation and calculations of elastic constants."
+
+import gtk
+from ase.gui.simulation import Simulation
+from ase.gui.minimize import MinimizeMixin
+from ase.gui.energyforces import OutputFieldMixin
+from ase.gui.widgets import oops, pack, AseGuiCancelException
+import ase
+import numpy as np
+
+scaling_txt = """\
+This module is intended for calculating elastic constants by homogeneously
+deforming a system."""
+
+class HomogeneousDeformation(Simulation, MinimizeMixin, OutputFieldMixin):
+    "Window for homogeneous deformation and elastic constants."
+    
+    def __init__(self, gui):
+        Simulation.__init__(self, gui)
+        self.set_title("Homogeneous scaling")
+        vbox = gtk.VBox()
+        self.packtext(vbox, scaling_txt)
+        self.packimageselection(vbox, txt1="", txt2="")
+        self.start_radio_nth.set_active(True)
+        pack(vbox, gtk.Label(""))
+
+        # Radio buttons for choosing deformation mode.
+        tbl = gtk.Table(4,3)
+        for i, l in enumerate(('3D', '2D', '1D')):
+            l = l + " deformation   "
+            lbl = gtk.Label(l)
+            tbl.attach(lbl, i, i+1, 0, 1)
+        self.radio_bulk = gtk.RadioButton(None, "Bulk")
+        tbl.attach(self.radio_bulk, 0, 1, 1, 2)
+        self.radio_xy = gtk.RadioButton(self.radio_bulk, "xy-plane")
+        tbl.attach(self.radio_xy, 1, 2, 1, 2)
+        self.radio_xz = gtk.RadioButton(self.radio_bulk, "xz-plane")
+        tbl.attach(self.radio_xz, 1, 2, 2, 3)
+        self.radio_yz = gtk.RadioButton(self.radio_bulk, "yz-plane")
+        tbl.attach(self.radio_yz, 1, 2, 3, 4)
+        self.radio_x = gtk.RadioButton(self.radio_bulk, "x-axis")
+        tbl.attach(self.radio_x, 2, 3, 1, 2)
+        self.radio_y = gtk.RadioButton(self.radio_bulk, "y-axis")
+        tbl.attach(self.radio_y, 2, 3, 2, 3)
+        self.radio_z = gtk.RadioButton(self.radio_bulk, "z-axis")
+        tbl.attach(self.radio_z, 2, 3, 3, 4)
+        tbl.show_all()
+        pack(vbox, [tbl])
+        self.deformtable = [
+            (self.radio_bulk, (1,1,1)),
+            (self.radio_xy, (1,1,0)),
+            (self.radio_xz, (1,0,1)),
+            (self.radio_yz, (0,1,1)),
+            (self.radio_x, (1,0,0)),
+            (self.radio_y, (0,1,0)),
+            (self.radio_z, (0,0,1))]
+        self.deform_label = gtk.Label("")
+        pack(vbox, [self.deform_label])
+        self.choose_possible_deformations(first=True)
+
+        # Parameters for the deformation
+        framedef = gtk.Frame("Deformation:")
+        vbox2 = gtk.VBox()
+        vbox2.show()
+        framedef.add(vbox2)
+        self.max_scale = gtk.Adjustment(0.010, 0.001, 10.0, 0.001)
+        max_scale_spin = gtk.SpinButton(self.max_scale, 10.0, 3)
+        pack(vbox2, [gtk.Label("Maximal scale factor: "), max_scale_spin])
+        self.scale_offset = gtk.Adjustment(0.0, -10.0, 10.0, 0.001)
+        scale_offset_spin = gtk.SpinButton(self.scale_offset, 10.0, 3)
+        pack(vbox2, [gtk.Label("Scale offset: "), scale_offset_spin])
+        self.nsteps = gtk.Adjustment(5, 3, 100, 1)
+        nsteps_spin = gtk.SpinButton(self.nsteps, 1, 0)
+        pack(vbox2, [gtk.Label("Number of steps: "), nsteps_spin])
+        
+        # Atomic relaxations
+        framerel = gtk.Frame("Atomic relaxations:")
+        vbox2 = gtk.VBox()
+        vbox2.show()
+        framerel.add(vbox2)
+        self.radio_relax_on = gtk.RadioButton(None, "On   ")
+        self.radio_relax_off = gtk.RadioButton(self.radio_relax_on, "Off")
+        self.radio_relax_off.set_active(True)
+        pack(vbox2, [self.radio_relax_on, self.radio_relax_off])
+        self.make_minimize_gui(vbox2)
+        for r in (self.radio_relax_on, self.radio_relax_off):
+            r.connect("toggled", self.relax_toggled)
+        self.relax_toggled()
+        pack(vbox, [framedef, gtk.Label(" "), framerel])
+        pack(vbox, gtk.Label(""))
+        
+        # Results
+        pack(vbox, [gtk.Label("Results:")])
+        self.radio_results_optimal = gtk.RadioButton(
+            None, "Load optimal configuration")
+        self.radio_results_all =  gtk.RadioButton(
+            self.radio_results_optimal, "Load all configurations")
+        self.radio_results_optimal.set_active(True)
+        pack(vbox, [self.radio_results_optimal])
+        pack(vbox, [self.radio_results_all])
+
+        # Output field
+        outframe = self.makeoutputfield(None)
+        fitframe = gtk.Frame("Fit:")
+        vbox2 = gtk.VBox()
+        vbox2.show()
+        fitframe.add(vbox2)
+        self.radio_fit_2 = gtk.RadioButton(None, "2nd")
+        self.radio_fit_3 = gtk.RadioButton(self.radio_fit_2, "3rd")
+        self.radio_fit_2.connect("toggled", self.change_fit)
+        self.radio_fit_3.connect("toggled", self.change_fit)
+        self.radio_fit_3.set_active(True)
+        pack(vbox2, [gtk.Label("Order of fit: "), self.radio_fit_2,
+                     self.radio_fit_3])
+        pack(vbox2, [gtk.Label("")])
+        scrwin = gtk.ScrolledWindow()
+        scrwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+        self.fit_output = gtk.TextBuffer()
+        txtview = gtk.TextView(self.fit_output)
+        txtview.set_editable(False)
+        scrwin.add(txtview)
+        scrwin.show_all()
+        self.fit_win = scrwin
+        print "SIZE RQ:", scrwin.size_request()
+        vbox2.pack_start(scrwin, True, True, 0)
+        hbox = gtk.HBox(homogeneous=True)
+        for w in [outframe, fitframe]:
+            hbox.pack_start(w)
+            w.show()
+        pack(vbox, hbox)    
+        pack(vbox, gtk.Label(""))
+
+        # Status field
+        self.status_label = gtk.Label("")
+        pack(vbox, [self.status_label])
+        
+        # Run buttons etc.
+        self.makebutbox(vbox)
+        vbox.show()
+        self.add(vbox)
+        self.show()
+        self.gui.register_vulnerable(self)
+
+    def choose_possible_deformations(self, first=False):
+        """Turn on sensible radio buttons.
+
+        Only radio buttons corresponding to deformations in directions
+        with periodic boundary conditions should be turned on.
+        """
+        if self.setup_atoms():
+            pbc = self.atoms.get_pbc()
+        else:
+            pbc = [False, False, False]
+        for radio, requirement in self.deformtable:
+            ok = True
+            for i in range(3):
+                if requirement[i] and not pbc[i]:
+                    ok = False
+            radio.set_sensitive(ok)
+            if first and ok:
+                # The first acceptable choice, choose it to prevent
+                # inconsistent state.
+                radio.set_active(True)
+                first = False
+
+    def relax_toggled(self, *args):
+        "Turn minimization widgets on or off."
+        state = self.radio_relax_on.get_active()
+        for widget in (self.algo, self.fmax_spin, self.steps_spin):
+            widget.set_sensitive(state)
+        
+    def notify_atoms_changed(self):
+        "When atoms have changed, check for the number of images."
+        self.setupimageselection()
+        self.choose_possible_deformations()
+
+    def get_deformation_axes(self):
+        """Return which axes the user wants to deform along."""
+        for but, deform in self.deformtable:
+            if but.get_active():
+                return np.array(deform)
+        # No deformation chosen!
+        oops("No deformation chosen: Please choose a deformation mode.")
+        return False
+            
+    def run(self, *args):
+        """Make the deformation."""
+        self.output.set_text("")
+        if not self.setup_atoms():
+            return
+        deform_axes = self.get_deformation_axes()
+        if deform_axes is False:
+            return  #Nothing to do!
+
+        # Prepare progress bar
+        if self.radio_relax_on.get_active():
+            fmax = self.fmax.value
+            mininame = self.minimizers[self.algo.get_active()]
+            self.begin(mode="scale/min", algo=mininame, fmax=fmax,
+                       steps=self.steps.value, scalesteps=self.nsteps.value)
+        else:
+            self.begin(mode="scale", scalesteps=self.nsteps.value)
+        try:
+            logger_func = self.gui.simulation['progress'].get_logger_stream
+        except (KeyError, AttributeError):
+            logger = None
+        else:
+            logger = logger_func()  # Don't catch errors in the function.
+
+        # Display status message
+        self.status_label.set_text("Running ...")
+        self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                    gtk.gdk.color_parse('#AA0000'))
+        while gtk.events_pending():
+            gtk.main_iteration()
+
+        # Do the scaling
+        scale = self.max_scale.value
+        steps = np.linspace(-scale, scale, self.nsteps.value)
+        steps += self.scale_offset.value
+        undef_cell = self.atoms.get_cell()
+        results = []
+        txt = "Strain\t\tEnergy [eV]\n"
+        # If we load all configurations, prepare it.
+        if self.radio_results_all.get_active():
+            self.prepare_store_atoms()
+
+        try:
+            # Now, do the deformation
+            for i, d in enumerate(steps):
+                deformation = np.diag(1.0 + d * deform_axes)
+                self.atoms.set_cell(np.dot(undef_cell, deformation),
+                                    scale_atoms=True)
+                if self.gui.simulation.has_key('progress'):
+                    self.gui.simulation['progress'].set_scale_progress(i)
+                if self.radio_relax_on.get_active():
+                    algo = getattr(ase.optimize, mininame)
+                    minimizer = algo(self.atoms, logfile=logger)
+                    minimizer.run(fmax=fmax, steps=self.steps.value)
+                e = self.atoms.get_potential_energy()
+                results.append((d, e))
+                txt = txt + ("%.3f\t\t%.3f\n" % (d, e))
+                self.output.set_text(txt)
+                if self.radio_results_all.get_active():
+                    self.store_atoms()
+        except AseGuiCancelException:
+            # Update display to reflect cancellation of simulation.
+            self.status_label.set_text("Calculation CANCELLED.")
+            self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                        gtk.gdk.color_parse('#AA4000'))
+        except MemoryError:
+            self.status_label.set_text("Out of memory, consider using LBFGS instead")
+            self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                        gtk.gdk.color_parse('#AA4000'))
+            
+        else:
+            # Update display to reflect succesful end of simulation.
+            self.status_label.set_text("Calculation completed.")
+            self.status_label.modify_fg(gtk.STATE_NORMAL,
+                                        gtk.gdk.color_parse('#007700'))
+                     
+        if results:
+            self.do_fit(np.array(results))
+            if self.radio_results_optimal.get_active():
+                if self.minimum_ok:
+                    deformation = np.diag(1.0 + self.x0 * deform_axes)
+                    self.atoms.set_cell(np.dot(undef_cell, deformation),
+                                        scale_atoms=True)
+                    if self.radio_relax_on.get_active():
+                        if self.gui.simulation.has_key('progress'):
+                            self.gui.simulation['progress'].set_scale_progress(
+                                len(steps))
+                        algo = getattr(ase.optimize, mininame)
+                        minimizer = algo(self.atoms, logfile=logger)
+                        minimizer.run(fmax=fmax, steps=self.steps.value)
+                    # Store the optimal configuration.
+                    self.prepare_store_atoms()
+                    self.store_atoms()  
+                else:
+                    oops("No trustworthy minimum: Old configuration kept.")
+            self.activate_output()
+            self.gui.notify_vulnerable()
+        self.end()    
+            
+        # If we store all configurations: Open movie window and energy graph
+        if self.gui.images.nimages > 1:
+            self.gui.movie()
+            assert not np.isnan(self.gui.images.E[0])
+            if not self.gui.plot_graphs_newatoms():
+                expr = 'i, e - E[-1]'            
+                self.gui.plot_graphs(expr=expr)
+            # Continuations should use the best image
+            nbest = np.argmin(np.array(results)[:,1])
+            self.start_nth_adj.value = nbest
+            
+
+    def change_fit(self, widget):
+        "Repeat the fitting if the order is changed."
+        # It may be called both for the button being turned on and the
+        # one being turned off.  But we only want to call do_fit once.
+        # And only if there are already cached results (ie. if the
+        # order is changed AFTER the calculation is done).
+        if widget.get_active() and getattr(self, "results", None) is not None:
+            self.do_fit(None)
+                
+    def do_fit(self, results):
+        "Fit the results to a polynomial"
+        if results is None:
+            results = self.results  # Use cached results
+        else:
+            self.results = results  # Keep for next time
+        self.minimum_ok = False
+        if self.radio_fit_3.get_active():
+            order = 3
+        else:
+            order = 2
+            
+        if len(results) < 3:
+            txt = ("Insufficent data for a fit\n(only %i data points)\n"
+                   % (len(results),) )
+            order = 0
+        elif len(results) == 3 and order == 3:
+            txt = "REVERTING TO 2ND ORDER FIT\n(only 3 data points)\n\n"
+            order = 2
+        else:
+            txt = ""
+
+        if order > 0:
+            fit0 = np.poly1d(np.polyfit(results[:,0], results[:,1], order))
+            fit1 = np.polyder(fit0, 1)
+            fit2 = np.polyder(fit1, 1)
+
+            x0 = None
+            for t in np.roots(fit1):
+                if fit2(t) > 0:
+                    x0 = t
+                    break
+            if x0 is None:
+                txt = txt + "No minimum found!"
+            else:
+                e0 = fit0(x0)
+                e2 = fit2(x0)
+                txt += "E = "
+                if order == 3:
+                    txt += "A(x - x0)³ + "
+                txt += "B(x - x0)² + C\n\n"
+                txt += "B = %.5g eV\n" % (e2,)
+                txt += "C = %.5g eV\n" % (e0,)
+                txt += "x0 = %.5g\n" % (x0,)
+                lowest = self.scale_offset.value - self.max_scale.value
+                highest = self.scale_offset.value + self.max_scale.value
+                if x0 < lowest or x0 > highest:
+                    txt += "\nWARNING: Minimum is outside interval\n"
+                    txt += "It is UNRELIABLE!\n"
+                else:
+                    self.minimum_ok = True
+                    self.x0 = x0
+        self.fit_output.set_text(txt)
+        
Index: ase/gui/repeat.py
===================================================================
--- ase/gui/repeat.py	(revision 0)
+++ ase/gui/repeat.py	(revision 1)
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+import gtk
+from math import sqrt
+
+import numpy as np
+
+from ase.gui.languages import translate as _
+from ase.gui.widgets import pack, Help
+
+
+class Repeat(gtk.Window):
+    def __init__(self, gui):
+        gtk.Window.__init__(self)
+        self.set_title('Repeat')
+        vbox = gtk.VBox()
+        pack(vbox, gtk.Label(_('Repeat atoms:')))
+        self.repeat = [gtk.Adjustment(r, 1, 9, 1) for r in gui.images.repeat]
+        pack(vbox, [gtk.SpinButton(r, 0, 0) for r in self.repeat])
+        for r in self.repeat:
+            r.connect('value-changed', self.change)
+        button = pack(vbox, gtk.Button('Set unit cell'))
+        button.connect('clicked', self.set_unit_cell)
+        self.add(vbox)
+        vbox.show()
+        self.show()
+        self.gui = gui
+
+    def change(self, adjustment):
+        self.gui.images.repeat_images([int(r.value) for r in self.repeat])
+        self.gui.set_coordinates()
+        return True
+        
+    def set_unit_cell(self, button):
+        self.gui.images.A *= self.gui.images.repeat.reshape((3, 1))
+        self.gui.images.E *= self.gui.images.repeat.prod()
+        self.gui.images.repeat = np.ones(3, int)
+        for r in self.repeat:
+            r.value = 1
+        self.gui.set_coordinates()
+        
Index: ase/lattice/__init__.py
===================================================================
Index: ase/lattice/orthorhombic.py
===================================================================
--- ase/lattice/orthorhombic.py	(revision 0)
+++ ase/lattice/orthorhombic.py	(revision 1)
@@ -0,0 +1,144 @@
+"""Function-like objects creating orthorhombic lattices.
+
+The following lattice creators are defined:
+    SimleOrthorhombic
+    BaseCenteredOrthorhombic
+    BodyCenteredOrthorhombic
+    FaceCenteredOrthorhombic
+"""
+
+from ase.lattice.bravais import Bravais
+import numpy as np
+from ase.data import reference_states as _refstate
+
+
+class SimpleOrthorhombicFactory(Bravais):
+    "A factory for creating simple orthorhombic lattices."
+
+    # The name of the crystal structure in ChemicalElements
+    xtal_name = "orthorhombic"
+
+    # The natural basis vectors of the crystal structure
+    int_basis = np.array([[1, 0, 0],
+                          [0, 1, 0],
+                          [0, 0, 1]])
+    basis_factor = 1.0
+
+    # Converts the natural basis back to the crystallographic basis
+    inverse_basis = np.array([[1, 0, 0],
+                              [0, 1, 0],
+                              [0, 0, 1]])
+    inverse_basis_factor = 1.0
+
+    def get_lattice_constant(self):
+        "Get the lattice constant of an element with orhtorhombic crystal structure."
+        if _refstate[self.atomicnumber]['symmetry'].lower() != self.xtal_name:
+            raise ValueError, (("Cannot guess the %s lattice constant of"
+                                + " an element with crystal structure %s.")
+                               % (self.xtal_name,
+                                  _refstate[self.atomicnumber]['symmetry']))
+        return _refstate[self.atomicnumber].copy()
+
+    def make_crystal_basis(self):
+        "Make the basis matrix for the crystal unit cell and the system unit cell."
+        lattice = self.latticeconstant
+        if type(lattice) == type({}):
+            a = lattice['a']
+            try:
+                b = lattice['b']
+            except KeyError:
+                b = a * lattice['b/a']
+            try:
+                c = lattice['c']
+            except KeyError:
+                c = a * lattice['c/a']
+        else:
+            if len(lattice) == 3:
+                (a,b,c) = lattice
+            else:
+                raise ValueError, "Improper lattice constants for orthorhombic crystal."
+
+        lattice = np.array([[a,0,0],[0,b,0],[0,0,c]])
+        self.latticeconstant = lattice
+        self.miller_basis = lattice
+        self.crystal_basis = (self.basis_factor *
+                              np.dot(self.int_basis, lattice))
+        self.basis = np.dot(self.directions, self.crystal_basis)
+        self.check_basis_volume()
+
+    def check_basis_volume(self):
+        "Check the volume of the unit cell."
+        vol1 = abs(np.linalg.det(self.basis))
+        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant)
+        if self.bravais_basis is not None:
+            vol2 /= len(self.bravais_basis)
+        if abs(vol1-vol2) > 1e-5:
+            print "WARNING: Got volume %f, expected %f" % (vol1, vol2)
+
+SimpleOrthorhombic = SimpleOrthorhombicFactory()
+
+class BaseCenteredOrthorhombicFactory(SimpleOrthorhombicFactory):
+    "A factory for creating base-centered orthorhombic lattices."
+
+    # The natural basis vectors of the crystal structure
+    int_basis = np.array([[1, -1, 0],
+                          [1, 1, 0],
+                          [0, 0, 2]])
+    basis_factor = 0.5
+
+    # Converts the natural basis back to the crystallographic basis
+    inverse_basis = np.array([[1, 1, 0],
+                              [-1, 1, 0],
+                              [0, 0, 1]])
+    inverse_basis_factor = 1.0
+
+    def check_basis_volume(self):
+        "Check the volume of the unit cell."
+        vol1 = abs(np.linalg.det(self.basis))
+        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant) / 2.0
+        if abs(vol1-vol2) > 1e-5:
+            print "WARNING: Got volume %f, expected %f" % (vol1, vol2)
+
+BaseCenteredOrthorhombic = BaseCenteredOrthorhombicFactory()
+
+class BodyCenteredOrthorhombicFactory(SimpleOrthorhombicFactory):
+    "A factory for creating body-centered orthorhombic lattices."
+
+    int_basis = np.array([[-1, 1, 1],
+                          [1, -1, 1],
+                          [1, 1, -1]])
+    basis_factor = 0.5
+    inverse_basis = np.array([[0, 1, 1],
+                              [1, 0, 1],
+                              [1, 1, 0]])
+    inverse_basis_factor = 1.0
+
+    def check_basis_volume(self):
+        "Check the volume of the unit cell."
+        vol1 = abs(np.linalg.det(self.basis))
+        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant) / 2.0
+        if abs(vol1-vol2) > 1e-5:
+            print "WARNING: Got volume %f, expected %f" % (vol1, vol2)
+
+BodyCenteredOrthorhombic = BodyCenteredOrthorhombicFactory()
+class FaceCenteredOrthorhombicFactory(SimpleOrthorhombicFactory):
+    "A factory for creating face-centered orthorhombic lattices."
+
+    int_basis = np.array([[0, 1, 1],
+                          [1, 0, 1],
+                          [1, 1, 0]])
+    basis_factor = 0.5
+    inverse_basis = np.array([[-1, 1, 1],
+                              [1, -1, 1],
+                              [1, 1, -1]])
+    inverse_basis_factor = 1.0
+
+    def check_basis_volume(self):
+        "Check the volume of the unit cell."
+        vol1 = abs(np.linalg.det(self.basis))
+        vol2 = self.calc_num_atoms() * np.linalg.det(self.latticeconstant) / 4.0
+        if abs(vol1-vol2) > 1e-5:
+            print "WARNING: Got volume %f, expected %f" % (vol1, vol2)
+
+FaceCenteredOrthorhombic = FaceCenteredOrthorhombicFactory()
+
Index: ase/lattice/tetragonal.py
===================================================================
--- ase/lattice/tetragonal.py	(revision 0)
+++ ase/lattice/tetragonal.py	(revision 1)
@@ -0,0 +1,44 @@
+"""Function-like objects creating tetragonal lattices.
+
+The following lattice creators are defined:
+    SimleTetragonal
+    CenteredTetragonal
+"""
+
+from ase.lattice.orthorhombic import SimpleOrthorhombicFactory,\
+     BodyCenteredOrthorhombicFactory
+import numpy as np
+from ase.data import reference_states as _refstate
+
+
+class _Tetragonalize:
+    "A mixin class for implementing tetragonal crystals as orthorhombic ones."
+
+    # The name of the crystal structure in ChemicalElements
+    xtal_name = "tetragonal"
+
+    def make_crystal_basis(self):
+        lattice = self.latticeconstant
+        if type(lattice) == type({}):
+            lattice['b/a'] = 1.0
+        else:
+            if len(lattice) == 2:
+                lattice = (lattice[0], lattice[0], lattice[1])
+            else:
+                raise ValueError, "Improper lattice constants for tetragonal crystal."
+        self.latticeconstant = lattice
+        self.orthobase.make_crystal_basis(self)
+
+class SimpleTetragonalFactory(_Tetragonalize, SimpleOrthorhombicFactory):
+    "A factory for creating simple tetragonal lattices."
+    orthobase = SimpleOrthorhombicFactory
+
+SimpleTetragonal = SimpleTetragonalFactory()
+
+
+class CenteredTetragonalFactory(_Tetragonalize,
+                                BodyCenteredOrthorhombicFactory):
+    "A factory for creating centered tetragonal lattices."
+    orthobase = BodyCenteredOrthorhombicFactory
+
+CenteredTetragonal = CenteredTetragonalFactory()
Index: ase/lattice/triclinic.py
===================================================================
--- ase/lattice/triclinic.py	(revision 0)
+++ ase/lattice/triclinic.py	(revision 1)
@@ -0,0 +1,83 @@
+"""Function-like object creating triclinic lattices.
+
+The following lattice creator is defined:
+    Triclinic
+"""
+
+from ase.lattice.bravais import Bravais
+import numpy as np
+from ase.data import reference_states as _refstate
+
+class TriclinicFactory(Bravais):
+    "A factory for creating triclinic lattices."
+
+    # The name of the crystal structure in ChemicalElements
+    xtal_name = "triclinic"
+
+    # The natural basis vectors of the crystal structure
+    int_basis = np.array([[1, 0, 0],
+                          [0, 1, 0],
+                          [0, 0, 1]])
+    basis_factor = 1.0
+
+    # Converts the natural basis back to the crystallographic basis
+    inverse_basis = np.array([[1, 0, 0],
+                              [0, 1, 0],
+                              [0, 0, 1]])
+    inverse_basis_factor = 1.0
+
+    def get_lattice_constant(self):
+        "Get the lattice constant of an element with triclinic crystal structure."
+        if _refstate[self.atomicnumber]['symmetry'].lower() != self.xtal_name:
+            raise ValueError, (("Cannot guess the %s lattice constant of"
+                                + " an element with crystal structure %s.")
+                               % (self.xtal_name,
+                                  _refstate[self.atomicnumber]['symmetry']))
+        return _refstate[self.atomicnumber].copy()
+
+
+    def make_crystal_basis(self):
+        "Make the basis matrix for the crystal unit cell and the system unit cell."
+        lattice = self.latticeconstant
+        if type(lattice) == type({}):
+            a = lattice['a']
+            try:
+                b = lattice['b']
+            except KeyError:
+                b = a * lattice['b/a']
+            try:
+                c = lattice['c']
+            except KeyError:
+                c = a * lattice['c/a']
+            alpha = lattice['alpha']
+            beta = lattice['beta']
+            gamma = lattice['gamma']
+        else:
+            if len(lattice) == 6:
+                (a,b,c,alpha,beta,gamma) = lattice
+            else:
+                raise ValueError, "Improper lattice constants for triclinic crystal."
+
+        degree = np.pi / 180.0
+        cosa = np.cos(alpha*degree)
+        cosb = np.cos(beta*degree)
+        sinb = np.sin(beta*degree)
+        cosg = np.cos(gamma*degree)
+        sing = np.sin(gamma*degree)
+        lattice = np.array([[a,0,0],
+                            [b*cosg, b*sing,0],
+                            [c*cosb, c*(cosa-cosb*cosg)/sing,
+                             c*np.sqrt(sinb**2 - ((cosa-cosb*cosg)/sing)**2)]])
+        self.latticeconstant = lattice
+        self.miller_basis = lattice
+        self.crystal_basis = (self.basis_factor *
+                              np.dot(self.int_basis, lattice))
+        self.basis = np.dot(self.directions, self.crystal_basis)
+        assert abs(np.dot(lattice[0],lattice[1]) - a*b*cosg) < 1e-5
+        assert abs(np.dot(lattice[0],lattice[2]) - a*c*cosb) < 1e-5
+        assert abs(np.dot(lattice[1],lattice[2]) - b*c*cosa) < 1e-5
+        assert abs(np.dot(lattice[0],lattice[0]) - a*a) < 1e-5
+        assert abs(np.dot(lattice[1],lattice[1]) - b*b) < 1e-5
+        assert abs(np.dot(lattice[2],lattice[2]) - c*c) < 1e-5
+
+Triclinic = TriclinicFactory()
Index: ase/lattice/spacegroup/spacegroup.dat
===================================================================
--- ase/lattice/spacegroup/spacegroup.dat	(revision 0)
+++ ase/lattice/spacegroup/spacegroup.dat	(revision 1)
@@ -0,0 +1,6665 @@
+1     P 1
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  1 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+2     P -1
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  1 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+3     P 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+3     P 2
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+4     P 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+
+4     P 21
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+5     C 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+5     A 2
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+6     P m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+6     P m
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+7     P c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+7     P n
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+
+8     C m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+8     A m
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+9     C c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+9     A n
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+
+10    P 2/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+10    P 2/m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+11    P 21/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+
+11    P 21/m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+12    C 2/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+12    A 2/m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+13    P 2/c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+13    P 2/n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+
+14    P 21/c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+
+14    P 21/n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+15    C 2/c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+15    A 2/n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  2 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+
+16    P 2 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+17    P 2 2 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+18    P 21 21 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+19    P 21 21 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+20    C 2 2 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+21    C 2 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+22    F 2 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+23    I 2 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+24    I 21 21 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+25    P m m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+26    P m c 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+27    P c c 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+28    P m a 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+
+29    P c a 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+
+30    P n c 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+
+31    P m n 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+32    P b a 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+33    P n a 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+34    P n n 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+35    C m m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+36    C m c 21
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+37    C c c 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+38    A m m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+39    A b m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+
+40    A m a 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+
+41    A b a 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000 -0.5000000000
+     0.0000000000  0.5000000000  0.5000000000
+     1.0000000000  0.0000000000  0.0000000000
+  reciprocal primitive cell
+      0   1  -1
+      0   1   1
+      1   0   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+42    F m m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+43    F d d 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.2500000000  0.2500000000  0.2500000000
+   -1  0  0     0  1  0     0  0  1    0.2500000000  0.2500000000  0.2500000000
+
+44    I m m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+45    I b a 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+46    I m a 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+
+47    P m m m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+48    P n n n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+48    P n n n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+
+49    P c c m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+50    P b a n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+50    P b a n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+
+51    P m m a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+
+52    P n n a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+
+53    P m n a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+54    P c c A
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+
+55    P b a m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+56    P c c n
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+
+57    P b c m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+
+58    P n n m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+59    P m m n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+59    P m m n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+
+60    P b c n
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+61    P b c a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+62    P n m a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+63    C m c m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+64    C m c a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+65    C m m m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+66    C c c m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+67    C m m a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+68    C c c a
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+
+68    C c c a
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.5000000000 -0.5000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1  -1   0
+      1   1   0
+      0   0   1
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+
+69    F m m m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+70    F d d d
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0  1  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0 -1  0     0  0  1    0.2500000000  0.2500000000  0.2500000000
+   -1  0  0     0  1  0     0  0  1    0.2500000000  0.2500000000  0.2500000000
+
+70    F d d d
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.7500000000  0.7500000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.7500000000  0.0000000000  0.7500000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.7500000000  0.7500000000
+
+71    I m m m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+72    I b a m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+73    I b c a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+74    I m m a
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+75    P 4
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+76    P 41
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.7500000000
+
+77    P 42
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+78    P 43
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.7500000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.2500000000
+
+79    I 4
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+80    I 41
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+
+81    P -4
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+82    I -4
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+83    P 4/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+84    P 42/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+85    P 4/n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+85    P 4/n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+
+86    P 42/n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+86    P 42/n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+
+87    I 4/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+88    I 41/a
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+   -1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.2500000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.7500000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+88    I 41/a
+  setting 2
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  4 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.7500000000  0.2500000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.7500000000  0.7500000000  0.7500000000
+
+89    P 4 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+90    P 4 21 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+91    P 41 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.7500000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.7500000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.2500000000
+
+92    P 41 21 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.7500000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.2500000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.7500000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+93    P 42 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+94    P 42 21 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+95    P 43 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.7500000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.2500000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.2500000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.7500000000
+
+96    P 43 21 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.7500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.2500000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.7500000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.2500000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+97    I 4 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+98    I 41 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.7500000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+99    P 4 m m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+100   P 4 b n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+101   P 42 c m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+102   P 42 n m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+103   P 4 c c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+104   P 4 n c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+105   P 42 m c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+106   P 42 b c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+107   I 4 m m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+108   I 4 c m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+109   I 41 m d
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+
+110   I 41 c d
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.7500000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.2500000000
+
+111   P -4 2 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+112   P -4 2 c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+113   P -4 21 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+114   P -4 21 c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+115   P -4 m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+116   P -4 c 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+117   P -4 b 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+118   P -4 n 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+119   I -4 m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+120   I -4 c 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+121   I -4 2 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+122   I -4 2 d
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.7500000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.7500000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+
+123   P 4/m m m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+124   P 4/m c c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+125   P 4/n b m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+125   P 4/n b m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+126   P 4/n n c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+126   P 4/n n c
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+127   P 4/m b m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+128   P 4/m n c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+129   P 4/n m m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+129   P 4/n m m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+130   P 4/n c c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+130   P 4/n c c
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+131   P 42/m m c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+132   P 42/m c m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+133   P 42/n b c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+133   P 42/n b c
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+134   P 42/n n m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+134   P 42/n n m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+
+135   P 42/m b c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+
+136   P 42/m n m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+137   P 42/n m c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+
+137   P 42/n m c
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+138   P 42/n c m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+
+138   P 42/n c m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+139   I 4/m m m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+140   I 4/m c m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+141   I 41/a m d
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.7500000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.2500000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.7500000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+
+141   I 41/a m d
+  setting 2
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.2500000000  0.7500000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.2500000000  0.7500000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.2500000000  0.7500000000  0.2500000000
+    0 -1  0    -1  0  0     0  0 -1    0.2500000000  0.2500000000  0.7500000000
+
+142   I 41/a c d
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  16 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.7500000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.2500000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.7500000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.2500000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.7500000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.2500000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.7500000000
+
+142   I 41/a c d
+  setting 2
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  8 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.2500000000  0.7500000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.2500000000  0.7500000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.2500000000  0.7500000000  0.7500000000
+    0 -1  0    -1  0  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+
+143   P 3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+144   P 31
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+
+145   P 32
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+
+146   R 3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.6666666667  0.3333333333  0.3333333333
+    -0.3333333333  0.3333333333  0.3333333333
+    -0.3333333333 -0.6666666667  0.3333333333
+  reciprocal primitive cell
+      1   1   0
+     -1   1   1
+      0  -1   1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.6666666667  0.3333333333  0.3333333333
+     0.3333333333  0.6666666667  0.6666666667
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+146   R 3
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     0.3333333333  0.6666666667 -0.3333333333
+     0.3333333333 -0.3333333333 -0.3333333333
+    -0.6666666667 -0.3333333333 -0.3333333333
+  reciprocal primitive cell
+      1   1   0
+      1  -1  -1
+     -1   0  -1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.3333333333  0.6666666667  0.3333333333
+     0.6666666667  0.3333333333  0.6666666667
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+147   P -3
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+148   R -3
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.6666666667  0.3333333333  0.3333333333
+    -0.3333333333  0.3333333333  0.3333333333
+    -0.3333333333 -0.6666666667  0.3333333333
+  reciprocal primitive cell
+      1   1   0
+     -1   1   1
+      0  -1   1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.6666666667  0.3333333333  0.3333333333
+     0.3333333333  0.6666666667  0.6666666667
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+148   R -3
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.3333333333  0.6666666667 -0.3333333333
+     0.3333333333 -0.3333333333 -0.3333333333
+    -0.6666666667 -0.3333333333 -0.3333333333
+  reciprocal primitive cell
+      1   1   0
+      1  -1  -1
+     -1   0  -1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.3333333333  0.6666666667  0.3333333333
+     0.6666666667  0.3333333333  0.6666666667
+  3 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+149   P 3 1 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+150   P 3 2 1
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+151   P 31 1 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+152   P 31 2 1
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+
+153   P 32 1 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+154   P 32 2 1
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+
+155   R 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.6666666667  0.3333333333  0.3333333333
+    -0.3333333333  0.3333333333  0.3333333333
+    -0.3333333333 -0.6666666667  0.3333333333
+  reciprocal primitive cell
+      1   1   0
+     -1   1   1
+      0  -1   1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.6666666667  0.3333333333  0.3333333333
+     0.3333333333  0.6666666667  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+155   R 3 2
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     0.3333333333  0.6666666667 -0.3333333333
+     0.3333333333 -0.3333333333 -0.3333333333
+    -0.6666666667 -0.3333333333 -0.3333333333
+  reciprocal primitive cell
+      1   1   0
+      1  -1  -1
+     -1   0  -1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.3333333333  0.6666666667  0.3333333333
+     0.6666666667  0.3333333333  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+156   P 3 m 1
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+157   P 3 1 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+158   P 3 c 1
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+159   P 3 1 c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+160   R 3 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.6666666667  0.3333333333  0.3333333333
+    -0.3333333333  0.3333333333  0.3333333333
+    -0.3333333333 -0.6666666667  0.3333333333
+  reciprocal primitive cell
+      1   1   0
+     -1   1   1
+      0  -1   1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.6666666667  0.3333333333  0.3333333333
+     0.3333333333  0.6666666667  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+160   R 3 m
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     0.3333333333  0.6666666667 -0.3333333333
+     0.3333333333 -0.3333333333 -0.3333333333
+    -0.6666666667 -0.3333333333 -0.3333333333
+  reciprocal primitive cell
+      1   1   0
+      1  -1  -1
+     -1   0  -1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.3333333333  0.6666666667  0.3333333333
+     0.6666666667  0.3333333333  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+161   R 3 c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.6666666667  0.3333333333  0.3333333333
+    -0.3333333333  0.3333333333  0.3333333333
+    -0.3333333333 -0.6666666667  0.3333333333
+  reciprocal primitive cell
+      1   1   0
+     -1   1   1
+      0  -1   1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.6666666667  0.3333333333  0.3333333333
+     0.3333333333  0.6666666667  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+161   R 3 c
+  setting 2
+  centrosymmetric 0
+  primitive cell
+     0.3333333333  0.6666666667 -0.3333333333
+     0.3333333333 -0.3333333333 -0.3333333333
+    -0.6666666667 -0.3333333333 -0.3333333333
+  reciprocal primitive cell
+      1   1   0
+      1  -1  -1
+     -1   0  -1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.3333333333  0.6666666667  0.3333333333
+     0.6666666667  0.3333333333  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+162   P -3 1 m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+163   P -3 1 c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+164   P -3 m 1
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+165   P -3 c 1
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+166   R -3 m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.6666666667  0.3333333333  0.3333333333
+    -0.3333333333  0.3333333333  0.3333333333
+    -0.3333333333 -0.6666666667  0.3333333333
+  reciprocal primitive cell
+      1   1   0
+     -1   1   1
+      0  -1   1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.6666666667  0.3333333333  0.3333333333
+     0.3333333333  0.6666666667  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+166   R -3 m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.3333333333  0.6666666667 -0.3333333333
+     0.3333333333 -0.3333333333 -0.3333333333
+    -0.6666666667 -0.3333333333 -0.3333333333
+  reciprocal primitive cell
+      1   1   0
+      1  -1  -1
+     -1   0  -1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.3333333333  0.6666666667  0.3333333333
+     0.6666666667  0.3333333333  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+167   R -3 c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.6666666667  0.3333333333  0.3333333333
+    -0.3333333333  0.3333333333  0.3333333333
+    -0.3333333333 -0.6666666667  0.3333333333
+  reciprocal primitive cell
+      1   1   0
+     -1   1   1
+      0  -1   1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.6666666667  0.3333333333  0.3333333333
+     0.3333333333  0.6666666667  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+167   R -3 c
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.3333333333  0.6666666667 -0.3333333333
+     0.3333333333 -0.3333333333 -0.3333333333
+    -0.6666666667 -0.3333333333 -0.3333333333
+  reciprocal primitive cell
+      1   1   0
+      1  -1  -1
+     -1   0  -1
+  3 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.3333333333  0.6666666667  0.3333333333
+     0.6666666667  0.3333333333  0.6666666667
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+168   P 6
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+169   P 61
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.8333333333
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.1666666667
+
+170   P 65
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.1666666667
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.8333333333
+
+171   P 62
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+
+172   P 64
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+
+173   P 63
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+174   P -6
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+175   P 6/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+176   P 63/m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  6 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+177   P 6 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+178   P 61 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.8333333333
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.1666666667
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.8333333333
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.1666666667
+
+179   P 65 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.1666666667
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.8333333333
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.1666666667
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.8333333333
+
+180   P 62 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+
+181   P 64 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.3333333333
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.6666666667
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.3333333333
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.6666666667
+
+182   P 63 2 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+183   P 6 m m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+184   P 6 c c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+185   P 63 c m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+186   P 63 m c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+187   P -6 m 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+188   P -6 c 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+189   P -6 2 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+
+190   P -6 2 c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+
+191   P 6/m m m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+192   P 6/m c c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+193   P 63/m c m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+
+194   P 63/m m c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0    -1  1  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    1 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1 -1  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0    -1  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  1  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     1 -1  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+
+195   P 2 3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+196   F 2 3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+197   I 2 3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+198   P 21 3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+
+199   I 21 3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+
+200   P m -3
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+201   P n -3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     1  0  0     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     1  0  0     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1    -1  0  0     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0  1     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+201   P n -3
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.5000000000  0.0000000000
+
+202   F m -3
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+203   F d -3
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0  1  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0 -1  0     0  0  1    0.2500000000  0.2500000000  0.2500000000
+   -1  0  0     0  1  0     0  0  1    0.2500000000  0.2500000000  0.2500000000
+    0  0 -1    -1  0  0     0 -1  0    0.2500000000  0.2500000000  0.2500000000
+    0  0 -1     1  0  0     0  1  0    0.2500000000  0.2500000000  0.2500000000
+    0  0  1     1  0  0     0 -1  0    0.2500000000  0.2500000000  0.2500000000
+    0  0  1    -1  0  0     0  1  0    0.2500000000  0.2500000000  0.2500000000
+    0 -1  0     0  0 -1    -1  0  0    0.2500000000  0.2500000000  0.2500000000
+    0  1  0     0  0 -1     1  0  0    0.2500000000  0.2500000000  0.2500000000
+    0 -1  0     0  0  1     1  0  0    0.2500000000  0.2500000000  0.2500000000
+    0  1  0     0  0  1    -1  0  0    0.2500000000  0.2500000000  0.2500000000
+
+203   F d -3
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.2500000000  0.2500000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.2500000000  0.0000000000  0.2500000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.2500000000  0.2500000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.2500000000  0.2500000000
+    0  0 -1    -1  0  0     0  1  0    0.2500000000  0.2500000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.2500000000  0.0000000000  0.2500000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.2500000000  0.0000000000  0.2500000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.2500000000  0.2500000000
+    0 -1  0     0  0 -1     1  0  0    0.2500000000  0.2500000000  0.0000000000
+
+204   I m -3
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+205   P a -3
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+
+206   I a -3
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  12 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+
+207   P 4 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+208   P 42 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+209   F 4 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+210   F 41 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.7500000000
+    0 -1  0    -1  0  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.7500000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.7500000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.7500000000
+   -1  0  0     0  0  1     0  1  0    0.7500000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.7500000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.7500000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.7500000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.7500000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.2500000000  0.2500000000  0.2500000000
+
+211   I 4 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+212   P 43 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.2500000000  0.7500000000  0.7500000000
+    0 -1  0    -1  0  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.7500000000  0.7500000000  0.2500000000
+    0 -1  0     1  0  0     0  0  1    0.7500000000  0.2500000000  0.7500000000
+    1  0  0     0  0  1     0 -1  0    0.2500000000  0.7500000000  0.7500000000
+   -1  0  0     0  0  1     0  1  0    0.7500000000  0.2500000000  0.7500000000
+   -1  0  0     0  0 -1     0 -1  0    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0  0 -1     0  1  0    0.7500000000  0.7500000000  0.2500000000
+    0  0  1     0  1  0    -1  0  0    0.2500000000  0.7500000000  0.7500000000
+    0  0  1     0 -1  0     1  0  0    0.7500000000  0.7500000000  0.2500000000
+    0  0 -1     0  1  0     1  0  0    0.7500000000  0.2500000000  0.7500000000
+    0  0 -1     0 -1  0    -1  0  0    0.2500000000  0.2500000000  0.2500000000
+
+213   P 41 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.2500000000
+    0 -1  0    -1  0  0     0  0 -1    0.7500000000  0.7500000000  0.7500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.2500000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.2500000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.2500000000
+   -1  0  0     0  0  1     0  1  0    0.2500000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.7500000000  0.7500000000  0.7500000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.2500000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.2500000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.2500000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.2500000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.7500000000  0.7500000000  0.7500000000
+
+214   I 41 3 2
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.2500000000
+    0 -1  0    -1  0  0     0  0 -1    0.7500000000  0.7500000000  0.7500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.2500000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.2500000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.2500000000
+   -1  0  0     0  0  1     0  1  0    0.2500000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.7500000000  0.7500000000  0.7500000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.2500000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.2500000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.2500000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.2500000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.7500000000  0.7500000000  0.7500000000
+
+215   P -4 3 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+216   F -4 3 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+217   I -4 3 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+218   P -4 3 n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+219   F -4 3 c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+220   I -4 3 d
+  setting 1
+  centrosymmetric 0
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.2500000000  0.2500000000  0.2500000000
+    0 -1  0    -1  0  0     0  0  1    0.2500000000  0.7500000000  0.7500000000
+    0  1  0    -1  0  0     0  0 -1    0.7500000000  0.2500000000  0.7500000000
+    0 -1  0     1  0  0     0  0 -1    0.7500000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0  1  0    0.2500000000  0.2500000000  0.2500000000
+   -1  0  0     0  0  1     0 -1  0    0.7500000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0  1  0    0.2500000000  0.7500000000  0.7500000000
+    1  0  0     0  0 -1     0 -1  0    0.7500000000  0.2500000000  0.7500000000
+    0  0  1     0  1  0     1  0  0    0.2500000000  0.2500000000  0.2500000000
+    0  0  1     0 -1  0    -1  0  0    0.7500000000  0.2500000000  0.7500000000
+    0  0 -1     0  1  0    -1  0  0    0.7500000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0     1  0  0    0.2500000000  0.7500000000  0.7500000000
+
+221   P m -3 m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+222   P n -3 n
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  48 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     1  0  0     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     1  0  0     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1    -1  0  0     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0  1     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+222   P n -3 n
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  0  1     0  1  0    0.5000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.5000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.5000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.5000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.5000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+223   P m -3 n
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+224   P n -3 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  48 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     1  0  0     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     1  0  0     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1    -1  0  0     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0  1     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+224   P n -3 m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     1.0000000000  0.0000000000  0.0000000000
+     0.0000000000  1.0000000000  0.0000000000
+     0.0000000000  0.0000000000  1.0000000000
+  reciprocal primitive cell
+      1   0   0
+      0   1   0
+      0   0   1
+  1 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0  1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+225   F m -3 m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+226   F m -3 c
+  setting 1
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0  1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0  1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+227   F d -3 m
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  48 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.7500000000
+    0 -1  0    -1  0  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.7500000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.7500000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.7500000000
+   -1  0  0     0  0  1     0  1  0    0.7500000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.7500000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.7500000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.7500000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.7500000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.2500000000  0.2500000000  0.2500000000
+   -1  0  0     0 -1  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0  1  0     0  0 -1    0.2500000000  0.7500000000  0.7500000000
+    1  0  0     0 -1  0     0  0  1    0.7500000000  0.7500000000  0.2500000000
+   -1  0  0     0  1  0     0  0  1    0.7500000000  0.2500000000  0.7500000000
+    0  0 -1    -1  0  0     0 -1  0    0.2500000000  0.2500000000  0.2500000000
+    0  0 -1     1  0  0     0  1  0    0.7500000000  0.2500000000  0.7500000000
+    0  0  1     1  0  0     0 -1  0    0.2500000000  0.7500000000  0.7500000000
+    0  0  1    -1  0  0     0  1  0    0.7500000000  0.7500000000  0.2500000000
+    0 -1  0     0  0 -1    -1  0  0    0.2500000000  0.2500000000  0.2500000000
+    0  1  0     0  0 -1     1  0  0    0.7500000000  0.7500000000  0.2500000000
+    0 -1  0     0  0  1     1  0  0    0.7500000000  0.2500000000  0.7500000000
+    0  1  0     0  0  1    -1  0  0    0.2500000000  0.7500000000  0.7500000000
+    0 -1  0    -1  0  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+   -1  0  0     0  0 -1     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1     0 -1  0     1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     0  1  0    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  0  1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+227   F d -3 m
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.7500000000  0.2500000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.2500000000  0.5000000000  0.7500000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.7500000000  0.2500000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.7500000000  0.2500000000
+    0  0 -1    -1  0  0     0  1  0    0.7500000000  0.2500000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.2500000000  0.5000000000  0.7500000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.2500000000  0.5000000000  0.7500000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.7500000000  0.2500000000
+    0 -1  0     0  0 -1     1  0  0    0.7500000000  0.2500000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.5000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.5000000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.5000000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.5000000000
+   -1  0  0     0  0  1     0  1  0    0.5000000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.5000000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.5000000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.5000000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.5000000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+228   F d -3 c
+  setting 1
+  centrosymmetric 0
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  48 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.5000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.0000000000  0.5000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.5000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.7500000000
+    0 -1  0    -1  0  0     0  0 -1    0.2500000000  0.2500000000  0.2500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.7500000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.7500000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.7500000000
+   -1  0  0     0  0  1     0  1  0    0.7500000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.2500000000  0.2500000000  0.2500000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.7500000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.7500000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.7500000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.7500000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.2500000000  0.2500000000  0.2500000000
+   -1  0  0     0 -1  0     0  0 -1    0.7500000000  0.7500000000  0.7500000000
+    1  0  0     0  1  0     0  0 -1    0.7500000000  0.2500000000  0.2500000000
+    1  0  0     0 -1  0     0  0  1    0.2500000000  0.2500000000  0.7500000000
+   -1  0  0     0  1  0     0  0  1    0.2500000000  0.7500000000  0.2500000000
+    0  0 -1    -1  0  0     0 -1  0    0.7500000000  0.7500000000  0.7500000000
+    0  0 -1     1  0  0     0  1  0    0.2500000000  0.7500000000  0.2500000000
+    0  0  1     1  0  0     0 -1  0    0.7500000000  0.2500000000  0.2500000000
+    0  0  1    -1  0  0     0  1  0    0.2500000000  0.2500000000  0.7500000000
+    0 -1  0     0  0 -1    -1  0  0    0.7500000000  0.7500000000  0.7500000000
+    0  1  0     0  0 -1     1  0  0    0.2500000000  0.2500000000  0.7500000000
+    0 -1  0     0  0  1     1  0  0    0.2500000000  0.7500000000  0.2500000000
+    0  1  0     0  0  1    -1  0  0    0.7500000000  0.2500000000  0.2500000000
+    0 -1  0    -1  0  0     0  0  1    0.0000000000  0.5000000000  0.0000000000
+    0  1  0     1  0  0     0  0  1    0.5000000000  0.5000000000  0.5000000000
+    0 -1  0     1  0  0     0  0 -1    0.5000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.5000000000
+   -1  0  0     0  0 -1     0  1  0    0.0000000000  0.5000000000  0.0000000000
+    1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.5000000000
+    1  0  0     0  0  1     0  1  0    0.5000000000  0.5000000000  0.5000000000
+   -1  0  0     0  0  1     0 -1  0    0.5000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0     1  0  0    0.0000000000  0.5000000000  0.0000000000
+    0  0 -1     0  1  0    -1  0  0    0.5000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.5000000000
+    0  0  1     0  1  0     1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+228   F d -3 c
+  setting 2
+  centrosymmetric 1
+  primitive cell
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  reciprocal primitive cell
+     -1   1   1
+      1  -1   1
+      1   1  -1
+  4 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.0000000000  0.5000000000  0.5000000000
+     0.5000000000  0.0000000000  0.5000000000
+     0.5000000000  0.5000000000  0.0000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.2500000000  0.7500000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.7500000000  0.5000000000  0.2500000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.2500000000  0.7500000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.2500000000  0.7500000000
+    0  0 -1    -1  0  0     0  1  0    0.2500000000  0.7500000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.7500000000  0.5000000000  0.2500000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.7500000000  0.5000000000  0.2500000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.2500000000  0.7500000000
+    0 -1  0     0  0 -1     1  0  0    0.2500000000  0.7500000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.5000000000  0.5000000000  0.5000000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.0000000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.5000000000  0.5000000000  0.5000000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.0000000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.0000000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.5000000000  0.5000000000  0.5000000000
+
+229   I m -3 m
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0 -1  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     0  0 -1    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  1  0     1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0    -1  0  0     0  0 -1    0.0000000000  0.0000000000  0.0000000000
+    0  1  0    -1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     1  0  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0  1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0  1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0  0 -1     0 -1  0    0.0000000000  0.0000000000  0.0000000000
+    1  0  0     0  0 -1     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0  1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1     0 -1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0  1  0     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0  0 -1     0 -1  0    -1  0  0    0.0000000000  0.0000000000  0.0000000000
+
+230   I a -3 d
+  setting 1
+  centrosymmetric 1
+  primitive cell
+    -0.5000000000  0.5000000000  0.5000000000
+     0.5000000000 -0.5000000000  0.5000000000
+     0.5000000000  0.5000000000 -0.5000000000
+  reciprocal primitive cell
+      0   1   1
+      1   0   1
+      1   1   0
+  2 subtranslations
+     0.0000000000  0.0000000000  0.0000000000
+     0.5000000000  0.5000000000  0.5000000000
+  24 symmetry operations (rot+trans)
+    1  0  0     0  1  0     0  0  1    0.0000000000  0.0000000000  0.0000000000
+   -1  0  0     0 -1  0     0  0  1    0.5000000000  0.0000000000  0.5000000000
+   -1  0  0     0  1  0     0  0 -1    0.0000000000  0.5000000000  0.5000000000
+    1  0  0     0 -1  0     0  0 -1    0.5000000000  0.5000000000  0.0000000000
+    0  0  1     1  0  0     0  1  0    0.0000000000  0.0000000000  0.0000000000
+    0  0  1    -1  0  0     0 -1  0    0.5000000000  0.5000000000  0.0000000000
+    0  0 -1    -1  0  0     0  1  0    0.5000000000  0.0000000000  0.5000000000
+    0  0 -1     1  0  0     0 -1  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0  1     1  0  0    0.0000000000  0.0000000000  0.0000000000
+    0 -1  0     0  0  1    -1  0  0    0.0000000000  0.5000000000  0.5000000000
+    0  1  0     0  0 -1    -1  0  0    0.5000000000  0.5000000000  0.0000000000
+    0 -1  0     0  0 -1     1  0  0    0.5000000000  0.0000000000  0.5000000000
+    0  1  0     1  0  0     0  0 -1    0.7500000000  0.2500000000  0.2500000000
+    0 -1  0    -1  0  0     0  0 -1    0.7500000000  0.7500000000  0.7500000000
+    0  1  0    -1  0  0     0  0  1    0.2500000000  0.2500000000  0.7500000000
+    0 -1  0     1  0  0     0  0  1    0.2500000000  0.7500000000  0.2500000000
+    1  0  0     0  0  1     0 -1  0    0.7500000000  0.2500000000  0.2500000000
+   -1  0  0     0  0  1     0  1  0    0.2500000000  0.7500000000  0.2500000000
+   -1  0  0     0  0 -1     0 -1  0    0.7500000000  0.7500000000  0.7500000000
+    1  0  0     0  0 -1     0  1  0    0.2500000000  0.2500000000  0.7500000000
+    0  0  1     0  1  0    -1  0  0    0.7500000000  0.2500000000  0.2500000000
+    0  0  1     0 -1  0     1  0  0    0.2500000000  0.2500000000  0.7500000000
+    0  0 -1     0  1  0     1  0  0    0.2500000000  0.7500000000  0.2500000000
+    0  0 -1     0 -1  0    -1  0  0    0.7500000000  0.7500000000  0.7500000000
+
Index: ase/lattice/spacegroup/spacegroup.py
===================================================================
--- ase/lattice/spacegroup/spacegroup.py	(revision 0)
+++ ase/lattice/spacegroup/spacegroup.py	(revision 1)
@@ -0,0 +1,420 @@
+# Copyright (C) 2010, Jesper Friis
+# (see accompanying license files for details).
+
+"""Definition of the Spacegroup class.
+
+This module only depends on NumPy and the space group database.
+"""
+
+import os
+import warnings
+
+import numpy as np
+
+__all__ = ['Spacegroup']
+
+
+class SpacegroupError(Exception):
+    """Base exception for the spacegroup module."""
+    pass
+
+class SpacegroupNotFoundError(SpacegroupError):
+    """Raised when given space group cannot be found in data base."""
+    pass
+
+class SpacegroupValueError(SpacegroupError):
+    """Raised when arguments have invalid value."""
+    pass
+
+
+class Spacegroup(object):
+    """A space group class. 
+    
+    The instances of Spacegroup describes the symmetry operations for
+    the given space group. 
+
+    Example:
+    
+    >>> from spacegroup import Spacegroup
+    >>> 
+    >>> sg = Spacegroup(225)
+    >>> print 'Space group', sg.no, sg.symbol
+    Space group 225 F m -3 m
+    >>> sg.scaled_primitive_cell
+    array([[ 0. ,  0.5,  0.5],
+           [ 0.5,  0. ,  0.5],
+           [ 0.5,  0.5,  0. ]])
+    >>> sites, kinds = sg.equivalent_sites([[0,0,0]])
+    >>> sites
+    array([[ 0. ,  0. ,  0. ],
+           [ 0. ,  0.5,  0.5],
+           [ 0.5,  0. ,  0.5],
+           [ 0.5,  0.5,  0. ]])
+    """
+    no = property(
+        lambda self: self._no,
+        doc='Space group number in International Tables of Crystallography.')
+    symbol = property(
+        lambda self: self._symbol, 
+        doc='Hermann-Mauguin (or international) symbol for the space group.')
+    setting = property(
+        lambda self: self._setting, 
+        doc='Space group setting. Either one or two.')
+    lattice = property(
+        lambda self: self._symbol[0], 
+        doc="""Lattice type: 
+            P      primitive
+            I      body centering, h+k+l=2n
+            F      face centering, h,k,l all odd or even
+            A,B,C  single face centering, k+l=2n, h+l=2n, h+k=2n
+            R      rhombohedral centering, -h+k+l=3n (obverse); h-k+l=3n (reverse)
+            """)
+    centrosymmetric = property(
+        lambda self: self._centrosymmetric, 
+        doc='Whether a center of symmetry exists.')
+    scaled_primitive_cell = property(
+        lambda self: self._scaled_primitive_cell, 
+        doc='Primitive cell in scaled coordinates as a matrix with the '
+        'primitive vectors along the rows.')
+    reciprocal_cell = property(
+        lambda self: self._reciprocal_cell, 
+        doc='Tree Miller indices that span all kinematically non-forbidden '
+        'reflections as a matrix with the Miller indices along the rows.')
+    nsubtrans = property(
+        lambda self: len(self._subtrans), 
+        doc='Number of cell-subtranslation vectors.')
+    def _get_nsymop(self):
+        if self.centrosymmetric:
+            return 2 * len(self._rotations) * len(self._subtrans)
+        else:
+            return len(self._rotations) * len(self._subtrans)
+    nsymop = property(_get_nsymop, doc='Total number of symmetry operations.')
+    subtrans = property(
+        lambda self: self._subtrans, 
+        doc='Translations vectors belonging to cell-sub-translations.')
+    rotations = property(
+        lambda self: self._rotations, 
+        doc='Symmetry rotation matrices. The invertions are not included '
+        'for centrosymmetrical crystals.')
+    translations = property(
+        lambda self: self._translations, 
+        doc='Symmetry translations. The invertions are not included '
+        'for centrosymmetrical crystals.')
+
+    def __init__(self, spacegroup, setting=1, datafile=None):
+        """Returns a new Spacegroup instance. 
+
+        Parameters:
+
+        spacegroup : int | string | Spacegroup instance
+            The space group number in International Tables of
+            Crystallography or its Hermann-Mauguin symbol. E.g. 
+            spacegroup=225 and spacegroup='F m -3 m' are equivalent.
+        setting : 1 | 2
+            Some space groups have more than one setting. `setting`
+            determines Which of these should be used.
+        datafile : None | string
+            Path to database file. If `None`, the the default database 
+            will be used.
+        """
+        if isinstance(spacegroup, Spacegroup):
+            for k, v in spacegroup.__dict__.iteritems():
+                setattr(self, k, v)
+            return 
+        if not datafile:
+            datafile = get_datafile()
+        f = open(datafile, 'r')
+        try:
+            _read_datafile(self, spacegroup, setting, f)
+        finally:
+            f.close()
+
+    def __repr__(self):
+        return 'Spacegroup(%d, setting=%d)'%(self.no, self.setting)
+    
+    def __str__(self):
+        """Return a string representation of the space group data in
+        the same format as found the database."""
+        retval = []
+        # no, symbol
+        retval.append('%-3d   %s\n' % (self.no, self.symbol))
+        # setting
+        retval.append('  setting %d\n' % (self.setting))
+        # centrosymmetric
+        retval.append('  centrosymmetric %d\n' % (self.centrosymmetric))
+        # primitive vectors
+        retval.append('  primitive vectors\n')
+        for i in range(3):
+            retval.append('   ')
+            for j in range(3):
+                retval.append(' %13.10f' % (self.scaled_primitive_cell[i, j]))
+            retval.append('\n')
+        # primitive reciprocal vectors
+        retval.append('  reciprocal vectors\n')
+        for i in range(3):
+            retval.append('   ')
+            for j in range(3):
+                retval.append(' %3d' % (self.reciprocal_cell[i, j]))
+            retval.append('\n')
+        # sublattice
+        retval.append('  %d subtranslations\n' % self.nsubtrans)
+        for i in range(self.nsubtrans):
+            retval.append('   ')
+            for j in range(3):
+                retval.append(' %13.10f' % (self.subtrans[i, j]))
+            retval.append('\n')
+        # symmetry operations
+        nrot = len(self.rotations)
+        retval.append('  %d symmetry operations (rot+trans)\n' % nrot)
+        for i in range(nrot):
+            retval.append(' ')
+            for j in range(3):
+                retval.append(' ')
+                for k in range(3):
+                    retval.append(' %2d' % (self.rotations[i, j, k]))
+                retval.append('  ')
+            for j in range(3):
+                retval.append(' %13.10f' % self.translations[i, j])
+            retval.append('\n')
+        retval.append('\n')
+        return ''.join(retval)
+
+    def __eq__(self, other):
+        """Chech whether *self* and *other* refer to the same
+        spacegroup number and setting."""
+        if isinstance(other, int):
+            return self.no == other
+        elif isinstance(other, str):
+            return self.symbol == other
+        else:
+            return self.no == other.no and self.setting == other.setting
+
+    def __index__(self):
+        return self.no
+
+    def get_symop(self):
+        """Returns all symmetry operations (including inversions and
+        subtranslations) as a sequence of (rotation, translation)
+        tuples."""
+        symop = []
+        parities = [1]
+        if self.centrosymmetric:
+            parities.append(-1)
+        for parity in parities:
+            for subtrans in self.subtrans:
+                for rot,trans in zip(self.rotations, self.translations):
+                    newtrans = np.mod(trans + subtrans, 1)
+                    symop.append((parity*rot, newtrans))
+        return symop
+
+    def get_rotations(self):
+        """Return all rotations, including inversions for
+        centrosymmetric crystals."""
+        if self.centrosymmetric:
+            return np.vstack((self.rotations, -self.rotations))
+        else:
+            return self.rotations
+
+    def equivalent_reflections(self, hkl):
+        """Return all equivalent reflections to the list of Miller indices
+        in hkl.
+
+        Example:
+
+        >>> from spacegroup import Spacegroup
+        >>> sg = Spacegroup(225)  # fcc
+        >>> sg.equivalent_reflections([[0,0,2]])
+        array([[ 0,  0, -2],
+               [ 0, -2,  0],
+               [-2,  0,  0],
+               [ 2,  0,  0],
+               [ 0,  2,  0],
+               [ 0,  0,  2]])
+        """
+        hkl = np.array(hkl, dtype='int')
+        rot = self.get_rotations()
+        n,nrot = len(hkl), len(rot)
+        R = rot.transpose(0, 2, 1).reshape((3*nrot, 3)).T
+        refl = np.dot(hkl, R).reshape((n*nrot, 3))
+        ind = np.lexsort(refl.T)
+        refl = refl[ind]
+        diff = np.diff(refl, axis=0)
+        mask = np.any(diff, axis=1)
+        return np.vstack((refl[mask], refl[-1,:]))
+            
+    def equivalent_sites(self, scaled_positions, ondublicates='error', 
+                         symprec=1e-3):
+        """Returns the scaled positions and all their equivalent sites.
+
+        Parameters:
+
+        scaled_positions: list | array
+            List of non-equivalent sites given in unit cell coordinates.
+        ondublicates : 'keep' | 'replace' | 'warn' | 'error'
+            Action if `scaled_positions` contain symmetry-equivalent
+            positions:
+            
+            'keep'
+               ignore additional symmetry-equivalent positions
+            'replace'
+                replace
+            'warn'
+                like 'keep', but issue an UserWarning
+            'error'
+                raises a SpacegroupValueError
+                    
+        symprec: float
+            Minimum "distance" betweed two sites in scaled coordinates
+            before they are counted as the same site.
+
+        Returns:
+
+        sites: array
+            A NumPy array of equivalent sites.
+        kinds: list
+            A list of integer indices specifying which input site is 
+            equivalent to the corresponding returned site.
+        """
+        kinds = []
+        sites = []
+        symprec2 = symprec**2
+        for kind, pos in enumerate(scaled_positions):
+            for rot, trans in self.get_symop():
+                site = np.mod(np.dot(rot, pos) + trans, 1.)
+                if not sites:
+                    sites.append(site)
+                    kinds.append(kind)
+                    continue
+                t = site - sites
+                mask = np.sum(t*t, 1) < symprec2
+                if np.any(mask):
+                    ind = np.argwhere(mask)[0][0]
+                    if kinds[ind] == kind:
+                        pass
+                    elif ondublicates == 'keep':
+                        pass
+                    elif ondublicates == 'replace':
+                        kinds[ind] = kind
+                    elif ondublicates == 'warn':
+                        warnings.warn('scaled_positions %d and %d '
+                                      'are equivalent'%(kinds[ind], kind))
+                    elif ondublicates == 'error':
+                        raise SpacegroupValueError(
+                            'scaled_positions %d and %d are equivalent'%(
+                                kinds[ind], kind))
+                    else:
+                        raise SpacegroupValueError(
+                            'Argument "ondublicates" must be one of: '
+                            '"keep", "replace", "warn" or "error".')
+                else:
+                    sites.append(site)
+                    kinds.append(kind)
+        return np.array(sites), kinds
+
+
+
+
+def get_datafile():
+    """Return default path to datafile."""
+    return os.path.join(os.path.dirname(__file__), 'spacegroup.dat')
+
+
+
+
+#-----------------------------------------------------------------
+# Functions for parsing the database. They are moved outside the
+# Spacegroup class in order to make it easier to later implement
+# caching to avoid reading the database each time a new Spacegroup
+# instance is created.
+#-----------------------------------------------------------------
+
+def _skip_to_blank(f, spacegroup, setting):
+    """Read lines from f until a blank line is encountered."""
+    while True:
+        line = f.readline()
+        if not line:
+            raise SpacegroupNotFoundError(
+                'invalid spacegroup %s, setting %i not found in data base' % 
+                ( spacegroup, setting ) )
+        if not line.strip():
+            break
+
+
+def _skip_to_nonblank(f, spacegroup, setting):
+    """Read lines from f until a nonblank line not starting with a
+    hash (#) is encountered and returns this and the next line."""
+    while True:
+        line1 = f.readline()
+        if not line1:
+            raise SpacegroupNotFoundError(
+                'invalid spacegroup %s, setting %i not found in data base' %
+                ( spacegroup, setting ) )
+        line1.strip()
+        if line1 and not line1.startswith('#'):
+            line2 = f.readline()
+            break
+    return line1, line2
+
+
+def _read_datafile_entry(spg, no, symbol, setting, f):
+    """Read space group data from f to spg."""
+    spg._no = no
+    spg._symbol = symbol.strip()
+    spg._setting = setting
+    spg._centrosymmetric = bool(int(f.readline().split()[1]))
+    # primitive vectors
+    f.readline()
+    spg._scaled_primitive_cell = np.array([map(float, f.readline().split()) 
+                                       for i in range(3)], 
+                                      dtype=np.float)
+    # primitive reciprocal vectors
+    f.readline()
+    spg._reciprocal_cell = np.array([map(int, f.readline().split()) 
+                                        for i in range(3)],
+                                       dtype=np.int)
+    # subtranslations
+    spg._nsubtrans = int(f.readline().split()[0])
+    spg._subtrans = np.array([map(float, f.readline().split()) 
+                              for i in range(spg._nsubtrans)],
+                             dtype=np.float)
+    # symmetry operations
+    nsym = int(f.readline().split()[0])
+    symop = np.array([map(float, f.readline().split()) for i in range(nsym)],
+                     dtype=np.float)
+    spg._nsymop = nsym
+    spg._rotations = np.array(symop[:,:9].reshape((nsym,3,3)), dtype=np.int)
+    spg._translations = symop[:,9:]
+
+
+def _read_datafile(spg, spacegroup, setting, f):
+    if isinstance(spacegroup, int):
+        pass
+    elif isinstance(spacegroup, basestring):
+        spacegroup = ' '.join(spacegroup.strip().split())
+    else:
+        raise SpacegroupValueError('`spacegroup` must be of type int or str')
+    while True:
+        line1, line2 = _skip_to_nonblank(f, spacegroup, setting)
+        _no,_symbol = line1.strip().split(None, 1)
+        _setting = int(line2.strip().split()[1])
+        _no = int(_no)
+        if ((isinstance(spacegroup, int) and _no == spacegroup) or
+            (isinstance(spacegroup, basestring) and 
+             _symbol == spacegroup)) and _setting == setting:
+            _read_datafile_entry(spg, _no, _symbol, _setting, f)
+            break
+        else:
+            _skip_to_blank(f, spacegroup, setting)
+                
+
+
+#-----------------------------------------------------------------
+# Self test
+if __name__ == '__main__':
+
+    # Import spacegroup in order to ensure that __file__ is defined
+    # such that the data base can be found.
+    import spacegroup
+
+    import doctest
+    print 'doctest: ', doctest.testmod()
Index: ase/lattice/spacegroup/__init__.py
===================================================================
--- ase/lattice/spacegroup/__init__.py	(revision 0)
+++ ase/lattice/spacegroup/__init__.py	(revision 1)
@@ -0,0 +1,4 @@
+from spacegroup import Spacegroup
+from crystal import crystal
+
+__all__ = ['Spacegroup', 'crystal']
Index: ase/lattice/spacegroup/cell.py
===================================================================
--- ase/lattice/spacegroup/cell.py	(revision 0)
+++ ase/lattice/spacegroup/cell.py	(revision 1)
@@ -0,0 +1,115 @@
+# Copyright (C) 2010, Jesper Friis
+# (see accompanying license files for details).
+
+import numpy as np
+from numpy import pi, sin, cos, tan, arcsin, arccos, arctan, sqrt
+from numpy import dot
+from numpy.linalg import norm
+
+import ase
+
+
+__ALL__ = ['cell_to_cellpar', 'cellpar_to_cell', 'metric_from_cell']
+
+
+def unit_vector(x):
+    """Return a unit vector in the same direction as x."""
+    y = np.array(x, dtype='float')
+    return y/norm(y)
+
+
+def angle(x, y):
+    """Return the angle between vectors a and b in degrees."""
+    return arccos(dot(x, y)/(norm(x)*norm(y)))*180./pi
+
+
+def cell_to_cellpar(cell):
+    """Returns the cell parameters [a, b, c, alpha, beta, gamma] as a
+    numpy array."""
+    va, vb, vc = cell
+    a = np.linalg.norm(va)
+    b = np.linalg.norm(vb)
+    c = np.linalg.norm(vc)
+    alpha = 180.0/pi*arccos(dot(vb, vc)/(b*c))
+    beta  = 180.0/pi*arccos(dot(vc, va)/(c*a))
+    gamma = 180.0/pi*arccos(dot(va, vb)/(a*b))
+    return np.array([a, b, c, alpha, beta, gamma])
+        
+
+def cellpar_to_cell(cellpar, ab_normal=(0,0,1), a_direction=None):
+    """Return a 3x3 cell matrix from `cellpar` = [a, b, c, alpha,
+    beta, gamma].  The returned cell is orientated such that a and b
+    are normal to `ab_normal` and a is parallel to the projection of
+    `a_direction` in the a-b plane.
+
+    Default `a_direction` is (1,0,0), unless this is parallel to
+    `ab_normal`, in which case default `a_direction` is (0,0,1).
+
+    The returned cell has the vectors va, vb and vc along the rows. The
+    cell will be oriented such that va and vb are normal to `ab_normal`
+    and va will be along the projection of `a_direction` onto the a-b
+    plane.
+
+    Example:
+
+    >>> cell = cellpar_to_cell([1, 2, 4,  10,  20, 30], (0,1,1), (1,2,3))
+    >>> np.round(cell, 3)
+    array([[ 0.816, -0.408,  0.408],
+           [ 1.992, -0.13 ,  0.13 ],
+           [ 3.859, -0.745,  0.745]])
+
+    """
+    if a_direction is None:
+        if np.linalg.norm(np.cross(ab_normal, (1,0,0))) < 1e-5:
+            a_direction = (0,0,1)
+        else:
+            a_direction = (1,0,0)
+
+    # Define rotated X,Y,Z-system, with Z along ab_normal and X along
+    # the projection of a_direction onto the normal plane of Z.
+    ad = np.array(a_direction)
+    Z = unit_vector(ab_normal)
+    X = unit_vector(ad - dot(ad, Z)*Z)
+    Y = np.cross(Z, X)
+
+    # Express va, vb and vc in the X,Y,Z-system
+    alpha, beta, gamma = 90., 90., 90.
+    if isinstance(cellpar, (int, long, float)):
+        a = b = c = cellpar
+    elif len(cellpar) == 1:
+        a = b = c = cellpar[0]
+    elif len(cellpar) == 3:
+        a, b, c = cellpar
+        alpha, beta, gamma = 90., 90., 90.
+    else:
+        a, b, c, alpha, beta, gamma = cellpar
+    alpha *= pi/180.0
+    beta *= pi/180.0
+    gamma *= pi/180.0
+    va = a * np.array([1, 0, 0])
+    vb = b * np.array([cos(gamma), sin(gamma), 0])
+    cx = cos(beta)
+    cy = (cos(alpha) - cos(beta)*cos(gamma))/sin(gamma)
+    cz = sqrt(1. - cx*cx - cy*cy)
+    vc = c * np.array([cx, cy, cz])
+
+    # Convert to the Cartesian x,y,z-system
+    abc = np.vstack((va, vb, vc))
+    T = np.vstack((X, Y, Z))
+    cell = dot(abc, T)
+    return cell
+
+
+def metric_from_cell(cell):
+    """Calculates the metric matrix from cell, which is given in the
+    Cartesian system."""
+    cell = np.asarray(cell, dtype=float)
+    return np.dot(cell, cell.T)
+
+
+
+
+if __name__ == '__main__':
+    import doctest
+    print 'doctest: ', doctest.testmod()
+
Index: ase/lattice/spacegroup/crystal.py
===================================================================
--- ase/lattice/spacegroup/crystal.py	(revision 0)
+++ ase/lattice/spacegroup/crystal.py	(revision 1)
@@ -0,0 +1,146 @@
+# Copyright (C) 2010, Jesper Friis
+# (see accompanying license files for details).
+
+"""
+A module for ASE for simple creation of crystalline structures from
+knowledge of the space group.
+
+"""
+
+import numpy as np
+
+import ase
+from ase.atoms import string2symbols
+from spacegroup import Spacegroup
+from cell import cellpar_to_cell
+
+__all__ = ['crystal']
+
+
+
+def crystal(symbols=None, basis=None, spacegroup=1, setting=1, 
+            cell=None, cellpar=None, 
+            ab_normal=(0,0,1), a_direction=None, size=(1,1,1),
+            ondublicates='warn', symprec=0.001, 
+            pbc=True, **kwargs):
+    """Create an Atoms instance for a conventional unit cell of a
+    space group.
+
+    Parameters:
+
+    symbols : string | sequence of strings
+        Either a string formula or a sequence of element
+        symbols. E.g. ('Na', 'Cl') and 'NaCl' are equivalent.
+    basis : list of scaled coordinates | atoms instance
+        Positions of the non-equivalent sites given either as
+        scaled positions or through an atoms instance.
+    spacegroup : int | string | Spacegroup instance
+        Space group given either as its number in International Tables
+        or as its Hermann-Mauguin symbol.
+    setting : 1 | 2
+        Space group setting.
+    cell : 3x3 matrix
+        Unit cell vectors.
+    cellpar : [a, b, c, alpha, beta, gamma]
+        Cell parameters with angles in degree. Is not used when `cell`
+        is given. 
+    ab_normal : vector
+        Is used to define the orientation of the unit cell relative
+        to the Cartesian system when `cell` is not given. It is the
+        normal vector of the plane spanned by a and b.
+    a_direction : vector
+        Defines the orientation of the unit cell a vector. a will be 
+        parallel to the projection of `a_direction` onto the a-b plane.
+    size : 3 positive integers
+        How many times the conventional unit cell should be repeated
+        in each direction.
+    ondublicates : 'keep' | 'replace' | 'warn' | 'error'
+        Action if `basis` contain symmetry-equivalent positions:
+            'keep'    - ignore additional symmetry-equivalent positions
+            'replace' - reolace
+            'warn'    - like 'keep', but issue an UserWarning
+            'error'   - raises a SpacegroupValueError
+    symprec : float
+        Minimum "distance" betweed two sites in scaled coordinates
+        before they are counted as the same site.
+    pbc : one or three bools
+        Periodic boundary conditions flags.  Examples: True,
+        False, 0, 1, (1, 1, 0), (True, False, False).  Default
+        is True.
+
+    Keyword arguments:
+
+    All additional keyword arguments are passed on to the Atoms constructor. 
+    Currently, probably the most useful additional keyword arguments are
+    `constraint` and `calculator`.
+
+    Examples:
+
+    Two diamond unit cells (space group number 227)
+
+    >>> diamond = crystal('C', [(0,0,0)], spacegroup=227, 
+    ...     cellpar=[3.57, 3.57, 3.57, 90, 90, 90], size=(2,1,1))
+    >>> ase.view(diamond)  # doctest: +SKIP
+
+    A CoSb3 skutterudite unit cell containing 32 atoms
+
+    >>> skutterudite = crystal(('Co', 'Sb'), 
+    ...     basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)], 
+    ...     spacegroup=204, cellpar=[9.04, 9.04, 9.04, 90, 90, 90])
+    >>> len(skutterudite)
+    32
+    """
+    sg = Spacegroup(spacegroup, setting)
+    if isinstance(symbols, ase.Atoms):
+        basis = symbols
+        symbols = basis.get_chemical_symbols()
+    if isinstance(basis, ase.Atoms):
+        basis_coords = basis.get_scaled_positions()
+        if cell is None and cellpar is None:
+            cell = basis.cell
+        if symbols is None:
+            symbols = basis.get_chemical_symbols()
+    else:
+        basis_coords = np.array(basis, dtype=float, copy=False, ndmin=2)
+    sites, kinds = sg.equivalent_sites(basis_coords, 
+                                       ondublicates=ondublicates, 
+                                       symprec=symprec)
+    symbols = parse_symbols(symbols)
+    symbols = [symbols[i] for i in kinds]
+    if cell is None:
+        cell = cellpar_to_cell(cellpar, ab_normal, a_direction)
+
+    atoms = ase.Atoms(symbols, 
+                      scaled_positions=sites, 
+                      cell=cell,
+                      pbc=pbc,
+                      **kwargs)
+
+    if isinstance(basis, ase.Atoms):
+        for name in basis.arrays:
+            if not atoms.has(name):
+                array = basis.get_array(name)
+                atoms.new_array(name, [array[i] for i in kinds], 
+                                dtype=array.dtype, shape=array.shape[1:])
+
+    if size != (1, 1, 1):
+        atoms = atoms.repeat(size)
+    return atoms
+
+def parse_symbols(symbols):
+    """Return `sumbols` as a sequence of element symbols."""
+    if isinstance(symbols, basestring):
+        symbols = string2symbols(symbols)
+    return symbols
+
+
+
+
+
+
+#-----------------------------------------------------------------
+# Self test
+if __name__ == '__main__':
+    import doctest
+    print 'doctest: ', doctest.testmod()
+    
Index: ase/lattice/cubic.py
===================================================================
--- ase/lattice/cubic.py	(revision 0)
+++ ase/lattice/cubic.py	(revision 1)
@@ -0,0 +1,125 @@
+"""Function-like objects creating cubic lattices (SC, FCC, BCC and Diamond).
+
+The following lattice creators are defined:
+    SimpleCubic
+    FaceCenteredCubic
+    BodyCenteredCubic
+    Diamond
+"""
+
+from ase.lattice.bravais import Bravais
+import numpy as np
+from ase.data import reference_states as _refstate
+
+class SimpleCubicFactory(Bravais):
+    "A factory for creating simple cubic lattices."
+
+    # The name of the crystal structure in ChemicalElements
+    xtal_name = "sc"
+
+    # The natural basis vectors of the crystal structure
+    int_basis = np.array([[1, 0, 0],
+                          [0, 1, 0],
+                          [0, 0, 1]])
+    basis_factor = 1.0
+
+    # Converts the natural basis back to the crystallographic basis
+    inverse_basis = np.array([[1, 0, 0],
+                              [0, 1, 0],
+                              [0, 0, 1]])
+    inverse_basis_factor = 1.0
+
+    # For checking the basis volume
+    atoms_in_unit_cell = 1
+    
+    def get_lattice_constant(self):
+        "Get the lattice constant of an element with cubic crystal structure."
+        if _refstate[self.atomicnumber]['symmetry'].lower() != self.xtal_name:
+            raise ValueError, (("Cannot guess the %s lattice constant of"
+                                + " an element with crystal structure %s.")
+                               % (self.xtal_name,
+                                  _refstate[self.atomicnumber]['symmetry']))
+        return _refstate[self.atomicnumber]['a']
+
+    def make_crystal_basis(self):
+        "Make the basis matrix for the crystal unit cell and the system unit cell."
+        self.crystal_basis = (self.latticeconstant * self.basis_factor
+                              * self.int_basis)
+        self.miller_basis = self.latticeconstant * np.identity(3)
+        self.basis = np.dot(self.directions, self.crystal_basis)
+        self.check_basis_volume()
+
+    def check_basis_volume(self):
+        "Check the volume of the unit cell."
+        vol1 = abs(np.linalg.det(self.basis))
+        cellsize = self.atoms_in_unit_cell
+        if self.bravais_basis is not None:
+            cellsize *= len(self.bravais_basis)
+        vol2 = (self.calc_num_atoms() * self.latticeconstant**3 / cellsize)
+        assert abs(vol1-vol2) < 1e-5
+
+    def find_directions(self, directions, miller):
+        "Find missing directions and miller indices from the specified ones."
+        directions = list(directions)
+        miller = list(miller)
+        # Process keyword "orthogonal"
+        self.find_ortho(directions)
+        self.find_ortho(miller)
+        Bravais.find_directions(self, directions, miller)
+        
+    def find_ortho(self, idx):
+        "Replace keyword 'ortho' or 'orthogonal' with a direction."
+        for i in range(3):
+            if isinstance(idx[i], str) and (idx[i].lower() == "ortho" or
+                                            idx[i].lower() == "orthogonal"):
+                if self.debug:
+                    print "Calculating orthogonal direction", i
+                    print  idx[i-2], "X", idx[i-1],  
+                idx[i] = reduceindex(cross(idx[i-2], idx[i-1]))
+                if self.debug:
+                    print "=", idx[i]
+                
+
+SimpleCubic = SimpleCubicFactory()
+
+class FaceCenteredCubicFactory(SimpleCubicFactory):
+    "A factory for creating face-centered cubic lattices."
+
+    xtal_name = "fcc"
+    int_basis = np.array([[0, 1, 1],
+                          [1, 0, 1],
+                          [1, 1, 0]])
+    basis_factor = 0.5
+    inverse_basis = np.array([[-1, 1, 1],
+                              [1, -1, 1],
+                              [1, 1, -1]])
+    inverse_basis_factor = 1.0
+
+    atoms_in_unit_cell = 4
+    
+FaceCenteredCubic = FaceCenteredCubicFactory()
+
+class BodyCenteredCubicFactory(SimpleCubicFactory):
+    "A factory for creating body-centered cubic lattices."
+
+    xtal_name = "bcc"
+    int_basis = np.array([[-1, 1, 1],
+                          [1, -1, 1],
+                          [1, 1, -1]])
+    basis_factor = 0.5
+    inverse_basis = np.array([[0, 1, 1],
+                              [1, 0, 1],
+                              [1, 1, 0]])
+    inverse_basis_factor = 1.0
+
+    atoms_in_unit_cell = 2
+    
+BodyCenteredCubic = BodyCenteredCubicFactory()
+
+class DiamondFactory(FaceCenteredCubicFactory):
+    "A factory for creating diamond lattices."
+    xtal_name = "diamond"
+    bravais_basis = [[0,0,0], [0.25, 0.25, 0.25]]
+    
+Diamond = DiamondFactory()
+
Index: ase/lattice/hexagonal.py
===================================================================
--- ase/lattice/hexagonal.py	(revision 0)
+++ ase/lattice/hexagonal.py	(revision 1)
@@ -0,0 +1,93 @@
+"""Function-like object creating hexagonal lattices.
+
+The following lattice creators are defined:
+    Hexagonal
+    HexagonalClosedPacked
+    Graphite
+"""
+
+from ase.lattice.triclinic import TriclinicFactory
+import numpy as np
+from ase.data import reference_states as _refstate
+
+
+class HexagonalFactory(TriclinicFactory):
+    "A factory for creating simple hexagonal lattices."
+    # The name of the crystal structure in ChemicalElements
+    xtal_name = "hexagonal"
+
+    def make_crystal_basis(self):
+        "Make the basis matrix for the crystal unit cell and the system unit cell."
+        # First convert the basis specification to a triclinic one
+        if type(self.latticeconstant) == type({}):
+            self.latticeconstant['alpha'] = 90
+            self.latticeconstant['beta'] = 90
+            self.latticeconstant['gamma'] = 120
+            self.latticeconstant['b/a'] = 1.0
+        else:
+            if len(self.latticeconstant) == 2:
+                a,c = self.latticeconstant
+                self.latticeconstant = (a,a,c,90,90,120)
+            else:
+                raise ValueError, "Improper lattice constants for hexagonal crystal."
+        TriclinicFactory.make_crystal_basis(self)
+
+    def find_directions(self, directions, miller):
+        """Find missing directions and miller indices from the specified ones.
+
+        Also handles the conversion of hexagonal-style 4-index notation to
+        the normal 3-index notation.
+        """
+        directions = list(directions)
+        miller = list(miller)
+        for obj in (directions,miller):
+            for i in range(3):
+                if obj[i] is not None:
+                    (a,b,c,d) = obj[i]
+                    if a + b + c != 0:
+                        raise ValueError(
+                            ("(%d,%d,%d,%d) is not a valid hexagonal Miller " +
+                             "index, as the sum of the first three numbers " +
+                             "should be zero.") % (a,b,c,d))
+                    x = 4*a + 2*b
+                    y = 2*a + 4*b
+                    z = 3*d
+                    obj[i] = (x,y,z)
+        TriclinicFactory.find_directions(self, directions, miller)
+        
+    def print_directions_and_miller(self, txt=""):
+        "Print direction vectors and Miller indices."
+        print "Direction vectors of unit cell%s:" % (txt,)
+        for i in (0,1,2):
+            self.print_four_vector("[]", self.directions[i])
+        print "Miller indices of surfaces%s:" % (txt,)
+        for i in (0,1,2):
+            self.print_four_vector("()", self.miller[i])
+
+    def print_four_vector(self, bracket, numbers):
+        bra, ket = bracket
+        (x,y,z) = numbers
+        a = 2*x - y
+        b = -x + 2*y
+        c = -x -y
+        d = 2*z
+        print "   %s%d, %d, %d%s  ~  %s%d, %d, %d, %d%s" % \
+              (bra,x,y,z,ket, bra,a,b,c,d,ket)
+        
+        
+Hexagonal = HexagonalFactory()
+
+class HexagonalClosedPackedFactory(HexagonalFactory):
+    "A factory for creating HCP lattices."
+    xtal_name = "hcp"
+    bravais_basis = [[0,0,0], [1.0/3.0, 2.0/3.0, 0.5]]
+
+HexagonalClosedPacked = HexagonalClosedPackedFactory()
+
+class GraphiteFactory(HexagonalFactory):
+    "A factory for creating graphite lattices."
+    xtal_name = "graphite"
+    bravais_basis = [[0,0,0], [1.0/3.0, 2.0/3.0, 0], [1.0/3.0,2.0/3.0,0.5], [2.0/3.0,1.0/3.0,0.5]]
+
+Graphite = GraphiteFactory()
+
Index: ase/lattice/bravais.py
===================================================================
--- ase/lattice/bravais.py	(revision 0)
+++ ase/lattice/bravais.py	(revision 1)
@@ -0,0 +1,448 @@
+"""Bravais.py - class for generating Bravais lattices etc.
+
+This is a base class for numerous classes setting up pieces of crystal.
+"""
+
+import math
+import numpy as np
+from ase.atoms import Atoms
+import ase.data
+
+class Bravais:
+    """Bravais lattice factory.
+
+    This is a base class for the objects producing various lattices
+    (SC, FCC, ...).
+    """
+
+    # The following methods are NOT defined here, but must be defined
+    # in classes inhering from Bravais:
+    #   get_lattice_constant
+    #   make_crystal_basis
+    # The following class attributes are NOT defined here, but must be defined
+    # in classes inhering from Bravais:
+    #   int_basis
+    #   inverse_basis
+
+    other = {0:(1,2), 1:(2,0), 2:(0,1)}
+
+    # For Bravais lattices with a basis, set the basis here.  Leave as
+    # None if no basis is present.
+    bravais_basis = None
+
+    # If more than one type of element appear in the crystal, give the
+    # order here.  For example, if two elements appear in a 3:1 ratio,
+    # bravais_basis could contain four vectors, and element_basis
+    # could be (0,0,1,0) - the third atom in the basis is different
+    # from the other three.  Leave as None if all atoms are of the
+    # same type.
+    element_basis = None
+
+    # How small numbers should be considered zero in the unit cell?
+    chop_tolerance = 1e-10
+
+    def __call__(self, symbol,
+                 directions=(None,None,None), miller=(None,None,None),
+                 size=(1,1,1), latticeconstant=None,
+                 pbc=True, align=True, debug=0):
+        "Create a lattice."
+        self.size = size
+        self.pbc = pbc
+        self.debug = debug
+        self.process_element(symbol)
+        self.find_directions(directions, miller)
+        if self.debug:
+            self.print_directions_and_miller()
+        self.convert_to_natural_basis()
+        if self.debug >= 2:
+            self.print_directions_and_miller(" (natural basis)")
+        if latticeconstant is None:
+            if self.element_basis is None:
+                self.latticeconstant = self.get_lattice_constant()
+            else:
+                raise ValueError,\
+                      "A lattice constant must be specified for a compound"
+        else:
+            self.latticeconstant = latticeconstant
+        if self.debug:
+            print "Expected number of atoms in unit cell:", self.calc_num_atoms()
+        if self.debug >= 2:
+            print "Bravais lattice basis:", self.bravais_basis
+            if self.bravais_basis is not None:
+                print " ... in natural basis:", self.natural_bravais_basis
+        self.make_crystal_basis()
+        self.make_unit_cell()
+        if align:
+            self.align()
+        return self.make_list_of_atoms()
+
+    def align(self):
+        "Align the first axis along x-axis and the second in the x-y plane."
+        degree = 180/np.pi
+        if self.debug >= 2:
+            print "Basis before alignment:"
+            print self.basis
+        if self.basis[0][0]**2 + self.basis[0][2]**2 < 0.01 * self.basis[0][1]**2:
+            # First basis vector along y axis - rotate 90 deg along z
+            t = np.array([[0, -1, 0],
+                          [1, 0, 0],
+                          [0, 0, 1]], np.float)
+            self.basis = np.dot(self.basis, t)
+            transf = t
+            if self.debug >= 2:
+                print "Rotating -90 degrees around z axis for numerical stability."
+                print self.basis
+        else:
+            transf = np.identity(3, np.float)
+        assert abs(np.linalg.det(transf) - 1) < 1e-6
+        # Rotate first basis vector into xy plane
+        theta = math.atan2(self.basis[0,2], self.basis[0,0])
+        t = np.array([[np.cos(theta), 0, -np.sin(theta)],
+                      [         0, 1, 0          ],
+                      [np.sin(theta), 0, np.cos(theta) ]])
+        self.basis = np.dot(self.basis, t)
+        transf = np.dot(transf, t)
+        if self.debug >= 2:
+            print "Rotating %f degrees around y axis." % (-theta*degree,)
+            print self.basis
+        assert abs(np.linalg.det(transf) - 1) < 1e-6
+        # Rotate first basis vector to point along x axis
+        theta = math.atan2(self.basis[0,1], self.basis[0,0])
+        t = np.array([[np.cos(theta), -np.sin(theta), 0],
+                      [np.sin(theta),  np.cos(theta), 0],
+                      [         0,           0, 1]])
+        self.basis = np.dot(self.basis, t)
+        transf = np.dot(transf, t)
+        if self.debug >= 2:
+            print "Rotating %f degrees around z axis." % (-theta*degree,)
+            print self.basis
+        assert abs(np.linalg.det(transf) - 1) < 1e-6
+        # Rotate second basis vector into xy plane
+        theta = math.atan2(self.basis[1,2], self.basis[1,1])
+        t = np.array([[1, 0, 0],
+                      [0, np.cos(theta), -np.sin(theta)],
+                      [0, np.sin(theta),  np.cos(theta)]])
+        self.basis = np.dot(self.basis, t)
+        transf = np.dot(transf, t)
+        if self.debug >= 2:
+            print "Rotating %f degrees around x axis." % (-theta*degree,)
+            print self.basis
+        assert abs(np.linalg.det(transf) - 1) < 1e-6
+        # Now we better rotate the atoms as well
+        self.atoms = np.dot(self.atoms, transf)
+        # ... and rotate miller_basis
+        self.miller_basis = np.dot(self.miller_basis, transf)
+
+    def make_list_of_atoms(self):
+        "Repeat the unit cell."
+        nrep = self.size[0] * self.size[1] * self.size[2]
+        if nrep <= 0:
+            raise ValueError, "Cannot create a non-positive number of unit cells"
+        # Now the unit cells must be merged.
+        a2 = []
+        e2 = []
+        for i in xrange(self.size[0]):
+            offset = self.basis[0] * i
+            a2.append(self.atoms + offset[np.newaxis,:])
+            e2.append(self.elements)
+        atoms = np.concatenate(a2)
+        elements = np.concatenate(e2)
+        a2 = []
+        e2 = []
+        for j in xrange(self.size[1]):
+            offset = self.basis[1] * j
+            a2.append(atoms + offset[np.newaxis,:])
+            e2.append(elements)
+        atoms = np.concatenate(a2)
+        elements = np.concatenate(e2)
+        a2 = []
+        e2 = []
+        for k in xrange(self.size[2]):
+            offset = self.basis[2] * k
+            a2.append(atoms + offset[np.newaxis,:])
+            e2.append(elements)
+        atoms = np.concatenate(a2)
+        elements = np.concatenate(e2)
+        del a2, e2
+        assert len(atoms) == nrep * len(self.atoms)
+        basis = np.array([[self.size[0],0,0],
+                          [0,self.size[1],0],
+                          [0,0,self.size[2]]])
+        basis = np.dot(basis, self.basis)
+
+        # Tiny elements should be replaced by zero.  The cutoff is
+        # determined by chop_tolerance which is a class attribute.
+        basis = np.where(np.abs(basis) < self.chop_tolerance,
+                         0.0, basis)
+
+        # None should be replaced, and memory should be freed.
+        lattice = Lattice(positions=atoms, cell=basis, numbers=elements,
+                          pbc=self.pbc)
+        lattice.millerbasis = self.miller_basis
+        # Add info for lattice.surface.AddAdsorbate
+        lattice._addsorbate_info_size = np.array(self.size[:2])
+        return lattice
+
+    def process_element(self, element):
+        "Extract atomic number from element"
+        # The types that can be elements: integers and strings
+        if self.element_basis is None:
+            if isinstance(element, type("string")):
+                self.atomicnumber = ase.data.atomic_numbers[element]
+            elif isinstance(element, int):
+                self.atomicnumber = element
+            else:
+                raise TypeError("The symbol argument must be a string or an atomic number.")
+        else:
+            atomicnumber = []
+            try:
+                if len(element) != max(self.element_basis) + 1:
+                    oops = True
+                else:
+                    oops = False
+            except TypeError:
+                oops = True
+            if oops:
+                raise TypeError(
+                    ("The symbol argument must be a sequence of length %d"
+                         +" (one for each kind of lattice position")
+                        % (max(self.element_basis)+1,))
+            for e in element:
+                if isinstance(e, type("string")):
+                    atomicnumber.append(ase.data.atomic_numbers[e])
+                elif isinstance(element, int):
+                    atomicnumber.append(e)
+                else:
+                    raise TypeError("The symbols argument must be a sequence of strings or atomic numbers.")
+            self.atomicnumber = [atomicnumber[i] for i in self.element_basis]
+            assert len(self.atomicnumber) == len(self.bravais_basis)
+
+    def convert_to_natural_basis(self):
+        "Convert directions and miller indices to the natural basis."
+        self.directions = np.dot(self.directions, self.inverse_basis)
+        if self.bravais_basis is not None:
+            self.natural_bravais_basis = np.dot(self.bravais_basis,
+                                                        self.inverse_basis)
+        for i in (0,1,2):
+            self.directions[i] = reduceindex(self.directions[i])
+        for i in (0,1,2):
+            (j,k) = self.other[i]
+            self.miller[i] = reduceindex(self.handedness *
+                                         cross(self.directions[j],
+                                                self.directions[k]))
+
+    def calc_num_atoms(self):
+        v = int(round(abs(np.linalg.det(self.directions))))
+        if self.bravais_basis is None:
+            return v
+        else:
+            return v * len(self.bravais_basis)
+
+    def make_unit_cell(self):
+        "Make the unit cell."
+        # Make three loops, and find the positions in the integral
+        # lattice.  Each time a position is found, the atom is placed
+        # in the real unit cell by put_atom().
+        self.natoms = self.calc_num_atoms()
+        self.nput = 0
+        self.atoms = np.zeros((self.natoms,3), np.float)
+        self.elements = np.zeros(self.natoms, np.int)
+        self.farpoint = farpoint = sum(self.directions)
+        #printprogress = self.debug and (len(self.atoms) > 250)
+        percent = 0
+        # Find the radius of the sphere containing the whole system
+        sqrad = 0
+        for i in (0,1):
+            for j in (0,1):
+                for k in (0,1):
+                    vect = (i * self.directions[0] +
+                            j * self.directions[1] +
+                            k * self.directions[2])
+                    if np.dot(vect,vect) > sqrad:
+                        sqrad = np.dot(vect,vect)
+        del i,j,k
+        # Loop along first crystal axis (i)
+        for (istart, istep) in ((0,1), (-1,-1)):
+            i = istart
+            icont = True
+            while icont:
+                nj = 0
+                for (jstart, jstep) in ((0,1), (-1,-1)):
+                    j = jstart
+                    jcont = True
+                    while jcont:
+                        nk = 0
+                        for (kstart, kstep) in ((0,1), (-1,-1)):
+                            k = kstart
+                            #print "Starting line i=%d, j=%d, k=%d, step=(%d,%d,%d)" % (i,j,k,istep,jstep,kstep)
+                            kcont = True
+                            while kcont:
+                                # Now (i,j,k) loops over Z^3, except that
+                                # the loops can be cut off when we get outside
+                                # the unit cell.
+                                point = np.array((i,j,k))
+                                if self.inside(point):
+                                    self.put_atom(point)
+                                    nk += 1
+                                    nj += 1
+                                # Is k too high?
+                                if np.dot(point,point) > sqrad:
+                                    assert not self.inside(point)
+                                    kcont = False
+                                k += kstep
+                        # Is j too high?
+                        if i*i+j*j > sqrad:
+                            jcont = False
+                        j += jstep
+                # Is i too high?
+                if i*i > sqrad:
+                    icont = False
+                i += istep
+                #if printprogress:
+                #    perce = int(100*self.nput / len(self.atoms))
+                #    if perce > percent + 10:
+                #        print ("%d%%" % perce),
+                #        percent = perce
+        assert(self.nput == self.natoms)
+
+    def inside(self, point):
+        "Is a point inside the unit cell?"
+        return (np.dot(self.miller[0], point) >= 0 and
+                np.dot(self.miller[0], point - self.farpoint) < 0 and
+                np.dot(self.miller[1], point) >= 0 and
+                np.dot(self.miller[1], point - self.farpoint) < 0 and
+                np.dot(self.miller[2], point) >= 0 and
+                np.dot(self.miller[2], point - self.farpoint) < 0)
+
+    def put_atom(self, point):
+        "Place an atom given its integer coordinates."
+        if self.bravais_basis is None:
+            # No basis - just place a single atom
+            pos = np.dot(point, self.crystal_basis)
+            if self.debug >= 2:
+                print ("Placing an atom at (%d,%d,%d) ~ (%.3f, %.3f, %.3f)."
+                       % (tuple(point) + tuple(pos)))
+            self.atoms[self.nput] = pos
+            self.elements[self.nput] = self.atomicnumber
+            self.nput += 1
+        else:
+            for i, offset in enumerate(self.natural_bravais_basis):
+                pos = np.dot(point + offset, self.crystal_basis)
+                if self.debug >= 2:
+                    print ("Placing an atom at (%d+%f, %d+%f, %d+%f) ~ (%.3f, %.3f, %.3f)."
+                           % (point[0], offset[0], point[1], offset[1],
+                              point[2], offset[2], pos[0], pos[1], pos[2]))
+                self.atoms[self.nput] = pos
+                if self.element_basis is None:
+                    self.elements[self.nput] = self.atomicnumber
+                else:
+                    self.elements[self.nput] = self.atomicnumber[i]
+                self.nput += 1
+
+    def find_directions(self, directions, miller):
+        "Find missing directions and miller indices from the specified ones."
+        directions = list(directions)
+        miller = list(miller)
+        # If no directions etc are specified, use a sensible default.
+        if directions == [None, None, None] and miller == [None, None, None]:
+            directions = [[1,0,0], [0,1,0], [0,0,1]]
+        # Now fill in missing directions and miller indices.  This is an
+        # iterative process.
+        change = 1
+        while change:
+            change = False
+            missing = 0
+            for i in (0,1,2):
+                (j,k) = self.other[i]
+                if directions[i] is None:
+                    missing += 1
+                    if miller[j] is not None and miller[k] is not None:
+                        directions[i] = reduceindex(cross(miller[j],
+                                                          miller[k]))
+                        change = True
+                        if self.debug >= 2:
+                            print "Calculating directions[%d] from miller indices" % i
+                if miller[i] is None:
+                    missing += 1
+                    if directions[j] is not None and directions[k] is not None:
+                        miller[i] = reduceindex(cross(directions[j],
+                                                      directions[k]))
+                        change = True
+                        if self.debug >= 2:
+                            print "Calculating miller[%d] from directions" % i
+        if missing:
+            raise ValueError, "Specification of directions and miller indices is incomplete."
+        # Make sure that everything is Numeric arrays
+        self.directions = np.array(directions)
+        self.miller = np.array(miller)
+        # Check for left-handed coordinate system
+        if np.linalg.det(self.directions) < 0:
+            print "WARNING: Creating a left-handed coordinate system!"
+            self.miller = -self.miller
+            self.handedness = -1
+        else:
+            self.handedness = 1
+        # Now check for consistency
+        for i in (0,1,2):
+            (j,k) = self.other[i]
+            m = reduceindex(self.handedness *
+                            cross(self.directions[j], self.directions[k]))
+            if sum(np.not_equal(m, self.miller[i])):
+                print "ERROR: Miller index %s is inconsisten with directions %d and %d" % (i,j,k)
+                print "Miller indices:"
+                print str(self.miller)
+                print "Directions:"
+                print str(self.directions)
+                raise ValueError, "Inconsistent specification of miller indices and directions."
+
+    def print_directions_and_miller(self, txt=""):
+        "Print direction vectors and Miller indices."
+        print "Direction vectors of unit cell%s:" % (txt,)
+        for i in (0,1,2):
+            print "   ", self.directions[i]
+        print "Miller indices of surfaces%s:" % (txt,)
+        for i in (0,1,2):
+            print "   ", self.miller[i]
+
+class MillerInfo:
+    """Mixin class to provide information about Miller indices."""
+    def miller_to_direction(self, miller):
+        """Returns the direction corresponding to a given Miller index."""
+        return np.dot(miller, self.millerbasis)
+
+class Lattice(Atoms, MillerInfo):
+    """List of atoms initially containing a regular lattice of atoms.
+
+    A part from the usual list of atoms methods this list of atoms type
+    also has a method, `miller_to_direction`, used to convert from Miller
+    indices to directions in the coordinate system of the lattice.
+    """
+    pass
+
+# Helper functions
+def cross(a, b):
+    """The cross product of two vectors."""
+    return np.array((a[1]*b[2] - b[1]*a[2],
+                     a[2]*b[0] - b[2]*a[0],
+                     a[0]*b[1] - b[0]*a[1]))
+
+def gcd(a,b):
+    """Greatest Common Divisor of a and b."""
+    while a != 0:
+        a,b = b%a,a
+    return b
+
+def reduceindex(M):
+    "Reduce Miller index to the lowest equivalent integers."
+    oldM = M
+    g = gcd(M[0], M[1])
+    h = gcd(g, M[2])
+    while h != 1:
+        M = M/h
+        g = gcd(M[0], M[1])
+        h = gcd(g, M[2])
+    if np.dot(oldM, M) > 0:
+        return M
+    else:
+        return -M
+
Index: ase/lattice/compounds.py
===================================================================
--- ase/lattice/compounds.py	(revision 0)
+++ ase/lattice/compounds.py	(revision 1)
@@ -0,0 +1,73 @@
+"""Function-like objects creating lattices with more than one element.
+
+These lattice creators are mainly intended as examples for how to build you
+own.  The following crystal structures are defined:
+
+    B1 = NaCl = Rocksalt
+    B2 = CsCl
+    B3 = ZnS = Zincblende
+    L1_2 = AuCu3
+    L1_0 = AuCu
+    
+"""
+from ase.lattice.cubic import FaceCenteredCubicFactory,\
+    BodyCenteredCubicFactory, DiamondFactory, SimpleCubicFactory
+from ase.lattice.tetragonal import SimpleTetragonalFactory
+import numpy as np
+from ase.data import reference_states as _refstate
+
+
+# To prevent a layer of element one on one side, and a layer of
+# element two on the other side, NaCl is based on SimpleCubic instead
+# of on FaceCenteredCubic
+class NaClFactory(SimpleCubicFactory):
+    "A factory for creating NaCl (B1, Rocksalt) lattices."
+
+    bravais_basis = [[0, 0, 0], [0, 0, 0.5], [0, 0.5, 0], [0, 0.5, 0.5],
+                     [0.5, 0, 0], [0.5, 0, 0.5], [0.5, 0.5, 0],
+                     [0.5, 0.5, 0.5]]
+    element_basis = (0, 1, 1, 0, 1, 0, 0, 1)
+    
+
+B1 = NaCl = Rocksalt = NaClFactory()
+
+class CsClFactory(SimpleCubicFactory):
+    "A factory for creating CsCl (B2) lattices."
+    bravais_basis = [[0, 0, 0], [0.5, 0.5, 0.5]]
+    element_basis = (0, 1)
+
+B2 = CsCl = CsClFactory()
+
+
+#The zincblende structure is easily derived from Diamond, which
+#already has the right basis.
+class ZnSFactory(DiamondFactory):
+    "A factory for creating ZnS (B3, Zincblende) lattices."
+    element_basis = (0, 1)
+
+B3 = ZnS = Zincblende = ZnSFactory()
+
+
+# The L1_0 structure is "based on FCC", but is a tetragonal distortion
+# of fcc.  It must therefore be derived from the base-centered
+# tetragonal structure.  That structure, however, does not exist,
+# since it is equivalent to a simple tetragonal structure rotated 45
+# degrees along the z-axis.  Basing L1_2 on that would however give
+# unexpected miller indices.  L1_2 will therefore be based on a simple
+# tetragonal structure, but with a basis corresponding to a
+# base-centered tetragonal.
+class AuCuFactory(SimpleTetragonalFactory):
+    "A factory for creating AuCu (L1_0) lattices (tetragonal symmetry)."
+    bravais_basis = [[0, 0, 0], [0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]]
+    element_basis = (0, 1, 1, 0)
+
+AuCu = L1_0 = AuCuFactory()
+
+# The L1_2 structure is "based on FCC", but is really simple cubic
+# with a basis.
+class AuCu3Factory(SimpleCubicFactory):
+    "A factory for creating AuCu3 (L1_2) lattices."
+    bravais_basis = [[0, 0, 0], [0, 0.5, 0.5], [0.5, 0, 0.5], [0.5, 0.5, 0]]
+    element_basis = (0, 1, 1, 1)
+
+AuCu3 = L1_2 = AuCu3Factory()
Index: ase/lattice/surface.py
===================================================================
--- ase/lattice/surface.py	(revision 0)
+++ ase/lattice/surface.py	(revision 1)
@@ -0,0 +1,295 @@
+"""Helper functions for creating the most common surfaces and related tasks.
+
+The helper functions can create the most common low-index surfaces,
+add vacuum layers and add adsorbates.
+
+"""
+
+from math import sqrt
+
+import numpy as np
+
+from ase.atom import Atom
+from ase.atoms import Atoms
+from ase.data import reference_states, atomic_numbers
+
+
+def fcc100(symbol, size, a=None, vacuum=None):
+    """FCC(100) surface.
+ 
+    Supported special adsorption sites: 'ontop', 'bridge', 'hollow'."""
+    return surface(symbol, 'fcc', '100', size, a, None, vacuum)
+
+def fcc110(symbol, size, a=None, vacuum=None):
+    """FCC(110) surface.
+ 
+    Supported special adsorption sites: 'ontop', 'longbridge',
+    'shortbridge','hollow'."""
+    return surface(symbol, 'fcc', '110', size, a, None, vacuum)
+
+def bcc100(symbol, size, a=None, vacuum=None):
+    """BCC(100) surface.
+ 
+    Supported special adsorption sites: 'ontop', 'bridge', 'hollow'."""
+    return surface(symbol, 'bcc', '100', size, a, None, vacuum)
+
+def bcc110(symbol, size, a=None, vacuum=None, orthogonal=False):
+    """BCC(110) surface.
+ 
+    Supported special adsorption sites: 'ontop', 'longbridge',
+    'shortbridge', 'hollow'.
+ 
+    Use *orthogonal=True* to get an orthogonal unit cell - works only
+    for size=(i,j,k) with j even."""
+    return surface(symbol, 'bcc', '110', size, a, None, vacuum, orthogonal)
+
+def bcc111(symbol, size, a=None, vacuum=None, orthogonal=False):
+    """BCC(111) surface.
+ 
+    Supported special adsorption sites: 'ontop'.
+ 
+    Use *orthogonal=True* to get an orthogonal unit cell - works only
+    for size=(i,j,k) with j even."""
+    return surface(symbol, 'bcc', '111', size, a, None, vacuum, orthogonal)
+
+def fcc111(symbol, size, a=None, vacuum=None, orthogonal=False):
+    """FCC(111) surface.
+ 
+    Supported special adsorption sites: 'ontop', 'bridge', 'fcc' and 'hcp'.
+ 
+    Use *orthogonal=True* to get an orthogonal unit cell - works only
+    for size=(i,j,k) with j even."""
+    return surface(symbol, 'fcc', '111', size, a, None, vacuum, orthogonal)
+
+def hcp0001(symbol, size, a=None, c=None, vacuum=None, orthogonal=False):
+    """HCP(0001) surface.
+ 
+    Supported special adsorption sites: 'ontop', 'bridge', 'fcc' and 'hcp'.
+ 
+    Use *orthogonal=True* to get an orthogonal unit cell - works only
+    for size=(i,j,k) with j even."""
+    return surface(symbol, 'hcp', '0001', size, a, c, vacuum, orthogonal)
+
+    
+def add_adsorbate(slab, adsorbate, height, position=(0, 0), offset=None,
+                  mol_index=0):
+    """Add an adsorbate to a surface.
+
+    This function adds an adsorbate to a slab.  If the slab is
+    produced by one of the utility functions in ase.lattice.surface, it
+    is possible to specify the position of the adsorbate by a keyword
+    (the supported keywords depend on which function was used to
+    create the slab).
+
+    If the adsorbate is a molecule, the atom indexed by the mol_index
+    optional argument is positioned on top of the adsorption position
+    on the surface, and it is the responsibility of the user to orient
+    the adsorbate in a sensible way.
+
+    This function can be called multiple times to add more than one
+    adsorbate.
+
+    Parameters:
+
+    slab: The surface onto which the adsorbate should be added.
+
+    adsorbate:  The adsorbate. Must be one of the following three types:
+        A string containing the chemical symbol for a single atom.
+        An atom object.
+        An atoms object (for a molecular adsorbate).
+
+    height: Height above the surface.
+
+    position: The x-y position of the adsorbate, either as a tuple of
+        two numbers or as a keyword (if the surface is produced by one
+        of the functions in ase.lattice.surfaces).
+
+    offset (default: None): Offsets the adsorbate by a number of unit
+        cells. Mostly useful when adding more than one adsorbate.
+
+    mol_index (default: 0): If the adsorbate is a molecule, index of
+        the atom to be positioned above the location specified by the
+        position argument.
+
+    Note *position* is given in absolute xy coordinates (or as
+    a keyword), whereas offset is specified in unit cells.  This
+    can be used to give the positions in units of the unit cell by
+    using *offset* instead.
+    
+    """
+    info = slab.adsorbate_info
+    if 'cell' not in info:
+        info['cell'] = slab.get_cell()[:2,:2]
+
+    
+    pos = np.array([0.0, 0.0])  # (x, y) part
+    spos = np.array([0.0, 0.0]) # part relative to unit cell
+    if offset is not None:
+        spos += np.asarray(offset, float)
+
+    if isinstance(position, str):
+        # A site-name:
+        if 'sites' not in info:
+            raise TypeError('If the atoms are not made by an ' +
+                            'ase.lattice.surface function, ' +
+                            'position cannot be a name.')
+        if position not in info['sites']:
+            raise TypeError('Adsorption site %s not supported.' % position)
+        spos += info['sites'][position]
+    else:
+        pos += position
+
+    pos += np.dot(spos, info['cell'])
+
+    # Convert the adsorbate to an Atoms object
+    if isinstance(adsorbate, Atoms):
+        ads = adsorbate
+    elif isinstance(adsorbate, Atom):
+        ads = Atoms([adsorbate])
+    else:
+        # Hope it is a useful string or something like that
+        ads = Atoms(adsorbate)
+
+    # Get the z-coordinate:
+    try:
+        a = info['top layer atom index']
+    except KeyError:
+        a = slab.positions[:, 2].argmax()
+        info['top layer atom index']= a
+    z = slab.positions[a, 2] + height
+
+    # Move adsorbate into position
+    ads.translate([pos[0], pos[1], z] - ads.positions[mol_index])
+
+    # Attach the adsorbate
+    slab.extend(ads)
+
+
+def surface(symbol, structure, face, size, a, c, vacuum, orthogonal=True):
+    """Function to build often used surfaces.
+
+    Don't call this function directly - use fcc100, fcc110, bcc111, ..."""
+    
+    Z = atomic_numbers[symbol]
+
+    if a is None:
+        sym = reference_states[Z]['symmetry'].lower()
+        if sym != structure:
+            raise ValueError("Can't guess lattice constant for %s-%s!" %
+                             (structure, symbol))
+        a = reference_states[Z]['a']
+
+    if structure == 'hcp' and c is None:
+        if reference_states[Z]['symmetry'].lower() == 'hcp':
+            c = reference_states[Z]['c/a'] * a
+        else:
+            c = sqrt(8 / 3.0) * a
+
+    positions = np.empty((size[2], size[1], size[0], 3))
+    positions[..., 0] = np.arange(size[0]).reshape((1, 1, -1))
+    positions[..., 1] = np.arange(size[1]).reshape((1, -1, 1))
+    positions[..., 2] = np.arange(size[2]).reshape((-1, 1, 1))
+
+    numbers = np.ones(size[0] * size[1] * size[2], int) * Z
+
+    tags = np.empty((size[2], size[1], size[0]), int)
+    tags[:] = np.arange(size[2], 0, -1).reshape((-1, 1, 1))
+
+    slab = Atoms(numbers,
+                 tags=tags.ravel(),
+                 pbc=(True, True, False),
+                 cell=size)
+
+    surface_cell = None
+    sites = {'ontop': (0, 0)}
+    surf = structure + face
+    if surf == 'fcc100':
+        cell = (sqrt(0.5), sqrt(0.5), 0.5)
+        positions[-2::-2, ..., :2] += 0.5
+        sites.update({'hollow': (0.5, 0.5), 'bridge': (0.5, 0)})
+    elif surf == 'fcc110':
+        cell = (1.0, sqrt(0.5), sqrt(0.125))
+        positions[-2::-2, ..., :2] += 0.5
+        sites.update({'hollow': (0.5, 0.5), 'longbridge': (0.5, 0),
+                      'shortbridge': (0, 0.5)})
+    elif surf == 'bcc100':
+        cell = (1.0, 1.0, 0.5)
+        positions[-2::-2, ..., :2] += 0.5
+        sites.update({'hollow': (0.5, 0.5), 'bridge': (0.5, 0)})
+    else:
+        if orthogonal and size[1] % 2 == 1:
+            raise ValueError(("Can't make orthorhombic cell with size=%r.  " %
+                              (tuple(size),)) +
+                             'Second number in size must be even.')
+        if surf == 'fcc111':
+            cell = (sqrt(0.5), sqrt(0.375), 1 / sqrt(3))
+            if orthogonal:
+                positions[-1::-3, 1::2, :, 0] += 0.5
+                positions[-2::-3, 1::2, :, 0] += 0.5
+                positions[-3::-3, 1::2, :, 0] -= 0.5
+                positions[-2::-3, ..., :2] += (0.0, 2.0 / 3)
+                positions[-3::-3, ..., :2] += (0.5, 1.0 / 3)
+            else:
+                positions[-2::-3, ..., :2] += (-1.0 / 3, 2.0 / 3)
+                positions[-3::-3, ..., :2] += (1.0 / 3, 1.0 / 3)
+            sites.update({'bridge': (0.5, 0), 'fcc': (1.0 / 3, 1.0 / 3),
+                          'hcp': (2.0 / 3, 2.0 / 3)})
+        elif surf == 'hcp0001':
+            cell = (1.0, sqrt(0.75), 0.5 * c / a)
+            if orthogonal:
+                positions[:, 1::2, :, 0] += 0.5
+                positions[-2::-2, ..., :2] += (0.0, 2.0 / 3)
+            else:
+                positions[-2::-2, ..., :2] += (-1.0 / 3, 2.0 / 3)
+            sites.update({'bridge': (0.5, 0), 'fcc': (1.0 / 3, 1.0 / 3),
+                          'hcp': (2.0 / 3, 2.0 / 3)})
+        elif surf == 'bcc110':
+            cell = (1.0, sqrt(0.5), sqrt(0.5))
+            if orthogonal:
+                positions[:, 1::2, :, 0] += 0.5
+                positions[-2::-2, ..., :2] += (0.0, 1.0)
+            else:
+                positions[-2::-2, ..., :2] += (-0.5, 1.0)
+            sites.update({'shortbridge': (0, 0.5),
+                          'longbridge': (0.5, 0),
+                          'hollow': (0.375, 0.25)})
+        elif surf == 'bcc111':
+            cell = (sqrt(2), sqrt(1.5), sqrt(3) / 6)
+            if orthogonal:
+                positions[-1::-3, 1::2, :, 0] += 0.5
+                positions[-2::-3, 1::2, :, 0] += 0.5
+                positions[-3::-3, 1::2, :, 0] -= 0.5
+                positions[-2::-3, ..., :2] += (0.0, 2.0 / 3)
+                positions[-3::-3, ..., :2] += (0.5, 1.0 / 3)
+            else:
+                positions[-2::-3, ..., :2] += (-1.0 / 3, 2.0 / 3)
+                positions[-3::-3, ..., :2] += (1.0 / 3, 1.0 / 3)
+            sites.update({'hollow': (1.0 / 3, 1.0 / 3)})
+            
+        surface_cell = a * np.array([(cell[0], 0),
+                                     (cell[0] / 2, cell[1])])
+        if not orthogonal:
+            cell = np.array([(cell[0], 0, 0),
+                             (cell[0] / 2, cell[1], 0),
+                             (0, 0, cell[2])])
+
+    if surface_cell is None:
+        surface_cell = a * np.diag(cell[:2])
+
+    if isinstance(cell, tuple):
+        cell = np.diag(cell)
+        
+    slab.set_positions(positions.reshape((-1, 3)))
+
+    slab.set_cell([a * v * n for v, n in zip(cell, size)], scale_atoms=True)
+
+    if vacuum is not None:
+        slab.center(vacuum=vacuum, axis=2)
+    
+    slab.adsorbate_info['cell'] = surface_cell
+    slab.adsorbate_info['sites'] = sites
+    
+    return slab
+
+    
+        
Index: ase/lattice/monoclinic.py
===================================================================
--- ase/lattice/monoclinic.py	(revision 0)
+++ ase/lattice/monoclinic.py	(revision 1)
@@ -0,0 +1,47 @@
+"""Function-like object creating monoclinic lattices.
+
+The following lattice creator is defined:
+    SimpleMonoclinic
+    BaseCenteredMonoclinic
+"""
+
+from ase.lattice.triclinic import TriclinicFactory
+import numpy as np
+from ase.data import reference_states as _refstate
+
+
+class SimpleMonoclinicFactory(TriclinicFactory):
+    "A factory for creating simple monoclinic lattices."
+    # The name of the crystal structure in ChemicalElements
+    xtal_name = "monoclinic"
+
+    def make_crystal_basis(self):
+        "Make the basis matrix for the crystal unit cell and the system unit cell."
+        # First convert the basis specification to a triclinic one
+        if type(self.latticeconstant) == type({}):
+            self.latticeconstant['beta'] = 90
+            self.latticeconstant['gamma'] = 90
+        else:
+            if len(self.latticeconstant) == 4:
+                self.latticeconstant = self.latticeconstant + (90,90)
+            else:
+                raise ValueError, "Improper lattice constants for monoclinic crystal."
+
+        TriclinicFactory.make_crystal_basis(self)
+        
+SimpleMonoclinic = SimpleMonoclinicFactory()
+
+class BaseCenteredMonoclinicFactory(SimpleMonoclinicFactory):
+    # The natural basis vectors of the crystal structure
+    int_basis = np.array([[1, -1, 0],
+                          [1, 1, 0],
+                          [0, 0, 2]])
+    basis_factor = 0.5
+
+    # Converts the natural basis back to the crystallographic basis
+    inverse_basis = np.array([[1, 1, 0],
+                              [-1, 1, 0],
+                              [0, 0, 1]])
+    inverse_basis_factor = 1.0
+
+BaseCenteredMonoclinic = BaseCenteredMonoclinicFactory()
Index: ase/__init__.py
===================================================================
--- ase/__init__.py	(revision 0)
+++ ase/__init__.py	(revision 1)
@@ -0,0 +1,77 @@
+# Copyright 2008, 2009 CAMd
+# (see accompanying license files for details).
+
+"""Atomic Simulation Environment."""
+
+
+from ase.atom import Atom
+from ase.atoms import Atoms
+
+_deprecate_things_from_ase_module = True
+
+# Some day in the future, we will uncomment this line:
+#__all__ = ['Atoms', 'Atom']  
+
+from ase.units import *
+from ase.io import read, write
+from ase.io.trajectory import PickleTrajectory
+from ase.dft import STM, monkhorst_pack, DOS
+from ase.optimize.mdmin import MDMin
+from ase.optimize.lbfgs import HessLBFGS
+from ase.optimize.fire import FIRE
+from ase.optimize.lbfgs import LBFGS, LBFGSLineSearch
+from ase.optimize.bfgs import BFGS
+from ase.optimize import QuasiNewton
+from ase.md.verlet import VelocityVerlet
+from ase.md.langevin import Langevin
+from ase.constraints import *
+from ase.calculators.lj import LennardJones
+from ase.calculators.emt import EMT
+from ase.calculators.siesta import Siesta
+from ase.calculators.dacapo import Dacapo
+from ase.calculators.vasp import Vasp
+from ase.calculators.aims import Aims, AimsCube
+from ase.calculators.turbomole import Turbomole
+from ase.calculators.dftb import Dftb
+from ase.neb import NEB, SingleCalculatorNEB
+from ase.visualize import view
+from ase.data import chemical_symbols, atomic_numbers, atomic_names, \
+     atomic_masses, covalent_radii, reference_states
+from ase.data.molecules import molecule
+
+from math import sqrt, pi
+import numpy as np
+
+
+
+if _deprecate_things_from_ase_module:
+    import types
+    import ase.utils.deprecate as dep
+
+    _locals = locals()
+
+    for name, obj in _locals.items():
+        if name.startswith('_') or name in ['Atoms', 'Atom']:
+            continue
+
+        if isinstance(obj, float):
+            if name == 'pi':
+                pi = dep.DeprecatedFloat(pi, 'pi', 'math')
+            else:
+                _locals[name] = dep.DeprecatedFloat(obj, name, 'ase.units')
+        elif isinstance(obj, types.ModuleType):
+            pass  # how about np?  XXX
+        elif hasattr(obj, '__module__'):
+            module = obj.__module__
+            if module.startswith('ase.optimize'):
+                module = 'ase.optimize'
+            elif module.startswith('ase.md'):
+                module = 'ase.md'
+            elif name == 'PickleTrajectory':
+                module = 'ase.io'
+            _locals[name] = dep.Deprecate(obj, name, module)
+        else:
+            pass  # how about atomic_numbers, covalent_radii, ... ? XXX
+
+    np = dep.DeprecatedNumpyImport()
+
Index: ase/atom.py
===================================================================
--- ase/atom.py	(revision 0)
+++ ase/atom.py	(revision 1)
@@ -0,0 +1,267 @@
+"""This module defines the Atom object."""
+
+import numpy as np
+
+from ase.data import atomic_numbers, chemical_symbols, atomic_masses
+
+
+#        singular,    plural,     type,  shape
+data = {'symbol':   ('symbols',   str,   ()  ),
+        'number':   ('numbers',   int,   ()  ),
+        'position': ('positions', float, (3,)),
+        'tag':      ('tags',      int,   ()  ),
+        'momentum': ('momenta',   float, (3,)),
+        'mass':     ('masses',    float, ()  ),
+        'magmom':   ('magmoms',   float, ()  ),
+        'charge':   ('charges',   float, ()  ),
+        }
+
+class Atom(object):
+    """Class for representing a single atom.
+
+        Parameters:
+
+        symbol: str or int
+            Can be a chemical symbol (str) or an atomic number (int).
+        position: sequence of 3 floats
+            Atomi position.
+        tag: int
+            Special purpose tag.
+        momentum: sequence of 3 floats
+            Momentum for atom.
+        mass: float
+            Atomic mass in atomic units.
+        magmom: float
+            Magnetic moment.
+        charge: float
+            Atomic charge.
+
+        Examples:
+
+        >>> a = Atom('O', charge=-2)
+        >>> b = Atom(8, charge=-2)
+        >>> c = Atom('H', (1, 2, 3), magmom=1)
+        >>> print a.charge, a.position
+        -2 [ 0. 0. 0.]
+        >>> c.x = 0.0
+        >>> c.position
+        array([ 0.,  2.,  3.])
+        >>> b.symbol
+        'O'
+        >>> c.tag = 42
+        >>> c.number
+        1
+        >>> c.symbol = 'Li'
+        >>> c.number
+        3
+
+        If the atom object belongs to an Atoms object, then assigning
+        values to the atom attributes will change the corresponding
+        arrays of the atoms object:
+
+        >>> OH = Atoms('OH')
+        >>> OH[0].charge = -1
+        >>> OH.get_charges()
+        array([-1.,  0.])
+
+        Another example:
+
+        >>> for atom in bulk:
+        ...     if atom.symbol = 'Ni':
+        ...         atom.magmom = 0.7
+        
+
+        """
+
+    __slots__ = ['_number', '_symbol', '_position', '_tag', '_momentum',
+                 '_mass', '_magmom', '_charge', 'atoms', 'index']
+
+    def __init__(self, symbol='X', position=(0, 0, 0),
+                 tag=None, momentum=None, mass=None,
+                 magmom=None, charge=None,
+                 atoms=None, index=None):
+        if atoms is None:
+            # This atom is not part of any Atoms object:
+            if isinstance(symbol, str):
+                self._number = atomic_numbers[symbol]
+                self._symbol = symbol
+            else:
+                self._number = symbol
+                self._symbol = chemical_symbols[symbol]
+            self._position = np.array(position, float)
+            self._tag = tag
+            if momentum is not None:
+                momentum = np.array(momentum, float)
+            self._momentum = momentum
+            self._mass = mass
+            self._magmom = magmom
+            self._charge = charge
+
+        self.index = index
+        self.atoms = atoms
+
+    def __repr__(self):
+        s = "Atom('%s', %s" % (self.symbol, list(self.position))
+        for attr in ['tag', 'momentum', 'mass', 'magmom', 'charge']:
+            value = getattr(self, attr)
+            if value is not None:
+                if isinstance(value, np.ndarray):
+                    value = value.tolist()
+                s += ', %s=%s' % (attr, value)
+        if self.atoms is None:
+            s += ')'
+        else:
+            s += ', index=%d)' % self.index
+        return s
+
+    def get_data(self):
+        """Helper method."""
+        return (self.position, self.number,
+                self.tag, self.momentum, self.mass,
+                self.magmom, self.charge)
+
+    def cut_reference_to_atoms(self):
+        """Cut reference to atoms object."""
+        data = self.get_data()
+        self.index = None
+        self.atoms = None
+        (self._position,
+         self._number,
+         self._tag,
+         self._momentum,
+         self._mass,
+         self._magmom,
+         self._charge) = data
+        self._symbol = chemical_symbols[self._number]
+        
+    def _get(self, name):
+        if self.atoms is None:
+            return getattr(self, '_' + name)
+        elif name == 'symbol':
+            return chemical_symbols[self.number]
+        else:
+            plural = data[name][0]
+            if plural in self.atoms.arrays:
+                return self.atoms.arrays[plural][self.index]
+            else:
+                return None
+
+    def _get_copy(self, name, copy=False):
+        if self.atoms is None:
+            return getattr(self, '_' + name)
+        elif name == 'symbol':
+            return chemical_symbols[self.number]
+        else:
+            plural = data[name][0]
+            if plural in self.atoms.arrays:
+                return self.atoms.arrays[plural][self.index].copy()
+            else:
+                return None
+
+    def _set(self, name, value):
+        if self.atoms is None:
+            setattr(self, '_' + name, value)
+            if name == 'symbol':
+                self._number = atomic_numbers[value]
+            elif name == 'number':
+                self._symbol = chemical_symbols[value]
+        elif name == 'symbol':
+            self.number = atomic_numbers[value]
+        else:
+            plural, dtype, shape = data[name]
+            if plural in self.atoms.arrays:
+                self.atoms.arrays[plural][self.index] = value
+            else:
+                array = np.zeros((len(self.atoms),) + shape, dtype)
+                array[self.index] = value
+                self.atoms.new_array(plural, array)
+
+    def get_symbol(self): return self._get('symbol')
+    def get_atomic_number(self): return self._get('number')
+    def get_position(self): return self._get_copy('position')
+    def _get_position(self): return self._get('position')
+    def get_tag(self): return self._get('tag')
+    def get_momentum(self): return self._get_copy('momentum')
+    def _get_momentum(self): return self._get('momentum')
+    def get_initial_magnetic_moment(self): return self._get('magmom')
+    def get_charge(self): return self._get('charge')
+
+    def set_symbol(self, symbol): self._set('symbol', symbol)
+    def set_atomic_number(self, number): self._set('number', number)
+    def set_position(self, position):
+        self._set('position', np.array(position, float))
+    def set_tag(self, tag): self._set('tag', tag)
+    def set_momentum(self, momentum): self._set('momentum', momentum)
+    def set_initial_magnetic_moment(self, magmom): self._set('magmom', magmom)
+    def set_charge(self, charge): self._set('charge', charge)
+
+    def set_magmom(self, magmom):
+        "Deprecated, use set_initial_magnetic_moment instead."
+        import warnings
+        warnings.warn('set_magmom is deprecated. Please use set_initial_magnetic_moment' \
+                      ' instead.', DeprecationWarning, stacklevel=2)
+        return self.set_initial_magnetic_moment(magmom)
+
+    def get_number(self):
+        "Deprecated, use get_atomic_number instead."
+        import warnings
+        warnings.warn(
+            'get_number is deprecated. Please use get_atomic_number instead.',
+            DeprecationWarning, stacklevel=2)
+        return self.get_atomic_number()
+        
+    def set_number(self, number):
+        "Deprecated, use set_atomic_number instead."
+        import warnings
+        warnings.warn(
+            'set_number is deprecated. Please use set_atomic_number instead.',
+            DeprecationWarning, stacklevel=2)
+        return self.set_atomic_number(number)
+
+    def get_mass(self):
+        """Get the mass of the atom.
+
+        Returns the mass of the atom, if known.  If unknown, returns the
+        atomic mass corresponding to the element.
+        """
+        m = self._get('mass')
+        if m is None:
+            m = atomic_masses[self.get_atomic_number()]
+        return m
+
+    def set_mass(self, mass):
+        """Sets the mass of the atom.
+
+        If the atom is part of a list of atoms, and the atoms do not yet
+        have masses, all other atoms are assigned their default masses.
+        """
+        if self.atoms is None:
+            self._mass = mass
+        else:
+            if 'masses' not in self.atoms.arrays:
+                # Assign default masses to all atoms
+                self.atoms.set_masses(self.atoms.get_masses())
+            self.atoms.arrays['masses'][self.index] = mass        
+        
+    symbol = property(get_symbol, set_symbol, doc='Chemical symbol')
+    number = property(get_atomic_number, set_atomic_number, doc='Atomic number')
+    position = property(_get_position, set_position, doc='XYZ-coordinates')
+    tag = property(get_tag, set_tag, doc='Integer tag')
+    momentum = property(_get_momentum, set_momentum, doc='XYZ-momentum')
+    mass = property(get_mass, set_mass, doc='Atomic mass')
+    magmom = property(get_initial_magnetic_moment, set_initial_magnetic_moment,
+                      doc='Initial magnetic moment')
+    charge = property(get_charge, set_charge, doc='Atomic charge')
+
+    def get_x(self): return self.position[0]
+    def get_y(self): return self.position[1]
+    def get_z(self): return self.position[2]
+    
+    def set_x(self, x): self.position[0] = x
+    def set_y(self, y): self.position[1] = y
+    def set_z(self, z): self.position[2] = z
+
+    x = property(get_x, set_x, doc='X-coordiante')
+    y = property(get_y, set_y, doc='Y-coordiante')
+    z = property(get_z, set_z, doc='Z-coordiante')
+
Index: ase/structure.py
===================================================================
--- ase/structure.py	(revision 0)
+++ ase/structure.py	(revision 1)
@@ -0,0 +1,396 @@
+"""Atomic structure.
+
+This mudule contains helper functions for setting up nanotubes,
+graphene nanoribbons and simple bulk crystals."""
+
+from math import sqrt
+
+import numpy as np
+
+from ase.atoms import Atoms, string2symbols
+from ase.data import covalent_radii
+
+
+def gcd(m,n):
+    while n:
+        m,n=n,m%n
+    return m
+
+def nanotube(n, m, length=1, bond=1.42, symbol='C', verbose=False):
+    if n < m:
+        m, n = n, m
+    nk = 6000
+    sq3 = sqrt(3.0)
+    a = sq3 * bond
+    l2 = n * n + m * m + n * m
+    l = sqrt(l2)
+    dt = a * l / np.pi
+
+    nd = gcd(n ,m)
+    if (n - m) % (3 * nd ) == 0:
+        ndr = 3 * nd
+    else:
+        ndr = nd
+
+    nr = (2 * m + n) / ndr
+    ns = -(2 * n + m) / ndr
+    nt2 = 3 * l2 / ndr / ndr
+    nt = np.floor(sqrt(nt2))
+    nn = 2 * l2 / ndr
+
+    ichk = 0
+    if nr == 0:
+        n60 = 1
+    else:
+        n60 = nr * 4
+   
+    absn = abs(n60)
+    nnp = []
+    nnq = []
+    for i in range(-absn, absn + 1):
+        for j in range(-absn, absn + 1):
+            j2 = nr * j - ns * i
+            if j2 == 1:
+                j1 = m * i - n * j
+                if j1 > 0 and j1 < nn:
+                    ichk += 1
+                    nnp.append(i)
+                    nnq.append(j)
+
+    if ichk == 0:
+        raise RuntimeError('not found p, q strange!!')
+    if ichk >= 2:
+        raise RuntimeError('more than 1 pair p, q strange!!')
+
+    nnnp = nnp[0]
+    nnnq = nnq[0]
+
+    if verbose:   
+        print 'the symmetry vector is', nnnp, nnnq
+
+    lp = nnnp * nnnp + nnnq * nnnq + nnnp * nnnq
+    r = a * sqrt(lp)
+    c = a * l
+    t = sq3 * c / ndr
+   
+    if 2 * nn > nk:
+        raise RuntimeError('parameter nk is too small!')
+
+    rs = c / (2.0 * np.pi)
+   
+    if verbose:
+        print 'radius=', rs, t
+
+    q1 = np.arctan((sq3 * m) / (2 * n + m))
+    q2 = np.arctan((sq3 * nnnq) / (2 * nnnp + nnnq))
+    q3 = q1 - q2
+
+    q4 = 2.0 * np.pi / nn
+    q5 = bond * np.cos((np.pi / 6.0) - q1) / c * 2.0 * np.pi
+
+    h1 = abs(t) / abs(np.sin(q3))
+    h2 = bond * np.sin((np.pi / 6.0) - q1)
+
+    ii = 0
+    x, y, z = [], [], []   
+    for i in range(nn):
+        x1, y1, z1 = 0, 0, 0
+   
+        k = np.floor(i * abs(r) / h1)
+        x1 = rs * np.cos(i * q4)
+        y1 = rs * np.sin(i * q4)
+        z1 = (i * abs(r) - k * h1) * np.sin(q3)
+        kk2 = abs(np.floor((z1 + 0.0001) / t))
+        if z1 >= t - 0.0001:
+            z1 -= t * kk2
+        elif z1 < 0:
+            z1 += t * kk2
+        ii += 1
+
+        x.append(x1)
+        y.append(y1)
+        z.append(z1)
+        z3 = (i * abs(r) - k * h1) * np.sin(q3) - h2
+        ii += 1
+
+        if z3 >= 0 and z3 < t:
+            x2 = rs * np.cos(i * q4 + q5)
+            y2 = rs * np.sin(i * q4 + q5)
+            z2 = (i * abs(r) - k * h1) * np.sin(q3) - h2
+            x.append(x2)
+            y.append(y2)
+            z.append(z2)
+        else:
+            x2 = rs * np.cos(i * q4 + q5)
+            y2 = rs * np.sin(i * q4 + q5)
+            z2 = (i * abs(r) - (k + 1) * h1) * np.sin(q3) - h2
+            kk = abs(np.floor(z2 / t))
+            if z2 >= t - 0.0001:
+                z2 -= t * kk
+            elif z2 < 0:
+                z2 += t * kk
+            x.append(x2)
+            y.append(y2)
+            z.append(z2) 
+       
+    ntotal = 2 * nn
+    X = []
+    for i in range(ntotal):
+        X.append([x[i], y[i], z[i]])
+
+    if length > 1:
+        xx = X[:]
+        for mnp in range(2, length + 1):
+            for i in range(len(xx)):
+                X.append(xx[i][:2] + [xx[i][2] + (mnp - 1) * t])
+               
+    TransVec = t
+    NumAtom = ntotal * length
+    Diameter = rs * 2
+    ChiralAngle = np.arctan((sq3 * n) / (2 * m + n)) / (np.pi * 180)
+   
+    cell = [Diameter * 2, Diameter * 2, length * t]
+    atoms = Atoms(symbol + str(NumAtom), positions=X, cell=cell,
+                  pbc=[False, False, True])
+    atoms.center()
+    if verbose:
+        print 'translation vector =', TransVec
+        print 'diameter = ', Diameter
+        print 'chiral angle = ', ChiralAngle
+    return atoms
+
+def graphene_nanoribbon(n, m, type='zigzag', saturated=False, C_H=1.09,
+                        C_C=1.42, vacc=5., magnetic=None, initial_mag=1.12,
+                        sheet=False, main_element='C', satur_element='H'):
+    """Create a graphene nanoribbon.
+
+    Creates a graphene nanoribbon in the x-z plane, with the nanoribbon
+    running along the z axis.
+
+    Parameters:
+
+    n: The width of the nanoribbon
+
+    m: The length of the nanoribbon.
+
+    type ('zigzag'): The orientation of the ribbon.  Must be either 'zigzag'
+    or 'armchair'.
+
+    saturated (Falsi):  If true, hydrogen atoms are placed along the edge.
+
+    C_H: Carbon-hydrogen bond length.  Default: 1.09 Angstrom
+
+    C_C: Carbon-carbon bond length.  Default: 1.42 Angstrom.
+
+    vacc:  Amount of vacuum.  Default 5 Angstrom.
+
+    magnetic:  Make the edges magnetic.
+
+    initial_mag: Magnitude of magnetic moment if magnetic=True.
+
+    sheet:  If true, make an infinite sheet instead of a ribbon.
+    """
+    #This function creates the coordinates for a graphene nanoribbon,
+    #n is width, m is length
+    
+    b = sqrt(3) * C_C / 4
+    arm_unit = Atoms(main_element+'4', pbc=(1,0,1),
+                     cell = [4 * b,  vacc,  3 * C_C])
+    arm_unit.positions = [[0, 0, 0],
+                          [b * 2, 0, C_C / 2.],
+                          [b * 2, 0, 3 * C_C / 2.],
+                          [0, 0, 2 * C_C]]
+    zz_unit = Atoms(main_element+'2', pbc=(1,0,1),
+                    cell = [3 * C_C /2., vacc, b * 4])
+    zz_unit.positions = [[0, 0, 0],
+                         [C_C / 2., 0, b * 2]]
+    atoms = Atoms()   
+    tol = 1e-4   
+    if sheet:
+        vacc2 = 0.0
+    else:
+        vacc2 = vacc
+    if type == 'zigzag':
+        edge_index0 = np.arange(m) * 2 + 1
+        edge_index1 = (n - 1) * m * 2 + np.arange(m) * 2
+        if magnetic:
+            mms = np.zeros(m * n * 2)
+            for i in edge_index0:
+                mms[i] = initial_mag 
+            for i in edge_index1:
+                mms[i] = -initial_mag
+               
+        for i in range(n):
+            layer = zz_unit.repeat((1, 1, m))
+            layer.positions[:, 0] -= 3 * C_C / 2 * i
+            if i % 2 == 1:
+                layer.positions[:, 2] += 2 * b
+                layer[-1].position[2] -= b * 4 * m
+            atoms += layer
+        if magnetic:
+            atoms.set_initial_magnetic_moments(mms)
+        if saturated:
+            H_atoms0 = Atoms(satur_element + str(m))
+            H_atoms0.positions = atoms[edge_index0].positions
+            H_atoms0.positions[:, 0] += C_H
+            H_atoms1 = Atoms(satur_element + str(m))
+            H_atoms1.positions = atoms[edge_index1].positions
+            H_atoms1.positions[:, 0] -= C_H
+            atoms += H_atoms0 + H_atoms1
+        atoms.cell = [n * 3 * C_C / 2 + vacc2, vacc, m * 4 * b]
+   
+    elif type == 'armchair':
+        for i in range(n):
+            layer = arm_unit.repeat((1, 1, m))
+            layer.positions[:, 0] -= 4 * b * i
+            atoms += layer
+        atoms.cell = [b * 4 * n + vacc2, vacc, 3 * C_C * m]
+    atoms.center()
+    atoms.set_pbc([sheet, False, True])
+    return atoms
+
+
+def bulk(name, crystalstructure, a=None, c=None, covera=None,
+         orthorhombic=False, cubic=False):
+    """Helper function for creating bulk systems.
+
+    name: str
+        Chemical symbol or symbols as in 'MgO' or 'NaCl'.
+    crystalstructure: str
+        Must be one of sc, fcc, bcc, hcp, diamond, zincblende or
+        rocksalt.
+    a: float
+        Lattice constant.
+    c: float
+        Lattice constant.
+    covera: float
+        c/a raitio used for hcp.  Defaults to ideal ratio.
+    orthorhombic: bool
+        Construct orthorhombic unit cell instead of primitive cell
+        which is the default.
+    cubic: bool
+        Construct cubic unit cell.
+    """
+
+    if covera is not None and  c is not None:
+        raise ValueError("Don't specify both c and c/a!")
+    
+    if covera is None and c is None:
+        covera = sqrt(8.0 / 3.0)
+        
+    if a is None:
+        a = estimate_lattice_constant(name, crystalstructure, covera)
+
+    if covera is None and c is not None:
+        covera = c / a
+        
+    x = crystalstructure.lower()
+
+    if orthorhombic and x != 'sc':
+        return _orthorhombic_bulk(name, x, a, covera)
+
+    if cubic and x == 'bcc':
+        return _orthorhombic_bulk(name, x, a, covera)
+
+    if cubic and x != 'sc':
+        return _cubic_bulk(name, x, a)
+    
+    if x == 'sc':
+        atoms = Atoms(name, cell=(a, a, a), pbc=True)
+    elif x == 'fcc':
+        b = a / 2
+        atoms = Atoms(name, cell=[(0, b, b), (b, 0, b), (b, b, 0)], pbc=True)
+    elif x == 'bcc':
+        b = a / 2
+        atoms = Atoms(name, cell=[(-b, b, b), (b, -b, b), (b, b, -b)],
+                      pbc=True)
+    elif x == 'hcp':
+        atoms = Atoms(2 * name,
+                      scaled_positions=[(0, 0, 0),
+                                        (1.0 / 3.0, 1.0 / 3.0, 0.5)],
+                      cell=[(a, 0, 0),
+                            (a / 2, a * sqrt(3) / 2, 0),
+                            (0, 0, covera * a)],
+                      pbc=True)
+    elif x == 'diamond':
+        atoms = bulk(2 * name, 'zincblende', a)
+    elif x == 'zincblende':
+        s1, s2 = string2symbols(name)
+        atoms = bulk(s1, 'fcc', a) + bulk(s2, 'fcc', a)
+        atoms.positions[1] += a / 4
+    elif x == 'rocksalt':
+        s1, s2 = string2symbols(name)
+        atoms = bulk(s1, 'fcc', a) + bulk(s2, 'fcc', a)
+        atoms.positions[1, 0] += a / 2
+    else:
+        raise ValueError('Unknown crystal structure: ' + crystalstructure)
+    
+    return atoms
+
+def estimate_lattice_constant(name, crystalstructure, covera):
+    atoms = bulk(name, crystalstructure, 1.0, covera)
+    v0 = atoms.get_volume()
+    v = 0.0
+    for Z in atoms.get_atomic_numbers():
+        r = covalent_radii[Z]
+        v += 4 * np.pi / 3 * r**3 * 1.5
+    return (v / v0)**(1.0 / 3)
+
+def _orthorhombic_bulk(name, x, a, covera=None):
+    if x == 'fcc':
+        b = a / sqrt(2)
+        atoms = Atoms(2 * name, cell=(b, b, a), pbc=True,
+                      scaled_positions=[(0, 0, 0), (0.5, 0.5, 0.5)])
+    elif x == 'bcc':
+        atoms = Atoms(2 * name, cell=(a, a, a), pbc=True,
+                      scaled_positions=[(0, 0, 0), (0.5, 0.5, 0.5)])
+    elif x == 'hcp':
+        atoms = Atoms(4 * name,
+                      cell=(a, a * sqrt(3), covera * a),
+                      scaled_positions=[(0, 0, 0),
+                                        (0.5, 0.5, 0),
+                                        (0.5, 1.0 / 6.0, 0.5),
+                                        (0, 2.0 / 3.0, 0.5)],
+                      pbc=True)
+    elif x == 'diamond':
+        atoms = _orthorhombic_bulk(2 * name, 'zincblende', a)
+    elif x == 'zincblende':
+        s1, s2 = string2symbols(name)
+        b = a / sqrt(2)
+        atoms = Atoms(2 * name, cell=(b, b, a), pbc=True,
+                      scaled_positions=[(0, 0, 0), (0.5, 0, 0.25),
+                                        (0.5, 0.5, 0.5), (0, 0.5, 0.75)])
+    elif x == 'rocksalt':
+        s1, s2 = string2symbols(name)
+        b = a / sqrt(2)
+        atoms = Atoms(2 * name, cell=(b, b, a), pbc=True,
+                      scaled_positions=[(0, 0, 0), (0.5, 0.5, 0),
+                                        (0.5, 0.5, 0.5), (0, 0, 0.5)])
+    else:
+        raise RuntimeError
+    
+    return atoms
+
+def _cubic_bulk(name, x, a):
+    if x == 'fcc':
+        atoms = Atoms(4 * name, cell=(a, a, a), pbc=True,
+                      scaled_positions=[(0, 0, 0), (0, 0.5, 0.5),
+                                        (0.5, 0, 0.5), (0.5, 0.5, 0)])
+    elif x == 'diamond':
+        atoms = _cubic_bulk(2 * name, 'zincblende', a)
+    elif x == 'zincblende':
+        atoms = Atoms(4 * name, cell=(a, a, a), pbc=True,
+                      scaled_positions=[(0, 0, 0), (0.25, 0.25, 0.25),
+                                        (0, 0.5, 0.5), (0.25, 0.75, 0.75),
+                                        (0.5, 0, 0.5), (0.75, 0.25, 0.75),
+                                        (0.5, 0.5, 0), (0.75, 0.75, 0.25)])
+    elif x == 'rocksalt':
+        atoms = Atoms(4 * name, cell=(a, a, a), pbc=True,
+                      scaled_positions=[(0, 0, 0), (0.5, 0, 0),
+                                        (0, 0.5, 0.5), (0.5, 0.5, 0.5),
+                                        (0.5, 0, 0.5), (0, 0, 0.5),
+                                        (0.5, 0.5, 0), (0, 0.5, 0)])
+    else:
+        raise RuntimeError
+    
+    return atoms
Index: ase/units.py
===================================================================
--- ase/units.py	(revision 0)
+++ ase/units.py	(revision 1)
@@ -0,0 +1,40 @@
+from math import pi, sqrt
+
+# Constants from Konrad Hinsen's PhysicalQuantities module:
+_c = 299792458.              # speed of light, m/s
+_mu0 = 4.e-7 * pi            # permeability of vacuum
+_eps0 = 1 / _mu0 / _c**2     # permittivity of vacuum
+_Grav = 6.67259e-11          # gravitational constant
+_hplanck = 6.6260755e-34     # Planck constant, J s
+_hbar = _hplanck / (2 * pi)  # Planck constant / 2pi, J s
+_e = 1.60217733e-19          # elementary charge
+_me = 9.1093897e-31          # electron mass
+_mp = 1.6726231e-27          # proton mass
+_Nav = 6.0221367e23          # Avogadro number
+_k = 1.380658e-23            # Boltzmann constant, J/K
+_amu = 1.6605402e-27         # atomic mass unit, kg
+
+Ang = Angstrom = 1.0
+nm = 10.0
+Bohr = 4e10 * pi * _eps0 * _hbar**2 / _me / _e**2  # Bohr radius
+
+eV = 1.0
+Hartree = _me * _e**3 / 16 / pi**2 / _eps0**2 / _hbar**2
+kJ = 1000.0 / _e
+kcal = 4.184 * kJ
+mol = _Nav
+Rydberg = 0.5 * Hartree
+Ry = Rydberg
+Ha = Hartree
+
+second = 1e10 * sqrt(_e / _amu)
+fs = 1e-15 * second
+
+kB = _k / _e                 # Boltzmann constant, eV/K
+
+Pascal = (1 / _e) / 1e30  # J/m^3
+GPa = 1e9 * Pascal
+
+Debye = 1e11 *_e * _c
+
+del pi, sqrt
Index: ase/xrdebye.py
===================================================================
--- ase/xrdebye.py	(revision 0)
+++ ase/xrdebye.py	(revision 1)
@@ -0,0 +1,101 @@
+from math import exp, pi, sin, sqrt, cos, acos
+import numpy as np
+
+from ase.data import atomic_numbers
+
+# Table (1) of
+# D. WAASMAIER AND A. KIRFEL, Acta Cryst. (1995). A51, 416-431
+waasmaier = {
+    #      a1        b1         a2        b2        a3        b3          a4         b4         a5         b5        c
+    'C' : [2.657506, 14.780758, 1.078079, 0.776775, 1.490909, 42.086843,  -4.241070, -0.000294, 0.713791, 0.239535, 4.297983],
+    'S' : [6.372157, 1.514347, 5.154568, 22.092528, 1.473732, 0.061373,   1.635073,  55.445176, 1.209372, 0.646925, 0.154722],
+    'Pd': [6.121511, 0.062549,  4.784063, 0.784031, 16.631683, 8.751391,  4.318258, 34.489983, 13.246773, 0.784031, 0.883099],
+    'Ag': [6.073874, 0.055333, 17.155437, 7.896512, 4.173344, 28.443739,  0.852238, 110.376108, 17.988685, 0.716809, 0.756603],
+    'Au': [16.777389, 0.122737, 19.317156, 8.621570, 32.979682, 1.256902, 5.595453, 38.008821, 10.576854, 0.000601, -6.279078],
+    'P' : [1.950541, 0.908139, 4.146930, 27.044953, 1.494560, 0.071280, 1.522042, 67.520190, 5.729711, 1.981173, 0.155233],
+    'Cl': [1.446071, 0.052357, 6.870609, 1.193165, 6.151801, 18.343416, 1.750347, 46.398394, 0.634168, 0.401005, 0.146773],
+}
+
+class XrDebye:
+    def __init__(self, wavelength, alpha=1.01, damping=0.04, warn=True,
+                 method='Iwasa'):
+        """
+        Obtain powder x-ray spectra.
+
+        wavelength in Angstrom
+        damping in Angstrom**2
+        """
+        self.wavelength = wavelength
+        self.damping = damping
+        self.alpha = alpha
+        self.warn = warn
+        self.method = method
+
+    def set_damping(self, damping):
+        self.damping = damping
+
+    def get(self, atoms, s):
+        """Get the powder x-ray (XRD) pattern using the Debye-Formula.
+
+        After: T. Iwasa and K. Nobusada, J. Phys. Chem. C 111 (2007) 45
+               s is assumed to be in 1/Angstrom
+        """
+
+        sinth = self.wavelength * s / 2.
+        costh = sqrt(1. - sinth**2)
+        cos2th = cos(2. * acos(costh))
+        pre = exp(- self.damping * s**2 / 2)
+ 
+        if self.method == 'Iwasa':
+            pre *= costh / (1. + self.alpha * cos2th**2)
+
+        f = {}
+        def atomic(symbol):
+            if not f.has_key(symbol):
+                if self.method == 'Iwasa':
+                    f[symbol] = self.get_waasmaier(symbol, s)
+                else:
+                    f[symbol] = atomic_numbers[symbol]
+            return f[symbol]
+
+        def sinc(x):
+            if x < 1.e-6:
+                x2 = x * x
+                return 1 - x2 / 6. + x2 * x2 / 120.
+            else:
+                return sin(x) / x
+
+        I = 0.
+        for a in atoms:
+            fa = atomic(a.symbol)
+#            print a.symbol, fa
+            for b in atoms:
+                fb = atomic(b.symbol)
+
+                if a == b:
+                    twopis = 0.
+                else:
+                    vrij = a.position - b.position
+                    rij = np.sqrt(np.dot(vrij, vrij))
+                    twopisr = 2 * pi * s * rij
+
+                I += fa * fb * sinc(twopisr)
+                    
+        return pre * I
+
+    def get_waasmaier(self, symbol, s):
+        """Scattering factor for free atoms."""
+        if symbol == 'H':
+            # XXXX implement analytical H
+            return 0
+        elif waasmaier.has_key(symbol):
+            abc = waasmaier[symbol]
+            f = abc[10]
+            s2 = s*s
+            for i in range(5):
+                f += abc[2 * i] * exp(-abc[2 * i + 1] * s2)
+            return f
+        if self.warn:
+            print '<xrdebye::get_atomic> Element', symbol, 'not available'
+        return 0
+        
Index: ase/svnversion_io.py
===================================================================
--- ase/svnversion_io.py	(revision 0)
+++ ase/svnversion_io.py	(revision 1)
@@ -0,0 +1,39 @@
+# Copyright (C) 2003  CAMP
+# Please see the accompanying LICENSE file for further information.
+
+from os import path
+try:
+    from subprocess import Popen, PIPE
+except ImportError:
+    from os import popen3
+else:
+    def popen3(cmd):
+        p = Popen(cmd, shell=True, close_fds=True,
+                  stdin=PIPE, stdout=PIPE, stderr=PIPE)
+        return p.stdin, p.stdout, p.stderr
+
+def write_svnversion(svnversion, dir):
+    svnversionfile = path.join(dir, 'svnversion.py')
+    f = open(svnversionfile,'w')
+    f.write('svnversion = "%s"\n' % svnversion)
+    f.close()
+    print 'svnversion = ' +svnversion+' written to '+svnversionfile
+    # assert svn:ignore property if the installation is under svn control
+    # because svnversion.py has to be ignored by svn!
+    cmd = popen3('svn propset svn:ignore svnversion.py '+dir)[1]
+    output = cmd.read()
+    cmd.close()
+
+def get_svnversion_from_svn(dir):
+    # try to get the last svn version number from svnversion
+    cmd = popen3('svnversion -n '+dir)[1] # assert we are in the project dir
+    output = cmd.read().strip()
+    cmd.close()
+    if output.startswith('exported'):
+        # we build from exported source (e.g. rpmbuild)
+        output = None
+    return output
+
+svnversion = get_svnversion_from_svn(dir='ase')
+if svnversion:
+    write_svnversion(svnversion, dir='ase')
Index: ase/neb.py
===================================================================
--- ase/neb.py	(revision 0)
+++ ase/neb.py	(revision 1)
@@ -0,0 +1,287 @@
+from math import sqrt
+
+import numpy as np
+
+from ase.parallel import world, rank, size
+from ase.calculators.singlepoint import SinglePointCalculator
+from ase.io import read
+
+class NEB:
+    def __init__(self, images, k=0.1, climb=False, parallel=False):
+        self.images = images
+        self.k = k
+        self.climb = climb
+        self.parallel = parallel
+        self.natoms = len(images[0])
+        self.nimages = len(images)
+        self.emax = np.nan
+
+    def interpolate(self):
+        pos1 = self.images[0].get_positions()
+        pos2 = self.images[-1].get_positions()
+        d = (pos2 - pos1) / (self.nimages - 1.0)
+        for i in range(1, self.nimages - 1):
+            self.images[i].set_positions(pos1 + i * d)
+
+    def get_positions(self):
+        positions = np.empty(((self.nimages - 2) * self.natoms, 3))
+        n1 = 0
+        for image in self.images[1:-1]:
+            n2 = n1 + self.natoms
+            positions[n1:n2] = image.get_positions()
+            n1 = n2
+        return positions
+
+    def set_positions(self, positions):
+        n1 = 0
+        for image in self.images[1:-1]:
+            n2 = n1 + self.natoms
+            image.set_positions(positions[n1:n2])
+            n1 = n2
+        
+    def get_forces(self):
+        """Evaluate and return the forces."""
+        images = self.images
+        forces = np.empty(((self.nimages - 2), self.natoms, 3))
+        energies = np.empty(self.nimages - 2)
+
+        if not self.parallel:
+            # Do all images - one at a time:
+            for i in range(1, self.nimages - 1):
+                energies[i - 1] = images[i].get_potential_energy()
+                forces[i - 1] = images[i].get_forces()
+        else:
+            # Parallelize over images:
+            i = rank * (self.nimages - 2) // size + 1
+            try:
+                energies[i - 1] = images[i].get_potential_energy()
+                forces[i - 1] = images[i].get_forces()
+            except:
+                # Make sure other images also fail:
+                error = world.sum(1.0)
+                raise
+            else:
+                error = world.sum(0.0)
+                if error:
+                    raise RuntimeError('Parallel NEB failed!')
+                
+            for i in range(1, self.nimages - 1):
+                root = (i - 1) * size // (self.nimages - 2)
+                world.broadcast(energies[i - 1:i], root)
+                world.broadcast(forces[i - 1], root)
+
+        imax = 1 + np.argsort(energies)[-1]
+        self.emax = energies[imax - 1]
+        
+        tangent1 = images[1].get_positions() - images[0].get_positions()
+        for i in range(1, self.nimages - 1):
+            tangent2 = (images[i + 1].get_positions() -
+                        images[i].get_positions())
+            if i < imax:
+                tangent = tangent2
+            elif i > imax:
+                tangent = tangent1
+            else:
+                tangent = tangent1 + tangent2
+                
+            tt = np.vdot(tangent, tangent)
+            f = forces[i - 1]
+            ft = np.vdot(f, tangent)
+            if i == imax and self.climb:
+                f -= 2 * ft / tt * tangent
+            else:
+                f -= ft / tt * tangent
+                f -= (np.vdot(tangent1 - tangent2, tangent) *
+                      self.k / tt * tangent)
+                
+            tangent1 = tangent2
+
+        return forces.reshape((-1, 3))
+
+    def get_potential_energy(self):
+        return self.emax
+
+    def __len__(self):
+        return (self.nimages - 2) * self.natoms
+
+class SingleCalculatorNEB(NEB):
+    def __init__(self, images, k=0.1, climb=False):
+        if isinstance(images, str):
+            # this is a filename
+            traj = read(images, '0:')
+            images = []
+            for atoms in traj:
+                images.append(atoms)
+
+        NEB.__init__(self, images, k, climb, False)
+        self.calculators = [None] * self.nimages
+        self.energies_ok = False
+ 
+    def interpolate(self, initial=0, final=-1):
+        """Interpolate linearly between initial and final images."""
+        if final < 0:
+            final = self.nimages + final
+        n = final - initial
+        pos1 = self.images[initial].get_positions()
+        pos2 = self.images[final].get_positions()
+        d = (pos2 - pos1) / n
+        for i in range(1, n):
+            self.images[initial + i].set_positions(pos1 + i * d)
+
+    def refine(self, steps=1, begin=0, end=-1):
+        """Refine the NEB trajectory."""
+        if end < 0:
+            end = self.nimages + end
+        j = begin
+        n = end - begin
+        for i in range(n):
+            for k in range(steps):
+                self.images.insert(j + 1, self.images[j].copy())
+                self.calculators.insert(j + 1, None)
+            self.nimages = len(self.images)
+            self.interpolate(j, j + steps + 1)
+            j += steps + 1
+
+    def set_positions(self, positions):
+        # new positions -> new forces
+        if self.energies_ok:
+            # restore calculators
+            self.set_calculators(self.calculators[1:-1])
+        NEB.set_positions(self, positions)
+
+    def get_calculators(self):
+        """Return the original calculators."""
+        calculators = []
+        for i, image in enumerate(self.images):
+            if self.calculators[i] is None:
+                calculators.append(image.get_calculator())
+            else:
+                calculators.append(self.calculators[i])
+        return calculators
+    
+    def set_calculators(self, calculators):
+        """Set new calculators to the images."""
+        self.energies_ok = False
+
+        if not isinstance(calculators, list):
+            calculators = [calculators] * self.nimages
+
+        n = len(calculators)
+        if n == self.nimages:
+            for i in range(self.nimages):
+                self.images[i].set_calculator(calculators[i])   
+        elif n == self.nimages - 2:
+            for i in range(1, self.nimages -1):
+                self.images[i].set_calculator(calculators[i-1])   
+        else:
+            raise RuntimeError(
+                'len(calculators)=%d does not fit to len(images)=%d'
+                % (n, self.nimages))
+
+    def get_energies_and_forces(self, all=False):
+        """Evaluate energies and forces and hide the calculators"""
+        if self.energies_ok:
+            return
+
+        images = self.images
+        forces = np.zeros(((self.nimages - 2), self.natoms, 3))
+        energies = np.zeros(self.nimages - 2)
+        self.emax = -1.e32
+
+        def calculate_and_hide(i):
+            image = self.images[i]
+            calc = image.get_calculator()
+            if self.calculators[i] is None:
+                self.calculators[i] = calc
+            if calc is not None:
+                if not isinstance(calc, SinglePointCalculator):
+                    self.images[i].set_calculator(
+                        SinglePointCalculator(image.get_potential_energy(),
+                                              image.get_forces(),
+                                              None,
+                                              None,
+                                              image))
+                self.emax = min(self.emax, image.get_potential_energy())
+
+        if all and self.calculators[0] is None:
+            calculate_and_hide(0)
+
+        # Do all images - one at a time:
+        for i in range(1, self.nimages - 1):
+            calculate_and_hide(i)
+
+        if all and self.calculators[-1] is None:
+            calculate_and_hide(-1)
+
+        self.energies_ok = True
+       
+    def get_forces(self):
+        self.get_energies_and_forces()
+        return NEB.get_forces(self)
+
+    def n(self):
+        return self.nimages
+
+    def write(self, filename):
+        from ase.io.trajectory import PickleTrajectory
+        traj = PickleTrajectory(filename, 'w', self)
+        traj.write()
+        traj.close()
+
+    def __add__(self, other):
+        for image in other:
+            self.images.append(image)
+        return self
+
+def fit(images):
+    E = [i.get_potential_energy() for i in images]
+    F = [i.get_forces() for i in images]
+    R = [i.get_positions() for i in images]
+    return fit0(E, F, R)
+
+def fit0(E, F, R):
+    E = np.array(E) - E[0]
+    n = len(E)
+    Efit = np.empty((n - 1) * 20 + 1)
+    Sfit = np.empty((n - 1) * 20 + 1)
+
+    s = [0]
+    for i in range(n - 1):
+        s.append(s[-1] + sqrt(((R[i + 1] - R[i])**2).sum()))
+
+    lines = []
+    for i in range(n):
+        if i == 0:
+            d = R[1] - R[0]
+            ds = 0.5 * s[1]
+        elif i == n - 1:
+            d = R[-1] - R[-2]
+            ds = 0.5 * (s[-1] - s[-2])
+        else:
+            d = R[i + 1] - R[i - 1]
+            ds = 0.25 * (s[i + 1] - s[i - 1])
+
+        d = d / sqrt((d**2).sum())
+        dEds = -(F[i] * d).sum()
+        x = np.linspace(s[i] - ds, s[i] + ds, 3)
+        y = E[i] + dEds * (x - s[i])
+        lines.append((x, y))
+
+        if i > 0:
+            s0 = s[i - 1]
+            s1 = s[i]
+            x = np.linspace(s0, s1, 20, endpoint=False)
+            c = np.linalg.solve(np.array([(1, s0,   s0**2,     s0**3),
+                                          (1, s1,   s1**2,     s1**3),
+                                          (0,  1,  2 * s0, 3 * s0**2),
+                                          (0,  1,  2 * s1, 3 * s1**2)]),
+                                np.array([E[i - 1], E[i], dEds0, dEds]))
+            y = c[0] + x * (c[1] + x * (c[2] + x * c[3]))
+            Sfit[(i - 1) * 20:i * 20] = x
+            Efit[(i - 1) * 20:i * 20] = y
+        
+        dEds0 = dEds
+
+    Sfit[-1] = s[-1]
+    Efit[-1] = E[-1]
+    return s, E, Sfit, Efit, lines
Index: ase/transport/__init__.py
===================================================================
--- ase/transport/__init__.py	(revision 0)
+++ ase/transport/__init__.py	(revision 1)
@@ -0,0 +1 @@
+from calculators import TransportCalculator
Index: ase/transport/tools.py
===================================================================
--- ase/transport/tools.py	(revision 0)
+++ ase/transport/tools.py	(revision 1)
@@ -0,0 +1,432 @@
+import numpy as np
+from math import sqrt, exp
+
+def tri2full(H_nn, UL='L'):
+    """Fill in values of hermitian matrix.
+
+    Fill values in lower or upper triangle of H_nn based on the opposite
+    triangle, such that the resulting matrix is symmetric/hermitian.
+
+    UL='U' will copy (conjugated) values from upper triangle into the
+    lower triangle.
+
+    UL='L' will copy (conjugated) values from lower triangle into the
+    upper triangle.
+    """
+    N, tmp = H_nn.shape
+    assert N == tmp, 'Matrix must be square'
+    #assert np.isreal(H_nn.diagonal()).all(), 'Diagonal should be real'
+    if UL != 'L':
+        H_nn = H_nn.T
+
+    for n in range(N - 1):
+        H_nn[n, n + 1:] = H_nn[n + 1:, n].conj()
+
+def dagger(matrix):
+    return np.conj(matrix.T)
+
+def rotate_matrix(h, u):
+    return np.dot(u.T.conj(), np.dot(h, u))
+
+def get_subspace(matrix, index):
+    """Get the subspace spanned by the basis function listed in index"""
+    assert matrix.ndim == 2 and matrix.shape[0] == matrix.shape[1]
+    return matrix.take(index, 0).take(index, 1)   
+
+permute_matrix = get_subspace
+
+def normalize(matrix, S=None):
+    """Normalize column vectors.
+
+    ::
+
+      <matrix[:,i]| S |matrix[:,i]> = 1
+
+    """
+    for col in matrix.T:
+        if S is None:
+            col /= np.linalg.norm(col)
+        else:
+            col /= np.sqrt(np.dot(col.conj(), np.dot(S, col)))
+
+def subdiagonalize(h_ii, s_ii, index_j):
+    nb = h_ii.shape[0]
+    nb_sub = len(index_j)
+    h_sub_jj = get_subspace(h_ii, index_j)
+    s_sub_jj = get_subspace(s_ii, index_j)
+    e_j, v_jj = np.linalg.eig(np.linalg.solve(s_sub_jj, h_sub_jj))
+    normalize(v_jj, s_sub_jj) # normalize: <v_j|s|v_j> = 1
+    permute_list = np.argsort(e_j.real)
+    e_j = np.take(e_j, permute_list)
+    v_jj = np.take(v_jj, permute_list, axis=1)
+    
+    #setup transformation matrix
+    c_ii = np.identity(nb, complex)
+    for i in xrange(nb_sub):
+        for j in xrange(nb_sub):
+            c_ii[index_j[i], index_j[j]] = v_jj[i, j]
+
+    h1_ii = rotate_matrix(h_ii, c_ii)
+    s1_ii = rotate_matrix(s_ii, c_ii)
+
+    return h1_ii, s1_ii, c_ii, e_j
+
+def cutcoupling(h, s, index_n):
+    for i in index_n:
+        s[:, i] = 0.0
+        s[i, :] = 0.0
+        s[i, i] = 1.0
+        Ei = h[i, i]
+        h[:, i] = 0.0
+        h[i, :] = 0.0
+        h[i, i] = Ei
+
+def fermidistribution(energy, kt):
+    #fermi level is fixed to zero
+    return 1.0 / (1.0 + np.exp(energy / kt) )
+
+def fliplr(a):
+    length=len(a)
+    b = [0] * length
+    for i in range(length):
+        b[i] = a[length - i - 1]
+    return b
+
+def plot_path(energy):
+    import pylab
+    pylab.plot(np.real(energy), np.imag(energy), 'b--o')
+    pylab.show()
+    
+
+def function_integral(function, calcutype):
+    #return the integral of the 'function' on 'intrange'    
+    #the function can be a value or a matrix, arg1,arg2 are the possible
+    #parameters of the function
+    
+    intctrl = function.intctrl
+    if calcutype == 'eqInt':
+        intrange = intctrl.eqintpath
+        tol = intctrl.eqinttol
+        if hasattr(function.intctrl, 'eqpath_radius'):
+            radius = function.intctrl.eqpath_radius
+        else:
+            radius = -1
+        if hasattr(function.intctrl, 'eqpath_origin'):
+            origin = function.intctrl.eqpath_origin
+        else:
+            origin = 1000
+    elif calcutype == 'neInt':
+        intrange = intctrl.neintpath
+        tol = intctrl.neinttol
+        radius = -1
+        origin = 1000
+    elif calcutype == 'locInt':
+        intrange = intctrl.locintpath
+        tol = intctrl.locinttol
+        if hasattr(function.intctrl, 'locpath_radius'):
+            radius = function.intctrl.locpath_radius
+        else:
+            radius = -1
+        if hasattr(function.intctrl, 'locpath_origin'):
+            origin = function.intctrl.locpath_origin
+        else:
+            origin = 1000
+    trace = 0    
+    a = 0.
+    b = 1.
+
+    #Initialize with 13 function evaluations.
+    c = (a + b) / 2
+    h = (b - a) / 2
+    realmin = 2e-17
+
+    s = [.942882415695480, sqrt(2.0/3),
+         .641853342345781, 1/sqrt(5.0), .236383199662150]
+    s1 = [0] * len(s)
+    s2 = [0] * len(s)
+    for i in range(len(s)):
+        s1[i] = c - s[i] * h
+        s2[i] = c + fliplr(s)[i] * h
+    x0 = [a] + s1 + [c] + s2 + [b]
+
+    s0 = [.0158271919734802, .094273840218850, .155071987336585,
+          .188821573960182,  .199773405226859, .224926465333340]
+    w0 = s0 + [.242611071901408] + fliplr(s0)
+    w1 = [1, 0, 0, 0, 5, 0, 0, 0, 5, 0, 0, 0, 1]
+    w2 = [77, 0, 432, 0, 625, 0, 672, 0, 625, 0, 432, 0, 77]
+    for i in range(len(w1)):
+        w1[i] = w1[i] / 6.0
+        w2[i] = w2[i] / 1470.0
+                                                        
+    dZ = [intrange[:len(intrange) - 1], intrange[1:]]
+    hmin = [0] * len(dZ[1])
+
+    path_type = []
+    for i in range(len(intrange) - 1):
+        rs = np.abs(dZ[0][i] - origin)
+        re = np.abs(dZ[1][i] - origin)
+        if abs(rs - radius) < 1.0e-8 and abs(re - radius) < 1.0e-8:
+            path_type.append('half_circle')
+        else:
+            path_type.append('line')
+   
+    for i in range(len(dZ[1])):
+        if path_type[i] == 'half_circle':
+            dZ[0][i] = 0
+            dZ[1][i] = np.pi
+    for i in range(len(dZ[1])):
+        dZ[1][i] = dZ[1][i] - dZ[0][i]
+        hmin[i] = realmin / 1024 * abs(dZ[1][i])
+
+
+    temp = np.array([[1] * 13, x0]).transpose()
+    
+    Zx = np.dot(temp, np.array(dZ))
+      
+    Zxx = []
+    for i in range(len(intrange) - 1):
+        for j in range(13):
+            Zxx.append(Zx[j][i])
+
+    ns = 0
+    ne = 12
+    if path_type[0] == 'line':
+        yns = function.calgfunc(Zxx[ns], calcutype)
+    elif path_type[0] == 'half_circle':
+        energy = origin + radius * np.exp((np.pi - Zxx[ns + i]) * 1.j)
+        yns = -1.j * radius * np.exp(-1.j* Zxx[ns +i])* function.calgfunc(energy, calcutype)        
+    fcnt = 0
+    
+
+    for n in range(len(intrange)-1):
+        # below evaluate the integral and adjust the tolerance
+        Q1pQ0 = yns * (w1[0] - w0[0])
+        Q2pQ0 = yns * (w2[0] - w0[0])
+        fcnt = fcnt + 12
+        for i in range(1,12):
+            if path_type[n] == 'line':
+                yne = function.calgfunc(Zxx[ns + i], calcutype)
+            elif path_type[n] == 'half_circle':
+                energy = origin + radius * np.exp((np.pi -Zxx[ns + i]) * 1.j)
+                yne = -1.j * radius * np.exp(-1.j * Zxx[ns + i])* function.calgfunc(energy, calcutype)
+            Q1pQ0 += yne * (w1[i] - w0[i])
+            Q2pQ0 += yne * (w2[i] - w0[i])
+
+        # Increase the tolerance if refinement appears to be effective
+        r = np.abs(Q2pQ0) / (np.abs(Q1pQ0) + np.abs(realmin))
+        dim = np.product(r.shape)
+        r = np.sum(r) / dim
+        if r > 0 and r < 1:
+            thistol = tol / r
+        else:
+            thistol = tol
+        if path_type[n] == 'line':
+            yne = function.calgfunc(Zxx[ne], calcutype)
+        elif path_type[n] == 'half_circle':
+            energy = origin + radius * np.exp((np.pi -Zxx[ne]) * 1.j)
+            yne = -1.j * radius * np.exp(-1.j * Zxx[ne])* function.calgfunc(energy, calcutype)
+        #Call the recursive core integrator
+       
+        Qk, xpk, wpk, fcnt, warn = quadlstep(function, Zxx[ns],
+                                            Zxx[ne], yns, yne,
+                                            thistol, trace, fcnt,
+                                            hmin[n], calcutype, path_type[n],
+                                            origin, radius)
+        if n == 0:
+            Q = np.copy(Qk)
+            Xp = xpk[:]
+            Wp = wpk[:]
+        else:
+            Q += Qk
+            Xp = Xp[:-1] + xpk
+            Wp = Wp[:-1] + [Wp[-1] + wpk[0]] + wpk[1:]
+        if warn == 1:
+            print 'warning: Minimum step size reached,singularity possible'
+        elif warn == 2:
+            print 'warning: Maximum function count excced; singularity likely'
+        elif warn == 3:
+            print 'warning: Infinite or Not-a-Number function value encountered'
+        else:
+            pass
+        
+        ns += 13
+        ne += 13
+        yns = np.copy(yne)
+      
+    return Q,Xp,Wp,fcnt
+
+def quadlstep(f, Za, Zb, fa, fb, tol, trace, fcnt, hmin, calcutype,
+                                                   path_type, origin, radius):
+    #Gaussian-Lobatto and Kronrod method
+    #QUADLSTEP Recursive core routine for integral
+    #input parameters:
+    #      f      ----------   function, here we just use the module calgfunc
+    #                          to return the value, if wanna use it for
+    #                          another one, change it
+    #     Za, Zb  ----------   the start and end point of the integral
+    #     fa, fb  ----------   the function value on Za and Zb
+    #     fcnt    ----------   the number of the funtion recalled till now
+    #output parameters:
+    #      Q      ----------   integral
+    #     Xp      ----------   selected points
+    #     Wp      ----------   weight
+    #    fcnt     ----------   the number of the function recalled till now
+
+    maxfcnt = 10000
+
+    # Evaluate integrand five times in interior of subintrval [a,b]
+    Zh = (Zb - Za) / 2.0
+    if abs(Zh) < hmin:
+        # Minimun step size reached; singularity possible
+        Q = Zh * (fa + fb)
+        if path_type == 'line':
+            Xp = [Za, Zb]
+        elif path_type == 'half_circle':
+            Xp = [origin + radius * np.exp((np.pi - Za) * 1.j),
+                                      origin + radius * np.exp((np.pi - Zb) * 1.j)]
+        Wp = [Zh, Zh]
+        warn = 1
+        return Q, Xp, Wp, fcnt, warn
+    fcnt += 5
+    if fcnt > maxfcnt:
+        #Maximum function count exceed; singularity likely
+        Q = Zh * (fa + fb)
+        if path_type == 'line':
+            Xp = [Za, Zb]
+        elif path_type == 'half_circle':
+            Xp = [origin + radius * np.exp((np.pi - Za) * 1.j),
+                                      origin + radius * np.exp((np.pi - Zb) * 1.j)]
+        Wp = [Zh, Zh]
+        warn = 2
+        return Q, Xp, Wp, fcnt, warn
+    x = [0.18350341907227,   0.55278640450004,   1.0,
+         1.44721359549996,   1.81649658092773];
+    Zx = [0] * len(x)
+    y = [0] * len(x)
+    for i in range(len(x)):
+        x[i] *= 0.5
+        Zx[i] = Za + (Zb - Za) * x[i]
+        if path_type == 'line':
+            y[i] = f.calgfunc(Zx[i], calcutype)
+        elif path_type == 'half_circle':
+            energy = origin + radius * np.exp((np.pi - Zx[i]) * 1.j)
+            y[i] = f.calgfunc(energy, calcutype)
+    #Four point Lobatto quadrature
+    s1 = [1.0, 0.0, 5.0, 0.0, 5.0, 0.0, 1.0]
+    s2 = [77.0, 432.0, 625.0, 672.0, 625.0, 432.0, 77.0]
+    Wk = [0] * 7
+    Wp = [0] * 7
+    for i in range(7):
+        Wk[i] = (Zh / 6.0) * s1[i]
+        Wp[i] = (Zh / 1470.0) * s2[i]
+    if path_type == 'line':
+        Xp = [Za] + Zx + [Zb]
+    elif path_type == 'half_circle':
+        Xp = [Za] + Zx + [Zb] 
+        for i in range(7):
+            factor = -1.j * radius * np.exp(1.j * (np.pi - Xp[i]))
+            Wk[i] *= factor
+            Wp[i] *= factor
+            Xp[i] = origin + radius * np.exp((np.pi - Xp[i]) * 1.j)
+    Qk = fa * Wk[0] + fb * Wk[6]
+    Q = fa * Wp[0] + fb * Wp[6]
+    for i in range(1, 6):
+        Qk += y[i-1] * Wk[i]
+        Q  += y[i-1] * Wp[i]
+    if np.isinf(np.max(np.abs(Q))):
+        Q = Zh * (fa + fb)
+        if path_type == 'line':
+            Xp = [Za, Zb]
+        elif path_type == 'half_circle':
+            Xp = [origin + radius * np.exp((np.pi - Za) * 1.j),
+                                      origin + radius * np.exp((np.pi - Zb) * 1.j)]
+        Wp = [Zh, Zh]
+        warn = 3
+        return Qk, Xp, Wp, fcnt, warn
+    else:
+        pass
+    if trace:
+        print fcnt, real(Za), imag(Za), abs(Zh)
+    #Check accurancy of integral over this subinterval
+    XXk = [Xp[0], Xp[2], Xp[4], Xp[6]]
+    WWk = [Wk[0], Wk[2], Wk[4], Wk[6]]
+    YYk = [fa, y[1], y[3], fb]
+    if np.max(np.abs(Qk - Q)) <= tol:
+        warn = 0
+        return Q, XXk, WWk, fcnt, warn
+    #Subdivide into six subintevals
+    else:
+        Q, Xk, Wk, fcnt, warn = quadlstep(f, Za, Zx[1], fa, YYk[1],
+                                           tol, trace, fcnt, hmin,
+                                               calcutype, path_type,
+                                                origin, radius)
+
+        Qk, xkk, wkk, fcnt, warnk = quadlstep(f, Zx[1],
+                          Zx[3], YYk[1], YYk[2], tol, trace, fcnt, hmin,
+                                             calcutype, path_type,
+                                             origin, radius)
+        Q += Qk
+        Xk = Xk[:-1] + xkk
+        Wk = Wk[:-1] + [Wk[-1] + wkk[0]] + wkk[1:]
+        warn = max(warn, warnk)
+        
+        Qk, xkk, wkk, fcnt, warnk = quadlstep(f, Zx[3], Zb, YYk[2], fb,
+                                           tol, trace, fcnt, hmin,
+                                                   calcutype, path_type,
+                                                   origin, radius)
+        Q += Qk
+        Xk = Xk[:-1] + xkk
+        Wk = Wk[:-1] + [Wk[-1] + wkk[0]] + wkk[1:]
+        warn = max(warn, warnk)
+    return Q, Xk, Wk, fcnt, warn
+
+def mytextread0(filename):
+    num = 0
+    df = file(filename)
+    df.seek(0)
+    for line in df:
+        if num == 0:
+            dim = line.strip().split(' ')
+            row = int(dim[0])
+            col = int(dim[1])
+            mat = np.empty([row, col])
+        else:
+            data = line.strip().split(' ')
+            if len(data) == 0 or len(data)== 1:
+                break
+            else:
+                for i in range(len(data)):
+                    mat[num - 1, i] = float(data[i])
+        num += 1
+    return mat
+
+def mytextread1(filename):
+    num = 0
+    df = file(filename)
+    df.seek(0)
+    data = []
+    for line in df:
+        tmp = line.strip()
+        if len(tmp) != 0: 
+            data.append(float(tmp))
+        else:
+            break
+    dim = int(sqrt(len(data)))
+    mat = np.empty([dim, dim])
+    for i in range(dim):
+        for j in range(dim):
+            mat[i, j] = data[num]
+            num += 1
+    return mat
+
+def mytextwrite1(filename, mat):
+    num = 0
+    df = open(filename,'w')
+    df.seek(0)
+    dim = mat.shape[0]
+    if dim != mat.shape[1]:
+        print 'matwirte, matrix is not square'
+    for i in range(dim):
+        for j in range(dim):
+            df.write('%20.20e\n'% mat[i, j])
+    df.close()
Index: ase/transport/test_transport_calulator.py
===================================================================
--- ase/transport/test_transport_calulator.py	(revision 0)
+++ ase/transport/test_transport_calulator.py	(revision 1)
@@ -0,0 +1,85 @@
+from ase.transport.calculators import TransportCalculator
+import numpy as np
+
+#Aux. function to write data to a text file.
+def write(fname,xs,ys):
+    fd = open(fname,'w')
+    for x,y in zip(xs,ys):
+        print >> fd, x, y
+    fd.close()
+
+H_lead = np.zeros([4,4])
+
+# On-site energies are zero
+for i in range(4):
+    H_lead[i,i] = 0.0
+
+# Nearest neighbor hopping is -1.0
+for i in range(3):
+    H_lead[i,i+1] = -1.0
+    H_lead[i+1,i] = -1.0
+
+# Next-nearest neighbor hopping is 0.2
+for i in range(2):
+    H_lead[i,i+2] = 0.2
+    H_lead[i+2,i] = 0.2
+
+H_scat = np.zeros([6,6])
+# Principal layers on either side of S
+H_scat[:2,:2] = H_lead[:2,:2]
+H_scat[-2:,-2:] = H_lead[:2,:2]
+
+# Scattering region
+H_scat[2,2] = 0.0
+H_scat[3,3] = 0.0
+H_scat[2,3] = -0.8
+H_scat[3,2] = -0.8
+
+# External coupling
+H_scat[1,2] = 0.2
+H_scat[2,1] = 0.2
+H_scat[3,4] = 0.2
+H_scat[4,3] = 0.2
+
+energies = np.arange(-3,3,0.02)
+tcalc = TransportCalculator(h=H_scat,
+                            h1=H_lead,
+                            eta=0.02,
+                            energies=energies)
+
+T = tcalc.get_transmission()
+tcalc.set(pdos=[2, 3])
+pdos = tcalc.get_pdos()
+
+tcalc.set(dos=True)
+dos = tcalc.get_dos()
+
+write('T.dat',tcalc.energies,T)
+write('pdos0.dat', tcalc.energies,pdos[0])
+write('pdos1.dat', tcalc.energies,pdos[1])
+
+#subdiagonalize
+h_rot, s_rot, eps, u = tcalc.subdiagonalize_bfs([2, 3], apply=True)
+T_rot = tcalc.get_transmission()
+dos_rot = tcalc.get_dos()
+pdos_rot = tcalc.get_pdos()
+
+write('T_rot.dat', tcalc.energies,T_rot)
+write('pdos0_rot.dat', tcalc.energies, pdos_rot[0])
+write('pdos1_rot.dat', tcalc.energies, pdos_rot[1])
+
+print 'Subspace eigenvalues:', eps
+assert sum(abs(eps-(-0.8, 0.8))) < 2.0e-15, 'Subdiagonalization. error'
+print 'Max deviation of T after the rotation:', np.abs(T-T_rot).max()
+assert max(abs(T-T_rot)) < 2.0e-15, 'Subdiagonalization. error'
+
+#remove coupling
+h_cut, s_cut = tcalc.cutcoupling_bfs([2], apply=True)
+T_cut = tcalc.get_transmission()
+dos_cut = tcalc.get_dos()
+pdos_cut = tcalc.get_pdos()
+
+write('T_cut.dat', tcalc.energies, T_cut)
+write('pdos0_cut.dat', tcalc.energies,pdos_cut[0])
+write('pdos1_cut.dat', tcalc.energies,pdos_cut[1])
+
Index: ase/transport/stm_test.py
===================================================================
--- ase/transport/stm_test.py	(revision 0)
+++ ase/transport/stm_test.py	(revision 1)
@@ -0,0 +1,94 @@
+import numpy as np
+import pylab
+import ase.transport.stm as stm
+
+
+#           Parameters for a simple model.
+#
+#                           
+#                         * eps_a
+#                 v_ts /   \ v_a2 
+#  ... *  *  *  *            *  *  *  *  * ...
+#       \/                          \/
+#       t1                          t2
+#
+#       Tip                      Surface
+# ----------------|      |-----------------------
+t1 = -1.0
+t2 = -2.0
+eps_a = 0.4
+
+v_ts  = 0.05
+v_a2 = 1.0
+
+#Tip
+h1 = np.zeros([2, 2])
+h1[0, 1] = t1
+h1[1, 0] = t1
+s1 = np.identity(2)
+
+h10 = np.zeros([2,2])
+h10[0, 1] = t1
+h10[1, 0] = t1
+s10 = np.identity(2)
+
+
+#Surface with "molecule" a.
+h2 = np.zeros([2,2])
+h2[0, 1] = v_a2 
+h2[1, 0] = v_a2
+h1[0, 0] = eps_a
+s2 = np.identity(2)
+
+h20 = np.zeros([2,2])
+h20[0, 1] = t2
+h20[1, 0] = t2
+s20 = np.identity(2)
+
+
+#Tip Surface coupling
+V_ts = np.zeros([2,2])
+V_ts[1, 0] = v_ts
+
+eta1 = 0.0001
+eta2 = 0.0001
+
+stm = reload(stm)
+stm_calc = stm.STM(h1, s1, h2, s2, h10, s10, h20, s20, eta1, eta2)
+energies = np.arange(-3.0, 3.0, 0.01)
+stm_calc.initialize(energies)
+
+
+T_stm = stm_calc.get_transmission(V_ts)
+
+
+#Perform the full calculation and compare
+from ase.transport.calculators import TransportCalculator as TC
+
+h = np.zeros([4,4])
+h[:2, :2] = h1
+h[-2:, -2:] = h2
+h[:2, -2:] = V_ts
+h[-2:, :2] = V_ts.T
+
+
+tc = TC(energies=energies,
+        h=h,
+        h1=h10,
+        h2=h20,
+        eta=eta1, eta1=eta1, eta2=eta2)
+        
+T_full = tc.get_transmission()
+
+pylab.plot(stm_calc.energies, T_stm, 'b')
+pylab.plot(tc.energies, T_full, 'r--')
+pylab.show()
+
+
+#bias stuff
+biass = np.arange(-2.0, 2.0, 0.2)
+Is = [stm_calc.get_current(bias, V_ts) for bias in biass]
+pylab.plot(biass, Is, '+')
+pylab.show()
+
+
Index: ase/transport/selfenergy.py
===================================================================
--- ase/transport/selfenergy.py	(revision 0)
+++ ase/transport/selfenergy.py	(revision 1)
@@ -0,0 +1,83 @@
+import numpy as np
+
+
+class LeadSelfEnergy:
+    conv = 1e-8 # Convergence criteria for surface Green function
+    
+    def __init__(self, hs_dii, hs_dij, hs_dim, eta=1e-4):
+        self.h_ii, self.s_ii = hs_dii # onsite principal layer
+        self.h_ij, self.s_ij = hs_dij # coupling between principal layers
+        self.h_im, self.s_im = hs_dim # coupling to the central region
+        self.nbf = self.h_im.shape[1] # nbf for the scattering region
+        self.eta = eta
+        self.energy = None
+        self.bias = 0
+        self.sigma_mm = np.empty((self.nbf, self.nbf), complex)
+    
+    def retarded(self, energy):
+        """Return self-energy (sigma) evaluated at specified energy."""
+        if energy != self.energy:
+            self.energy = energy
+            z = energy - self.bias + self.eta * 1.j           
+            tau_im = z * self.s_im - self.h_im
+            a_im = np.linalg.solve(self.get_sgfinv(energy), tau_im)
+            tau_mi = z * self.s_im.T.conj() - self.h_im.T.conj()
+            self.sigma_mm[:] = np.dot(tau_mi, a_im)
+
+        return self.sigma_mm
+
+    def set_bias(self, bias):
+        self.bias = bias
+
+    def get_lambda(self, energy):
+        """Return the lambda (aka Gamma) defined by i(S-S^d).
+
+        Here S is the retarded selfenergy, and d denotes the hermitian
+        conjugate.
+        """
+        sigma_mm = self.retarded(energy)
+        return 1.j * (sigma_mm - sigma_mm.T.conj())
+        
+    def get_sgfinv(self, energy):
+        """The inverse of the retarded surface Green function""" 
+        z = energy - self.bias + self.eta * 1.j
+        
+        v_00 = z * self.s_ii.T.conj() - self.h_ii.T.conj()
+        v_11 = v_00.copy()
+        v_10 = z * self.s_ij - self.h_ij
+        v_01 = z * self.s_ij.T.conj() - self.h_ij.T.conj()
+
+        delta = self.conv + 1
+        while delta > self.conv:
+            a = np.linalg.solve(v_11, v_01)
+            b = np.linalg.solve(v_11, v_10)
+            v_01_dot_b = np.dot(v_01, b)
+            v_00 -= v_01_dot_b
+            v_11 -= np.dot(v_10, a) 
+            v_11 -= v_01_dot_b
+            v_01 = -np.dot(v_01, a)
+            v_10 = -np.dot(v_10, b)
+            delta = abs(v_01).max()
+
+        return v_00
+
+
+class BoxProbe:
+    """Box shaped Buttinger probe.
+    
+    Kramers-kroning: real = H(imag); imag = -H(real)
+    """
+    def __init__(self, eta, a, b, energies, S, T=0.3):
+        from Transport.Hilbert import hilbert
+        se = np.empty(len(energies), complex)
+        se.imag = .5 * (np.tanh(.5 * (energies - a) / T) -
+                        np.tanh(.5 * (energies - b) / T))
+        se.real = hilbert(se.imag)
+        se.imag -= 1
+        self.selfenergy_e = eta * se
+        self.energies = energies
+        self.S = S
+    
+    def retarded(self, energy):
+        return self.selfenergy_e[self.energies.searchsorted(energy)] * self.S
+        
Index: ase/transport/stm.py
===================================================================
--- ase/transport/stm.py	(revision 0)
+++ ase/transport/stm.py	(revision 1)
@@ -0,0 +1,209 @@
+import numpy as np
+from ase.transport.tools import dagger
+from ase.transport.selfenergy import LeadSelfEnergy
+from ase.transport.greenfunction import GreenFunction
+import time
+from gpaw.mpi import world
+
+
+class STM:
+    def __init__(self, h1, s1, h2, s2 ,h10, s10, h20, s20, eta1, eta2, w=0.5, pdos=[], logfile = None):
+        """XXX
+        
+        1. Tip
+        2. Surface
+        
+        h1: ndarray
+            Hamiltonian and overlap matrix for the isolated tip
+            calculation.  Note, h1 should contain (at least) one
+            principal layer.
+
+        h2: ndarray
+            Same as h1 but for the surface.
+
+        h10: ndarray
+            periodic part of the tip. must include two and only
+            two principal layers.
+
+        h20: ndarray
+            same as h10, but for the surface
+
+        The s* are the corresponding overlap matrices.  eta1, and eta
+        2 are (finite) infinitesimals.  """
+        
+        self.pl1 = len(h10) // 2 #principal layer size for the tip
+        self.pl2 = len(h20) // 2 #principal layer size for the surface
+        self.h1 = h1 
+        self.s1 = s1
+        self.h2 = h2
+        self.s2 = s2
+        self.h10 = h10 
+        self.s10 = s10 
+        self.h20 = h20 
+        self.s20 = s20
+        self.eta1 = eta1
+        self.eta2 = eta2
+        self.w = w #asymmetry of the applied bias (0.5=>symmetric)
+        self.pdos = []
+        self.log = logfile
+
+    def initialize(self, energies, bias=0):
+        """
+            energies: list of energies 
+            for which the transmission function should be evaluated.
+            bias.
+            Will precalculate the surface greenfunctions of the tip and
+            surface.
+        """
+        self.bias = bias
+        self.energies = energies
+        nenergies = len(energies)
+        pl1, pl2 = self.pl1, self.pl2
+        nbf1, nbf2 = len(self.h1), len(self.h2)
+      
+        #periodic part of the tip
+        hs1_dii = self.h10[:pl1, :pl1], self.s10[:pl1, :pl1]
+        hs1_dij = self.h10[:pl1, pl1:2*pl1], self.s10[:pl1, pl1:2*pl1]
+        #coupling betwen per. and non. per part of the tip
+        h1_im = np.zeros((pl1, nbf1), complex) 
+        s1_im = np.zeros((pl1, nbf1), complex)
+        h1_im[:pl1, :pl1], s1_im[:pl1, :pl1] = hs1_dij
+        hs1_dim = [h1_im, s1_im]
+
+        #periodic part the surface 
+        hs2_dii = self.h20[:pl2, :pl2], self.s20[:pl2, :pl2]
+        hs2_dij = self.h20[pl2:2*pl2, :pl2], self.s20[pl2:2*pl2, :pl2]
+        #coupling betwen per. and non. per part of the surface
+        h2_im = np.zeros((pl2, nbf2), complex)
+        s2_im = np.zeros((pl2, nbf2), complex) 
+        h2_im[-pl2:, -pl2:], s2_im[-pl2:, -pl2:] = hs2_dij
+        hs2_dim = [h2_im, s2_im]
+
+        #tip and surface greenfunction 
+        self.selfenergy1 = LeadSelfEnergy(hs1_dii, hs1_dij, hs1_dim, self.eta1)
+        self.selfenergy2 = LeadSelfEnergy(hs2_dii, hs2_dij, hs2_dim, self.eta2)
+        self.greenfunction1 = GreenFunction(self.h1-self.bias*self.w*self.s1, self.s1, 
+                                            [self.selfenergy1], self.eta1)
+        self.greenfunction2 = GreenFunction(self.h2-self.bias*(self.w-1)*self.s2, self.s2, 
+                                            [self.selfenergy2], self.eta2)
+        
+        #Shift the bands due to the bias.
+        bias_shift1 = -bias * self.w
+        bias_shift2 = -bias * (self.w - 1)
+        self.selfenergy1.set_bias(bias_shift1)
+        self.selfenergy2.set_bias(bias_shift2)
+        
+        #tip and surface greenfunction matrices.
+        nbf1_small = nbf1 #XXX Change this for efficiency in the future
+        nbf2_small = nbf2 #XXX -||-
+        coupling_list1 = range(nbf1_small)# XXX -||-
+        coupling_list2 = range(nbf2_small)# XXX -||-
+        self.gft1_emm = np.zeros((nenergies, nbf1_small, nbf1_small), complex) 
+        self.gft2_emm = np.zeros((nenergies, nbf2_small, nbf2_small), complex)
+ 
+        for e, energy in enumerate(self.energies):
+            if self.log != None: # and world.rank == 0:
+                    T = time.localtime()
+                    self.log.write(' %d:%02d:%02d, ' % (T[3], T[4], T[5]) +
+                                   '%d, %d, %02f\n' % (world.rank, e, energy))
+            gft1_mm = self.greenfunction1.retarded(energy)[coupling_list1]
+            gft1_mm = np.take(gft1_mm, coupling_list1, axis=1)
+
+            gft2_mm = self.greenfunction2.retarded(energy)[coupling_list2]
+            gft2_mm = np.take(gft2_mm, coupling_list2, axis=1)
+ 
+            self.gft1_emm[e] = gft1_mm
+            self.gft2_emm[e] = gft2_mm
+
+            if self.log != None and world.rank == 0:
+                self.log.flush()
+
+    def get_transmission(self, v_12, v_11_2=None, v_22_1=None):
+        """XXX
+
+        v_12:
+            coupling between tip and surface 
+        v_11_2:
+            correction to "on-site" tip elements due to the 
+            surface (eq.16). Is only included to first order.
+        v_22_1:
+            corretion to "on-site" surface elements due to he
+            tip (eq.17). Is only included to first order.
+        """
+
+        dim0 = v_12.shape[0]
+        dim1 = v_12.shape[1]
+
+        nenergies = len(self.energies)
+        T_e = np.empty(nenergies,float)
+        v_21 = dagger(v_12)
+        for e, energy in enumerate(self.energies):
+            gft1 = self.gft1_emm[e]
+            if v_11_2!=None:
+                gf1 = np.dot(v_11_2, np.dot(gft1, v_11_2)) 
+                gf1 += gft1 #eq. 16
+            else:
+                gf1 = gft1
+            
+            gft2 = self.gft2_emm[e]
+            if v_22_1!=None:
+                gf2 = np.dot(v_22_1,np.dot(gft2, v_22_1))
+                gf2 += gft2 #eq. 17
+            else:
+                gf2 = gft2
+            
+            a1 = (gf1 - dagger(gf1))
+            a2 = (gf2 - dagger(gf2))
+            self.v_12 = v_12
+            self.a2 = a2
+            self.v_21 = v_21
+            self.a1 = a1
+            v12_a2 = np.dot(v_12, a2[:dim1])
+            v21_a1 = np.dot(v_21, a1[-dim0:])
+            self.v12_a2 = v12_a2
+            self.v21_a1 = v21_a1
+            T = -np.trace(np.dot(v12_a2[:,:dim1], v21_a1[:,-dim0:])) #eq. 11
+            T_e[e] = T
+            self.T_e = T_e
+        return T_e
+
+
+    def get_current(self, bias, v_12, v_11_2=None, v_22_1=None):
+        """Very simple function to calculate the current.
+        
+        Asummes zero temperature.
+
+        bias: type? XXX
+            bias voltage (V)
+            
+        v_12: XXX
+            coupling between tip and surface.
+            
+        v_11_2:
+            correction to onsite elements of the tip
+            due to the potential of the surface.
+        v_22_1:
+            correction to onsite elements of the surface
+            due to the potential of the tip.
+        """
+        energies = self.energies
+        T_e = self.get_transmission(v_12, v_11_2, v_22_1)
+        bias_window = -np.array([bias * self.w, bias * (self.w - 1)])
+        bias_window.sort()
+        self.bias_window = bias_window
+        #print 'bias window', np.around(bias_window,3)
+        #print 'Shift of tip lead do to the bias:', self.selfenergy1.bias
+        #print 'Shift of surface lead do to the bias:', self.selfenergy2.bias
+        i1 = sum(energies < bias_window[0]) 
+        i2 = sum(energies < bias_window[1])
+        step = 1 
+        if i2 < i1:
+            step = -1
+        
+        return np.sign(bias)*np.trapz(x=energies[i1:i2:step], y=T_e[i1:i2:step])
+
+
+
+
+
+
Index: ase/transport/greenfunction.py
===================================================================
--- ase/transport/greenfunction.py	(revision 0)
+++ ase/transport/greenfunction.py	(revision 1)
@@ -0,0 +1,66 @@
+import numpy as np
+
+class GreenFunction:
+    """Equilibrium retarded Green function."""
+    
+    def __init__(self, H, S=None, selfenergies=[], eta=1e-4):
+        self.H = H
+        self.S = S
+        self.selfenergies = selfenergies
+        self.eta = eta
+        self.energy = None
+        self.Ginv = np.empty(H.shape, complex)
+
+    def retarded(self, energy, inverse=False):
+        """Get retarded Green function at specified energy.
+
+        If 'inverse' is True, the inverse Green function is returned (faster).
+        """
+        if energy != self.energy:
+            self.energy = energy
+            z = energy + self.eta * 1.j
+
+            if self.S is None:
+                self.Ginv[:] = 0.0
+                self.Ginv.flat[:: len(self.S) + 1] = z
+            else:
+                self.Ginv[:] = z
+                self.Ginv *= self.S
+            self.Ginv -= self.H
+
+            for selfenergy in self.selfenergies:
+                self.Ginv -= selfenergy.retarded(energy)
+
+        if inverse:
+            return self.Ginv
+        else:
+            return np.linalg.inv(self.Ginv)
+
+    def calculate(self, energy, sigma):
+        """XXX is this really needed"""
+        ginv = energy * self.S - self.H - sigma 
+        return np.linalg.inv(ginv)
+
+    def apply_retarded(self, energy, X):
+        """Apply retarded Green function to X.
+        
+        Returns the matrix product G^r(e) . X
+        """
+        return np.linalg.solve(self.retarded(energy, inverse=True), X)
+
+    def dos(self, energy):
+        """Total density of states -1/pi Im(Tr(GS))"""
+        if self.S is None:
+            return -self(energy).imag.trace() / np.pi
+        else:
+            GS = self.apply_retarded(energy, self.S)
+            return -GS.imag.trace() / np.pi
+        
+    def pdos(self, energy):
+        """Projected density of states -1/pi Im(SGS/S)"""
+        if self.S is None:
+            return -self.retarded(energy).imag.diagonal() / np.pi
+        else:
+            S = self.S
+            SGS = np.dot(S, self.apply_retarded(energy, S))
+            return -(SGS.diagonal() / S.diagonal()).imag / np.pi
Index: ase/transport/calculators.py
===================================================================
--- ase/transport/calculators.py	(revision 0)
+++ ase/transport/calculators.py	(revision 1)
@@ -0,0 +1,414 @@
+import numpy as np
+
+from numpy import linalg
+from ase.transport.selfenergy import LeadSelfEnergy, BoxProbe
+from ase.transport.greenfunction import GreenFunction
+from ase.transport.tools import subdiagonalize, cutcoupling, tri2full, dagger,\
+    rotate_matrix
+
+
+class TransportCalculator:
+    """Determine transport properties of a device sandwiched between
+    two semi-infinite leads using a Green function method.
+    """
+
+    def __init__(self, **kwargs):
+        """Create the transport calculator.
+
+        Parameters
+        ==========
+        h : (N, N) ndarray
+            Hamiltonian matrix for the central region. 
+        s : {None, (N, N) ndarray}, optional
+            Overlap matrix for the central region. 
+            Use None for an orthonormal basis.
+        h1 : (N1, N1) ndarray
+            Hamiltonian matrix for lead1.
+        h2 : {None, (N2, N2) ndarray}, optional
+            Hamiltonian matrix for lead2. You may use None if lead1 and lead2 
+            are identical.
+        s1 : {None, (N1, N1) ndarray}, optional
+            Overlap matrix for lead1. Use None for an orthonomormal basis.
+        hc1 : {None, (N1, N) ndarray}, optional
+            Hamiltonian coupling matrix between the first principal
+            layer in lead1 and the central region.
+        hc2 : {None, (N2, N} ndarray), optional
+            Hamiltonian coupling matrix between the first principal
+            layer in lead2 and the central region.
+        sc1 : {None, (N1, N) ndarray}, optional  
+            Overlap coupling matrix between the first principal
+            layer in lead1 and the central region.
+        sc2 : {None, (N2, N) ndarray}, optional  
+            Overlap coupling matrix between the first principal
+            layer in lead2 and the central region.
+        energies : {None, array_like}, optional
+            Energy points for which calculated transport properties are
+            evaluated.
+        eta : {1.0e-5, float}, optional
+            Infinitesimal for the central region Green function. 
+        eta1/eta2 : {1.0e-5, float}, optional
+            Infinitesimal for lead1/lead2 Green function.
+        align_bf : {None, int}, optional
+            Use align_bf=m to shift the central region 
+            by a constant potential such that the m'th onsite element
+            in the central region is aligned to the m'th onsite element
+            in lead1 principal layer.
+        logfile : {None, str}, optional 
+            Write a logfile to file with name `logfile`.
+            Use '-' to write to std out.
+        eigenchannels: {0, int}, optional
+            Number of eigenchannel transmission coefficients to 
+            calculate. 
+        pdos : {None, (N,) array_like}, optional
+            Specify which basis functions to calculate the
+            projected density of states for.
+        dos : {False, bool}, optional
+            The total density of states of the central region.
+        box: XXX
+            YYY
+            
+        If hc1/hc2 are None, they are assumed to be identical to
+        the coupling matrix elements between neareste neighbor 
+        principal layers in lead1/lead2.
+
+        Examples
+        ========
+        >>> import numpy as np
+        >>> h = np.array((0,)).reshape((1,1))
+        >>> h1 = np.array((0, -1, -1, 0)).reshape(2,2)
+        >>> energies = np.arange(-3, 3, 0.1)
+        >>> calc = TransportCalculator(h=h, h1=h1, energies=energies)
+        >>> T = calc.get_transmission()
+
+        """
+        
+        # The default values for all extra keywords
+        self.input_parameters = {'energies': None,
+                                 'h': None,
+                                 'h1': None,
+                                 'h2': None,
+                                 's': None,
+                                 's1': None,
+                                 's2': None,
+                                 'hc1': None,
+                                 'hc2': None,
+                                 'sc1': None,
+                                 'sc2': None,
+                                 'box': None,
+                                 'align_bf': None,
+                                 'eta1': 1e-5,
+                                 'eta2': 1e-5,
+                                 'eta': 1e-5,
+                                 'logfile': None, # '-',
+                                 'eigenchannels': 0,
+                                 'dos': False,
+                                 'pdos': [],
+                                 }
+        self.initialized = False # Changed Hamiltonians?
+        self.uptodate = False # Changed energy grid?
+        self.set(**kwargs)
+
+    def set(self, **kwargs):
+        for key in kwargs:
+            if key in ['h', 'h1', 'h2', 'hc1', 'hc2',
+                       's', 's1', 's2', 'sc1', 'sc2',
+                       'eta', 'eta1', 'eta2', 'align_bf', 'box']:
+                self.initialized = False
+                self.uptodate = False
+                break
+            elif key in ['energies', 'eigenchannels', 'dos', 'pdos']:
+                self.uptodate = False
+            elif key not in self.input_parameters:
+                raise KeyError, '\'%s\' not a vaild keyword' % key
+
+        self.input_parameters.update(kwargs)
+        log = self.input_parameters['logfile']
+        if log is None:
+            class Trash:
+                def write(self, s):
+                    pass
+                def flush(self):
+                    pass
+            self.log = Trash()
+        elif log == '-':
+            from sys import stdout
+            self.log = stdout
+        elif 'logfile' in kwargs:
+            self.log = open(log, 'w')
+
+    def initialize(self):
+        if self.initialized:
+            return
+
+        print >> self.log, '# Initializing calculator...'
+
+        p = self.input_parameters
+        if p['s'] == None:
+            p['s'] = np.identity(len(p['h']))
+        
+        identical_leads = False
+        if p['h2'] == None:   
+            p['h2'] = p['h1'] # Lead2 is idendical to lead1
+            identical_leads = True
+ 
+        if p['s1'] == None: 
+            p['s1'] = np.identity(len(p['h1']))
+       
+        if p['s2'] == None and not identical_leads:
+            p['s2'] = np.identity(len(p['h2'])) # Orthonormal basis for lead 2
+        else: # Lead2 is idendical to lead1
+            p['s2'] = p['s1']
+
+           
+        h_mm = p['h']
+        s_mm = p['s']
+        pl1 = len(p['h1']) / 2
+        pl2 = len(p['h2']) / 2
+        h1_ii = p['h1'][:pl1, :pl1]
+        h1_ij = p['h1'][:pl1, pl1:2 * pl1]
+        s1_ii = p['s1'][:pl1, :pl1]
+        s1_ij = p['s1'][:pl1, pl1:2 * pl1]
+        h2_ii = p['h2'][:pl2, :pl2]
+        h2_ij = p['h2'][pl2: 2 * pl2, :pl2]
+        s2_ii = p['s2'][:pl2, :pl2]
+        s2_ij = p['s2'][pl2: 2 * pl2, :pl2]
+        
+        if p['hc1'] is None:
+            nbf = len(h_mm)
+            h1_im = np.zeros((pl1, nbf), complex)
+            s1_im = np.zeros((pl1, nbf), complex)
+            h1_im[:pl1, :pl1] = h1_ij
+            s1_im[:pl1, :pl1] = s1_ij
+            p['hc1'] = h1_im
+            p['sc1'] = s1_im
+        else:
+            h1_im = p['hc1']
+            if p['sc1'] is not None:
+                s1_im = p['sc1']
+            else:
+                s1_im = np.zeros(h1_im.shape, complex)
+                p['sc1'] = s1_im
+
+        if p['hc2'] is None:
+            h2_im = np.zeros((pl2, nbf), complex)
+            s2_im = np.zeros((pl2, nbf), complex)
+            h2_im[-pl2:, -pl2:] = h2_ij
+            s2_im[-pl2:, -pl2:] = s2_ij
+            p['hc2'] = h2_im
+            p['sc2'] = s2_im
+        else:
+            h2_im = p['hc2']
+            if p['sc2'] is not None:
+                s2_im = p['sc2']
+            else:
+                s2_im = np.zeros(h2_im.shape, complex)
+                p['sc2'] = s2_im
+
+        align_bf = p['align_bf']
+        if align_bf != None:
+            diff = (h_mm[align_bf, align_bf] - h1_ii[align_bf, align_bf]) \
+                   / s_mm[align_bf, align_bf]
+            print >> self.log, '# Aligning scat. H to left lead H. diff=', diff
+            h_mm -= diff * s_mm
+
+        # Setup lead self-energies
+        # All infinitesimals must be > 0 
+        assert np.all(np.array((p['eta'], p['eta1'], p['eta2'])) > 0.0)
+        self.selfenergies = [LeadSelfEnergy((h1_ii, s1_ii), 
+                                            (h1_ij, s1_ij),
+                                            (h1_im, s1_im),
+                                            p['eta1']),
+                             LeadSelfEnergy((h2_ii, s2_ii), 
+                                            (h2_ij, s2_ij),
+                                            (h2_im, s2_im),
+                                            p['eta2'])]
+        box = p['box']
+        if box is not None:
+            print 'Using box probe!'
+            self.selfenergies.append(
+                BoxProbe(eta=box[0], a=box[1], b=box[2], energies=box[3],
+                         S=s_mm, T=0.3))
+        
+        #setup scattering green function
+        self.greenfunction = GreenFunction(selfenergies=self.selfenergies,
+                                           H=h_mm,
+                                           S=s_mm,
+                                           eta=p['eta'])
+
+        self.initialized = True
+    
+    def update(self):
+        if self.uptodate:
+            return
+        
+        p = self.input_parameters
+        self.energies = p['energies']
+        nepts = len(self.energies)
+        nchan = p['eigenchannels']
+        pdos = p['pdos']
+        self.T_e = np.empty(nepts)
+        if p['dos']:
+            self.dos_e = np.empty(nepts)
+        if pdos != []:
+            self.pdos_ne = np.empty((len(pdos), nepts))
+        if nchan > 0:
+            self.eigenchannels_ne = np.empty((nchan, nepts))
+
+        for e, energy in enumerate(self.energies):
+            Ginv_mm = self.greenfunction.retarded(energy, inverse=True)
+            lambda1_mm = self.selfenergies[0].get_lambda(energy)
+            lambda2_mm = self.selfenergies[1].get_lambda(energy)
+            a_mm = linalg.solve(Ginv_mm, lambda1_mm)
+            b_mm = linalg.solve(dagger(Ginv_mm), lambda2_mm)
+            T_mm = np.dot(a_mm, b_mm)
+            if nchan > 0:
+                t_n = linalg.eigvals(T_mm).real
+                self.eigenchannels_ne[:, e] = np.sort(t_n)[-nchan:]
+                self.T_e[e] = np.sum(t_n)
+            else:
+                self.T_e[e] = np.trace(T_mm).real
+
+            print >> self.log, energy, self.T_e[e]
+            self.log.flush()
+
+            if p['dos']:
+                self.dos_e[e] = self.greenfunction.dos(energy)
+
+            if pdos != []:
+                self.pdos_ne[:, e] = np.take(self.greenfunction.pdos(energy),
+                                             pdos)
+        
+        self.uptodate = True
+
+    def print_pl_convergence(self):
+        self.initialize()
+        pl1 = len(self.input_parameters['h1']) / 2
+        
+        h_ii = self.selfenergies[0].h_ii
+        s_ii = self.selfenergies[0].s_ii
+        ha_ii = self.greenfunction.H[:pl1, :pl1]
+        sa_ii = self.greenfunction.S[:pl1, :pl1]
+        c1 = np.abs(h_ii - ha_ii).max()
+        c2 = np.abs(s_ii - sa_ii).max()
+        print 'Conv (h,s)=%.2e, %2.e' % (c1, c2)
+
+    def plot_pl_convergence(self):
+        self.initialize()
+        pl1 = len(self.input_parameters['h1']) / 2       
+        hlead = self.selfenergies[0].h_ii.real.diagonal()
+        hprincipal = self.greenfunction.H.real.diagonal[:pl1]
+
+        import pylab as pl
+        pl.plot(hlead, label='lead')
+        pl.plot(hprincipal, label='principal layer')
+        pl.axis('tight')
+        pl.show()
+
+    def get_transmission(self):
+        self.initialize()
+        self.update()
+        return self.T_e
+
+    def get_dos(self):
+        self.initialize()
+        self.update()
+        return self.dos_e
+
+    def get_eigenchannels(self, n=None):
+        """Get ``n`` first eigenchannels."""
+        self.initialize()
+        self.update()
+        if n is None:
+            n = self.input_parameters['eigenchannels']
+        return self.eigenchannels_ne[:n]
+
+    def get_pdos(self):
+        self.initialize()
+        self.update()
+        return self.pdos_ne
+
+    def subdiagonalize_bfs(self, bfs, apply=False):
+        self.initialize()
+        bfs = np.array(bfs)
+        p = self.input_parameters
+        h_mm = p['h']
+        s_mm = p['s']
+        ht_mm, st_mm, c_mm, e_m = subdiagonalize(h_mm, s_mm, bfs)
+        if apply:
+            self.uptodate = False
+            h_mm[:] = ht_mm 
+            s_mm[:] = st_mm 
+            # Rotate coupling between lead and central region
+            for alpha, sigma in enumerate(self.selfenergies):
+                sigma.h_im[:] = np.dot(sigma.h_im, c_mm)
+                sigma.s_im[:] = np.dot(sigma.s_im, c_mm)
+        
+        c_mm = np.take(c_mm, bfs, axis=0)
+        c_mm = np.take(c_mm, bfs, axis=1)
+        return ht_mm, st_mm, e_m, c_mm
+
+    def cutcoupling_bfs(self, bfs, apply=False):
+        self.initialize()
+        bfs = np.array(bfs)
+        p = self.input_parameters
+        h_pp = p['h'].copy()
+        s_pp = p['s'].copy()
+        cutcoupling(h_pp, s_pp, bfs)
+        if apply:
+            self.uptodate = False
+            p['h'][:] = h_pp
+            p['s'][:] = s_pp
+            for alpha, sigma in enumerate(self.selfenergies):
+                for m in bfs:
+                    sigma.h_im[:, m] = 0.0
+                    sigma.s_im[:, m] = 0.0
+        return h_pp, s_pp
+
+    def lowdin_rotation(self, apply=False):
+        p = self.input_parameters
+        h_mm = p['h']
+        s_mm = p['s']
+        eig, rot_mm = linalg.eigh(s_mm)
+        eig = np.abs(eig)
+        rot_mm = np.dot(rot_mm / np.sqrt(eig), dagger(rot_mm))
+        if apply:
+            self.uptodate = False
+            h_mm[:] = rotate_matrix(h_mm, rot_mm) # rotate C region
+            s_mm[:] = rotate_matrix(s_mm, rot_mm)
+            for alpha, sigma in enumerate(self.selfenergies):
+                sigma.h_im[:] = np.dot(sigma.h_im, rot_mm) # rotate L-C coupl.
+                sigma.s_im[:] = np.dot(sigma.s_im, rot_mm)
+
+        return rot_mm
+
+    def get_left_channels(self, energy, nchan=1):
+        self.initialize()
+        g_s_ii = self.greenfunction.retarded(energy)
+        lambda_l_ii = self.selfenergies[0].get_lambda(energy)
+        lambda_r_ii = self.selfenergies[1].get_lambda(energy)
+
+        if self.greenfunction.S is None:
+            s_s_qsrt_ii = s_s_isqrt = np.identity(len(g_s_ii))
+        else:
+            s_mm = self.greenfunction.S
+            s_s_i, s_s_ii = linalg.eig(s_mm)
+            s_s_i = np.abs(s_s_i)
+            s_s_sqrt_i = np.sqrt(s_s_i) # sqrt of eigenvalues  
+            s_s_sqrt_ii = np.dot(s_s_ii * s_s_sqrt_i, dagger(s_s_ii))
+            s_s_isqrt_ii = np.dot(s_s_ii / s_s_sqrt_i, dagger(s_s_ii))
+
+        lambdab_r_ii = np.dot(np.dot(s_s_isqrt_ii, lambda_r_ii),s_s_isqrt_ii)
+        a_l_ii = np.dot(np.dot(g_s_ii, lambda_l_ii), dagger(g_s_ii))
+        ab_l_ii = np.dot(np.dot(s_s_sqrt_ii, a_l_ii), s_s_sqrt_ii)
+        lambda_i, u_ii = linalg.eig(ab_l_ii)
+        ut_ii = np.sqrt(lambda_i / (2.0 * np.pi)) * u_ii
+        m_ii = 2 * np.pi * np.dot(np.dot(dagger(ut_ii), lambdab_r_ii),ut_ii)
+        T_i,c_in = linalg.eig(m_ii)
+        T_i = np.abs(T_i)
+        
+        channels = np.argsort(-T_i)[:nchan]
+        c_in = np.take(c_in, channels, axis=1)
+        T_n = np.take(T_i, channels)
+        v_in = np.dot(np.dot(s_s_isqrt_ii, ut_ii), c_in)
+
+        return T_n, v_in
Index: ase/constraints.py
===================================================================
--- ase/constraints.py	(revision 0)
+++ ase/constraints.py	(revision 1)
@@ -0,0 +1,680 @@
+from math import sqrt
+
+import numpy as np
+
+__all__ = ['FixCartesian', 'FixBondLength', 'FixedMode', 'FixConstraintSingle',
+           'FixAtoms', 'UnitCellFilter', 'FixScaled', 'StrainFilter',
+           'FixedPlane', 'Filter', 'FixConstraint', 'FixedLine',
+           'FixBondLengths']
+
+
+def slice2enlist(s):
+    """Convert a slice object into a list of (new, old) tuples."""
+    if isinstance(s, (list, tuple)):
+        return enumerate(s)
+    if s.step == None:
+        step = 1
+    else:
+        step = s.step
+    if s.start == None:
+        start = 0
+    else:
+        start = s.start
+    return enumerate(range(start, s.stop, step))
+
+class FixConstraint:
+    """Base class for classes that fix one or more atoms in some way."""
+
+    def index_shuffle(self, ind):
+        """Change the indices.
+
+        When the ordering of the atoms in the Atoms object changes,
+        this method can be called to shuffle the indices of the
+        constraints.
+
+        ind -- List or tuple of indices.
+
+        """
+        raise NotImplementedError
+
+    def repeat(self, m, n):
+        """ basic method to multiply by m, needs to know the length
+        of the underlying atoms object for the assignment of
+        multiplied constraints to work.
+        """
+        raise NotImplementedError
+
+class FixConstraintSingle(FixConstraint):
+    "Base class for classes that fix a single atom."
+
+    def index_shuffle(self, ind):
+        "The atom index must be stored as self.a."
+        newa = -1 # Signal error
+        for new, old in slice2enlist(ind):
+            if old == self.a:
+                newa = new
+                break
+        if newa == -1:
+            raise IndexError('Constraint not part of slice')
+        self.a = newa
+
+class FixAtoms(FixConstraint):
+    """Constraint object for fixing some chosen atoms."""
+    def __init__(self, indices=None, mask=None):
+        """Constrain chosen atoms.
+
+        Parameters
+        ----------
+        indices : list of int
+           Indices for those atoms that should be constrained.
+        mask : list of bool
+           One boolean per atom indicating if the atom should be
+           constrained or not.
+
+        Examples
+        --------
+        Fix all Copper atoms:
+
+        >>> c = FixAtoms(mask=[s == 'Cu' for s in atoms.get_chemical_symbols()])
+        >>> atoms.set_constraint(c)
+
+        Fix all atoms with z-coordinate less than 1.0 Angstrom:
+
+        >>> c = FixAtoms(mask=atoms.positions[:, 2] < 1.0)
+        >>> atoms.set_constraint(c)
+        """
+
+        if indices is None and mask is None:
+            raise ValueError('Use "indices" or "mask".')
+        if indices is not None and mask is not None:
+            raise ValueError('Use only one of "indices" and "mask".')
+
+        if mask is not None:
+            self.index = np.asarray(mask, bool)
+        else:
+            # Check for duplicates
+            srt = np.sort(indices)
+            for i in range(len(indices)-1):
+                if srt[i] == srt[i+1]:
+                    raise ValueError("FixAtoms: The indices array contained duplicates.  Perhaps you wanted to specify a mask instead, but forgot the mask= keyword.")
+            self.index = np.asarray(indices, int)
+
+        if self.index.ndim != 1:
+            raise ValueError('Wrong argument to FixAtoms class!')
+
+    def adjust_positions(self, old, new):
+        new[self.index] = old[self.index]
+
+    def adjust_forces(self, positions, forces):
+        forces[self.index] = 0.0
+
+    def index_shuffle(self, ind):
+        # See docstring of superclass
+        if self.index.dtype == bool:
+            self.index = self.index[ind]
+        else:
+            index = []
+            for new, old in slice2enlist(ind):
+                if old in self.index:
+                    index.append(new)
+            if len(index) == 0:
+                raise IndexError('All indices in FixAtoms not part of slice')
+            self.index = np.asarray(index, int)
+
+    def copy(self):
+        if self.index.dtype == bool:
+            return FixAtoms(mask=self.index.copy())
+        else:
+            return FixAtoms(indices=self.index.copy())
+
+    def __repr__(self):
+        if self.index.dtype == bool:
+            return 'FixAtoms(mask=%s)' % ints2string(self.index.astype(int))
+        return 'FixAtoms(indices=%s)' % ints2string(self.index)
+
+    def repeat(self, m, n):
+        i0 = 0
+        l = len(self.index)
+        natoms = 0
+        if isinstance(m, int):
+            m = (m, m, m)
+        index_new = []
+        for m2 in range(m[2]):
+            for m1 in range(m[1]):
+                for m0 in range(m[0]):
+                    i1 = i0 + n
+                    if self.index.dtype == bool:
+                        index_new += self.index
+                    else:
+                        index_new += [i+natoms for i in self.index]
+                    i0 = i1
+                    natoms += n
+        if self.index.dtype == bool:
+            self.index = np.asarray(index_new, bool)
+        else:
+            self.index = np.asarray(index_new, int)
+        return self
+
+def ints2string(x, threshold=10):
+    """Convert ndarray of ints to string."""
+    if len(x) <= threshold:
+        return str(x.tolist())
+    return str(x[:threshold].tolist())[:-1] + ', ...]'
+
+class FixBondLengths(FixConstraint):
+    def __init__(self, pairs, iterations=10):
+        self.constraints = [FixBondLength(a1, a2)
+                            for a1, a2 in pairs]
+        self.iterations = iterations
+
+    def adjust_positions(self, old, new):
+        for i in range(self.iterations):
+            for constraint in self.constraints:
+                constraint.adjust_positions(old, new)
+
+    def adjust_forces(self, positions, forces):
+        for i in range(self.iterations):
+            for constraint in self.constraints:
+                constraint.adjust_forces(positions, forces)
+
+    def copy(self):
+        return FixBondLengths([constraint.indices for constraint in self.constraints])
+
+class FixBondLength(FixConstraint):
+    """Constraint object for fixing a bond length."""
+    def __init__(self, a1, a2):
+        """Fix distance between atoms with indices a1 and a2."""
+        self.indices = [a1, a2]
+
+    def adjust_positions(self, old, new):
+        p1, p2 = old[self.indices]
+        d = p2 - p1
+        p = sqrt(np.dot(d, d))
+        q1, q2 = new[self.indices]
+        d = q2 - q1
+        q = sqrt(np.dot(d, d))
+        d *= 0.5 * (p - q) / q
+        new[self.indices] = (q1 - d, q2 + d)
+
+    def adjust_forces(self, positions, forces):
+        d = np.subtract.reduce(positions[self.indices])
+        d2 = np.dot(d, d)
+        d *= 0.5 * np.dot(np.subtract.reduce(forces[self.indices]), d) / d2
+        forces[self.indices] += (-d, d)
+
+    def index_shuffle(self, ind):
+        'Shuffle the indices of the two atoms in this constraint'
+        newa = [-1, -1] # Signal error
+        for new, old in slice2enlist(ind):
+            for i, a in enumerate(self.indices):
+                if old == a:
+                    newa[i] = new
+        if newa[0] == -1 or newa[1] == -1:
+            raise IndexError('Constraint not part of slice')
+        self.indices = newa
+
+    def copy(self):
+        return FixBondLength(*self.indices)
+
+    def __repr__(self):
+        return 'FixBondLength(%d, %d)' % tuple(self.indices)
+
+class FixedMode(FixConstraint):
+    """Constrain atoms to move along directions orthogonal to
+    a given mode only."""
+
+    def __init__(self,indices,mode):
+        if indices is None:
+            raise ValueError('Use "indices".')
+        if indices is not None:
+            self.index = np.asarray(indices, int)
+        self.mode = (np.asarray(mode) / np.sqrt((mode **2).sum())).reshape(-1)
+
+    def adjust_positions(self, oldpositions, newpositions):
+        newpositions = newpositions.ravel()
+        oldpositions = oldpositions.ravel()
+        step = newpositions - oldpositions
+        newpositions -= self.mode * np.dot(step, self.mode)
+        newpositions = newpositions.reshape(-1,3)
+        oldpositions = oldpositions.reshape(-1,3)
+
+    def adjust_forces(self, positions, forces):
+        forces = forces.ravel()
+        forces -= self.mode * np.dot(forces, self.mode)
+        forces = forces.reshape(-1 ,3)
+
+    def copy(self):
+        return FixedMode(self.index.copy(), self.mode)
+
+    def __repr__(self):
+        return 'FixedMode(%d, %s)' % (ints2string(self.index), self.mode.tolist())
+
+class FixedPlane(FixConstraintSingle):
+    """Constrain an atom *a* to move in a given plane only.
+
+    The plane is defined by its normal: *direction*."""
+
+    def __init__(self, a, direction):
+        self.a = a
+        self.dir = np.asarray(direction) / sqrt(np.dot(direction, direction))
+
+    def adjust_positions(self, oldpositions, newpositions):
+        step = newpositions[self.a] - oldpositions[self.a]
+        newpositions[self.a] -= self.dir * np.dot(step, self.dir)
+
+    def adjust_forces(self, positions, forces):
+        forces[self.a] -= self.dir * np.dot(forces[self.a], self.dir)
+
+    def copy(self):
+        return FixedPlane(self.a, self.dir)
+
+    def __repr__(self):
+        return 'FixedPlane(%d, %s)' % (self.a, self.dir.tolist())
+
+
+class FixedLine(FixConstraintSingle):
+    """Constrain an atom *a* to move on a given line only.
+
+    The line is defined by its *direction*."""
+
+    def __init__(self, a, direction):
+        self.a = a
+        self.dir = np.asarray(direction) / sqrt(np.dot(direction, direction))
+
+    def adjust_positions(self, oldpositions, newpositions):
+        step = newpositions[self.a] - oldpositions[self.a]
+        x = np.dot(step, self.dir)
+        newpositions[self.a] = oldpositions[self.a] + x * self.dir
+
+    def adjust_forces(self, positions, forces):
+        forces[self.a] = self.dir * np.dot(forces[self.a], self.dir)
+
+    def copy(self):
+        return FixedLine(self.a, self.dir)
+
+    def __repr__(self):
+        return 'FixedLine(%d, %s)' % (self.a, self.dir.tolist())
+
+class FixCartesian(FixConstraintSingle):
+    "Fix an atom in the directions of the cartesian coordinates."
+    def __init__(self, a, mask=[1,1,1]):
+        self.a=a
+        self.mask = -(np.array(mask)-1)
+
+    def adjust_positions(self, old, new):
+        step = new - old
+        step[self.a] *= self.mask
+        new = old + step
+
+    def adjust_forces(self, positions, forces):
+        forces[self.a] *= self.mask
+
+    def copy(self):
+        return FixCartesian(self.a, self.mask)
+
+    def __repr__(self):
+        return 'FixCartesian(indice=%s mask=%s)' % (self.a, self.mask)
+
+class fix_cartesian(FixCartesian):
+    "Backwards compatibility for FixCartesian."
+    def __init__(self, a, mask=[1,1,1]):
+        import warnings
+        super(fix_cartesian, self).__init__(a, mask)
+        warnings.warn('fix_cartesian is deprecated. Please use FixCartesian' \
+                      ' instead.', DeprecationWarning, stacklevel=2)
+
+class FixScaled(FixConstraintSingle):
+    "Fix an atom in the directions of the unit vectors."
+    def __init__(self, cell, a, mask=[1,1,1]):
+        self.cell = cell
+        self.a = a
+        self.mask = np.array(mask)
+
+    def adjust_positions(self, old, new):
+        scaled_old = np.linalg.solve(self.cell.T, old.T).T
+        scaled_new = np.linalg.solve(self.cell.T, new.T).T
+        for n in range(3):
+            if self.mask[n]:
+                scaled_new[self.a, n] = scaled_old[self.a, n]
+        new[self.a] = np.dot(scaled_new, self.cell)[self.a]
+
+    def adjust_forces(self, positions, forces):
+        scaled_forces = np.linalg.solve(self.cell.T, forces.T).T
+        scaled_forces[self.a] *= -(self.mask-1)
+        forces[self.a] = np.dot(scaled_forces, self.cell)[self.a]
+
+    def copy(self):
+        return FixScaled(self.cell ,self.a, self.mask)
+
+    def __repr__(self):
+        return 'FixScaled(indice=%s mask=%s)' % (self.a, self.mask)
+
+class fix_scaled(FixScaled):
+    "Backwards compatibility for FixScaled."
+    def __init__(self, cell, a, mask=[1,1,1]):
+        import warnings
+        super(fix_scaled, self).__init__(cell, a, mask)
+        warnings.warn('fix_scaled is deprecated. Please use FixScaled ' \
+                      'instead.', DeprecationWarning, stacklevel=2)
+
+class Filter:
+    """Subset filter class."""
+    def __init__(self, atoms, indices=None, mask=None):
+        """Filter atoms.
+
+        This filter can be used to hide degrees of freedom in an Atoms
+        object.
+
+        Parameters
+        ----------
+        indices : list of int
+           Indices for those atoms that should remain visible.
+        mask : list of bool
+           One boolean per atom indicating if the atom should remain
+           visible or not.
+        """
+
+        self.atoms = atoms
+        self.constraints = []
+
+        if indices is None and mask is None:
+            raise ValuError('Use "indices" or "mask".')
+        if indices is not None and mask is not None:
+            raise ValuError('Use only one of "indices" and "mask".')
+
+        if mask is not None:
+            self.index = np.asarray(mask, bool)
+            self.n = self.index.sum()
+        else:
+            self.index = np.asarray(indices, int)
+            self.n = len(self.index)
+
+    def get_cell(self):
+        """Returns the computational cell.
+
+        The computational cell is the same as for the original system.
+        """
+        return self.atoms.get_cell()
+
+    def get_pbc(self):
+        """Returns the periodic boundary conditions.
+
+        The boundary conditions are the same as for the original system.
+        """
+        return self.atoms.get_pbc()
+
+    def get_positions(self):
+        "Return the positions of the visible atoms."
+        return self.atoms.get_positions()[self.index]
+
+    def set_positions(self, positions):
+        "Set the positions of the visible atoms."
+        pos = self.atoms.get_positions()
+        pos[self.index] = positions
+        self.atoms.set_positions(pos)
+
+    def get_momenta(self):
+        "Return the momenta of the visible atoms."
+        return self.atoms.get_momenta()[self.index]
+
+    def set_momenta(self, momenta):
+        "Set the momenta of the visible atoms."
+        mom = self.atoms.get_momenta()
+        mom[self.index] = momenta
+        self.atoms.set_momenta(mom)
+
+    def get_atomic_numbers(self):
+        "Return the atomic numbers of the visible atoms."
+        return self.atoms.get_atomic_numbers()[self.index]
+
+    def set_atomic_numbers(self, atomic_numbers):
+        "Set the atomic numbers of the visible atoms."
+        z = self.atoms.get_atomic_numbers()
+        z[self.index] = atomic_numbers
+        self.atoms.set_atomic_numbers(z)
+
+    def get_tags(self):
+        "Return the tags of the visible atoms."
+        return self.atoms.get_tags()[self.index]
+
+    def set_tags(self, tags):
+        "Set the tags of the visible atoms."
+        tg = self.atoms.get_tags()
+        tg[self.index] = tags
+        self.atoms.set_tags(tg)
+
+    def get_forces(self, *args, **kwargs):
+        return self.atoms.get_forces(*args, **kwargs)[self.index]
+
+    def get_stress(self):
+        return self.atoms.get_stress()
+
+    def get_stresses(self):
+        return self.atoms.get_stresses()[self.index]
+
+    def get_masses(self):
+        return self.atoms.get_masses()[self.index]
+
+    def get_potential_energy(self):
+        """Calculate potential energy.
+
+        Returns the potential energy of the full system.
+        """
+        return self.atoms.get_potential_energy()
+
+    def get_calculator(self):
+        """Returns the calculator.
+
+        WARNING: The calculator is unaware of this filter, and sees a
+        different number of atoms.
+        """
+        return self.atoms.get_calculator()
+
+    def has(self, name):
+        """Check for existance of array."""
+        return self.atoms.has(name)
+
+    def __len__(self):
+        "Return the number of movable atoms."
+        return self.n
+
+    def __getitem__(self, i):
+        "Return an atom."
+        return self.atoms[self.index[i]]
+
+
+class StrainFilter:
+    """Modify the supercell while keeping the scaled positions fixed.
+
+    Presents the strain of the supercell as the generalized positions,
+    and the global stress tensor (times the volume) as the generalized
+    force.
+
+    This filter can be used to relax the unit cell until the stress is
+    zero.  If MDMin is used for this, the timestep (dt) to be used
+    depends on the system size. 0.01/x where x is a typical dimension
+    seems like a good choice.
+
+    The stress and strain are presented as 6-vectors, the order of the
+    components follow the standard engingeering practice: xx, yy, zz,
+    yz, xz, xy.
+
+    """
+    def __init__(self, atoms, mask=None):
+        """Create a filter applying a homogeneous strain to a list of atoms.
+
+        The first argument, atoms, is the atoms object.
+
+        The optional second argument, mask, is a list of six booleans,
+        indicating which of the six independent components of the
+        strain that are allowed to become non-zero.  It defaults to
+        [1,1,1,1,1,1].
+
+        """
+
+        self.atoms = atoms
+        self.strain = np.zeros(6)
+
+        if mask is None:
+            self.mask = np.ones(6)
+        else:
+            self.mask = np.array(mask)
+
+        self.origcell = atoms.get_cell()
+
+    def get_positions(self):
+        return self.strain.reshape((2, 3))
+
+    def set_positions(self, new):
+        new = new.ravel() * self.mask
+        eps = np.array([[1.0 + new[0], 0.5 * new[5], 0.5 * new[4]],
+                        [0.5 * new[5], 1.0 + new[1], 0.5 * new[3]],
+                        [0.5 * new[4], 0.5 * new[3], 1.0 + new[2]]])
+
+        self.atoms.set_cell(np.dot(self.origcell, eps), scale_atoms=True)
+        self.strain[:] = new
+
+    def get_forces(self):
+        stress = self.atoms.get_stress()
+        return -self.atoms.get_volume() * (stress * self.mask).reshape((2, 3))
+
+    def get_potential_energy(self):
+        return self.atoms.get_potential_energy()
+
+    def has(self, x):
+        return self.atoms.has(x)
+
+    def __len__(self):
+        return 2
+
+class UnitCellFilter:
+    """Modify the supercell and the atom positions. """
+    def __init__(self, atoms, mask=None):
+        """Create a filter that returns the atomic forces and unit
+        cell stresses together, so they can simultaneously be
+        minimized.
+
+        The first argument, atoms, is the atoms object.
+
+        The optional second argument, mask, is a list of booleans,
+        indicating which of the six independent
+        components of the strain are relaxed.
+        1, True = relax to zero
+        0, False = fixed, ignore this component
+
+        use atom Constraints, e.g. FixAtoms, to control relaxation of
+        the atoms.
+
+        #this should be equivalent to the StrainFilter
+        >>> atoms = Atoms(...)
+        >>> atoms.set_constraint(FixAtoms(mask=[True for atom in atoms]))
+        >>> ucf = UCFilter(atoms)
+
+        You should not attach this UCFilter object to a
+        trajectory. Instead, create a trajectory for the atoms, and
+        attach it to an optimizer like this:
+
+        >>> atoms = Atoms(...)
+        >>> ucf = UCFilter(atoms)
+        >>> qn = QuasiNewton(ucf)
+        >>> traj = PickleTrajectory('TiO2.traj','w',atoms)
+        >>> qn.attach(traj)
+        >>> qn.run(fmax=0.05)
+
+        Helpful conversion table
+        ========================
+        0.05 eV/A^3   = 8 GPA
+        0.003 eV/A^3  = 0.48 GPa
+        0.0006 eV/A^3 = 0.096 GPa
+        0.0003 eV/A^3 = 0.048 GPa
+        0.0001 eV/A^3 = 0.02 GPa
+        """
+
+        self.atoms = atoms
+        self.strain = np.zeros(6)
+
+        if mask is None:
+            self.mask = np.ones(6)
+        else:
+            self.mask = np.array(mask)
+
+        self.origcell = atoms.get_cell()
+
+    def get_positions(self):
+        '''
+        this returns an array with shape (natoms+2,3).
+
+        the first natoms rows are the positions of the atoms, the last
+        two rows are the strains associated with the unit cell
+        '''
+
+        atom_positions = self.atoms.get_positions()
+        strains = self.strain.reshape((2, 3))
+
+        natoms = len(self.atoms)
+        all_pos = np.zeros((natoms+2,3),np.float)
+        all_pos[0:natoms,:] = atom_positions
+        all_pos[natoms:,:] = strains
+
+        return all_pos
+
+    def set_positions(self, new):
+        '''
+        new is an array with shape (natoms+2,3).
+
+        the first natoms rows are the positions of the atoms, the last
+        two rows are the strains used to change the cell shape.
+
+        The atom positions are set first, then the unit cell is
+        changed keeping the atoms in their scaled positions.
+        '''
+
+        natoms = len(self.atoms)
+
+        atom_positions = new[0:natoms,:]
+        self.atoms.set_positions(atom_positions)
+
+        new = new[natoms:,:] #this is only the strains
+        new = new.ravel() * self.mask
+        eps = np.array([[1.0 + new[0], 0.5 * new[5], 0.5 * new[4]],
+                        [0.5 * new[5], 1.0 + new[1], 0.5 * new[3]],
+                        [0.5 * new[4], 0.5 * new[3], 1.0 + new[2]]])
+
+        self.atoms.set_cell(np.dot(self.origcell, eps), scale_atoms=True)
+        self.strain[:] = new
+
+    def get_forces(self,apply_constraint=False):
+        '''
+        returns an array with shape (natoms+2,3) of the atomic forces
+        and unit cell stresses.
+
+        the first natoms rows are the forces on the atoms, the last
+        two rows are the stresses on the unit cell, which have been
+        reshaped to look like "atomic forces". i.e.,
+
+        f[-2] = -vol*[sxx,syy,szz]*mask[0:3]
+        f[-1] = -vol*[syz, sxz, sxy]*mask[3:]
+
+        apply_constraint is an argument expected by ase
+        '''
+
+        stress = self.atoms.get_stress()
+        atom_forces = self.atoms.get_forces()
+
+        natoms = len(self.atoms)
+        all_forces = np.zeros((natoms+2,3),np.float)
+        all_forces[0:natoms,:] = atom_forces
+
+        vol = self.atoms.get_volume()
+        stress_forces = -vol * (stress * self.mask).reshape((2, 3))
+        all_forces[natoms:,:] = stress_forces
+        return all_forces
+
+    def get_potential_energy(self):
+        return self.atoms.get_potential_energy()
+
+    def has(self, x):
+        return self.atoms.has(x)
+
+    def __len__(self):
+        return (2 + len(self.atoms))
Index: ase/all.py
===================================================================
--- ase/all.py	(revision 0)
+++ ase/all.py	(revision 1)
@@ -0,0 +1,34 @@
+"""This module imports many important modules at once."""
+
+from ase.atom import Atom
+from ase.atoms import Atoms
+from ase.units import *
+from ase.io import read, write
+from ase.io.trajectory import PickleTrajectory
+from ase.dft import STM, monkhorst_pack, DOS
+from ase.optimize.mdmin import MDMin
+from ase.optimize.lbfgs import HessLBFGS
+from ase.optimize.fire import FIRE
+from ase.optimize.lbfgs import LBFGS, LBFGSLineSearch
+from ase.optimize.bfgs import BFGS
+from ase.optimize import QuasiNewton
+from ase.md.verlet import VelocityVerlet
+from ase.md.langevin import Langevin
+from ase.constraints import *
+from ase.calculators.lj import LennardJones
+from ase.calculators.emt import EMT
+from ase.calculators.siesta import Siesta
+from ase.calculators.dacapo import Dacapo
+from ase.calculators.vasp import Vasp
+from ase.calculators.aims import Aims, AimsCube
+from ase.calculators.turbomole import Turbomole
+from ase.calculators.dftb import Dftb
+from ase.neb import NEB, SingleCalculatorNEB
+from ase.visualize import view
+from ase.data import chemical_symbols, atomic_numbers, atomic_names, \
+     atomic_masses, covalent_radii, reference_states
+from ase.data.molecules import molecule
+from ase.structure import *
+
+from math import pi, sqrt
+import numpy as np
Index: ase/utils/memory.py
===================================================================
--- ase/utils/memory.py	(revision 0)
+++ ase/utils/memory.py	(revision 1)
@@ -0,0 +1,447 @@
+import os
+import numpy as np
+
+from UserDict import DictMixin
+
+# -------------------------------------------------------------------
+
+class MemoryBase(object, DictMixin):
+    """Virtual memory (VM) statistics of the current process
+    obtained from the relevant entries in /proc/<pid>/status:
+    VmPeak       Peak virtual memory size in bytes.
+    VmLck        ???
+    VmHWM        Peak resident set size ("high water mark") in bytes.
+    VmRSS        Resident memory usage in bytes.
+    VmSize       VM usage of the entire process in bytes.
+    VmData       VM usage of heap in bytes.
+    VmStk        VM usage of stack in bytes.
+    VmExe        VM usage of exe's and statically linked libraries in bytes.
+    VmLib        VM usage of dynamically linked libraries in bytes.
+    VmPTE        ???
+
+    Note that VmSize > VmData + VmStk + VmExe + VmLib due to overhead.
+    """
+
+    _scale = {'KB':1024.0, 'MB':1024.0**2}
+    _keys = ('VmPeak', 'VmLck', 'VmHWM', 'VmRSS', 'VmSize', 'VmData', \
+            'VmStk', 'VmExe', 'VmLib', 'VmPTE')
+
+    def __init__(self, verbose=0):
+        self.verbose = verbose
+        if self.verbose>=2: print 'MemoryBase.__init__'
+        object.__init__(self)
+        self._values = np.empty(len(self._keys), dtype=np.float)
+
+    def __repr__(self):
+        """Return a representation of recorded VM statistics.
+        x.__repr__() <==> repr(x)"""
+        if self.verbose>=2: print 'MemoryBase.__repr__'
+        s = object.__repr__(self)
+        w = max(map(len, self._keys))
+        unit = 'MB'
+        for k,v in self.iteritems():
+            res = '<N/A>'
+            if not np.isnan(v):
+                res = '%8.3f %s' % (v/self._scale[unit], unit)
+            s += '\n\t' + k.ljust(w) + ': ' + res.rjust(8)
+        return s
+
+    def __len__(self):
+        """Number of VM keys which have not been outdated.
+        x.__len__() <==> len(x)"""
+        if self.verbose>=3: print 'MemoryBase.__len__'
+        return np.sum(~np.isnan(self._values))
+
+    def __getitem__(self, key):
+        """Return floating point number associated with a VM key.
+        x.__getitem__(y) <==> x[y]"""
+        if self.verbose>=2: print 'MemoryBase.__getitem__'
+        if key not in self:
+            raise KeyError(key)
+        i = self.keys().index(key)
+        return self._values[i]
+
+    def __setitem__(self, key, value):
+        """x.__setitem__(i, y) <==> x[i]=y"""
+        if self.verbose>=2: print 'MemoryBase.__setitem__'
+        raise Exception('Virtual member function.')
+
+    def __delitem__(self, key):
+        """x.__delitem__(y) <==> del x[y]"""
+        if self.verbose>=2: print 'MemoryBase.__delitem__'
+        raise Exception('Virtual member function.')
+
+    def clear(self):
+        """D.clear() -> None.  Remove all items from D."""
+        if self.verbose>=1: print 'MemoryBase.clear'
+        raise Exception('Virtual member function.')
+
+    def update(self, other=None):
+        """D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
+        if self.verbose>=1: print 'MemoryBase.update'
+        DictMixin.update(self, other)
+
+    def copy(self):
+        """Return a shallow copy of a VM statistics instance.
+        D.copy() -> a shallow copy of D"""
+        if self.verbose>=1: print 'MemoryBase.copy'
+        res = object.__new__(self.__class__)
+        MemoryBase.__init__(res, self.verbose)
+        DictMixin.update(res, self)
+        return res
+
+    def has_key(self, key): #necessary to avoid infinite recursion
+        """Return boolean to indicate whether key is a supported VM key.
+        D.has_key(k) -> True if D has a key k, else False"""
+        if self.verbose>=3: print 'MemoryBase.has_key'
+        return key in self._keys
+
+    def keys(self):
+        """Return list of supported VM keys.
+        D.keys() -> list of D's keys"""
+        if self.verbose>=3: print 'MemoryBase.keys'
+        return list(self._keys)
+
+    def values(self):
+        """Return list of recorded VM statistics.
+        D.values() -> list of D's values"""
+        if self.verbose>=3: print 'MemoryBase.values'
+        return list(self._values)
+
+    def get(self, key, default=None):
+        """Return floating point number associated with a VM key.
+        D.get(k[,d]) -> D[k] if k in D, else d.  d defaults to None."""
+        if self.verbose>=1: print 'MemoryBase.get'
+        v = self[key]
+
+        if type(default) in [int,float]:
+            default = np.float_(default)
+        if default is not None and not isinstance(default, np.floating):
+            raise ValueError('Default value must be a floating point number.')
+
+        if default is not None and np.isnan(v):
+            return default
+        else:
+            return v
+
+    def setdefault(self, key, default=None):
+        """Return floating point number associated with a VM key.
+        D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D"""
+        if self.verbose>=1: print 'MemoryBase.setdefault'
+        v = self[key]
+
+        if type(default) in [int,float]:
+            default = np.float_(default)
+        if default is not None and not isinstance(default, np.floating):
+            raise ValueError('Default value must be a floating point number.')
+
+        if default is not None and np.isnan(v):
+            self[key] = default
+            return default
+        else:
+            return v
+
+    def pop(self, key, default=None):
+        """Return floating point number for a VM key and mark it as outdated.
+        D.pop(k[,d]) -> v, remove specified key and return the corresponding value
+        If key is not found, d is returned if given, otherwise KeyError is raised"""
+
+        if self.verbose>=1: print 'MemoryBase.pop'
+        v = self[key]
+
+        if type(default) in [int,float]:
+            default = np.float_(default)
+        if default is not None and not isinstance(default, np.floating):
+            raise ValueError('Default value must be a floating point number.')
+
+        if default is not None and np.isnan(v):
+            return default
+        else:
+            del self[key]
+            return v
+
+    def popitem(self):
+        """Return floating point number for some not-yet outdated VM key.
+        D.popitem() -> (k, v), remove and return some (key, value) pair as a
+        2-tuple; but raise KeyError if D is empty"""
+        if self.verbose>=1: print 'MemoryBase.popitem'
+
+        for k,v in self.iteritems():
+            if not np.isnan(v):
+                del self[k]
+                return (k,v)
+        raise KeyError
+
+    def __add__(self, other):
+        """x.__add__(y) <==> x+y"""
+        if self.verbose>=1: print 'MemoryBase.__add__(%s,%s)' \
+            % (object.__repr__(self), object.__repr__(other))
+        res = self.copy()
+        if isinstance(other, MemoryBase):
+            res._values.__iadd__(other._values)
+        elif type(other) in [int,float]:
+            res._values.__iadd__(other)
+        else:
+            raise TypeError('Unsupported operand type')
+        return res
+
+    def __sub__(self, other):
+        """x.__sub__(y) <==> x-y"""
+        if self.verbose>=1: print 'MemoryBase.__sub__(%s,%s)' \
+            % (object.__repr__(self), object.__repr__(other))
+        res = self.copy()
+        if isinstance(other, MemoryBase):
+            res._values.__isub__(other._values)
+        elif type(other) in [int,float]:
+            res._values.__isub__(other)
+        else:
+            raise TypeError('Unsupported operand type')
+        return res
+
+    def __radd__(self, other):
+        """x.__radd__(y) <==> y+x"""
+        if self.verbose>=1: print 'MemoryBase.__radd__(%s,%s)' \
+            % (object.__repr__(self), object.__repr__(other))
+        res = self.copy()
+        if isinstance(other, MemoryBase):
+            res._values.__iadd__(other._values)
+        elif type(other) in [int,float]:
+            res._values.__iadd__(other)
+        else:
+            raise TypeError('Unsupported operand type')
+        return res
+
+    def __rsub__(self, other):
+        """x.__rsub__(y) <==> y-x"""
+        if self.verbose>=1: print 'MemoryBase.__rsub__(%s,%s)' \
+            % (object.__repr__(self), object.__repr__(other))
+        res = self.copy()
+        res._values.__imul__(-1.0)
+        if isinstance(other, MemoryBase):
+            res._values.__iadd__(other._values)
+        elif type(other) in [int,float]:
+            res._values.__iadd__(other)
+        else:
+            raise TypeError('Unsupported operand type')
+        return res
+
+# -------------------------------------------------------------------
+
+class MemoryStatistics(MemoryBase):
+
+    def __init__(self, verbose=0):
+        MemoryBase.__init__(self, verbose)
+        self.update()
+
+    def __setitem__(self, key, value):
+        """Set VM key to a floating point number.
+        x.__setitem__(i, y) <==> x[i]=y"""
+        if self.verbose>=2: print 'MemoryStatistics.__setitem__'
+        if key not in self:
+            raise KeyError(key)
+        if type(value) in [int,float]:
+            value = np.float_(value)
+        if not isinstance(value, np.floating):
+            raise ValueError('Value must be a floating point number.')
+        i = self.keys().index(key)
+        self._values[i] = value
+
+    def __delitem__(self, key):
+        """Mark a VK key as outdated.
+        x.__delitem__(y) <==> del x[y]"""
+        if self.verbose>=2: print 'MemoryStatistics.__delitem__'
+        if key not in self:
+            raise KeyError(key)
+        self[key] = np.nan
+
+    def clear(self):
+        """Mark all supported VM keys as outdated.
+        D.clear() -> None.  Remove all items from D."""
+        if self.verbose>=1: print 'MemoryStatistics.clear'
+        self._values[:] = np.nan
+
+    def refresh(self):
+        """Refresh all outdated VM keys by reading /proc/<pid>/status."""
+        if self.verbose>=1: print 'MemoryBase.refresh'
+
+        # NB: Linux /proc is for humans; Solaris /proc is for programs!
+        # TODO: Use pipe from 'prstat -p <pid>' or 'pmap -x <pid> 1 1'
+
+        # Skip refresh if none are outdated (i.e. nan)
+        if not np.isnan(self._values).any():
+            if self.verbose>=2: print 'refresh: skipping...'
+            return
+
+        try:
+            f = open('/proc/%d/status' % os.getpid(), 'r')
+            for line in f:
+                k, v = line.decode('ascii').split(':')
+
+                # Only refresh supported keys that are outdated (i.e. nan)
+                if k in self and np.isnan(self[k]):
+                    t, s = v.strip().split(None, 1)
+                    if self.verbose >= 2:
+                        print 'refresh: k=%s, t=%s, s=%s' % (k, t, s)
+                    self[k] = float(t) * self._scale[s.upper()]
+
+            f.close()
+        except (IOError, UnicodeError, ValueError):
+            # Reset on error
+            self.clear()
+
+    def update(self, other=None):
+        """Update VM statistics from a supplied dict, else clear and refresh.
+        D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
+        if self.verbose>=1: print 'MemoryStatistics.update'
+
+        # Call to update without arguments has special meaning
+        if other is None:
+            self.clear()
+            self.refresh()
+        else:
+            MemoryBase.update(self, other)
+
+    def __iadd__(self, other):
+        """x.__iadd__(y) <==> x+=y"""
+        if self.verbose>=1: print 'MemoryStatistics.__iadd__(%s,%s)' \
+            % (object.__repr__(self), object.__repr__(other))
+        if isinstance(other, MemoryBase):
+            self._values.__iadd__(other._values)
+        elif type(other) in [int,float]:
+            self._values.__iadd__(other)
+        else:
+            raise TypeError('Unsupported operand type')
+        return self
+
+    def __isub__(self, other):
+        """x.__isub__(y) <==> x-=y"""
+        if self.verbose>=1: print 'MemoryStatistics.__isub__(%s,%s)' \
+            % (object.__repr__(self), object.__repr__(other))
+        if isinstance(other, MemoryBase):
+            self._values.__isub__(other._values)
+        elif type(other) in [int,float]:
+            self._values.__isub__(other)
+        else:
+            raise TypeError('Unsupported operand type')
+        return self
+
+# -------------------------------------------------------------------
+
+#http://www.eecho.info/Echo/python/singleton/
+#http://mail.python.org/pipermail/python-list/2007-July/622333.html
+
+class Singleton(object):
+    """A Pythonic Singleton object."""
+    def __new__(cls, *args, **kwargs):
+        if '_inst' not in vars(cls):
+            cls._inst = object.__new__(cls, *args, **kwargs)
+            #cls._inst = super(type, cls).__new__(cls, *args, **kwargs)
+        return cls._inst
+
+class MemorySingleton(MemoryBase, Singleton):
+    __doc__ = MemoryBase.__doc__ + """
+    The singleton variant is immutable once it has been instantiated, which
+    makes it suitable for recording the initial overhead of starting Python."""
+
+    def __init__(self, verbose=0):
+        if verbose>=1: print 'MemorySingleton.__init__'
+        if '_values' not in vars(self):
+            if verbose>=1: print 'MemorySingleton.__init__ FIRST!'
+            # Hack to circumvent singleton immutability
+            self.__class__ = MemoryStatistics
+            self.__init__(verbose)
+            self.__class__ = MemorySingleton
+
+    def __setitem__(self, key, value):
+        """Disabled for the singleton.
+        x.__setitem__(i, y) <==> x[i]=y"""
+        if self.verbose>=2: print 'MemorySingleton.__setitem__'
+        raise ReferenceError('Singleton is immutable.')
+
+    def __delitem__(self, key):
+        """Disabled for the singleton.
+        x.__delitem__(y) <==> del x[y]"""
+        if self.verbose>=2: print 'MemorySingleton.__delitem__'
+        raise ReferenceError('Singleton is immutable.')
+
+    def clear(self):
+        """Disabled for the singleton.
+        D.clear() -> None.  Remove all items from D."""
+        if self.verbose>=1: print 'MemorySingleton.clear'
+        raise ReferenceError('Singleton is immutable.')
+
+    def update(self):
+        """Disabled for the singleton.
+        D.update(E) -> None.  Update D from E: for k in E.keys(): D[k] = E[k]"""
+        if self.verbose>=1: print 'MemorySingleton.update'
+        raise ReferenceError('Singleton is immutable.')
+
+    def copy(self):
+        """Return a shallow non-singleton copy of a VM statistics instance.
+        D.copy() -> a shallow copy of D"""
+        if self.verbose>=1: print 'MemorySingleton.copy'
+        # Hack to circumvent singleton self-copy
+        self.__class__ = MemoryStatistics
+        res = self.copy()
+        self.__class__ = MemorySingleton
+        return res
+
+# Make sure singleton is instantiated
+MemorySingleton()
+
+# -------------------------------------------------------------------
+
+# Helper functions for leak testing with NumPy arrays
+
+def shapegen(size, ndims, ecc=0.5):
+    """Return a generator of an N-dimensional array shape
+    which approximately contains a given number of elements.
+
+        size:       int or long in [1,inf[
+                    The total number of elements
+        ndims=3:    int in [1,inf[
+                    The number of dimensions
+        ecc=0.5:    float in ]0,1[
+                    The eccentricity of the distribution
+    """
+    assert type(size) in [int,float] and size>=1
+    assert type(ndims) is int and ndims>=1
+    assert type(ecc) in [int,float] and ecc>0 and ecc<1
+
+    for i in range(ndims-1):
+        scale = size**(1.0/(ndims-i))
+        c = round(np.random.uniform((1-ecc)*scale, 1.0/(1-ecc)*scale))
+        size/=c
+        yield c
+    yield round(size)
+
+def shapeopt(maxseed, size, ndims, ecc=0.5):
+    """Return optimal estimate of an N-dimensional array shape
+    which is closest to containing a given number of elements.
+
+        maxseed:    int in [1,inf[
+                    The maximal number of seeds to try
+        size:       int or long in [1,inf[
+                    The total number of elements
+        ndims=3:    int in [1,inf[
+                    The number of dimensions
+        ecc=0.5:    float in ]0,1[
+                    The eccentricity of the distribution
+    """
+    assert type(maxseed) is int and maxseed>=1
+    assert type(size) in [int,float] and size>=1
+    assert type(ndims) is int and ndims>=1
+    assert type(ecc) in [int,float] and ecc>0 and ecc<1
+
+    digits_best = np.inf
+    shape_best = None
+    for seed in range(maxseed):
+        np.random.seed(seed)
+        shape = tuple(shapegen(size, ndims, ecc))
+        if np.prod(shape) == size:
+            return -np.inf, shape
+        digits = np.log10(abs(np.prod(shape)-size))
+        if digits < digits_best:
+            (digits_best, shape_best) = (digits, shape)
+    return digits_best, shape_best
+
Index: ase/utils/adsorb.py
===================================================================
--- ase/utils/adsorb.py	(revision 0)
+++ ase/utils/adsorb.py	(revision 1)
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+
+# Copyright 2010 CAMd
+# (see accompanying license files for details).
+ 
+from optparse import OptionParser
+
+import numpy as np
+
+from ase.lattice.surface import fcc111, hcp0001, bcc110, add_adsorbate
+from ase.structure import estimate_lattice_constant
+from ase.data import reference_states, atomic_numbers, covalent_radii
+from ase.io import write
+from ase.visualize import view
+from ase.atoms import Atoms, string2symbols
+from ase.data.molecules import molecule
+
+def build():
+    p = OptionParser(usage='%prog  [options] [ads@]surf [output file]',
+                     version='%prog 0.1',
+                     description='Example ads/surf: CO@2x2Ru0001')
+    p.add_option('-l', '--layers', type='int',
+                 default=4,
+                 help='Number of layers.')
+    p.add_option('-v', '--vacuum', type='float',
+                 default=5.0,
+                 help='Vacuum.')
+    p.add_option('-x', '--crystal-structure',
+                 help='Crystal structure.',
+                 choices=['sc', 'fcc', 'bcc', 'hcp'])
+    p.add_option('-a', '--lattice-constant', type='float',
+                 help='Lattice constant in Angstrom.')
+    p.add_option('--c-over-a', type='float',
+                 help='c/a ratio.')
+    p.add_option('--height', type='float',
+                 help='Height of adsorbate over surface.')
+    p.add_option('--distance', type='float',
+                 help='Distance between adsorbate and nearest surface atoms.')
+    p.add_option('--site',
+                 help='Adsorption site.',
+                 choices=['fcc', 'hcc', 'hollow', 'bridge'])
+    p.add_option('-M', '--magnetic-moment', type='float', default=0.0,
+                 help='Magnetic moment.')
+    p.add_option('-G', '--gui', action='store_true',
+                 help="Pop up ASE's GUI.")
+
+    opt, args = p.parse_args()
+
+    if not 1 <= len(args) <= 2:
+        p.error("incorrect number of arguments")
+
+    if '@' in args[0]:
+        ads, surf = args[0].split('@')
+    else:
+        ads = None
+        surf = args[0]
+
+    if surf[0].isdigit():
+        i1 = surf.index('x')
+        n = int(surf[:i1])
+        i2 = i1 + 1
+        while surf[i2].isdigit():
+            i2 += 1
+        m = int(surf[i1 + 1:i2])
+        surf = surf[i2:]
+    else:
+        n = 1
+        m = 1
+
+    if surf[-1].isdigit():
+        if surf[1].isdigit():
+            face = surf[1:]
+            surf = surf[0]
+        else:
+            face = surf[2:]
+            surf = surf[:2]
+    else:
+        face = None
+
+    Z = atomic_numbers[surf]
+    state = reference_states[Z]
+
+    if opt.crystal_structure:
+        x = opt.crystal_structure
+    else:
+        x = state['symmetry'].lower()
+    
+    if opt.lattice_constant:
+        a = opt.lattice_constant
+    else:
+        a = estimate_lattice_constant(surf, x, opt.c_over_a)
+
+    if x == 'fcc':
+        if face is None:
+            face = '111'
+        slab = fcc111(surf, (n, m, opt.layers), a, opt.vacuum)
+        r = a / np.sqrt(2) / 2
+    elif x == 'bcc':
+        if face is None:
+            face = '110'
+        slab = bcc110(surf, (n, m, opt.layers), a, opt.vacuum)
+        r = a * np.sqrt(3) / 4
+    elif x == 'hcp':
+        if face is None:
+            face = '0001'
+        slab = hcp0001(surf, (n, m, opt.layers), a, a * opt.c_over_a,
+                       opt.vacuum)
+        r = a / 2
+    else:
+        raise NotImplementedError
+
+    magmom = opt.magnetic_moment
+    if magmom is None:
+        magmom = {'Ni': 0.6, 'Co': 1.2, 'Fe': 2.3}.get(surf, 0.0)
+    slab.set_initial_magnetic_moments([magmom] * len(slab))
+    
+    slab.pbc = 1
+
+    name = '%dx%d%s%s' % (n, m, surf, face) 
+
+    if ads:
+        site = 'ontop'
+        if '-' in ads:
+            site, ads = ads.split('-')
+
+        name = site + '-' + ads + '@' + name
+        symbols = string2symbols(ads)
+        nads = len(symbols) 
+        if nads == 1:
+            ads = Atoms(ads)
+        else:
+            ads = molecule(ads)
+
+        add_adsorbate(slab, ads, 0.0, site)
+
+        d = opt.distance
+        if d is None:
+            d = r + covalent_radii[ads[0].number] / 2
+        
+        h = opt.height
+        if h is None:
+            R = slab.positions
+            y = ((R[:-nads] - R[-nads])**2).sum(1).min()**0.5
+            print y
+            h = (d**2 - y**2)**0.5
+            print h
+        else:
+            assert opt.distance is None
+        
+        slab.positions[-nads:, 2] += h
+
+    if len(args) == 2:
+        write(args[1], slab)
+    elif not opt.gui:
+        write(name + '.traj', slab)
+        
+    if opt.gui:
+        view(slab)
+
+
+if __name__ == '__main__':
+    build()
Index: ase/utils/bee.py
===================================================================
--- ase/utils/bee.py	(revision 0)
+++ ase/utils/bee.py	(revision 1)
@@ -0,0 +1,45 @@
+import numpy as np
+
+# NB! This module was ported from a 4 year old CamposASE2 module.
+
+"""Bayesian Error Estimation
+
+For details, see: "Bayesian Error Estimation in Density Functional
+Theory", J. J. Mortensen, K. Kaasbjerg, S. L. Frederiksen,
+J. K. Norskov, J. P. Sethna, K. W. Jacobsen, Phys. Rev. Lett. 95,
+216401 (2005)."""
+
+#                                 T
+# cost(c) = cost0 + 0.5 * (c - c0) H (c - c0)
+#
+
+# Cost function minimum value:
+cost0 = 3.4660625596
+
+# Best fit parameters:
+c0 = np.array([1.000787451, 0.1926284063, 1.896191546])
+
+# Hessian:
+# H = np.array([[ 1.770035168e+03, -3.732470432e+02, -2.105836167e+02],
+#               [-3.732470432e+02,  1.188857209e+02,  6.054102443e+01],
+#               [-2.105836167e+02,  6.054102443e+01,  3.211200293e+01]])
+#
+# 0.5 * np * T = cost0 (np=3: number of parameters)
+T = cost0 * 2 / 3
+
+def make_ensemble(N=1000, seed=None):
+    np.random.seed(seed) # None means /dev/urandom seed
+    M = np.array([(0.066, -0.812, 1.996),
+                  (0.055, 0.206, 0.082),
+                  (-0.034, 0.007, 0.004)])
+    alpha = np.random.normal(0.0, 1.0, (N, 3))
+    return c0 + np.dot(alpha, M)
+
+c = make_ensemble()
+
+def get_ensemble_energies(atoms, c=c):
+    if hasattr(atoms, 'get_calculator'):
+        coefs = atoms.get_calculator().get_ensemble_coefficients()
+    else:
+        coefs = atoms
+    return coefs[0] + np.dot(c, coefs[1:])
Index: ase/utils/geometry.py
===================================================================
--- ase/utils/geometry.py	(revision 0)
+++ ase/utils/geometry.py	(revision 1)
@@ -0,0 +1,345 @@
+# Copyright (C) 2010, Jesper Friis
+# (see accompanying license files for details).
+
+"""Utility tools for convenient creation of slabs and interfaces of
+different orientations."""
+
+import numpy as np
+
+
+
+def gcd(seq):
+    """Returns greatest common divisor of integers in *seq*."""
+    def _gcd(m, n):
+        while n:
+            m, n = n, m%n
+        return m       
+    return reduce(_gcd, seq)
+
+
+
+
+def get_layers(atoms, miller, tolerance=0.001):
+    """Returns two arrays describing which layer each atom belongs
+    to and the distance between the layers and origo. 
+
+    Parameters:
+
+    miller: 3 integers
+        The Miller indices of the planes. Actually, any direction
+        in reciprocal space works, so if a and b are two float
+        vectors spanning an atomic plane, you can get all layers
+        parallel to this with miller=np.cross(a,b).
+    tolerance: float
+        The maximum distance in Angstrom along the plane normal for
+        counting two atoms as belonging to the same plane.
+
+    Returns:
+
+    tags: array of integres
+        Array of layer indices for each atom.
+    levels: array of floats
+        Array of distances in Angstrom from each layer to origo.
+
+    Example:
+
+    >>> import numpy as np
+    >>> from ase.lattice.spacegroup import crystal
+    >>> atoms = crystal('Al', [(0,0,0)], spacegroup=225, cellpar=4.05)
+    >>> np.round(atoms.positions, decimals=5)
+    array([[ 0.   ,  0.   ,  0.   ],
+           [ 0.   ,  2.025,  2.025],
+           [ 2.025,  0.   ,  2.025],
+           [ 2.025,  2.025,  0.   ]])
+    >>> get_layers(atoms, (0,0,1))
+    (array([0, 1, 1, 0]), array([ 0.   ,  2.025]))
+    """
+    miller = np.asarray(miller)
+
+    metric = np.dot(atoms.cell, atoms.cell.T)
+    c = np.linalg.solve(metric.T, miller.T).T
+    miller_norm = np.sqrt(np.dot(c, miller))
+    d = np.dot(atoms.get_scaled_positions(), miller)/miller_norm
+
+    keys = np.argsort(d)
+    ikeys = np.argsort(keys)
+    mask = np.concatenate(([True], np.diff(d[keys]) > tolerance))
+    tags = np.cumsum(mask)[ikeys]
+    if tags.min() == 1:
+        tags -= 1
+
+    levels = d[keys][mask]
+    return tags, levels
+
+
+
+
+def cut(atoms, a=(1,0,0), b=(0,1,0), c=(0,0,1), origo=(0,0,0), 
+        nlayers=None, extend=1.0, tolerance=0.001):
+    """Cuts out a cell defined by *a*, *b*, *c* and *origo* from a
+    sufficiently repeated copy of *atoms*.
+
+    Typically, this function is used to create slabs of different
+    sizes and orientations. The vectors *a*, *b* and *c* are in scaled
+    coordinates and defines the returned cell and should normally be
+    integer-valued in order to end up with a periodic
+    structure. However, for systems with sub-translations, like fcc,
+    integer multiples of 1/2 or 1/3 might also make sence for some
+    directions (and will be treated correctly).
+
+    Parameters:
+    
+    atoms: Atoms instance
+        This should correspond to a repeatable unit cell.
+    a: int | 3 floats
+        The a-vector in scaled coordinates of the cell to cut out. If
+        integer, the a-vector will be the scaled vector from *origo* to the
+        atom with index *a*.
+    b: int | 3 floats
+        The b-vector in scaled coordinates of the cell to cut out. If
+        integer, the b-vector will be the scaled vector from *origo* to the
+        atom with index *b*.
+    c: int | 3 floats
+        The c-vector in scaled coordinates of the cell to cut out. If
+        integer, the c-vector will be the scaled vector from *origo* to the
+        atom with index *c*. Not used if *nlayers* is given.
+    origo: int | 3 floats
+        Position of origo of the new cell in scaled coordinates. If
+        integer, the position of the atom with index *origo* is used.
+    nlayers: int
+        If *nlayers* is not *None*, the returned cell will have
+        *nlayers* atomic layers in the c-direction. The direction of
+        the c-vector will be along cross(a, b) converted to real
+        space, i.e. normal to the plane spanned by a and b in
+        orthorombic systems.
+    extend: 1 or 3 floats
+        The *extend* argument scales the effective cell in which atoms
+        will be included. It must either be three floats or a single
+        float scaling all 3 directions.  By setting to a value just
+        above one, e.g. 1.05, it is possible to all the corner and
+        edge atoms in the returned cell.  This will of cause make the
+        returned cell non-repeatable, but is very usefull for
+        visualisation.
+    tolerance: float
+        Determines what is defined as a plane.  All atoms within
+        *tolerance* Angstroms from a given plane will be considered to
+        belong to that plane.
+
+    Example:
+
+    >>> import ase
+    >>> from ase.lattice.spacegroup import crystal
+    >>>
+    # Create an aluminium (111) slab with three layers
+    #
+    # First an unit cell of Al
+    >>> a = 4.05
+    >>> aluminium = crystal('Al', [(0,0,0)], spacegroup=225,
+    ...                     cellpar=[a, a, a, 90, 90, 90])
+    >>>
+    # Then cut out the slab
+    >>> al111 = cut(aluminium, (1,-1,0), (0,1,-1), nlayers=3)
+    >>>
+    # Visualisation of the skutterudite unit cell 
+    #
+    # Again, create a skutterudite unit cell
+    >>> a = 9.04
+    >>> skutterudite = crystal(
+    ...     ('Co', 'Sb'), 
+    ...     basis=[(0.25,0.25,0.25), (0.0, 0.335, 0.158)], 
+    ...     spacegroup=204, 
+    ...     cellpar=[a, a, a, 90, 90, 90])
+    >>>
+    # Then use *origo* to put 'Co' at the corners and *extend* to
+    # include all corner and edge atoms.
+    >>> s = cut(skutterudite, origo=(0.25, 0.25, 0.25), extend=1.01)
+    >>> ase.view(s)  # doctest: +SKIP
+    """
+    atoms = atoms.copy()
+    cell = atoms.cell
+
+    if isinstance(origo, int):
+        origo = atoms.get_scaled_positions()[origo]
+    scaled = (atoms.get_scaled_positions() - origo)%1.0
+    scaled %= 1.0 # needed to ensure that all numbers are *less* than one
+    atoms.set_scaled_positions(scaled)
+
+    if isinstance(a, int):
+        a = scaled[a] - origo
+    if isinstance(b, int):
+        b = scaled[b] - origo
+    if isinstance(c, int):
+        c = scaled[c] - origo
+
+    a = np.array(a, dtype=float)
+    b = np.array(b, dtype=float)
+    origo = np.array(origo, dtype=float)
+
+    if nlayers:
+        miller = np.cross(a, b) # surface normal
+        # The factor 36 = 2*2*3*3 is because the elements of a and b
+        # might be multiples of 1/2 or 1/3 because of lattice
+        # subtranslations
+        if np.all(36*miller - np.rint(36*miller)) < 1e-5:
+            miller = np.rint(36*miller)
+            miller /= gcd(miller)
+        tags, layers = get_layers(atoms, miller, tolerance)
+        while tags.max() < nlayers:
+            atoms = atoms.repeat(2)
+            tags, layers = get_layers(atoms, miller, tolerance)
+        # Convert surface normal in reciprocal space to direction in
+        # real space
+        metric = np.dot(cell, cell.T)
+        c = np.linalg.solve(metric.T, miller.T).T
+        c *= layers[nlayers]/np.sqrt(np.dot(c, miller))
+        if np.linalg.det(np.dot(np.array([a, b, c]), cell)) < 0:
+            c *= -1.0
+
+    newcell = np.dot(np.array([a, b, c]), cell)
+    
+    # Create a new atoms object, repeated and translated such that
+    # it completely covers the new cell
+    scorners_newcell = np.array([[0., 0., 0.], [0., 0., 1.], 
+                                 [0., 1., 0.], [0., 1., 1.], 
+                                 [1., 0., 0.], [1., 0., 1.], 
+                                 [1., 1., 0.], [1., 1., 1.]])
+    corners = np.dot(scorners_newcell, newcell*extend)
+    scorners = np.linalg.solve(cell.T, corners.T).T
+    rep = np.ceil(scorners.ptp(axis=0)).astype('int') + 1
+    trans = np.dot(np.floor(scorners.min(axis=0)), cell)
+    atoms = atoms.repeat(rep)
+    atoms.translate(trans)
+    atoms.set_cell(newcell)
+
+    # Mask out atoms outside new cell
+    stol = tolerance  # scaled tolerance, XXX
+    maskcell = atoms.cell*extend
+    sp = np.linalg.solve(maskcell.T, (atoms.positions).T).T
+    mask = np.all(np.logical_and(-stol <= sp, sp < 1-stol), axis=1)
+    atoms = atoms[mask]
+    
+    return atoms
+
+
+
+
+def stack(atoms1, atoms2, axis=2, cell=None, fix=0.5,  
+          maxstrain=0.5, distance=None):
+    """Return a new Atoms instance with *atoms2* added to atoms1
+    along the given axis. Periodicity in all directions is
+    ensured.
+
+    The size of the final cell is determined by *cell*, except
+    that the length alongh *axis* will be the sum of
+    *atoms1.cell[axis]* and *atoms2.cell[axis]*. If *cell* is None,
+    it will be interpolated between *atoms1* and *atoms2*, where
+    *fix* determines their relative weight. Hence, if *fix* equals
+    zero, the final cell will be determined purely from *atoms1* and
+    if *fix* equals one, it will be determined purely from
+    *atoms2*.
+
+    An ValueError exception will be raised if the far corner of
+    the unit cell of either *atoms1* or *atoms2* is displaced more
+    than *maxstrain*. Setting *maxstrain* to None, disable this
+    check.
+
+    If *distance* is provided, the atomic positions in *atoms1* and
+    *atoms2* as well as the cell lengths along *axis* will be
+    adjusted such that the distance between the distance between
+    the closest atoms in *atoms1* and *atoms2* will equal *distance*.
+    This option uses scipy.optimize.fmin() and hence require scipy
+    to be installed.
+
+    Example:
+
+    >>> import ase
+    >>> from ase.lattice.spacegroup import crystal
+    >>>
+    # Create an Ag(110)-Si(110) interface with three atomic layers
+    # on each side. 
+    >>> a_ag = 4.09
+    >>> ag = crystal(['Ag'], basis=[(0,0,0)], spacegroup=225, 
+    ...              cellpar=[a_ag, a_ag, a_ag, 90., 90., 90.])
+    >>> ag110 = cut(ag, (0, 0, 3), (-1.5, 1.5, 0), nlayers=3)
+    >>>
+    >>> a_si = 5.43
+    >>> si = crystal(['Si'], basis=[(0,0,0)], spacegroup=227, 
+    ...              cellpar=[a_si, a_si, a_si, 90., 90., 90.])
+    >>> si110 = cut(si, (0, 0, 2), (-1, 1, 0), nlayers=3)
+    >>>
+    >>> interface = stack(ag110, si110, maxstrain=1)
+    >>> ase.view(interface)  # doctest: +SKIP
+    >>>
+    # Once more, this time adjusted such that the distance between
+    # the closest Ag and Si atoms will be 2.3 Angstrom.
+    >>> interface2 = stack(ag110, si110, 
+    ...                    maxstrain=1, distance=2.3)   # doctest:+ELLIPSIS
+    Optimization terminated successfully.
+        ...
+    >>> ase.view(interface2)  # doctest: +SKIP
+    """
+    atoms1 = atoms1.copy()
+    atoms2 = atoms2.copy()
+
+    c1 = np.linalg.norm(atoms1.cell[axis])
+    c2 = np.linalg.norm(atoms2.cell[axis])
+    if cell is None:
+        cell1 = atoms1.cell.copy()
+        cell2 = atoms2.cell.copy()
+        cell1[axis] /= c1
+        cell2[axis] /= c2
+        cell = cell1 + fix*(cell2 - cell1)
+    cell[axis] /= np.linalg.norm(cell[axis])
+    cell1 = cell.copy()
+    cell2 = cell.copy()
+    cell1[axis] *= c1
+    cell2[axis] *= c2
+
+    if (maxstrain and
+        (((cell1 - atoms1.cell).sum(axis=0)**2).sum() > maxstrain**2 or
+         ((cell2 - atoms2.cell).sum(axis=0)**2).sum() > maxstrain**2)):
+        raise ValueError('Incompatible cells.')
+
+    sp1 = np.linalg.solve(atoms1.cell.T, atoms1.positions.T).T
+    sp2 = np.linalg.solve(atoms2.cell.T, atoms2.positions.T).T
+    atoms1.set_cell(cell1)
+    atoms2.set_cell(cell2)
+    atoms1.set_scaled_positions(sp1)
+    atoms2.set_scaled_positions(sp2)
+
+    if distance is not None:
+        from scipy.optimize import fmin
+        def mindist(pos1, pos2):
+            n1 = len(pos1)
+            n2 = len(pos2)
+            idx1 = np.arange(n1).repeat(n2)
+            idx2 = np.tile(np.arange(n2), n1)
+            return np.sqrt(((pos1[idx1] - pos2[idx2])**2).sum(axis=1).min())
+        def func(x):
+            t1, t2, h1, h2 = x[0:3], x[3:6], x[6], x[7]
+            pos1 = atoms1.positions + t1
+            pos2 = atoms2.positions + t2
+            d1 = mindist(pos1, pos2 + (h1 + 1.0)*atoms1.cell[axis])
+            d2 = mindist(pos2, pos1 + (h2 + 1.0)*atoms2.cell[axis])
+            return (d1 - distance)**2 + (d2 - distance)**2
+        atoms1.center()
+        atoms2.center()
+        x0 = np.zeros((8,))
+        x = fmin(func, x0)
+        t1, t2, h1, h2 = x[0:3], x[3:6], x[6], x[7]
+        atoms1.translate(t1)
+        atoms2.translate(t2)
+        atoms1.cell[axis] *= 1.0 + h1
+        atoms2.cell[axis] *= 1.0 + h2
+
+    atoms2.translate(atoms1.cell[axis])
+    atoms1.cell[axis] += atoms2.cell[axis]
+    atoms1.extend(atoms2)
+    return atoms1
+
+
+#-----------------------------------------------------------------
+# Self test
+if __name__ == '__main__':
+    import doctest
+    print 'doctest: ', doctest.testmod()
Index: ase/utils/deprecate.py
===================================================================
--- ase/utils/deprecate.py	(revision 0)
+++ ase/utils/deprecate.py	(revision 1)
@@ -0,0 +1,44 @@
+import warnings
+
+class Deprecate:
+    def __init__(self, obj, name, newmodule, oldmodule='ase'):
+        self.obj = obj
+        self.name = name
+        self.newmodule = newmodule
+        self.oldmodule = oldmodule
+
+    def __call__(self, *args, **kwargs):
+        message = ('%s.%s is deprecated, use %s.%s instead' %
+                   (self.oldmodule, self.name, self.newmodule, self.name))
+        warnings.warn(message, DeprecationWarning, stacklevel=2)
+        return self.obj(*args, **kwargs)
+
+def _dep(method):
+    def _method(self, *args):
+        message = ('ase.%s is deprecated, use %s.%s instead' %
+                   (self.name, self.newmodule, self.name))
+        warnings.warn(message, DeprecationWarning, stacklevel=2)
+        return method(self, *args)
+    return _method
+
+class DeprecatedFloat(float):
+    def __new__(cls, value, name, newmodule):
+        return float.__new__(cls, value)
+
+    def __init__(self, value, name, newmodule):
+        self.name = name
+        self.newmodule = newmodule
+
+    __mul__ = _dep(float.__mul__)
+    __rmul__ = _dep(float.__rmul__)
+    __div__ = _dep(float.__div__)
+    __rdiv__ = _dep(float.__rdiv__)
+
+class DeprecatedNumpyImport:
+    def __init__(self):
+        import numpy
+        self.numpy = numpy
+
+    def __getattr__(self, key):
+        warnings.warn('ase.np is deprecated; use import numpy as np instead')
+        return getattr(self.numpy, key)
Index: ase/utils/linesearch.py
===================================================================
--- ase/utils/linesearch.py	(revision 0)
+++ ase/utils/linesearch.py	(revision 1)
@@ -0,0 +1,397 @@
+import numpy as np
+import __builtin__
+pymin = __builtin__.min
+pymax = __builtin__.max
+
+class LineSearch:
+    def __init__(self,  xtol=1e-14):
+
+        self.xtol = xtol
+        self.task = 'START'
+        self.isave = np.zeros((2,), np.intc)
+        self.dsave = np.zeros((13,), float)
+        self.fc = 0
+        self.gc = 0
+        self.case = 0
+        self.old_stp = 0
+
+    def _line_search(self, func, myfprime, xk, pk, gfk, old_fval, old_old_fval,
+                     maxstep=.2, c1=.23, c2=0.46, xtrapl=1.1, xtrapu=4.,
+                     stpmax=50., stpmin=1e-8, args=()):
+        self.stpmin = stpmin
+        self.pk = pk
+        p_size = np.sqrt((pk **2).sum())
+        self.stpmax = stpmax
+        self.xtrapl = xtrapl
+        self.xtrapu = xtrapu
+        self.maxstep = maxstep
+        phi0 = old_fval
+        derphi0 = np.dot(gfk,pk)
+        self.dim = len(pk)
+        self.gms = np.sqrt(self.dim) * maxstep
+        #alpha1 = pymin(maxstep,1.01*2*(phi0-old_old_fval)/derphi0)
+        alpha1 = 1.
+        self.no_update = False
+
+        if isinstance(myfprime,type(())):
+            eps = myfprime[1]
+            fprime = myfprime[0]
+            newargs = (f,eps) + args
+            gradient = False
+        else:
+            fprime = myfprime
+            newargs = args
+            gradient = True
+
+        fval = old_fval
+        gval = gfk
+        self.steps=[]
+
+        while 1:
+            stp = self.step(alpha1, phi0, derphi0, c1, c2,
+                                             self.xtol,
+                                             self.isave, self.dsave)
+
+            if self.task[:2] == 'FG':
+                alpha1 = stp
+                fval = func(xk + stp * pk, *args)
+                self.fc += 1
+                gval = fprime(xk + stp * pk, *newargs)
+                if gradient: self.gc += 1
+                else: self.fc += len(xk) + 1
+                phi0 = fval
+                derphi0 = np.dot(gval,pk)
+                self.old_stp = alpha1
+                if self.no_update == True:
+                    break
+            else:
+                break
+
+        if self.task[:5] == 'ERROR' or self.task[1:4] == 'WARN':
+            stp = None  # failed
+        return stp, fval, old_fval, self.no_update
+
+    def step(self, stp, f, g, c1, c2, xtol, isave, dsave):
+        if self.task[:5] == 'START':
+            # Check the input arguments for errors.
+            if stp < self.stpmin:
+                self.task = 'ERROR: STP .LT. minstep'
+            if stp > self.stpmax:
+                self.task = 'ERROR: STP .GT. maxstep'
+            if g >= 0:
+                self.task = 'ERROR: INITIAL G >= 0'
+            if c1 < 0:
+                self.task = 'ERROR: c1 .LT. 0'
+            if c2 < 0:
+                self.task = 'ERROR: c2 .LT. 0'
+            if xtol < 0:
+                self.task = 'ERROR: XTOL .LT. 0'
+            if self.stpmin < 0:
+                self.task = 'ERROR: minstep .LT. 0'
+            if self.stpmax < self.stpmin:
+                self.task = 'ERROR: maxstep .LT. minstep'
+            if self.task[:5] == 'ERROR':
+                return stp
+
+            # Initialize local variables.
+            self.bracket = False
+            stage = 1
+            finit = f
+            ginit = g
+            gtest = c1 * ginit
+            width = self.stpmax - self.stpmin
+            width1 = width / .5
+#           The variables stx, fx, gx contain the values of the step,
+#           function, and derivative at the best step.
+#           The variables sty, fy, gy contain the values of the step,
+#           function, and derivative at sty.
+#           The variables stp, f, g contain the values of the step,
+#           function, and derivative at stp.
+            stx = 0
+            fx = finit
+            gx = ginit
+            sty = 0
+            fy = finit
+            gy = ginit
+            stmin = 0
+            stmax = stp + self.xtrapu * stp
+            self.task = 'FG'
+            self.save((stage, ginit, gtest, gx,
+                       gy, finit, fx, fy, stx, sty,
+                       stmin, stmax, width, width1))
+            stp = self.determine_step(stp)
+            #return stp, f, g
+            return stp
+        else:
+            if self.isave[0] == 1:
+                self.bracket = True
+            else:
+                self.bracket = False
+            stage = self.isave[1]
+            (ginit, gtest, gx, gy, finit, fx, fy, stx, sty, stmin, stmax, \
+            width, width1) =self.dsave
+
+#           If psi(stp) <= 0 and f'(stp) >= 0 for some step, then the
+#           algorithm enters the second stage.
+            ftest = finit + stp * gtest
+            if stage == 1 and f < ftest and g >= 0.:
+                stage = 2
+
+#           Test for warnings.
+            if self.bracket and (stp <= stmin or stp >= stmax):
+                self.task = 'WARNING: ROUNDING ERRORS PREVENT PROGRESS'
+            if self.bracket and stmax - stmin <= self.xtol * stmax:
+                self.task = 'WARNING: XTOL TEST SATISFIED'
+            if stp == self.stpmax and f <= ftest and g <= gtest:
+                self.task = 'WARNING: STP = maxstep'
+            if stp == self.stpmin and (f > ftest or g >= gtest):
+                self.task = 'WARNING: STP = minstep'
+
+#           Test for convergence.
+            if f <= ftest and abs(g) <= c2 * (- ginit):
+                self.task = 'CONVERGENCE'
+
+#           Test for termination.
+            if self.task[:4] == 'WARN' or self.task[:4] == 'CONV':
+                self.save((stage, ginit, gtest, gx,
+                           gy, finit, fx, fy, stx, sty,
+                           stmin, stmax, width, width1))
+                #return stp, f, g
+                return stp
+
+#              A modified function is used to predict the step during the
+#              first stage if a lower function value has been obtained but
+#              the decrease is not sufficient.
+            #if stage == 1 and f <= fx and f > ftest:
+#           #    Define the modified function and derivative values.
+            #    fm =f - stp * gtest
+            #    fxm = fx - stx * gtest
+            #    fym = fy - sty * gtest
+            #    gm = g - gtest
+            #    gxm = gx - gtest
+            #    gym = gy - gtest
+
+#               Call step to update stx, sty, and to compute the new step.
+            #    stx, sty, stp, gxm, fxm, gym, fym = self.update (stx, fxm, gxm, sty,
+            #                                        fym, gym, stp, fm, gm,
+            #                                        stmin, stmax)
+
+#           #    Reset the function and derivative values for f.
+
+            #    fx = fxm + stx * gtest
+            #    fy = fym + sty * gtest
+            #    gx = gxm + gtest
+            #    gy = gym + gtest
+
+            #else:
+#           Call step to update stx, sty, and to compute the new step.
+
+            stx, sty, stp, gx, fx, gy, fy= self.update(stx, fx, gx, sty,
+                                               fy, gy, stp, f, g,
+                                               stmin, stmax)
+
+
+#           Decide if a bisection step is needed.
+
+            if self.bracket:
+                if abs(sty-stx) >= .66 * width1:
+                    stp = stx + .5 * (sty - stx)
+                width1 = width
+                width = abs(sty - stx)
+
+#           Set the minimum and maximum steps allowed for stp.
+
+            if self.bracket:
+                stmin = min(stx, sty)
+                stmax = max(stx, sty)
+            else:
+                stmin = stp + self.xtrapl * (stp - stx)
+                stmax = stp + self.xtrapu * (stp - stx)
+
+#           Force the step to be within the bounds maxstep and minstep.
+
+            stp = max(stp, self.stpmin)
+            stp = min(stp, self.stpmax)
+
+            if (stx == stp and stp == self.stpmax and stmin > self.stpmax):
+                self.no_update = True
+#           If further progress is not possible, let stp be the best
+#           point obtained during the search.
+
+            if (self.bracket and stp < stmin or stp >= stmax) \
+               or (self.bracket and stmax - stmin < self.xtol * stmax):
+                stp = stx
+
+#           Obtain another function and derivative.
+
+            self.task = 'FG'
+            self.save((stage, ginit, gtest, gx,
+                       gy, finit, fx, fy, stx, sty,
+                       stmin, stmax, width, width1))
+            return stp
+
+    def update(self, stx, fx, gx, sty, fy, gy, stp, fp, gp,
+               stpmin, stpmax):
+        sign = gp * (gx / abs(gx))
+
+#       First case: A higher function value. The minimum is bracketed.
+#       If the cubic step is closer to stx than the quadratic step, the
+#       cubic step is taken, otherwise the average of the cubic and
+#       quadratic steps is taken.
+        if fp > fx:  #case1
+            self.case = 1
+            theta = 3. * (fx - fp) / (stp - stx) + gx + gp
+            s = max(abs(theta), abs(gx), abs(gp))
+            gamma = s * np.sqrt((theta / s) ** 2. - (gx / s) * (gp / s))
+            if stp < stx:
+                gamma = -gamma
+            p = (gamma - gx) + theta
+            q = ((gamma - gx) + gamma) + gp
+            r = p / q
+            stpc = stx + r * (stp - stx)
+            stpq = stx + ((gx / ((fx - fp) / (stp-stx) + gx)) / 2.) \
+                   * (stp - stx)
+            if (abs(stpc - stx) < abs(stpq - stx)):
+               stpf = stpc
+            else:
+               stpf = stpc + (stpq - stpc) / 2.
+
+            self.bracket = True
+
+#       Second case: A lower function value and derivatives of opposite
+#       sign. The minimum is bracketed. If the cubic step is farther from
+#       stp than the secant step, the cubic step is taken, otherwise the
+#       secant step is taken.
+
+        elif sign < 0:  #case2
+            self.case = 2
+            theta = 3. * (fx - fp) / (stp - stx) + gx + gp
+            s = max(abs(theta), abs(gx), abs(gp))
+            gamma = s * np.sqrt((theta / s) ** 2 - (gx / s) * (gp / s))
+            if stp > stx:
+                 gamma = -gamma
+            p = (gamma - gp) + theta
+            q = ((gamma - gp) + gamma) + gx
+            r = p / q
+            stpc = stp + r * (stx - stp)
+            stpq = stp + (gp / (gp - gx)) * (stx - stp)
+            if (abs(stpc - stp) > abs(stpq - stp)):
+               stpf = stpc
+            else:
+               stpf = stpq
+            self.bracket = True
+
+#       Third case: A lower function value, derivatives of the same sign,
+#       and the magnitude of the derivative decreases.
+
+        elif abs(gp) < abs(gx):  #case3
+            self.case = 3
+#           The cubic step is computed only if the cubic tends to infinity
+#           in the direction of the step or if the minimum of the cubic
+#           is beyond stp. Otherwise the cubic step is defined to be the
+#           secant step.
+
+            theta = 3. * (fx - fp) / (stp - stx) + gx + gp
+            s = max(abs(theta), abs(gx), abs(gp))
+
+#           The case gamma = 0 only arises if the cubic does not tend
+#           to infinity in the direction of the step.
+
+            gamma = s * np.sqrt(max(0.,(theta / s) ** 2-(gx / s) * (gp / s)))
+            if stp > stx:
+                gamma = -gamma
+            p = (gamma - gp) + theta
+            q = (gamma + (gx - gp)) + gamma
+            r = p / q
+            if r < 0. and gamma != 0:
+               stpc = stp + r * (stx - stp)
+            elif stp > stx:
+               stpc = stpmax
+            else:
+               stpc = stpmin
+            stpq = stp + (gp / (gp - gx)) * (stx - stp)
+
+            if self.bracket:
+
+#               A minimizer has been bracketed. If the cubic step is
+#               closer to stp than the secant step, the cubic step is
+#               taken, otherwise the secant step is taken.
+
+                if abs(stpc - stp) < abs(stpq - stp):
+                    stpf = stpc
+                else:
+                    stpf = stpq
+                if stp > stx:
+                    stpf = min(stp + .66 * (sty - stp), stpf)
+                else:
+                    stpf = max(stp + .66 * (sty - stp), stpf)
+            else:
+
+#               A minimizer has not been bracketed. If the cubic step is
+#               farther from stp than the secant step, the cubic step is
+#               taken, otherwise the secant step is taken.
+
+                if abs(stpc - stp) > abs(stpq - stp):
+                   stpf = stpc
+                else:
+                   stpf = stpq
+                stpf = min(stpmax, stpf)
+                stpf = max(stpmin, stpf)
+
+#       Fourth case: A lower function value, derivatives of the same sign,
+#       and the magnitude of the derivative does not decrease. If the
+#       minimum is not bracketed, the step is either minstep or maxstep,
+#       otherwise the cubic step is taken.
+
+        else:  #case4
+            self.case = 4
+            if self.bracket:
+                theta = 3. * (fp - fy) / (sty - stp) + gy + gp
+                s = max(abs(theta), abs(gy), abs(gp))
+                gamma = s * np.sqrt((theta / s) ** 2 - (gy / s) * (gp / s))
+                if stp > sty:
+                    gamma = -gamma
+                p = (gamma - gp) + theta
+                q = ((gamma - gp) + gamma) + gy
+                r = p / q
+                stpc = stp + r * (sty - stp)
+                stpf = stpc
+            elif stp > stx:
+                stpf = stpmax
+            else:
+                stpf = stpmin
+
+#       Update the interval which contains a minimizer.
+
+        if fp > fx:
+            sty = stp
+            fy = fp
+            gy = gp
+        else:
+            if sign < 0:
+                sty = stx
+                fy = fx
+                gy = gx
+            stx = stp
+            fx = fp
+            gx = gp
+#       Compute the new step.
+
+        stp = self.determine_step(stpf)
+
+        return stx, sty, stp, gx, fx, gy, fy
+
+    def determine_step(self, stp):
+        dr = stp - self.old_stp
+        if abs(pymax(self.pk) * dr) > self.maxstep:
+            dr /= abs((pymax(self.pk) * dr) / self.maxstep)
+        stp = self.old_stp + dr
+        return stp
+
+    def save(self, data):
+        if self.bracket:
+            self.isave[0] = 1
+        else:
+            self.isave[0] = 0
+        self.isave[1] = data[0]
+        self.dsave = data[1:]
Index: ase/utils/__init__.py
===================================================================
--- ase/utils/__init__.py	(revision 0)
+++ ase/utils/__init__.py	(revision 1)
@@ -0,0 +1,131 @@
+from math import sin, cos, radians, atan2, degrees
+
+import numpy as np
+
+
+class DevNull:
+    def write(self, string): pass
+    def flush(self): pass
+    def tell(self): return 0
+    def close(self): pass
+
+devnull = DevNull()
+
+
+def rotate(rotations, rotation=np.identity(3)):
+    """Convert string of format '50x,-10y,120z' to a rotation matrix.
+
+    Note that the order of rotation matters, i.e. '50x,40z' is different
+    from '40z,50x'.
+    """
+        
+    if rotations == '':
+        return rotation.copy()
+    
+    for i, a in [('xyz'.index(s[-1]), radians(float(s[:-1])))
+                 for s in rotations.split(',')]:
+        s = sin(a)
+        c = cos(a)
+        if i == 0:
+            rotation = np.dot(rotation, [( 1,  0,  0),
+                                          ( 0,  c, s),
+                                          ( 0,  -s,  c)])
+        elif i == 1:
+            rotation = np.dot(rotation, [( c,  0, -s),
+                                          ( 0,  1,  0),
+                                          ( s,  0,  c)])
+        else:
+            rotation = np.dot(rotation, [( c, s,  0),
+                                          ( -s,  c,  0),
+                                          ( 0,  0,  1)])
+    return rotation
+
+
+def givens(a, b):
+    """Solve the equation system::
+
+      [ c s]   [a]   [r]
+      [    ] . [ ] = [ ]
+      [-s c]   [b]   [0]
+    """
+    sgn = lambda x: cmp(x, 0)
+    if b == 0:
+        c = sgn(a)
+        s = 0
+        r = abs(a)
+    elif abs(b) >= abs(a):
+        cot = a / b
+        u = sgn(b) * (1 + cot**2)**.5
+        s = 1. / u
+        c = s * cot
+        r = b * u
+    else:
+        tan = b / a
+        u = sgn(a) * (1 + tan**2)**.5
+        c = 1. / u
+        s = c * tan
+        r = a * u
+    return c, s, r
+
+
+def irotate(rotation, initial=np.identity(3)):
+    """Determine x, y, z rotation angles from rotation matrix."""
+    a = np.dot(initial, rotation)
+    cx, sx, rx = givens(a[2, 2], a[1, 2])
+    cy, sy, ry = givens(rx, a[0, 2])
+    cz, sz, rz = givens(cx * a[1, 1] - sx * a[2, 1],
+                        cy * a[0, 1] - sy * (sx * a[1, 1] + cx * a[2, 1]))
+    x = degrees(atan2(sx, cx))
+    y = degrees(atan2(-sy, cy))
+    z = degrees(atan2(sz, cz))
+    return x, y, z
+
+
+def hsv2rgb(h, s, v):
+    """http://en.wikipedia.org/wiki/HSL_and_HSV
+
+    h (hue) in [0, 360[
+    s (saturation) in [0, 1]
+    v (value) in [0, 1]
+
+    return rgb in range [0, 1]
+    """
+    if v == 0:
+        return 0, 0, 0
+    if s == 0:
+        return v, v, v
+
+    i, f = divmod(h / 60., 1)
+    p = v * (1 - s)
+    q = v * (1 - s * f)
+    t = v * (1 - s * (1 - f))
+
+    if i == 0:
+        return v, t, p
+    elif i == 1:
+        return q, v, p
+    elif i == 2:
+        return p, v, t
+    elif i == 3:
+        return p, q, v
+    elif i == 4:
+        return t, p, v
+    elif i == 5:
+        return v, p, q
+    else:
+        raise RuntimeError, 'h must be in [0, 360]'
+
+
+def hsv(array, s=.9, v=.9):
+    array = (array + array.min()) * 359. / (array.max() - array.min())
+    result = np.empty((len(array.flat), 3))
+    for rgb, h in zip(result, array.flat):
+        rgb[:] = hsv2rgb(h, s, v)
+    return np.reshape(result, array.shape + (3,))
+
+## This code does the same, but requires pylab
+## def cmap(array, name='hsv'):
+##     import pylab
+##     a = (array + array.min()) / array.ptp()
+##     rgba = getattr(pylab.cm, name)(a)
+##     return rgba[:-1] # return rgb only (not alpha) 
Index: ase/utils/molecule_test.py
===================================================================
--- ase/utils/molecule_test.py	(revision 0)
+++ ase/utils/molecule_test.py	(revision 1)
@@ -0,0 +1,279 @@
+#!/usr/bin/env python
+
+"""This module defines extensible classes for running tests on molecules.
+
+Use this to compare different calculators, XC functionals and so on by
+calculating e.g. atomization energies and bond lengths across the
+g2 database of small molecules.
+"""
+
+import os
+import sys
+import traceback
+
+import numpy as np
+
+from ase import PickleTrajectory, read
+from ase.calculators.emt import EMT
+from ase.data.molecules import molecule, atoms as g2_atoms, g1
+
+
+class BatchTest:
+    """Contains logic for looping over tests and file management."""
+    def __init__(self, test):
+        self.test = test
+        self.txt = sys.stdout # ?
+
+    def run_single_test(self, formula):
+        print >> self.txt, self.test.name, formula, '...',
+        self.txt.flush()
+        filename = self.test.get_filename(formula)
+        if os.path.exists(filename):
+            print >> self.txt, 'Skipped.'
+            return
+        try:
+            open(filename, 'w').close() # Empty file
+            system, calc = self.test.setup(formula)
+            self.test.run(formula, system, filename)
+            print >> self.txt, 'OK!'
+            self.txt.flush()
+        except self.test.exceptions:
+            print >> self.txt, 'Failed!'
+            traceback.print_exc(file=self.txt)
+            print >> self.txt
+            self.txt.flush()
+
+    def run(self, formulas):
+        """Run a batch of tests.
+
+        This will invoke the test method on each formula, printing
+        status to stdout.
+
+        Those formulas that already have test result files will
+        be skipped."""
+        
+        # Create directories if necessary
+        if self.test.dir and not os.path.isdir(self.test.dir):
+            os.mkdir(self.test.dir) # Won't work on 'dir1/dir2', but oh well
+        
+        for formula in formulas:
+            self.run_single_test(formula)
+    
+    def collect(self, formulas, verbose=False):
+        """Yield results of previous calculations."""
+        for formula in formulas:
+            try:
+                filename = self.test.get_filename(formula)
+                results = self.test.retrieve(formula, filename)
+                if verbose:
+                    print >> self.txt, 'Loaded:', formula, filename
+                yield formula, results
+            except (IOError, RuntimeError, TypeError):
+                # XXX which errors should we actually catch?
+                if verbose:
+                    print >> self.txt, 'Error:', formula, '[%s]' % filename
+                    traceback.print_exc(file=self.txt)
+
+
+class MoleculeTest:
+    """Generic class for runnings various tests on the g2 dataset.
+
+    Usage: instantiate MoleculeTest with desired test settings and
+    invoke its run() method on the desired formulas.
+
+    This class will use the ASE EMT calculator by default.  You can
+    create a subclass using an arbitrary calculator by overriding the
+    setup_calculator method.  Most methods can be overridden to
+    provide highly customized behaviour.  """
+    
+    def __init__(self, name, vacuum=6.0, exceptions=None):
+        """Create a molecule test.
+
+        The name parameter will be part of all output files generated
+        by this molecule test.  If name contains a '/' character, the
+        preceding part will be interpreted as a directory in which to
+        put files.
+
+        The vacuum parameter is used to set the cell size.
+
+        A tuple of exception types can be provided which will be
+        caught during a batch of calculations.  Types not specified
+        will be considered fatal."""
+
+        dir, path = os.path.split(name)
+        self.dir = dir
+        self.name = name
+        self.vacuum = vacuum
+        if exceptions is None:
+            exceptions = ()
+        self.exceptions = exceptions
+
+    def setup_calculator(self, system, formula):
+        """Create a new calculator.
+
+        Default is an EMT calculator.  Most implementations will want to
+        override this method."""
+        raise NotImplementedError
+
+    def setup_system(self, formula):
+        """Create an Atoms object from the given formula.
+
+        By default this will be loaded from the g2 database, setting
+        the cell size by means of the molecule test's vacuum parameter."""
+        system = molecule(formula)
+        system.center(vacuum=self.vacuum)
+        return system
+
+    def setup(self, formula):
+        """Build calculator and atoms objects.
+
+        This will invoke the setup_calculator and setup_system methods."""
+        system = self.setup_system(formula)
+        calc = self.setup_calculator(system, formula)
+        system.set_calculator(calc)
+        return system, calc
+        
+    def get_filename(self, formula, extension='traj'):
+        """Returns the filename for a test result file.
+
+        Default format is <name>.<formula>.traj
+
+        The test may write other files, but this filename is used as a
+        flag denoting whether the calculation has been done
+        already."""
+        return '.'.join([self.name, formula, extension])
+
+    def run(self, formula, system, filename):
+        raise NotImplementedError
+
+    def retrieve(self, formula, filename):
+        """Retrieve results of previous calculation from file.
+
+        Default implementation returns the total energy.
+
+        This method should be overridden whenever the test method is
+        overridden to calculate something else than the total energy."""
+        raise NotImplementedError
+
+
+class EnergyTest:
+    def run(self, formula, system, filename):
+        """Calculate energy of specified system and save to file."""
+        system.get_potential_energy()
+         # Won't create .bak file:
+        traj = PickleTrajectory(open(filename, 'w'), 'w')
+        traj.write(system)
+        traj.close()
+
+    def retrieve(self, formula, filename):
+        system = read(filename)
+        energy = system.get_potential_energy()
+        return energy
+    
+    def calculate_atomization_energies(self, molecular_energies,
+                                       atomic_energies):
+        atomic_energy_dict = dict(atomic_energies)
+        for formula, molecular_energy in molecular_energies:
+            try:
+                system = molecule(formula)
+                atomic = [atomic_energy_dict[s]
+                          for s in system.get_chemical_symbols()]            
+                atomization_energy = molecular_energy - sum(atomic)
+                yield formula, atomization_energy
+            except KeyError:
+                pass
+
+
+class BondLengthTest:
+    def run(self, formula, system, filename):
+        """Calculate bond length of a dimer.
+
+        This will calculate total energies for varying atomic
+        separations close to the g2 bond length, allowing
+        determination of bond length by fitting.
+        """
+        if len(system) != 2:
+            raise ValueError('Not a dimer')
+        traj = PickleTrajectory(open(filename, 'w'), 'w')
+        pos = system.positions
+        d = np.linalg.norm(pos[1] - pos[0])
+        for x in range(-2, 3):
+            system.set_distance(0, 1, d * (1.0 + x * 0.02))
+            traj.write(system)
+        traj.close()
+    
+    def retrieve(self, formula, filename):
+        traj = PickleTrajectory(filename, 'r')
+        distances = np.array([np.linalg.norm(a.positions[1] - a.positions[0])
+                              for a in traj])
+        energies = np.array([a.get_potential_energy() for a in traj])
+        polynomial = np.polyfit(distances, energies, 2) # or maybe 3rd order?
+        # With 3rd order it is not always obvious which root is right
+        pderiv = np.polyder(polynomial, 1)
+        d0 = np.roots(pderiv)
+        e0 = np.polyval(energies, d0)
+        return distances, energies, d0, e0, polynomial
+    
+
+class EMTTest(MoleculeTest):
+    def setup_calculator(self, system, calculator):
+        return EMT()
+
+
+class EMTEnergyTest(EnergyTest, EMTTest):
+    pass
+
+
+class EMTBondLengthTest(BondLengthTest, EMTTest):
+    pass
+
+
+def main():
+    supported_elements = 'Ni, C, Pt, Ag, H, Al, O, N, Au, Pd, Cu'.split(', ')
+    formulas = [formula for formula in g1
+                if np.all([symbol in supported_elements
+                           for symbol
+                           in molecule(formula).get_chemical_symbols()])]
+    
+    atoms = [symbol for symbol in g2_atoms if symbol in supported_elements]
+    dimers = [formula for formula in formulas if len(molecule(formula)) == 2]
+
+
+    name1 = 'testfiles/energy'
+    name2 = 'testfiles/bond'
+    test1 = BatchTest(EMTEnergyTest(name1, vacuum=3.0))
+    test2 = BatchTest(EMTBondLengthTest(name2, vacuum=3.0))
+
+    print 'Energy test'
+    print '-----------'
+    test1.run(formulas + atoms)
+
+    print
+    print 'Bond length test'
+    print '----------------'
+    test2.run(dimers)
+
+    print
+    print 'Atomization energies'
+    print '--------------------'
+    atomic_energies = dict(test1.collect(atoms))
+    molecular_energies = dict(test1.collect(formulas))
+    atomization_energies = {}
+    for formula, energy in molecular_energies.iteritems():
+        system = molecule(formula)
+        atomic = [atomic_energies[s] for s in system.get_chemical_symbols()]
+        atomization_energy = energy - sum(atomic)
+        atomization_energies[formula] = atomization_energy
+        print formula.rjust(10), '%.02f' % atomization_energy
+
+    print
+    print 'Bond lengths'
+    print '------------'
+    for formula, (d_i, e_i, d0, e0, poly) in test2.collect(dimers):
+        system = molecule(formula)
+        bref = np.linalg.norm(system.positions[1] - system.positions[0])
+        print formula.rjust(10), '%6.3f' % d0, '  g2ref =', '%2.3f' % bref
+
+        
+if __name__ == '__main__':
+    main()

Property changes on: ase/utils/molecule_test.py
___________________________________________________________________
Added: svn:executable
   + 

Index: ase/utils/eos.py
===================================================================
--- ase/utils/eos.py	(revision 0)
+++ ase/utils/eos.py	(revision 1)
@@ -0,0 +1,92 @@
+# -*- coding: utf-8 -*-
+from math import sqrt
+
+import numpy as np
+
+from ase.units import kJ
+
+class EquationOfState:
+    """Fit equation of state for bulk systems.
+
+    The following equation is used::
+
+                          2      3        -1/3
+      E(V) = c + c t + c t  + c t ,  t = V
+              0   1     2      3
+
+    Use::
+
+       eos = EquationOfState(volumes, energies)
+       v0, e0, B = eos.fit()
+       eos.plot()
+
+    """
+    def __init__(self, volumes, energies):
+        self.v = np.array(volumes)
+        self.e = np.array(energies)
+        self.v0 = None
+
+    def fit(self):
+        """Calculate volume, energy, and bulk modulus.
+
+        Returns the optimal volume, the minumum energy, and the bulk
+        modulus.  Notice that the ASE units for the bulk modulus is
+        eV/Angstrom^3 - to get the value in GPa, do this::
+
+          v0, e0, B = eos.fit()
+          print B / kJ * 1.0e24, 'GPa'
+          
+        """
+        
+        fit0 = np.poly1d(np.polyfit(self.v**-(1.0 / 3), self.e, 3))
+        fit1 = np.polyder(fit0, 1)
+        fit2 = np.polyder(fit1, 1)
+
+        self.v0 = None
+        for t in np.roots(fit1):
+            if t > 0 and fit2(t) > 0:
+                self.v0 = t**-3
+                break
+
+        if self.v0 is None:
+            raise ValueError('No minimum!')
+        
+        self.e0 = fit0(t)
+        self.B = t**5 * fit2(t) / 9
+        self.fit0 = fit0
+        
+        return self.v0, self.e0, self.B
+
+    def plot(self, filename=None, show=None):
+        """Plot fitted energy curve.
+
+        Uses Matplotlib to plot the energy curve.  Use *show=True* to
+        show the figure and *filename='abc.png'* or
+        *filename='abc.eps'* to save the figure to a file."""
+        
+        #import matplotlib.pyplot as plt
+        import pylab as plt
+
+        if self.v0 is None:
+            self.fit()
+            
+        if filename is None and show is None:
+            show = True
+
+        x = 3.95
+        f = plt.figure(figsize=(x * 2.5**0.5, x))
+        f.subplots_adjust(left=0.12, right=0.9, top=0.9, bottom=0.15)
+        plt.plot(self.v, self.e, 'o')
+        x = np.linspace(min(self.v), max(self.v), 100)
+        plt.plot(x, self.fit0(x**-(1.0 / 3)), '-r')
+        plt.xlabel(u'volume [Å^3]')
+        plt.ylabel(u'energy [eV]')
+        plt.title(u'E: %.3f eV, V: %.3f Å^3, B: %.3f GPa' %
+                  (self.e0, self.v0, self.B / kJ * 1.0e24))
+
+        if show:
+            plt.show()
+        if filename is not None:
+            f.savefig(filename)
+
+        return f
Index: ase/optimize/optimize.py
===================================================================
--- ase/optimize/optimize.py	(revision 0)
+++ ase/optimize/optimize.py	(revision 1)
@@ -0,0 +1,184 @@
+"""Structure optimization. """
+
+import sys
+import pickle
+import time
+from math import sqrt
+from os.path import isfile
+
+import numpy as np
+
+from ase.parallel import rank, barrier
+from ase.io.trajectory import PickleTrajectory
+
+
+class Dynamics:
+    """Base-class for all MD and structure optimization classes.
+
+    Dynamics(atoms, logfile)
+
+    atoms: Atoms object
+        The Atoms object to operate on
+    logfile: file object or str
+        If *logfile* is a string, a file with that name will be opened.
+        Use '-' for stdout.
+    trajectory: Trajectory object or str
+        Attach trajectory object.  If *trajectory* is a string a
+        PickleTrajectory will be constructed.  Use *None* for no
+        trajectory.
+    """
+    def __init__(self, atoms, logfile, trajectory):
+        self.atoms = atoms
+
+        if rank != 0:
+            logfile = None
+        elif isinstance(logfile, str):
+            if logfile == '-':
+                logfile = sys.stdout
+            else:
+                logfile = open(logfile, 'a')
+        self.logfile = logfile
+        
+        self.observers = []
+        self.nsteps = 0
+
+        if trajectory is not None:
+            if isinstance(trajectory, str):
+                trajectory = PickleTrajectory(trajectory, 'w', atoms)
+            self.attach(trajectory)
+
+    def get_number_of_steps(self):
+        return self.nsteps
+
+    def insert_observer(self, function, position=0, interval=1, 
+                        *args, **kwargs):
+        """Insert an observer."""
+        if not callable(function):
+            function = function.write
+        self.observers.insert(position, (function, interval, args, kwargs))
+
+    def attach(self, function, interval=1, *args, **kwargs):
+        """Attach callback function.
+
+        At every *interval* steps, call *function* with arguments
+        *args* and keyword arguments *kwargs*."""
+
+        if not hasattr(function, '__call__'):
+            function = function.write
+        self.observers.append((function, interval, args, kwargs))
+
+    def call_observers(self):
+        for function, interval, args, kwargs in self.observers:
+            if self.nsteps % interval == 0:
+                function(*args, **kwargs)
+
+
+class Optimizer(Dynamics):
+    """Base-class for all structure optimization classes."""
+    def __init__(self, atoms, restart, logfile, trajectory):
+        """Structure optimizer object.
+
+        atoms: Atoms object
+            The Atoms object to relax.
+        restart: str
+            Filename for restart file.  Default value is *None*.
+        logfile: file object or str
+            If *logfile* is a string, a file with that name will be opened.
+            Use '-' for stdout.
+        trajectory: Trajectory object or str
+            Attach trajectory object.  If *trajectory* is a string a
+            PickleTrajectory will be constructed.  Use *None* for no
+            trajectory.
+        """
+        Dynamics.__init__(self, atoms, logfile, trajectory)
+        self.restart = restart
+
+        if restart is None or not isfile(restart):
+            self.initialize()
+        else:
+            self.read()
+            barrier()
+    def initialize(self):
+        pass
+
+    def run(self, fmax=0.05, steps=100000000):
+        """Run structure optimization algorithm.
+
+        This method will return when the forces on all individual
+        atoms are less than *fmax* or when the number of steps exceeds
+        *steps*."""
+
+        self.fmax = fmax
+        step = 0
+        while step < steps:
+            f = self.atoms.get_forces()
+            self.log(f)
+            self.call_observers()
+            if self.converged(f):
+                return
+            self.step(f)
+            self.nsteps += 1
+            step += 1
+
+    def converged(self, forces=None):
+        """Did the optimization converge?"""
+        if forces is None:
+            forces = self.atoms.get_forces()
+        return (forces**2).sum(axis=1).max() < self.fmax**2
+
+    def log(self, forces):
+        fmax = sqrt((forces**2).sum(axis=1).max())
+        e = self.atoms.get_potential_energy()
+        T = time.localtime()
+        if self.logfile is not None:
+            name = self.__class__.__name__
+            self.logfile.write('%s: %3d  %02d:%02d:%02d %15.6f %12.4f\n' %
+                               (name, self.nsteps, T[3], T[4], T[5], e, fmax))
+            self.logfile.flush()
+        
+    def dump(self, data):
+        if rank == 0 and self.restart is not None:
+            pickle.dump(data, open(self.restart, 'wb'), protocol=2)
+
+    def load(self):
+        return pickle.load(open(self.restart))
+
+
+class NDPoly:
+    def __init__(self, ndims=1, order=3):
+        """Multivariate polynomium.
+
+        ndims: int
+            Number of dimensions.
+        order: int
+            Order of polynomium."""
+        
+        if ndims == 0:
+            exponents = [()]
+        else:
+            exponents = []
+            for i in range(order + 1):
+                E = NDPoly(ndims - 1, order - i).exponents
+                exponents += [(i,) + tuple(e) for e in E]
+        self.exponents = np.array(exponents)
+        self.c = None
+        
+    def __call__(self, *x):
+        """Evaluate polynomial at x."""
+        return np.dot(self.c, (x**self.exponents).prod(1))
+
+    def fit(self, x, y):
+        """Fit polynomium at points in x to values in y."""
+        A = (x**self.exponents[:, np.newaxis]).prod(2)
+        self.c = np.linalg.solve(np.inner(A, A), np.dot(A, y))
+
+
+def polyfit(x, y, order=3):
+    """Fit polynomium at points in x to values in y.
+
+    With D dimensions and N points, x must have shape (N, D) and y
+    must have length N."""
+    
+    p = NDPoly(len(x[0]), order)
+    p.fit(x, y)
+    return p
Index: ase/optimize/bfgs.py
===================================================================
--- ase/optimize/bfgs.py	(revision 0)
+++ ase/optimize/bfgs.py	(revision 1)
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+import numpy as np
+from numpy.linalg import eigh, solve
+
+from ase.optimize.optimize import Optimizer
+
+
+class BFGS(Optimizer):
+    def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
+                 maxstep=None):
+        """BFGS optimizer.
+
+        Parameters:
+
+        restart: string
+            Pickle file used to store hessian matrix. If set, file with 
+            such a name will be searched and hessian matrix stored will
+            be used, if the file exists.
+        trajectory: string
+            Pickle file used to store trajectory of atomic movement.
+        maxstep: float
+            Used to set the maximum distance an atom can move per
+            iteration (default value is 0.04 Å).
+        """
+        
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+
+        if maxstep is not None:
+            if maxstep > 1.0:
+                raise ValueError('You are using a much too large value for ' +
+                                 'the maximum step size: %.1f Å' % maxstep)
+            self.maxstep = maxstep
+
+    def initialize(self):
+        self.H = None
+        self.r0 = None
+        self.f0 = None
+        self.maxstep = 0.04
+
+    def read(self):
+        self.H, self.r0, self.f0, self.maxstep = self.load()
+
+    def step(self, f):
+        atoms = self.atoms
+        r = atoms.get_positions()
+        f = f.reshape(-1)
+        self.update(r.flat, f, self.r0, self.f0)
+        omega, V = eigh(self.H)
+        dr = np.dot(V, np.dot(f, V) / np.fabs(omega)).reshape((-1, 3))
+        #dr = solve(self.H, f).reshape((-1, 3))
+        steplengths = (dr**2).sum(1)**0.5
+        dr = self.determine_step(dr, steplengths)
+        atoms.set_positions(r + dr)
+        self.r0 = r.flat.copy()
+        self.f0 = f.copy()
+        self.dump((self.H, self.r0, self.f0, self.maxstep))
+
+    def determine_step(self, dr, steplengths):
+        """Determine step to take according to maxstep
+        
+        Normalize all steps as the largest step. This way
+        we still move along the eigendirection.
+        """
+        maxsteplength = np.max(steplengths)
+        if maxsteplength >= self.maxstep:
+            dr *= self.maxstep / maxsteplength
+        
+        return dr
+
+    def update(self, r, f, r0, f0):
+        if self.H is None:
+            self.H = np.eye(3 * len(self.atoms)) * 70.0
+            return
+        dr = r - r0
+
+        if np.abs(dr).max() < 1e-7:
+            # Same configuration again (maybe a restart):
+            return
+
+        df = f - f0
+        a = np.dot(dr, df)
+        dg = np.dot(self.H, dr)
+        b = np.dot(dr, dg)
+        self.H -= np.outer(df, df) / a + np.outer(dg, dg) / b
+
+    def replay_trajectory(self, traj):
+        """Initialize hessian from old trajectory."""
+        if isinstance(traj, str):
+            from ase.io.trajectory import PickleTrajectory
+            traj = PickleTrajectory(traj, 'r')
+        self.H = None
+        atoms = traj[0]
+        r0 = atoms.get_positions().ravel()
+        f0 = atoms.get_forces().ravel()
+        for atoms in traj:
+            r = atoms.get_positions().ravel()
+            f = atoms.get_forces().ravel()
+            self.update(r, f, r0, f0)
+            r0 = r
+            f0 = f
+
+        self.r0 = r0
+        self.f0 = f0
+
+class oldBFGS(BFGS):
+    def determine_step(self, dr, steplengths):
+        """Old BFGS behaviour for scaling step lengths
+
+        This keeps the behaviour of truncating individual steps. Some might
+        depend of this as some absurd kind of stimulated annealing to find the
+        global minimum.
+        """
+        dr /= np.maximum(steplengths / self.maxstep, 1.0).reshape(-1, 1)
+        return dr
Index: ase/optimize/sciopt.py
===================================================================
--- ase/optimize/sciopt.py	(revision 0)
+++ ase/optimize/sciopt.py	(revision 1)
@@ -0,0 +1,272 @@
+import numpy as np
+try:
+    import scipy.optimize as opt
+except ImportError:
+    pass
+
+from ase.optimize.optimize import Optimizer
+
+
+class Converged(Exception):
+    pass
+
+class OptimizerConvergenceError(Exception):
+    pass
+
+class SciPyOptimizer(Optimizer):
+    """General interface for SciPy optimizers
+
+    Only the call to the optimizer is still needed
+    """
+    def __init__(self, atoms, logfile='-', trajectory=None,
+                 callback_always=False, alpha=70.0):
+        """Initialize object
+
+        Parameters:
+
+        callback_always: book
+            Should the callback be run after each force call (also in the
+            linesearch)
+
+        alpha: float
+            Initial guess for the Hessian (curvature of energy surface). A
+            conservative value of 70.0 is the default, but number of needed
+            steps to converge might be less if a lower value is used. However,
+            a lower value also means risk of instability.
+
+        """
+        restart = None
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+        self.force_calls = 0
+        self.callback_always = callback_always
+        self.H0 = alpha
+
+    def x0(self):
+        """Return x0 in a way SciPy can use
+
+        This class is mostly usable for subclasses wanting to redefine the
+        parameters (and the objective function)"""
+        return self.atoms.get_positions().reshape(-1)
+
+    def f(self, x):
+        """Objective function for use of the optimizers"""
+        self.atoms.set_positions(x.reshape(-1, 3))
+        # Scale the problem as SciPy uses I as initial Hessian.
+        return self.atoms.get_potential_energy() / self.H0
+
+    def fprime(self, x):
+        """Gradient of the objective function for use of the optimizers"""
+        self.atoms.set_positions(x.reshape(-1, 3))
+        self.force_calls += 1
+
+        if self.callback_always:
+            self.callback(x)
+
+        # Remember that forces are minus the gradient!
+        # Scale the problem as SciPy uses I as initial Hessian.
+        return - self.atoms.get_forces().reshape(-1) / self.H0
+
+    def callback(self, x):
+        """Callback function to be run after each iteration by SciPy
+
+        This should also be called once before optimization starts, as SciPy
+        optimizers only calls it after each iteration, while ase optimizers
+        call something similar before as well.
+        """
+        f = self.atoms.get_forces()
+        self.log(f)
+        self.call_observers()
+        if self.converged(f):
+            raise Converged
+        self.nsteps += 1
+
+    def run(self, fmax=0.05, steps=100000000):
+        self.fmax = fmax
+        # As SciPy does not log the zeroth iteration, we do that manually
+        self.callback(None)
+        try:
+            # Scale the problem as SciPy uses I as initial Hessian.
+            self.call_fmin(fmax / self.H0, steps)
+        except Converged:
+            pass
+
+    def dump(self, data):
+        pass
+
+    def load(self):
+        pass
+
+    def call_fmin(self, fmax, steps):
+        raise NotImplementedError
+
+class SciPyFminCG(SciPyOptimizer):
+    """Non-linear (Polak-Ribiere) conjugate gradient algorithm"""
+    def call_fmin(self, fmax, steps):
+        output = opt.fmin_cg(self.f,
+                             self.x0(),
+                             fprime=self.fprime,
+                             #args=(),
+                             gtol=fmax * 0.1, #Should never be reached
+                             norm=np.inf,
+                             #epsilon=
+                             maxiter=steps,
+                             full_output=1,
+                             disp=0,
+                             #retall=0, 
+                             callback=self.callback
+                            )
+        warnflag = output[-1]
+        if warnflag == 2:
+            raise OptimizerConvergenceError('Warning: Desired error not necessarily achieved ' \
+                                            'due to precision loss')
+
+class SciPyFminBFGS(SciPyOptimizer):
+    """Quasi-Newton method (Broydon-Fletcher-Goldfarb-Shanno)"""
+    def call_fmin(self, fmax, steps):
+        output = opt.fmin_bfgs(self.f,
+                               self.x0(),
+                               fprime=self.fprime,
+                               #args=(), 
+                               gtol=fmax * 0.1, #Should never be reached
+                               norm=np.inf,
+                               #epsilon=1.4901161193847656e-08, 
+                               maxiter=steps,
+                               full_output=1,
+                               disp=0,
+                               #retall=0, 
+                               callback=self.callback
+                              )
+        warnflag = output[-1]
+        if warnflag == 2:
+            raise OptimizerConvergenceError('Warning: Desired error not necessarily achieved' \
+                                            'due to precision loss')
+
+class SciPyGradientlessOptimizer(Optimizer):
+    """General interface for gradient less SciPy optimizers
+
+    Only the call to the optimizer is still needed
+
+    Note: If you redefien x0() and f(), you don't even need an atoms object.
+    Redefining these also allows you to specify an arbitrary objective
+    function.
+
+    XXX: This is still a work in progress
+    """
+    def __init__(self, atoms, logfile='-', trajectory=None,
+                 callback_always=False):
+        """Parameters:
+
+        callback_always: book
+            Should the callback be run after each force call (also in the
+            linesearch)
+        """
+        restart = None
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+        self.function_calls = 0
+        self.callback_always = callback_always
+
+    def x0(self):
+        """Return x0 in a way SciPy can use
+
+        This class is mostly usable for subclasses wanting to redefine the
+        parameters (and the objective function)"""
+        return self.atoms.get_positions().reshape(-1)
+
+    def f(self, x):
+        """Objective function for use of the optimizers"""
+        self.atoms.set_positions(x.reshape(-1, 3))
+        self.function_calls += 1
+        # Scale the problem as SciPy uses I as initial Hessian.
+        return self.atoms.get_potential_energy()
+
+    def callback(self, x):
+        """Callback function to be run after each iteration by SciPy
+
+        This should also be called once before optimization starts, as SciPy
+        optimizers only calls it after each iteration, while ase optimizers
+        call something similar before as well.
+        """
+        # We can't assume that forces are available!
+        #f = self.atoms.get_forces()
+        #self.log(f)
+        self.call_observers()
+        #if self.converged(f):
+        #    raise Converged
+        self.nsteps += 1
+
+    def run(self, ftol=0.01, xtol=0.01, steps=100000000):
+        self.xtol = xtol
+        self.ftol = ftol
+        # As SciPy does not log the zeroth iteration, we do that manually
+        self.callback(None)
+        try:
+            # Scale the problem as SciPy uses I as initial Hessian.
+            self.call_fmin(xtol, ftol, steps)
+        except Converged:
+            pass
+
+    def dump(self, data):
+        pass
+
+    def load(self):
+        pass
+
+    def call_fmin(self, fmax, steps):
+        raise NotImplementedError
+
+class SciPyFmin(SciPyGradientlessOptimizer):
+    """Nelder-Mead Simplex algorithm
+
+    Uses only function calls.
+
+    XXX: This is still a work in progress
+    """
+    def call_fmin(self, xtol, ftol, steps):
+        output = opt.fmin(self.f,
+                          self.x0(),
+                          #args=(),
+                          xtol=xtol,
+                          ftol=ftol,
+                          maxiter=steps,
+                          #maxfun=None,
+                          #full_output=1,
+                          disp=0,
+                          #retall=0,
+                          callback=self.callback
+                         )
+
+class SciPyFminPowell(SciPyGradientlessOptimizer):
+    """Powell's (modified) level set method
+
+    Uses only function calls.
+
+    XXX: This is still a work in progress
+    """
+    def __init__(self, *args, **kwargs):
+        """Parameters:
+
+        direc: float
+            How much to change x to initially. Defaults to 0.04.
+        """
+        direc = kwargs.pop('direc', None)
+        SciPyGradientlessOptimizer.__init__(self, *args, **kwargs)
+
+        if direc is None:
+            self.direc = np.eye(len(self.x0()), dtype=float) * 0.04
+        else:
+            self.direc = np.eye(len(self.x0()), dtype=float) * direc
+
+    def call_fmin(self, xtol, ftol, steps):
+        output = opt.fmin_powell(self.f,
+                                 self.x0(),
+                                 #args=(),
+                                 xtol=xtol,
+                                 ftol=ftol,
+                                 maxiter=steps,
+                                 #maxfun=None,
+                                 #full_output=1,
+                                 disp=0,
+                                 #retall=0,
+                                 callback=self.callback,
+                                 direc=self.direc
+                                )
Index: ase/optimize/mdmin.py
===================================================================
--- ase/optimize/mdmin.py	(revision 0)
+++ ase/optimize/mdmin.py	(revision 1)
@@ -0,0 +1,38 @@
+import numpy as np
+
+from ase.optimize.optimize import Optimizer
+
+
+class MDMin(Optimizer):
+    def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
+                 dt=None):
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+
+        if dt is not None:
+            self.dt = dt
+
+    def initialize(self):
+        self.v = None
+        self.dt = 0.2
+
+    def read(self):
+        self.v, self.dt = self.load()
+        
+    def step(self, f):
+        atoms = self.atoms
+
+        if self.v is None:
+            self.v = np.zeros((len(atoms), 3))
+        else:
+            self.v += 0.5 * self.dt * f
+            # Correct velocities:
+            vf = np.vdot(self.v, f)
+            if vf < 0.0:
+                self.v[:] = 0.0
+            else:
+                self.v[:] = f * vf / np.vdot(f, f)
+
+        self.v += 0.5 * self.dt * f
+        r = atoms.get_positions()
+        atoms.set_positions(r + self.dt * self.v)
+        self.dump((self.v, self.dt))
Index: ase/optimize/fire.py
===================================================================
--- ase/optimize/fire.py	(revision 0)
+++ ase/optimize/fire.py	(revision 1)
@@ -0,0 +1,67 @@
+import numpy as np
+
+from ase.optimize.optimize import Optimizer
+
+
+class FIRE(Optimizer):
+    def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
+                 dt=0.1, maxmove=0.2, dtmax=1.0, Nmin=5, finc=1.1, fdec=0.5,
+                 astart=0.1, fa=0.99, a=0.1):
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+
+        self.dt = dt
+        self.Nsteps = 0
+        self.maxmove = maxmove
+        self.dtmax = dtmax
+        self.Nmin = Nmin
+        self.finc = finc
+        self.fdec = fdec
+        self.astart = astart
+        self.fa = fa
+        self.a = a
+
+    def initialize(self):
+        self.v = None
+
+    def read(self):
+        self.v, self.dt = self.load()
+       
+    def step(self,f):
+        atoms = self.atoms
+        if self.v is None:
+            self.v = np.zeros((len(atoms), 3))
+        else:
+            vf = np.vdot(f, self.v)
+            if vf > 0.0:
+                self.v = (1.0 - self.a) * self.v + self.a * f / np.sqrt(
+                    np.vdot(f, f)) * np.sqrt(np.vdot(self.v, self.v))
+                if self.Nsteps > self.Nmin:
+                    self.dt = min(self.dt * self.finc, self.dtmax)
+                    self.a *= self.fa
+                self.Nsteps += 1
+            else:
+                self.v[:] *= 0.0
+                self.a = self.astart
+                self.dt *= self.fdec
+                self.Nsteps = 0
+#            if vf < 0.0:
+#                self.v[:] = 0.0
+#                self.a = self.astart
+#                self.dt *= self.fdec
+#                self.Nsteps = 0
+#            else:
+#                self.v = (1.0 - self.a) * self.v + self.a * f * np.sqrt(
+#                    np.vdot(f, f) / np.vdot(self.v, self.v))
+#                if self.Nsteps > self.Nmin:
+#                    dt = min(dt * self.finc, dtmax)
+#                    self.a *= self.fa
+#                    self.Nsteps += 1
+
+            self.v += self.dt * f
+            dr = self.dt * self.v
+            normdr = np.sqrt(np.vdot(dr, dr))
+            if normdr > self.maxmove:
+                dr = self.maxmove * dr / normdr
+            r = atoms.get_positions()
+            atoms.set_positions(r + dr)
+            self.dump((self.v, self.dt))
Index: ase/optimize/test/Cu_bulk.py
===================================================================
--- ase/optimize/test/Cu_bulk.py	(revision 0)
+++ ase/optimize/test/Cu_bulk.py	(revision 1)
@@ -0,0 +1,16 @@
+from ase.calculators.emt import EMT
+from ase.lattice.cubic import FaceCenteredCubic
+from ase.optimize.test import run_test
+
+name = 'Cu_bulk'
+
+def get_atoms():
+    atoms = FaceCenteredCubic(directions=[[1,-1,0], [1,1,0], [0,0,1]],
+                              size=(3,3,3), symbol='Cu', pbc=(1,1,1))
+    atoms.rattle(stdev=0.1,seed=42)
+    return atoms
+
+def get_calculator():
+    return EMT()
+
+run_test(get_atoms, get_calculator, name, fmax=0.02)
Index: ase/optimize/test/neb.py
===================================================================
--- ase/optimize/test/neb.py	(revision 0)
+++ ase/optimize/test/neb.py	(revision 1)
@@ -0,0 +1,83 @@
+#/usr/bin/env python
+#PBS -l nodes=4:ppn=8
+#PBS -l walltime=13:00:00
+from ase.optimize import QuasiNewton
+from ase.constraints import FixAtoms
+from ase.calculators.emt import EMT
+from ase.neb import NEB
+from ase.lattice.surface import fcc100, add_adsorbate
+from ase.optimize.test import run_test
+from gpaw import GPAW, Mixer, PoissonSolver
+import gpaw.mpi as mpi
+
+
+name = 'neb'
+
+def get_atoms():
+    # 2x2-Al(001) surface with 3 layers and an
+    # Au atom adsorbed in a hollow site:
+    slab = fcc100('Al', size=(2, 2, 3))
+    add_adsorbate(slab, 'Au', 1.7, 'hollow')
+    slab.center(axis=2, vacuum=4.0)
+
+    # Fix second and third layers:
+    mask = [atom.tag > 1 for atom in slab]
+    slab.set_constraint(FixAtoms(mask=mask))
+
+    # Use EMT potential:
+    slab.set_calculator(EMT())
+
+    # Initial state:
+    qn = QuasiNewton(slab, logfile=None)
+    qn.run(fmax=0.05)
+    initial = slab.copy()
+
+    # Final state:
+    slab[-1].x += slab.get_cell()[0, 0] / 2
+    qn = QuasiNewton(slab, logfile=None)
+    qn.run(fmax=0.05)
+    final = slab.copy()
+
+    # Setup a NEB calculation
+    constraint = FixAtoms(mask=[atom.tag > 1 for atom in initial])
+
+    images = [initial]
+    for i in range(3):
+        image = initial.copy()
+        image.set_constraint(constraint)
+        images.append(image)
+
+    images.append(final)
+
+    neb = NEB(images, parallel=mpi.parallel)
+    neb.interpolate()
+
+    def set_calculator(calc):
+        i = 0
+        for image in neb.images[1:-1]:
+            if not mpi.parallel or mpi.rank // (mpi.size // 3) == i:
+                image.set_calculator(calc)
+            i += 1
+    neb.set_calculator = set_calculator
+
+    return neb
+
+def get_calculator_emt():
+    return EMT()
+
+def get_calculator_gpaw():
+    if mpi.parallel:
+        assert mpi.size % 3 == 0
+        s = mpi.size // 3
+        r0 = mpi.rank // s * s
+        comm = range(r0, r0 + s)
+    else:
+        comm = mpi.world
+    calc = GPAW(h=0.25,
+                kpts=(2, 2, 1),
+                communicator=comm,
+                txt='neb-%d.txt' % r0)
+    return calc
+
+run_test(get_atoms, get_calculator_emt, name + '-emt')
+run_test(get_atoms, get_calculator_gpaw, name + '-gpaw')
Index: ase/optimize/test/CO_Au111.py
===================================================================
--- ase/optimize/test/CO_Au111.py	(revision 0)
+++ ase/optimize/test/CO_Au111.py	(revision 1)
@@ -0,0 +1,30 @@
+from math import sin, cos, pi
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.constraints import FixAtoms
+from ase.optimize.test import run_test
+from ase.lattice.surface import fcc111, add_adsorbate
+
+name = 'CO_Au111'
+
+def get_atoms():
+    zpos = cos(134.3/2.0*pi/180.0)*1.197
+    xpos = sin(134.3/2.0*pi/180.0)*1.19
+    no2 =Atoms('CO', positions=[(-xpos+1.2,0,-zpos), (-xpos+1.2,-1.1,-zpos)])
+
+    # Surface slab
+    slab =fcc111('Au', size=(2, 2, 4),vacuum=2*5, orthogonal = True )
+    slab.center()
+    add_adsorbate(slab,no2,1.5,'bridge')
+    slab.set_pbc((True,True,False))
+
+    #constraints
+    constraint = FixAtoms(mask=[(a.tag == 4) or (a.tag == 3) or (a.tag==2) for a in slab])
+    slab.set_constraint(constraint)
+    return slab
+
+def get_calculator():
+    calc = EMT()
+    return calc
+
+run_test(get_atoms, get_calculator, name, steps=200)
Index: ase/optimize/test/H2.py
===================================================================
--- ase/optimize/test/H2.py	(revision 0)
+++ ase/optimize/test/H2.py	(revision 1)
@@ -0,0 +1,23 @@
+from ase import Atoms
+from ase.calculators.emt import EMT
+from ase.optimize.test import run_test
+from gpaw import GPAW
+
+name = 'H2'
+
+def get_atoms():
+    cell = (5, 5, 5)
+    atoms = Atoms('H2', [(0, 0, 0), (0, 0, 1.4)], cell=cell)
+    atoms.center()
+    return atoms
+
+def get_calculator_emt():
+    calc = EMT()
+    return calc
+
+def get_calculator_gpaw():
+    calc = GPAW(xc='PBE',txt=None)
+    return calc
+
+run_test(get_atoms, get_calculator_emt, name + '-emt')
+run_test(get_atoms, get_calculator_gpaw, name + '-gpaw', steps=25)
Index: ase/optimize/test/C2_Cu100.py
===================================================================
--- ase/optimize/test/C2_Cu100.py	(revision 0)
+++ ase/optimize/test/C2_Cu100.py	(revision 1)
@@ -0,0 +1,103 @@
+#PBS -l nodes=4:ppn=8
+#PBS -l walltime=13:00:00
+import numpy as np
+from ase import Atoms
+from ase.constraints import FixedPlane, FixAtoms
+from ase.optimize.test import run_test
+from gpaw import GPAW, Mixer
+from gpaw.poisson import PoissonSolver
+
+def get_calculator():
+    basis = 'szp(dzp)'
+    calc = GPAW(gpts=(64, 64, 128), # ca h=0.25
+                width=0.1,
+                nbands=-5,
+                xc='LDA',
+                mode='lcao',
+                txt='C2_Cu100.txt',
+                mixer=Mixer(beta=0.1, nmaxold=5, weight=50.0),
+                poissonsolver=PoissonSolver(nn='M', relax='GS'),
+                stencils=(3, 3),
+                maxiter=400,
+                basis=basis)
+    return calc
+
+def get_atoms():
+    srf = Atoms('Cu64',[(1.2763,    1.2763,    4.0000),
+                        (3.8290,    1.2763,    4.0000),
+                        (6.3816,    1.2763,    4.0000),
+                        (8.9343,    1.2763,    4.0000),
+                        (1.2763,    3.8290,    4.0000),
+                        (3.8290,    3.8290,    4.0000),
+                        (6.3816,    3.8290,    4.0000),
+                        (8.9343,    3.8290,    4.0000),
+                        (1.2763,    6.3816,    4.0000),
+                        (3.8290,    6.3816,    4.0000),
+                        (6.3816,    6.3816,    4.0000),
+                        (8.9343,    6.3816,    4.0000),
+                        (1.2763,    8.9343,    4.0000),
+                        (3.8290,    8.9343,    4.0000),
+                        (6.3816,    8.9343,    4.0000),
+                        (8.9343,    8.9343,    4.0000),
+                        (0.0000,    0.0000,    5.8050),
+                        (2.5527,    0.0000,    5.8050),
+                        (5.1053,    0.0000,    5.8050),
+                        (7.6580,    0.0000,    5.8050),
+                        (0.0000,    2.5527,    5.8050),
+                        (2.5527,    2.5527,    5.8050),
+                        (5.1053,    2.5527,    5.8050),
+                        (7.6580,    2.5527,    5.8050),
+                        (0.0000,    5.1053,    5.8050),
+                        (2.5527,    5.1053,    5.8050),
+                        (5.1053,    5.1053,    5.8050),
+                        (7.6580,    5.1053,    5.8050),
+                        (0.0000,    7.6580,    5.8050),
+                        (2.5527,    7.6580,    5.8050),
+                        (5.1053,    7.6580,    5.8050),
+                        (7.6580,    7.6580,    5.8050),
+                        (1.2409,    1.2409,    7.6081),
+                        (3.7731,    1.2803,    7.6603),
+                        (6.3219,    1.3241,    7.6442),
+                        (8.8935,    1.2669,    7.6189),
+                        (1.2803,    3.7731,    7.6603),
+                        (3.8188,    3.8188,    7.5870),
+                        (6.3457,    3.8718,    7.6649),
+                        (8.9174,    3.8340,    7.5976),
+                        (1.3241,    6.3219,    7.6442),
+                        (3.8718,    6.3457,    7.6649),
+                        (6.3945,    6.3945,    7.6495),
+                        (8.9576,    6.3976,    7.6213),
+                        (1.2669,    8.8935,    7.6189),
+                        (3.8340,    8.9174,    7.5976),
+                        (6.3976,    8.9576,    7.6213),
+                        (8.9367,    8.9367,    7.6539),
+                        (0.0582,    0.0582,    9.4227),
+                        (2.5965,   -0.2051,    9.4199),
+                        (5.1282,    0.0663,    9.4037),
+                        (7.6808,   -0.0157,    9.4235),
+                        (-0.2051,   2.5965,    9.4199),
+                        (2.1913,    2.1913,    9.6123),
+                        (5.0046,    2.5955,    9.4873),
+                        (7.5409,    2.5336,    9.4126),
+                        (0.0663,    5.1282,    9.4037),
+                        (2.5955,    5.0046,    9.4873),
+                        (5.3381,    5.3381,    9.6106),
+                        (7.8015,    5.0682,    9.4237),
+                        (-0.0157,   7.6808,    9.4235),
+                        (2.5336,    7.5409,    9.4126),
+                        (5.0682,    7.8015,    9.4237),
+                        (7.6155,    7.6155,    9.4317)])
+    c2=Atoms('C2', [(3.2897,    3.2897,   10.6627),
+                    (4.2113,    4.2113,   10.6493)])
+    srf.extend(c2)
+    srf.pbc=(1, 1, 0)
+    srf.set_cell([ 10.2106, 10.2106, 20.6572],scale_atoms=False)
+
+    mask=[a.index < 32  for a in srf]
+    c1 = FixedPlane(-1, (1/np.sqrt(2), 1/np.sqrt(2), 1))
+    c2 = FixedPlane(-2, (1/np.sqrt(2), 1/np.sqrt(2), 1))
+    constraint = FixAtoms(mask=mask)
+    srf.set_constraint([constraint, c1, c2])
+    return srf
+
+run_test(get_atoms, get_calculator, 'C2_Cu100')

Property changes on: ase/optimize/test/C2_Cu100.py
___________________________________________________________________
Added: svn:executable
   + 

Index: ase/optimize/test/__init__.py
===================================================================
--- ase/optimize/test/__init__.py	(revision 0)
+++ ase/optimize/test/__init__.py	(revision 1)
@@ -0,0 +1,152 @@
+"""Define a helper function for running tests
+
+The skeleton for making a new setup is as follows:
+
+from ase.optimize.test import run_test
+
+def get_atoms():
+    return Atoms('H')
+
+def get_calculator():
+    return EMT()
+
+run_test(get_atoms, get_calculator, 'Hydrogen')
+"""
+import matplotlib
+matplotlib.rcParams['backend']="Agg"
+
+from ase.optimize.bfgs import BFGS
+from ase.optimize.lbfgs import LBFGS, LBFGSLineSearch
+from ase.optimize.fire import FIRE
+from ase.optimize.mdmin import MDMin
+from ase.optimize.sciopt import SciPyFminCG
+from ase.optimize.sciopt import SciPyFminBFGS
+from ase.optimize.bfgslinesearch import BFGSLineSearch
+
+from ase.parallel import rank, paropen
+
+import matplotlib.pyplot as pl
+import numpy as np
+
+import traceback
+
+optimizers = [
+    'BFGS',
+    'LBFGS',
+    'LBFGSLineSearch',
+    'FIRE',
+    'MDMin',
+    'SciPyFminCG',
+    'SciPyFminBFGS',
+    'BFGSLineSearch'
+]
+
+def get_optimizer(optimizer):
+    if optimizer == 'BFGS': return BFGS
+    elif optimizer == 'LBFGS': return LBFGS
+    elif optimizer == 'LBFGSLineSearch': return LBFGSLineSearch
+    elif optimizer == 'FIRE': return FIRE
+    elif optimizer == 'MDMin': return MDMin
+    elif optimizer == 'SciPyFminCG': return SciPyFminCG
+    elif optimizer == 'SciPyFminBFGS': return SciPyFminBFGS
+    elif optimizer == 'BFGSLineSearch': return BFGSLineSearch
+
+def run_test(get_atoms, get_calculator, name,
+             fmax=0.05, steps=100, plot=True):
+
+    plotter = Plotter(name, fmax)
+    csvwriter = CSVWriter(name)
+    for optimizer in optimizers:
+        note = ''
+        logname = name + '-' + optimizer
+
+        atoms = get_atoms()
+        atoms.set_calculator(get_calculator())
+        opt = get_optimizer(optimizer)
+        relax = opt(atoms, logfile=None)
+                    #logfile = logname + '.log',
+                    #trajectory = logname + '.traj')
+
+        obs = DataObserver(atoms)
+        relax.attach(obs)
+        try:
+            relax.run(fmax = fmax, steps = steps)
+            E = atoms.get_potential_energy()
+
+            if relax.get_number_of_steps() == steps:
+                note = 'Not converged in %i steps' % steps
+        except Exception:
+            traceback.print_exc()
+            note = 'An exception occurred'
+            E = np.nan
+
+        nsteps = relax.get_number_of_steps()
+        if hasattr(relax, 'force_calls'):
+            fc = relax.force_calls
+            if rank == 0:
+                print '%-15s %-15s %3i %8.3f (%3i) %s' % (name, optimizer, nsteps, E, fc, note)
+        else:
+            fc = nsteps
+            if rank == 0:
+                print '%-15s %-15s %3i %8.3f       %s' % (name, optimizer, nsteps, E, note)
+
+        plotter.plot(optimizer, obs.get_E(), obs.get_fmax())
+        csvwriter.write(optimizer, nsteps, E, fc, note)
+
+    plotter.save()
+    csvwriter.finalize()
+
+class Plotter:
+    def __init__(self, name, fmax):
+        self.name = name
+        self.fmax = fmax
+        if rank == 0: 
+            self.fig = pl.figure(figsize=[12.0, 9.0])
+            self.axes0 = self.fig.add_subplot(2, 1, 1)
+            self.axes1 = self.fig.add_subplot(2, 1, 2)
+
+    def plot(self, optimizer, E, fmax):
+        if rank == 0:
+            self.axes0.plot(E, label = optimizer)
+            self.axes1.plot(fmax)
+
+    def save(self, format='png'):
+        if rank == 0:
+            self.axes0.legend()
+            self.axes0.set_title(self.name)
+            self.axes0.set_ylabel('E [eV]')
+            #self.axes0.set_yscale('log')
+
+            self.axes1.set_xlabel('steps')
+            self.axes1.set_ylabel('fmax [eV/A]')
+            self.axes1.set_yscale('log')
+            self.axes1.axhline(self.fmax, color='k', linestyle='--')
+            self.fig.savefig(self.name + '.' + format)
+
+class CSVWriter:
+    def __init__(self, name):
+        self.f = paropen(name + '.csv', 'w')
+
+    def write(self, optimizer, nsteps, E, fc, note=''):
+        self.f.write(
+            '%s,%i,%i,%f,%s\n' % (optimizer, nsteps, fc, E, note)
+        )
+
+    def finalize(self):
+        self.f.close()
+
+class DataObserver:
+    def __init__(self, atoms):
+        self.atoms = atoms
+        self.E = []
+        self.fmax = []
+
+    def __call__(self):
+        self.E.append(self.atoms.get_potential_energy())
+        self.fmax.append(np.sqrt((self.atoms.get_forces()**2).sum(axis=1)).max())
+
+    def get_E(self):
+        return np.array(self.E)
+
+    def get_fmax(self):
+        return np.array(self.fmax)
Index: ase/optimize/test/nanoparticle.py
===================================================================
--- ase/optimize/test/nanoparticle.py	(revision 0)
+++ ase/optimize/test/nanoparticle.py	(revision 1)
@@ -0,0 +1,45 @@
+#/usr/bin/env python
+#PBS -l nodes=4:ppn=8
+#PBS -l walltime=02:15:00
+
+from ase import Atom, Atoms
+from ase.io import read
+from ase.constraints import FixAtoms
+from ase.optimize.test import run_test
+from gpaw import GPAW
+from gpaw import Mixer
+from gpaw.poisson import PoissonSolver
+
+name = 'nanoparticle'
+
+def get_atoms():
+    atoms = Atoms([
+        Atom('Pd', [5.078689759346383, 5.410678028467162, 4.000000000000000]),
+        Atom('Pd', [7.522055777772603, 4.000000000000000, 4.000000000000000]),
+        Atom('Pd', [7.522055777772603, 6.821356056934325, 4.000000000000000]),
+        Atom('Pd', [6.707600438297196, 5.410678028467162, 6.303627574066606]),
+        Atom('N',  [4.807604264052752, 5.728625577716107, 5.919407072553396]),
+        Atom('H',  [4.000000000000000, 5.965167390141987, 6.490469524180266]),
+    ])
+
+    constraint = FixAtoms(mask=[a.symbol == 'Pd' for a in atoms])
+    atoms.set_constraint(constraint)
+    atoms.center(vacuum=4.0)
+    atoms.set_pbc(False)
+    return atoms
+
+def get_calculator():
+    calc = GPAW(gpts=(64, 64, 64), #h=0.18, gives 64x60x60
+                mode='lcao', 
+                basis='szp(dzp)',
+                nbands=-5,
+                xc='LDA',
+                width=0.1,
+                mixer=Mixer(beta=0.1, nmaxold=5, weight=50.0),
+                poissonsolver=PoissonSolver(nn='M', relax='GS'),
+                convergence={'energy': 1e-4, 'bands': -3},
+                stencils=(3, 3),
+                txt='nanoparticle.txt')
+    return calc
+
+run_test(get_atoms, get_calculator, name, fmax=0.05, steps=200)
Index: ase/optimize/test/test.py
===================================================================
--- ase/optimize/test/test.py	(revision 0)
+++ ase/optimize/test/test.py	(revision 1)
@@ -0,0 +1,17 @@
+import os
+import ase
+
+tests = [
+    'N2Cu_relax.py',
+    'Cu_bulk.py',
+    'CO_Au111.py',
+    'H2.py',
+    'C5H12.py',
+    'nanoparticle.py', # SLOW
+#    'C2_Cu100.py', # Extremely slow
+]
+
+for test in tests:
+    filename = ase.__path__[0] + '/optimize/test/' + test
+    execfile(filename, {})
+
Index: ase/optimize/test/C5H12.py
===================================================================
--- ase/optimize/test/C5H12.py	(revision 0)
+++ ase/optimize/test/C5H12.py	(revision 1)
@@ -0,0 +1,50 @@
+#/usr/bin/env python
+#PBS -l nodes=4:ppn=8
+#PBS -l walltime=13:00:00
+from ase import Atoms
+from ase.optimize.test import run_test
+from gpaw import GPAW
+from gpaw import Mixer
+from gpaw.poisson import PoissonSolver
+
+name = 'C5H12'
+
+def get_atoms():
+    atoms = Atoms(symbols='C5H12',
+                  pbc=[False, False, False],
+                  cell=[
+                      [ 16.83752497,   0.        ,   0.        ],
+                      [  0.        ,  12.18645905,   0.        ],
+                      [  0.        ,   0.        ,  11.83462179]
+                  ],
+                  positions=[
+                      [  5.90380523,   5.65545388,   5.91569796],
+                      [  7.15617518,   6.52907738,   5.91569796],
+                      [  8.41815022,   5.66384716,   5.92196554],
+                      [  9.68108996,   6.52891016,   5.91022362],
+                      [ 10.93006206,   5.65545388,   5.91569796],
+                      [  5.00000011,   6.30002353,   5.9163716 ],
+                      [  5.88571848,   5.0122839 ,   6.82246859],
+                      [  5.88625613,   5.01308931,   5.01214155],
+                      [  7.14329342,   7.18115393,   6.81640316],
+                      [  7.14551332,   7.17200869,   5.00879027],
+                      [  8.41609966,   5.00661165,   5.02355167],
+                      [  8.41971183,   5.0251482 ,   6.83462168],
+                      [  9.69568096,   7.18645894,   6.8078633 ],
+                      [  9.68914668,   7.16663649,   5.00000011],
+                      [ 10.95518898,   5.02163182,   6.8289018 ],
+                      [ 11.83752486,   6.29836826,   5.90274952],
+                      [ 10.94464142,   5.00000011,   5.01802495]
+                  ])
+    return atoms
+
+def get_calculator():
+    calc = GPAW(h=0.2,
+                mode = 'lcao',
+                basis = 'szp(dzp)',
+                mixer=Mixer(beta=0.1, nmaxold=5, weight=50.0),
+                poissonsolver=PoissonSolver(nn='M', relax='GS'),
+                txt='C5H12.txt')
+    return calc
+
+run_test(get_atoms, get_calculator, name + '-gpaw')
Index: ase/optimize/test/N2Cu_relax.py
===================================================================
--- ase/optimize/test/N2Cu_relax.py	(revision 0)
+++ ase/optimize/test/N2Cu_relax.py	(revision 1)
@@ -0,0 +1,80 @@
+from ase import Atoms, Atom
+from ase.calculators.emt import EMT
+from ase.constraints import FixAtoms
+from ase.optimize.test import run_test
+
+name = 'N2Cu'
+
+def get_atoms_surf():
+    a = 2.70
+    c = 1.59 * a
+    h = 1.85
+    d = 1.10
+
+    slab = Atoms('2Cu', [(0., 0., 0.), (1/3., 1/3., -0.5*c)], 
+                 tags=(0, 1),
+                 pbc=(1, 1, 0))
+    slab.set_cell([(a, 0, 0),
+                   (a / 2, 3**0.5 * a / 2, 0),
+                   (0, 0, 1)])
+    slab = slab.repeat((4, 4, 1))
+    mask = [a.tag == 1 for a in slab]
+    slab.set_constraint(FixAtoms(mask=mask))
+    return slab
+
+def get_atoms_adsorbate():
+    # We need the relaxed slab here! 
+    slab = Atoms([
+        Atom('Cu', [     -1.028468159509163,     -0.432387156877267,     -0.202086055768265]),
+        Atom('Cu', [      0.333333333333333,      0.333333333333333,     -2.146500000000000]),
+        Atom('Cu', [      1.671531840490805,     -0.432387156877287,     -0.202086055768242]),
+        Atom('Cu', [      3.033333333333334,      0.333333333333333,     -2.146500000000000]),
+        Atom('Cu', [      4.371531840490810,     -0.432387156877236,     -0.202086055768261]),
+        Atom('Cu', [      5.733333333333333,      0.333333333333333,     -2.146500000000000]),
+        Atom('Cu', [      7.071531840490944,     -0.432387156877258,     -0.202086055768294]),
+        Atom('Cu', [      8.433333333333335,      0.333333333333333,     -2.146500000000000]),
+        Atom('Cu', [      0.321531840490810,      1.905881433340708,     -0.202086055768213]),
+        Atom('Cu', [      1.683333333333333,      2.671601923551318,     -2.146500000000000]),
+        Atom('Cu', [      3.021531840490771,      1.905881433340728,     -0.202086055768250]),
+        Atom('Cu', [      4.383333333333334,      2.671601923551318,     -2.146500000000000]),
+        Atom('Cu', [      5.721531840490857,      1.905881433340735,     -0.202086055768267]),
+        Atom('Cu', [      7.083333333333333,      2.671601923551318,     -2.146500000000000]),
+        Atom('Cu', [      8.421531840490820,      1.905881433340739,     -0.202086055768265]),
+        Atom('Cu', [      9.783333333333335,      2.671601923551318,     -2.146500000000000]),
+        Atom('Cu', [      1.671531840490742,      4.244150023558601,     -0.202086055768165]),
+        Atom('Cu', [      3.033333333333334,      5.009870513769302,     -2.146500000000000]),
+        Atom('Cu', [      4.371531840490840,      4.244150023558694,     -0.202086055768265]),
+        Atom('Cu', [      5.733333333333333,      5.009870513769302,     -2.146500000000000]),
+        Atom('Cu', [      7.071531840490880,      4.244150023558786,     -0.202086055768352]),
+        Atom('Cu', [      8.433333333333335,      5.009870513769302,     -2.146500000000000]),
+        Atom('Cu', [      9.771531840491031,      4.244150023558828,     -0.202086055768371]),
+        Atom('Cu', [     11.133333333333335,      5.009870513769302,     -2.146500000000000]),
+        Atom('Cu', [      3.021531840490714,      6.582418613776583,     -0.202086055768197]),
+        Atom('Cu', [      4.383333333333334,      7.348139103987287,     -2.146500000000000]),
+        Atom('Cu', [      5.721531840490814,      6.582418613776629,     -0.202086055768203]),
+        Atom('Cu', [      7.083333333333333,      7.348139103987287,     -2.146500000000000]),
+        Atom('Cu', [      8.421531840490985,      6.582418613776876,     -0.202086055768357]),
+        Atom('Cu', [      9.783333333333335,      7.348139103987287,     -2.146500000000000]),
+        Atom('Cu', [     11.121531840490929,      6.582418613776676,     -0.202086055768221]),
+        Atom('Cu', [     12.483333333333334,      7.348139103987287,     -2.146500000000000]),
+    ])
+    mask = [a.position[2] < -1 for a in slab]
+    slab.set_constraint(FixAtoms(mask=mask))
+
+    a = 2.70
+    c = 1.59 * a
+    h = 1.85
+    d = 1.10
+    x = slab.positions[0, 2] / (c / 2) * 100
+
+    molecule = Atoms('2N', positions=[(0., 0., h),
+                                      (0., 0., h + d)])
+    molecule.set_calculator(EMT())
+    slab.extend(molecule)
+    return slab
+
+def get_calculator():
+    return EMT()
+
+run_test(get_atoms_surf, get_calculator, name + '-surf', steps=200)
+run_test(get_atoms_adsorbate, get_calculator, name + '-N2', steps=200)
Index: ase/optimize/test/generate_rst.py
===================================================================
--- ase/optimize/test/generate_rst.py	(revision 0)
+++ ase/optimize/test/generate_rst.py	(revision 1)
@@ -0,0 +1,45 @@
+import os
+import re
+dirlist = os.listdir('.')
+name = '.*\.csv'
+filterre = re.compile(name)
+dirlist = filter(filterre.search, dirlist)
+namelist = [d.strip('.csv') for d in dirlist]
+
+f = open('testoptimize.rst', 'w')
+f.write(
+""".. _optimizer_tests:
+
+===============
+Optimizer tests
+===============
+This page shows benchmarks of optimizations done with our different optimizers.
+Note that the iteration number (steps) is not the same as the number of force
+evaluations. This is because some of the optimizers uses internal line searches
+or similar.
+"""
+)
+
+for name in namelist:
+    lines = open(name + '.csv', 'r').read().split('\n')
+    firstline = lines.pop(0)
+    f.write(
+        '\n' + 
+        name + '\n' + \
+        '=' * len(name) + '\n'
+        'Calculator used: %s\n' % firstline.split(',')[-1] + \
+        '\n' + \
+        '=============== ===== ================= ========== ===============\n' + \
+        'Optimizer       Steps Force evaluations Energy     Note           \n' + \
+        '=============== ===== ================= ========== ===============\n'
+    )
+    for line in lines:
+        if len(line):
+            print line.split(',')
+            f.write(
+                '%-15s %5s %17s %10s %s\n' % tuple(line.split(','))
+            )
+    f.write(
+        '=============== ===== ================= ========== ===============\n'
+    )
+f.close()
Index: ase/optimize/fmin_bfgs.py
===================================================================
--- ase/optimize/fmin_bfgs.py	(revision 0)
+++ ase/optimize/fmin_bfgs.py	(revision 1)
@@ -0,0 +1,477 @@
+#__docformat__ = "restructuredtext en"
+# ******NOTICE***************
+# optimize.py module by Travis E. Oliphant
+#
+# You may copy and use this module as you see fit with no
+# guarantee implied provided you keep this notice in all copies.
+# *****END NOTICE************
+
+import numpy
+from numpy import atleast_1d, eye, mgrid, argmin, zeros, shape, empty, \
+     squeeze, vectorize, asarray, absolute, sqrt, Inf, asfarray, isinf
+from ase.utils.linesearch import LineSearch
+
+# These have been copied from Numeric's MLab.py
+# I don't think they made the transition to scipy_core
+
+# Copied and modified from scipy_optimize
+abs = absolute
+import __builtin__
+pymin = __builtin__.min
+pymax = __builtin__.max
+__version__="0.7"
+_epsilon = sqrt(numpy.finfo(float).eps)
+
+def fmin_bfgs(f, x0, fprime=None, args=(), gtol=1e-5, norm=Inf,
+              epsilon=_epsilon, maxiter=None, full_output=0, disp=1,
+              retall=0, callback=None, maxstep=0.2):
+    """Minimize a function using the BFGS algorithm.
+
+    Parameters:
+
+      f : callable f(x,*args)
+          Objective function to be minimized.
+      x0 : ndarray
+          Initial guess.
+      fprime : callable f'(x,*args)
+          Gradient of f.
+      args : tuple
+          Extra arguments passed to f and fprime.
+      gtol : float
+          Gradient norm must be less than gtol before succesful termination.
+      norm : float
+          Order of norm (Inf is max, -Inf is min)
+      epsilon : int or ndarray
+          If fprime is approximated, use this value for the step size.
+      callback : callable
+          An optional user-supplied function to call after each
+          iteration.  Called as callback(xk), where xk is the
+          current parameter vector.
+
+    Returns: (xopt, {fopt, gopt, Hopt, func_calls, grad_calls, warnflag}, <allvecs>)
+
+        xopt : ndarray
+            Parameters which minimize f, i.e. f(xopt) == fopt.
+        fopt : float
+            Minimum value.
+        gopt : ndarray
+            Value of gradient at minimum, f'(xopt), which should be near 0.
+        Bopt : ndarray
+            Value of 1/f''(xopt), i.e. the inverse hessian matrix.
+        func_calls : int
+            Number of function_calls made.
+        grad_calls : int
+            Number of gradient calls made.
+        warnflag : integer
+            1 : Maximum number of iterations exceeded.
+            2 : Gradient and/or function calls not changing.
+        allvecs  :  list
+            Results at each iteration.  Only returned if retall is True.
+
+    *Other Parameters*:
+        maxiter : int
+            Maximum number of iterations to perform.
+        full_output : bool
+            If True,return fopt, func_calls, grad_calls, and warnflag
+            in addition to xopt.
+        disp : bool
+            Print convergence message if True.
+        retall : bool
+            Return a list of results at each iteration if True.
+
+    Notes:
+
+        Optimize the function, f, whose gradient is given by fprime
+        using the quasi-Newton method of Broyden, Fletcher, Goldfarb,
+        and Shanno (BFGS) See Wright, and Nocedal 'Numerical
+        Optimization', 1999, pg. 198.
+
+    *See Also*:
+
+      scikits.openopt : SciKit which offers a unified syntax to call
+                        this and other solvers.
+
+    """
+    x0 = asarray(x0).squeeze()
+    if x0.ndim == 0:
+        x0.shape = (1,)
+    if maxiter is None:
+        maxiter = len(x0)*200
+    func_calls, f = wrap_function(f, args)
+    if fprime is None:
+        grad_calls, myfprime = wrap_function(approx_fprime, (f, epsilon))
+    else:
+        grad_calls, myfprime = wrap_function(fprime, args)
+    gfk = myfprime(x0)
+    k = 0
+    N = len(x0)
+    I = numpy.eye(N,dtype=int)
+    Hk = I
+    old_fval = f(x0)
+    old_old_fval = old_fval + 5000
+    xk = x0
+    if retall:
+        allvecs = [x0]
+    sk = [2*gtol]
+    warnflag = 0
+    gnorm = vecnorm(gfk,ord=norm)
+    while (gnorm > gtol) and (k < maxiter):
+        pk = -numpy.dot(Hk,gfk)
+        ls = LineSearch()
+        alpha_k, fc, gc, old_fval, old_old_fval, gfkp1 = \
+           ls._line_search(f,myfprime,xk,pk,gfk,
+                                  old_fval,old_old_fval,maxstep=maxstep)
+        if alpha_k is None:  # line search failed try different one.
+            alpha_k, fc, gc, old_fval, old_old_fval, gfkp1 = \
+                     line_search(f,myfprime,xk,pk,gfk,
+                                 old_fval,old_old_fval)
+            if alpha_k is None:
+                # This line search also failed to find a better solution.
+                warnflag = 2
+                break
+        xkp1 = xk + alpha_k * pk
+        if retall:
+            allvecs.append(xkp1)
+        sk = xkp1 - xk
+        xk = xkp1
+        if gfkp1 is None:
+            gfkp1 = myfprime(xkp1)
+
+        yk = gfkp1 - gfk
+        gfk = gfkp1
+        if callback is not None:
+            callback(xk)
+        k += 1
+        gnorm = vecnorm(gfk,ord=norm)
+        if (gnorm <= gtol):
+            break
+
+        try: # this was handled in numeric, let it remaines for more safety
+            rhok = 1.0 / (numpy.dot(yk,sk))
+        except ZeroDivisionError:
+            rhok = 1000.0
+            print "Divide-by-zero encountered: rhok assumed large"
+        if isinf(rhok): # this is patch for numpy
+            rhok = 1000.0
+            print "Divide-by-zero encountered: rhok assumed large"
+        A1 = I - sk[:,numpy.newaxis] * yk[numpy.newaxis,:] * rhok
+        A2 = I - yk[:,numpy.newaxis] * sk[numpy.newaxis,:] * rhok
+        Hk = numpy.dot(A1,numpy.dot(Hk,A2)) + rhok * sk[:,numpy.newaxis] \
+                 * sk[numpy.newaxis,:]
+
+    if disp or full_output:
+        fval = old_fval
+    if warnflag == 2:
+        if disp:
+            print "Warning: Desired error not necessarily achieved" \
+                  "due to precision loss"
+            print "         Current function value: %f" % fval
+            print "         Iterations: %d" % k
+            print "         Function evaluations: %d" % func_calls[0]
+            print "         Gradient evaluations: %d" % grad_calls[0]
+
+    elif k >= maxiter:
+        warnflag = 1
+        if disp:
+            print "Warning: Maximum number of iterations has been exceeded"
+            print "         Current function value: %f" % fval
+            print "         Iterations: %d" % k
+            print "         Function evaluations: %d" % func_calls[0]
+            print "         Gradient evaluations: %d" % grad_calls[0]
+    else:
+        if disp:
+            print "Optimization terminated successfully."
+            print "         Current function value: %f" % fval
+            print "         Iterations: %d" % k
+            print "         Function evaluations: %d" % func_calls[0]
+            print "         Gradient evaluations: %d" % grad_calls[0]
+
+    if full_output:
+        retlist = xk, fval, gfk, Hk, func_calls[0], grad_calls[0], warnflag
+        if retall:
+            retlist += (allvecs,)
+    else:
+        retlist = xk
+        if retall:
+            retlist = (xk, allvecs)
+
+    return retlist
+
+def vecnorm(x, ord=2):
+    if ord == Inf:
+        return numpy.amax(abs(x))
+    elif ord == -Inf:
+        return numpy.amin(abs(x))
+    else:
+        return numpy.sum(abs(x)**ord,axis=0)**(1.0/ord)
+
+def wrap_function(function, args):
+    ncalls = [0]
+    def function_wrapper(x):
+        ncalls[0] += 1
+        return function(x, *args)
+    return ncalls, function_wrapper
+
+def _cubicmin(a,fa,fpa,b,fb,c,fc):
+    # finds the minimizer for a cubic polynomial that goes through the
+    #  points (a,fa), (b,fb), and (c,fc) with derivative at a of fpa.
+    #
+    # if no minimizer can be found return None
+    #
+    # f(x) = A *(x-a)^3 + B*(x-a)^2 + C*(x-a) + D
+
+    C = fpa
+    D = fa
+    db = b-a
+    dc = c-a
+    if (db == 0) or (dc == 0) or (b==c): return None
+    denom = (db*dc)**2 * (db-dc)
+    d1 = empty((2,2))
+    d1[0,0] = dc**2
+    d1[0,1] = -db**2
+    d1[1,0] = -dc**3
+    d1[1,1] = db**3
+    [A,B] = numpy.dot(d1,asarray([fb-fa-C*db,fc-fa-C*dc]).flatten())
+    A /= denom
+    B /= denom
+    radical = B*B-3*A*C
+    if radical < 0:  return None
+    if (A == 0): return None
+    xmin = a + (-B + sqrt(radical))/(3*A)
+    return xmin
+
+def _quadmin(a,fa,fpa,b,fb):
+    # finds the minimizer for a quadratic polynomial that goes through
+    #  the points (a,fa), (b,fb) with derivative at a of fpa
+    # f(x) = B*(x-a)^2 + C*(x-a) + D
+    D = fa
+    C = fpa
+    db = b-a*1.0
+    if (db==0): return None
+    B = (fb-D-C*db)/(db*db)
+    if (B <= 0): return None
+    xmin = a  - C / (2.0*B)
+    return xmin
+
+def zoom(a_lo, a_hi, phi_lo, phi_hi, derphi_lo,
+         phi, derphi, phi0, derphi0, c1, c2):
+    maxiter = 10
+    i = 0
+    delta1 = 0.2  # cubic interpolant check
+    delta2 = 0.1  # quadratic interpolant check
+    phi_rec = phi0
+    a_rec = 0
+    while 1:
+        # interpolate to find a trial step length between a_lo and a_hi
+        # Need to choose interpolation here.  Use cubic interpolation and then if the
+        #  result is within delta * dalpha or outside of the interval bounded by a_lo or a_hi
+        #  then use quadratic interpolation, if the result is still too close, then use bisection
+
+        dalpha = a_hi-a_lo;
+        if dalpha < 0: a,b = a_hi,a_lo
+        else: a,b = a_lo, a_hi
+
+        # minimizer of cubic interpolant
+        #    (uses phi_lo, derphi_lo, phi_hi, and the most recent value of phi)
+        #      if the result is too close to the end points (or out of the interval)
+        #         then use quadratic interpolation with phi_lo, derphi_lo and phi_hi
+        #      if the result is stil too close to the end points (or out of the interval)
+        #         then use bisection
+
+        if (i > 0):
+            cchk = delta1*dalpha
+            a_j = _cubicmin(a_lo, phi_lo, derphi_lo, a_hi, phi_hi, a_rec, phi_rec)
+        if (i==0) or (a_j is None) or (a_j > b-cchk) or (a_j < a+cchk):
+            qchk = delta2*dalpha
+            a_j = _quadmin(a_lo, phi_lo, derphi_lo, a_hi, phi_hi)
+            if (a_j is None) or (a_j > b-qchk) or (a_j < a+qchk):
+                a_j = a_lo + 0.5*dalpha
+#                print "Using bisection."
+#            else: print "Using quadratic."
+#        else: print "Using cubic."
+
+        # Check new value of a_j
+
+        phi_aj = phi(a_j)
+        if (phi_aj > phi0 + c1*a_j*derphi0) or (phi_aj >= phi_lo):
+            phi_rec = phi_hi
+            a_rec = a_hi
+            a_hi = a_j
+            phi_hi = phi_aj
+        else:
+            derphi_aj = derphi(a_j)
+            if abs(derphi_aj) <= -c2*derphi0:
+                a_star = a_j
+                val_star = phi_aj
+                valprime_star = derphi_aj
+                break
+            if derphi_aj*(a_hi - a_lo) >= 0:
+                phi_rec = phi_hi
+                a_rec = a_hi
+                a_hi = a_lo
+                phi_hi = phi_lo
+            else:
+                phi_rec = phi_lo
+                a_rec = a_lo
+            a_lo = a_j
+            phi_lo = phi_aj
+            derphi_lo = derphi_aj
+        i += 1
+        if (i > maxiter):
+            a_star = a_j
+            val_star = phi_aj
+            valprime_star = None
+            break
+    return a_star, val_star, valprime_star
+
+def line_search(f, myfprime, xk, pk, gfk, old_fval, old_old_fval,
+                args=(), c1=1e-4, c2=0.9, amax=50):
+    """Find alpha that satisfies strong Wolfe conditions.
+
+    Parameters:
+
+        f : callable f(x,*args)
+            Objective function.
+        myfprime : callable f'(x,*args)
+            Objective function gradient (can be None).
+        xk : ndarray
+            Starting point.
+        pk : ndarray
+            Search direction.
+        gfk : ndarray
+            Gradient value for x=xk (xk being the current parameter
+            estimate).
+        args : tuple
+            Additional arguments passed to objective function.
+        c1 : float
+            Parameter for Armijo condition rule.
+        c2 : float
+            Parameter for curvature condition rule.
+
+    Returns:
+
+        alpha0 : float
+            Alpha for which ``x_new = x0 + alpha * pk``.
+        fc : int
+            Number of function evaluations made.
+        gc : int
+            Number of gradient evaluations made.
+
+    Notes:
+
+        Uses the line search algorithm to enforce strong Wolfe
+        conditions.  See Wright and Nocedal, 'Numerical Optimization',
+        1999, pg. 59-60.
+
+        For the zoom phase it uses an algorithm by [...].
+
+    """
+
+    global _ls_fc, _ls_gc, _ls_ingfk
+    _ls_fc = 0
+    _ls_gc = 0
+    _ls_ingfk = None
+    def phi(alpha):
+        global _ls_fc
+        _ls_fc += 1
+        return f(xk+alpha*pk,*args)
+
+    if isinstance(myfprime,type(())):
+        def phiprime(alpha):
+            global _ls_fc, _ls_ingfk
+            _ls_fc += len(xk)+1
+            eps = myfprime[1]
+            fprime = myfprime[0]
+            newargs = (f,eps) + args
+            _ls_ingfk = fprime(xk+alpha*pk,*newargs)  # store for later use
+            return numpy.dot(_ls_ingfk,pk)
+    else:
+        fprime = myfprime
+        def phiprime(alpha):
+            global _ls_gc, _ls_ingfk
+            _ls_gc += 1
+            _ls_ingfk = fprime(xk+alpha*pk,*args)  # store for later use
+            return numpy.dot(_ls_ingfk,pk)
+
+    alpha0 = 0
+    phi0 = old_fval
+    derphi0 = numpy.dot(gfk,pk)
+
+    alpha1 = pymin(1.0,1.01*2*(phi0-old_old_fval)/derphi0)
+
+    if alpha1 == 0:
+        # This shouldn't happen. Perhaps the increment has slipped below
+        # machine precision?  For now, set the return variables skip the
+        # useless while loop, and raise warnflag=2 due to possible imprecision.
+        alpha_star = None
+        fval_star = old_fval
+        old_fval = old_old_fval
+        fprime_star = None
+
+    phi_a1 = phi(alpha1)
+    #derphi_a1 = phiprime(alpha1)  evaluated below
+
+    phi_a0 = phi0
+    derphi_a0 = derphi0
+
+    i = 1
+    maxiter = 10
+    while 1:         # bracketing phase
+        if alpha1 == 0:
+            break
+        if (phi_a1 > phi0 + c1*alpha1*derphi0) or \
+           ((phi_a1 >= phi_a0) and (i > 1)):
+            alpha_star, fval_star, fprime_star = \
+                        zoom(alpha0, alpha1, phi_a0,
+                             phi_a1, derphi_a0, phi, phiprime,
+                             phi0, derphi0, c1, c2)
+            break
+
+        derphi_a1 = phiprime(alpha1)
+        if (abs(derphi_a1) <= -c2*derphi0):
+            alpha_star = alpha1
+            fval_star = phi_a1
+            fprime_star = derphi_a1
+            break
+
+        if (derphi_a1 >= 0):
+            alpha_star, fval_star, fprime_star = \
+                        zoom(alpha1, alpha0, phi_a1,
+                             phi_a0, derphi_a1, phi, phiprime,
+                             phi0, derphi0, c1, c2)
+            break
+
+        alpha2 = 2 * alpha1   # increase by factor of two on each iteration
+        i = i + 1
+        alpha0 = alpha1
+        alpha1 = alpha2
+        phi_a0 = phi_a1
+        phi_a1 = phi(alpha1)
+        derphi_a0 = derphi_a1
+
+        # stopping test if lower function not found
+        if (i > maxiter):
+            alpha_star = alpha1
+            fval_star = phi_a1
+            fprime_star = None
+            break
+
+    if fprime_star is not None:
+        # fprime_star is a number (derphi) -- so use the most recently
+        # calculated gradient used in computing it derphi = gfk*pk
+        # this is the gradient at the next step no need to compute it
+        # again in the outer loop.
+        fprime_star = _ls_ingfk
+
+    return alpha_star, _ls_fc, _ls_gc, fval_star, old_fval, fprime_star
+
+def approx_fprime(xk,f,epsilon,*args):
+    f0 = f(*((xk,)+args))
+    grad = numpy.zeros((len(xk),), float)
+    ei = numpy.zeros((len(xk),), float)
+    for k in range(len(xk)):
+        ei[k] = epsilon
+        grad[k] = (f(*((xk+ei,)+args)) - f0)/epsilon
+        ei[k] = 0.0
+    return grad
+
Index: ase/optimize/basin.py
===================================================================
--- ase/optimize/basin.py	(revision 0)
+++ ase/optimize/basin.py	(revision 1)
@@ -0,0 +1,119 @@
+import numpy as np
+
+from ase.optimize.optimize import Dynamics
+from ase.optimize.fire import FIRE
+from ase.units import kB
+from ase.parallel import world
+from ase.io.trajectory import PickleTrajectory
+
+class BasinHopping(Dynamics):
+    """Basin hopping algorythm.
+
+    After Wales and Doye, J. Phys. Chem. A, vol 101 (1997) 5111-5116"""
+
+    def __init__(self, atoms,
+                 temperature=100 * kB,
+                 optimizer=FIRE,
+                 fmax=0.1,
+                 dr=.1,
+                 logfile='-', 
+                 trajectory='lowest.traj',
+                 optimizer_logfile='-',
+                 local_minima_trajectory='local_minima.traj',
+                 adjust_cm=True):
+        Dynamics.__init__(self, atoms, logfile, trajectory)
+        self.kT = temperature
+        self.optimizer = optimizer
+        self.fmax = fmax
+        self.dr = dr
+        if adjust_cm:
+            self.cm = atoms.get_center_of_mass()
+        else:
+            self.cm = None
+
+        self.optimizer_logfile = optimizer_logfile
+        self.lm_trajectory = local_minima_trajectory
+        if isinstance(local_minima_trajectory, str):
+            self.lm_trajectory = PickleTrajectory(local_minima_trajectory,
+                                                  'w', atoms)
+
+        self.initialize()
+
+    def initialize(self):
+        self.positions = 0. * self.atoms.get_positions()
+        self.Emin = self.get_energy(self.atoms.get_positions())
+        self.rmin = self.atoms.get_positions()
+        self.positions = self.atoms.get_positions()
+        self.call_observers()
+        self.log(-1, self.Emin, self.Emin)
+                
+    def run(self, steps):
+        """Hop the basins for defined number of steps."""
+
+        ro = self.positions
+        Eo = self.get_energy(ro)
+ 
+        for step in range(steps):
+            rn = self.move(ro)
+            En = self.get_energy(rn)
+
+            if En < self.Emin:
+                # new minimum found
+                self.Emin = En
+                self.rmin = self.atoms.get_positions()
+                self.call_observers()
+                rn = self.rmin
+            self.log(step, En, self.Emin)
+
+            accept = np.exp((Eo - En) / self.kT) > np.random.uniform()
+            if accept:
+                ro = rn
+                Eo = En
+
+    def log(self, step, En, Emin):
+        if self.logfile is None:
+            return
+        name = self.__class__.__name__
+        self.logfile.write('%s: step %d, energy %15.6f, emin %15.6f\n'
+                           % (name, step, En, self.Emin))
+        self.logfile.flush()
+
+    def move(self, ro):
+        atoms = self.atoms
+        # displace coordinates
+        disp = np.random.uniform(-1., 1., (len(atoms), 3))
+        rn = ro + self.dr * disp
+        atoms.set_positions(rn)
+        if self.cm is not None:
+            cm = atoms.get_center_of_mass()
+            atoms.translate(self.cm - cm)
+        rn = atoms.get_positions()
+        if world is not None:
+            world.broadcast(rn, 0)
+        atoms.set_positions(rn)
+        return atoms.get_positions()
+
+    def get_minimum(self):
+        atoms = self.atoms.copy()
+        atoms.set_positions(self.rmin)
+        return self.Emin, atoms
+
+    def get_energy(self, positions):
+        """Return the energy of the nearest local minimum."""
+        if np.sometrue(self.positions != positions):
+            self.positions = positions
+            self.atoms.set_positions(positions)
+ 
+            try:
+                opt = self.optimizer(self.atoms, logfile=self.optimizer_logfile)
+                opt.run(fmax=self.fmax)
+                if self.lm_trajectory is not None:
+                    self.lm_trajectory.write(self.atoms)
+
+                self.energy = self.atoms.get_potential_energy()
+            except:
+                # the atoms are probably to near to each other
+                self.energy = 1.e32
+            
+        return self.energy
+       
Index: ase/optimize/lbfgs.py
===================================================================
--- ase/optimize/lbfgs.py	(revision 0)
+++ ase/optimize/lbfgs.py	(revision 1)
@@ -0,0 +1,318 @@
+# -*- coding: utf-8 -*-
+import sys
+import numpy as np
+from ase.optimize.optimize import Optimizer
+from ase.utils.linesearch import LineSearch
+
+class LBFGS(Optimizer):
+    """Limited memory BFGS optimizer.
+    
+    A limited memory version of the bfgs algorithm. Unlike the bfgs algorithm
+    used in bfgs.py, the inverse of Hessian matrix is updated.  The inverse
+    Hessian is represented only as a diagonal matrix to save memory
+
+    """
+    def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
+                 maxstep=None, memory=100, damping = 1.0, alpha = 10.0,
+                 use_line_search=False):
+        """
+        Parameters:
+
+        restart: string
+            Pickle file used to store vectors for updating the inverse of Hessian
+            matrix. If set, file with such a name will be searched and information
+            stored will be used, if the file exists.
+
+        logfile: string
+            Where should output go. None for no output, '-' for stdout.
+
+        trajectory: string
+            Pickle file used to store trajectory of atomic movement.
+
+        maxstep: float
+            How far is a single atom allowed to move. This is useful for DFT
+            calculations where wavefunctions can be reused if steps are small.
+            Default is 0.04 Angstrom.
+
+        memory: int
+            Number of steps to be stored. Default value is 100. Three numpy
+            arrays of this length containing floats are stored.
+
+        damping: float
+            The calculated step is multiplied with this number before added to
+            the positions. 
+
+        alpha: float
+            Initial guess for the Hessian (curvature of energy surface). A
+            conservative value of 70.0 is the default, but number of needed
+            steps to converge might be less if a lower value is used. However,
+            a lower value also means risk of instability.
+            
+        """
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+
+        if maxstep is not None:
+            if maxstep > 1.0:
+                raise ValueError('You are using a much too large value for ' +
+                                 'the maximum step size: %.1f Angstrom' % maxstep)
+            self.maxstep = maxstep
+        else:
+            self.maxstep = 0.04
+
+        self.memory = memory
+        self.H0 = 1. / alpha  # Initial approximation of inverse Hessian
+                            # 1./70. is to emulate the behaviour of BFGS
+                            # Note that this is never changed!
+        self.damping = damping
+        self.use_line_search = use_line_search
+        self.p = None
+        self.function_calls = 0
+        self.force_calls = 0
+
+    def initialize(self):
+        """Initalize everything so no checks have to be done in step"""
+        self.iteration = 0
+        self.s = []
+        self.y = []
+        self.rho = [] # Store also rho, to avoid calculationg the dot product
+                      # again and again
+
+        self.r0 = None
+        self.f0 = None
+        self.e0 = None
+        self.task = 'START'
+        self.load_restart = False
+
+    def read(self):
+        """Load saved arrays to reconstruct the Hessian"""
+        self.iteration, self.s, self.y, self.rho, \
+        self.r0, self.f0, self.e0, self.task = self.load()
+        self.load_restart = True
+
+    def step(self, f):
+        """Take a single step
+        
+        Use the given forces, update the history and calculate the next step --
+        then take it"""
+        r = self.atoms.get_positions()
+        p0 = self.p
+    
+        self.update(r, f, self.r0, self.f0)
+        
+        s = self.s
+        y = self.y
+        rho = self.rho
+        H0 = self.H0
+
+        loopmax = np.min([self.memory, self.iteration])
+        a = np.empty((loopmax,), dtype=np.float64)
+
+        ### The algorithm itself:
+        q = - f.reshape(-1) 
+        for i in range(loopmax - 1, -1, -1):
+            a[i] = rho[i] * np.dot(s[i], q)
+            q -= a[i] * y[i]
+        z = H0 * q
+        
+        for i in range(loopmax):
+            b = rho[i] * np.dot(y[i], z)
+            z += s[i] * (a[i] - b)
+
+        self.p = - z.reshape((-1, 3))
+        ###
+ 
+        g = -f
+        if self.use_line_search == True:
+            e = self.func(r)
+            self.line_search(r, g, e)
+            dr = (self.alpha_k * self.p).reshape(len(self.atoms),-1)
+        else:
+            self.force_calls += 1
+            self.function_calls += 1
+            dr = self.determine_step(self.p) * self.damping
+        self.atoms.set_positions(r+dr)
+        
+        self.iteration += 1
+        self.r0 = r
+        self.f0 = -g
+        self.dump((self.iteration, self.s, self.y, 
+                   self.rho, self.r0, self.f0, self.e0, self.task))
+
+    def determine_step(self, dr):
+        """Determine step to take according to maxstep
+        
+        Normalize all steps as the largest step. This way
+        we still move along the eigendirection.
+        """
+        steplengths = (dr**2).sum(1)**0.5
+        longest_step = np.max(steplengths)
+        if longest_step >= self.maxstep:
+            dr *= self.maxstep / longest_step
+        
+        return dr
+
+    def update(self, r, f, r0, f0):
+        """Update everything that is kept in memory
+
+        This function is mostly here to allow for replay_trajectory.
+        """
+        if self.iteration > 0:
+            s0 = r.reshape(-1) - r0.reshape(-1)
+            self.s.append(s0)
+
+            # We use the gradient which is minus the force!
+            y0 = f0.reshape(-1) - f.reshape(-1)
+            self.y.append(y0)
+            
+            rho0 = 1.0 / np.dot(y0, s0)
+            self.rho.append(rho0)
+
+        if self.iteration > self.memory:
+            self.s.pop(0)
+            self.y.pop(0)
+            self.rho.pop(0)
+
+
+    def replay_trajectory(self, traj):
+        """Initialize history from old trajectory."""
+        if isinstance(traj, str):
+            from ase.io.trajectory import PickleTrajectory
+            traj = PickleTrajectory(traj, 'r')
+        r0 = None
+        f0 = None
+        # The last element is not added, as we get that for free when taking
+        # the first qn-step after the replay
+        for i in range(0, len(traj) - 1):
+            r = traj[i].get_positions()
+            f = traj[i].get_forces()
+            self.update(r, f, r0, f0)
+            r0 = r.copy()
+            f0 = f.copy()
+            self.iteration += 1
+        self.r0 = r0
+        self.f0 = f0
+
+    def func(self, x):
+        """Objective function for use of the optimizers"""
+        self.atoms.set_positions(x.reshape(-1, 3))
+        self.function_calls += 1
+        return self.atoms.get_potential_energy()
+
+    def fprime(self, x):
+        """Gradient of the objective function for use of the optimizers"""
+        self.atoms.set_positions(x.reshape(-1, 3))
+        self.force_calls += 1
+        # Remember that forces are minus the gradient!
+        return - self.atoms.get_forces().reshape(-1)
+
+    def line_search(self, r, g, e):
+        self.p = self.p.ravel()
+        p_size = np.sqrt((self.p **2).sum())
+        if p_size <= np.sqrt(len(self.atoms) * 1e-10):
+            self.p /= (p_size / np.sqrt(len(self.atoms)*1e-10))
+        g = g.ravel()
+        r = r.ravel()
+        ls = LineSearch()
+        self.alpha_k, e, self.e0, self.no_update = \
+           ls._line_search(self.func, self.fprime, r, self.p, g, e, self.e0,
+                           maxstep=self.maxstep, c1=.23,
+                           c2=.46, stpmax=50.)
+
+class LBFGSLineSearch(LBFGS):
+    """This optimizer uses the LBFGS algorithm, but does a line search that fulfills
+    the Wolff conditions.
+    """
+
+    def __init__(self, *args, **kwargs):
+        kwargs['use_line_search'] = True
+        LBFGS.__init__(self, *args, **kwargs)
+
+#    """Modified version of LBFGS.
+#
+#    This optimizer uses the LBFGS algorithm, but does a line search for the
+#    minimum along the search direction. This is done by issuing an additional
+#    force call for each step, thus doubling the number of calculations.
+#
+#    Additionally the Hessian is reset if the new guess is not sufficiently
+#    better than the old one.
+#    """
+#    def __init__(self, *args, **kwargs):
+#        self.dR = kwargs.pop('dR', 0.1)         
+#        LBFGS.__init__(self, *args, **kwargs)
+#
+#    def update(self, r, f, r0, f0):
+#        """Update everything that is kept in memory
+#
+#        This function is mostly here to allow for replay_trajectory.
+#        """
+#        if self.iteration > 0:
+#            a1 = abs(np.dot(f.reshape(-1), f0.reshape(-1)))
+#            a2 = np.dot(f0.reshape(-1), f0.reshape(-1))
+#            if not (a1 <= 0.5 * a2 and a2 != 0):
+#                # Reset optimization
+#                self.initialize()
+#
+#        # Note that the reset above will set self.iteration to 0 again
+#        # which is why we should check again
+#        if self.iteration > 0:
+#            s0 = r.reshape(-1) - r0.reshape(-1)
+#            self.s.append(s0)
+#
+#            # We use the gradient which is minus the force!
+#            y0 = f0.reshape(-1) - f.reshape(-1)
+#            self.y.append(y0)
+#            
+#            rho0 = 1.0 / np.dot(y0, s0)
+#            self.rho.append(rho0)
+#
+#        if self.iteration > self.memory:
+#            self.s.pop(0)
+#            self.y.pop(0)
+#            self.rho.pop(0)
+#
+#    def determine_step(self, dr):
+#        f = self.atoms.get_forces()
+#        
+#        # Unit-vector along the search direction
+#        du = dr / np.sqrt(np.dot(dr.reshape(-1), dr.reshape(-1)))
+#
+#        # We keep the old step determination before we figure 
+#        # out what is the best to do.
+#        maxstep = self.maxstep * np.sqrt(3 * len(self.atoms))
+#
+#        # Finite difference step using temporary point
+#        self.atoms.positions += (du * self.dR)
+#        # Decide how much to move along the line du
+#        Fp1 = np.dot(f.reshape(-1), du.reshape(-1))
+#        Fp2 = np.dot(self.atoms.get_forces().reshape(-1), du.reshape(-1))
+#        CR = (Fp1 - Fp2) / self.dR
+#        #RdR = Fp1*0.1
+#        if CR < 0.0:
+#            #print "negcurve"
+#            RdR = maxstep
+#            #if(abs(RdR) > maxstep):
+#            #    RdR = self.sign(RdR) * maxstep
+#        else:
+#            Fp = (Fp1 + Fp2) * 0.5
+#            RdR = Fp / CR 
+#            if abs(RdR) > maxstep:
+#                RdR = np.sign(RdR) * maxstep
+#            else:
+#                RdR += self.dR * 0.5
+#        return du * RdR
+
+class HessLBFGS(LBFGS):
+    """Backwards compatibiliyt class"""
+    def __init__(self, *args, **kwargs):
+        if 'method' in kwargs:
+            del kwargs['method']
+        sys.stderr.write('Please use LBFGS instead of HessLBFGS!')
+        LBFGS.__init__(self, *args, **kwargs)
+
+class LineLBFGS(LBFGSLineSearch):
+    """Backwards compatibiliyt class"""
+    def __init__(self, *args, **kwargs):
+        if 'method' in kwargs:
+            del kwargs['method']
+        sys.stderr.write('Please use LBFGSLineSearch instead of LineLBFGS!')
+        LBFGSLineSearch.__init__(self, *args, **kwargs)
Index: ase/optimize/oldqn.py
===================================================================
--- ase/optimize/oldqn.py	(revision 0)
+++ ase/optimize/oldqn.py	(revision 1)
@@ -0,0 +1,445 @@
+# Copyright (C) 2003  CAMP
+# Please see the accompanying LICENSE file for further information.
+
+"""
+Quasi-Newton algorithm
+"""
+
+__docformat__ = 'reStructuredText'
+
+import numpy as np
+import weakref,time,sys
+
+
+def f(lamda,Gbar,b,radius): 
+        b1 = b - lamda
+        g = radius**2 - np.dot(Gbar/b1, Gbar/b1)
+        return g
+
+
+
+def scale_radius_energy(f,r):
+        scale = 1.0
+#       if(r<=0.01):
+#               return scale
+        
+        if f<0.01: scale*=1.4
+        if f<0.05: scale*=1.4
+        if f<0.10: scale*=1.4
+        if f<0.40: scale*=1.4
+
+        if f>0.5: scale *= 1./1.4               
+        if f>0.7: scale *= 1./1.4               
+        if f>1.0: scale *= 1./1.4
+
+        return scale
+
+def scale_radius_force(f,r):
+        scale = 1.0
+#       if(r<=0.01):
+#               return scale
+        g = abs(f -1)
+        if g<0.01: scale*=1.4
+        if g<0.05: scale*=1.4
+        if g<0.10: scale*=1.4
+        if g<0.40: scale*=1.4
+
+        if g>0.5: scale *= 1./1.4               
+        if g>0.7: scale *= 1./1.4               
+        if g>1.0: scale *= 1./1.4
+
+        return scale
+
+def find_lamda(upperlimit,Gbar,b,radius):
+        lowerlimit = upperlimit
+        eps = 1e-12
+        step = 0.1
+        while  f(lowerlimit,Gbar,b,radius) < 0:
+                lowerlimit -= step
+                
+        converged = False
+
+        while not converged: 
+
+                midt = (upperlimit+lowerlimit)/2.
+                lamda = midt
+                fmidt = f(midt,Gbar,b,radius)
+                fupper = f(upperlimit,Gbar,b,radius)
+                flower = f(lowerlimit,Gbar,b,radius)
+        
+                if fupper*fmidt<0: 
+                        lowerlimit = midt 
+                else: 
+                        upperlimit = midt
+
+                if abs(upperlimit-lowerlimit)<1e-6: 
+                        converged = True
+
+        return lamda
+
+def get_hessian_inertia(eigenvalues):
+        # return number of negative modes
+        n = 0
+        print 'eigenvalues ',eigenvalues[0],eigenvalues[1],eigenvalues[2]
+        while eigenvalues[n]<0:
+                n+=1
+        return n 
+
+
+from numpy.linalg import eigh, solve
+
+from ase.optimize.optimize import Optimizer
+
+
+
+class GoodOldQuasiNewton(Optimizer):
+
+    def __init__(self, atoms, restart=None, logfile='-', trajectory=None,
+                 fmax=None, converged=None,
+                hessianupdate='BFGS',hessian=None,forcemin=True,
+                verbosity=None,maxradius=None,
+                diagonal=20.,radius=None,
+                transitionstate = False):
+            
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+
+        self.eps = 1e-12
+        self.hessianupdate = hessianupdate
+        self.forcemin = forcemin
+        self.verbosity = verbosity
+        self.diagonal = diagonal
+
+        self.atoms = atoms
+
+        n = len(self.atoms) * 3
+        if radius is None: 
+                self.radius = 0.05*np.sqrt(n)/10.0
+        else:
+                self.radius = radius
+
+        if maxradius is None: 
+                self.maxradius = 0.5*np.sqrt(n)
+        else:
+                self.maxradius = maxradius
+                
+        # 0.01 < radius < maxradius
+        self.radius = max(min( self.radius, self.maxradius ), 0.0001)
+
+        self.transitionstate = transitionstate
+
+        # check if this is a nudged elastic band calculation
+        if hasattr(atoms,'springconstant'): 
+                self.forcemin=False
+
+        self.t0 = time.time() 
+
+    def initialize(self):pass
+
+    def write_log(self,text):
+        if self.logfile is not None:
+            self.logfile.write(text + '\n')
+            self.logfile.flush()
+
+    def set_max_radius(self, maxradius):
+                self.maxradius = maxradius
+                self.radius = min(self.maxradius, self.radius)
+                
+    def set_hessian(self,hessian):
+        self.hessian = hessian
+
+    def get_hessian(self):
+        if not hasattr(self,'hessian'): 
+                self.set_default_hessian() 
+        return self.hessian
+
+    def set_default_hessian(self): 
+        # set unit matrix
+        n = len(self.atoms) * 3
+        hessian = np.zeros((n,n)) 
+        for i in range(n): 
+                        hessian[i][i] = self.diagonal
+        self.set_hessian(hessian) 
+
+    def read_hessian(self,filename): 
+        import cPickle
+        f = open(filename,'r')
+        self.set_hessian(cPickle.load(f))
+        f.close()
+
+    def write_hessian(self,filename): 
+        import cPickle
+        f = paropen(filename,'w')
+        cPickle.dump(self.get_hessian(),f)
+        f.close()
+
+    def write_to_restartfile(self):
+        import cPickle
+        f = paropen(self.restartfile,'w')
+        cPickle.dump((self.oldpos,
+                      self.oldG,
+                      self.oldenergy,
+                      self.radius,
+                      self.hessian,
+                      self.energy_estimate),f)
+        f.close()
+        
+
+
+    def update_hessian(self,pos,G):
+        import copy
+        if hasattr(self,'oldG'): 
+                if self.hessianupdate=='BFGS': 
+                        self.update_hessian_bfgs(pos,G) 
+                elif self.hessianupdate== 'Powell': 
+                        self.update_hessian_powell(pos,G) 
+                else:           
+                        self.update_hessian_bofill(pos,G) 
+        else: 
+                if not hasattr(self,'hessian'): 
+                        self.set_default_hessian()
+
+        self.oldpos = copy.copy(pos)
+        self.oldG = copy.copy(G)
+
+        if self.verbosity: 
+                print 'hessian ',self.hessian
+
+
+        
+    def update_hessian_bfgs(self,pos,G): 
+        n = len(self.hessian)
+        dgrad = G - self.oldG
+        dpos  = pos - self.oldpos
+        absdpos = np.sqrt(np.dot(dpos, dpos))
+        dotg  = np.dot(dgrad,dpos) 
+        tvec  = np.dot(dpos,self.hessian)
+        dott  = np.dot(dpos,tvec)
+        if (abs(dott)>self.eps) and (abs(dotg)>self.eps): 
+                for i in range(n): 
+                        for j in range(n): 
+                                h = dgrad[i]*dgrad[j]/dotg - tvec[i]*tvec[j]/dott
+                                self.hessian[i][j] += h
+
+
+
+    def update_hessian_powell(self,pos,G):          
+        n = len(self.hessian)
+        dgrad = G - self.oldG
+        dpos  = pos - self.oldpos
+        absdpos = np.dot(dpos, dpos)
+        if absdpos<self.eps: 
+                return
+
+        dotg  = np.dot(dgrad,dpos) 
+        tvec  = dgrad-np.dot(dpos,self.hessian)
+        tvecdot = np.dot(tvec,tvec)
+        tvecdpos = np.dot(tvec,dpos) 
+        ddot = tvecdpos/absdpos
+
+        dott  = np.dot(dpos,tvec)
+        if (abs(dott)>self.eps) and (abs(dotg)>self.eps): 
+                for i in range(n): 
+                        for j in range(n): 
+                                h = tvec[i]*dpos[j] + dpos[i]*tvec[j]-ddot*dpos[i]*dpos[j]
+                                h *= 1./absdpos
+                                self.hessian[i][j] += h
+
+
+    def update_hessian_bofill(self,pos,G):                                                                     
+        print 'update Bofill'
+        n = len(self.hessian)                                                                               
+        dgrad = G - self.oldG                                                                               
+        dpos  = pos - self.oldpos                                                                           
+        absdpos = np.dot(dpos, dpos)                                                                          
+        if absdpos<self.eps: 
+                return
+        dotg  = np.dot(dgrad,dpos)                                                                         
+        tvec  = dgrad-np.dot(dpos,self.hessian)                                                 
+        tvecdot = np.dot(tvec,tvec)                                                                        
+        tvecdpos = np.dot(tvec,dpos)                                                                       
+        ddot = tvecdpos/absdpos                                                                             
+
+        coef1 = 1. - tvecdpos*tvecdpos/(absdpos*tvecdot)
+        coef2 = (1. - coef1)*absdpos/tvecdpos
+        coef3 = coef1*tvecdpos/absdpos
+
+        dott  = np.dot(dpos,tvec)                                                                          
+        if (abs(dott)>self.eps) and (abs(dotg)>self.eps):                                                   
+                for i in range(n):                                                                          
+                        for j in range(n):                                                                  
+                                h = coef1*(tvec[i]*dpos[j] + dpos[i]*tvec[j])-dpos[i]*dpos[j]*coef3 + coef2*tvec[i]*tvec[j]
+                                h *= 1./absdpos
+                                self.hessian[i][j] += h                                                     
+
+
+
+    def step(self, f):
+        """ Do one QN step
+        """
+
+        pos = self.atoms.get_positions().ravel()
+        G = -self.atoms.get_forces().ravel()
+        energy = self.atoms.get_potential_energy()
+
+
+        self.write_iteration(energy,G)
+
+        if hasattr(self,'oldenergy'):
+
+                self.write_log('energies ' + str(energy) + ' ' + str(self.oldenergy))
+
+                if self.forcemin:
+                        de = 1e-4
+                else:
+                        de = 1e-2
+
+                if self.transitionstate:
+                        de = 0.2
+
+                if (energy-self.oldenergy)>de:
+                        self.write_log('reject step')
+                        self.atoms.set_positions(self.oldpos.reshape((-1, 3)))
+                        G = self.oldG
+                        energy = self.oldenergy
+                        self.radius *= 0.5
+                else: 
+                        self.update_hessian(pos,G)
+                        de = energy - self.oldenergy
+                        f = 1.0
+                        if self.forcemin: 
+                                self.write_log("energy change; actual: %f estimated: %f "%(de,self.energy_estimate))
+                                if abs(self.energy_estimate)>self.eps: 
+                                        f = abs((de/self.energy_estimate)-1)
+                                        self.write_log('Energy prediction factor ' + str(f))
+                                        # fg = self.get_force_prediction(G)
+                                        self.radius *= scale_radius_energy(f,self.radius) 
+
+                        else:
+                                self.write_log("energy change; actual: %f "%(de))
+                                self.radius*=1.5
+
+                        fg = self.get_force_prediction(G)
+                        self.write_log("Scale factors %f %f "%(scale_radius_energy(f,self.radius),
+                                                                scale_radius_force(fg,self.radius)))
+                        
+                                   
+                self.radius = max(min(self.radius,self.maxradius), 0.0001)
+        else: 
+                self.update_hessian(pos,G)
+
+        self.write_log("new radius %f "%(self.radius))          
+        self.oldenergy = energy
+
+        b,V = eigh(self.hessian)
+        V=V.T.copy()
+        self.V = V
+
+        # calculate projection of G onto eigenvectors V
+        Gbar = np.dot(G,np.transpose(V))
+        
+        lamdas = self.get_lambdas(b,Gbar)
+
+        D = -Gbar/(b-lamdas) 
+        n = len(D)
+        step = np.zeros((n))
+        for i in range(n): 
+                step += D[i]*V[i]
+
+        pos = self.atoms.get_positions().ravel()
+        pos += step
+
+        energy_estimate = self.get_energy_estimate(D,Gbar,b) 
+        self.energy_estimate = energy_estimate
+        self.gbar_estimate = self.get_gbar_estimate(D,Gbar,b)
+        self.old_gbar = Gbar
+
+        self.atoms.set_positions(pos.reshape((-1, 3)))
+
+
+
+
+    def get_energy_estimate(self,D,Gbar,b): 
+
+        de = 0.0
+        for n in range(len(D)): 
+                de += D[n]*Gbar[n] + 0.5*D[n]*b[n]*D[n]
+        return de
+
+    def get_gbar_estimate(self,D,Gbar,b):
+        gbar_est = (D*b) + Gbar
+        self.write_log('Abs Gbar estimate ' + str(np.dot(gbar_est,gbar_est)))
+        return gbar_est
+
+    def get_lambdas(self,b,Gbar):
+        lamdas = np.zeros((len(b)))
+
+        D = -Gbar/b
+        #absD = np.sqrt(np.sum(D**2))
+        absD = np.sqrt(np.dot(D, D))
+
+        eps = 1e-12
+        nminus = self.get_hessian_inertia(b)
+
+        if absD < self.radius:
+                if not self.transitionstate:
+                        self.write_log('Newton step') 
+                        return lamdas
+                else:
+                        if nminus==1:
+                                self.write_log('Newton step')
+                                return lamdas
+                        else:
+                                self.write_log("Wrong inertia of Hessian matrix: %2.2f %2.2f "%(b[0],b[1]))
+
+        else:
+                self.write_log("Corrected Newton step: abs(D) = %2.2f "%(absD))
+
+        if not self.transitionstate: 
+                # upper limit
+                upperlimit = min(0,b[0])-eps
+                lowerlimit = upperlimit
+                lamda = find_lamda(upperlimit,Gbar,b,self.radius)
+                lamdas += lamda
+        else:
+                # upperlimit
+                upperlimit = min(-b[0],b[1],0)-eps
+                lamda = find_lamda(upperlimit,Gbar,b,self.radius)
+                lamdas += lamda
+                lamdas[0] -= 2*lamda
+                
+        return lamdas
+
+
+
+    def print_hessian(self): 
+        hessian = self.get_hessian()
+        n = len(hessian)
+        for i in range(n): 
+            for j in range(n): 
+                print "%2.4f " %(hessian[i][j]),
+            print " "
+
+
+    
+
+    def get_hessian_inertia(self,eigenvalues):
+        # return number of negative modes
+        self.write_log("eigenvalues %2.2f %2.2f %2.2f "%(eigenvalues[0],
+                                                        eigenvalues[1],
+                                                        eigenvalues[2]))
+        n = 0
+        while eigenvalues[n]<0:
+                n+=1
+        return n
+
+    def get_force_prediction(self,G):
+        # return measure of how well the forces are predicted
+        Gbar = np.dot(G,np.transpose(self.V))
+        dGbar_actual = Gbar-self.old_gbar
+        dGbar_predicted = Gbar-self.gbar_estimate
+
+        f = np.dot(dGbar_actual,dGbar_predicted)/np.dot(dGbar_actual,dGbar_actual)
+        self.write_log('Force prediction factor ' + str(f))
+        return f
+
+    def write_iteration(self,energy,G):pass
Index: ase/optimize/__init__.py
===================================================================
--- ase/optimize/__init__.py	(revision 0)
+++ ase/optimize/__init__.py	(revision 1)
@@ -0,0 +1,12 @@
+"""Structure optimization. """
+
+from ase.optimize.optimize import NDPoly, polyfit
+from ase.optimize.mdmin import MDMin
+from ase.optimize.lbfgs import HessLBFGS, LineLBFGS
+from ase.optimize.fire import FIRE
+from ase.optimize.lbfgs import LBFGS, LBFGSLineSearch
+from ase.optimize.bfgslinesearch import BFGSLineSearch
+from ase.optimize.bfgs import BFGS
+
+QuasiNewton = BFGS
+
Index: ase/optimize/bfgslinesearch.py
===================================================================
--- ase/optimize/bfgslinesearch.py	(revision 0)
+++ ase/optimize/bfgslinesearch.py	(revision 1)
@@ -0,0 +1,457 @@
+#__docformat__ = "restructuredtext en"
+# ******NOTICE***************
+# optimize.py module by Travis E. Oliphant
+#
+# You may copy and use this module as you see fit with no
+# guarantee implied provided you keep this notice in all copies.
+# *****END NOTICE************
+
+import numpy as np
+from numpy import atleast_1d, eye, mgrid, argmin, zeros, shape, empty, \
+     squeeze, vectorize, asarray, absolute, sqrt, Inf, asfarray, isinf
+from ase.utils.linesearch import LineSearch
+from ase.optimize.optimize import Optimizer
+from numpy import arange
+
+
+# These have been copied from Numeric's MLab.py
+# I don't think they made the transition to scipy_core
+
+# Modified from scipy_optimize
+abs = absolute
+import __builtin__
+pymin = __builtin__.min
+pymax = __builtin__.max
+__version__="0.1"
+
+class BFGSLineSearch(Optimizer):
+    def __init__(self, atoms, restart=None, logfile='-', maxstep=.2,
+                 trajectory=None, c1=.23, c2=0.46, alpha=10., stpmax=50.):
+        """Minimize a function using the BFGS algorithm.
+
+        Notes:
+
+            Optimize the function, f, whose gradient is given by fprime
+            using the quasi-Newton method of Broyden, Fletcher, Goldfarb,
+            and Shanno (BFGS) See Wright, and Nocedal 'Numerical
+            Optimization', 1999, pg. 198.
+
+        *See Also*:
+
+          scikits.openopt : SciKit which offers a unified syntax to call
+                            this and other solvers.
+
+        """
+        self.maxstep = maxstep
+        self.stpmax = stpmax
+        self.alpha = alpha
+        self.H = None
+        self.c1 = c1
+        self.c2 = c2
+        self.force_calls = 0
+        self.function_calls = 0
+        self.r0 = None
+        self.g0 = None
+        self.e0 = None
+        self.load_restart = False
+        self.task = 'START'
+        self.rep_count = 0
+        self.p = None
+        self.alpha_k = None
+        self.no_update = False
+        self.replay = False
+
+        Optimizer.__init__(self, atoms, restart, logfile, trajectory)
+
+    def read(self):
+        self.r0, self.g0, self.e0, self.task, self.H = self.load()
+        self.load_restart = True    
+
+    def reset(self):
+        print 'reset'
+        self.H = None
+        self.r0 = None
+        self.g0 = None
+        self.e0 = None
+        self.rep_count = 0
+          
+
+    def step(self, f):
+        atoms = self.atoms
+        r = atoms.get_positions()
+        r = r.reshape(-1)
+        g = -f.reshape(-1) / self.alpha
+        #g = -f.reshape(-1) 
+        p0 = self.p
+        self.update(r, g, self.r0, self.g0, p0)
+        e = atoms.get_potential_energy() / self.alpha
+        #e = self.func(r)
+
+        self.p = -np.dot(self.H,g)
+        p_size = np.sqrt((self.p **2).sum())
+        if self.nsteps != 0:
+            p0_size = np.sqrt((p0 **2).sum())
+            delta_p = self.p/p_size + p0/p0_size
+        if p_size <= np.sqrt(len(atoms) * 1e-10):
+            self.p /= (p_size / np.sqrt(len(atoms)*1e-10))
+        ls = LineSearch()
+        self.alpha_k, e, self.e0, self.no_update = \
+           ls._line_search(self.func, self.fprime, r, self.p, g, e, self.e0,
+                           maxstep=self.maxstep, c1=self.c1,
+                           c2=self.c2, stpmax=self.stpmax)
+        #if alpha_k is None:  # line search failed try different one.
+        #    alpha_k, fc, gc, e, e0, gfkp1 = \
+        #             line_search(self.func, self.fprime,r,p,g,
+        #                         e,self.e0)
+        #if abs(e - self.e0) < 0.000001:
+        #    self.rep_count += 1
+        #else:
+        #    self.rep_count = 0
+
+        #if (alpha_k is None) or (self.rep_count >= 3):
+        #    # If the line search fails, reset the Hessian matrix and
+        #    # start a new line search.
+        #    self.reset()
+        #    return
+
+        dr = self.alpha_k * self.p
+        atoms.set_positions((r+dr).reshape(len(atoms),-1))
+        self.r0 = r
+        self.g0 = g
+        self.dump((self.r0, self.g0, self.e0, self.task, self.H))
+
+    def update(self, r, g, r0, g0, p0):
+        self.I = eye(len(self.atoms) * 3, dtype=int)
+        if self.H is None:
+            self.H = eye(3 * len(self.atoms))
+            #self.H = eye(3 * len(self.atoms)) / self.alpha
+            return
+        else:
+            dr = r - r0
+            dg = g - g0 
+            if not ((self.alpha_k > 0 and abs(np.dot(g,p0))-abs(np.dot(g0,p0)) < 0) \
+                or self.replay):
+                return
+            if self.no_update == True:
+                print 'skip update'
+                return
+
+            try: # this was handled in numeric, let it remaines for more safety
+                rhok = 1.0 / (np.dot(dg,dr))
+            except ZeroDivisionError:
+                rhok = 1000.0
+                print "Divide-by-zero encountered: rhok assumed large"
+            if isinf(rhok): # this is patch for np
+                rhok = 1000.0
+                print "Divide-by-zero encountered: rhok assumed large"
+            A1 = self.I - dr[:, np.newaxis] * dg[np.newaxis, :] * rhok
+            A2 = self.I - dg[:, np.newaxis] * dr[np.newaxis, :] * rhok
+            H0 = self.H
+            self.H = np.dot(A1, np.dot(self.H, A2)) + rhok * dr[:, np.newaxis] \
+                     * dr[np.newaxis, :]
+            #self.B = np.linalg.inv(self.H)
+            #omega, V = np.linalg.eigh(self.B)
+            #eighfile = open('eigh.log','w')
+
+    def func(self, x):
+        """Objective function for use of the optimizers"""
+        self.atoms.set_positions(x.reshape(-1, 3))
+        self.function_calls += 1
+        # Scale the problem as SciPy uses I as initial Hessian.
+        return self.atoms.get_potential_energy() / self.alpha
+        #return self.atoms.get_potential_energy() 
+    
+    def fprime(self, x):
+        """Gradient of the objective function for use of the optimizers"""
+        self.atoms.set_positions(x.reshape(-1, 3))
+        self.force_calls += 1
+        # Remember that forces are minus the gradient!
+        # Scale the problem as SciPy uses I as initial Hessian.
+        return - self.atoms.get_forces().reshape(-1) / self.alpha
+        #return - self.atoms.get_forces().reshape(-1) 
+
+    def replay_trajectory(self, traj):
+        """Initialize hessian from old trajectory."""
+        self.replay = True
+        if isinstance(traj, str):
+            from ase.io.trajectory import PickleTrajectory
+            traj = PickleTrajectory(traj, 'r')
+        atoms = traj[0]
+        r0 = None
+        g0 = None
+        for i in range(0, len(traj) - 1):
+            r = traj[i].get_positions().ravel()
+            g = - traj[i].get_forces().ravel() / self.alpha
+            self.update(r, g, r0, g0, self.p)
+            self.p = -np.dot(self.H,g)
+            r0 = r.copy()
+            g0 = g.copy()
+        self.r0 = r0
+        self.g0 = g0
+        #self.r0 = traj[-2].get_positions().ravel()
+        #self.g0 = - traj[-2].get_forces().ravel()
+
+def wrap_function(function, args):
+    ncalls = [0]
+    def function_wrapper(x):
+        ncalls[0] += 1
+        return function(x, *args)
+    return ncalls, function_wrapper
+
+def _cubicmin(a,fa,fpa,b,fb,c,fc):
+    # finds the minimizer for a cubic polynomial that goes through the
+    #  points (a,fa), (b,fb), and (c,fc) with derivative at a of fpa.
+    #
+    # if no minimizer can be found return None
+    #
+    # f(x) = A *(x-a)^3 + B*(x-a)^2 + C*(x-a) + D
+
+    C = fpa
+    D = fa
+    db = b-a
+    dc = c-a
+    if (db == 0) or (dc == 0) or (b==c): return None
+    denom = (db*dc)**2 * (db-dc)
+    d1 = empty((2,2))
+    d1[0,0] = dc**2
+    d1[0,1] = -db**2
+    d1[1,0] = -dc**3
+    d1[1,1] = db**3
+    [A,B] = np.dot(d1,asarray([fb-fa-C*db,fc-fa-C*dc]).flatten())
+    A /= denom
+    B /= denom
+    radical = B*B-3*A*C
+    if radical < 0:  return None
+    if (A == 0): return None
+    xmin = a + (-B + sqrt(radical))/(3*A)
+    return xmin
+
+def _quadmin(a,fa,fpa,b,fb):
+    # finds the minimizer for a quadratic polynomial that goes through
+    #  the points (a,fa), (b,fb) with derivative at a of fpa
+    # f(x) = B*(x-a)^2 + C*(x-a) + D
+    D = fa
+    C = fpa
+    db = b-a*1.0
+    if (db==0): return None
+    B = (fb-D-C*db)/(db*db)
+    if (B <= 0): return None
+    xmin = a  - C / (2.0*B)
+    return xmin
+
+def zoom(a_lo, a_hi, phi_lo, phi_hi, derphi_lo,
+         phi, derphi, phi0, derphi0, c1, c2):
+    maxiter = 10
+    i = 0
+    delta1 = 0.2  # cubic interpolant check
+    delta2 = 0.1  # quadratic interpolant check
+    phi_rec = phi0
+    a_rec = 0
+    while 1:
+        # interpolate to find a trial step length between a_lo and a_hi
+        # Need to choose interpolation here.  Use cubic interpolation and then 
+        #if the result is within delta * dalpha or outside of the interval 
+        #bounded by a_lo or a_hi then use quadratic interpolation, if the 
+        #result is still too close, then use bisection
+
+        dalpha = a_hi-a_lo;
+        if dalpha < 0: a,b = a_hi,a_lo
+        else: a,b = a_lo, a_hi
+
+        # minimizer of cubic interpolant
+        #    (uses phi_lo, derphi_lo, phi_hi, and the most recent value of phi)
+        #      if the result is too close to the end points (or out of the 
+        #         interval) then use quadratic interpolation with phi_lo, 
+        #         derphi_lo and phi_hi
+        #      if the result is stil too close to the end points (or out of 
+        #         the interval) then use bisection
+
+        if (i > 0):
+            cchk = delta1*dalpha
+            a_j = _cubicmin(a_lo, phi_lo, derphi_lo, a_hi, phi_hi, a_rec, 
+                            phi_rec)
+        if (i==0) or (a_j is None) or (a_j > b-cchk) or (a_j < a+cchk):
+            qchk = delta2*dalpha
+            a_j = _quadmin(a_lo, phi_lo, derphi_lo, a_hi, phi_hi)
+            if (a_j is None) or (a_j > b-qchk) or (a_j < a+qchk):
+                a_j = a_lo + 0.5*dalpha
+#                print "Using bisection."
+#            else: print "Using quadratic."
+#        else: print "Using cubic."
+
+        # Check new value of a_j
+
+        phi_aj = phi(a_j)
+        if (phi_aj > phi0 + c1*a_j*derphi0) or (phi_aj >= phi_lo):
+            phi_rec = phi_hi
+            a_rec = a_hi
+            a_hi = a_j
+            phi_hi = phi_aj
+        else:
+            derphi_aj = derphi(a_j)
+            if abs(derphi_aj) <= -c2*derphi0:
+                a_star = a_j
+                val_star = phi_aj
+                valprime_star = derphi_aj
+                break
+            if derphi_aj*(a_hi - a_lo) >= 0:
+                phi_rec = phi_hi
+                a_rec = a_hi
+                a_hi = a_lo
+                phi_hi = phi_lo
+            else:
+                phi_rec = phi_lo
+                a_rec = a_lo
+            a_lo = a_j
+            phi_lo = phi_aj
+            derphi_lo = derphi_aj
+        i += 1
+        if (i > maxiter):
+            a_star = a_j
+            val_star = phi_aj
+            valprime_star = None
+            break
+    return a_star, val_star, valprime_star
+
+def line_search(f, myfprime, xk, pk, gfk, old_fval, old_old_fval,
+                args=(), c1=1e-4, c2=0.9, amax=50):
+    """Find alpha that satisfies strong Wolfe conditions.
+
+    Parameters:
+
+        f : callable f(x,*args)
+            Objective function.
+        myfprime : callable f'(x,*args)
+            Objective function gradient (can be None).
+        xk : ndarray
+            Starting point.
+        pk : ndarray
+            Search direction.
+        gfk : ndarray
+            Gradient value for x=xk (xk being the current parameter
+            estimate).
+        args : tuple
+            Additional arguments passed to objective function.
+        c1 : float
+            Parameter for Armijo condition rule.
+        c2 : float
+            Parameter for curvature condition rule.
+
+    Returns:
+
+        alpha0 : float
+            Alpha for which ``x_new = x0 + alpha * pk``.
+        fc : int
+            Number of function evaluations made.
+        gc : int
+            Number of gradient evaluations made.
+
+    Notes:
+
+        Uses the line search algorithm to enforce strong Wolfe
+        conditions.  See Wright and Nocedal, 'Numerical Optimization',
+        1999, pg. 59-60.
+
+        For the zoom phase it uses an algorithm by [...].
+
+    """
+
+    global _ls_fc, _ls_gc, _ls_ingfk
+    _ls_fc = 0
+    _ls_gc = 0
+    _ls_ingfk = None
+    def phi(alpha):
+        global _ls_fc
+        _ls_fc += 1
+        return f(xk+alpha*pk,*args)
+
+    if isinstance(myfprime,type(())):
+        def phiprime(alpha):
+            global _ls_fc, _ls_ingfk
+            _ls_fc += len(xk)+1
+            eps = myfprime[1]
+            fprime = myfprime[0]
+            newargs = (f,eps) + args
+            _ls_ingfk = fprime(xk+alpha*pk,*newargs)  # store for later use
+            return np.dot(_ls_ingfk,pk)
+    else:
+        fprime = myfprime
+        def phiprime(alpha):
+            global _ls_gc, _ls_ingfk
+            _ls_gc += 1
+            _ls_ingfk = fprime(xk+alpha*pk,*args)  # store for later use
+            return np.dot(_ls_ingfk,pk)
+
+
+    alpha0 = 0
+    phi0 = old_fval
+    derphi0 = np.dot(gfk,pk)
+
+    alpha1 = pymin(1.,1.01*2*(phi0-old_old_fval)/derphi0)
+
+    if alpha1 == 0:
+        # This shouldn't happen. Perhaps the increment has slipped below
+        # machine precision?  For now, set the return variables skip the
+        # useless while loop, and raise warnflag=2 due to possible imprecision.
+        alpha_star = None
+        fval_star = old_fval
+        old_fval = old_old_fval
+        fprime_star = None
+
+    phi_a1 = phi(alpha1)
+    #derphi_a1 = phiprime(alpha1)  evaluated below
+
+    phi_a0 = phi0
+    derphi_a0 = derphi0
+
+    i = 1
+    maxiter = 10
+    while 1:         # bracketing phase
+        if alpha1 == 0:
+            break
+        if (phi_a1 > phi0 + c1*alpha1*derphi0) or \
+           ((phi_a1 >= phi_a0) and (i > 1)):
+            alpha_star, fval_star, fprime_star = \
+                        zoom(alpha0, alpha1, phi_a0,
+                             phi_a1, derphi_a0, phi, phiprime,
+                             phi0, derphi0, c1, c2)
+            break
+
+        derphi_a1 = phiprime(alpha1)
+        if (abs(derphi_a1) <= -c2*derphi0):
+            alpha_star = alpha1
+            fval_star = phi_a1
+            fprime_star = derphi_a1
+            break
+
+        if (derphi_a1 >= 0):
+            alpha_star, fval_star, fprime_star = \
+                        zoom(alpha1, alpha0, phi_a1,
+                             phi_a0, derphi_a1, phi, phiprime,
+                             phi0, derphi0, c1, c2)
+            break
+
+        alpha2 = 2 * alpha1   # increase by factor of two on each iteration
+        i = i + 1
+        alpha0 = alpha1
+        alpha1 = alpha2
+        phi_a0 = phi_a1
+        phi_a1 = phi(alpha1)
+        derphi_a0 = derphi_a1
+
+        # stopping test if lower function not found
+        if (i > maxiter):
+            alpha_star = alpha1
+            fval_star = phi_a1
+            fprime_star = None
+            break
+
+    if fprime_star is not None:
+        # fprime_star is a number (derphi) -- so use the most recently
+        # calculated gradient used in computing it derphi = gfk*pk
+        # this is the gradient at the next step no need to compute it
+        # again in the outer loop.
+        fprime_star = _ls_ingfk
+
+    return alpha_star, _ls_fc, _ls_gc, fval_star, old_fval, fprime_star
+
Index: ase/md/nptberendsen.py
===================================================================
--- ase/md/nptberendsen.py	(revision 0)
+++ ase/md/nptberendsen.py	(revision 1)
@@ -0,0 +1,137 @@
+"""Berendsen NPT dynamics class."""
+
+import numpy as np
+
+#from ase.md import MolecularDynamics
+from ase.md.nvtberendsen import NVTBerendsen
+import ase.units as units
+#import math
+
+
+class NPTBerendsen(NVTBerendsen):
+    """Berendsen (constant N, P, T) molecular dynamics.
+
+    Usage: NPTBerendsen(atoms, timestep, temperature, taut, pressure, taup)
+
+    atoms
+        The list of atoms.
+        
+    timestep
+        The time step.
+
+    temperature
+        The desired temperature, in Kelvin.
+
+    taut
+        Time constant for Berendsen temperature coupling.
+
+    fixcm
+        If True, the position and momentum of the center of mass is
+        kept unperturbed.  Default: True.
+
+    pressure
+        The desired pressure, in bar (1 bar = 1e5 Pa).
+
+    taup
+        Time constant for Berendsen pressure coupling.
+
+    compressibility
+        The compressibility of the material, water 4.57E-5 bar-1, in bar-1
+
+    """
+
+    def __init__(self, atoms, timestep, temperature, taut=0.5e3*units.fs,
+                 pressure = 1.01325, taup=1e3*units.fs,
+                 compressibility=4.57e-5, fixcm=True,
+                 trajectory=None, logfile=None, loginterval=1):
+
+        NVTBerendsen.__init__(self, atoms, timestep, temperature, taut, fixcm,
+                              trajectory, 
+                              logfile, loginterval)
+        self.taup = taup
+        self.pressure = pressure
+        self.compressibility = compressibility
+
+    def set_taup(self, taut):
+        self.taut = taut
+
+    def get_taup(self):
+        return self.taut
+
+    def set_pressure(self, pressure):
+        self.pressure = pressure
+
+    def get_pressure(self):
+        return self.pressure
+
+    def set_compressibility(self, compressibility):
+        self.compressibility = compressibility
+
+    def get_compressibility(self):
+        return self.compressibility
+
+    def set_timestep(self, timestep):
+        self.dt = timestep
+
+    def get_timestep(self):
+        return self.dt
+
+
+
+    def scale_positions_and_cell(self):
+        """ Do the Berendsen pressure coupling,
+        scale the atom positon and the simulation cell."""
+
+        taupscl = self.dt / self.taup
+        stress = self.atoms.get_stress()
+        old_pressure = self.atoms.get_isotropic_pressure(stress)
+        scl_pressure = 1.0 - taupscl * self.compressibility / 3.0 * \
+                       (self.pressure - old_pressure)
+
+        #print "old_pressure", old_pressure
+        #print "volume scaling by:", scl_pressure
+
+        cell = self.atoms.get_cell()
+        positions = self.atoms.get_positions()
+
+        cell = scl_pressure * cell
+        positions = scl_pressure * positions 
+
+        self.atoms.set_cell(cell, scale_atoms=False)
+        self.atoms.set_positions(positions)
+
+        return 
+
+
+    def step(self, f):
+        """ move one timestep forward using Berenden NPT molecular dynamics."""
+
+        NVTBerendsen.scale_velocities(self)
+        self.scale_positions_and_cell()
+
+        #one step velocity verlet
+        atoms = self.atoms
+        p = self.atoms.get_momenta()
+        p += 0.5 * self.dt * f
+
+        if self.fixcm:
+            # calculate the center of mass
+            # momentum and subtract it
+            psum = p.sum(axis=0) / float(len(p))
+            p = p - psum
+
+        self.atoms.set_positions(self.atoms.get_positions() +
+             self.dt * p / self.atoms.get_masses()[:,np.newaxis])
+
+        # We need to store the momenta on the atoms before calculating
+        # the forces, as in a parallel Asap calculation atoms may
+        # migrate during force calculations, and the momenta need to
+        # migrate along with the atoms.  For the same reason, we
+        # cannot use self.masses in the line above.
+
+        self.atoms.set_momenta(p)
+        f = self.atoms.get_forces()
+        atoms.set_momenta(self.atoms.get_momenta() + 0.5 * self.dt * f)
+
+
+        return f
Index: ase/md/nvtberendsen.py
===================================================================
--- ase/md/nvtberendsen.py	(revision 0)
+++ ase/md/nvtberendsen.py	(revision 1)
@@ -0,0 +1,117 @@
+"""Berendsen NVT dynamics class."""
+
+import sys
+import numpy as np
+from ase.md.md import MolecularDynamics
+
+
+# For parallel GPAW simulations, the random forces should be distributed.
+if '_gpaw' in sys.modules:
+    # http://wiki.fysik.dtu.dk/gpaw
+    from gpaw.mpi import world as gpaw_world
+else:
+    gpaw_world = None
+
+
+class NVTBerendsen(MolecularDynamics):
+    """Berendsen (constant N, V, T) molecular dynamics.
+
+    Usage: NVTBerendsen(atoms, timestep, temperature, taut, fixcm)
+
+    atoms
+        The list of atoms.
+        
+    timestep
+        The time step.
+
+    temperature
+        The desired temperature, in Kelvin.
+
+    taut
+        Time constant for Berendsen temperature coupling.
+
+    fixcm
+        If True, the position and momentum of the center of mass is
+        kept unperturbed.  Default: True.
+
+    """
+
+    def __init__(self, atoms, timestep, temperature, taut, fixcm=True,
+                 trajectory=None, logfile=None, loginterval=1,
+                 communicator=gpaw_world):
+
+        MolecularDynamics.__init__(self, atoms, timestep, trajectory, 
+                                   logfile, loginterval)
+        self.taut = taut
+        self.temperature = temperature
+        self.fixcm = fixcm  # will the center of mass be held fixed?
+        self.communicator = communicator
+
+    def set_taut(self, taut):
+        self.taut = taut
+
+    def get_taut(self):
+        return self.taut
+
+    def set_temperature(self, temperature):
+        self.temperature = temperature
+
+    def get_temperature(self):
+        return self.temperature
+
+    def set_timestep(self, timestep):
+        self.dt = timestep
+
+    def get_timestep(self):
+        return self.dt
+
+    def scale_velocities(self):
+        """ Do the NVT Berendsen velocity scaling """
+        tautscl = self.dt / self.taut
+        old_temperature = self.atoms.get_temperature()
+
+        scl_temperature = np.sqrt(1.0+ (self.temperature/ old_temperature- 1.0)
+                                  *tautscl)
+        #limit the velocity scaling to reasonable values
+        if scl_temperature > 1.1:
+            scl_temperature = 1.1
+        if scl_temperature < 0.9:
+            scl_temperature = 0.9
+        
+        atoms = self.atoms
+        p = self.atoms.get_momenta()
+        p = scl_temperature * p 
+        self.atoms.set_momenta(p)
+        return 
+
+
+    def step(self, f):
+        """ move one timestep forward using Berenden NVT molecular dynamics."""
+        self.scale_velocities()
+
+        #one step velocity verlet
+        atoms = self.atoms
+        p = self.atoms.get_momenta()
+        p += 0.5 * self.dt * f
+
+        if self.fixcm:
+            # calculate the center of mass
+            # momentum and subtract it
+            psum = p.sum(axis=0) / float(len(p))
+            p = p - psum
+
+        self.atoms.set_positions(self.atoms.get_positions() +
+             self.dt * p / self.atoms.get_masses()[:,np.newaxis])
+
+        # We need to store the momenta on the atoms before calculating
+        # the forces, as in a parallel Asap calculation atoms may
+        # migrate during force calculations, and the momenta need to
+        # migrate along with the atoms.  For the same reason, we
+        # cannot use self.masses in the line above.
+
+        self.atoms.set_momenta(p)
+        f = self.atoms.get_forces()
+        atoms.set_momenta(self.atoms.get_momenta() + 0.5 * self.dt * f)
+
+        return f
+
Index: ase/md/velocitydistribution.py
===================================================================
--- ase/md/velocitydistribution.py	(revision 0)
+++ ase/md/velocitydistribution.py	(revision 1)
@@ -0,0 +1,44 @@
+# VelocityDistributions.py -- set up a velocity distribution
+
+"""Module for setting up e.g. Maxwell-Boltzmann velocity distributions.
+
+Currently, only one function is defined, MaxwellBoltzmannDistribution,
+which sets the momenta of a list of atoms according to a
+Maxwell-Boltzmann distribution at a given temperature.
+"""
+
+import numpy as np
+import sys
+
+# For parallel GPAW simulations, the random velocities should be distributed.
+# Uses gpaw world communicator as default, but allow option of 
+# specifying other communicator (for ensemble runs)
+if '_gpaw' in sys.modules:
+    # http://wiki.fysik.dtu.dk/gpaw
+    from gpaw.mpi import world as gpaw_world
+else:
+    gpaw_world = None
+
+def _maxwellboltzmanndistribution(masses, temp, communicator=gpaw_world):
+    xi = np.random.standard_normal((len(masses),3))
+    if communicator is not None:
+       communicator.broadcast(xi, 0)
+    momenta = xi * np.sqrt(masses * temp)[:,np.newaxis]
+    return momenta
+
+def MaxwellBoltzmannDistribution(atoms, temp, communicator=gpaw_world):
+    """Sets the momenta to a Maxwell-Boltzmann distribution."""
+    momenta = _maxwellboltzmanndistribution(atoms.get_masses(), temp, communicator)
+    atoms.set_momenta(momenta)
+
+def Stationary(atoms):
+    "Sets the center-of-mass momentum to zero."
+    p = atoms.get_momenta()
+    p0 = np.sum(p, 0)
+    # We should add a constant velocity, not momentum, to the atoms
+    m = atoms.get_masses()
+    mtot = np.sum(m)
+    v0 = p0/mtot
+    p -= v0*m[:,np.newaxis]
+    atoms.set_momenta(p)
+    
Index: ase/md/__init__.py
===================================================================
--- ase/md/__init__.py	(revision 0)
+++ ase/md/__init__.py	(revision 1)
@@ -0,0 +1,5 @@
+"""Molecular Dynamics."""
+
+from ase.md.logger import MDLogger
+from ase.md.verlet import VelocityVerlet
+from ase.md.langevin import Langevin
Index: ase/md/logger.py
===================================================================
--- ase/md/logger.py	(revision 0)
+++ ase/md/logger.py	(revision 1)
@@ -0,0 +1,100 @@
+"""Logging for molecular dynamics."""
+
+import weakref
+import sys
+import ase.units as units
+# ase.parallel imported in __init__
+
+class MDLogger:
+    """Class for logging molecular dynamics simulations.
+
+    Parameters:
+    dyn:           The dynamics.  Only a weak reference is kept.
+
+    atoms:         The atoms.
+
+    logfile:       File name or open file, "-" meaning standart output.
+
+    stress=False:  Include stress in log.
+
+    peratom=False: Write energies per atom.
+
+    mode="a":      How the file is opened if logfile is a filename.
+    """
+    def __init__(self, dyn, atoms, logfile, header=True, stress=False,
+                 peratom=False, mode="a"):
+        import ase.parallel
+        if ase.parallel.rank > 0:
+            logfile="/dev/null"  # Only log on master
+        if hasattr(dyn, "get_time"):
+            self.dyn = weakref.proxy(dyn)
+        else:
+            self.dyn = None
+        self.atoms = atoms
+        self.natoms = atoms.get_number_of_atoms()
+        if logfile == "-":
+            self.logfile = sys.stdout
+            self.ownlogfile = False
+        elif hasattr(logfile, "write"):
+            self.logfile = logfile
+            self.ownlogfile = False
+        else:
+            self.logfile = open(logfile, mode)
+            self.ownlogfile = True
+        self.stress = stress
+        self.peratom = peratom
+        if self.dyn is not None:
+            self.hdr = "%-8s " % ("Time[ps]",)
+            self.fmt = "%-8.2f "
+        else:
+            self.hdr = ""
+            self.fmt = ""
+        if self.peratom:
+            self.hdr += "%12s %12s %12s  %6s" % ("Etot/N[eV]", "Epot/N[eV]",
+                                                 "Ekin/N[eV]", "T[K]")
+            self.fmt += "%12.4f %12.4f %12.4f  %6.1f"
+        else:
+            self.hdr += "%12s %12s %12s  %6s" % ("Etot[eV]", "Epot[eV]",
+                                                 "Ekin[eV]", "T[K]")
+            # Choose a sensible number of decimals
+            if self.natoms <= 10:
+                digits = 4
+            elif self.natoms <= 100:
+                digits = 3
+            elif self.natoms <= 1000:
+                digits = 2
+            else:
+                digits = 1
+            self.fmt += 3*("%%12.%df " % (digits,)) + " %6.1f"
+        if self.stress:
+            self.hdr += "---------------- stress [GPa] -----------------"
+            self.fmt += 6*" %10.3f"
+        self.fmt += "\n"
+        if header:
+            self.logfile.write(self.hdr+"\n")
+            
+    def __del__(self):
+        self.close()
+
+    def close(self):
+        if self.ownlogfile:
+            self.logfile.close()
+
+    def __call__(self):
+        epot = self.atoms.get_potential_energy()
+        ekin = self.atoms.get_kinetic_energy()
+        temp = ekin / (1.5 * units.kB * self.natoms)
+        if self.peratom:
+            epot /= self.natoms
+            ekin /= self.natoms
+        if self.dyn is not None:
+            t = self.dyn.get_time() / (1000*units.fs)
+            dat = (t,)
+        else:
+            dat = ()
+        dat += (epot+ekin, epot, ekin, temp)
+        if self.stress:
+            dat += tuple(self.atoms.get_stress() / units.GPa)
+        self.logfile.write(self.fmt % dat)
+        self.logfile.flush()
+        
Index: ase/md/md.py
===================================================================
--- ase/md/md.py	(revision 0)
+++ ase/md/md.py	(revision 1)
@@ -0,0 +1,36 @@
+"""Molecular Dynamics."""
+
+import numpy as np
+
+from ase.optimize.optimize import Dynamics
+from ase.data import atomic_masses
+from ase.md.logger import MDLogger
+
+
+class MolecularDynamics(Dynamics):
+    """Base-class for all MD classes."""
+    def __init__(self, atoms, timestep, trajectory, logfile=None,
+                 loginterval=1):
+        Dynamics.__init__(self, atoms, logfile=None, trajectory=trajectory)
+        self.dt = timestep
+        self.masses = self.atoms.get_masses()
+        self.masses.shape = (-1, 1)
+        if logfile:
+            self.attach(MDLogger(dyn=self, atoms=atoms, logfile=logfile),
+                        interval=loginterval)
+
+    def run(self, steps=50):
+        """Integrate equation of motion."""
+        f = self.atoms.get_forces()
+
+        if not self.atoms.has('momenta'):
+            self.atoms.set_momenta(np.zeros_like(f))
+
+        for step in xrange(steps):
+            f = self.step(f)
+            self.nsteps += 1
+            self.call_observers()
+
+    def get_time(self):
+        return self.nsteps * self.dt
+    
Index: ase/md/npt.py
===================================================================
--- ase/md/npt.py	(revision 0)
+++ ase/md/npt.py	(revision 1)
@@ -0,0 +1,687 @@
+'''Constant pressure/stress and temperature dynamics.
+
+Combined Nose-Hoover and Parrinello-Rahman dynamics, creating an NPT
+(or N,stress,T) ensemble.
+
+The method is the one proposed by Melchionna et al. [1] and later
+modified by Melchionna [2].  The differential equations are integrated
+using a centered difference method [3].
+
+ 1. S. Melchionna, G. Ciccotti and B. L. Holian, "Hoover NPT dynamics
+    for systems varying in shape and size", Molecular Physics 78, p. 533
+    (1993).
+
+ 2. S. Melchionna, "Constrained systems and statistical distribution",
+    Physical Review E, 61, p. 6165 (2000).
+
+ 3. B. L. Holian, A. J. De Groot, W. G. Hoover, and C. G. Hoover,
+    "Time-reversible equilibrium and nonequilibrium isothermal-isobaric
+    simulations with centered-difference Stoermer algorithms.", Physical
+    Review A, 41, p. 4552 (1990).
+'''
+
+__docformat__ = 'reStructuredText'
+
+from numpy import *
+import sys
+import time
+import weakref
+from ase.md.md import MolecularDynamics
+#from ASE.Trajectories.NetCDFTrajectory import NetCDFTrajectory
+
+# Delayed imports:  If the trajectory object is reading a special ASAP version
+# of HooverNPT, that class is imported from Asap.Dynamics.NPTDynamics.
+
+class NPT(MolecularDynamics):
+    '''Constant pressure/stress and temperature dynamics.
+
+    Combined Nose-Hoover and Parrinello-Rahman dynamics, creating an
+    NPT (or N,stress,T) ensemble.
+
+    The method is the one proposed by Melchionna et al. [1] and later
+    modified by Melchionna [2].  The differential equations are integrated
+    using a centered difference method [3].  See also NPTdynamics.tex
+
+    The dynamics object is called with the following parameters:
+
+    atoms
+        The list of atoms.
+
+    dt
+        The timestep in units matching eV, A, u.
+
+    temperature
+        The desired temperature in eV.
+
+    externalstress
+        The external stress in eV/A^3.  Either a symmetric
+        3x3 tensor, a 6-vector representing the same, or a
+        scalar representing the pressure.  Note that the
+        stress is positive in tension whereas the pressure is
+        positive in compression: giving a scalar p is
+        equivalent to giving the tensor (-p, -p, -p, 0, 0, 0).
+
+    ttime
+        Characteristic timescale of the thermostat.
+        Set to None to disable the thermostat.
+
+    pfactor
+        A constant in the barostat differential equation.  If
+        a characteristic barostat timescale of ptime is
+        desired, set pfactor to ptime^2 * B (where B is the
+        Bulk Modulus).  Set to None to disable the barostat.
+        Typical metallic bulk moduli are of the order of
+        100 GPa or 0.6 eV/A^3.
+
+    mask=None
+        Optional argument.  A tuple of three integers (0 or 1),
+        indicating if the system can change size along the
+        three Cartesian axes.  Set to (1,1,1) or None to allow
+        a fully flexible computational box.  Set to (1,1,0)
+        to disallow elongations along the z-axis etc.
+
+    Useful parameter values:
+
+    * The same timestep can be used as in Verlet dynamics, i.e. 5 fs is fine
+      for bulk copper.
+
+    * The ttime and pfactor are quite critical[4], too small values may
+      cause instabilites and/or wrong fluctuations in T / p.  Too
+      large values cause an oscillation which is slow to die.  Good
+      values for the characteristic times seem to be 25 fs for ttime,
+      and 75 fs for ptime (used to calculate pfactor), at least for
+      bulk copper with 15000-200000 atoms.  But this is not well
+      tested, it is IMPORTANT to monitor the temperature and
+      stress/pressure fluctuations.
+    
+    It has the following methods:
+
+    __call__(n)
+        Perform n timesteps.
+    initialize()
+        Estimates the dynamic variables for time=-1 to start
+        the algorithm.   This is automatically called before
+        the first timestep.
+    set_stress()
+        Set the external stress.  Use with care.  It is
+        preferable to set the right value when creating the
+        object.
+    set_mask()
+        Change the mask.  Use with care, as you may "freeze"
+        a fluctuation in the strain rate.
+    get_gibbs_free_energy()
+        Gibbs free energy is supposed to be preserved by this
+        dynamics.  This is mainly intended as a diagnostic
+        tool.
+    
+    References:
+    
+    1) S. Melchionna, G. Ciccotti and B. L. Holian, Molecular
+       Physics 78, p. 533 (1993).
+
+    2) S. Melchionna, Physical
+       Review E 61, p. 6165 (2000).
+
+    3) B. L. Holian, A. J. De Groot, W. G. Hoover, and C. G. Hoover,
+       Physical Review A 41, p. 4552 (1990).
+
+    4) F. D. Di Tolla and M. Ronchetti, Physical
+       Review E 48, p. 1726 (1993).
+    
+    '''
+
+    classname = "NPT"  # Used by the trajectory.
+    def __init__(self, atoms, 
+                 timestep, temperature, externalstress, ttime, pfactor,
+                 mask=None, trajectory=None, logfile=None, loginterval=1):
+        MolecularDynamics.__init__(self, atoms, timestep, trajectory,
+                                   logfile, loginterval)
+        #self.atoms = atoms
+        #self.timestep = timestep
+        self.zero_center_of_mass_momentum(verbose=1)
+        self.temperature = temperature
+        self.set_stress(externalstress)
+        self.set_mask(mask)
+        self.eta = zeros((3,3), float)
+        self.zeta = 0.0
+        self.zeta_integrated = 0.0
+        self.initialized = 0
+        self.ttime = ttime
+        self.pfactor_given = pfactor
+        self._calculateconstants()
+        self.timeelapsed = 0.0
+        self.frac_traceless = 1
+
+    def set_temperature(self, temperature):
+        self.temperature = temperature
+        self._calculateconstants()
+        
+    def set_stress(self, stress):
+        """Set the applied stress.
+
+        Must be a symmetric 3x3 tensor, a 6-vector representing a symmetric
+        3x3 tensor, or a number representing the pressure.
+        """
+        if type(stress) == type(1.0) or type(stress) == type(1):
+            stress = array((-stress, -stress, -stress, 0.0, 0.0, 0.0))
+        elif stress.shape == (3,3):
+            if not self._issymmetric(stress):
+                raise ValueError, "The external stress must be a symmetric tensor."
+            stress = array((stress[0,0], stress[1,1], stress[2,2], stress[1,2],
+                            stress[0,2], stress[0,1]))
+        elif stress.shape != (6,):
+            raise ValueError, "The external stress has the wrong shape."
+        self.externalstress = stress
+
+    def set_mask(self, mask):
+        """Set the mask indicating dynamic elements of the computational box.
+
+        If set to None, all elements may change.  If set to a 3-vector
+        of ones and zeros, elements which are zero specify directions
+        along which the size of the computational box cannot change.
+        For example, if mask = {1,1,0} the length of the system along
+        the z-axis cannot change, although xz and yz shear is still
+        possible.  To disable shear globally, set the mode to diagonal
+        (not yet implemented).
+        """
+        if mask is None:
+            mask = ones((3,))
+        if not hasattr(mask, "shape"):
+            mask = array(mask)        
+        if mask.shape != (3,) and mask.shape != (3,3):
+            raise "The mask has the wrong shape (must be a 3-vector or 3x3 matrix)"
+        else:
+            mask = not_equal(mask, 0)  # Make sure it is 0/1
+
+        if mask.shape == (3,):
+            self.mask = outer(mask, mask)
+        else:
+            self.mask = mask
+        
+    def set_fraction_traceless(self, fracTraceless):
+        """set what fraction of the traceless part of the force
+        on eta is kept.
+
+        By setting this to zero, the volume may change but the shape may not.
+        """
+        self.frac_traceless = fracTraceless
+
+    def get_strain_rate(self):
+        "Get the strain rate as an upper-triangular 3x3 matrix"
+        return array(self.eta, copy=1)
+
+    def set_strain_rate(self, rate):
+        "Set the strain rate.  Must be an upper triangular 3x3 matrix."
+        if not (rate.shape == (3,3) and self._isuppertriangular(rate)):
+            raise ValueError, "Strain rate must be an upper triangular matrix."
+        self.eta = rate
+        if self.initialized:
+            # Recalculate h_past and eta_past so they match the current value.
+            self._initialize_eta_h()
+
+    def get_time(self):
+        "Get the elapsed time."
+        return self.timeelapsed
+    
+    def run(self, steps):
+        """Perform a number of time steps."""
+        if not self.initialized:
+            self.initialize()
+        else:
+            if self.have_the_atoms_been_changed():
+                raise NotImplementedError, "You have modified the atoms since the last timestep."
+
+        for i in xrange(steps):
+            self.step()
+            self.nsteps += 1
+            self.call_observers()
+
+    def have_the_atoms_been_changed(self):
+        "Checks if the user has modified the positions or momenta of the atoms"
+        limit = 1e-10
+        h = self._getbox()
+        if max(abs((h - self.h).ravel())) > limit:
+            self._warning("The computational box has been modified.")
+            return 1
+        expected_r = dot(self.q + 0.5, h)
+        err = max(abs((expected_r - self.atoms.get_positions()).ravel())) 
+        if err > limit:
+            self._warning("The atomic positions have been modified: "+ str(err))
+            return 1
+        return 0
+    
+    def step(self):
+        """Perform a single time step.
+        
+        Assumes that the forces and stresses are up to date, and that
+        the positions and momenta have not been changed since last
+        timestep.
+        """
+        
+        ## Assumes the following variables are OK
+        # q_past, q, q_future, p, eta, eta_past, zeta, zeta_past, h, h_past
+        #
+        # q corresponds to the current positions
+        # p must be equal to self.atoms.GetCartesianMomenta()
+        # h must be equal to self.atoms.GetUnitCell()
+        #
+        #print "Making a timestep"
+        dt = self.dt
+        h_future = self.h_past + 2*dt * dot(self.h, self.eta)
+        if self.pfactor_given is None:
+            deltaeta = zeros(6, float)
+        else:
+            deltaeta = -2*dt * (self.pfact * linalg.det(self.h)
+                                * (self.atoms.get_stress()
+                                   - self.externalstress))
+        
+        if self.frac_traceless == 1:
+            eta_future = self.eta_past + self.mask * self._makeuppertriangular(deltaeta)
+        else:
+            trace_part, traceless_part = self._separatetrace(self._makeuppertriangular(deltaeta))
+            eta_future = self.eta_past + trace_part + self.frac_traceless * traceless_part
+
+        deltazeta = 2*dt*self.tfact * (self.atoms.get_kinetic_energy()
+                                       - self.desiredEkin)
+        zeta_future = self.zeta_past + deltazeta
+        # Advance time
+        #print "Max change in scaled positions:", max(abs(self.q_future.flat - self.q.flat))
+        #print "Max change in basis set", max(abs((h_future - self.h).flat))
+        self.timeelapsed += dt
+        self.h_past = self.h
+        self.h = h_future
+        self.inv_h = linalg.inv(self.h)
+        # Do not throw away the q arrays, they are "magical" on parallel
+        # simulations (the contents migrate along with the atoms).
+        (self.q_past, self.q, self.q_future) = (self.q, self.q_future,
+                                                self.q_past)
+        self._setbox_and_positions(self.h,self.q)
+        self.eta_past = self.eta
+        self.eta = eta_future
+        self.zeta_past = self.zeta
+        self.zeta = zeta_future
+        self._synchronize()  # for parallel simulations.
+        self.zeta_integrated += dt * self.zeta
+        #self.forcecalculator()
+        force = self.atoms.get_forces()
+        # The periodic boundary conditions may have moved the atoms.
+        self.post_pbc_fix(fixfuture=0)  
+        self._calculate_q_future(force)
+        self.atoms.set_momenta(dot(self.q_future-self.q_past, self.h/(2*dt)) *
+                               self._getmasses())
+        #self.stresscalculator()
+
+    def initialize(self):
+        """Initialize the dynamics.
+
+        The dynamics requires positions etc for the two last times to
+        do a timestep, so the algorithm is not self-starting.  This
+        method performs a 'backwards' timestep to generate a
+        configuration before the current.
+        """
+        #print "Initializing the NPT dynamics."
+        dt = self.dt
+        atoms = self.atoms
+        self.h = self._getbox()
+        if not self._isuppertriangular(self.h):
+            print "I am", self
+            print "self.h:"
+            print self.h
+            print "Min:", min((self.h[1,0], self.h[2,0], self.h[2,1]))
+            print "Max:", max((self.h[1,0], self.h[2,0], self.h[2,1]))
+            raise NotImplementedError, "Can (so far) only operate on lists of atoms where the computational box is an upper triangular matrix."
+        self.inv_h = linalg.inv(self.h)
+        # The contents of the q arrays should migrate in parallel simulations.
+        self._make_special_q_arrays()
+        self.q[:] = dot(self.atoms.get_positions(),
+                                self.inv_h) - 0.5
+        # zeta and eta were set in __init__
+        self._initialize_eta_h()
+        deltazeta = dt * self.tfact * (atoms.get_kinetic_energy() -
+                                       self.desiredEkin)
+        self.zeta_past = self.zeta - deltazeta
+        self._calculate_q_past_and_future()
+        self.initialized = 1
+
+    def get_gibbs_free_energy(self):
+        """Return the Gibb's free energy, which is supposed to be conserved.
+
+        Requires that the energies of the atoms are up to date.
+
+        This is mainly intended as a diagnostic tool.  If called before the
+        first timestep, Initialize will be called.
+        """
+        if not self.initialized:
+            self.Initialize()
+        n = self._getnatoms()
+        #tretaTeta = sum(diagonal(matrixmultiply(transpose(self.eta),
+        #                                        self.eta)))
+        contractedeta = sum((self.eta*self.eta).ravel())
+        gibbs = (self.atoms.get_potential_energy() +
+                 self.atoms.get_kinetic_energy()
+                 - sum(self.externalstress[0:3]) * linalg.det(self.h) / 3.0)
+        if self.ttime is not None:
+            gibbs += (1.5 * n * self.temperature * (self.ttime * self.zeta)**2
+                      + 3 * self.temperature * (n-1) * self.zeta_integrated)
+        else:
+            assert self.zeta == 0.0
+        if self.pfactor_given is not None:
+            gibbs += 0.5 / self.pfact * contractedeta
+        else:
+            assert contractedeta == 0.0
+        return gibbs
+
+    def get_center_of_mass_momentum(self):
+        "Get the center of mass momentum."
+        return self.atoms.get_momenta().sum(0)
+
+    def zero_center_of_mass_momentum(self, verbose=0):
+        "Set the center of mass momentum to zero."
+        cm = self.get_center_of_mass_momentum()
+        abscm = sqrt(sum(cm*cm))
+        if verbose and abscm > 1e-4:
+            self._warning(self.classname+": Setting the center-of-mass momentum to zero (was %.6g %.6g %.6g)" % tuple(cm))
+        self.atoms.set_momenta(self.atoms.get_momenta()
+                               - cm / self._getnatoms())
+    
+    def post_pbc_fix(self, fixfuture=1):
+        """Correct for atoms moved by the boundary conditions.
+
+        If the fixfuture argument is 1 (the default), q_future is also
+        corrected.  This is not necessary when post_pbc_fix() is called from
+        within Timestep(), but must be done when the user calls post_pbc_fix
+        (for example if a CNA calculation may have triggered a migration).
+        """
+        q = dot(self.atoms.get_positions(),
+                           self.inv_h) - 0.5
+        delta_q = floor(0.5 + (q - self.q))
+        self.q += delta_q
+        self.q_past += delta_q
+        if fixfuture:
+            self.q_future += delta_q
+        
+    def attach_atoms(self, atoms):
+        """Assign atoms to a restored dynamics object.
+
+        This function must be called to set the atoms immediately after the
+        dynamics object has been read from a trajectory.
+        """
+        try:
+            self.atoms
+        except AttributeError:
+            pass
+        else:
+            raise RuntimeError, "Cannot call attach_atoms on a dynamics which already has atoms."
+        MolecularDynamics.__init__(self, atoms, self.dt)
+        ####self.atoms = atoms
+        limit = 1e-6
+        h = self._getbox()
+        if max(abs((h - self.h).ravel())) > limit:
+            raise RuntimeError, "The unit cell of the atoms does not match the unit cell stored in the file."
+        self.inv_h = linalg.inv(self.h)
+        self._make_special_q_arrays()
+        self.q[:] = dot(self.atoms.get_positions(),
+                                           self.inv_h) - 0.5
+        self._calculate_q_past_and_future()
+        self.initialized = 1
+    
+    def _getbox(self):
+        "Get the computational box."
+        return self.atoms.get_cell()
+
+    def _getmasses(self):
+        "Get the masses as an Nx1 array."
+        return reshape(self.atoms.get_masses(), (-1,1))
+    
+#    def _getcartesianpositions(self):
+#        "Get the cartesian positions of the atoms"
+#        return self.atoms.get_positions()
+    
+#    def _getmomenta(self):
+#        "Get the (cartesian) momenta of the atoms"
+#        return self.atoms.GetCartesianMomenta()
+
+#    def _getforces(self):
+#        "Get the (cartesian) forces of the atoms"
+#        return self.atoms.GetCartesianForces()
+
+#    def _setmomenta(self, momenta):
+#        "Set the (cartesian) momenta of the atoms"
+#        self.atoms.SetCartesianMomenta(momenta)
+        
+    def _separatetrace(self, mat):
+        """return two matrices, one proportional to the identity
+        the other traceless, which sum to the given matrix
+        """
+        tracePart = ((mat[0][0] + mat[1][1] + mat[2][2]) / 3.) * identity(3)
+        return tracePart, mat - tracePart
+
+    # A number of convenient helper methods
+    def _warning(self, text):
+        "Emit a warning."
+        sys.stderr.write("WARNING: "+text+"\n")
+        sys.stderr.flush()
+    
+    def _calculate_q_future(self, force):
+        "Calculate future q.  Needed in Timestep and Initialization."
+        dt = self.dt
+        id3 = identity(3)
+        alpha = (dt * dt) * dot(force / self._getmasses(),
+                                self.inv_h)
+        beta = dt * dot(self.h, dot(self.eta + 0.5 * self.zeta * id3,
+                                    self.inv_h))
+        inv_b = linalg.inv(beta + id3)
+        self.q_future[:] = dot(2*self.q + dot(self.q_past, beta - id3) + alpha,
+                               inv_b)
+
+    def _calculate_q_past_and_future(self):
+        def ekin(p, m = self.atoms.get_masses()):
+            p2 = sum(p*p, -1)
+            return 0.5 * sum(p2 / m) / len(m)
+        p0 = self.atoms.get_momenta()
+        m = self._getmasses()
+        e0 = ekin(p0)
+        p = array(p0, copy=1)
+        dt = self.dt
+        for i in range(2):
+            self.q_past[:] = self.q - dt * dot(p / m, self.inv_h)
+            self._calculate_q_future(self.atoms.get_forces())
+            p = dot(self.q_future - self.q_past, self.h/(2*dt)) * m
+            e = ekin(p)
+            if e < 1e-5:
+                # The kinetic energy and momenta are virtually zero
+                return
+            p = (p0 - p) + p0
+
+    def _initialize_eta_h(self):
+        self.h_past = self.h - self.dt * dot(self.h, self.eta)
+        if self.pfactor_given is None:
+            deltaeta = zeros(6, float)
+        else:
+            deltaeta = (-self.dt * self.pfact * linalg.det(self.h)
+                        * (self.atoms.get_stress() - self.externalstress))
+        if self.frac_traceless == 1:
+            self.eta_past = self.eta - self.mask * self._makeuppertriangular(deltaeta)
+        else:
+            trace_part, traceless_part = self._separatetrace(self._makeuppertriangular(deltaeta))
+            self.eta_past = self.eta - trace_part - self.frac_traceless * traceless_part
+        
+    
+    def _makeuppertriangular(self, sixvector):
+        "Make an upper triangular matrix from a 6-vector."
+        return array(((sixvector[0], sixvector[5], sixvector[4]),
+                      (0,            sixvector[1], sixvector[3]),
+                      (0,            0,            sixvector[2])))
+
+    def _isuppertriangular(self, m):
+        "Check that a matrix is on upper triangular form."
+        return m[1,0] == m[2,0] == m[2,1] == 0.0
+    
+    def _calculateconstants(self):
+        "(Re)calculate some constants when pfactor, ttime or temperature have been changed."
+        n = self._getnatoms()
+        if self.ttime is None:
+            self.tfact = 0.0
+        else:
+            self.tfact = 2.0 / (3 * n * self.temperature *
+                                self.ttime * self.ttime)
+        if self.pfactor_given is None:
+            self.pfact = 0.0
+        else:
+            self.pfact = 1.0 / (self.pfactor_given
+                                * linalg.det(self._getbox()))
+            #self.pfact = 1.0/(n * self.temperature * self.ptime * self.ptime)
+        self.desiredEkin = 1.5 * (n - 1) * self.temperature
+
+    def _setbox_and_positions(self, h, q):
+        """Set the computational box and the positions."""
+        self.atoms.set_cell(h, scale_atoms=True)
+        r = dot(q + 0.5, h)
+        self.atoms.set_positions(r)
+
+    # A few helper methods, which have been placed in separate methods
+    # so they can be replaced in the parallel version.
+    def _synchronize(self):
+        """Synchronizes eta, h and zeta on all processors in a parallel simulation.
+
+        In a parallel simulation, eta, h and zeta are communicated
+        from the master to all slaves, to prevent numerical noise from
+        causing them to diverge.
+
+        In a serial simulation, do nothing.
+        """
+        pass  # This is a serial simulation object.  Do nothing.
+    
+    def _getnatoms(self):
+        """Get the number of atoms.
+
+        In a parallel simulation, this is the total number of atoms on all
+        processors.
+        """
+        return len(self.atoms)
+    
+    def _make_special_q_arrays(self):
+        """Make the arrays used to store data about the atoms.
+
+        In a parallel simulation, these are migrating arrays.  In a
+        serial simulation they are ordinary Numeric arrays.
+        """
+        natoms = len(self.atoms)
+        self.q = zeros((natoms,3), float)
+        self.q_past = zeros((natoms,3), float)
+        self.q_future = zeros((natoms,3), float)
+
+
+
+# class _HooverNPTTrajectory:
+#     """A Trajectory-like object storing data in a HooverNPT object."""
+#     def InitForWrite(self):
+#         """Does initialization related to write mode."""
+#         self.CreateDimension('unlim', None)
+#         self.nc.history = 'ASE NPT trajectory'
+#         self.nc.version = '0.1'
+#         self.nc.classname = self.atoms.classname
+#         self.unlim = 0
+#         self.nc.lengthunit = units.GetLengthUnit()
+#         self.nc.energyunit = units.GetEnergyUnit()
+#         self.conversion = (1, 1)
+
+#     def InitForWriteOrAppend(self):
+#         """Does initialization related to write and append mode.
+
+#         Either InitForWrite or InitForReadOrAppend will have been
+#         called before calling this method.
+#         """
+#         names = copy.copy(self.known_names)
+#         if self.atoms.ttime is None:
+#             del names['ttime']
+#         if self.atoms.pfactor_given is None:
+#             del names['pfactor_given']
+#         for d in names.keys():
+#             def getdata(atoms=self.atoms, name=d):
+#                 return getattr(atoms, name)
+#             self.Add(d, data = getdata)
+                     
+#     known_names = {
+#         #    name                 shape        typecode  once    units
+#         # ----------------------------------------------------------------
+#         'dt':              ((),                Float,    True,   (1, -0.5)),
+#         'temperature':     ((),                Float,    True,   (0, 1)),
+#         'desiredEkin':     ((),                Float,    True,   (0, 1)),
+#         'externalstress':  ((6,),              Float,    True,   (-3, 1)),
+#         'mask':            ((3, 3),            Float,    True,   (0, 0)),
+#         'ttime':           ((),                Float,    True,   (1, -0.5)),
+#         'tfact':           ((),                Float,    True,   (-2, 0)),
+#         'pfactor_given':   ((),                Float,    True,   (-1, 0)),
+#         'pfact':           ((),                Float,    True,   (-2, 0)),
+#         'frac_traceless':  ((),                Float,    True,   (0, 0)),
+#         'eta':             ((3, 3),            Float,    False,  (-1, 0.5)),
+#         'eta_past':        ((3, 3),            Float,    False,  (-1, 0.5)),
+#         'zeta':            ((),                Float,    False,  (-1, 0.5)),
+#         'zeta_past':       ((),                Float,    False,  (-1, 0.5)),
+#         'zeta_integrated': ((),                Float,    False,  (0, 0)),
+#         'h':               ((3, 3),            Float,    False,  (1, 0)),
+#         'h_past':          ((3, 3),            Float,    False,  (1, 0)),
+#         'timeelapsed':     ((),                Float,    False,  (1, -0.5))
+#         }
+
+#     # This trajectory does not store a list of atoms
+#     def GetListOfAtoms(self, frame=None):
+#         raise AttributeError, "GetListOfAtoms makes no sense in a HooverNPTTrajectory"
+
+#     # Instead, we store a dynamics
+#     def GetDynamics(self, frame=None):
+#         """Get a HooverNPT Dynamics object.
+
+#         If a frame number is not given, the current frame is used.
+
+#         The variant of the object (ASE HooverNPT, ASAP Serial/Parallel NPT)
+#         will be the same as the stored object.
+
+#         After getting the dynamics, the atoms should be attached with the
+#         dynamics.attach_atoms(atoms) method.        
+#         """
+#         # Bypass calling the normal constructor
+#         class Dummy:
+#             pass
+#         dyn = Dummy()
+#         dyn.__class__ = self.getClass(self.nc.classname)
+#         vars = self.nc.variables
+#         for q in self.known_names.keys():
+#             if vars.has_key(q):
+#                 once = self.known_names[q][2]
+#                 if once:
+#                     setattr(dyn, q, vars[q].getValue())
+#                 else:
+#                     setattr(dyn, q, vars[q][frame])
+#         return dyn
+
+#     def getClass(self, classname):
+#         "Internal function: turns a class name into a class object."
+#         if self.nc.classname == "HooverNPT":
+#             return HooverNPT
+#         else:
+#             raise RuntimeError, ("Cannot create a dynamics of type "
+#                                  + self.nc.classname)
+
+# class HooverNPTTrajectory(_HooverNPTTrajectory,NetCDFTrajectory):
+#     """A Trajectory-like object storing data in a HooverNPT object."""
+#     def __init__(self, filename, dynamics=None, mode=None, interval=1):
+#         """Open the NetCDF file.
+
+#         If there is no ``dynamics`` argument, then the file is opened
+#         in read mode - otherwise, write or append mode is used.  The
+#         ``interval`` argument determines how often the configurations
+#         are written to file."""
+#         # Call the original constructor, but passing the dynamics instead of
+#         # the atoms.
+#         if dynamics is not None:
+#             # Prevents a circular reference when the trajectory is attached
+#             # to the dynamics it observes.
+#             dynamics = weakref.proxy(dynamics)
+#         NetCDFTrajectory.__init__(self, filename,
+#                                   atoms=dynamics,
+#                                   mode=mode, interval=interval)
+
+    
+
Index: ase/md/verlet.py
===================================================================
--- ase/md/verlet.py	(revision 0)
+++ ase/md/verlet.py	(revision 1)
@@ -0,0 +1,26 @@
+import numpy as np
+
+from ase.md.md import MolecularDynamics
+
+
+class VelocityVerlet(MolecularDynamics):
+    def __init__(self, atoms, dt, trajectory=None, logfile=None,
+                 loginterval=1):
+        MolecularDynamics.__init__(self, atoms, dt, trajectory, logfile,
+                                   loginterval)
+            
+    def step(self, f):
+        atoms = self.atoms
+        p = self.atoms.get_momenta()
+        p += 0.5 * self.dt * f
+        self.atoms.set_positions(self.atoms.get_positions() +
+            self.dt * p / self.atoms.get_masses()[:,np.newaxis])
+        # We need to store the momenta on the atoms before calculating
+        # the forces, as in a parallel Asap calculation atoms may
+        # migrate during force calculations, and the momenta need to
+        # migrate along with the atoms.  For the same reason, we
+        # cannot use self.masses in the line above.
+        self.atoms.set_momenta(p)
+        f = self.atoms.get_forces()
+        atoms.set_momenta(self.atoms.get_momenta() + 0.5 * self.dt * f)
+        return f
Index: ase/md/langevin.py
===================================================================
--- ase/md/langevin.py	(revision 0)
+++ ase/md/langevin.py	(revision 1)
@@ -0,0 +1,136 @@
+"""Langevin dynamics class."""
+
+
+import sys
+import numpy as np
+from numpy.random import standard_normal
+from ase.md.md import MolecularDynamics
+
+# For parallel GPAW simulations, the random forces should be distributed.
+if '_gpaw' in sys.modules:
+    # http://wiki.fysik.dtu.dk/gpaw
+    from gpaw.mpi import world as gpaw_world
+else:
+    gpaw_world = None
+
+class Langevin(MolecularDynamics):
+    """Langevin (constant N, V, T) molecular dynamics.
+
+    Usage: Langevin(atoms, dt, temperature, friction)
+
+    atoms
+        The list of atoms.
+        
+    dt
+        The time step.
+
+    temperature
+        The desired temperature, in energy units.
+
+    friction
+        A friction coefficient, typically 1e-4 to 1e-2.
+
+    fixcm
+        If True, the position and momentum of the center of mass is
+        kept unperturbed.  Default: True.
+
+    The temperature and friction are normally scalars, but in principle one
+    quantity per atom could be specified by giving an array.
+
+    This dynamics accesses the atoms using Cartesian coordinates."""
+    
+    def __init__(self, atoms, timestep, temperature, friction, fixcm=True,
+                 trajectory=None, logfile=None, loginterval=1,
+                 communicator=gpaw_world):
+        MolecularDynamics.__init__(self, atoms, timestep, trajectory,
+                                   logfile, loginterval)
+        self.temp = temperature
+        self.frict = friction
+        self.fixcm = fixcm  # will the center of mass be held fixed?
+        self.communicator = communicator
+        self.updatevars()
+        
+    def set_temperature(self, temperature):
+        self.temp = temperature
+        self.updatevars()
+
+    def set_friction(self, friction):
+        self.frict = friction
+        self.updatevars()
+
+    def set_timestep(self, timestep):
+        self.dt = timestep
+        self.updatevars()
+
+    def updatevars(self):
+        dt = self.dt
+        # If the friction is an array some other constants must be arrays too.
+        self._localfrict = hasattr(self.frict, 'shape')
+        lt = self.frict * dt
+        masses = self.masses
+        sdpos = dt * np.sqrt(self.temp / masses * (2.0/3.0 - 0.5 * lt) * lt)
+        sdpos.shape = (-1, 1)
+        sdmom = np.sqrt(self.temp * masses * 2.0 * (1.0 - lt) * lt)
+        sdmom.shape = (-1, 1)
+        pmcor = np.sqrt(3.0)/2.0 * (1.0 - 0.125 * lt)
+        cnst = np.sqrt((1.0 - pmcor) * (1.0 + pmcor))
+
+        act0 = 1.0 - lt + 0.5 * lt * lt
+        act1 = (1.0 - 0.5 * lt + (1.0/6.0) * lt * lt)
+        act2 = 0.5 - (1.0/6.0) * lt + (1.0/24.0) * lt * lt
+        c1 = act1 * dt / masses
+        c1.shape = (-1, 1)
+        c2 = act2 * dt * dt / masses
+        c2.shape = (-1, 1)
+        c3 = (act1 - act2) * dt
+        c4 = act2 * dt
+        del act1, act2
+        if self._localfrict:
+            # If the friction is an array, so are these
+            act0.shape = (-1, 1)
+            c3.shape = (-1, 1)
+            c4.shape = (-1, 1)
+            pmcor.shape = (-1, 1)
+            cnst.shape = (-1, 1)
+        self.sdpos = sdpos
+        self.sdmom = sdmom
+        self.c1 = c1
+        self.c2 = c2
+        self.act0 = act0
+        self.c3 = c3
+        self.c4 = c4
+        self.pmcor = pmcor
+        self.cnst = cnst
+
+    def step(self, f):
+        atoms = self.atoms
+        p = self.atoms.get_momenta()
+
+        random1 = standard_normal(size=(len(atoms), 3))
+        random2 = standard_normal(size=(len(atoms), 3))
+
+        if self.communicator is not None:
+            self.communicator.broadcast(random1, 0)
+            self.communicator.broadcast(random2, 0)
+        
+        rrnd = self.sdpos * random1
+        prnd = (self.sdmom * self.pmcor * random1 +
+                self.sdmom * self.cnst * random2)
+
+        if self.fixcm:
+            rrnd = rrnd - np.sum(rrnd, 0) / len(atoms)
+            prnd = prnd - np.sum(prnd, 0) / len(atoms)
+            n = len(atoms)
+            rrnd *= np.sqrt(n / (n - 1.0))
+            prnd *= np.sqrt(n / (n - 1.0))
+
+        atoms.set_positions(atoms.get_positions() +
+                            self.c1 * p +
+                            self.c2 * f + rrnd)
+        p *= self.act0
+        p += self.c3 * f + prnd
+        atoms.set_momenta(p)
+                      
+        f = atoms.get_forces()
+        atoms.set_momenta(p + self.c4 * f)
+        return f
Index: ase/svnversion.py
===================================================================
--- ase/svnversion.py	(revision 0)
+++ ase/svnversion.py	(revision 1)
@@ -0,0 +1 @@
+svnversion = "1765"
Index: ase/old.py
===================================================================
--- ase/old.py	(revision 0)
+++ ase/old.py	(revision 1)
@@ -0,0 +1,194 @@
+import numpy as np
+try:
+    import Numeric as num
+except ImportError:
+    pass
+else:
+    def npy2num(a, typecode=num.Float):
+        return num.array(a, typecode)
+    if num.__version__ <= '23.8':
+        #def npy2num(a, typecode=num.Float):
+        #    return num.array(a.tolist(), typecode)
+        def npy2num(a, typecode=num.Float):
+            b = num.fromstring(a.tostring(), typecode)
+            b.shape = a.shape
+            return b
+    
+from ase.data import chemical_symbols
+
+
+class OldASEListOfAtomsWrapper:
+    def __init__(self, atoms):
+        self.atoms = atoms
+        self.constraints = []
+
+    def get_positions(self):
+        return np.array(self.atoms.GetCartesianPositions())
+
+    def get_calculator(self):
+        calc = self.atoms.GetCalculator()
+        if calc is not None:
+            return OldASECalculatorWrapper(calc)
+
+    def get_potential_energy(self):
+        return self.atoms.GetPotentialEnergy()
+
+    def get_forces(self):
+        return np.array(self.atoms.GetCartesianForces())
+
+    def get_stress(self):
+        return np.array(self.atoms.GetStress())
+
+    def get_atomic_numbers(self):
+        return np.array(self.atoms.GetAtomicNumbers())
+
+    def get_tags(self):
+        return np.array(self.atoms.GetTags())
+    
+    def get_momenta(self):
+        return np.array(self.atoms.GetCartesianMomenta())
+    
+    def get_masses(self):
+        return np.array(self.atoms.GetMasses())
+    
+    def get_initial_magnetic_moments(self):
+        return np.array(self.atoms.GetMagneticMoments())
+    
+    def get_magnetic_moments(self):
+        return None
+    
+    def get_charges(self):
+        return np.zeros(len(self))
+
+    def has(self, name):
+        return True
+    
+    def get_cell(self):
+        return np.array(self.atoms.GetUnitCell())
+
+    def get_pbc(self):
+        return np.array(self.atoms.GetBoundaryConditions(), bool)
+
+    def __len__(self):
+        return len(self.atoms)
+
+    def copy(self):
+        from ase.atoms import Atoms
+        return Atoms(positions=self.get_positions(),
+                     numbers=self.get_atomic_numbers(),
+                     tags=self.get_tags(),
+                     momenta=self.get_momenta(),
+                     masses=self.get_masses(),
+                     magmoms=self.get_initial_magnetic_moments(),
+                     charges=self.get_charges(),
+                     cell=self.get_cell(),
+                     pbc=self.get_pbc(),
+                     constraint=None,
+                     calculator=None) # Don't copy the calculator
+
+
+class OldASECalculatorWrapper:
+    def __init__(self, calc, atoms=None):
+        self.calc = calc
+            
+        if atoms is None:
+            try:
+                self.atoms = calc.GetListOfAtoms()
+            except AttributeError:
+                self.atoms = None
+        else:
+            from ASE import Atom, ListOfAtoms
+            
+            numbers = atoms.get_atomic_numbers()
+            positions = atoms.get_positions()
+            magmoms = atoms.get_initial_magnetic_moments()
+            self.atoms = ListOfAtoms(
+                [Atom(Z=numbers[a], position=positions[a], magmom=magmoms[a])
+                 for a in range(len(atoms))],
+                cell=npy2num(atoms.get_cell()),
+                periodic=tuple(atoms.get_pbc()))
+            self.atoms.SetCalculator(calc)
+
+    def get_atoms(self):
+        return OldASEListOfAtomsWrapper(self.atoms)
+            
+    def get_potential_energy(self, atoms):
+        self.atoms.SetCartesianPositions(npy2num(atoms.get_positions()))
+        self.atoms.SetUnitCell(npy2num(atoms.get_cell()), fix=True)
+        return self.calc.GetPotentialEnergy()
+
+    def get_forces(self, atoms):
+        self.atoms.SetCartesianPositions(npy2num(atoms.get_positions()))
+        self.atoms.SetUnitCell(npy2num(atoms.get_cell()), fix=True)
+        return np.array(self.calc.GetCartesianForces())
+
+    def get_stress(self, atoms):
+        self.atoms.SetCartesianPositions(npy2num(atoms.get_positions()))
+        self.atoms.SetUnitCell(npy2num(atoms.get_cell()), fix=True)
+        return np.array(self.calc.GetStress())
+
+    def get_number_of_bands(self):
+        return self.calc.GetNumberOfBands()
+
+    def get_kpoint_weights(self):
+        return np.array(self.calc.GetIBZKPointWeights())
+
+    def get_number_of_spins(self):
+        return 1 + int(self.calc.GetSpinPolarized())
+
+    def get_eigenvalues(self, kpt=0, spin=0):
+        return np.array(self.calc.GetEigenvalues(kpt, spin))
+
+    def get_fermi_level(self):
+        return self.calc.GetFermiLevel()
+
+    def get_number_of_grid_points(self):
+        return np.array(self.get_pseudo_wave_function(0, 0, 0).shape)
+
+    def get_pseudo_wave_function(self, n=0, k=0, s=0, pad=True):
+        kpt = self.get_bz_k_points()[k]
+        state = self.calc.GetElectronicStates().GetState(band=n, spin=s,
+                                                         kptindex=k)
+
+        # Get wf, without bolch phase (Phase = True doesn't do anything!)
+        wave = state.GetWavefunctionOnGrid(phase=False)
+
+        # Add bloch phase if this is not the Gamma point
+        if np.all(kpt == 0):
+            return wave
+        coord = state.GetCoordinates()
+        phase = coord[0] * kpt[0] + coord[1] * kpt[1] + coord[2] * kpt[2]
+        return np.array(wave) * np.exp(-2.j * np.pi * phase) # sign! XXX
+
+        #return np.array(self.calc.GetWaveFunctionArray(n, k, s)) # No phase!
+
+    def get_bz_k_points(self):
+        return np.array(self.calc.GetBZKPoints())
+
+    def get_ibz_k_points(self):
+        return np.array(self.calc.GetIBZKPoints())
+
+    def get_wannier_localization_matrix(self, nbands, dirG, kpoint,
+                                        nextkpoint, G_I, spin):
+        return np.array(self.calc.GetWannierLocalizationMatrix(
+            G_I=G_I.tolist(), nbands=nbands, dirG=dirG.tolist(),
+            kpoint=kpoint, nextkpoint=nextkpoint, spin=spin))
+    
+    def initial_wannier(self, initialwannier, kpointgrid, fixedstates,
+                        edf, spin):
+        # Use initial guess to determine U and C
+        init = self.calc.InitialWannier(initialwannier, self.atoms,
+                                        npy2num(kpointgrid, num.Int))
+
+        states = self.calc.GetElectronicStates()
+        waves = [[state.GetWaveFunction()
+                  for state in states.GetStatesKPoint(k, spin)]
+                 for k in self.calc.GetIBZKPoints()] 
+
+        init.SetupMMatrix(waves, self.calc.GetBZKPoints())
+        c, U = init.GetListOfCoefficientsAndRotationMatrices(
+            (self.calc.GetNumberOfBands(), fixedstates, edf))
+        U = np.array(U)
+        for k in range(len(c)):
+            c[k] = np.array(c[k])
+        return c, U
Index: ase/vibrations.py
===================================================================
--- ase/vibrations.py	(revision 0)
+++ ase/vibrations.py	(revision 1)
@@ -0,0 +1,390 @@
+# -*- coding: utf-8 -*-
+
+"""Vibrational modes."""
+
+import pickle
+from math import sin, pi, sqrt
+from os import remove
+from os.path import isfile
+import sys
+
+import numpy as np
+
+import ase.units as units
+from ase.io.trajectory import PickleTrajectory
+from ase.parallel import rank, barrier, paropen
+
+
+class Vibrations:
+    """Class for calculating vibrational modes using finite difference.
+
+    The vibrational modes are calculated from a finite difference
+    approximation of the Hessian matrix.
+
+    The *summary()*, *get_energies()* and *get_frequencies()*
+    methods all take an optional *method* keyword.  Use
+    method='Frederiksen' to use the method described in:
+
+      T. Frederiksen, M. Paulsson, M. Brandbyge, A. P. Jauho:
+      "Inelastic transport theory from first-principles: methodology
+      and applications for nanoscale devices", 
+      Phys. Rev. B 75, 205413 (2007) 
+
+    atoms: Atoms object
+        The atoms to work on.
+    indices: list of int
+        List of indices of atoms to vibrate.  Default behavior is
+        to vibrate all atoms.
+    name: str
+        Name to use for files.
+    delta: float
+        Magnitude of displacements.
+    nfree: int
+        Number of displacements per atom and cartesian coordinate,
+        2 and 4 are supported. Default is 2 which will displace 
+        each atom +delta and -delta for each cartesian coordinate.
+
+    Example:
+
+    >>> from ase import Atoms
+    >>> from ase.calculators.emt import EMT
+    >>> from ase.optimizers import BFGS
+    >>> from ase.vibrations import Vibrations
+    >>> n2 = Atoms('N2', [(0, 0, 0), (0, 0, 1.1)],
+    ...            calculator=EMT())
+    >>> BFGS(n2).run(fmax=0.01)
+    BFGS:   0  19:16:06        0.042171       2.9357
+    BFGS:   1  19:16:07        0.104197       3.9270
+    BFGS:   2  19:16:07        0.000963       0.4142
+    BFGS:   3  19:16:07        0.000027       0.0698
+    BFGS:   4  19:16:07        0.000000       0.0010
+    >>> vib = Vibrations(n2)
+    >>> vib.run()
+    >>> vib.summary()
+    ---------------------
+      #    meV     cm^-1
+    ---------------------
+      0    0.0i      0.0i
+      1    0.0i      0.0i
+      2    0.0i      0.0i
+      3    1.6      13.1 
+      4    1.6      13.1 
+      5  232.7    1877.2 
+    ---------------------
+    Zero-point energy: 0.118 eV
+    Thermodynamic properties at 298.00 K
+    Enthalpy: 0.050 eV
+    Entropy : 0.648 meV/K
+    T*S     : 0.193 eV
+    E->G    : -0.025 eV
+
+    >>> vib.write_mode(-1)  # write last mode to trajectory file
+
+    """
+    def __init__(self, atoms, indices=None, name='vib', delta=0.01, nfree=2):
+        assert nfree in [2, 4]
+        self.atoms = atoms
+        if indices is None:
+            indices = range(len(atoms))
+        self.indices = np.asarray(indices)
+        self.name = name
+        self.delta = delta
+        self.nfree = nfree
+        self.H = None
+        self.ir = None
+
+    def run(self):
+        """Run the vibration calculations.
+
+        This will calculate the forces for 6 displacements per atom
+        ±x, ±y, ±z.  Only those calculations that are not already done
+        will be started. Be aware that an interrupted calculation may
+        produce an empty file (ending with .pckl), which must be deleted
+        before restarting the job. Otherwise the forces will not be
+        calculated for that displacement."""
+        filename = self.name + '.eq.pckl'
+        if not isfile(filename):
+            barrier()
+            forces = self.atoms.get_forces()
+            if self.ir:
+                dipole = self.calc.get_dipole_moment(self.atoms)
+            if rank == 0:
+                fd = open(filename, 'w')
+                if self.ir:
+                    pickle.dump([forces, dipole], fd)
+                    sys.stdout.write(
+                        'Writing %s, dipole moment = (%.6f %.6f %.6f)\n' % 
+                        (filename, dipole[0], dipole[1], dipole[2]))
+                else:
+                    pickle.dump(forces, fd)
+                    sys.stdout.write('Writing %s\n' % filename)
+                fd.close()
+            sys.stdout.flush()
+        
+        p = self.atoms.positions.copy()
+        for a in self.indices:
+            for i in range(3):
+                for sign in [-1, 1]:
+                    for ndis in range(1, self.nfree//2+1):
+                        filename = ('%s.%d%s%s.pckl' %
+                                    (self.name, a, 'xyz'[i], ndis*' +-'[sign]))
+                        if isfile(filename):
+                            continue
+                        barrier()
+                        self.atoms.positions[a, i] = (p[a, i] +
+                                                      ndis * sign * self.delta)
+                        forces = self.atoms.get_forces()
+                        if self.ir:
+                            dipole = self.calc.get_dipole_moment(self.atoms)
+                        if rank == 0:
+                            fd = open(filename, 'w')
+                            if self.ir:
+                                pickle.dump([forces, dipole], fd)
+                                sys.stdout.write(
+                                    'Writing %s, ' % filename +
+                                    'dipole moment = (%.6f %.6f %.6f)\n' % 
+                                    (dipole[0], dipole[1], dipole[2]))
+                            else:
+                                pickle.dump(forces, fd)
+                                sys.stdout.write('Writing %s\n' % filename)
+                            fd.close()
+                        sys.stdout.flush()
+                        self.atoms.positions[a, i] = p[a, i]
+        self.atoms.set_positions(p)
+
+    def clean(self):
+        if isfile(self.name + '.eq.pckl'):
+            remove(self.name + '.eq.pckl')
+        
+        for a in self.indices:
+            for i in 'xyz':
+                for sign in '-+':
+                    for ndis in range(1, self.nfree/2+1):
+                        name = '%s.%d%s%s.pckl' % (self.name, a, i, ndis*sign)
+                        if isfile(name):
+                            remove(name)
+        
+    def read(self, method='standard', direction='central'):
+        self.method = method.lower()
+        self.direction = direction.lower()
+        assert self.method in ['standard', 'frederiksen']
+        assert self.direction in ['central', 'forward', 'backward']
+        
+        n = 3 * len(self.indices)
+        H = np.empty((n, n))
+        r = 0
+        if direction != 'central':
+            feq = pickle.load(open(self.name + '.eq.pckl'))
+        for a in self.indices:
+            for i in 'xyz':
+                name = '%s.%d%s' % (self.name, a, i)
+                fminus = pickle.load(open(name + '-.pckl'))
+                fplus = pickle.load(open(name + '+.pckl'))
+                if self.method == 'frederiksen':
+                    fminus[a] -= fminus.sum(0)
+                    fplus[a] -= fplus.sum(0)
+                if self.nfree == 4:
+                    fminusminus = pickle.load(open(name + '--.pckl'))
+                    fplusplus = pickle.load(open(name + '++.pckl'))
+                    if self.method == 'frederiksen':
+                        fminusminus[a] -= fminusminus.sum(0)
+                        fplusplus[a] -= fplusplus.sum(0)
+                if self.direction == 'central':
+                    if self.nfree == 2:
+                        H[r] = .5 * (fminus - fplus)[self.indices].ravel()
+                    else:
+                        H[r] = H[r] = (-fminusminus +
+                                       8 * fminus -
+                                       8 * fplus +
+                                       fplusplus)[self.indices].ravel() / 12.0
+                elif self.direction == 'forward':
+                    H[r] = (feq - fplus)[self.indices].ravel()
+                else: # self.direction == 'backward':
+                    H[r] = (fminus - feq)[self.indices].ravel()
+                H[r] /= 2 * self.delta
+                r += 1
+        H += H.copy().T
+        self.H = H
+        m = self.atoms.get_masses()
+        self.im = np.repeat(m[self.indices]**-0.5, 3)
+        omega2, modes = np.linalg.eigh(self.im[:, None] * H * self.im)
+        self.modes = modes.T.copy()
+
+        # Conversion factor:
+        s = units._hbar * 1e10 / sqrt(units._e * units._amu)
+        self.hnu = s * omega2.astype(complex)**0.5
+
+    def get_energies(self, method='standard', direction='central'):
+        """Get vibration energies in eV."""
+        if (self.H is None or method.lower() != self.method or
+            direction.lower() != self.direction):
+            self.read(method, direction)
+        return self.hnu
+
+    def get_frequencies(self, method='standard', direction='central'):
+        """Get vibration frequencies in cm^-1."""
+        s = 0.01 * units._e / units._c / units._hplanck
+        return s * self.get_energies(method, direction)
+
+    def summary(self, method='standard', direction='central', T=298., 
+                threshold=10, freq=None, log=sys.stdout):
+        """Print a summary of the frequencies and derived thermodynamic
+        properties. The equations for the calculation of the enthalpy and 
+        entropy diverge for zero frequencies and a threshold value is used
+        to ignore extremely low frequencies (default = 10 cm^-1).
+
+        Parameters:
+
+        method : string
+            Can be 'standard'(default) or 'Frederiksen'.
+        direction: string
+            Direction for finite differences. Can be one of 'central'
+            (default), 'forward', 'backward'.
+        T : float
+            Temperature in K at which thermodynamic properties are calculated.
+        threshold : float
+            Threshold value for low frequencies (default 10 cm^-1).
+        freq : numpy array
+            Optional. Can be used to create a summary on a set of known
+            frequencies.
+        destination : string or object with a .write() method
+            Where to print the summary info. Default is to print to
+            standard output. If another string is given, it creates that
+            text file and writes the output to it. If a file object (or any
+            object with a .write(string) function) is given, it writes to that
+            file.
+
+        Notes:
+
+        The enthalpy and entropy calculations are very sensitive to low
+        frequencies and the threshold value must be used with caution."""
+
+        if isinstance(log, str):
+            log = paropen(log, 'a')
+        write = log.write
+        
+        s = 0.01 * units._e / units._c / units._hplanck
+        if freq != None:
+            hnu = freq / s
+        else:
+            hnu = self.get_energies(method, direction)
+        write('---------------------\n')
+        write('  #    meV     cm^-1\n')
+        write('---------------------\n')
+        for n, e in enumerate(hnu):
+            if e.imag != 0:
+                c = 'i'
+                e = e.imag
+            else:
+                c = ' '
+            write('%3d %6.1f%s  %7.1f%s\n' % (n, 1000 * e, c, s * e, c))
+        write('---------------------\n')
+        write('Zero-point energy: %.3f eV\n' %
+              self.get_zero_point_energy(freq=freq))
+        write('Thermodynamic properties at %.2f K\n' % T)
+        write('Enthalpy: %.3f eV\n' % self.get_enthalpy(method=method,
+                                                        direction=direction,
+                                                        T=T,
+                                                        threshold=threshold,
+                                                        freq=freq))
+        write('Entropy : %.3f meV/K\n' % 
+              (1E3 * self.get_entropy(method=method,
+                                      direction=direction,
+                                      T=T,
+                                      threshold=threshold,
+                                      freq=freq)))
+        write('T*S     : %.3f eV\n' %
+              (T * self.get_entropy(method=method,
+                                    direction=direction,
+                                    T=T,
+                                    threshold=threshold,
+                                    freq=freq)))
+        write('E->G    : %.3f eV\n' %
+              (self.get_zero_point_energy(freq=freq) +
+               self.get_enthalpy(method=method,
+                                 direction=direction,
+                                 T=T,
+                                 threshold=threshold,
+                                 freq=freq) -
+               T * self.get_entropy(method=method,
+                                    direction=direction,
+                                    T=T,
+                                    threshold=threshold,
+                                    freq=freq)))
+
+    def get_zero_point_energy(self, freq=None):
+        if freq is None:
+            return 0.5 * self.hnu.real.sum()
+        else:
+            s = 0.01 * units._e / units._c / units._hplanck
+            return 0.5 * freq.real.sum() / s
+
+    def get_enthalpy(self, method='standard', direction='central', T=298.0,
+                     threshold=10, freq=None):
+        H = 0.0
+        if freq is None:
+            freq = self.get_frequencies(method=method, direction=direction)
+        for f in freq:
+            if f.imag == 0 and f.real >= threshold:
+                # The formula diverges for f->0
+                x = (f.real * 100 * units._hplanck * units._c) / units._k
+                H += units._k / units._e * x / (np.exp(x / T) - 1)
+        return H
+
+    def get_entropy(self, method='standard', direction='central', T=298.0,
+                    threshold=10, freq=None):
+        S = 0.0
+        if freq is None:
+            freq=self.get_frequencies(method=method, direction=direction)
+        for f in freq:
+            if f.imag == 0 and f.real >= threshold:
+                # The formula diverges for f->0
+                x = (f.real * 100 * units._hplanck * units._c) / units._k
+                S += (units._k / units._e * (((x / T) / (np.exp(x / T) - 1)) -
+                                             np.log(1 - np.exp(-x / T))))
+        return S
+
+    def get_mode(self, n):
+        mode = np.zeros((len(self.atoms), 3))
+        mode[self.indices] = (self.modes[n] * self.im).reshape((-1, 3))
+        return mode
+
+    def write_mode(self, n, kT=units.kB * 300, nimages=30):
+        """Write mode to trajectory file."""
+        mode = self.get_mode(n) * sqrt(kT / abs(self.hnu[n]))
+        p = self.atoms.positions.copy()
+        n %= 3 * len(self.indices)
+        traj = PickleTrajectory('%s.%d.traj' % (self.name, n), 'w')
+        calc = self.atoms.get_calculator()
+        self.atoms.set_calculator()
+        for x in np.linspace(0, 2 * pi, nimages, endpoint=False):
+            self.atoms.set_positions(p + sin(x) * mode)
+            traj.write(self.atoms)
+        self.atoms.set_positions(p)
+        self.atoms.set_calculator(calc)
+        traj.close()
+
+    def write_jmol(self):
+        """Writes file for viewing of the modes with jmol."""
+
+        fd = open(self.name + '.xyz', 'w')
+        symbols = self.atoms.get_chemical_symbols()
+        f = self.get_frequencies()
+        for n in range(3 * len(self.atoms)):
+            fd.write('%6d\n' % len(self.atoms))
+            if f[n].imag != 0:
+                c = 'i'
+                f[n] = f[n].imag
+            else:
+                c = ' ' 
+            fd.write('Mode #%d, f = %.1f%s cm^-1' % (n, f[n], c))
+            if self.ir:
+                fd.write(', I = %.4f (D/Å)^2 amu^-1.\n' % self.intensities[n])
+            else:
+                fd.write('.\n')
+            mode = self.get_mode(n)
+            for i, pos in enumerate(self.atoms.positions):
+                fd.write('%2s %12.5f %12.5f %12.5f %12.5f %12.5f %12.5f \n' % 
+                         (symbols[i], pos[0], pos[1], pos[2],
+                          mode[i,0], mode[i,1], mode[i,2]))
+        fd.close()
Index: ase/io/cube.py
===================================================================
--- ase/io/cube.py	(revision 0)
+++ ase/io/cube.py	(revision 1)
@@ -0,0 +1,104 @@
+"""
+IO support for the Gaussian cube format.
+
+See the format specifications on:
+http://local.wasp.uwa.edu.au/~pbourke/dataformats/cube/
+"""
+
+
+import numpy as np
+
+from ase.atoms import Atoms
+from ase.units import Bohr
+from ase.parallel import paropen
+
+
+def write_cube(fileobj, atoms, data=None):
+    if isinstance(fileobj, str):
+        fileobj = paropen(fileobj, 'w')
+        
+    if isinstance(atoms, list):
+        if len(atoms) > 1:
+            raise ValueError('Can only write one configuration '
+                             'to a cube file!')
+        atoms = atoms[0]
+
+    if data is None:
+        data = np.ones((2, 2, 2))
+    data = np.asarray(data)
+
+    if data.dtype == complex:
+        data = np.abs(data)
+
+    fileobj.write('cube file from ase\n')
+    fileobj.write('OUTER LOOP: X, MIDDLE LOOP: Y, INNER LOOP: Z\n')
+
+    cell = atoms.get_cell()
+    shape = np.array(data.shape)
+
+    corner = np.zeros(3)
+    for i in range(3):
+        if shape[i] % 2 == 1:
+            shape[i] += 1
+            corner += cell[i] / shape[i] / Bohr
+
+    fileobj.write('%5d%12.6f%12.6f%12.6f\n' % (len(atoms), corner[0],
+                                               corner[1], corner[2]))
+
+    for i in range(3):
+        n = data.shape[i]
+        d = cell[i] / shape[i] / Bohr
+        fileobj.write('%5d%12.6f%12.6f%12.6f\n' % (n, d[0], d[1], d[2]))
+
+    positions = atoms.get_positions() / Bohr
+    numbers = atoms.get_atomic_numbers()
+    for Z, (x, y, z) in zip(numbers, positions):
+        fileobj.write('%5d%12.6f%12.6f%12.6f%12.6f\n' % (Z, 0.0, x, y, z)) 
+
+    data.tofile(fileobj, sep='\n', format='%e')
+
+
+def read_cube(fileobj, index=-1, read_data=False):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    readline = fileobj.readline
+    readline()
+    axes = ['XYZ'.index(s[0]) for s in readline().split()[2::3]]
+    if axes == []:
+        axes = [0, 1, 2]
+    line = readline().split()
+    natoms = int(line[0])
+    corner = [Bohr * float(x) for x in line[1:]]
+
+    cell = np.empty((3, 3))
+    shape = []
+    for i in range(3):
+        n, x, y, z = [float(s) for s in readline().split()]
+        shape.append(n)
+        if n % 2 == 1:
+            n += 1
+        cell[i] = n * Bohr * np.array([x, y, z])
+        
+    numbers = np.empty(natoms, int)
+    positions = np.empty((natoms, 3))
+    for i in range(natoms):
+        line = readline().split()
+        numbers[i] = int(line[0])
+        positions[i] = [float(s) for s in line[2:]]
+
+    positions *= Bohr
+    atoms = Atoms(numbers=numbers, positions=positions, cell=cell)
+
+    if read_data:
+        data = np.array([float(s)
+                         for s in fileobj.read().split()]).reshape(shape)
+        if axes != [0, 1, 2]:
+            data = data.transpose(axes).copy()
+        return data, atoms
+
+    return atoms
+
+
+def read_cube_data(fileobj):
+    return read_cube(fileobj, read_data=True)
Index: ase/io/dftb.py
===================================================================
--- ase/io/dftb.py	(revision 0)
+++ ase/io/dftb.py	(revision 1)
@@ -0,0 +1,110 @@
+from ase.atoms import Atoms
+from ase.units import Bohr
+
+
+def read_dftb(filename='dftb_in.hsd'):
+    """Method to read coordinates form DFTB+ input file dftb_in.hsd
+    additionally read information about fixed atoms
+    and periodic boundary condition
+    """
+    from ase import Atoms, Atom
+    from ase.constraints import FixAtoms
+    import sys, string
+
+    if isinstance(filename, str):
+        f = open(filename)
+
+    lines = f.readlines()
+    atoms_pos = []
+    atom_symbols = []
+    type_names = []
+    my_pbc = False
+    myconstraints=[]
+    moved_atoms_found = False
+    range_found = False
+    moved_atoms = []
+
+    for line in lines:
+        if (line.strip().startswith('#')):
+            pass
+        else:
+            if ('TypeNames' in line):
+                col=line.split()
+                for i in range(3,len(col)-1):
+                    type_names.append(col[i].strip("\""))
+            elif ('Periodic' in line):
+                if ('Yes' in line):
+                    my_pbc = True
+            else:
+                pass
+
+    start_reading_coords=False
+    stop_reading_coords=False
+    for line in lines:
+        if (line.strip().startswith('#')):
+            pass
+        else:
+            if ('TypesAndCoordinates' in line):
+                start_reading_coords=True
+            if start_reading_coords:
+                if ('}' in line):
+                    stop_reading_coords=True
+            if (start_reading_coords and not(stop_reading_coords)
+            and not 'TypesAndCoordinates' in line):
+                typeindexstr, x, y, z = line.split()[:4]
+                typeindex=int(typeindexstr)
+                symbol=type_names[typeindex-1]
+                atom_symbols.append(symbol)
+                atoms_pos.append([float(x), float(y), float(z)])
+
+            
+    if type(filename) == str:
+        f.close()
+
+    atoms = Atoms(positions = atoms_pos, symbols = atom_symbols, pbc = my_pbc)
+
+
+    return atoms
+
+
+def write_dftb(filename,atoms):
+    """Method to write coordinates in DFTB+ format
+    """
+
+    import numpy as np
+    from ase.constraints import FixAtoms
+
+    if isinstance(filename, str):
+        f = open(filename)
+
+    lines = f.readlines()
+
+    if type(filename) == str:
+        f.close()
+
+    if isinstance(filename, str):
+        f = open(filename, 'w')
+    else: # Assume it's a 'file-like object'
+        f = filename
+
+    coord = atoms.get_positions()
+    symbols = atoms.get_chemical_symbols()
+
+
+    start_writing_coords = False
+    stop_writing_coords = False
+    i=0
+    for line in lines:
+        if ('TypesAndCoordinates' in line):
+            start_writing_coords=True
+        if (start_writing_coords and not(stop_writing_coords)):
+            if ('}' in line):
+                stop_writing_coords = True
+        if (start_writing_coords and not(stop_writing_coords)and 
+            not ('TypesAndCoordinates' in line)):
+            atom_type_index = line.split()[0]
+            f.write('%6s  %20.14f  %20.14f  %20.14f\n'
+                    % (atom_type_index,coord[i][0],coord[i][1],coord[i][2]))
+            i=i+1
+        else:
+            f.write(line)
Index: ase/io/png.py
===================================================================
--- ase/io/png.py	(revision 0)
+++ ase/io/png.py	(revision 1)
@@ -0,0 +1,37 @@
+from ase.io.eps import EPS
+
+
+class PNG(EPS):
+    def write_header(self):
+        from matplotlib.backends.backend_agg import RendererAgg
+
+        try:
+            from matplotlib.transforms import Value
+        except ImportError:
+            dpi = 72
+        else:
+            dpi = Value(72)
+
+        self.renderer = RendererAgg(self.w, self.h, dpi)
+
+        #self.gc = GraphicsContextBase()
+        #self.gc.set_linewidth(2)
+
+    def write_trailer(self):
+        renderer = self.renderer
+        if hasattr(renderer._renderer, 'write_png'):
+            # Old version of matplotlib:
+            renderer._renderer.write_png(self.filename)
+        else:
+            x = renderer._renderer.buffer_rgba(0, 0)
+            from matplotlib import _png
+            _png.write_png(renderer._renderer.buffer_rgba(0, 0),
+                           renderer.width, renderer.height,
+                           self.filename, 72)
+
+
+def write_png(filename, atoms, **parameters):
+    if isinstance(atoms, list):
+        assert len(atoms) == 1
+        atoms = atoms[0]
+    PNG(atoms, **parameters).write(filename)
Index: ase/io/vtkxml.py
===================================================================
--- ase/io/vtkxml.py	(revision 0)
+++ ase/io/vtkxml.py	(revision 1)
@@ -0,0 +1,195 @@
+import numpy as np
+#from Numeric import asarray as Numeric_asarray
+
+#from ase.units import Bohr
+#from ase.parallel import paropen
+
+fast = False
+
+# -------------------------------------------------------------------
+
+from vtk import vtkStructuredPoints, vtkDoubleArray, vtkXMLImageDataWriter
+
+def write_vti(filename, atoms, data):
+
+    #if isinstance(fileobj, str):
+    #    fileobj = paropen(fileobj, 'w')
+        
+    if isinstance(atoms, list):
+        if len(atoms) > 1:
+            raise ValueError('Can only write one configuration to a VTI file!')
+        atoms = atoms[0]
+
+    if data is None:
+        raise ValueError('VTK XML Image Data (VTI) format requires data!')
+
+    data = np.asarray(data)
+
+    if data.dtype == complex:
+        data = np.abs(data)
+
+    cell = atoms.get_cell()
+
+    assert np.all(cell==np.diag(cell.diagonal())), 'Unit cell must be orthogonal'
+
+    bbox = np.array(zip(np.zeros(3),cell.diagonal())).ravel()
+
+    # Create a VTK grid of structured points
+    spts = vtkStructuredPoints()
+    spts.SetWholeBoundingBox(bbox)
+    spts.SetDimensions(data.shape)
+    spts.SetSpacing(cell.diagonal() / data.shape)
+    #spts.SetSpacing(paw.gd.h_c * Bohr)
+
+    #print 'paw.gd.h_c * Bohr=',paw.gd.h_c * Bohr
+    #print 'atoms.cell.diagonal() / data.shape=', cell.diagonal()/data.shape
+    #assert np.all(paw.gd.h_c * Bohr==cell.diagonal()/data.shape)
+
+    #s = paw.wfs.kpt_u[0].psit_nG[0].copy()
+    #data = paw.get_pseudo_wave_function(band=0, kpt=0, spin=0, pad=False)
+    #spts.point_data.scalars = data.swapaxes(0,2).flatten()
+    #spts.point_data.scalars.name = 'scalars'
+
+    # Allocate a VTK array of type double and copy data
+    da = vtkDoubleArray()
+    da.SetName('scalars')
+    da.SetNumberOfComponents(1)
+    da.SetNumberOfTuples(np.prod(data.shape))
+
+    for i,d in enumerate(data.swapaxes(0,2).flatten()):
+        da.SetTuple1(i,d)
+
+    # Assign the VTK array as point data of the grid
+    spd = spts.GetPointData() # type(spd) is vtkPointData
+    spd.SetScalars(da)
+
+    """
+    from vtk.util.vtkImageImportFromArray import vtkImageImportFromArray
+    iia = vtkImageImportFromArray()
+    #iia.SetArray(Numeric_asarray(data.swapaxes(0,2).flatten()))
+    iia.SetArray(Numeric_asarray(data))
+    ida = iia.GetOutput()
+    ipd = ida.GetPointData()
+    ipd.SetName('scalars')
+    spd.SetScalars(ipd.GetScalars())
+    """
+
+    # Save the ImageData dataset to a VTK XML file.
+    w = vtkXMLImageDataWriter()
+
+    if fast:
+        w.SetDataModeToAppend()
+        w.EncodeAppendedDataOff()
+    else:
+        w.SetDataModeToAscii()
+
+    w.SetFileName(filename)
+    w.SetInput(spts)
+    w.Write()
+
+# -------------------------------------------------------------------
+
+from vtk import vtkStructuredGrid, vtkPoints, vtkXMLStructuredGridWriter
+
+def write_vts(filename, atoms, data=None):
+    raise NotImplementedError
+
+# -------------------------------------------------------------------
+
+from vtk import vtkUnstructuredGrid, vtkPoints, vtkXMLUnstructuredGridWriter
+
+def write_vtu(filename, atoms, data=None):
+
+    #if isinstance(fileobj, str):
+    #    fileobj = paropen(fileobj, 'w')
+        
+    if isinstance(atoms, list):
+        if len(atoms) > 1:
+            raise ValueError('Can only write one configuration to a VTI file!')
+        atoms = atoms[0]
+
+    """
+    if data is None:
+        raise ValueError('VTK XML Unstructured Grid (VTI) format requires data!')
+
+    data = np.asarray(data)
+
+    if data.dtype == complex:
+        data = np.abs(data)
+    """
+
+    cell = atoms.get_cell()
+
+    assert np.all(cell==np.diag(cell.diagonal())), 'Unit cell must be orthogonal' #TODO bounding box with general unit cell?!
+
+    bbox = np.array(zip(np.zeros(3),cell.diagonal())).ravel()
+
+    # Create a VTK grid of structured points
+    ugd = vtkUnstructuredGrid()
+    ugd.SetWholeBoundingBox(bbox)
+
+    """
+    # Allocate a VTK array of type double and copy data
+    da = vtkDoubleArray()
+    da.SetName('scalars')
+    da.SetNumberOfComponents(3)
+    da.SetNumberOfTuples(len(atoms))
+
+    for i,pos in enumerate(atoms.get_positions()):
+        da.SetTuple3(i,pos[0],pos[1],pos[2])
+    """
+    p = vtkPoints()
+    p.SetNumberOfPoints(len(atoms))
+    p.SetDataTypeToDouble()
+    for i,pos in enumerate(atoms.get_positions()):
+        p.InsertPoint(i,pos[0],pos[1],pos[2])
+
+
+    ugd.SetPoints(p)
+
+    # Assign the VTK array as point data of the grid
+    #upd = ugd.GetPointData() # type(spd) is vtkPointData
+    #upd.SetScalars(da)
+
+
+    # Save the UnstructuredGrid dataset to a VTK XML file.
+    w = vtkXMLUnstructuredGridWriter()
+
+    if fast:
+        w.SetDataModeToAppend()
+        w.EncodeAppendedDataOff()
+    else:
+        w.GetCompressor().SetCompressionLevel(0)
+        w.SetDataModeToAscii()
+
+    w.SetFileName(filename)
+    w.SetInput(ugd)
+    w.Write()
+
+
+# -------------------------------------------------------------------
+
+def read_vti(filename):
+    raise NotImplementedError
+
+def read_vts(filename):
+    raise NotImplementedError
+
+def read_vtu(filename):
+    raise NotImplementedError
+
+# -------------------------------------------------------------------
+
+from vtk import vtkXMLFileReadTester
+
+def probe_vtkxml(filename):
+    """something..."""
+
+    r = vtkXMLFileReadTester()
+    r.SetFileName(filename)
+
+    if r.TestReadFile():
+        return r.GetFileDataType()
+    else:
+        return None
+
Index: ase/io/trajectory.py
===================================================================
--- ase/io/trajectory.py	(revision 0)
+++ ase/io/trajectory.py	(revision 1)
@@ -0,0 +1,368 @@
+import os
+import cPickle as pickle
+
+from ase.calculators.singlepoint import SinglePointCalculator
+from ase.atoms import Atoms
+from ase.parallel import rank, barrier
+from ase.utils import devnull
+
+
+class PickleTrajectory:
+    "Reads/writes Atoms objects into a .traj file."
+    # Per default, write these quantities
+    write_energy = True
+    write_forces = True
+    write_stress = True
+    write_magmoms = True
+    write_momenta = True
+    
+    def __init__(self, filename, mode='r', atoms=None, master=None,
+                 backup=True):
+        """A PickleTrajectory can be created in read, write or append mode.
+
+        Parameters:
+
+        filename:
+            The name of the parameter file.  Should end in .traj.
+
+        mode='r':
+            The mode.
+
+            'r' is read mode, the file should already exist, and
+            no atoms argument should be specified.
+
+            'w' is write mode.  If the file already exists, is it
+            renamed by appending .bak to the file name.  The atoms
+            argument specifies the Atoms object to be written to the
+            file, if not given it must instead be given as an argument
+            to the write() method.
+
+            'a' is append mode.  It acts a write mode, except that
+            data is appended to a preexisting file.
+
+        atoms=None:
+            The Atoms object to be written in write or append mode.
+
+        master=None:
+            Controls which process does the actual writing. The
+            default is that process number 0 does this.  If this
+            argument is given, processes where it is True will write.
+
+        backup=True:
+            Use backup=False to disable renaming of an existing file.
+        """
+        self.offsets = []
+        if master is None:
+            master = (rank == 0)
+        self.master = master
+        self.backup = backup
+        self.set_atoms(atoms)
+        self.open(filename, mode)
+
+    def open(self, filename, mode):
+        """Opens the file.
+
+        For internal use only.
+        """
+        self.fd = filename
+        if mode == 'r':
+            if isinstance(filename, str):
+                self.fd = open(filename, 'rb')
+            self.read_header()
+        elif mode == 'a':
+            exists = True
+            if isinstance(filename, str):
+                exists = os.path.isfile(filename)
+                if exists:
+                    self.fd = open(filename, 'rb')
+                    self.read_header()
+                    self.fd.close()
+                barrier()
+                if self.master:
+                    self.fd = open(filename, 'ab+')
+                else:
+                    self.fd = devnull
+        elif mode == 'w':
+            if self.master:
+                if isinstance(filename, str):
+                    if self.backup and os.path.isfile(filename):
+                        os.rename(filename, filename + '.bak')
+                    self.fd = open(filename, 'wb')
+            else:
+                self.fd = devnull
+        else:
+            raise ValueError('mode must be "r", "w" or "a".')
+
+    def set_atoms(self, atoms=None):
+        """Associate an Atoms object with the trajectory.
+
+        Mostly for internal use.
+        """
+        if atoms is not None and not hasattr(atoms, 'get_positions'):
+            raise TypeError('"atoms" argument is not an Atoms object.')
+        self.atoms = atoms
+
+    def read_header(self):
+        self.fd.seek(0)
+        try:
+            if self.fd.read(len('PickleTrajectory')) != 'PickleTrajectory':
+                raise IOError('This is not a trajectory file!')
+            d = pickle.load(self.fd)
+        except EOFError:
+            raise EOFError('Bad trajectory file.')
+        self.pbc = d['pbc']
+        self.numbers = d['numbers']
+        self.tags = d.get('tags')
+        self.masses = d.get('masses')
+        self.constraints = d['constraints']
+        self.offsets.append(self.fd.tell())
+
+    def write(self, atoms=None):
+        """Write the atoms to the file.
+
+        If the atoms argument is not given, the atoms object specified
+        when creating the trajectory object is used.
+        """
+        if atoms is None:
+            atoms = self.atoms
+
+        if hasattr(atoms, 'interpolate'):
+            # seems to be a NEB
+            neb = atoms
+            try:
+                neb.get_energies_and_forces(all=True)
+            except AttributeError:
+                pass
+            for image in neb.images:
+                self.write(image)
+            return
+
+        if len(self.offsets) == 0:
+            self.write_header(atoms)
+
+        if atoms.has('momenta'):
+            momenta = atoms.get_momenta()
+        else:
+            momenta = None
+
+        d = {'positions': atoms.get_positions(),
+             'cell': atoms.get_cell(),
+             'momenta': momenta}
+
+        if atoms.get_calculator() is not None:
+            if self.write_energy:
+                d['energy'] = atoms.get_potential_energy()
+            if self.write_forces:
+                assert self.write_energy
+                try:
+                    d['forces'] = atoms.get_forces(apply_constraint=False)
+                except NotImplementedError:
+                    pass
+            if self.write_stress:
+                assert self.write_energy
+                try:
+                    d['stress'] = atoms.get_stress()
+                except NotImplementedError:
+                    pass
+
+            if self.write_magmoms:
+                try:
+                    if atoms.calc.get_spin_polarized():
+                        d['magmoms'] = atoms.get_magnetic_moments()
+                except (NotImplementedError, AttributeError):
+                    pass
+
+        if 'magmoms' not in d and atoms.has('magmoms'):
+            d['magmoms'] = atoms.get_initial_magnetic_moments()
+            
+        if self.master:
+            pickle.dump(d, self.fd, protocol=-1)
+        self.fd.flush()
+        self.offsets.append(self.fd.tell())
+
+    def write_header(self, atoms):
+        self.fd.write('PickleTrajectory')
+        if atoms.has('tags'):
+            tags = atoms.get_tags()
+        else:
+            tags = None
+        if atoms.has('masses'):
+            masses = atoms.get_masses()
+        else:
+            masses = None
+        d = {'pbc': atoms.get_pbc(),
+             'numbers': atoms.get_atomic_numbers(),
+             'tags': tags,
+             'masses': masses,
+             'constraints': atoms.constraints}
+        pickle.dump(d, self.fd, protocol=-1)
+        self.header_written = True
+        self.offsets.append(self.fd.tell())
+        
+    def close(self):
+        """Close the trajectory file."""
+        self.fd.close()
+
+    def __getitem__(self, i=-1):
+        N = len(self.offsets)
+        if 0 <= i < N:
+            self.fd.seek(self.offsets[i])
+            try:
+                d = pickle.load(self.fd)
+            except EOFError:
+                raise IndexError
+            if i == N - 1:
+                self.offsets.append(self.fd.tell())
+            try:
+                magmoms = d['magmoms']
+            except KeyError:
+                magmoms = None    
+            atoms = Atoms(positions=d['positions'],
+                          numbers=self.numbers,
+                          cell=d['cell'],
+                          momenta=d['momenta'],
+                          magmoms=magmoms,
+                          tags=self.tags,
+                          masses=self.masses,
+                          pbc=self.pbc,
+                          constraint=[c.copy() for c in self.constraints])
+            if 'energy' in d:
+                calc = SinglePointCalculator(
+                    d.get('energy', None), d.get('forces', None),
+                    d.get('stress', None), magmoms, atoms)
+                atoms.set_calculator(calc)
+            return atoms
+
+        if i >= N:
+            for j in range(N - 1, i + 1):
+                atoms = self[j]
+            return atoms
+
+        i = len(self) + i
+        if i < 0:
+            raise IndexError('Trajectory index out of range.')
+        return self[i]
+
+    def __len__(self):
+        N = len(self.offsets) - 1
+        while True:
+            self.fd.seek(self.offsets[N])
+            try:
+                pickle.load(self.fd)
+            except EOFError:
+                return N
+            self.offsets.append(self.fd.tell())
+            N += 1
+
+    def __iter__(self):
+        del self.offsets[1:]
+        return self
+
+    def next(self):
+        try:
+            return self[len(self.offsets) - 1]
+        except IndexError:
+            raise StopIteration
+
+    def guess_offsets(self):
+        size = os.path.getsize(self.fd.name)
+
+        while True:
+            self.fd.seek(self.offsets[-1])
+            try:
+                pickle.load(self.fd)
+            except:
+                raise EOFError('Damaged trajectory file.')
+            else:
+                self.offsets.append(self.fd.tell())
+
+            if self.offsets[-1] >= size:
+                break
+
+            if len(self.offsets) > 2:
+                step1 = self.offsets[-1] - self.offsets[-2]
+                step2 = self.offsets[-2] - self.offsets[-3]
+
+                if step1 == step2:
+                    m = int((size - self.offsets[-1]) / step1) - 1
+
+                    while m > 1:
+                        self.fd.seek(self.offsets[-1] + m * step1)
+                        try:
+                            pickle.load(self.fd)
+                        except:
+                            m = m / 2
+                        else:
+                            for i in range(m):
+                                self.offsets.append(self.offsets[-1] + step1)
+                            m = 0
+
+        return
+
+def read_trajectory(filename, index=-1):
+    traj = PickleTrajectory(filename, mode='r')
+
+    if isinstance(index, int):
+        return traj[index]
+    else:
+        # Here, we try to read only the configurations we need to read
+        # and len(traj) should only be called if we need to as it will
+        # read all configurations!
+
+        # XXX there must be a simpler way?
+        step = index.step or 1
+        if step > 0:
+            start = index.start or 0
+            if start < 0:
+                start += len(traj)
+            stop = index.stop or len(traj)
+            if stop < 0:
+                stop += len(traj)
+        else:
+            if index.start is None:
+                start = len(traj) - 1
+            else:
+                start = index.start
+                if start < 0:
+                    start += len(traj)
+            if index.stop is None:
+                stop = -1
+            else:
+                stop = index.stop
+                if stop < 0:
+                    stop += len(traj)
+                    
+        return [traj[i] for i in range(start, stop, step)]
+
+def write_trajectory(filename, images):
+    """Write image(s) to trajectory.
+
+    Write also energy, forces, and stress if they are already
+    calculated."""
+
+    traj = PickleTrajectory(filename, mode='w')
+
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+        
+    for atoms in images:
+        # Avoid potentially expensive calculations:
+        calc = atoms.get_calculator()
+        if calc is not None:
+            if  hasattr(calc, 'calculation_required'):
+                if calc.calculation_required(atoms, ['energy']):
+                    traj.write_energy = False
+                if calc.calculation_required(atoms, ['forces']):
+                    traj.write_forces = False
+                if calc.calculation_required(atoms, ['stress']):
+                    traj.write_stress = False
+                if calc.calculation_required(atoms, ['magmoms']):
+                    traj.write_magmoms = False
+        else:
+            traj.write_energy = False
+            traj.write_forces = False
+            traj.write_stress = False
+            traj.write_magmoms = False
+            
+        traj.write(atoms)
+    traj.close()
Index: ase/io/dacapo.py
===================================================================
--- ase/io/dacapo.py	(revision 0)
+++ ase/io/dacapo.py	(revision 1)
@@ -0,0 +1,85 @@
+import numpy as np
+
+from ase.calculators.singlepoint import SinglePointCalculator
+from ase.atom import Atom
+from ase.atoms import Atoms
+
+
+def read_dacapo_text(fileobj):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    lines = fileobj.readlines()
+    i = lines.index(' Structure:             A1           A2            A3\n')
+    cell = np.array([[float(w) for w in line.split()[2:5]]
+                     for line in lines[i + 1:i + 4]]).transpose()
+    i = lines.index(' Structure:  >>         Ionic positions/velocities ' +
+                    'in cartesian coordinates       <<\n')
+    atoms = []
+    for line in lines[i + 4:]:
+        words = line.split()
+        if len(words) != 9:
+            break
+        Z, x, y, z = words[2:6]
+        atoms.append(Atom(int(Z), [float(x), float(y), float(z)]))
+
+    atoms = Atoms(atoms, cell=cell.tolist())
+
+    try:
+        i = lines.index(
+            ' DFT:  CPU time                           Total energy\n')
+    except ValueError:
+        pass
+    else:
+        column = lines[i + 3].split().index('selfcons') - 1
+        try:
+            i2 = lines.index(' ANALYSIS PART OF CODE\n', i)
+        except ValueError:
+            pass
+        else:
+            while i2 > i:
+                if lines[i2].startswith(' DFT:'):
+                    break
+                i2 -= 1
+            energy = float(lines[i2].split()[column])
+            atoms.set_calculator(SinglePointCalculator(energy, None, None,
+                                                       None, atoms))
+
+    return atoms
+
+
+
+def read_dacapo(filename):
+    from ase.io.pupynere import NetCDFFile
+
+    nc = NetCDFFile(filename)
+    dims = nc.dimensions
+    vars = nc.variables
+
+    cell = vars['UnitCell'][-1]
+    try:
+        magmoms = vars['InitialAtomicMagneticMoment'][:]
+    except KeyError:
+        magmoms = None
+    try:
+        tags = vars['AtomTags'][:]
+    except KeyError:
+        tags = None
+    atoms = Atoms(scaled_positions=vars['DynamicAtomPositions'][-1],
+                  symbols=[(a + b).strip() 
+                           for a, b in vars['DynamicAtomSpecies'][:]],
+                  cell=cell,
+                  magmoms=magmoms,
+                  tags=tags,
+                  pbc=True)
+
+    try:
+        energy = vars['TotalEnergy'][-1] 
+        force = vars['DynamicAtomForces'][-1] 
+    except KeyError: 
+        energy = None 
+        force = None 
+    calc = SinglePointCalculator(energy,force,None, None, atoms)  ### Fixme magmoms
+    atoms.set_calculator(calc)
+        
+    return atoms
Index: ase/io/eps.py
===================================================================
--- ase/io/eps.py	(revision 0)
+++ ase/io/eps.py	(revision 1)
@@ -0,0 +1,208 @@
+import time
+from math import sqrt
+
+import numpy as np
+
+from ase.utils import rotate
+from ase.data import covalent_radii
+from ase.data.colors import jmol_colors
+
+
+class EPS:
+    def __init__(self, atoms,
+                 rotation='', show_unit_cell=False, radii=None,
+                 bbox=None, colors=None, scale=20):
+        self.numbers = atoms.get_atomic_numbers()
+        self.colors = colors
+        if colors is None:
+            self.colors = jmol_colors[self.numbers]
+
+        if radii is None:
+            radii = covalent_radii[self.numbers]
+        elif type(radii) is float:
+            radii = covalent_radii[self.numbers] * radii
+            
+        natoms = len(atoms)
+
+        if isinstance(rotation, str):
+            rotation = rotate(rotation)
+
+        A = atoms.get_cell()
+        if show_unit_cell > 0:
+            L, T, D = self.cell_to_lines(A)
+            C = np.empty((2, 2, 2, 3))
+            for c1 in range(2):
+                for c2 in range(2):
+                    for c3 in range(2):
+                        C[c1, c2, c3] = np.dot([c1, c2, c3], A)
+            C.shape = (8, 3)
+            C = np.dot(C, rotation) # Unit cell vertices
+        else:
+            L = np.empty((0, 3))
+            T = None
+            D = None
+            C = None
+
+        nlines = len(L)
+
+        X = np.empty((natoms + nlines, 3))
+        R = atoms.get_positions()
+        X[:natoms] = R
+        X[natoms:] = L
+
+        r2 = radii**2
+        for n in range(nlines):
+            d = D[T[n]]
+            if ((((R - L[n] - d)**2).sum(1) < r2) &
+                (((R - L[n] + d)**2).sum(1) < r2)).any():
+                T[n] = -1
+
+        X = np.dot(X, rotation)
+        R = X[:natoms]
+
+        if bbox is None:
+            X1 = (R - radii[:, None]).min(0) 
+            X2 = (R + radii[:, None]).max(0) 
+            if show_unit_cell == 2:
+                X1 = np.minimum(X1, C.min(0))
+                X2 = np.maximum(X2, C.max(0))
+            M = (X1 + X2) / 2
+            S = 1.05 * (X2 - X1)
+            w = scale * S[0]
+            if w > 500:
+                w = 500
+                scale = w / S[0]
+            h = scale * S[1]
+            offset = np.array([scale * M[0] - w / 2, scale * M[1] - h / 2, 0])
+        else:
+            w = (bbox[2] - bbox[0]) * scale
+            h = (bbox[3] - bbox[1]) * scale
+            offset = np.array([bbox[0], bbox[1], 0]) * scale
+
+        self.w = w
+        self.h = h
+        
+        X *= scale
+        X -= offset
+
+        if nlines > 0:
+            D = np.dot(D, rotation)[:, :2] * scale
+        
+        if C is not None:
+            C *= scale
+            C -= offset
+
+        A = np.dot(A, rotation)
+        A *= scale
+
+        self.A = A
+        self.X = X
+        self.D = D
+        self.T = T
+        self.C = C
+        self.natoms = natoms
+        self.d = 2 * scale * radii
+
+    def cell_to_lines(self, A):
+        nlines = 0
+        nn = []
+        for c in range(3):
+            d = sqrt((A[c]**2).sum())
+            n = max(2, int(d / 0.3))
+            nn.append(n)
+            nlines += 4 * n
+
+        X = np.empty((nlines, 3))
+        T = np.empty(nlines, int)
+        D = np.zeros((3, 3))
+
+        n1 = 0
+        for c in range(3):
+            n = nn[c]
+            dd = A[c] / (4 * n - 2)
+            D[c] = dd
+            P = np.arange(1, 4 * n + 1, 4)[:, None] * dd
+            T[n1:] = c
+            for i, j in [(0, 0), (0, 1), (1, 0), (1, 1)]:
+                n2 = n1 + n
+                X[n1:n2] = P + i * A[(c + 1) % 3] + j * A[(c + 2) % 3]
+                n1 = n2
+
+        return X, T, D
+
+    def write(self, filename):
+        self.filename = filename
+        self.write_header()
+        self.write_body()
+        self.write_trailer()
+
+    def write_header(self):
+        import matplotlib
+        if matplotlib.__version__ <= '0.8':
+            raise RuntimeError('Your version of matplotlib (%s) is too old' %
+                               matplotlib.__version__)
+
+        from matplotlib.backends.backend_ps import RendererPS, \
+             GraphicsContextPS, psDefs
+
+        self.fd = open(self.filename, 'w')
+        self.fd.write('%!PS-Adobe-3.0 EPSF-3.0\n')
+        self.fd.write('%%Creator: G2\n')
+        self.fd.write('%%CreationDate: %s\n' % time.ctime(time.time()))
+        self.fd.write('%%Orientation: portrait\n')
+        bbox = (0, 0, self.w, self.h)
+        self.fd.write('%%%%BoundingBox: %d %d %d %d\n' % bbox)
+        self.fd.write('%%EndComments\n')
+
+        Ndict = len(psDefs)
+        self.fd.write('%%BeginProlog\n')
+        self.fd.write('/mpldict %d dict def\n' % Ndict)
+        self.fd.write('mpldict begin\n')
+        for d in psDefs:
+            d = d.strip()
+            for l in d.split('\n'):
+                self.fd.write(l.strip() + '\n')
+        self.fd.write('%%EndProlog\n')
+
+        self.fd.write('mpldict begin\n')
+        self.fd.write('%d %d 0 0 clipbox\n' % (self.w, self.h))
+
+        self.renderer = RendererPS(self.w, self.h, self.fd)
+        
+    def write_body(self):
+        try:
+            from matplotlib.path import Path
+        except ImportError:
+            Path = None
+            from matplotlib.patches import Circle, Polygon
+        else:
+            from matplotlib.patches import Circle, PathPatch
+
+        indices = self.X[:, 2].argsort()
+        for a in indices:
+            xy = self.X[a, :2]
+            if a < self.natoms:
+                circle = Circle(xy, self.d[a] / 2, facecolor=self.colors[a])
+                circle.draw(self.renderer)
+            else:
+                a -= self.natoms
+                c = self.T[a]
+                if c != -1:
+                    hxy = self.D[c]
+                    if Path is None:
+                        line = Polygon((xy + hxy, xy - hxy))
+                    else:
+                        line = PathPatch(Path((xy + hxy, xy - hxy)))
+                    line.draw(self.renderer)
+
+    def write_trailer(self):
+        self.fd.write('end\n')
+        self.fd.write('showpage\n')
+        self.fd.close()
+
+
+def write_eps(filename, atoms, **parameters):
+    if isinstance(atoms, list):
+        assert len(atoms) == 1
+        atoms = atoms[0]
+    EPS(atoms, **parameters).write(filename)
Index: ase/io/mol.py
===================================================================
--- ase/io/mol.py	(revision 0)
+++ ase/io/mol.py	(revision 1)
@@ -0,0 +1,23 @@
+from math import pi, cos, sin, sqrt, acos
+
+from ase.atoms import Atoms
+from ase.parallel import paropen
+
+
+def read_mol(fileobj, index=-1):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    lines = fileobj.readlines()
+    del(lines[:3])
+    L1 = lines[0].split()
+    del(lines[0])
+    natoms = int(L1[0])
+    positions = []
+    symbols = []
+    for line in lines[:natoms]:
+            x, y, z, symbol = line.split()[:4]
+            symbols.append(symbol)
+            positions.append([float(x), float(y), float(z)])
+    return Atoms(symbols=symbols, positions=positions)
+
Index: ase/io/py.py
===================================================================
--- ase/io/py.py	(revision 0)
+++ ase/io/py.py	(revision 1)
@@ -0,0 +1,25 @@
+from ase.atoms import Atoms
+
+
+def write_py(fileobj, images, **kwargs):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj, 'w')
+
+    fileobj.write('from ase import Atoms\n\n')
+    fileobj.write('import numpy as np\n\n')
+    
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+    fileobj.write('images = [\n')
+
+    for image in images:
+        fileobj.write("    Atoms(symbols='%s',\n"
+                      "          pbc=np.%s,\n"
+                      "          cell=np.array(\n      %s,\n"
+                      "          positions=np.array(\n      %s),\n" % (
+            image.get_chemical_symbols(reduce=True),
+            repr(image.pbc),
+            repr(image.cell)[6:],
+            repr(image.positions)[6:]))
+        
+    fileobj.write(']')
Index: ase/io/ascii.py
===================================================================
--- ase/io/ascii.py	(revision 0)
+++ ase/io/ascii.py	(revision 1)
@@ -0,0 +1,19 @@
+from ase.atoms import Atoms
+
+
+def write_ascii(fileobj, images):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj, 'w')
+
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+        fileobj.write('atoms = ')
+    else:
+        fileobj.write('images = [')
+
+    symbols = images[0].get_chemical_symbols()
+    natoms = len(symbols)
+    for atoms in images:
+        fileobj.write('%d\n\n' % natoms)
+        for s, (x, y, z) in zip(symbols, atoms.get_positions()):
+            fileobj.write('%-2s %22.15f %22.15f %22.15f\n' % (s, x, y, z))
Index: ase/io/aims.py
===================================================================
--- ase/io/aims.py	(revision 0)
+++ ase/io/aims.py	(revision 1)
@@ -0,0 +1,302 @@
+
+def read_aims(filename):
+    """Import FHI-aims geometry type files.
+
+    Reads unitcell, atom positions and constraints from
+    a geometry.in file.
+    """
+
+    from ase import Atoms
+    from ase.constraints import FixAtoms, FixCartesian
+    import numpy as np
+
+    atoms = Atoms()
+    fd = open(filename, 'r')
+    lines = fd.readlines()
+    fd.close()
+    positions = []
+    cell = []
+    symbols = []
+    fix = []
+    fix_cart = []
+    xyz = np.array([0, 0, 0])
+    i = -1
+    n_periodic = -1
+    periodic = np.array([False, False, False])
+    for n, line in enumerate(lines):
+        inp = line.split()
+        if inp == []:
+            continue
+        if inp[0] == 'atom':
+            if xyz.all():
+                fix.append(i)
+            elif xyz.any():
+                print 1
+                fix_cart.append(FixCartesian(i, xyz))
+            floatvect = float(inp[1]), float(inp[2]), float(inp[3])
+            positions.append(floatvect)
+            symbols.append(inp[-1])
+            i += 1
+            xyz = np.array([0, 0, 0])
+        elif inp[0] == 'lattice_vector':
+            floatvect = float(inp[1]), float(inp[2]), float(inp[3])
+            cell.append(floatvect)
+            n_periodic = n_periodic + 1
+            periodic[n_periodic] = True
+        if inp[0] == 'constrain_relaxation':
+            if inp[1] == '.true.':
+                fix.append(i)
+            elif inp[1] == 'x':
+                xyz[0] = 1
+            elif inp[1] == 'y':
+                xyz[1] = 1
+            elif inp[1] == 'z':
+                xyz[2] = 1
+    if xyz.all():
+        fix.append(i)
+    elif xyz.any():
+        fix_cart.append(FixCartesian(i, xyz))
+    atoms = Atoms(symbols, positions)
+    if periodic.all():
+        atoms.set_cell(cell)
+        atoms.set_pbc(periodic)
+    if len(fix):
+        atoms.set_constraint([FixAtoms(indices=fix)]+fix_cart)
+    else:
+        atoms.set_constraint(fix_cart)
+    return atoms
+
+def write_aims(filename, atoms):
+    """Method to write FHI-aims geometry files.
+
+    Writes the atoms positions and constraints (only FixAtoms is
+    supported at the moment). 
+    """
+
+    from ase.constraints import FixAtoms, FixCartesian
+    import numpy as np
+
+    if isinstance(atoms, (list, tuple)):
+        if len(atoms) > 1:
+            raise RuntimeError("Don't know how to save more than "+
+                               "one image to FHI-aims input")
+        else:
+            atoms = atoms[0]
+
+    fd = open(filename, 'w')
+    fd.write('#=======================================================\n')
+    fd.write('#FHI-aims file: '+filename+'\n')
+    fd.write('#Created using the Atomic Simulation Environment (ASE)\n')
+    fd.write('#=======================================================\n')
+    i = 0
+    if atoms.get_pbc().any():
+        for n, vector in enumerate(atoms.get_cell()):
+            fd.write('lattice_vector ')
+            for i in range(3):
+                fd.write('%16.16f ' % vector[i])
+            fd.write('\n')
+    fix_cart = np.zeros([len(atoms),3]) 
+
+    if atoms.constraints:
+        for constr in atoms.constraints:
+            if isinstance(constr, FixAtoms):
+                fix_cart[constr.index] = [1,1,1]
+            elif isinstance(constr, FixCartesian):
+                fix_cart[constr.a] = -constr.mask+1
+
+    for i, atom in enumerate(atoms):
+        fd.write('atom ')
+        for pos in atom.get_position():
+            fd.write('%16.16f ' % pos)
+        fd.write(atom.symbol)
+        fd.write('\n')
+# (1) all coords are constrained:
+        if fix_cart[i].all():
+            fd.write('constrain_relaxation .true.\n')
+# (2) some coords are constrained:
+        elif fix_cart[i].any():
+            xyz = fix_cart[i]
+            for n in range(3):
+                if xyz[n]:
+                    fd.write('constrain_relaxation %s\n' % 'xyz'[n])
+        if atom.charge:
+            fd.write('initial_charge %16.6f\n' % atom.charge)
+        if atom.magmom:
+            fd.write('initial_moment %16.6f\n' % atom.magmom)
+# except KeyError:
+#     continue
+
+def read_energy(filename):
+    for line in open(filename, 'r'):
+        if line.startswith('  | Total energy corrected'):
+            E = float(line.split()[-2])
+    return E
+
+def read_aims_output(filename, index = -1):
+    """  Import FHI-aims output files with all data available, i.e. relaxations, 
+    MD information, force information etc etc etc. """
+    from ase import Atoms, Atom 
+    from ase.calculators.singlepoint import SinglePointCalculator
+    from ase.units import Ang, fs
+    molecular_dynamics = False
+    fd = open(filename, 'r')
+    cell = []
+    images = []
+    n_periodic = -1
+    f = None
+    pbc = False
+    found_aims_calculator = False
+    v_unit = Ang/(1000.0*fs)
+    while True:
+        line = fd.readline()
+        if not line:
+            break
+        if "List of parameters used to initialize the calculator:" in line:
+            fd.readline()
+            calc = read_aims_calculator(fd)
+            calc.out = filename
+            found_aims_calculator = True
+        if "Number of atoms" in line:
+            inp = line.split()
+            n_atoms = int(inp[5])
+        if "| Unit cell:" in line:
+            if not pbc:
+                pbc = True
+                for i in range(3):
+                    inp = fd.readline().split()
+                    cell.append([inp[1],inp[2],inp[3]])
+        if "Atomic structure:" in line and not molecular_dynamics:
+            fd.readline()
+            atoms = Atoms()
+            for i in range(n_atoms):
+                inp = fd.readline().split()
+                atoms.append(Atom(inp[3],(inp[4],inp[5],inp[6])))
+        if "Complete information for previous time-step:" in line:
+            molecular_dynamics = True
+        if "Updated atomic structure:" in line and not molecular_dynamics:
+            fd.readline()
+            atoms = Atoms()
+            velocities = []
+            for i in range(n_atoms):
+                inp = fd.readline().split()
+                atoms.append(Atom(inp[4],(inp[1],inp[2],inp[3])))  
+                if molecular_dynamics:
+                    inp = fd.readline().split()
+        if "Atomic structure (and velocities)" in line:
+            fd.readline()
+            atoms = Atoms()
+            velocities = []
+            for i in range(n_atoms):
+                inp = fd.readline().split()
+                atoms.append(Atom(inp[4],(inp[1],inp[2],inp[3])))  
+                inp = fd.readline().split()
+                velocities += [[float(inp[1])*v_unit,float(inp[2])*v_unit,float(inp[3])*v_unit]]
+            atoms.set_velocities(velocities)
+            images.append(atoms)
+        if "Total atomic forces" in line:
+            f = []
+            for i in range(n_atoms):
+                inp = fd.readline().split()
+                f.append([float(inp[2]),float(inp[3]),float(inp[4])])
+            if not found_aims_calculator:
+                e = images[-1].get_potential_energy()
+                images[-1].set_calculator(SinglePointCalculator(e,f,None,None,atoms))
+            e = None
+            f = None
+        if "Total energy corrected" in line:
+            e = float(line.split()[5])
+            if pbc:
+                atoms.set_cell(cell)
+                atoms.pbc = True
+            if not found_aims_calculator:
+                atoms.set_calculator(SinglePointCalculator(e,None,None,None,atoms))
+            if not molecular_dynamics: 
+                images.append(atoms)
+            e = None
+            if found_aims_calculator:
+                calc.set_results(images[-1])
+                images[-1].set_calculator(calc)
+    fd.close()
+    if molecular_dynamics:
+        images = images[1:]
+
+    # return requested images, code borrowed from ase/io/trajectory.py
+    if isinstance(index, int):
+        return images[index]
+    else:
+        step = index.step or 1
+        if step > 0:
+            start = index.start or 0
+            if start < 0:
+                start += len(images)
+            stop = index.stop or len(images)
+            if stop < 0:
+                stop += len(images)
+        else:
+            if index.start is None:
+                start = len(images) - 1
+            else:
+                start = index.start
+                if start < 0:
+                    start += len(images)
+            if index.stop is None:
+                stop = -1
+            else:
+                stop = index.stop
+                if stop < 0:
+                    stop += len(images)
+        return [images[i] for i in range(start, stop, step)]
+
+def read_aims_calculator(file):
+    """  found instructions for building an FHI-aims calculator in the output file, 
+    read its specifications and return it. """
+    from ase.calculators.aims import Aims
+    calc = Aims()
+    while True:
+        line = file.readline()
+        if "=======================================================" in line:
+            break
+        else:
+            args = line.split()
+            key = args[0]
+            if key == '#':
+                comment = True   
+            elif calc.float_params.has_key(key):
+                calc.float_params[key] = float(args[1])
+            elif calc.exp_params.has_key(key):
+                calc.exp_params[key] = float(args[1])
+            elif calc.string_params.has_key(key):
+                calc.string_params[key] = args[1]
+                if len(args) > 2:
+                    for s in args[2:]:
+                        calc.string_params[key] += " "+s
+            elif calc.int_params.has_key(key):
+                calc.int_params[key] = int(args[1])
+            elif calc.bool_params.has_key(key):
+                try:
+                    calc.bool_params[key] = bool(args[1])
+                except:
+                    if key == 'vdw_correction_hirshfeld':
+                        calc.bool_params[key] = True
+            elif calc.list_params.has_key(key):
+                if key == 'output':
+                    # build output string from args:
+                    out_option = ''
+                    for arg in args[1:]:
+                        out_option +=str(arg)+' '
+                    if calc.list_params['output'] is not None:
+                        calc.list_params['output'] += [out_option]
+                    else:
+                        calc.list_params['output'] = [out_option]
+                else:
+                    calc.list_params[key] = list(args[1:])
+            elif '#' in key:
+                key = key[1:]
+                if calc.input_parameters.has_key(key):
+                    calc.input_parameters[key] = args[1]
+                    if len(args) > 2: 
+                        for s in args[2:]:
+                            calc.input_parameters[key] += " "+s                
+            else:
+                raise TypeError('FHI-aims keyword not defined in ASE: ' + key + '. Please check.')
+    return calc
Index: ase/io/xyz.py
===================================================================
--- ase/io/xyz.py	(revision 0)
+++ ase/io/xyz.py	(revision 1)
@@ -0,0 +1,42 @@
+from math import pi, cos, sin, sqrt, acos
+
+from ase.atoms import Atoms
+from ase.parallel import paropen
+
+
+def read_xyz(fileobj, index=-1):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    lines = fileobj.readlines()
+    L1 = lines[0].split()
+    if len(L1) == 1:
+        del lines[:2]
+        natoms = int(L1[0])
+    else:
+        natoms = len(lines)
+    images = []
+    while len(lines) >= natoms:
+        positions = []
+        symbols = []
+        for line in lines[:natoms]:
+            symbol, x, y, z = line.split()[:4]
+            symbols.append(symbol)
+            positions.append([float(x), float(y), float(z)])
+        images.append(Atoms(symbols=symbols, positions=positions))
+        del lines[:natoms + 2]
+    return images[index]
+
+def write_xyz(fileobj, images):
+    if isinstance(fileobj, str):
+        fileobj = paropen(fileobj, 'w')
+
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+
+    symbols = images[0].get_chemical_symbols()
+    natoms = len(symbols)
+    for atoms in images:
+        fileobj.write('%d\n\n' % natoms)
+        for s, (x, y, z) in zip(symbols, atoms.get_positions()):
+            fileobj.write('%-2s %22.15f %22.15f %22.15f\n' % (s, x, y, z))
Index: ase/io/iwm.py
===================================================================
--- ase/io/iwm.py	(revision 0)
+++ ase/io/iwm.py	(revision 1)
@@ -0,0 +1,45 @@
+from math import pi, cos, sin, sqrt, acos
+import numpy as np
+
+from ase.data import chemical_symbols
+from ase.atoms import Atoms
+from ase.parallel import paropen
+
+iwm_symbols = {'1' : 'C',
+               '2' : 'Au',
+               '5' : 'Ag'}
+
+def read_iwm(fileobj, index=-1):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    lines = fileobj.readlines()
+    L1 = lines[1].split()
+    if len(L1) == 1:
+        del lines[:3]
+        natoms = int(L1[0])
+    else:
+        natoms = len(lines)
+    images = []
+
+    positions = []
+    symbols = []
+    for line in lines[:natoms]:
+        symbol, mass, x, y, z = line.split()[:5]
+        if symbol in iwm_symbols:
+            symbols.append(iwm_symbols[symbol])
+        else:
+            symbols.append(chemical_symbols[int(symbol)])
+        positions.append([float(x), float(y), float(z)])
+    
+    del(lines[natoms:3 * natoms + 3])
+    
+    cell = []
+    for line in lines[natoms:natoms+3]:
+        x, y, z = line.split()[:3]
+        cell.append(np.array([float(x), float(y), float(z)]))
+      
+    images.append(Atoms(symbols=symbols, positions=positions, cell=cell))
+
+    return images[index]
+
Index: ase/io/cmdft.py
===================================================================
--- ase/io/cmdft.py	(revision 0)
+++ ase/io/cmdft.py	(revision 1)
@@ -0,0 +1,28 @@
+from math import pi, cos, sin, sqrt, acos
+import numpy as np
+
+from ase.atom import Atom
+from ase.atoms import Atoms
+from ase.parallel import paropen
+from ase.units import Bohr
+
+
+def read_I_info(fileobj, index=-1):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    lines = fileobj.readlines()
+
+    del lines[0]
+
+    finished = False
+    s = Atoms()
+    while not finished:
+        w = lines.pop(0).split()
+        if w[0].startswith('"'):
+            position = Bohr * np.array([float(w[3]), float(w[4]), float(w[5])])
+            s.append(Atom(w[0].replace('"',''), position))
+        else:
+            finished = True
+
+    return s
Index: ase/io/wien2k.py
===================================================================
--- ase/io/wien2k.py	(revision 0)
+++ ase/io/wien2k.py	(revision 1)
@@ -0,0 +1,168 @@
+from math import sin, cos, pi, sqrt
+
+import numpy as np
+
+from ase.atoms import Atoms, Atom
+from ase.units import Bohr, Ry
+
+def read_scf(filename):
+    try:
+        f = open(filename + '.scf', 'r')
+        pip = f.readlines()
+        ene = []
+        for line in pip:
+            if line[0:4] == ':ENE':
+                ene.append(float(line[43:59]) * Ry)
+        f.close()
+        return ene
+    except:
+        return None
+
+def read_struct(filename, ase = True):
+    f = open(filename, 'r')
+    pip = f.readlines()
+    lattice = pip[1][0:3]
+    nat = int(pip[1][27:30])
+    cell = np.zeros(6)
+    for i in range(6):
+        cell[i] = float(pip[3][0 + i * 10:10 + i * 10])
+    cell[0:3] = cell[0:3] * Bohr
+    if lattice == 'P  ':
+        lattice = 'P'
+    elif lattice == 'H  ':
+        lattice = 'P'
+        cell[3:6] = [90.0, 90.0, 120.0]
+    elif lattice == 'R  ':
+        lattice = 'R'
+    elif lattice == 'F  ':
+        lattice = 'F'
+    elif lattice == 'B  ':
+        lattice = 'I'
+    elif lattice == 'CXY':
+        lattice = 'C'
+    elif lattice == 'CXZ':
+        lattice = 'B'
+    elif lattice == 'CYZ':
+        lattice = 'A'
+    else:
+        print 'TEST needed'
+    pos = np.array([])
+    atomtype = []
+    rmt = []
+    neq = np.zeros(nat)
+    iline = 4
+    indif = 0
+    for iat in range(nat):
+        indifini = indif
+        if len(pos) == 0:
+            pos = np.array([[float(pip[iline][12:22]),
+                             float(pip[iline][25:35]),
+                             float(pip[iline][38:48])]])
+        else:
+            pos = np.append(pos, np.array([[float(pip[iline][12:22]),
+                                            float(pip[iline][25:35]),
+                                            float(pip[iline][38:48])]]),
+                            axis = 0)
+        indif += 1
+        iline += 1
+        neq[iat] = int(pip[iline][15:17])
+        iline += 1
+        for ieq in range(1, int(neq[iat])):
+            pos = np.append(pos, np.array([[float(pip[iline][12:22]),
+                                            float(pip[iline][25:35]),
+                                            float(pip[iline][38:48])]]),
+                            axis = 0)
+            indif += 1
+            iline += 1
+        for i in range(indif - indifini):
+            atomtype.append(pip[iline][0:2].replace(' ', ''))
+            rmt.append(float(pip[iline][43:48]))
+        iline += 4
+    if ase:
+        cell2 = coorsys(cell)
+        atoms = Atoms(atomtype, pos, pbc = True)
+        atoms.set_cell(cell2, scale_atoms = True)
+        cell2 = np.dot(c2p(lattice), cell2)
+        if lattice == 'R':
+            atoms.set_cell(cell2, scale_atoms = True)
+        else:
+            atoms.set_cell(cell2)
+        return atoms
+    else:
+        return cell, lattice, pos, atomtype, rmt
+
+def write_struct(filename, atoms2 = None, rmt = None, lattice = 'P'):
+    atoms=atoms2.copy()
+    atoms.set_scaled_positions(atoms.get_scaled_positions())
+    f = file(filename, 'w')
+    f.write('ASE generated\n')
+    nat = len(atoms)
+    if rmt == None:
+        rmt = [2.0] * nat
+    f.write(lattice+'   LATTICE,NONEQUIV.ATOMS:%3i\nMODE OF CALC=RELA\n'%nat)
+    cell = atoms.get_cell()
+    metT = np.dot(cell, np.transpose(cell))
+    cell2 = cellconst(metT)
+    cell2[0:3] = cell2[0:3] / Bohr
+    f.write(('%10.6f' * 6) % tuple(cell2) + '\n')
+    #print atoms.get_positions()[0]
+    for ii in range(nat):
+        f.write('ATOM %3i: ' % (ii + 1))
+        pos = atoms.get_scaled_positions()[ii]
+        f.write('X=%10.8f Y=%10.8f Z=%10.8f\n' % tuple(pos))
+        f.write('          MULT= 1          ISPLIT= 1\n')
+        zz = atoms.get_atomic_numbers()[ii]
+        if zz > 71:
+            ro = 0.000005 
+        elif zz > 36:
+            ro = 0.00001
+        elif zz > 18:
+            ro = 0.00005
+        else:
+            ro = 0.0001
+        f.write('%-10s NPT=%5i  R0=%9.8f RMT=%10.4f   Z:%10.5f\n' %
+                (atoms.get_chemical_symbols()[ii], 781, ro, rmt[ii], zz))
+        f.write('LOCAL ROT MATRIX:    %9.7f %9.7f %9.7f\n' % (1.0, 0.0, 0.0))
+        f.write('                     %9.7f %9.7f %9.7f\n' % (0.0, 1.0, 0.0))
+        f.write('                     %9.7f %9.7f %9.7f\n' % (0.0, 0.0, 1.0))
+    f.write('   0\n')
+
+def cellconst(metT):
+    aa = np.sqrt(metT[0, 0])
+    bb = np.sqrt(metT[1, 1])
+    cc = np.sqrt(metT[2, 2])
+    gamma = np.arccos(metT[0, 1] / (aa * bb)) / np.pi * 180.0
+    beta  = np.arccos(metT[0, 2] / (aa * cc)) / np.pi * 180.0
+    alpha = np.arccos(metT[1, 2] / (bb * cc)) / np.pi * 180.0
+    return np.array([aa, bb, cc, alpha, beta, gamma])
+
+def coorsys(latconst):
+    a = latconst[0]
+    b = latconst[1]
+    c = latconst[2]
+    cal = np.cos(latconst[3] * np.pi / 180.0)
+    cbe = np.cos(latconst[4] * np.pi / 180.0)
+    cga = np.cos(latconst[5] * np.pi / 180.0)
+    sal = np.sin(latconst[3] * np.pi / 180.0)
+    sbe = np.sin(latconst[4] * np.pi / 180.0)
+    sga = np.sin(latconst[5] * np.pi / 180.0)
+    return np.array([[a, b * cga, c * cbe],
+                     [0, b * sga, c * (cal - cbe * cga) / sga],
+                     [0, 0, c * np.sqrt(1 - cal**2 - cbe**2 - cga**2 + 2 * cal * cbe * cga) / sga]]).transpose()
+
+def c2p(lattice):
+    # apply as eg. cell2 = np.dot(ct.c2p('F'), cell)
+    if lattice == 'P':
+        cell = np.eye(3)
+    elif lattice == 'F':
+        cell = np.array([[0.0, 0.5, 0.5], [0.5, 0.0, 0.5], [0.5, 0.5, 0.0]])
+    elif lattice == 'I':
+        cell = np.array([[-0.5, 0.5, 0.5], [0.5, -0.5, 0.5], [0.5, 0.5, -0.5]])
+    elif lattice == 'C':
+        cell = np.array([[0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [0.0, 0.0, -1.0]])
+    elif lattice == 'R':
+        cell = np.array([[2.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0], [-1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0], [-1.0 / 3.0, -2.0/3.0, 1.0 / 3.0]])
+
+    else:
+        print 'lattice is ' + lattice + '!'
+    return cell
Index: ase/io/vnl.py
===================================================================
--- ase/io/vnl.py	(revision 0)
+++ ase/io/vnl.py	(revision 1)
@@ -0,0 +1,37 @@
+import numpy as np
+
+from ase.atoms import Atoms
+
+
+class VNL:
+    def __setstate__(self, data):
+        self.data = data
+
+def ac(shape, typecode, data, endian):
+    x = np.fromstring(data, typecode)
+    try:
+        x.shape = shape
+    except ValueError:
+        x = x[::2].copy()
+        x.shape = shape
+        
+    if np.LittleEndian != endian: 
+        return x.byteswap() 
+    else: 
+        return x 
+
+class VNLUnpickler(pickle.Unpickler):
+    def find_class(self, module, name):
+        if module == 'VNLATKStorage.Core.Sample':
+            return VNL
+        if name == 'array_constructor':
+            return ac
+        return pickle.Unpickler.find_class(self, module, name)
+    
+def read_vnl(filename):
+    from cStringIO import StringIO
+    vnl = VNLUnpickler(StringIO(ZipFile(filename).read('0_object'))).load()
+    conf = vnl.data['__properties__']['Atomic Configuration'].data
+    numbers = conf['_dataarray_']
+    positions = conf['_positions_'].data['_dataarray_']
+    return Atoms(numbers=numbers, positions=positions)
Index: ase/io/plt.py
===================================================================
--- ase/io/plt.py	(revision 0)
+++ ase/io/plt.py	(revision 1)
@@ -0,0 +1,35 @@
+import numpy as np
+
+from ase.atoms import Atoms
+
+def write_plt(filename, atoms, data):
+    if isinstance(atoms, Atoms):
+        cell = atoms.get_cell()
+    else:
+        cell = np.asarray(atoms, float)
+
+    if cell.ndim == 2:
+        c = cell.copy()
+        cell = c.diagonal()
+        c.flat[::4] = 0.0
+        if c.any():
+            raise ValueError('Unit cell must be orthorhombic!')
+
+    f = open(filename, 'w')
+    np.array([3, 4], np.int32).tofile(f)
+
+    dims = np.array(data.shape, np.int32)
+    dims[::-1].tofile(f)
+
+    for n, L in zip(dims[::-1], cell[::-1]):
+        if n % 2 == 0:
+            d = L / n
+            np.array([0.0, L - d], np.float32).tofile(f)
+        else:
+            d = L / (n + 1)
+            np.array([d, L - d], np.float32).tofile(f)
+
+    if data.dtype == complex:
+        data = np.abs(data)
+    data.astype(np.float32).T.tofile(f)
+    f.close()
Index: ase/io/__init__.py
===================================================================
--- ase/io/__init__.py	(revision 0)
+++ ase/io/__init__.py	(revision 1)
@@ -0,0 +1,470 @@
+import os
+import sys
+from os.path import basename
+from tarfile import is_tarfile
+from zipfile import is_zipfile
+
+from ase.atoms import Atoms
+from ase.units import Bohr
+from ase.io.trajectory import PickleTrajectory
+
+__all__ = ['read', 'write', 'PickleTrajectory']
+
+
+def read(filename, index=-1, format=None):
+    """Read Atoms object(s) from file.
+
+    filename: str
+        Name of the file to read from.
+    index: int or slice
+        If the file contains several configurations, the last configuration
+        will be returned by default.  Use index=n to get configuration
+        number n (counting from zero).
+    format: str
+        Used to specify the file-format.  If not given, the
+        file-format will be guessed by the *filetype* function.
+
+    Known formats:
+
+    =========================  ===========
+    format                     short name
+    =========================  ===========
+    GPAW restart-file          gpw
+    Dacapo netCDF output file  dacapo
+    Old ASE netCDF trajectory  nc
+    Virtual Nano Lab file      vnl
+    ASE pickle trajectory      traj
+    GPAW text output           gpaw-text
+    CUBE file                  cube
+    XCrySDen Structure File    xsf  
+    Dacapo text output         dacapo-text
+    XYZ-file                   xyz
+    VASP POSCAR/CONTCAR file   vasp
+    VASP OUTCAR file           vasp_out
+    Protein Data Bank          pdb
+    FHI-aims geometry file     aims
+    FHI-aims output file       aims_out
+    VTK XML Image Data         vti
+    VTK XML Structured Grid    vts
+    VTK XML Unstructured Grid  vtu
+    TURBOMOLE coord file       tmol
+    exciting input             exi
+    AtomEye configuration      cfg
+    WIEN2k structure file      struct
+    DftbPlus input file        dftb
+    =========================  ===========
+
+    """
+    if isinstance(filename, str):
+        p = filename.rfind('@')
+        if p != -1:
+            try:
+                index = string2index(filename[p + 1:])
+            except ValueError:
+                pass
+            else:
+                filename = filename[:p]
+
+    if isinstance(index, str):
+        index = string2index(index)
+
+    if format is None:
+        format = filetype(filename)
+
+    if format.startswith('gpw'):
+        import gpaw
+        r = gpaw.io.open(filename, 'r')
+        positions = r.get('CartesianPositions') * Bohr
+        numbers = r.get('AtomicNumbers')
+        cell = r.get('UnitCell') * Bohr
+        pbc = r.get('BoundaryConditions')
+        tags = r.get('Tags')
+        magmoms = r.get('MagneticMoments')
+
+        atoms = Atoms(positions=positions,
+                      numbers=numbers,
+                      cell=cell,
+                      pbc=pbc)
+        if tags.any():
+            atoms.set_tags(tags)
+        if magmoms.any():
+            atoms.set_initial_magnetic_moments(magmoms)
+
+        return atoms
+
+    if format == 'exi':
+        from ase.io.exciting import read_exciting
+        return read_exciting(filename, index)
+
+    if format == 'xyz':
+        from ase.io.xyz import read_xyz
+        return read_xyz(filename, index)
+
+    if format == 'traj':
+        from ase.io.trajectory import read_trajectory
+        return read_trajectory(filename, index)
+
+    if format == 'cube':
+        from ase.io.cube import read_cube
+        return read_cube(filename, index)
+
+    if format == 'nc':
+        from ase.io.netcdf import read_netcdf
+        return read_netcdf(filename, index)
+
+    if format == 'gpaw-text':
+        from ase.io.gpawtext import read_gpaw_text
+        return read_gpaw_text(filename, index)
+
+    if format == 'dacapo-text':
+        from ase.io.dacapo import read_dacapo_text
+        return read_dacapo_text(filename)
+
+    if format == 'dacapo':
+        from ase.io.dacapo import read_dacapo
+        return read_dacapo(filename)
+    
+    if format == 'xsf':
+        from ase.io.xsf import read_xsf
+        return read_xsf(filename, index)
+
+    if format == 'vasp':
+        from ase.io.vasp import read_vasp
+        return read_vasp(filename)
+    
+    if format == 'vasp_out':
+        from ase.io.vasp import read_vasp_out
+        return read_vasp_out(filename, index)
+    
+    if format == 'mol':
+        from ase.io.mol import read_mol
+        return read_mol(filename)
+
+    if format == 'pdb':
+        from ase.io.pdb import read_pdb
+        return read_pdb(filename)
+
+    if format == 'cif':
+        from ase.io.cif import read_cif
+        return read_cif(filename)
+
+    if format == 'struct':
+        from ase.io.wien2k import read_struct
+        return read_struct(filename)
+
+    if format == 'vti':
+        from ase.io.vtkxml import read_vti
+        return read_vti(filename)
+
+    if format == 'vts':
+        from ase.io.vtkxml import read_vts
+        return read_vts(filename)
+
+    if format == 'vtu':
+        from ase.io.vtkxml import read_vtu
+        return read_vtu(filename)
+
+    if format == 'aims':
+        from ase.io.aims import read_aims
+        return read_aims(filename)
+    
+    if format == 'aims_out':
+        from ase.io.aims import read_aims_output
+        return read_aims_output(filename, index)
+
+    if format == 'iwm':
+        from ase.io.iwm import read_iwm
+        return read_iwm(filename)
+
+    if format == 'Cmdft':
+        from ase.io.cmdft import read_I_info
+        return read_I_info(filename)
+
+    if format == 'tmol':
+        from ase.io.turbomole import read_turbomole
+        return read_turbomole(filename)
+
+    if format == 'cfg':
+        from ase.io.cfg import read_cfg
+        return read_cfg(filename)
+
+    if format == 'dftb':
+        from ase.io.dftb import read_dftb
+        return read_dftb(filename)
+
+    if format == 'sdf':
+        from ase.io.sdf import read_sdf
+        return read_sdf(filename)
+
+    raise RuntimeError('File format descriptor '+format+' not recognized!')
+
+
+def write(filename, images, format=None, **kwargs):
+    """Write Atoms object(s) to file.
+
+    filename: str
+        Name of the file to write to.
+    images: Atoms object or list of Atoms objects
+        A single Atoms object or a list of Atoms objects.
+    format: str
+        Used to specify the file-format.  If not given, the
+        file-format will be taken from suffix of the filename. 
+
+    The accepted output formats:
+  
+    =========================  ===========
+    format                     short name
+    =========================  ===========
+    ASE pickle trajectory      traj
+    CUBE file                  cube
+    XYZ-file                   xyz
+    VASP POSCAR/CONTCAR file   vasp
+    Protein Data Bank          pdb
+    XCrySDen Structure File    xsf
+    FHI-aims geometry file     aims
+    gOpenMol .plt file         plt  
+    Python script              py
+    Encapsulated Postscript    eps
+    Portable Network Graphics  png
+    Persistance of Vision      pov
+    VTK XML Image Data         vti
+    VTK XML Structured Grid    vts
+    VTK XML Unstructured Grid  vtu
+    TURBOMOLE coord file       tmol
+    exciting                   exi
+    AtomEye configuration      cfg
+    WIEN2k structure file      struct
+    DftbPlus input file        dftb
+    =========================  ===========
+  
+    The use of additional keywords is format specific.
+  
+    The ``cube`` and ``plt`` formats accept (plt requires it) a ``data``
+    keyword, which can be used to write a 3D array to the file along
+    with the nuclei coordinates.
+  
+    The ``vti``, ``vts`` and ``vtu`` formats are all specifically directed
+    for use with MayaVi, and the latter is designated for visualization of
+    the atoms whereas the two others are intended for volume data. Further,
+    it should be noted that the ``vti`` format is intended for orthogonal
+    unit cells as only the grid-spacing is stored, whereas the ``vts`` format
+    additionally stores the coordinates of each grid point, thus making it
+    useful for volume date in more general unit cells.
+
+    The ``eps``, ``png``, and ``pov`` formats are all graphics formats,
+    and accept the additional keywords:
+
+    rotation: str (default '')
+      The rotation angles, e.g. '45x,70y,90z'.
+
+    show_unit_cell: int (default 0)
+      Can be 0, 1, 2 to either not show, show, or show all of the unit cell.
+
+    radii: array or float (default 1.0)
+      An array of same length as the list of atoms indicating the sphere radii.
+      A single float specifies a uniform scaling of the default covalent radii.
+
+    bbox: 4 floats (default None)
+      Set the bounding box to (xll, yll, xur, yur) (lower left, upper right).
+
+    colors: array (default None)
+      An array of same length as the list of atoms, indicating the rgb color
+      code for each atom. Default is the jmol_colors of ase/data/colors.
+
+    scale: int (default 20)
+      Number of pixels per Angstrom.
+      
+    For the ``pov`` graphics format, ``scale`` should not be specified.
+    The elements of the color array can additionally be strings, or 4
+    and 5 vectors for named colors, rgb + filter, and rgb + filter + transmit
+    specification. This format accepts the additional keywords:
+
+    ``run_povray``, ``display``, ``pause``, ``transparent``,
+    ``canvas_width``, ``canvas_height``, ``camera_dist``,
+    ``image_plane``, ``camera_type``, ``point_lights``,
+    ``area_light``, ``background``, ``textures``, ``celllinewidth``,
+    ``bondlinewidth``, ``bondatoms``
+    """
+    
+    if format is None:
+        if filename == '-':
+            format = 'xyz'
+            filename = sys.stdout
+        elif 'POSCAR' in filename or 'CONTCAR' in filename:
+            format = 'vasp'
+        elif 'OUTCAR' in filename:
+            format = 'vasp_out'
+        else:
+            suffix = filename.split('.')[-1]
+            format = {}.get(suffix, suffix) # XXX this does not make sense
+            # Maybe like this:
+##             format = {'traj': 'trajectory',
+##                       'nc': 'netcdf',
+##                       'exi': 'exciting',
+##                       'in': 'aims',
+##                       'tmol': 'turbomole',
+##                       }.get(suffix, suffix)
+
+    if format == 'exi':
+        from ase.io.exciting import write_exciting
+        write_exciting(filename, images)
+        return
+
+
+    if format == 'xyz':
+        from ase.io.xyz import write_xyz
+        write_xyz(filename, images)
+        return
+    elif format == 'in':
+        format = 'aims'
+    elif format == 'tmol':
+        from ase.io.turbomole import write_turbomole
+        write_turbomole(filename, images)
+        return
+    elif format == 'dftb':
+        from ase.io.dftb import write_dftb
+        write_dftb(filename, images)
+        return
+    elif format == 'struct':
+        from ase.io.wien2k import write_struct
+        write_struct(filename, images, **kwargs)
+        return
+
+    format = {'traj': 'trajectory', 'nc': 'netcdf'}.get(format, format)
+    name = 'write_' + format
+
+    if format in ['vti', 'vts', 'vtu']:
+        format = 'vtkxml'
+
+    if format is None:
+        format = filetype(filename)
+
+    try:
+        write = getattr(__import__('ase.io.%s' % format, {}, {}, [name]), name)
+    except ImportError:
+        raise TypeError('Unknown format: "%s".' % format)
+    
+    write(filename, images, **kwargs)
+
+
+def string2index(string):
+    if ':' not in string:
+        return int(string)
+    i = []
+    for s in string.split(':'):
+        if s == '':
+            i.append(None)
+        else:
+            i.append(int(s))
+    i += (3 - len(i)) * [None]
+    return slice(*i)
+
+
+def filetype(filename):
+    """Try to guess the type of the file."""
+    fileobj = open(filename)
+    s3 = fileobj.read(3)
+    if len(s3) == 0:
+        raise IOError('Empty file: ' + filename)
+    
+    if is_tarfile(filename):
+        return 'gpw'
+
+    if s3 == 'CDF':
+        from ase.io.pupynere import NetCDFFile
+        nc = NetCDFFile(filename)
+        if 'number_of_dynamic_atoms' in nc.dimensions:
+            return 'dacapo'
+
+        history = nc.history
+        if history == 'GPAW restart file':
+            return 'gpw-nc'
+        if history == 'ASE trajectory':
+            return 'nc'
+        if history == 'Dacapo':
+            return 'dacapo'
+        raise IOError('Unknown netCDF file!')
+
+    
+    if is_zipfile(filename):
+        return 'vnl'
+
+    fileobj.seek(0)
+    lines = fileobj.readlines(1000)
+
+    if lines[0].startswith('PickleTrajectory'):
+        return 'traj'
+
+    if lines[1].startswith('OUTER LOOP:') or filename.lower().endswith('.cube'):
+        return 'cube'
+    
+    if '  ___ ___ ___ _ _ _  \n' in lines:
+        return 'gpaw-text'
+
+    if (' &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&\n'
+        in lines[:90]):
+        return 'dacapo-text'
+
+    for word in ['ANIMSTEPS', 'CRYSTAL', 'SLAB', 'POLYMER', 'MOLECULE']:
+        if lines[0].startswith(word):
+            return 'xsf'
+
+    filename_v = basename(filename) 
+    if 'POSCAR' in filename_v or 'CONTCAR' in filename_v:
+        return 'vasp'    
+
+    if 'OUTCAR' in filename_v:
+        return 'vasp_out'
+    
+    if filename.lower().endswith('.exi'):
+        return 'exi'
+    
+    if filename.lower().endswith('.mol'):
+        return 'mol'
+
+    if filename.lower().endswith('.pdb'):
+        return 'pdb'
+
+    if filename.lower().endswith('.cif'):
+        return 'cif'
+   
+    if filename.lower().endswith('.struct'):
+        return 'struct'
+
+    if filename.lower().endswith('.in'):
+        return 'aims'
+
+    if filename.lower().endswith('.out'):
+        return 'aims_out'
+
+    if filename.lower().endswith('.cfg'):
+        return 'cfg'
+        
+    if os.path.split(filename)[1] == 'atoms.dat':
+        return 'iwm'
+
+    if filename.endswith('I_info'):
+        return 'Cmdft'
+
+    if lines[0].startswith('$coord'):
+        return 'tmol'
+
+    if lines[0].startswith('Geometry'):
+        return 'dftb'
+
+    if s3 == '<?x':
+        from ase.io.vtkxml import probe_vtkxml
+        xmltype = probe_vtkxml(filename)
+        if xmltype == 'ImageData':
+            return 'vti'
+        elif xmltype == 'StructuredGrid':
+            return 'vts'
+        elif xmltype == 'UnstructuredGrid':
+            return 'vtu'
+        elif xmltype is not None:
+            raise IOError('Unknown VTK XML file!')
+
+    if filename.lower().endswith('.sdf'):
+        return 'sdf'
+
+    return 'xyz'
Index: ase/io/cfg.py
===================================================================
--- ase/io/cfg.py	(revision 0)
+++ ase/io/cfg.py	(revision 1)
@@ -0,0 +1,253 @@
+import numpy as np
+
+import ase
+
+from ase.parallel import paropen
+
+
+cfg_default_fields = np.array( [ 'positions', 'momenta', 'numbers', 'magmoms' ] )
+
+def write_cfg(f, a):
+    """Write atomic configuration to a CFG-file (native AtomEye format).
+       See: http://mt.seas.upenn.edu/Archive/Graphics/A/
+    """
+    if isinstance(f, str):
+        f = paropen(f, 'w')
+
+    f.write('Number of particles = %i\n' % len(a))
+    f.write('A = 1.0 Angstrom\n')
+    cell = a.get_cell()
+    for i in range(3):
+        for j in range(3):
+            f.write('H0(%1.1i,%1.1i) = %f A\n' % ( i + 1, j + 1, cell[i, j] ))
+
+    entry_count  = 3
+    for x in a.arrays.keys():
+        if not x in cfg_default_fields:
+            if len(a.get_array(x).shape) == 1:
+                entry_count += 1
+            else:
+                entry_count += a.get_array(x).shape[1]
+
+    vels = a.get_velocities()
+    if type(vels) == np.ndarray:
+        entry_count += 3
+    else:
+        f.write('.NO_VELOCITY.\n')
+
+    f.write('entry_count = %i\n' % entry_count)
+
+    i = 0
+    for name, aux in a.arrays.iteritems():
+        if not name in cfg_default_fields:
+            if len(aux.shape) == 1:
+                f.write('auxiliary[%i] = %s [a.u.]\n' % ( i, name ))
+                i += 1
+            else:
+                for j in range(aux.shape[1]):
+                    f.write('auxiliary[%i] = %s_%1.1i [a.u.]\n' % ( i, name, j ))
+                    i += 1
+
+    # Distinct elements
+    spos = a.get_scaled_positions()
+    for i in a:
+        el  = i.get_symbol()
+
+        f.write('%f\n' % ase.data.atomic_masses[ase.data.chemical_symbols.index(el)])
+        f.write('%s\n' % el)
+
+        x, y, z = spos[i.index, :]
+        s =  '%e %e %e ' % ( x, y, z )
+
+        if type(vels) == np.ndarray:
+            vx, vy, vz  = vels[i.index, :]
+            s  = s + ' %e %e %e ' % ( vx, vy, vz )
+
+        for name, aux in a.arrays.iteritems():
+            if not name in cfg_default_fields:
+                if len(aux.shape) == 1:
+                    s += ' %e' % aux[i.index]
+                else:
+                    s += ( aux.shape[1]*' %e' ) % tuple(aux[i.index].tolist())
+                         
+        f.write('%s\n' % s)
+
+
+default_color = {
+    'H': [ 0.800, 0.800, 0.800 ],
+    'C': [ 0.350, 0.350, 0.350 ],
+    'O': [ 0.800, 0.200, 0.200 ]
+    }
+
+default_radius = {
+    'H': 0.435,
+    'C': 0.655,
+    'O': 0.730
+    }
+       
+
+
+def write_clr(f, atoms):
+    """Write extra color and radius code to a CLR-file (for use with AtomEye).
+       Hit F12 in AtomEye to use.
+       See: http://mt.seas.upenn.edu/Archive/Graphics/A/
+    """
+    color   = None
+    radius  = None
+    if atoms.has('color'):
+        color  = atoms.get_array('color')
+    if atoms.has('radius'):
+        radius  = atoms.get_array('radius')
+
+    if color is None:
+        color  = np.zeros([len(atoms),3], dtype=float)
+        for a in atoms:
+            color[a.index, :]  = default_color[a.get_symbol()]
+
+    if radius is None:
+        radius  = np.zeros(len(atoms), dtype=float)
+        for a in atoms:
+            radius[a.index]  = default_radius[a.get_symbol()]
+
+    radius.shape = (-1, 1)
+
+    if isinstance(f, str):
+        f = paropen(f, 'w')   
+    for c1, c2, c3, r in np.append(color, radius, axis=1):
+        f.write('%f %f %f %f\n' % ( c1, c2, c3, r ))
+
+
+###
+
+def read_key_val(f):
+    if isinstance(f, str):
+        l = f
+    else:
+        l = f.readline()
+    s = l.split('=')
+    if len(s) != 2:
+        raise RuntimeError("Line '%s' is not of the form 'key = value'." % l[:-1])
+    return ( s[0].strip(), s[1].strip() )
+
+
+def read_str_key(f, key, key2=None):
+    in_key, val  = read_key_val(f)
+    if key2 is None:
+        if key.upper() != in_key.upper():
+            raise RuntimeError("Key '%s' expected, '%s' found." % ( key, in_key ))
+    else:
+        if key.upper() != in_key.upper() and key2.upper() != in_key.upper():
+            raise RuntimeError("Key '%s' or '%s' expected, '%s' found." % ( key, key2, in_key ))
+    return val
+
+
+def read_int_key(f, key):
+    vals  = read_str_key(f, key).split()
+    # Ignore units
+    return int(vals[0])
+
+
+def read_float_key(f, key):
+    vals  = read_str_key(f, key).split()
+    # Ignore units
+    return float(vals[0])
+
+
+###
+
+def read_cfg(f):
+    """Read atomic configuration from a CFG-file (native AtomEye format).
+       See: http://mt.seas.upenn.edu/Archive/Graphics/A/
+    """
+    if isinstance(f, str):
+        f  = open(f)
+
+    nat   = read_int_key(f, 'Number of particles')
+    unit  = read_float_key(f, 'A')
+
+    cell  = np.zeros( [ 3, 3 ] )
+    for i in range(3):
+        for j in range(3):
+            cell[i, j]  = read_float_key(f, 'H0(%1.1i,%1.1i)' % (i + 1, j + 1))
+
+    l     = f.readline()
+    vels  = None
+    if l.strip() == '.NO_VELOCITY.':
+        l     = f.readline()
+    else:
+        vels  = np.zeros( [ nat, 3 ] )
+
+    naux      = read_int_key(l, 'entry_count') - 3
+    if vels is not None:
+        naux -= 3
+    aux       = np.zeros( [ nat, naux ] )
+
+    auxstrs   = [ ]
+    for i in range(naux):
+        s  = read_str_key(f, 'auxiliary[%1.1i]' % i, 'auxiliary[%2.2i]' % i)
+        auxstrs += [ s[:s.find('[')].strip() ]
+
+    spos     = np.zeros( [ nat, 3 ] )
+    masses   = np.zeros( nat )
+    syms     = [ '' for i in range(nat) ]
+
+    i  = 0
+    s  = f.readline().split()
+    while l:
+        mass   = float(s[0])
+        sym    = f.readline().strip()
+
+        l      = f.readline()
+        s      = l.split()
+
+        while l and len(s) > 1:
+            masses[i]  = mass
+            syms[i]    = sym
+            props      = [ float(x) for x in s ]
+
+            spos[i, :] = props[0:3]
+            off        = 3
+            if vels is not None:
+                off         = 6
+                vels[i, :]  = props[3:6]
+
+            aux[i, :]  = props[off:]
+
+            i  += 1
+
+            l  = f.readline()
+            if l:
+                s  = l.split()
+                
+    if vels is None:
+        a = ase.Atoms(
+            symbols           = syms,
+            masses            = masses,
+            scaled_positions  = spos,
+            cell              = cell,
+            pbc               = True
+        )
+    else:
+        a = ase.Atoms(
+            symbols           = syms,
+            masses            = masses,
+            scaled_positions  = spos,
+            momenta           = masses.reshape(-1,1)*vels,
+            cell              = cell,
+            pbc               = True
+        )
+
+    i  = 0
+    while i < naux:
+        auxstr  = auxstrs[i]
+
+        if auxstr[-2:] == '_x':
+            a.set_array(auxstr[:-2], aux[:, i:i+3])
+
+            i  += 3
+        else:
+            a.set_array(auxstr, aux[:, i])
+
+            i  += 1
+
+    return a
Index: ase/io/xsf.py
===================================================================
--- ase/io/xsf.py	(revision 0)
+++ ase/io/xsf.py	(revision 1)
@@ -0,0 +1,153 @@
+import numpy as np
+
+from ase.atoms import Atoms
+from ase.units import Hartree
+from ase.parallel import paropen
+from ase.calculators.singlepoint import SinglePointCalculator
+
+
+def write_xsf(fileobj, images, data=None):
+    if isinstance(fileobj, str):
+        fileobj = paropen(fileobj, 'w')
+        
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+
+    fileobj.write('ANIMSTEPS %d\n' % len(images))
+
+    numbers = images[0].get_atomic_numbers()
+    
+    pbc = images[0].get_pbc()
+    if pbc[2]:
+        fileobj.write('CRYSTAL\n')
+    elif pbc[1]:
+        fileobj.write('SLAB\n')
+    elif pbc[0]:
+        fileobj.write('POLYMER\n')
+    else:
+        fileobj.write('MOLECULE\n')
+
+    for n, atoms in enumerate(images):
+        if pbc.any():
+            fileobj.write('PRIMVEC %d\n' % (n + 1))
+            cell = atoms.get_cell()
+            for i in range(3):
+                fileobj.write(' %.14f %.14f %.14f\n' % tuple(cell[i]))
+
+        fileobj.write('PRIMCOORD %d\n' % (n + 1))
+
+        # Get the forces if it's not too expensive:
+        calc = atoms.get_calculator()
+        if (calc is not None and
+            (hasattr(calc, 'calculation_required') and
+             not calc.calculation_required(atoms,
+                                           ['energy', 'forces', 'stress']))):
+            forces = atoms.get_forces()
+        else:
+            forces = None
+
+        pos = atoms.get_positions()
+
+        fileobj.write(' %d 1\n' % len(pos))
+        for a in range(len(pos)):
+            fileobj.write(' %2d' % numbers[a])
+            fileobj.write(' %20.14f %20.14f %20.14f' % tuple(pos[a]))
+            if forces is None:
+                fileobj.write('\n')
+            else:
+                fileobj.write(' %20.14f %20.14f %20.14f\n' % tuple(forces[a]))
+            
+    if data is None:
+        return
+
+    fileobj.write('BEGIN_BLOCK_DATAGRID_3D\n')
+    fileobj.write(' data\n')
+    fileobj.write(' BEGIN_DATAGRID_3Dgrid#1\n')
+
+    data = np.asarray(data)
+    if data.dtype == complex:
+        data = np.abs(data)
+
+    shape = data.shape
+    fileobj.write('  %d %d %d\n' % shape)
+
+    cell = atoms.get_cell()
+    origin = np.zeros(3)
+    for i in range(3):
+        if not pbc[i]:
+            origin += cell[i] / shape[i]
+    fileobj.write('  %f %f %f\n' % tuple(origin))
+
+    for i in range(3):
+        fileobj.write('  %f %f %f\n' %
+                      tuple(cell[i] * (shape[i] + 1) / shape[i]))
+
+    for x in range(shape[2]):
+        for y in range(shape[1]):
+            fileobj.write('   ')
+            fileobj.write(' '.join(['%f' % d for d in data[x, y]]))
+            fileobj.write('\n')
+        fileobj.write('\n')
+
+    fileobj.write(' END_DATAGRID_3D\n')
+    fileobj.write('END_BLOCK_DATAGRID_3D\n')
+
+
+def read_xsf(fileobj, index=-1):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    readline = fileobj.readline
+    line = readline()
+
+    if line.startswith('ANIMSTEPS'):
+        nimages = int(line.split()[1])
+        line = readline()
+    else:
+        nimages = 1
+
+    if line.startswith('CRYSTAL'):
+        pbc = True
+    elif line.startswith('SLAB'):
+        pbc = (True, True, Flase)
+    elif line.startswith('POLYMER'):
+        pbc = (True, False, Flase)
+    else:
+        pbc = False
+
+    images = []
+    for n in range(nimages):
+        cell = None
+        if pbc:
+            line = readline()
+            assert line.startswith('PRIMVEC')
+            cell = []
+            for i in range(3):
+                cell.append([float(x) for x in readline().split()])
+
+        line = readline()
+        assert line.startswith('PRIMCOORD')
+
+        natoms = int(readline().split()[0])
+        numbers = []
+        positions = []
+        for a in range(natoms):
+            line = readline().split()
+            numbers.append(int(line[0]))
+            positions.append([float(x) for x in line[1:]])
+
+        positions = np.array(positions)
+        if len(positions[0]) == 3:
+            forces = None
+        else:
+            positions = positions[:, :3]
+            forces = positions[:, 3:] * Hartree
+
+        image = Atoms(numbers, positions, cell=cell, pbc=pbc)
+
+        if forces is not None:
+            image.set_calculator(SinglePointCalculator(None, forces, None,
+                                                       None, image))
+        images.append(image)
+
+    return images[index]
Index: ase/io/cif.py
===================================================================
--- ase/io/cif.py	(revision 0)
+++ ase/io/cif.py	(revision 1)
@@ -0,0 +1,58 @@
+from math import sin, cos, pi, sqrt
+import numpy as np
+
+from ase.atoms import Atoms, Atom
+from ase.parallel import paropen
+
+"""Module to read and write atoms in cif file format"""
+
+
+def read_cif(fileobj, index=-1):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    def search_key(fobj, key):
+        for line in fobj:
+            if key in line:
+                return line
+        return None
+
+    def get_key(fobj, key, pos=1):
+        line = search_key(fobj, key)
+        if line:
+            return float(line.split()[pos].split('(')[0])
+        return None
+
+    a = get_key(fileobj, '_cell_length_a')
+    b = get_key(fileobj, '_cell_length_b')
+    c = get_key(fileobj, '_cell_length_c')
+    alpha =  pi * get_key(fileobj, '_cell_angle_alpha') / 180
+    beta = pi * get_key(fileobj, '_cell_angle_beta') / 180
+    gamma =  pi * get_key(fileobj, '_cell_angle_gamma') / 180
+
+    va = a * np.array([1, 0, 0])
+    vb = b * np.array([cos(gamma), sin(gamma), 0])
+    cx = cos(beta)
+    cy = (cos(alpha) - cos(beta) * cos(gamma)) / sin(gamma)
+    cz = sqrt(1. - cx*cx - cy*cy)
+    vc = c * np.array([cx, cy, cz])
+    cell = np.array([va, vb, vc])
+
+    atoms = Atoms(cell=cell)
+    read = False
+    for line in fileobj:
+        if not read:
+            if '_atom_site_disorder_group' in line:
+                read = True
+        else:
+            word = line.split()
+            if len(word) < 5:
+                break
+            symbol = word[1]
+            pos = (float(word[2].split('(')[0]) * va +
+                   float(word[3].split('(')[0]) * vb +
+                   float(word[4].split('(')[0]) * vc   )
+            atoms.append(Atom(symbol, pos))
+
+    return atoms
+
Index: ase/io/netcdf.py
===================================================================
--- ase/io/netcdf.py	(revision 0)
+++ ase/io/netcdf.py	(revision 1)
@@ -0,0 +1,112 @@
+"""Read and write ASE2's netCDF trajectory files."""
+
+from ase.io.pupynere import NetCDFFile
+from ase.atoms import Atoms
+from ase.calculators.singlepoint import SinglePointCalculator
+
+
+def read_netcdf(filename, index=-1):
+    nc = NetCDFFile(filename)
+    dims = nc.dimensions
+    vars = nc.variables
+
+    positions = vars['CartesianPositions']
+    numbers = vars['AtomicNumbers'][:]
+    pbc = vars['BoundaryConditions'][:]
+    cell = vars['UnitCell']
+    tags = vars['Tags'][:]
+    if not tags.any():
+        tags = None
+    magmoms = vars['MagneticMoments'][:]
+    if not magmoms.any():
+        magmoms = None
+
+    nimages = positions.shape[0]
+
+    attach_calculator = False
+    if 'PotentialEnergy' in vars:
+        energy = vars['PotentialEnergy']
+        attach_calculator = True
+    else:
+        energy = nimages * [None]
+
+    if 'CartesianForces' in vars:
+        forces = vars['CartesianForces']
+        attach_calculator = True
+    else:
+        forces = nimages * [None]
+
+    if 'Stress' in vars:
+        stress = vars['Stress']
+        attach_calculator = True
+    else:
+        stress = nimages * [None]
+
+    if isinstance(index, int):
+        indices = [index]
+    else:
+        indices = range(nimages)[index]
+
+    images = []
+    for i in indices:
+        atoms = Atoms(positions=positions[i],
+                      numbers=numbers,
+                      cell=cell[i],
+                      pbc=pbc,
+                      tags=tags, magmoms=magmoms)
+
+        if attach_calculator:
+            calc = SinglePointCalculator(energy[i], forces[i], stress[i],
+                                         None, atoms) ### Fixme magmoms
+            atoms.set_calculator(calc)
+            
+        images.append(atoms)
+        
+    if isinstance(index, int):
+        return images[0]
+    else:
+        return images
+
+
+class LOA:
+    def __init__(self, images):
+        self.set_atoms(images[0])
+
+    def __len__(self):
+        return len(self.atoms)
+    
+    def set_atoms(self, atoms):
+        self.atoms = atoms
+        
+    def GetPotentialEnergy(self):
+        return self.atoms.get_potential_energy()
+
+    def GetCartesianForces(self):
+        return self.atoms.get_forces()
+
+    def GetUnitCell(self):
+        return self.atoms.get_cell()
+
+    def GetAtomicNumbers(self):
+        return self.atoms.get_atomic_numbers()
+
+    def GetCartesianPositions(self):
+        return self.atoms.get_positions()
+
+    def GetBoundaryConditions(self):
+        return self.atoms.get_pbc()
+    
+
+def write_netcdf(filename, images):
+    from ASE.Trajectories.NetCDFTrajectory import NetCDFTrajectory
+
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+        
+    loa = LOA(images)
+    traj = NetCDFTrajectory(filename, loa)
+    for atoms in images:
+        loa.set_atoms(atoms)
+        traj.Update()
+    traj.Close()
+
Index: ase/io/gpawtext.py
===================================================================
--- ase/io/gpawtext.py	(revision 0)
+++ ase/io/gpawtext.py	(revision 1)
@@ -0,0 +1,90 @@
+from ase.atoms import Atom, Atoms
+from ase.calculators.singlepoint import SinglePointCalculator
+
+
+def read_gpaw_text(fileobj, index=-1):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    def index_startswith(lines, string):
+        for i, line in enumerate(lines):
+            if line.startswith(string):
+                return i
+        raise ValueError
+
+    lines = fileobj.readlines()
+    images = []
+    while True:
+        try:
+            i = lines.index('Unit Cell:\n')
+        except ValueError:
+            pass
+        else:
+            cell = []
+            pbc = []
+            for line in lines[i + 3:i + 6]:
+                words = line.split()
+                if len(words) == 5:  # old format
+                    cell.append(float(words[2]))
+                    pbc.append(words[1] == 'yes')
+                else:                # new format with GUC
+                    cell.append([float(word) for word in words[3:6]])
+                    pbc.append(words[2] == 'yes')
+            
+        try:
+            i = lines.index('Positions:\n')
+        except ValueError:
+            break
+
+        atoms = Atoms(cell=cell, pbc=pbc)
+        for line in lines[i + 1:]:
+            words = line.split()
+            if len(words) != 5:
+                break
+            n, symbol, x, y, z = words
+            symbol = symbol.split('.')[0]
+            atoms.append(Atom(symbol, [float(x), float(y), float(z)]))
+        lines = lines[i + 5:]
+        try:
+            i = lines.index('-------------------------\n')
+        except ValueError:
+            e = None
+        else:
+            line = lines[i + 9]
+            assert line.startswith('Zero Kelvin:')
+            e = float(line.split()[-1])
+        try:
+            ii = index_startswith(lines, 'Total Charge:')
+        except ValueError:
+            q = None
+        else:
+            q = float(lines[ii].split()[2])
+        try:
+            ii = lines.index('Forces in eV/Ang:\n')
+        except ValueError:
+            f = None
+        else:
+            f = []
+            for i in range(ii + 1, ii + 1 + len(atoms)):
+                try:
+                    x, y, z = lines[i].split()[-3:]
+                    f.append((float(x), float(y), float(z)))
+                except (ValueError, IndexError), m:
+                    raise IOError('Malformed GPAW log file: %s' % m)
+
+        if len(images) > 0 and e is None:
+            break
+
+        if e is not None or f is not None:
+            atoms.set_calculator(SinglePointCalculator(e, f, None, None, atoms)) ### Fixme magmoms
+        if q is not None:
+            n = len(atoms)
+            atoms.set_charges([q / n] * n)
+
+        images.append(atoms)
+        lines = lines[i:]
+
+    if len(images) == 0:
+        raise IOError('Corrupted GPAW-text file!')
+    
+    return images[index]
Index: ase/io/pov.py
===================================================================
--- ase/io/pov.py	(revision 0)
+++ ase/io/pov.py	(revision 1)
@@ -0,0 +1,253 @@
+"""
+Module for povray file format support.
+
+See http://www.povray.org/ for details on the format.
+"""
+import os
+
+import numpy as np
+
+from ase.io.eps import EPS
+from ase.data import chemical_symbols
+
+
+def pa(array):
+    """Povray array syntax"""
+    return '<% 6.2f, % 6.2f, % 6.2f>' % tuple(array)
+
+
+def pc(array):
+    """Povray color syntax"""
+    if type(array) == str:
+        return 'color ' + array
+    if type(array) == float:
+        return 'rgb <%.2f>*3' % array
+    if len(array) == 3:
+        return 'rgb <%.2f, %.2f, %.2f>' % tuple(array)
+    if len(array) == 4: # filter
+        return 'rgbf <%.2f, %.2f, %.2f, %.2f>' % tuple(array)
+    if len(array) == 5: # filter and transmit
+        return 'rgbft <%.2f, %.2f, %.2f, %.2f, %.2f>' % tuple(array)
+
+
+def get_bondpairs(atoms, radius=1.1):
+    """Get all pairs of bonding atoms
+
+    Return all pairs of atoms which are closer than radius times the
+    sum of their respective covalent radii.  The pairs are returned as
+    tuples::
+
+      (a, b, (i1, i2, i3))
+
+    so that atoms a bonds to atom b displaced by the vector::
+
+        _     _     _
+      i c + i c + i c ,
+       1 1   2 2   3 3
+
+    where c1, c2 and c3 are the unit cell vectors and i1, i2, i3 are
+    integers."""
+    
+    from ase.data import covalent_radii
+    from ase.calculators.neighborlist import NeighborList
+    cutoffs = radius * covalent_radii[atoms.numbers]
+    nl = NeighborList(cutoffs=cutoffs, self_interaction=False)
+    nl.update(atoms)
+    bondpairs = []
+    for a in range(len(atoms)):
+        indices, offsets = nl.get_neighbors(a)
+        bondpairs.extend([(a, a2, offset)
+                          for a2, offset in zip(indices, offsets)])
+    return bondpairs
+
+
+class POVRAY(EPS):
+    default_settings = {
+        # x, y is the image plane, z is *out* of the screen
+        'display'      : True, # Display while rendering
+        'pause'        : True, # Pause when done rendering (only if display)
+        'transparent'  : True, # Transparent background
+        'canvas_width' : None, # Width of canvas in pixels
+        'canvas_height': None, # Height of canvas in pixels 
+        'camera_dist'  : 50.,  # Distance from camera to front atom
+        'image_plane'  : None, # Distance from front atom to image plane
+        'camera_type'  : 'orthographic', # perspective, ultra_wide_angle
+        'point_lights' : [],             # [[loc1, color1], [loc2, color2],...]
+        'area_light'   : [(2., 3., 40.), # location
+                          'White',       # color
+                          .7, .7, 3, 3], # width, height, Nlamps_x, Nlamps_y
+        'background'   : 'White',        # color
+        'textures'     : None, # Length of atoms list of texture names
+        'celllinewidth': 0.05, # Radius of the cylinders representing the cell
+        'bondlinewidth': 0.10, # Radius of the cylinders representing the bonds
+        'bondatoms'    : [], # [[atom1, atom2], ...] pairs of bonding atoms
+        }
+
+    def __init__(self, atoms, scale=1.0, **parameters):
+        for k, v in self.default_settings.items():
+            setattr(self, k, parameters.pop(k, v))
+        EPS.__init__(self, atoms, scale=scale, **parameters)
+
+    def cell_to_lines(self, A):
+        return np.empty((0, 3)), None, None
+
+    def write(self, filename, **settings):
+        # Determine canvas width and height
+        ratio = float(self.w) / self.h
+        if self.canvas_width is None:
+            if self.canvas_height is None:
+                self.canvas_width = min(self.w * 15, 640)
+            else:
+                self.canvas_width = self.canvas_height * ratio
+        elif self.canvas_height is not None:
+            raise RuntimeError, "Can't set *both* width and height!"
+
+        # Distance to image plane from camera
+        if self.image_plane is None:
+            if self.camera_type == 'orthographic':
+                self.image_plane = 1 - self.camera_dist
+            else:
+                self.image_plane = 0
+        self.image_plane += self.camera_dist
+
+        # Produce the .ini file
+        if filename.endswith('.pov'):
+            ini = open(filename[:-4] + '.ini', 'w').write
+        else:
+            ini = open(filename + '.ini', 'w').write
+        ini('Input_File_Name=%s\n' % filename)
+        ini('Output_to_File=True\n')
+        ini('Output_File_Type=N\n')
+        ini('Output_Alpha=%s\n' % self.transparent)
+        ini('; if you adjust Height, and width, you must preserve the ratio\n')
+        ini('; Width / Height = %s\n' % repr(ratio))
+        ini('Width=%s\n' % self.canvas_width)
+        ini('Height=%s\n' % (self.canvas_width / ratio))
+        ini('Antialias=True\n')
+        ini('Antialias_Threshold=0.1\n')
+        ini('Display=%s\n' % self.display)
+        ini('Pause_When_Done=%s\n' % self.pause)
+        ini('Verbose=False\n')
+        del ini
+
+        # Produce the .pov file
+        w = open(filename, 'w').write
+        w('#include "colors.inc"\n')
+        w('#include "finish.inc"\n')
+        w('\n')
+        w('global_settings {assumed_gamma 1 max_trace_level 6}\n')
+        w('background {%s}\n' % pc(self.background))
+        w('camera {%s\n' % self.camera_type)
+        w('  right -%.2f*x up %.2f*y\n' % (self.w, self.h))
+        w('  direction %.2f*z\n' % self.image_plane)
+        w('  location <0,0,%.2f> look_at <0,0,0>}\n' % self.camera_dist)
+        for loc, rgb in self.point_lights:
+            w('light_source {%s %s}\n' % (pa(loc), pc(rgb)))
+
+        if self.area_light is not None:
+            loc, color, width, height, nx, ny = self.area_light
+            w('light_source {%s %s\n' % (pa(loc), pc(color)))
+            w('  area_light <%.2f, 0, 0>, <0, %.2f, 0>, %i, %i\n' % (
+                width, height, nx, ny))
+            w('  adaptive 1 jitter}\n')
+
+        w('\n')
+        w('#declare simple = finish {phong 0.7}\n')
+        w('#declare vmd = finish {'
+          'ambient .0 '
+          'diffuse .65 '
+          'phong 0.1 '
+          'phong_size 40. '
+          'specular 0.500 }\n')
+        w('#declare jmol = finish {'
+          'ambient .2 '
+          'diffuse .6 '
+          'specular 1 '
+          'roughness .001 '
+          'metallic}\n')
+        w('#declare ase2 = finish {'
+          'ambient 0.05 '
+          'brilliance 3 '
+          'diffuse 0.6 '
+          'metallic '
+          'specular 0.70 '
+          'roughness 0.04 '
+          'reflection 0.15}\n')
+        w('#declare ase3 = finish {'
+          'ambient .15 '
+          'brilliance 2 '
+          'diffuse .6 '
+          'metallic '
+          'specular 1. '
+          'roughness .001 '
+          'reflection .0}\n')
+        w('#declare glass = finish {' # Use filter 0.7
+          'ambient .05 '
+          'diffuse .3 '
+          'specular 1. '
+          'roughness .001}\n')
+        w('#declare Rcell = %.3f;\n' % self.celllinewidth)
+        w('#declare Rbond = %.3f;\n' % self.bondlinewidth)
+        w('\n')
+        w('#macro atom(LOC, R, COL, FIN)\n')
+        w('  sphere{LOC, R texture{pigment{COL} finish{FIN}}}\n')
+        w('#end\n')
+        w('\n')
+        
+        z0 = self.X[:, 2].max()
+        self.X -= (self.w / 2, self.h / 2, z0)
+
+        # Draw unit cell
+        if self.C is not None:
+            self.C -= (self.w / 2, self.h / 2, z0)
+            self.C.shape = (2, 2, 2, 3)
+            for c in range(3):
+                for j in ([0, 0], [1, 0], [1, 1], [0, 1]):
+                    w('cylinder {')
+                    for i in range(2):
+                        j.insert(c, i)
+                        w(pa(self.C[tuple(j)]) + ', ')
+                        del j[c]
+                    w('Rcell pigment {Black}}\n')
+
+        # Draw atoms
+        a = 0
+        for loc, dia, color in zip(self.X, self.d, self.colors):
+            tex = 'ase3'
+            if self.textures is not None:
+                tex = self.textures[a]
+            w('atom(%s, %.2f, %s, %s) // #%i \n' % (
+                pa(loc), dia / 2., pc(color), tex, a))
+            a += 1
+
+        # Draw atom bonds
+        for pair in self.bondatoms:
+            if len(pair) == 2:
+                a, b = pair
+                offset = (0, 0, 0)
+            else:
+                a, b, offset = pair
+            R = np.dot(offset, self.A)
+            mida = 0.5 * (self.X[a] + self.X[b] + R)
+            midb = 0.5 * (self.X[a] + self.X[b] - R)
+            if self.textures is not None:
+                texa = self.textures[a]
+                texb = self.textures[b]
+            else:
+                texa = texb = 'ase3'
+            w('cylinder {%s, %s, Rbond texture{pigment {%s} finish{%s}}}\n' % (
+                pa(self.X[a]), pa(mida), pc(self.colors[a]), texa))
+            w('cylinder {%s, %s, Rbond texture{pigment {%s} finish{%s}}}\n' % (
+                pa(self.X[b]), pa(midb), pc(self.colors[b]), texb))
+
+
+def write_pov(filename, atoms, run_povray=False, **parameters):
+    if isinstance(atoms, list):
+        assert len(atoms) == 1
+        atoms = atoms[0]
+    assert 'scale' not in parameters
+    POVRAY(atoms, **parameters).write(filename)
+    if run_povray:
+        errcode = os.system('povray %s.ini 2> /dev/null' % filename[:-4])
+        if errcode != 0:
+            raise OSError('Povray failed with error code %d' % errcode)
Index: ase/io/pdb.py
===================================================================
--- ase/io/pdb.py	(revision 0)
+++ ase/io/pdb.py	(revision 1)
@@ -0,0 +1,59 @@
+import numpy as np 
+
+from ase.atoms import Atom, Atoms
+from ase.parallel import paropen
+
+"""Module to read and write atoms in PDB file format"""
+
+
+def read_pdb(fileobj, index=-1):
+    """Read PDB files.
+
+    The format is assumed to follow the description given in
+    http://www.wwpdb.org/documentation/format32/sect9.html."""
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    atoms = Atoms()
+    for line in fileobj.readlines():
+        if line.startswith('ATOM') or line.startswith('HETATM'):
+            try:
+                symbol = line[12:16].strip()
+                # we assume that the second character is a label 
+                # in case that it is upper case
+                if len(symbol) > 1 and symbol[1].isupper():
+                    symbol = symbol[0]
+                words = line[30:55].split()
+                position = np.array([float(words[0]), 
+                                     float(words[1]),
+                                     float(words[2])])
+                atoms.append(Atom(symbol, position))
+            except:
+                pass
+
+    return atoms
+
+def write_pdb(fileobj, images):
+    """Write images to PDB-file."""
+    if isinstance(fileobj, str):
+        fileobj = paropen(fileobj, 'w')
+
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+
+    format = 'ATOM  %5d %-4s              %8.3f%8.3f%8.3f  0.00  0.00\n'
+
+    # RasMol complains if the atom index exceeds 100000. There might
+    # be a limit of 5 digit numbers in this field.
+    MAXNUM = 100000
+
+    symbols = images[0].get_chemical_symbols()
+    natoms = len(symbols)
+    
+    for atoms in images:
+        fileobj.write('MODEL         1\n')
+        p = atoms.get_positions()
+        for a in range(natoms):
+            x, y, z = p[a]
+            fileobj.write(format % (a % MAXNUM, symbols[a], x, y, z))
+        fileobj.write('ENDMDL\n')
Index: ase/io/pupynere.py
===================================================================
--- ase/io/pupynere.py	(revision 0)
+++ ase/io/pupynere.py	(revision 1)
@@ -0,0 +1,614 @@
+"""
+NetCDF reader/writer module.
+
+This module implements the Scientific.IO.NetCDF API to read and create
+NetCDF files. The same API is also used in the PyNIO and pynetcdf
+modules, allowing these modules to be used interchangebly when working
+with NetCDF files. The major advantage of ``scipy.io.netcdf`` over other 
+modules is that it doesn't require the code to be linked to the NetCDF
+libraries as the other modules do.
+
+The code is based on the NetCDF file format specification
+(http://www.unidata.ucar.edu/software/netcdf/guide_15.html). A NetCDF
+file is a self-describing binary format, with a header followed by
+data. The header contains metadata describing dimensions, variables
+and the position of the data in the file, so access can be done in an
+efficient manner without loading unnecessary data into memory. We use
+the ``mmap`` module to create Numpy arrays mapped to the data on disk,
+for the same purpose.
+
+The structure of a NetCDF file is as follows:
+
+    C D F <VERSION BYTE> <NUMBER OF RECORDS>
+    <DIMENSIONS> <GLOBAL ATTRIBUTES> <VARIABLES METADATA>
+    <NON-RECORD DATA> <RECORD DATA>
+
+Record data refers to data where the first axis can be expanded at
+will. All record variables share a same dimension at the first axis,
+and they are stored at the end of the file per record, ie
+
+    A[0], B[0], ..., A[1], B[1], ..., etc,
+    
+so that new data can be appended to the file without changing its original
+structure. Non-record data are padded to a 4n bytes boundary. Record data
+are also padded, unless there is exactly one record variable in the file,
+in which case the padding is dropped.  All data is stored in big endian
+byte order.
+
+The Scientific.IO.NetCDF API allows attributes to be added directly to
+instances of ``netcdf_file`` and ``netcdf_variable``. To differentiate
+between user-set attributes and instance attributes, user-set attributes
+are automatically stored in the ``_attributes`` attribute by overloading
+``__setattr__``. This is the reason why the code sometimes uses
+``obj.__dict__['key'] = value``, instead of simply ``obj.key = value``;
+otherwise the key would be inserted into userspace attributes.
+
+To create a NetCDF file::
+
+    >>> import time
+    >>> f = netcdf_file('simple.nc', 'w')
+    >>> f.history = 'Created for a test'
+    >>> f.createDimension('time', 10)
+    >>> time = f.createVariable('time', 'i', ('time',))
+    >>> time[:] = range(10)
+    >>> time.units = 'days since 2008-01-01'
+    >>> f.close()
+
+To read the NetCDF file we just created::
+
+    >>> f = netcdf_file('simple.nc', 'r')
+    >>> print f.history
+    Created for a test
+    >>> time = f.variables['time']
+    >>> print time.units
+    days since 2008-01-01
+    >>> print time.shape
+    (10,)
+    >>> print time[-1]
+    9
+    >>> f.close()
+
+TODO: properly implement ``_FillValue``.
+"""
+
+__all__ = ['netcdf_file', 'netcdf_variable']
+
+
+from operator import mul
+from mmap import mmap, ACCESS_READ
+
+from numpy import fromstring, ndarray, dtype, empty, array, asarray
+from numpy import little_endian as LITTLE_ENDIAN
+
+
+ABSENT       = '\x00\x00\x00\x00\x00\x00\x00\x00' 
+ZERO         = '\x00\x00\x00\x00'
+NC_BYTE      = '\x00\x00\x00\x01'
+NC_CHAR      = '\x00\x00\x00\x02'
+NC_SHORT     = '\x00\x00\x00\x03'
+NC_INT       = '\x00\x00\x00\x04'
+NC_FLOAT     = '\x00\x00\x00\x05'
+NC_DOUBLE    = '\x00\x00\x00\x06'
+NC_DIMENSION = '\x00\x00\x00\n'
+NC_VARIABLE  = '\x00\x00\x00\x0b'
+NC_ATTRIBUTE = '\x00\x00\x00\x0c'
+
+
+TYPEMAP = { NC_BYTE:   ('b', 1),
+            NC_CHAR:   ('c', 1),
+            NC_SHORT:  ('h', 2),
+            NC_INT:    ('i', 4),
+            NC_FLOAT:  ('f', 4),
+            NC_DOUBLE: ('d', 8) }
+
+REVERSE = { 'b': NC_BYTE,
+            'c': NC_CHAR,
+            'h': NC_SHORT,
+            'i': NC_INT,
+            'f': NC_FLOAT,
+            'd': NC_DOUBLE,
+
+            # these come from asarray(1).dtype.char and asarray('foo').dtype.char,
+            # used when getting the types from generic attributes.
+            'l': NC_INT,
+            'S': NC_CHAR }
+
+
+class netcdf_file(object):
+    """
+    A ``netcdf_file`` object has two standard attributes: ``dimensions`` and
+    ``variables``. The values of both are dictionaries, mapping dimension
+    names to their associated lengths and variable names to variables,
+    respectively. Application programs should never modify these
+    dictionaries.
+
+    All other attributes correspond to global attributes defined in the
+    NetCDF file. Global file attributes are created by assigning to an
+    attribute of the ``netcdf_file`` object.
+
+    """
+    def __init__(self, filename, mode='r', mmap=True):
+        if not __debug__:
+            raise RuntimeError('Current version of pupynere does not ' +
+                               'work with -O option.  We need to update ' +
+                               'to version 1.0.7!')
+
+        self.filename = filename
+        self.use_mmap = mmap
+
+        assert mode in 'rw', "Mode must be either 'r' or 'w'."
+        self.mode = mode
+
+        self.dimensions = {}
+        self.variables = {}
+
+        self._dims = []
+        self._recs = 0
+        self._recsize = 0
+
+        self.fp = open(self.filename, '%sb' % mode)
+
+        self._attributes = {}
+
+        if mode is 'r':
+            self._read()
+
+    def __setattr__(self, attr, value):
+        # Store user defined attributes in a separate dict,
+        # so we can save them to file later.
+        try:
+            self._attributes[attr] = value
+        except AttributeError:
+            pass
+        self.__dict__[attr] = value
+
+    def close(self):
+        if not self.fp.closed:
+            try:
+                self.flush()
+            finally:
+                self.fp.close()
+    __del__ = close
+
+    def createDimension(self, name, length):
+        self.dimensions[name] = length
+        self._dims.append(name)
+
+    def createVariable(self, name, type, dimensions):
+        shape = tuple([self.dimensions[dim] for dim in dimensions]) 
+        shape_ = tuple([dim or 0 for dim in shape])  # replace None with 0 for numpy
+
+        if isinstance(type, basestring): type = dtype(type)
+        typecode, size = type.char, type.itemsize
+        dtype_ = '>%s' % typecode
+        if size > 1: dtype_ += str(size)
+
+        data = empty(shape_, dtype=dtype_)
+        self.variables[name] = netcdf_variable(data, typecode, shape, dimensions)
+        return self.variables[name]
+
+    def flush(self):
+        if self.mode is 'w':
+            self._write()
+    sync = flush
+
+    def _write(self):
+        self.fp.write('CDF')
+
+        self.__dict__['version_byte'] = 1
+        self.fp.write(array(1, '>b').tostring())
+
+        # Write headers and data.
+        self._write_numrecs()
+        self._write_dim_array()
+        self._write_gatt_array()
+        self._write_var_array()
+
+    def _write_numrecs(self):
+        # Get highest record count from all record variables.
+        for var in self.variables.values():
+            if var.isrec and len(var.data) > self._recs:
+                self.__dict__['_recs'] = len(var.data)
+        self._pack_int(self._recs)
+
+    def _write_dim_array(self):
+        if self.dimensions:
+            self.fp.write(NC_DIMENSION)
+            self._pack_int(len(self.dimensions))
+            for name in self._dims:
+                self._pack_string(name)
+                length = self.dimensions[name]
+                self._pack_int(length or 0)  # replace None with 0 for record dimension
+        else:
+            self.fp.write(ABSENT)
+
+    def _write_gatt_array(self):
+        self._write_att_array(self._attributes)
+
+    def _write_att_array(self, attributes):
+        if attributes:
+            self.fp.write(NC_ATTRIBUTE)
+            self._pack_int(len(attributes))
+            for name, values in attributes.items():
+                self._pack_string(name)
+                self._write_values(values)
+        else:
+            self.fp.write(ABSENT)
+
+    def _write_var_array(self):
+        if self.variables:
+            self.fp.write(NC_VARIABLE)
+            self._pack_int(len(self.variables))
+
+            # Sort variables non-recs first, then recs.
+            variables = self.variables.items()
+            if True: # Backwards compatible with Python versions < 2.4
+                keys = [(v._shape and not v.isrec, k) for k, v in variables]
+                keys.sort()
+                keys.reverse()
+                variables = [k for isrec, k in keys]
+            else: # Python version must be >= 2.4
+                variables.sort(key=lambda (k, v): v._shape and not v.isrec)
+                variables.reverse()
+                variables = [k for (k, v) in variables]
+
+            # Set the metadata for all variables.
+            for name in variables:
+                self._write_var_metadata(name)
+            # Now that we have the metadata, we know the vsize of
+            # each record variable, so we can calculate recsize.
+            self.__dict__['_recsize'] = sum([
+                    var._vsize for var in self.variables.values()
+                    if var.isrec])
+            # Set the data for all variables.
+            for name in variables:
+                self._write_var_data(name)
+        else:
+            self.fp.write(ABSENT)
+
+    def _write_var_metadata(self, name):
+        var = self.variables[name]
+
+        self._pack_string(name)
+        self._pack_int(len(var.dimensions))
+        for dimname in var.dimensions:
+            dimid = self._dims.index(dimname)
+            self._pack_int(dimid)
+
+        self._write_att_array(var._attributes)
+
+        nc_type = REVERSE[var.typecode()]
+        self.fp.write(nc_type)
+
+        if not var.isrec:
+            vsize = var.data.size * var.data.itemsize
+            vsize += -vsize % 4
+        else:  # record variable
+            try:
+                vsize = var.data[0].size * var.data.itemsize
+            except IndexError:
+                vsize = 0
+            rec_vars = len([var for var in self.variables.values()
+                    if var.isrec])
+            if rec_vars > 1:
+                vsize += -vsize % 4
+        self.variables[name].__dict__['_vsize'] = vsize
+        self._pack_int(vsize)
+
+        # Pack a bogus begin, and set the real value later.
+        self.variables[name].__dict__['_begin'] = self.fp.tell()
+        self._pack_begin(0)
+
+    def _write_var_data(self, name):
+        var = self.variables[name]
+        
+        # Set begin in file header.
+        the_beguine = self.fp.tell()
+        self.fp.seek(var._begin)
+        self._pack_begin(the_beguine)
+        self.fp.seek(the_beguine)
+
+        # Write data.
+        if not var.isrec:
+            self.fp.write(var.data.tostring())    
+            count = var.data.size * var.data.itemsize
+            self.fp.write('0' * (var._vsize - count))
+        else:  # record variable
+            # Handle rec vars with shape[0] < nrecs.
+            if self._recs > len(var.data):
+                shape = (self._recs,) + var.data.shape[1:]
+                var.data.resize(shape)
+
+            pos0 = pos = self.fp.tell()
+            for rec in var.data:
+                # Apparently scalars cannot be converted to big endian. If we
+                # try to convert a ``=i4`` scalar to, say, '>i4' the dtype
+                # will remain as ``=i4``.
+                if not rec.shape and (rec.dtype.byteorder == '<' or
+                        (rec.dtype.byteorder == '=' and LITTLE_ENDIAN)):
+                    rec = rec.byteswap()
+                self.fp.write(rec.tostring())
+                # Padding
+                count = rec.size * rec.itemsize
+                self.fp.write('0' * (var._vsize - count))
+                pos += self._recsize
+                self.fp.seek(pos)
+            self.fp.seek(pos0 + var._vsize)
+
+    def _write_values(self, values):
+        values = asarray(values) 
+        values = values.astype(values.dtype.newbyteorder('>'))
+
+        nc_type = REVERSE[values.dtype.char]
+        self.fp.write(nc_type)
+
+        if values.dtype.char == 'S':
+            nelems = values.itemsize
+        else:
+            nelems = values.size
+        self._pack_int(nelems)
+
+        if not values.shape and (values.dtype.byteorder == '<' or
+                (values.dtype.byteorder == '=' and LITTLE_ENDIAN)):
+            values = values.byteswap()
+        self.fp.write(values.tostring())
+        count = values.size * values.itemsize
+        self.fp.write('0' * (-count % 4))  # pad
+
+    def _read(self):
+        # Check magic bytes and version
+        assert self.fp.read(3) == 'CDF', "Error: %s is not a valid NetCDF 3 file" % self.filename
+        self.__dict__['version_byte'] = fromstring(self.fp.read(1), '>b')[0]
+
+        # Read file headers and set data.
+        self._read_numrecs()
+        self._read_dim_array()
+        self._read_gatt_array()
+        self._read_var_array()
+
+    def _read_numrecs(self):
+        self.__dict__['_recs'] = self._unpack_int()
+
+    def _read_dim_array(self):
+        assert self.fp.read(4) in [ZERO, NC_DIMENSION]
+        count = self._unpack_int()
+
+        for dim in range(count):
+            name = self._unpack_string()
+            length = self._unpack_int() or None  # None for record dimension
+            self.dimensions[name] = length
+            self._dims.append(name)  # preserve order
+
+    def _read_gatt_array(self):
+        for k, v in self._read_att_array().items():
+            self.__setattr__(k, v)
+
+    def _read_att_array(self):
+        assert self.fp.read(4) in [ZERO, NC_ATTRIBUTE]
+        count = self._unpack_int()
+
+        attributes = {}
+        for attr in range(count):
+            name = self._unpack_string()
+            attributes[name] = self._read_values()
+        return attributes
+
+    def _read_var_array(self):
+        assert self.fp.read(4) in [ZERO, NC_VARIABLE]
+
+        begin = 0
+        dtypes = {'names': [], 'formats': []}
+        rec_vars = []
+        count = self._unpack_int()
+        for var in range(count):
+            name, dimensions, shape, attributes, typecode, size, dtype_, begin_, vsize = self._read_var()
+            if shape and shape[0] is None:
+                rec_vars.append(name)
+                self.__dict__['_recsize'] += vsize
+                if begin == 0: begin = begin_
+                dtypes['names'].append(name)
+                dtypes['formats'].append(str(shape[1:]) + dtype_)
+
+                # Handle padding with a virtual variable.
+                if typecode in 'bch':
+                    actual_size = reduce(mul, (1,) + shape[1:]) * size
+                    padding = -actual_size % 4
+                    if padding:
+                        dtypes['names'].append('_padding_%d' % var)
+                        dtypes['formats'].append('(%d,)>b' % padding)
+
+                # Data will be set later.
+                data = None
+            else:
+                if self.use_mmap:
+                    mm = mmap(self.fp.fileno(), begin_+vsize, access=ACCESS_READ)
+                    data = ndarray.__new__(ndarray, shape, dtype=dtype_,
+                            buffer=mm, offset=begin_, order=0)
+                else:
+                    pos = self.fp.tell()
+                    self.fp.seek(begin_)
+                    data = fromstring(self.fp.read(vsize), dtype=dtype_)
+                    data.shape = shape
+                    self.fp.seek(pos)
+
+            # Add variable.
+            self.variables[name] = netcdf_variable(
+                    data, typecode, shape, dimensions, attributes)
+
+        if rec_vars:
+            # Remove padding when only one record variable.
+            if len(rec_vars) == 1:
+                dtypes['names'] = dtypes['names'][:1]
+                dtypes['formats'] = dtypes['formats'][:1]
+
+            # Build rec array.
+            if self.use_mmap:
+                mm = mmap(self.fp.fileno(), begin+self._recs*self._recsize, access=ACCESS_READ)
+                rec_array = ndarray.__new__(ndarray, (self._recs,), dtype=dtypes,
+                        buffer=mm, offset=begin, order=0)
+            else:
+                pos = self.fp.tell()
+                self.fp.seek(begin)
+                rec_array = fromstring(self.fp.read(self._recs*self._recsize), dtype=dtypes)
+                rec_array.shape = (self._recs,)
+                self.fp.seek(pos)
+
+            for var in rec_vars:
+                self.variables[var].__dict__['data'] = rec_array[var]
+
+    def _read_var(self):
+        name = self._unpack_string()
+        dimensions = []
+        shape = []
+        dims = self._unpack_int()
+        
+        for i in range(dims):
+            dimid = self._unpack_int()
+            dimname = self._dims[dimid]
+            dimensions.append(dimname)
+            dim = self.dimensions[dimname]
+            shape.append(dim)
+        dimensions = tuple(dimensions)
+        shape = tuple(shape)
+
+        attributes = self._read_att_array()
+        nc_type = self.fp.read(4)
+        vsize = self._unpack_int()
+        begin = [self._unpack_int, self._unpack_int64][self.version_byte-1]()
+
+        typecode, size = TYPEMAP[nc_type]
+        if typecode is 'c':
+            dtype_ = '>c'
+        else:
+            dtype_ = '>%s' % typecode
+            if size > 1: dtype_ += str(size)
+
+        return name, dimensions, shape, attributes, typecode, size, dtype_, begin, vsize
+
+    def _read_values(self):
+        nc_type = self.fp.read(4)
+        n = self._unpack_int()
+
+        typecode, size = TYPEMAP[nc_type]
+
+        count = n*size
+        values = self.fp.read(count)
+        self.fp.read(-count % 4)  # read padding
+
+        if typecode is not 'c':
+            values = fromstring(values, dtype='>%s%d' % (typecode, size))
+            if values.shape == (1,): values = values[0]
+        else:
+            values = values.rstrip('\x00') 
+        return values
+
+    def _pack_begin(self, begin):
+        if self.version_byte == 1:
+            self._pack_int(begin)
+        elif self.version_byte == 2:
+            self._pack_int64(begin)
+
+    def _pack_int(self, value):
+        self.fp.write(array(value, '>i').tostring())
+    _pack_int32 = _pack_int
+
+    def _unpack_int(self):
+        return int(fromstring(self.fp.read(4), '>i')[0])
+    _unpack_int32 = _unpack_int
+
+    def _pack_int64(self, value):
+        self.fp.write(array(value, '>q').tostring())
+
+    def _unpack_int64(self):
+        return int(fromstring(self.fp.read(8), '>q')[0])
+
+    def _pack_string(self, s):
+        count = len(s)
+        self._pack_int(count)
+        self.fp.write(s)
+        self.fp.write('0' * (-count % 4))  # pad
+
+    def _unpack_string(self):
+        count = self._unpack_int()
+        s = self.fp.read(count).rstrip('\x00')
+        self.fp.read(-count % 4)  # read padding
+        return s
+
+
+class netcdf_variable(object):
+    """
+    ``netcdf_variable`` objects are constructed by calling the method
+    ``createVariable`` on the netcdf_file object.
+
+    ``netcdf_variable`` objects behave much like array objects defined in
+    Numpy, except that their data resides in a file. Data is read by
+    indexing and written by assigning to an indexed subset; the entire
+    array can be accessed by the index ``[:]`` or using the methods
+    ``getValue`` and ``assignValue``. ``netcdf_variable`` objects also
+    have attribute ``shape`` with the same meaning as for arrays, but
+    the shape cannot be modified. There is another read-only attribute
+    ``dimensions``, whose value is the tuple of dimension names.
+
+    All other attributes correspond to variable attributes defined in
+    the NetCDF file. Variable attributes are created by assigning to an
+    attribute of the ``netcdf_variable`` object.
+
+    """
+    def __init__(self, data, typecode, shape, dimensions, attributes=None):
+        self.data = data
+        self._typecode = typecode
+        self._shape = shape
+        self.dimensions = dimensions
+
+        self._attributes = attributes or {}
+        for k, v in self._attributes.items():
+            self.__dict__[k] = v
+
+    def __setattr__(self, attr, value):
+        # Store user defined attributes in a separate dict,
+        # so we can save them to file later.
+        try:
+            self._attributes[attr] = value
+        except AttributeError:
+            pass
+        self.__dict__[attr] = value
+
+    def isrec(self):
+        return self.data.shape and not self._shape[0]
+    isrec = property(isrec)
+
+    def shape(self):
+        return self.data.shape
+    shape = property(shape)
+    
+    def getValue(self):
+        return self.data.item()
+
+    def assignValue(self, value):
+        self.data.itemset(value)
+
+    def typecode(self):
+        return self._typecode
+
+    def __getitem__(self, index):
+        return self.data[index]
+
+    def __setitem__(self, index, data):
+        # Expand data for record vars?
+        if self.isrec:
+            if isinstance(index, tuple):
+                rec_index = index[0]
+            else:
+                rec_index = index
+            if isinstance(rec_index, slice):
+                recs = (rec_index.start or 0) + len(data)
+            else:
+                recs = rec_index + 1
+            if recs > len(self.data):
+                shape = (recs,) + self._shape[1:]
+                self.data.resize(shape)
+        self.data[index] = data
+
+
+NetCDFFile = netcdf_file
+NetCDFVariable = netcdf_variable
Index: ase/io/turbomole.py
===================================================================
--- ase/io/turbomole.py	(revision 0)
+++ ase/io/turbomole.py	(revision 1)
@@ -0,0 +1,102 @@
+from ase.atoms import Atoms
+from ase.units import Bohr
+
+
+def read_turbomole(filename='coord'):
+    """Method to read turbomole coord file
+    
+    coords in bohr, atom types in lowercase, format:
+    $coord
+    x y z atomtype 
+    x y z atomtype f
+    $end
+    Above 'f' means a fixed atom.
+    """
+    from ase import Atoms, Atom
+    from ase.constraints import FixAtoms
+
+    if isinstance(filename, str):
+        f = open(filename)
+
+    lines = f.readlines()
+    atoms_pos = []
+    atom_symbols = []
+    dollar_count=0
+    myconstraints=[]
+    for line in lines:
+        if ('$' in line):
+            dollar_count = dollar_count + 1
+            if (dollar_count >= 2):
+                break
+        else:
+            x, y, z, symbolraw = line.split()[:4]
+            symbolshort=symbolraw.strip()
+            symbol=symbolshort[0].upper()+symbolshort[1:].lower()
+            #print symbol
+            atom_symbols.append(symbol)
+            atoms_pos.append([float(x)*Bohr, float(y)*Bohr, float(z)*Bohr])
+            cols = line.split()
+            if (len(cols) == 5):
+                fixedstr = line.split()[4].strip()
+                if (fixedstr == "f"):
+                    myconstraints.append(True)
+                else:
+                    myconstraints.append(False)
+            else:
+                myconstraints.append(False)
+            
+    if type(filename) == str:
+        f.close()
+
+    atoms = Atoms(positions = atoms_pos, symbols = atom_symbols, pbc = False)
+    c = FixAtoms(myconstraints)
+    atoms.set_constraint(c)
+    #print c
+    
+
+    return atoms
+
+def write_turbomole(filename, atoms):
+    """Method to write turbomole coord file
+    """
+
+    import numpy as np
+    from ase.constraints import FixAtoms
+
+    if isinstance(filename, str):
+        f = open(filename, 'w')
+    else: # Assume it's a 'file-like object'
+        f = filename
+
+    coord = atoms.get_positions()
+    symbols = atoms.get_chemical_symbols()
+    printfixed = False
+
+    if atoms.constraints:
+        for constr in atoms.constraints:
+            if isinstance(constr, FixAtoms):
+                fix_index=constr.index
+                printfixed=True
+    #print sflags
+        
+    if (printfixed):
+        fix_str=[]
+        for i in fix_index:
+            if i == 1:
+                fix_str.append("f")
+            else:
+                fix_str.append(" ")
+
+
+    f.write("$coord\n")
+    if (printfixed):
+        for (x, y, z), s, fix in zip(coord,symbols,fix_str):
+            f.write('%20.14f  %20.14f  %20.14f      %2s  %2s \n' 
+                    % (x/Bohr, y/Bohr, z/Bohr, s.lower(), fix))
+
+    else:
+        for (x, y, z), s in zip(coord,symbols):
+            f.write('%20.14f  %20.14f  %20.14f      %2s \n' 
+                    % (x/Bohr, y/Bohr, z/Bohr, s.lower()))
+    f.write("$end\n")
+    
Index: ase/io/vasp.py
===================================================================
--- ase/io/vasp.py	(revision 0)
+++ ase/io/vasp.py	(revision 1)
@@ -0,0 +1,373 @@
+"""
+This module contains functionality for reading and writing an ASE
+Atoms object in VASP POSCAR format.
+
+"""
+
+import os
+
+def get_atomtypes(fname):
+    """Given a file name, get the atomic symbols. 
+
+    The function can get this information from OUTCAR and POTCAR
+    format files.  The files can also be compressed with gzip or
+    bzip2.
+
+    """
+    atomtypes=[]
+    if fname.find('.gz') != -1:
+        import gzip
+        f = gzip.open(fname)
+    elif fname.find('.bz2') != -1:
+        import bz2
+        f = bz2.BZ2File(fname)
+    else:
+        f = open(fname)
+    for line in f:
+        if line.find('TITEL') != -1:
+            atomtypes.append(line.split()[3].split('_')[0].split('.')[0])
+    return atomtypes
+
+def atomtypes_outpot(posfname, numsyms):
+    """Try to retreive chemical symbols from OUTCAR or POTCAR
+    
+    If getting atomtypes from the first line in POSCAR/CONTCAR fails, it might
+    be possible to find the data in OUTCAR or POTCAR, if these files exist.
+
+    posfname -- The filename of the POSCAR/CONTCAR file we're trying to read
+    
+    numsyms -- The number of symbols we must find
+
+    """
+    import os.path as op
+    import glob
+
+    # First check files with exactly same name except POTCAR/OUTCAR instead
+    # of POSCAR/CONTCAR.
+    fnames = [posfname.replace('POSCAR', 'POTCAR').replace('CONTCAR', 
+                                                           'POTCAR')]
+    fnames.append(posfname.replace('POSCAR', 'OUTCAR').replace('CONTCAR',
+                                                               'OUTCAR'))
+    # Try the same but with compressed files
+    fsc = []
+    for fn in fnames:
+        fsc.append(fn + '.gz')
+        fsc.append(fn + '.bz2')
+    for f in fsc:
+        fnames.append(f)
+    # Finally try anything with POTCAR or OUTCAR in the name
+    vaspdir = op.dirname(posfname)
+    fs = glob.glob(vaspdir + '*POTCAR*')
+    for f in fs:
+        fnames.append(f)
+    fs = glob.glob(vaspdir + '*OUTCAR*')
+    for f in fs:
+        fnames.append(f)
+
+    tried = []
+    files_in_dir = os.listdir('.')
+    for fn in fnames:
+        tried.append(fn)
+        if fn in files_in_dir:
+            at = get_atomtypes(fn)
+            if len(at) == numsyms:
+                return at
+
+    raise IOError('Could not determine chemical symbols. Tried files ' 
+                  + str(tried))
+
+
+def read_vasp(filename='CONTCAR'):
+    """Import POSCAR/CONTCAR type file.
+
+    Reads unitcell, atom positions and constraints from the POSCAR/CONTCAR
+    file and tries to read atom types from POSCAR/CONTCAR header, if this fails
+    the atom types are read from OUTCAR or POTCAR file.
+    """
+ 
+    from ase import Atoms, Atom
+    from ase.constraints import FixAtoms, FixScaled
+    from ase.data import chemical_symbols
+    import numpy as np
+
+    if isinstance(filename, str):
+        f = open(filename)
+    else: # Assume it's a file-like object
+        f = filename
+
+    # First line should contain the atom symbols , eg. "Ag Ge" in
+    # the same order
+    # as later in the file (and POTCAR for the full vasp run)
+    atomtypes = f.readline().split()
+
+    lattice_constant = float(f.readline())
+
+    # Now the lattice vectors
+    a = []
+    for ii in range(3):
+        s = f.readline().split()
+        floatvect = float(s[0]), float(s[1]), float(s[2])
+        a.append(floatvect)
+
+    basis_vectors = np.array(a) * lattice_constant
+
+    # Number of atoms. Again this must be in the same order as
+    # in the first line
+    # or in the POTCAR or OUTCAR file
+    atom_symbols = []
+    numofatoms = f.readline().split()
+    #vasp5.1 has an additional line which gives the atom types
+    #the following try statement skips this line
+    try:
+        int(numofatoms[0])
+    except ValueError:
+        numofatoms = f.readline().split()
+
+    numsyms = len(numofatoms)
+    if len(atomtypes) < numsyms:
+        # First line in POSCAR/CONTCAR didn't contain enough symbols.
+        atomtypes = atomtypes_outpot(f.name, numsyms)
+    else:
+        try:
+            for atype in atomtypes[:numsyms]:
+                if not atype in chemical_symbols:
+                    raise KeyError
+        except KeyError:
+            atomtypes = atomtypes_outpot(f.name, numsyms)
+
+    for i, num in enumerate(numofatoms):
+        numofatoms[i] = int(num)
+        [atom_symbols.append(atomtypes[i]) for na in xrange(numofatoms[i])]
+
+    # Check if Selective dynamics is switched on
+    sdyn = f.readline()
+    selective_dynamics = sdyn[0].lower() == "s"
+
+    # Check if atom coordinates are cartesian or direct
+    if selective_dynamics:
+        ac_type = f.readline()
+    else:
+        ac_type = sdyn
+    cartesian = ac_type[0].lower() == "c" or ac_type[0].lower() == "k"
+    tot_natoms = sum(numofatoms)
+    atoms_pos = np.empty((tot_natoms, 3))
+    if selective_dynamics:
+        selective_flags = np.empty((tot_natoms, 3), dtype=bool)
+    for atom in xrange(tot_natoms):
+        ac = f.readline().split()
+        atoms_pos[atom] = (float(ac[0]), float(ac[1]), float(ac[2]))
+        if selective_dynamics:
+            curflag = []
+            for flag in ac[3:6]:
+                curflag.append(flag == 'F')
+            selective_flags[atom] = curflag
+    # Done with all reading
+    if type(filename) == str:
+        f.close()
+    if cartesian:
+        atoms_pos *= lattice_constant
+    atoms = Atoms(symbols = atom_symbols, cell = basis_vectors, pbc = True)
+    if cartesian:
+        atoms.set_positions(atoms_pos)
+    else:
+        atoms.set_scaled_positions(atoms_pos)
+    if selective_dynamics:
+        constraints = []
+        indices = []
+        for ind, sflags in enumerate(selective_flags):
+            if sflags.any() and not sflags.all():
+                constraints.append(FixScaled(atoms.get_cell(), ind, sflags))
+            elif sflags.all():
+                indices.append(ind)
+        if indices:
+            constraints.append(FixAtoms(indices))
+        if constraints:
+            atoms.set_constraint(constraints)
+    return atoms
+
+def read_vasp_out(filename='OUTCAR',index = -1):
+    """Import OUTCAR type file.
+
+    Reads unitcell, atom positions, energies, and forces from the OUTCAR file.
+
+    - does not (yet) read any constraints
+    """
+    import os
+    import numpy as np
+    from ase.calculators.singlepoint import SinglePointCalculator
+    from ase import Atoms, Atom
+
+    if isinstance(filename, str):
+        f = open(filename)
+    else: # Assume it's a file-like object
+        f = filename
+    data    = f.readlines()
+    natoms  = 0
+    images  = []
+    atoms   = Atoms(pbc = True)
+    energy  = 0
+    species = []
+    species_num = []
+    symbols = []
+    for n,line in enumerate(data):
+        if 'POSCAR:' in line:
+            temp = line.split()
+            species = temp[1:]
+        if 'ions per type' in line:
+            temp = line.split()
+            for ispecies in range(len(species)):
+                species_num += [int(temp[ispecies+4])]
+                natoms += species_num[-1]
+                for iatom in range(species_num[-1]): symbols += [species[ispecies]]
+        if 'direct lattice vectors' in line:
+            cell = []
+            for i in range(3):
+                temp = data[n+1+i].split()
+                cell += [[float(temp[0]), float(temp[1]), float(temp[2])]]
+            atoms.set_cell(cell)
+        if 'FREE ENERGIE OF THE ION-ELECTRON SYSTEM' in line:
+            energy = float(data[n+2].split()[4])
+        if 'POSITION          ' in line:
+            forces = []
+            for iatom in range(natoms):
+                temp    = data[n+2+iatom].split()
+                atoms  += Atom(symbols[iatom],[float(temp[0]),float(temp[1]),float(temp[2])])
+                forces += [[float(temp[3]),float(temp[4]),float(temp[5])]]
+                atoms.set_calculator(SinglePointCalculator(energy,forces,None,None,atoms))
+            images += [atoms]
+            atoms = Atoms(pbc = True)
+
+    # return requested images, code borrowed from ase/io/trajectory.py
+    if isinstance(index, int):
+        return images[index]
+    else:
+        step = index.step or 1
+        if step > 0:
+            start = index.start or 0
+            if start < 0:
+                start += len(images)
+            stop = index.stop or len(images)
+            if stop < 0:
+                stop += len(images)
+        else:
+            if index.start is None:
+                start = len(images) - 1
+            else:
+                start = index.start
+                if start < 0:
+                    start += len(images)
+            if index.stop is None:
+                stop = -1
+            else:
+                stop = index.stop
+                if stop < 0:
+                    stop += len(images)
+        return [images[i] for i in range(start, stop, step)]
+
+def write_vasp(filename, atoms, label='', direct=False, sort=None, symbol_count = None ):
+    """Method to write VASP position (POSCAR/CONTCAR) files.
+
+    Writes label, scalefactor, unitcell, # of various kinds of atoms,
+    positions in cartesian or scaled coordinates (Direct), and constraints
+    to file. Cartesian coordiantes is default and default label is the 
+    atomic species, e.g. 'C N H Cu'.
+    """
+    
+    import numpy as np
+    from ase.constraints import FixAtoms, FixScaled
+
+    if isinstance(filename, str):
+        f = open(filename, 'w')
+    else: # Assume it's a 'file-like object'
+        f = filename
+    
+    if isinstance(atoms, (list, tuple)):
+        if len(atoms) > 1:
+            raise RuntimeError("Don't know how to save more than "+
+                               "one image to VASP input")
+        else:
+            atoms = atoms[0]
+
+    # Write atom positions in scaled or cartesian coordinates
+    if direct:
+        coord = atoms.get_scaled_positions()
+    else:
+        coord = atoms.get_positions()
+
+    if atoms.constraints:
+        sflags = np.zeros((len(atoms), 3), dtype=bool)
+        for constr in atoms.constraints:
+            if isinstance(constr, FixScaled):
+                sflags[constr.a] = constr.mask
+            elif isinstance(constr, FixAtoms):
+                sflags[constr.index] = [True, True, True]
+
+    if sort:
+        ind = np.argsort(atoms.get_chemical_symbols())
+        symbols = np.array(atoms.get_chemical_symbols())[ind]
+        coord = coord[ind]
+        if atoms.constraints:
+            sflags = sflags[ind]
+    else:
+        symbols = atoms.get_chemical_symbols()
+
+    # Create a list sc of (symbol, count) pairs
+    if symbol_count:
+        sc = symbol_count
+    else:
+        sc = []
+        psym = symbols[0]
+        count = 0
+        for sym in symbols:
+            if sym != psym:
+                sc.append((psym, count))
+                psym = sym
+                count = 1
+            else:
+                count += 1
+        sc.append((psym, count))
+
+    # Create the label
+    if label == '':
+        for sym, c in sc:
+            label += '%2s ' % sym
+    f.write(label + '\n')
+
+    # Write unitcell in real coordinates and adapt to VASP convention 
+    # for unit cell
+    # ase Atoms doesn't store the lattice constant separately, so always
+    # write 1.0.
+    f.write('%19.16f\n' % 1.0)
+    for vec in atoms.get_cell():
+        f.write(' ')
+        for el in vec:
+            f.write(' %21.16f' % el)
+        f.write('\n')
+
+    # Numbers of each atom
+    for sym, count in sc:
+        f.write(' %3i' % count)
+    f.write('\n')
+
+    if atoms.constraints:
+        f.write('Selective dynamics\n')
+
+    if direct:
+        f.write('Direct\n')
+    else:
+        f.write('Cartesian\n')
+
+    for iatom, atom in enumerate(coord):
+        for dcoord in atom:
+            f.write(' %19.16f' % dcoord)
+        if atoms.constraints:
+            for flag in sflags[iatom]:
+                if flag:
+                    s = 'F'
+                else:
+                    s = 'T'
+                f.write('%4s' % s)
+        f.write('\n')
+
+    if type(filename) == str:
+        f.close()
Index: ase/io/exciting.py
===================================================================
--- ase/io/exciting.py	(revision 0)
+++ ase/io/exciting.py	(revision 1)
@@ -0,0 +1,126 @@
+"""
+This is the Implementation of the exciting I/O Functions
+The functions are called with read write usunf the format "exi"
+
+The module depends on lxml  http://codespeak.net/lxml/
+"""
+from math import pi, cos, sin, sqrt, acos
+
+from ase.atoms import Atoms
+from ase.parallel import paropen
+from ase.units import Bohr
+
+def read_exciting(fileobj, index=-1):
+    """Reads structure from exiting xml file.
+    
+    Parameters
+    ----------
+    fileobj: file object
+        Filehandle from which data should be read.
+        
+    Other parameters
+    ----------------
+    index: integer -1
+        Not used in this implementation.
+    """
+    from lxml import etree as ET
+    #parse file into element tree
+    doc = ET.parse(fileobj)
+    root = doc.getroot()
+    speciesnodes = root.find('structure').getiterator('species') 
+ 
+    symbols = []
+    positions = []
+    basevects = []
+    atoms = None
+    #collect data from tree
+    for speciesnode in speciesnodes:
+        symbol = speciesnode.get('speciesfile').split('.')[0]
+        natoms = speciesnode.getiterator('atom')
+        for atom in natoms:
+            x, y, z = atom.get('coord').split()
+            positions.append([float(x), float(y), float(z)])
+            symbols.append(symbol)
+    basevectsn = doc.xpath('//basevect/text()') 
+ 
+    for basevect in basevectsn:
+        x, y, z = basevect.split()
+        basevects.append([float(x) * Bohr, float(y) * Bohr, float(z) * Bohr])
+    atoms = Atoms(symbols=symbols, cell=basevects)
+    atoms.set_scaled_positions(positions)
+    if 'molecule' in root.find('structure').attrib.keys():
+        if root.find('structure').attrib['molecule']:
+            atoms.set_pbc(False)
+    else:
+        atoms.set_pbc(True)
+        
+    return atoms
+
+def write_exciting(fileobj, images):
+    """writes exciting input structure in XML
+    
+    Parameters
+    ----------
+    fileobj : File object
+        Filehandle to which data should be written
+    images : Atom Object or List of Atoms objects
+        This function will write the first Atoms object to file 
+    
+    Returns
+    -------
+    """
+    from lxml import etree as ET
+    if isinstance(fileobj, str):
+        fileobj = paropen(fileobj, 'w')
+    root = atoms2etree(images)
+    fileobj.write(ET.tostring(root, method='xml', 
+                              pretty_print=True,
+                              xml_declaration=True,
+                              encoding='UTF-8'))
+
+def atoms2etree(images):
+    """This function creates the xml DOM corresponding
+     to the structure for use in write and calculator
+    
+    Parameters
+    ----------
+    
+    images : Atom Object or List of Atoms objects
+        This function will create a 
+    
+    Returns
+    -------
+    root : etree object
+        Element tree of exciting input file containing the structure
+    """
+    from lxml import etree as ET
+    if not isinstance(images, (list, tuple)):
+        images = [images]
+
+    root = ET.Element('input')
+    title = ET.SubElement(root, 'title')
+    title.text = ''
+    structure = ET.SubElement(root, 'structure')
+    crystal= ET.SubElement(structure, 'crystal')
+    atoms = images[0]
+    for vec in atoms.cell:
+        basevect = ET.SubElement(crystal, 'basevect')
+        basevect.text = '%.14f %.14f %.14f' % tuple(vec / Bohr)
+                             
+    species = {}
+    symbols = []
+    for a, symbol in enumerate(atoms.get_chemical_symbols()):
+        if symbol in species:
+            species[symbol].append(a)
+        else:
+            species[symbol] = [a]
+            symbols.append(symbol)
+    scaled = atoms.get_scaled_positions()
+    for symbol in symbols:
+        speciesnode = ET.SubElement(structure, 'species',
+                                    speciesfile='%s.xml' % symbol,
+                                    chemicalSymbol=symbol)
+        for a in species[symbol]:
+           atom = ET.SubElement(speciesnode, 'atom',
+                                coord='%.14f %.14f %.14f' % tuple(scaled[a]))
+    return root
Index: ase/io/sdf.py
===================================================================
--- ase/io/sdf.py	(revision 0)
+++ ase/io/sdf.py	(revision 1)
@@ -0,0 +1,23 @@
+from math import pi, cos, sin, sqrt, acos
+
+from ase.atoms import Atoms
+from ase.parallel import paropen
+
+def read_sdf(fileobj):
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    lines = fileobj.readlines()
+    # first three lines header
+    del lines[:3]
+
+    # 
+    L1 = lines.pop(0).split()
+    natoms = int(L1[0])
+    positions = []
+    symbols = []
+    for line in lines[:natoms]:
+        x, y, z, symbol = line.split()[:4]
+        symbols.append(symbol)
+        positions.append([float(x), float(y), float(z)])
+    return Atoms(symbols=symbols, positions=positions)
Index: ase/io/bader.py
===================================================================
--- ase/io/bader.py	(revision 0)
+++ ase/io/bader.py	(revision 1)
@@ -0,0 +1,46 @@
+import numpy as np
+from ase.units import Bohr
+
+def attach_charges(atoms, fileobj='ACF.dat', displacement=1e-4):
+    """Attach the charges from the fileobj to the Atoms."""
+    if isinstance(fileobj, str):
+        fileobj = open(fileobj)
+
+    sep = '---------------'
+    i = 0 # Counter for the lines
+    k = 0 # Counter of sep
+    assume6columns = False
+    for line in fileobj:
+        if line[0] == '\n': # check if there is an empty line in the 
+            i -= 1          # head of ACF.dat file
+        if i == 0:
+            headings = line
+            if 'BADER' in headings.split():
+                j = headings.split().index('BADER')
+            elif 'CHARGE' in headings.split():
+                j = headings.split().index('CHARGE')
+            else:
+                print 'Can\'t find keyword "BADER" or "CHARGE".' \
+                +' Assuming the ACF.dat file has 6 columns.'
+                j = 4
+                assume6columns = True
+        if sep in line: # Stop at last seperator line
+            if k == 1:
+                break
+            k += 1
+        if not i > 1:
+            pass
+        else:
+            words = line.split()
+            if assume6columns is True:
+                if len(words) != 6:
+                    raise IOError('Number of columns in ACF file incorrect!\n'
+                                  'Check that Bader program version >= 0.25')
+                
+            atom = atoms[int(words[0]) - 1]
+            atom.charge = atom.number - float(words[j])
+            if displacement is not None: # check if the atom positions match
+                xyz = np.array([float(w) for w in words[1:4]]) * Bohr
+                assert np.linalg.norm(atom.position - xyz) < displacement
+        i += 1
+
Index: ase/data/__init__.py
===================================================================
--- ase/data/__init__.py	(revision 0)
+++ ase/data/__init__.py	(revision 1)
@@ -0,0 +1,373 @@
+import numpy as np
+
+
+chemical_symbols = ['X',  'H',  'He', 'Li', 'Be',
+                    'B',  'C',  'N',  'O',  'F',
+                    'Ne', 'Na', 'Mg', 'Al', 'Si',
+                    'P',  'S',  'Cl', 'Ar', 'K',
+                    'Ca', 'Sc', 'Ti', 'V',  'Cr',
+                    'Mn', 'Fe', 'Co', 'Ni', 'Cu',
+                    'Zn', 'Ga', 'Ge', 'As', 'Se',
+                    'Br', 'Kr', 'Rb', 'Sr', 'Y',
+                    'Zr', 'Nb', 'Mo', 'Tc', 'Ru',
+                    'Rh', 'Pd', 'Ag', 'Cd', 'In',
+                    'Sn', 'Sb', 'Te', 'I',  'Xe',
+                    'Cs', 'Ba', 'La', 'Ce', 'Pr',
+                    'Nd', 'Pm', 'Sm', 'Eu', 'Gd',
+                    'Tb', 'Dy', 'Ho', 'Er', 'Tm',
+                    'Yb', 'Lu', 'Hf', 'Ta', 'W',
+                    'Re', 'Os', 'Ir', 'Pt', 'Au',
+                    'Hg', 'Tl', 'Pb', 'Bi', 'Po',
+                    'At', 'Rn', 'Fr', 'Ra', 'Ac',
+                    'Th', 'Pa', 'U',  'Np', 'Pu',
+                    'Am', 'Cm', 'Bk', 'Cf', 'Es',
+                    'Fm', 'Md', 'No', 'Lw']
+
+atomic_numbers = {}
+for Z, symbol in enumerate(chemical_symbols):
+    atomic_numbers[symbol] = Z
+
+atomic_names = [
+    '', 'Hydrogen', 'Helium', 'Lithium', 'Beryllium', 'Boron',
+    'Carbon', 'Nitrogen', 'Oxygen', 'Fluorine', 'Neon', 'Sodium',
+    'Magnesium', 'Aluminium', 'Silicon', 'Phosphorus', 'Sulfur',
+    'Chlorine', 'Argon', 'Potassium', 'Calcium', 'Scandium',
+    'Titanium', 'Vanadium', 'Chromium', 'Manganese', 'Iron',
+    'Cobalt', 'Nickel', 'Copper', 'Zinc', 'Gallium', 'Germanium',
+    'Arsenic', 'Selenium', 'Bromine', 'Krypton', 'Rubidium',
+    'Strontium', 'Yttrium', 'Zirconium', 'Niobium', 'Molybdenum',
+    'Technetium', 'Ruthenium', 'Rhodium', 'Palladium', 'Silver',
+    'Cadmium', 'Indium', 'Tin', 'Antimony', 'Tellurium',
+    'Iodine', 'Xenon', 'Caesium', 'Barium', 'Lanthanum',
+    'Cerium', 'Praseodymium', 'Neodymium', 'Promethium',
+    'Samarium', 'Europium', 'Gadolinium', 'Terbium',
+    'Dysprosium', 'Holmium', 'Erbium', 'Thulium', 'Ytterbium',
+    'Lutetium', 'Hafnium', 'Tantalum', 'Tungsten', 'Rhenium',
+    'Osmium', 'Iridium', 'Platinum', 'Gold', 'Mercury',
+    'Thallium', 'Lead', 'Bismuth', 'Polonium', 'Astatine',
+    'Radon', 'Francium', 'Radium', 'Actinium', 'Thorium',
+    'Protactinium', 'Uranium', 'Neptunium', 'Plutonium',
+    'Americium', 'Curium', 'Berkelium', 'Californium',
+    'Einsteinium', 'Fermium', 'Mendelevium', 'Nobelium',
+    'Lawrencium', 'Unnilquadium', 'Unnilpentium', 'Unnilhexium']
+
+atomic_masses = np.array([
+   0.00000, # X
+   1.00794, # H
+   4.00260, # He
+   6.94100, # Li
+   9.01218, # Be
+  10.81100, # B
+  12.01100, # C
+  14.00670, # N
+  15.99940, # O
+  18.99840, # F
+  20.17970, # Ne
+  22.98977, # Na
+  24.30500, # Mg
+  26.98154, # Al
+  28.08550, # Si
+  30.97376, # P
+  32.06600, # S
+  35.45270, # Cl
+  39.94800, # Ar
+  39.09830, # K
+  40.07800, # Ca
+  44.95590, # Sc
+  47.88000, # Ti
+  50.94150, # V
+  51.99600, # Cr
+  54.93800, # Mn
+  55.84700, # Fe
+  58.93320, # Co
+  58.69340, # Ni
+  63.54600, # Cu
+  65.39000, # Zn
+  69.72300, # Ga
+  72.61000, # Ge
+  74.92160, # As
+  78.96000, # Se
+  79.90400, # Br
+  83.80000, # Kr
+  85.46780, # Rb
+  87.62000, # Sr
+  88.90590, # Y
+  91.22400, # Zr
+  92.90640, # Nb
+  95.94000, # Mo
+    np.nan, # Tc
+ 101.07000, # Ru
+ 102.90550, # Rh
+ 106.42000, # Pd
+ 107.86800, # Ag
+ 112.41000, # Cd
+ 114.82000, # In
+ 118.71000, # Sn
+ 121.75700, # Sb
+ 127.60000, # Te
+ 126.90450, # I
+ 131.29000, # Xe
+ 132.90540, # Cs
+ 137.33000, # Ba
+ 138.90550, # La
+ 140.12000, # Ce
+ 140.90770, # Pr
+ 144.24000, # Nd
+    np.nan, # Pm
+ 150.36000, # Sm
+ 151.96500, # Eu
+ 157.25000, # Gd
+ 158.92530, # Tb
+ 162.50000, # Dy
+ 164.93030, # Ho
+ 167.26000, # Er
+ 168.93420, # Tm
+ 173.04000, # Yb
+ 174.96700, # Lu
+ 178.49000, # Hf
+ 180.94790, # Ta
+ 183.85000, # W
+ 186.20700, # Re
+ 190.20000, # Os
+ 192.22000, # Ir
+ 195.08000, # Pt
+ 196.96650, # Au
+ 200.59000, # Hg
+ 204.38300, # Tl
+ 207.20000, # Pb
+ 208.98040, # Bi
+    np.nan, # Po
+    np.nan, # At
+    np.nan, # Rn
+    np.nan, # Fr
+ 226.02540, # Ra
+    np.nan, # Ac
+ 232.03810, # Th
+ 231.03590, # Pa
+ 238.02900, # U
+ 237.04820, # Np
+    np.nan, # Pu
+    np.nan, # Am
+    np.nan, # Cm
+    np.nan, # Bk
+    np.nan, # Cf
+    np.nan, # Es
+    np.nan, # Fm
+    np.nan, # Md
+    np.nan, # No
+    np.nan])# Lw
+
+covalent_radius_default = 0.20 # for anything we don't know better
+
+covalent_radii = np.array([
+ covalent_radius_default, # X
+ 0.32, # H
+ 0.93, # He
+ 1.23, # Li
+ 0.90, # Be
+ 0.82, # B
+ 0.77, # C
+ 0.75, # N
+ 0.73, # O
+ 0.72, # F
+ 0.71, # Ne
+ 1.54, # Na
+ 1.36, # Mg
+ 1.18, # Al
+ 1.11, # Si
+ 1.06, # P
+ 1.02, # S
+ 0.99, # Cl
+ 0.98, # Ar
+ 2.03, # K
+ 1.74, # Ca
+ 1.44, # Sc
+ 1.32, # Ti
+ 1.22, # V
+ 1.18, # Cr
+ 1.17, # Mn
+ 1.17, # Fe
+ 1.16, # Co
+ 1.15, # Ni
+ 1.17, # Cu
+ 1.25, # Zn
+ 1.26, # Ga
+ 1.22, # Ge
+ 1.20, # As
+ 1.16, # Se
+ 1.14, # Br
+ 1.89, # Kr
+ 2.16, # Rb
+ 1.91, # Sr
+ 1.62, # Y
+ 1.45, # Zr
+ 1.34, # Nb
+ 1.30, # Mo
+ 1.27, # Tc
+ 1.25, # Ru
+ 1.25, # Rh
+ 1.28, # Pd
+ 1.34, # Ag
+ 1.41, # Cd
+ 1.44, # In
+ 1.41, # Sn
+ 1.40, # Sb
+ 1.36, # Te
+ 1.33, # I
+ 1.31, # Xe
+ 2.35, # Cs
+ 1.98, # Ba
+ 1.25, # La
+ 1.65, # Ce
+ 1.65, # Pr
+ 1.64, # Nd
+ 1.63, # Pm
+ 1.62, # Sm
+ 1.85, # Eu
+ 1.61, # Gd
+ 1.59, # Tb
+ 1.59, # Dy
+ 1.58, # Ho
+ 1.57, # Er
+ 1.56, # Tm
+ 1.70, # Yb
+ 1.56, # Lu
+ 1.44, # Hf
+ 1.34, # Ta
+ 1.30, # W
+ 1.28, # Re
+ 1.26, # Os
+ 1.27, # Ir
+ 1.30, # Pt
+ 1.34, # Au
+ 1.49, # Hg
+ 1.48, # Tl
+ 1.47, # Pb
+ 1.46, # Bi
+ 1.53, # Po
+ 1.47, # At
+ covalent_radius_default, # Rn
+ covalent_radius_default, # Fr
+ covalent_radius_default, # Ra
+ covalent_radius_default, # Ac
+ 1.65, # Th
+ covalent_radius_default, # Pa
+ 1.42, # U
+ covalent_radius_default, # Np
+ covalent_radius_default, # Pu
+ covalent_radius_default, # Am
+ covalent_radius_default, # Cm
+ covalent_radius_default, # Bk
+ covalent_radius_default, # Cf
+ covalent_radius_default, # Es
+ covalent_radius_default, # Fm
+ covalent_radius_default, # Md
+ covalent_radius_default, # No
+ covalent_radius_default]) # Lw
+
+# This data is from Ashcroft and Mermin.
+reference_states = [\
+    None, #X
+    {'symmetry': 'diatom', 'd': 0.74}, #H
+    {'symmetry': 'atom'}, #He
+    {'symmetry': 'BCC', 'a': 3.49}, #Li
+    {'symmetry': 'hcp', 'c/a': 1.567, 'a': 2.29}, #Be
+    {'symmetry': 'Tetragonal', 'c/a': 0.576, 'a': 8.73}, #B
+    {'symmetry': 'Diamond', 'a': 3.57},#C
+    {'symmetry': 'diatom', 'd': 1.10},#N
+    {'symmetry': 'diatom', 'd': 1.21},#O
+    {'symmetry': 'diatom', 'd': 1.42},#F
+    {'symmetry': 'fcc', 'a': 4.43},#Ne
+    {'symmetry': 'BCC', 'a': 4.23},#Na
+    {'symmetry': 'hcp', 'c/a': 1.624, 'a': 3.21},#Mg
+    {'symmetry': 'fcc', 'a': 4.05},#Al
+    {'symmetry': 'Diamond', 'a': 5.43},#Si
+    {'symmetry': 'Cubic', 'a': 7.17},#P
+    {'symmetry': 'Orthorhombic', 'c/a': 2.339, 'a': 10.47,'b/a': 1.229},#S
+    {'symmetry': 'Orthorhombic', 'c/a': 1.324, 'a': 6.24, 'b/a': 0.718},#Cl
+    {'symmetry': 'fcc', 'a': 5.26},#Ar
+    {'symmetry': 'BCC', 'a': 5.23},#K
+    {'symmetry': 'fcc', 'a': 5.58},#Ca
+    {'symmetry': 'hcp', 'c/a': 1.594, 'a': 3.31},#Sc
+    {'symmetry': 'hcp', 'c/a': 1.588, 'a': 2.95},#Ti
+    {'symmetry': 'BCC', 'a': 3.02},#V
+    {'symmetry': 'BCC', 'a': 2.88},#Cr
+    {'symmetry': 'Cubic', 'a': 8.89},#Mn
+    {'symmetry': 'BCC', 'a': 2.87},#Fe
+    {'symmetry': 'hcp', 'c/a': 1.622, 'a': 2.51},#Co
+    {'symmetry': 'fcc', 'a': 3.52},#Ni
+    {'symmetry': 'fcc', 'a': 3.61},#Cu
+    {'symmetry': 'hcp', 'c/a': 1.856, 'a': 2.66},#Zn
+    {'symmetry': 'Orthorhombic', 'c/a': 1.695, 'a': 4.51, 'b/a': 1.001},#Ga
+    {'symmetry': 'Diamond', 'a': 5.66},#Ge
+    {'symmetry': 'Rhombohedral', 'a': 4.13, 'alpha': 54.10},#As
+    {'symmetry': 'hcp', 'c/a': 1.136, 'a': 4.36},#Se
+    {'symmetry': 'Orthorhombic', 'c/a': 1.307, 'a': 6.67, 'b/a': 0.672},#Br
+    {'symmetry': 'fcc', 'a': 5.72},#Kr
+    {'symmetry': 'BCC', 'a': 5.59},#Rb
+    {'symmetry': 'fcc', 'a': 6.08},#Sr
+    {'symmetry': 'hcp', 'c/a': 1.571, 'a': 3.65},#Y
+    {'symmetry': 'hcp', 'c/a': 1.593, 'a': 3.23},#Zr
+    {'symmetry': 'BCC', 'a': 3.30},#Nb
+    {'symmetry': 'BCC', 'a': 3.15},#Mo
+    {'symmetry': 'hcp', 'c/a': 1.604, 'a': 2.74},#Tc
+    {'symmetry': 'hcp', 'c/a': 1.584, 'a': 2.70},#Ru
+    {'symmetry': 'fcc', 'a': 3.80},#Rh
+    {'symmetry': 'fcc', 'a': 3.89},#Pd
+    {'symmetry': 'fcc', 'a': 4.09},#Ag
+    {'symmetry': 'hcp', 'c/a': 1.886, 'a': 2.98},#Cd
+    {'symmetry': 'Tetragonal', 'c/a': 1.076, 'a': 4.59},#In
+    {'symmetry': 'Tetragonal', 'c/a': 0.546, 'a': 5.82},#Sn
+    {'symmetry': 'Rhombohedral', 'a': 4.51, 'alpha': 57.60},#Sb
+    {'symmetry': 'hcp', 'c/a': 1.330, 'a': 4.45},#Te
+    {'symmetry': 'Orthorhombic', 'c/a': 1.347, 'a': 7.27, 'b/a': 0.659},#I
+    {'symmetry': 'fcc', 'a': 6.20},#Xe
+    {'symmetry': 'BCC', 'a': 6.05},#Cs
+    {'symmetry': 'BCC', 'a': 5.02},#Ba
+    {'symmetry': 'hcp', 'c/a': 1.619, 'a': 3.75},#La
+    {'symmetry': 'fcc', 'a': 5.16},#Ce
+    {'symmetry': 'hcp', 'c/a': 1.614, 'a': 3.67},#Pr
+    {'symmetry': 'hcp', 'c/a': 1.614, 'a': 3.66},#Nd
+    None,#Pm
+    {'symmetry': 'Rhombohedral', 'a': 9.00, 'alpha': 23.13},#Sm
+    {'symmetry': 'BCC', 'a': 4.61},#Eu
+    {'symmetry': 'hcp', 'c/a': 1.588, 'a': 3.64},#Gd
+    {'symmetry': 'hcp', 'c/a': 1.581, 'a': 3.60},#Th
+    {'symmetry': 'hcp', 'c/a': 1.573, 'a': 3.59},#Dy
+    {'symmetry': 'hcp', 'c/a': 1.570, 'a': 3.58},#Ho
+    {'symmetry': 'hcp', 'c/a': 1.570, 'a': 3.56},#Er
+    {'symmetry': 'hcp', 'c/a': 1.570, 'a': 3.54},#Tm
+    {'symmetry': 'fcc', 'a': 5.49},#Yb
+    {'symmetry': 'hcp', 'c/a': 1.585, 'a': 3.51},#Lu
+    {'symmetry': 'hcp', 'c/a': 1.582, 'a': 3.20},#Hf
+    {'symmetry': 'BCC', 'a': 3.31},#Ta
+    {'symmetry': 'BCC', 'a': 3.16},#W
+    {'symmetry': 'hcp', 'c/a': 1.615, 'a': 2.76},#Re
+    {'symmetry': 'hcp', 'c/a': 1.579, 'a': 2.74},#Os
+    {'symmetry': 'fcc', 'a': 3.84},#Ir
+    {'symmetry': 'fcc', 'a': 3.92},#Pt
+    {'symmetry': 'fcc', 'a': 4.08},#Au
+    {'symmetry': 'Rhombohedral', 'a': 2.99, 'alpha': 70.45},#Hg
+    {'symmetry': 'hcp', 'c/a': 1.599, 'a': 3.46},#Tl
+    {'symmetry': 'fcc', 'a': 4.95},#Pb
+    {'symmetry': 'Rhombohedral', 'a': 4.75, 'alpha': 57.14},#Bi
+    {'symmetry': 'SC', 'a': 3.35},#Po
+    None,#At
+    None,#Rn
+    None,#Fr
+    None,#Ra
+    {'symmetry': 'fcc', 'a': 5.31},#Ac
+    {'symmetry': 'fcc', 'a': 5.08},#Th
+    {'symmetry': 'Tetragonal', 'c/a': 0.825, 'a': 3.92},#Pa
+    {'symmetry': 'Orthorhombic', 'c/a': 2.056, 'a': 2.85, 'b/a': 1.736},#U
+    {'symmetry': 'Orthorhombic', 'c/a': 1.411, 'a': 4.72, 'b/a': 1.035},#Np
+    {'symmetry': 'Monoclinic'},#Pu
+    None,#Am
+    None,#Cm
+    None,#Bk
+    None,#Cf
+    None,#Es
+    None,#Fm
+    None,#Md
+    None,#No
+    None]#Lw
Index: ase/data/colors.py
===================================================================
--- ase/data/colors.py	(revision 0)
+++ ase/data/colors.py	(revision 1)
@@ -0,0 +1,224 @@
+import numpy as np
+
+
+# Jmol colors.  See: http://jmol.sourceforge.net/jscolors/#color_U
+jmol_colors = np.array([
+(1.000,0.000,0.000) ,# None
+(1.000,1.000,1.000), # H
+(0.851,1.000,1.000), # He
+(0.800,0.502,1.000), # Li
+(0.761,1.000,0.000), # Be
+(1.000,0.710,0.710), # B
+(0.565,0.565,0.565), # C
+(0.188,0.314,0.973), # N
+(1.000,0.051,0.051), # O
+(0.565,0.878,0.314), # F
+(0.702,0.890,0.961), # Ne
+(0.671,0.361,0.949), # Na
+(0.541,1.000,0.000), # Mg
+(0.749,0.651,0.651), # Al
+(0.941,0.784,0.627), # Si
+(1.000,0.502,0.000), # P
+(1.000,1.000,0.188), # S
+(0.122,0.941,0.122), # Cl
+(0.502,0.820,0.890), # Ar
+(0.561,0.251,0.831), # K
+(0.239,1.000,0.000), # Ca
+(0.902,0.902,0.902), # Sc
+(0.749,0.761,0.780), # Ti
+(0.651,0.651,0.671), # V
+(0.541,0.600,0.780), # Cr
+(0.612,0.478,0.780), # Mn
+(0.878,0.400,0.200), # Fe
+(0.941,0.565,0.627), # Co
+(0.314,0.816,0.314), # Ni
+(0.784,0.502,0.200), # Cu
+(0.490,0.502,0.690), # Zn
+(0.761,0.561,0.561), # Ga
+(0.400,0.561,0.561), # Ge
+(0.741,0.502,0.890), # As
+(1.000,0.631,0.000), # Se
+(0.651,0.161,0.161), # Br
+(0.361,0.722,0.820), # Kr
+(0.439,0.180,0.690), # Rb
+(0.000,1.000,0.000), # Sr
+(0.580,1.000,1.000), # Y
+(0.580,0.878,0.878), # Zr
+(0.451,0.761,0.788), # Nb
+(0.329,0.710,0.710), # Mo
+(0.231,0.620,0.620), # Tc
+(0.141,0.561,0.561), # Ru
+(0.039,0.490,0.549), # Rh
+(0.000,0.412,0.522), # Pd
+(0.753,0.753,0.753), # Ag
+(1.000,0.851,0.561), # Cd
+(0.651,0.459,0.451), # In
+(0.400,0.502,0.502), # Sn
+(0.620,0.388,0.710), # Sb
+(0.831,0.478,0.000), # Te
+(0.580,0.000,0.580), # I
+(0.259,0.620,0.690), # Xe
+(0.341,0.090,0.561), # Cs
+(0.000,0.788,0.000), # Ba
+(0.439,0.831,1.000), # La
+(1.000,1.000,0.780), # Ce
+(0.851,1.000,0.780), # Pr
+(0.780,1.000,0.780), # Nd
+(0.639,1.000,0.780), # Pm
+(0.561,1.000,0.780), # Sm
+(0.380,1.000,0.780), # Eu
+(0.271,1.000,0.780), # Gd
+(0.188,1.000,0.780), # Tb
+(0.122,1.000,0.780), # Dy
+(0.000,1.000,0.612), # Ho
+(0.000,0.902,0.459), # Er
+(0.000,0.831,0.322), # Tm
+(0.000,0.749,0.220), # Yb
+(0.000,0.671,0.141), # Lu
+(0.302,0.761,1.000), # Hf
+(0.302,0.651,1.000), # Ta
+(0.129,0.580,0.839), # W
+(0.149,0.490,0.671), # Re
+(0.149,0.400,0.588), # Os
+(0.090,0.329,0.529), # Ir
+(0.816,0.816,0.878), # Pt
+(1.000,0.820,0.137), # Au
+(0.722,0.722,0.816), # Hg
+(0.651,0.329,0.302), # Tl
+(0.341,0.349,0.380), # Pb
+(0.620,0.310,0.710), # Bi
+(0.671,0.361,0.000), # Po
+(0.459,0.310,0.271), # At
+(0.259,0.510,0.588), # Rn
+(0.259,0.000,0.400), # Fr
+(0.000,0.490,0.000), # Ra
+(0.439,0.671,0.980), # Ac
+(0.000,0.729,1.000), # Th
+(0.000,0.631,1.000), # Pa
+(0.000,0.561,1.000), # U
+(0.000,0.502,1.000), # Np
+(0.000,0.420,1.000), # Pu
+(0.329,0.361,0.949), # Am
+(0.471,0.361,0.890), # Cm
+(0.541,0.310,0.890), # Bk
+(0.631,0.212,0.831), # Cf
+(0.702,0.122,0.831), # Es
+(0.702,0.122,0.729), # Fm
+(0.702,0.051,0.651), # Md
+(0.741,0.051,0.529), # No
+(0.780,0.000,0.400), # Lr
+(0.800,0.000,0.349), # Rf
+(0.820,0.000,0.310), # Db
+(0.851,0.000,0.271), # Sg
+(0.878,0.000,0.220), # Bh
+(0.902,0.000,0.180), # Hs
+(0.922,0.000,0.149), # Mt
+])
+
+# CPK colors in units of RGB values:
+cpk_colors = np.array([ 
+(1.000,0.000,0.000) ,# None
+(1.000,1.000,1.000) ,# H
+(1.000,0.753,0.796) ,# He
+(0.698,0.133,0.133) ,# Li
+(1.000,0.078,0.576) ,# Be
+(0.000,1.000,0.000) ,# B
+(0.784,0.784,0.784) ,# C
+(0.561,0.561,1.000) ,# N
+(0.941,0.000,0.000) ,# O
+(0.855,0.647,0.125) ,# F
+(1.000,0.078,0.576) ,# Ne
+(0.000,0.000,1.000) ,# Na
+(0.133,0.545,0.133) ,# Mg
+(0.502,0.502,0.565) ,# Al
+(0.855,0.647,0.125) ,# Si
+(1.000,0.647,0.000) ,# P
+(1.000,0.784,0.196) ,# S
+(0.000,1.000,0.000) ,# Cl
+(1.000,0.078,0.576) ,# Ar
+(1.000,0.078,0.576) ,# K
+(0.502,0.502,0.565) ,# Ca
+(1.000,0.078,0.576) ,# Sc
+(0.502,0.502,0.565) ,# Ti
+(1.000,0.078,0.576) ,# V
+(0.502,0.502,0.565) ,# Cr
+(0.502,0.502,0.565) ,# Mn
+(1.000,0.647,0.000) ,# Fe
+(1.000,0.078,0.576) ,# Co
+(0.647,0.165,0.165) ,# Ni
+(0.647,0.165,0.165) ,# Cu
+(0.647,0.165,0.165) ,# Zn
+(1.000,0.078,0.576) ,# Ga
+(1.000,0.078,0.576) ,# Ge
+(1.000,0.078,0.576) ,# As
+(1.000,0.078,0.576) ,# Se
+(0.647,0.165,0.165) ,# Br
+(1.000,0.078,0.576) ,# Kr
+(1.000,0.078,0.576) ,# Rb
+(1.000,0.078,0.576) ,# Sr
+(1.000,0.078,0.576) ,# Y
+(1.000,0.078,0.576) ,# Zr
+(1.000,0.078,0.576) ,# Nb
+(1.000,0.078,0.576) ,# Mo
+(1.000,0.078,0.576) ,# Tc
+(1.000,0.078,0.576) ,# Ru
+(1.000,0.078,0.576) ,# Rh
+(1.000,0.078,0.576) ,# Pd
+(0.502,0.502,0.565) ,# Ag
+(1.000,0.078,0.576) ,# Cd
+(1.000,0.078,0.576) ,# In
+(1.000,0.078,0.576) ,# Sn
+(1.000,0.078,0.576) ,# Sb
+(1.000,0.078,0.576) ,# Te
+(0.627,0.125,0.941) ,# I
+(1.000,0.078,0.576) ,# Xe
+(1.000,0.078,0.576) ,# Cs
+(1.000,0.647,0.000) ,# Ba
+(1.000,0.078,0.576) ,# La
+(1.000,0.078,0.576) ,# Ce
+(1.000,0.078,0.576) ,# Pr
+(1.000,0.078,0.576) ,# Nd
+(1.000,0.078,0.576) ,# Pm
+(1.000,0.078,0.576) ,# Sm
+(1.000,0.078,0.576) ,# Eu
+(1.000,0.078,0.576) ,# Gd
+(1.000,0.078,0.576) ,# Tb
+(1.000,0.078,0.576) ,# Dy
+(1.000,0.078,0.576) ,# Ho
+(1.000,0.078,0.576) ,# Er
+(1.000,0.078,0.576) ,# Tm
+(1.000,0.078,0.576) ,# Yb
+(1.000,0.078,0.576) ,# Lu
+(1.000,0.078,0.576) ,# Hf
+(1.000,0.078,0.576) ,# Ta
+(1.000,0.078,0.576) ,# W
+(1.000,0.078,0.576) ,# Re
+(1.000,0.078,0.576) ,# Os
+(1.000,0.078,0.576) ,# Ir
+(1.000,0.078,0.576) ,# Pt
+(0.855,0.647,0.125) ,# Au
+(1.000,0.078,0.576) ,# Hg
+(1.000,0.078,0.576) ,# Tl
+(1.000,0.078,0.576) ,# Pb
+(1.000,0.078,0.576) ,# Bi
+(1.000,0.078,0.576) ,# Po
+(1.000,0.078,0.576) ,# At
+(1.000,1.000,1.000) ,# Rn
+(1.000,1.000,1.000) ,# Fr
+(1.000,1.000,1.000) ,# Ra
+(1.000,1.000,1.000) ,# Ac
+(1.000,0.078,0.576) ,# Th
+(1.000,1.000,1.000) ,# Pa
+(1.000,0.078,0.576) ,# U
+(1.000,1.000,1.000) ,# Np
+(1.000,1.000,1.000) ,# Pu
+(1.000,1.000,1.000) ,# Am
+(1.000,1.000,1.000) ,# Cm
+(1.000,1.000,1.000) ,# Bk
+(1.000,1.000,1.000) ,# Cf
+(1.000,1.000,1.000) ,# Es
+(1.000,1.000,1.000) ,# Fm
+(1.000,1.000,1.000) ,# Md
+(1.000,1.000,1.000) ,# No
+(1.000,1.000,1.000)  # Lw
+])
Index: ase/data/molecules.py
===================================================================
--- ase/data/molecules.py	(revision 0)
+++ ase/data/molecules.py	(revision 1)
@@ -0,0 +1,2503 @@
+"""
+The following contains a database of small molecules
+
+Data for the G2 database are from
+Raghavachari, Redfern, and Pople, J. Chem. Phys. Vol. 106, 1063 (1997).
+See http://www.cse.anl.gov/Catalysis_and_Energy_Conversion/Computational_Thermochemistry.shtml for the original files.
+
+All numbers are experimental values, except for coordinates, which are
+MP2(full)/6-31G(d) optimized geometries
+
+Atomic species:
+ref: Curtiss et al. JCP 106, 1063 (1997).
+'Enthalpy' is the experimental enthalpies of formation at 0K
+'thermal correction' is the thermal corrections H(298)-H(0)
+
+Molecular species:
+ref: Staroverov et al. JCP 119, 12129 (2003)
+'Enthalpy' is the experimental enthalpies of formation at 298K
+'ZPE' is the zero-point energies
+'thermal correction' is the thermal enthalpy corrections H(298K) - H_exp(0K)
+ZPE and thermal corrections are estimated from B3LYP geometries and vibrations.
+
+Data for extra systems are from CCCBDB: http://srdata.nist.gov/cccbdb/
+Experimental ionization potentials are also from CCCBDB.
+"""
+
+from ase.atoms import Atoms, string2symbols
+
+atoms = ['H','Li','Be','B','C','N','O','F','Na','Mg','Al','Si','P','S','Cl']
+extra = ['Be2','C7NH5','BDA','biphenyl','C60']
+g1 = ['LiH','BeH','CH','CH2_s3B1d','CH2_s1A1d','CH3','CH4','NH','NH2','NH3','OH','H2O','HF','SiH2_s1A1d','SiH2_s3B1d','SiH3','SiH4','PH2','PH3','SH2','HCl','Li2','LiF','C2H2','C2H4','C2H6','CN','HCN','CO','HCO','H2CO','CH3OH','N2','N2H4','NO','O2','H2O2','F2','CO2','Na2','Si2','P2','S2','Cl2','NaCl','SiO','CS','SO','ClO','ClF','Si2H6','CH3Cl','CH3SH','HOCl','SO2']
+g2 = g1 + ['BF3','BCl3','AlF3','AlCl3','CF4','CCl4','OCS','CS2','COF2','SiF4','SiCl4','N2O','ClNO','NF3','PF3','O3','F2O','ClF3','C2F4','C2Cl4','CF3CN','C3H4_C3v','C3H4_D2d','C3H4_C2v','C3H6_Cs','C3H6_D3h','C3H8','butadiene','2-butyne','methylenecyclopropane','bicyclobutane','cyclobutene','cyclobutane','isobutene','trans-butane','isobutane','C5H8','C6H6','H2CF2','HCF3','H2CCl2','HCCl3','H3CNH2','CH3CN','CH3NO2','CH3ONO','CH3SiH3','HCOOH','HCOOCH3','CH3CONH2','CH2NHCH2','NCCN','C2H6NH','CH3CH2NH2','H2CCO','CH2OCH2','CH3CHO','OCHCHO','CH3CH2OH','CH3OCH3','CH2SCH2','C2H6SO','CH3CH2SH','CH3SCH3','H2CCHF','CH3CH2Cl','H2CCHCl','H2CCHCN','CH3COCH3','CH3COOH','CH3COF','CH3COCl','C3H7Cl','C2H6CHOH','CH3CH2OCH3','C3H9N','C4H4O','C4H4S','C4H4NH','C5H5N','H2','SH','CCH','C2H3','CH3CO','H2COH','CH3O','CH3CH2O','CH3S','C2H5','C3H7','C3H9C','NO2']
+g3 = g2 + ['Butadiene_1_2','Isoprene','Cyclopentane','n_Pentane','Neopentane','Cyclohexadiene_1_3','Cyclohexadiene_1_4','Cyclohexane','n_Hexane','Methyl_pentane_3','Toluene','n_Heptane','Cyclooctatetraene','n_Octane','Naphthalene','Azulene','Methyl_acetate','t_Butanol','Aniline','Phenol','Divinyl_ether','Tetrahydrofuran','Cyclopentanone','Benzoquinone_1_4','Pyrimidine','Dimethyl_sulfone','Chlorobenzene','Succinonitrile','Pyrazine','Acetyl_acetylene','Crotonaldehyde','Acetic_anhydride','Dihydrothiophene_2_5','Methyl_propanenitrile_2','Methyl_ethyl_ketone','Isobutyraldehyde','dioxane_1_4','Tetrahydrothiophene','t_Butyl_chloride','n_Butyl_chloride','Tetrahydropyrrole','Nitrobutane_2','Diethyl_ether','Dimethoxy_ethane_1_1','t_Butanethiol','Diethyl_disulfide','t_Butylamine','Tetramethylsilane','Methyl_thiophene','N_methyl_pyrrole','Tetrahydropyran','Diethyl_ketone','Isopropyl_acetate','Tetrahydrothiopyran','Piperidine','t_Butyl_methyl_ether','Difluorobenzene_1_3','Difluorobenzene_1_4','Fluorobenzene','Diisopropyl_ether','PF5','SF6','P4_Td','SO3_D3h','SCl2','POCl3','PCl5','SO2Cl2','PCl3','S2Cl2','SiCl2_1A1','CF3Cl','C2F6','CF3','Phenyl']
+
+data = {
+'H': {
+    'name': 'Hydrogen',
+    'magmom': 1,
+    'enthalpy': 51.63,
+    'thermal correction': 1.01,
+    'ionization energy': 13.60},
+'Li': {
+    'name': 'Lithium',
+    'magmom': 1,
+    'enthalpy': 37.69,
+    'thermal correction': 1.10,
+    'ionization energy': 5.39},
+'Be': {
+    'name': 'Beryllium',
+    'magmom': 0,
+    'enthalpy': 76.48,
+    'thermal correction': 0.46,
+    'ionization energy': 9.32},
+'B': {
+    'name': 'Boron',
+    'magmom': 1,
+    'enthalpy': 136.20,
+    'thermal correction': 0.29,
+    'ionization energy': 8.30},
+'C': {
+    'name': 'Carbon',
+    'magmom': 2,
+    'enthalpy': 169.98,
+    'thermal correction': 0.25,
+    'ionization energy': 11.26},
+'N': {
+    'name': 'Nitrogen',
+    'magmom': 3,
+    'enthalpy': 112.53,
+    'thermal correction': 1.04,
+    'ionization energy': 14.53},
+'O': {
+    'name': 'Oxygen',
+    'magmom': 2,
+    'enthalpy': 58.99,
+    'thermal correction': 1.04,
+    'ionization energy': 13.62},
+'F': {
+    'name': 'Fluorine',
+    'magmom': 1,
+    'enthalpy': 18.47,
+    'thermal correction': 1.05,
+    'ionization energy': 17.42},
+'Na': {
+    'name': 'Sodium',
+    'magmom': 1,
+    'enthalpy': 25.69,
+    'thermal correction': 1.54,
+    'ionization energy': 5.14},
+'Mg': {
+    'name': 'Magnesium',
+    'magmom': 0,
+    'enthalpy': 34.87,
+    'thermal correction': 1.19,
+    'ionization energy': 7.65},
+'Al': {
+    'name': 'Aluminium',
+    'magmom': 1,
+    'enthalpy': 78.23,
+    'thermal correction': 1.08,
+    'ionization energy': 5.99},
+'Si': {
+    'name': 'Silicon',
+    'magmom': 2,
+    'enthalpy': 106.60,
+    'thermal correction': 0.76,
+    'ionization energy': 8.15},
+'P': {
+    'name': 'Phosphorus',
+    'magmom': 3,
+    'enthalpy': 75.42,
+    'thermal correction': 1.28,
+    'ionization energy': 10.49},
+'S': {
+    'name': 'Sulfur',
+    'magmom': 2,
+    'enthalpy': 65.66,
+    'thermal correction': 1.05,
+    'ionization energy': 10.36},
+'Cl': {
+    'name': 'Chlorine',
+    'magmom': 1,
+    'enthalpy': 28.59,
+    'thermal correction': 1.10,
+    'ionization energy': 12.97},
+'Be2': {
+    'description': "Diatomic Beryllium",
+    'name': "Be_2",
+    'enthalpy': 155.1,
+    'ZPE': 1.0000,
+    'thermal correction': 5.0600,
+    'symbols': 'BeBe',
+    'magmoms': None,
+    'positions': [[ 0.  ,  0.  ,  1.0106],
+                  [ 0.  ,  0.  , -1.0106]]},
+'C7NH5': {
+    'description': "Benzonitride",
+    'name': "C_7NH_5",
+    'symbols': 'C7NH5',
+    'magmoms': None,
+    'positions': [[ -1.593581, -1.142601, 0.],
+                  [ -2.235542,  0.095555, 0.],
+                  [ -0.204885, -1.210726, 0.],
+                  [  0.549645, -0.025355, 0.],
+                  [  1.976332, -0.085321, 0.],
+                  [ -0.099258,  1.220706, 0.],
+                  [ -1.488628,  1.273345, 0.],
+                  [  3.136871, -0.128138, 0.],
+                  [ -2.177996, -2.060896, 0.],
+                  [ -3.323594,  0.141242, 0.],
+                  [  0.301694, -2.173705, 0.],
+                  [  0.488716,  2.136782, 0.],
+                  [ -1.987765,  2.240495, 0.]]},
+'BDA': {
+    'description': "1,4-Benzodiamine",
+    # aka p-Aminoaniline; p-Benzenediamine; p-Diaminobenzene;
+    #     p-Phenylenediamine; Paraphenylen-diamine
+    'name': "BDA",
+    # PBE-gpaw relaxed
+    'symbols': 'C6H4N2H4',
+    'magmoms': None,
+    'positions': [[ 0.004212,  1.406347,  0.061073],
+                  [ 1.193490,  0.687096,  0.029481],
+                  [ 1.190824, -0.690400, -0.028344],
+                  [ 0.000295, -1.406191, -0.059503],
+                  [-1.186974, -0.685668, -0.045413],
+                  [-1.185376,  0.690203,  0.009452],
+                  [ 2.147124,  1.219997,  0.064477],
+                  [ 2.141593, -1.227477, -0.054266],
+                  [-2.138408, -1.222814, -0.095050],
+                  [-2.137740,  1.226930,  0.023036],
+                  [-0.006314,  2.776024,  0.186278],
+                  [-0.007340, -2.777839, -0.159936],
+                  [ 0.844710, -3.256543,  0.110098],
+                  [-0.854965, -3.253324,  0.130125],
+                  [ 0.845826,  3.267270, -0.055549],
+                  [-0.854666,  3.254654, -0.092676]]},
+'biphenyl': {
+    'description': "Biphenyl",
+    'name': "biphenyl",
+    # PBE-gpaw relaxed
+    'ionization energy': 8.16,
+    'symbols': 'C6H5C6H5',
+    'magmoms': None,
+    'positions': [[-0.74081, -0.00000, -0.00003],
+                  [-1.46261, -1.20370, -0.00993],
+                  [-2.85531, -1.20350, -0.00663],
+                  [-3.55761, -0.00000, -0.00003],
+                  [-2.85531,  1.20350,  0.00667],
+                  [-1.46261,  1.20370,  0.00997],
+                  [-0.92071, -2.14850,  0.00967],
+                  [-3.38981, -2.15110, -0.00083],
+                  [-4.64571, -0.00000, -0.00003],
+                  [-3.38981,  2.15110,  0.00077],
+                  [-0.92071,  2.14850, -0.00963],
+                  [ 3.55849, -0.00000, -0.00003],
+                  [ 2.85509, -0.86640, -0.83553],
+                  [ 1.46289, -0.87000, -0.83153],
+                  [ 0.73969, -0.00000, -0.00003],
+                  [ 1.46289,  0.87000,  0.83157],
+                  [ 2.85509,  0.86640,  0.83547],
+                  [ 4.64659, -0.00000, -0.00003],
+                  [ 3.39189, -1.53770, -1.50253],
+                  [ 0.91869, -1.53310, -1.50263],
+                  [ 0.91869,  1.53310,  1.50267],
+                  [ 3.39189,  1.53770,  1.50257]]},
+'C60': {
+    'description': "Buckminsterfullerene, I*h symm.",
+    'name': "C_{60}",
+    # The Buckyball has two degrees of freedom, the C-C bond, and the C=C bond.
+    # This is an LDA-gpaw relaxed structure with bond lengths 1.437 and 1.385.
+    # Experimentally, the two bond lengths are 1.45 and 1.40 Angstrom.
+    'symbols': 'C60',
+    'magmoms': None,
+    'positions': [[ 2.2101953,  0.5866631,  2.6669504],
+                  [ 3.1076393,  0.1577008,  1.6300286],
+                  [ 1.3284430, -0.3158939,  3.2363232],
+                  [ 3.0908709, -1.1585005,  1.2014240],
+                  [ 3.1879245, -1.4574599, -0.1997005],
+                  [ 3.2214623,  1.2230966,  0.6739440],
+                  [ 3.3161210,  0.9351586, -0.6765151],
+                  [ 3.2984981, -0.4301142, -1.1204138],
+                  [-0.4480842,  1.3591484,  3.2081020],
+                  [ 0.4672056,  2.2949830,  2.6175264],
+                  [-0.0256575,  0.0764219,  3.5086259],
+                  [ 1.7727917,  1.9176584,  2.3529691],
+                  [ 2.3954623,  2.3095689,  1.1189539],
+                  [-0.2610195,  3.0820935,  1.6623117],
+                  [ 0.3407726,  3.4592388,  0.4745968],
+                  [ 1.6951171,  3.0692446,  0.1976623],
+                  [-2.1258394, -0.8458853,  2.6700963],
+                  [-2.5620990,  0.4855202,  2.3531715],
+                  [-0.8781521, -1.0461985,  3.2367302],
+                  [-1.7415096,  1.5679963,  2.6197333],
+                  [-1.6262468,  2.6357030,  1.6641811],
+                  [-3.2984810,  0.4301871,  1.1204208],
+                  [-3.1879469,  1.4573895,  0.1996030],
+                  [-2.3360261,  2.5813627,  0.4760912],
+                  [-0.5005210, -2.9797771,  1.7940308],
+                  [-1.7944338, -2.7729087,  1.2047891],
+                  [-0.0514245, -2.1328841,  2.7938830],
+                  [-2.5891471, -1.7225828,  1.6329715],
+                  [-3.3160705, -0.9350636,  0.6765268],
+                  [-1.6951919, -3.0692581, -0.1976564],
+                  [-2.3954901, -2.3096853, -1.1189862],
+                  [-3.2214182, -1.2231835, -0.6739581],
+                  [ 2.1758234, -2.0946263,  1.7922529],
+                  [ 1.7118619, -2.9749681,  0.7557198],
+                  [ 1.3130656, -1.6829416,  2.7943892],
+                  [ 0.3959024, -3.4051395,  0.7557638],
+                  [-0.3408219, -3.4591883, -0.4745610],
+                  [ 2.3360057, -2.5814499, -0.4761050],
+                  [ 1.6263757, -2.6357349, -1.6642309],
+                  [ 0.2611352, -3.0821271, -1.6622618],
+                  [-2.2100844, -0.5868636, -2.6670300],
+                  [-1.7726970, -1.9178969, -2.3530466],
+                  [-0.4670723, -2.2950509, -2.6175105],
+                  [-1.3283500,  0.3157683, -3.2362375],
+                  [-2.1759882,  2.0945383, -1.7923294],
+                  [-3.0909663,  1.1583472, -1.2015749],
+                  [-3.1076090, -0.1578453, -1.6301627],
+                  [-1.3131365,  1.6828292, -2.7943639],
+                  [ 0.5003224,  2.9799637, -1.7940203],
+                  [-0.3961148,  3.4052817, -0.7557272],
+                  [-1.7120629,  2.9749122, -0.7557988],
+                  [ 0.0512824,  2.1329478, -2.7937450],
+                  [ 2.1258630,  0.8460809, -2.6700534],
+                  [ 2.5891853,  1.7227742, -1.6329562],
+                  [ 1.7943010,  2.7730684, -1.2048262],
+                  [ 0.8781323,  1.0463514, -3.2365313],
+                  [ 0.4482452, -1.3591061, -3.2080510],
+                  [ 1.7416948, -1.5679557, -2.6197714],
+                  [ 2.5621724, -0.4853529, -2.3532026],
+                  [ 0.0257904, -0.0763567, -3.5084446]]},
+'LiH': {
+    'description': "Lithium hydride (LiH), C*v symm.",
+    'name': "LiH",
+    'enthalpy': 33.3,
+    'ZPE': 2.0149,
+    'thermal correction': 2.0783,
+    'ionization energy': 7.90,
+    'symbols': 'LiH',
+    'magmoms': None,
+    'positions': [[ 0.  ,  0.  ,  0.41],
+                  [ 0.  ,  0.  , -1.23]]},
+'BeH': {
+    'description': "Beryllium hydride (BeH), D*h symm.",
+    'name': "BeH",
+    'enthalpy': 81.7,
+    'ZPE': 2.9073,
+    'thermal correction': 2.0739,
+    'ionization energy': 8.21,
+    'symbols': 'BeH',
+    'magmoms': [ 1.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.269654],
+                  [ 0.      ,  0.      , -1.078616]]},
+'CH': {
+    'description': "CH radical. Doublet, C*v symm.",
+    'name': "CH",
+    'enthalpy': 142.5,
+    'ZPE': 3.9659,
+    'thermal correction': 2.0739,
+    'ionization energy': 10.64,
+    'symbols': 'CH',
+    'magmoms': [ 1.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.160074],
+                  [ 0.      ,  0.      , -0.960446]]},
+'CH2_s3B1d': {
+    'description': "Triplet methylene (CH2), C2v symm, 3-B1.",
+    'name': "CH_2 (^3B_1)",
+    'enthalpy': 93.7,
+    'ZPE': 10.6953,
+    'thermal correction': 2.3877,
+    'ionization energy': 10.40,
+    'symbols': 'CHH',
+    'magmoms': [ 2.,  0.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.110381],
+                  [ 0.      ,  0.982622, -0.331142],
+                  [ 0.      , -0.982622, -0.331142]]},
+'CH2_s1A1d': {
+    'description': "Singlet methylene (CH2), C2v symm, 1-A1.",
+    'name': "CH_2 (^1A_1)",
+    'enthalpy': 102.8,
+    'ZPE': 10.2422,
+    'thermal correction': 2.3745,
+    'symbols': 'CHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.174343],
+                  [ 0.      ,  0.862232, -0.523029],
+                  [ 0.      , -0.862232, -0.523029]]},
+'CH3': {
+    'description': "Methyl radical (CH3), D3h symm.",
+    'name': "CH_3",
+    'enthalpy': 35.0,
+    'ZPE': 18.3383,
+    'thermal correction': 2.5383,
+    'ionization energy': 9.84,
+    'symbols': 'CHHH',
+    'magmoms': [ 1.,  0.,  0.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  1.07841 ,  0.      ],
+                  [ 0.93393 , -0.539205,  0.      ],
+                  [-0.93393 , -0.539205,  0.      ]]},
+'CH4': {
+    'description': "Methane (CH4), Td symm.",
+    'name': "CH_4",
+    'enthalpy': -17.9,
+    'ZPE': 27.6744,
+    'thermal correction': 2.3939,
+    'ionization energy': 12.64,
+    'vertical ionization energy': 13.60,
+    'symbols': 'CHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.629118,  0.629118,  0.629118],
+                  [-0.629118, -0.629118,  0.629118],
+                  [ 0.629118, -0.629118, -0.629118],
+                  [-0.629118,  0.629118, -0.629118]]},
+'NH': {
+    'description': "NH, triplet, C*v symm.",
+    'name': "NH",
+    'enthalpy': 85.2,
+    'ZPE': 4.5739,
+    'thermal correction': 2.0739,
+    'ionization energy': 13.10,
+    'vertical ionization energy': 13.49,
+    'symbols': 'NH',
+    'magmoms': [ 2.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.129929],
+                  [ 0.      ,  0.      , -0.909501]]},
+'NH2': {
+    'description': "NH2 radical, C2v symm, 2-B1.",
+    'name': "NH_2",
+    'enthalpy': 45.1,
+    'ZPE': 11.7420,
+    'thermal correction': 2.3726,
+    'ionization energy': 10.78,
+    'vertical ionization energy': 12.00,
+    'symbols': 'NHH',
+    'magmoms': [ 1.,  0.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.14169 ],
+                  [ 0.      ,  0.806442, -0.495913],
+                  [ 0.      , -0.806442, -0.495913]]},
+'NH3': {
+    'description': "Ammonia (NH3), C3v symm.",
+    'name': "NH_3",
+    'enthalpy': -11.0,
+    'ZPE': 21.2462,
+    'thermal correction': 2.3896,
+    'ionization energy': 10.07,
+    'vertical ionization energy': 10.82,
+    'symbols': 'NHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.116489],
+                  [ 0.      ,  0.939731, -0.271808],
+                  [ 0.813831, -0.469865, -0.271808],
+                  [-0.813831, -0.469865, -0.271808]]},
+'OH': {
+    'description': "OH radical, C*v symm.",
+    'name': "OH",
+    'enthalpy': 9.4,
+    'ZPE': 5.2039,
+    'thermal correction': 2.0739,
+    'ionization energy': 13.02,
+    'symbols': 'OH',
+    'magmoms': [ 0.5,  0.5],
+    'positions': [[ 0.      ,  0.      ,  0.108786],
+                  [ 0.      ,  0.      , -0.870284]]},
+'H2O': {
+    'description': "Water (H2O), C2v symm.",
+    'name': "H_2O",
+    'enthalpy': -57.8,
+    'ZPE': 13.2179,
+    'thermal correction': 2.3720,
+    'ionization energy': 12.62,
+    'symbols': 'OHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.119262],
+                  [ 0.      ,  0.763239, -0.477047],
+                  [ 0.      , -0.763239, -0.477047]]},
+'HF': {
+    'description': "Hydrogen fluoride (HF), C*v symm.",
+    'name': "HF",
+    'enthalpy': -65.1,
+    'ZPE': 5.7994,
+    'thermal correction': 2.0733,
+    'ionization energy': 16.03,
+    'vertical ionization energy': 16.12,
+    'symbols': 'FH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.093389],
+                  [ 0.      ,  0.      , -0.840502]]},
+'SiH2_s1A1d': {
+    'description': "Singlet silylene (SiH2), C2v symm, 1-A1.",
+    'name': "SiH_2 (^1A_1)",
+    'enthalpy': 65.2,
+    'ZPE': 7.1875,
+    'thermal correction': 2.3927,
+    'ionization energy': 8.92,
+    'symbols': 'SiHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.131272],
+                  [ 0.      ,  1.096938, -0.918905],
+                  [ 0.      , -1.096938, -0.918905]]},
+'SiH2_s3B1d': {
+    'description': "Triplet silylene (SiH2), C2v symm, 3-B1.",
+    'name': "SiH_2 (^3B_1)",
+    'enthalpy': 86.2,
+    'ZPE': 7.4203,
+    'thermal correction': 2.4078,
+    'symbols': 'SiHH',
+    'magmoms': [ 2.,  0.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.094869],
+                  [ 0.      ,  1.271862, -0.664083],
+                  [ 0.      , -1.271862, -0.664083]]},
+'SiH3': {
+    'description': "Silyl radical (SiH3), C3v symm.",
+    'name': "SiH_3",
+    'enthalpy': 47.9,
+    'ZPE': 13.0898,
+    'thermal correction': 2.4912,
+    'ionization energy': 8.14,
+    'vertical ionization energy': 8.74,
+    'symbols': 'SiHHH',
+    'magmoms': [ 1.,  0.,  0.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.079299],
+                  [ 0.      ,  1.41328 , -0.370061],
+                  [ 1.223937, -0.70664 , -0.370061],
+                  [-1.223937, -0.70664 , -0.370061]]},
+'SiH4': {
+    'description': "Silane (SiH4), Td symm.",
+    'name': "SiH_4",
+    'enthalpy': 8.2,
+    'ZPE': 19.2664,
+    'thermal correction': 2.5232,
+    'ionization energy': 11.00,
+    'vertical ionization energy': 12.30,
+    'symbols': 'SiHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.856135,  0.856135,  0.856135],
+                  [-0.856135, -0.856135,  0.856135],
+                  [-0.856135,  0.856135, -0.856135],
+                  [ 0.856135, -0.856135, -0.856135]]},
+'PH2': {
+    'description': "PH2 radical, C2v symm.",
+    'name': "PH_2",
+    'enthalpy': 33.1,
+    'ZPE': 8.2725,
+    'thermal correction': 2.3845,
+    'ionization energy': 9.82,
+    'symbols': 'PHH',
+    'magmoms': [ 1.,  0.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.115396],
+                  [ 0.      ,  1.025642, -0.865468],
+                  [ 0.      , -1.025642, -0.865468]]},
+'PH3': {
+    'description': "Phosphine (PH3), C3v symm.",
+    'name': "PH_3",
+    'enthalpy': 1.3,
+    'ZPE': 14.7885,
+    'thermal correction': 2.4203,
+    'ionization energy': 9.87,
+    'vertical ionization energy': 10.95,
+    'symbols': 'PHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.124619],
+                  [ 0.      ,  1.200647, -0.623095],
+                  [ 1.039791, -0.600323, -0.623095],
+                  [-1.039791, -0.600323, -0.623095]]},
+'SH2': {
+    'description': "Hydrogen sulfide (H2S), C2v symm.",
+    'name': "SH_2",
+    'enthalpy': -4.9,
+    'ZPE': 9.3129,
+    'thermal correction': 2.3808,
+    'ionization energy': 10.46,
+    'vertical ionization energy': 10.50,
+    'symbols': 'SHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.102135],
+                  [ 0.      ,  0.974269, -0.817083],
+                  [ 0.      , -0.974269, -0.817083]]},
+'HCl': {
+    'description': "Hydrogen chloride (HCl), C*v symm.",
+    'name': "HCl",
+    'enthalpy': -22.1,
+    'ZPE': 4.1673,
+    'thermal correction': 2.0739,
+    'ionization energy': 12.74,
+    'symbols': 'ClH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.07111 ],
+                  [ 0.      ,  0.      , -1.208868]]},
+'Li2': {
+    'description': "Dilithium (Li2), D*h symm.",
+    'name': "Li_2",
+    'enthalpy': 51.6,
+    'ZPE': 0.4838,
+    'thermal correction': 2.3086,
+    'ionization energy': 5.11,
+    'symbols': 'LiLi',
+    'magmoms': None,
+    'positions': [[ 0.     ,  0.     ,  1.38653],
+                  [ 0.     ,  0.     , -1.38653]]},
+'LiF': {
+    'description': "Lithium Fluoride (LiF), C*v symm.",
+    'name': "LiF",
+    'enthalpy': -80.1,
+    'ZPE': 1.4019,
+    'thermal correction': 2.0990,
+    'ionization energy': 11.30,
+    'symbols': 'LiF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.174965],
+                  [ 0.      ,  0.      ,  0.391655]]},
+'C2H2': {
+    'description': "Acetylene (C2H2), D*h symm.",
+    'name': "C_2H_2",
+    'enthalpy': 54.2,
+    'ZPE': 16.6001,
+    'thermal correction': 2.4228,
+    'ionization energy': 11.40,
+    'vertical ionization energy': 11.49,
+    'symbols': 'CCHH',
+    'magmoms': None,
+    'positions': [[ 0.     ,  0.     ,  0.60808],
+                  [ 0.     ,  0.     , -0.60808],
+                  [ 0.     ,  0.     , -1.67399],
+                  [ 0.     ,  0.     ,  1.67399]]},
+'C2H4': {
+    'description': "Ethylene (H2C=CH2), D2h symm.",
+    'name': "C_2H_4",
+    'enthalpy': 12.5,
+    'ZPE': 31.5267,
+    'thermal correction': 2.5100,
+    'ionization energy': 11.40,
+    'vertical ionization energy': 11.49,
+    'symbols': 'CCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.66748 ],
+                  [ 0.      ,  0.      , -0.66748 ],
+                  [ 0.      ,  0.922832,  1.237695],
+                  [ 0.      , -0.922832,  1.237695],
+                  [ 0.      ,  0.922832, -1.237695],
+                  [ 0.      , -0.922832, -1.237695]]},
+'C2H6': {
+    'description': "Ethane (H3C-CH3), D3d symm.",
+    'name': "C_2H_6",
+    'enthalpy': -20.1,
+    'ZPE': 46.0950,
+    'thermal correction': 2.7912,
+    'symbols': 'CCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.762209],
+                  [ 0.      ,  0.      , -0.762209],
+                  [ 0.      ,  1.018957,  1.157229],
+                  [-0.882443, -0.509479,  1.157229],
+                  [ 0.882443, -0.509479,  1.157229],
+                  [ 0.      , -1.018957, -1.157229],
+                  [-0.882443,  0.509479, -1.157229],
+                  [ 0.882443,  0.509479, -1.157229]]},
+'CN': {
+    'description': "Cyano radical (CN), C*v symm, 2-Sigma+.",
+    'name': "CN",
+    'enthalpy': 104.9,
+    'ZPE': 3.0183,
+    'thermal correction': 2.0739,
+    'ionization energy': 13.60,
+    'symbols': 'CN',
+    'magmoms': [ 1.,  0.],
+    'positions': [[ 0.      ,  0.      , -0.611046],
+                  [ 0.      ,  0.      ,  0.523753]]},
+'HCN': {
+    'description': "Hydrogen cyanide (HCN), C*v symm.",
+    'name': "HCN",
+    'enthalpy': 31.5,
+    'ZPE': 10.2654,
+    'thermal correction': 2.1768,
+    'ionization energy': 13.60,
+    'vertical ionization energy': 13.61,
+    'symbols': 'CNH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -0.511747],
+                  [ 0.      ,  0.      ,  0.664461],
+                  [ 0.      ,  0.      , -1.580746]]},
+'CO': {
+    'description': "Carbon monoxide (CO), C*v symm.",
+    'name': "CO",
+    'enthalpy': -26.4,
+    'ZPE': 3.1062,
+    'thermal correction': 2.0739,
+    'ionization energy': 14.01,
+    'vertical ionization energy': 14.01,
+    'symbols': 'OC',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.493003],
+                  [ 0.      ,  0.      , -0.657337]]},
+'HCO': {
+    'description': "HCO radical, Bent Cs symm.",
+    'name': "HCO",
+    'enthalpy': 10.0,
+    'ZPE': 8.0290,
+    'thermal correction': 2.3864,
+    'ionization energy': 8.12,
+    'vertical ionization energy': 9.31,
+    'symbols': 'COH',
+    'magmoms': [ 1.,  0.,  0.],
+    'positions': [[ 0.06256 ,  0.593926,  0.      ],
+                  [ 0.06256 , -0.596914,  0.      ],
+                  [-0.875835,  1.211755,  0.      ]]},
+'H2CO': {
+    'description': "Formaldehyde (H2C=O), C2v symm.",
+    'name': "H_2CO",
+    'enthalpy': -26.0,
+    'ZPE': 16.4502,
+    'thermal correction': 2.3927,
+    'ionization energy': 10.88,
+    'vertical ionization energy': 10.88,
+    'symbols': 'OCHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.683501],
+                  [ 0.      ,  0.      , -0.536614],
+                  [ 0.      ,  0.93439 , -1.124164],
+                  [ 0.      , -0.93439 , -1.124164]]},
+'CH3OH': {
+    'description': "Methanol (CH3-OH), Cs symm.",
+    'name': "H_3COH",
+    'enthalpy': -48.0,
+    'ZPE': 31.6635,
+    'thermal correction': 2.6832,
+    'ionization energy': 10.84,
+    'vertical ionization energy': 10.96,
+    'symbols': 'COHHHH',
+    'magmoms': None,
+    'positions': [[-0.047131,  0.664389,  0.      ],
+                  [-0.047131, -0.758551,  0.      ],
+                  [-1.092995,  0.969785,  0.      ],
+                  [ 0.878534, -1.048458,  0.      ],
+                  [ 0.437145,  1.080376,  0.891772],
+                  [ 0.437145,  1.080376, -0.891772]]},
+'N2': {
+    'description': "N2 molecule, D*h symm.",
+    'name': "N_2",
+    'enthalpy': 0.0,
+    'ZPE': 3.4243,
+    'thermal correction': 2.0733,
+    'ionization energy': 15.58,
+    'vertical ionization energy': 15.58,
+    'symbols': 'NN',
+    'magmoms': None,
+    'positions': [[ 0.     ,  0.     ,  0.56499],
+                  [ 0.     ,  0.     , -0.56499]]},
+'N2H4': {
+    'description': "Hydrazine (H2N-NH2), C2 symm.",
+    'name': "H_2NNH_2",
+    'enthalpy': 22.8,
+    'ZPE': 32.9706,
+    'thermal correction': 2.6531,
+    'ionization energy': 8.10,
+    'vertical ionization energy': 8.98,
+    'symbols': 'NNHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.718959, -0.077687],
+                  [ 0.      , -0.718959, -0.077687],
+                  [ 0.211082,  1.092752,  0.847887],
+                  [-0.948214,  1.005026, -0.304078],
+                  [-0.211082, -1.092752,  0.847887],
+                  [ 0.948214, -1.005026, -0.304078]]},
+'NO': {
+    'description': "NO radical, C*v symm, 2-Pi.",
+    'name': "NO",
+    'enthalpy': 21.6,
+    'ZPE': 2.7974,
+    'thermal correction': 2.0745,
+    'ionization energy': 9.26,
+    'vertical ionization energy': 9.26,
+    'symbols': 'NO',
+    'magmoms': [ 0.6,  0.4],
+    'positions': [[ 0.      ,  0.      , -0.609442],
+                  [ 0.      ,  0.      ,  0.533261]]},
+'O2': {
+    'description': "O2 molecule, D*h symm, Triplet.",
+    'name': "O_2",
+    'enthalpy': 0.0,
+    'ZPE': 2.3444,
+    'thermal correction': 2.0752,
+    'ionization energy': 12.07,
+    'vertical ionization energy': 12.30,
+    'symbols': 'OO',
+    'magmoms': [ 1.,  1.],
+    'positions': [[ 0.      ,  0.      ,  0.622978],
+                  [ 0.      ,  0.      , -0.622978]]},
+'H2O2': {
+    'description': "Hydrogen peroxide (HO-OH), C2 symm.",
+    'name': "HOOH",
+    'enthalpy': -32.5,
+    'ZPE': 16.4081,
+    'thermal correction': 2.6230,
+    'ionization energy': 10.58,
+    'vertical ionization energy': 11.70,
+    'symbols': 'OOHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.734058, -0.05275 ],
+                  [ 0.      , -0.734058, -0.05275 ],
+                  [ 0.839547,  0.880752,  0.422001],
+                  [-0.839547, -0.880752,  0.422001]]},
+'F2': {
+    'description': "F2 molecule, D*h symm.",
+    'name': "F_2",
+    'enthalpy': 0.0,
+    'ZPE': 1.5179,
+    'thermal correction': 2.0915,
+    'ionization energy': 15.70,
+    'vertical ionization energy': 15.70,
+    'symbols': 'FF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.710304],
+                  [ 0.      ,  0.      , -0.710304]]},
+'CO2': {
+    'description': "Carbon dioxide (CO2), D*h symm.",
+    'name': "CO_2",
+    'enthalpy': -94.1,
+    'ZPE': 7.3130,
+    'thermal correction': 2.2321,
+    'ionization energy': 13.78,
+    'vertical ionization energy': 13.78,
+    'symbols': 'COO',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  0.      ,  1.178658],
+                  [ 0.      ,  0.      , -1.178658]]},
+'Na2': {
+    'description': "Disodium (Na2), D*h symm.",
+    'name': "Na_2",
+    'enthalpy': 34.0,
+    'ZPE': 0.2246,
+    'thermal correction': 2.4699,
+    'ionization energy': 4.89,
+    'symbols': 'NaNa',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.576262],
+                  [ 0.      ,  0.      , -1.576262]]},
+'Si2': {
+    'description': "Si2 molecule, D*h symm, Triplet (3-Sigma-G-).",
+    'name': "Si_2",
+    'enthalpy': 139.9,
+    'ZPE': 0.7028,
+    'thermal correction': 2.2182,
+    'ionization energy': 7.90,
+    'symbols': 'SiSi',
+    'magmoms': [ 1.,  1.],
+    'positions': [[ 0.      ,  0.      ,  1.130054],
+                  [ 0.      ,  0.      , -1.130054]]},
+'P2': {
+    'description': "P2 molecule, D*h symm.",
+    'name': "P_2",
+    'enthalpy': 34.3,
+    'ZPE': 1.1358,
+    'thermal correction': 2.1235,
+    'ionization energy': 10.53,
+    'vertical ionization energy': 10.62,
+    'symbols': 'PP',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.966144],
+                  [ 0.      ,  0.      , -0.966144]]},
+'S2': {
+    'description': "S2 molecule, D*h symm, triplet.",
+    'name': "S_2",
+    'enthalpy': 30.7,
+    'ZPE': 1.0078,
+    'thermal correction': 2.1436,
+    'ionization energy': 9.36,
+    'vertical ionization energy': 9.55,
+    'symbols': 'SS',
+    'magmoms': [ 1.,  1.],
+    'positions': [[ 0.      ,  0.      ,  0.960113],
+                  [ 0.      ,  0.      , -0.960113]]},
+'Cl2': {
+    'description': "Cl2 molecule, D*h symm.",
+    'name': "Cl_2",
+    'enthalpy': 0.0,
+    'ZPE': 0.7737,
+    'thermal correction': 2.1963,
+    'ionization energy': 11.48,
+    'vertical ionization energy': 11.49,
+    'symbols': 'ClCl',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.007541],
+                  [ 0.      ,  0.      , -1.007541]]},
+'NaCl': {
+    'description': "Sodium Chloride (NaCl), C*v symm.",
+    'name': "NaCl",
+    'enthalpy': -43.6,
+    'ZPE': 0.5152,
+    'thermal correction': 2.2935,
+    'ionization energy': 9.20,
+    'vertical ionization energy': 9.80,
+    'symbols': 'NaCl',
+    'magmoms': None,
+    'positions': [[ 0.     ,  0.     , -1.45166],
+                  [ 0.     ,  0.     ,  0.93931]]},
+'SiO': {
+    'description': "Silicon monoxide (SiO), C*v symm.",
+    'name': "SiO",
+    'enthalpy': -24.6,
+    'ZPE': 1.7859,
+    'thermal correction': 2.0821,
+    'ionization energy': 11.49,
+    'symbols': 'SiO',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.560846],
+                  [ 0.      ,  0.      , -0.98148 ]]},
+'CS': {
+    'description': "Carbon monosulfide (CS), C*v symm.",
+    'name': "SC",
+    'enthalpy': 66.9,
+    'ZPE': 1.8242,
+    'thermal correction': 2.0814,
+    'ionization energy': 11.33,
+    'symbols': 'CS',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.123382],
+                  [ 0.      ,  0.      ,  0.421268]]},
+'SO': {
+    'description': "Sulfur monoxide (SO), C*v symm, triplet.",
+    'name': "SO",
+    'enthalpy': 1.2,
+    'ZPE': 1.6158,
+    'thermal correction': 2.0877,
+    'ionization energy': 11.29,
+    'symbols': 'OS',
+    'magmoms': [ 1.,  1.],
+    'positions': [[ 0.      ,  0.      , -1.015992],
+                  [ 0.      ,  0.      ,  0.507996]]},
+'ClO': {
+    'description': "ClO radical, C*v symm, 2-PI.",
+    'name': "ClO",
+    'enthalpy': 24.2,
+    'ZPE': 1.1923,
+    'thermal correction': 2.1172,
+    'ionization energy': 10.89,
+    'vertical ionization energy': 11.01,
+    'symbols': 'ClO',
+    'magmoms': [ 1.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.514172],
+                  [ 0.      ,  0.      , -1.092615]]},
+'ClF': {
+    'description': "ClF molecule, C*v symm, 1-SG.",
+    'name': "FCl",
+    'enthalpy': -13.2,
+    'ZPE': 1.1113,
+    'thermal correction': 2.1273,
+    'ionization energy': 12.66,
+    'vertical ionization energy': 12.77,
+    'symbols': 'FCl',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.084794],
+                  [ 0.      ,  0.      ,  0.574302]]},
+'Si2H6': {
+    'description': "Disilane (H3Si-SiH3), D3d symm.",
+    'name': "Si_2H_6",
+    'enthalpy': 19.1,
+    'ZPE': 30.2265,
+    'thermal correction': 3.7927,
+    'ionization energy': 9.74,
+    'vertical ionization energy': 10.53,
+    'symbols': 'SiSiHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.167683],
+                  [ 0.      ,  0.      , -1.167683],
+                  [ 0.      ,  1.393286,  1.68602 ],
+                  [-1.206621, -0.696643,  1.68602 ],
+                  [ 1.206621, -0.696643,  1.68602 ],
+                  [ 0.      , -1.393286, -1.68602 ],
+                  [-1.206621,  0.696643, -1.68602 ],
+                  [ 1.206621,  0.696643, -1.68602 ]]},
+'CH3Cl': {
+    'description': "Methyl chloride (CH3Cl), C3v symm.",
+    'name': "CH_3Cl",
+    'enthalpy': -19.6,
+    'ZPE': 23.3013,
+    'thermal correction': 2.4956,
+    'symbols': 'CClHHH',
+    'ionization energy': 11.26,
+    'vertical ionization energy': 11.29,
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.121389],
+                  [ 0.      ,  0.      ,  0.655951],
+                  [ 0.      ,  1.029318, -1.47428 ],
+                  [ 0.891415, -0.514659, -1.47428 ],
+                  [-0.891415, -0.514659, -1.47428 ]]},
+'CH3SH': {
+    'description': "Methanethiol (H3C-SH), Staggered, Cs symm.",
+    'name': "H_3CSH",
+    'enthalpy': -5.5,
+    'ZPE': 28.3973,
+    'thermal correction': 2.8690,
+    'ionization energy': 9.44,
+    'vertical ionization energy': 9.44,
+    'symbols': 'CSHHHH',
+    'magmoms': None,
+    'positions': [[-0.047953,  1.149519,  0.      ],
+                  [-0.047953, -0.664856,  0.      ],
+                  [ 1.283076, -0.823249,  0.      ],
+                  [-1.092601,  1.461428,  0.      ],
+                  [ 0.432249,  1.551207,  0.892259],
+                  [ 0.432249,  1.551207, -0.892259]]},
+'HOCl': {
+    'description': "HOCl molecule, Cs symm.",
+    'name': "HOCl",
+    'enthalpy': -17.8,
+    'ZPE': 8.1539,
+    'thermal correction': 2.4416,
+    'ionization energy': 11.12,
+    'symbols': 'OHCl',
+    'magmoms': None,
+    'positions': [[ 0.036702,  1.113517,  0.      ],
+                  [-0.917548,  1.328879,  0.      ],
+                  [ 0.036702, -0.602177,  0.      ]]},
+'SO2': {
+    'description': "Sulfur dioxide (SO2), C2v symm.",
+    'name': "SO_2",
+    'enthalpy': -71.0,
+    'ZPE': 4.3242,
+    'thermal correction': 2.5245,
+    'ionization energy': 12.35,
+    'vertical ionization energy': 12.50,
+    'symbols': 'SOO',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.370268],
+                  [ 0.      ,  1.277617, -0.370268],
+                  [ 0.      , -1.277617, -0.370268]]},
+'BF3': {
+    'description': "BF3, Planar D3h symm.",
+    'name': "BF_3",
+    'enthalpy': -271.4,
+    'ZPE': 7.8257,
+    'thermal correction': 2.7893,
+    'symbols': 'BFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  1.32176 ,  0.      ],
+                  [ 1.144678, -0.66088 ,  0.      ],
+                  [-1.144678, -0.66088 ,  0.      ]]},
+'BCl3': {
+    'description': "BCl3, Planar D3h symm.",
+    'name': "BCl_3",
+    'enthalpy': -96.3,
+    'ZPE': 4.6536,
+    'thermal correction': 3.3729,
+    'symbols': 'BClClCl',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  1.735352,  0.      ],
+                  [ 1.502859, -0.867676,  0.      ],
+                  [-1.502859, -0.867676,  0.      ]]},
+'AlF3': {
+    'description': "AlF3, Planar D3h symm.",
+    'name': "AlF_3",
+    'enthalpy': -289.0,
+    'ZPE': 4.8645,
+    'thermal correction': 3.3986,
+    'symbols': 'AlFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  1.64472 ,  0.      ],
+                  [ 1.424369, -0.82236 ,  0.      ],
+                  [-1.424369, -0.82236 ,  0.      ]]},
+'AlCl3': {
+    'description': "AlCl3, Planar D3h symm.",
+    'name': "AlCl_3",
+    'enthalpy': -139.7,
+    'ZPE': 2.9687,
+    'thermal correction': 3.9464,
+    'symbols': 'AlClClCl',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  2.069041,  0.      ],
+                  [ 1.791842, -1.03452 ,  0.      ],
+                  [-1.791842, -1.03452 ,  0.      ]]},
+'CF4': {
+    'description': "CF4, Td symm.",
+    'name': "CF_4",
+    'enthalpy': -223.0,
+    'ZPE': 10.5999,
+    'thermal correction': 3.0717,
+    'symbols': 'CFFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.767436,  0.767436,  0.767436],
+                  [-0.767436, -0.767436,  0.767436],
+                  [-0.767436,  0.767436, -0.767436],
+                  [ 0.767436, -0.767436, -0.767436]]},
+'CCl4': {
+    'description': "CCl4, Td symm.",
+    'name': "CCl_4",
+    'enthalpy': -22.9,
+    'ZPE': 5.7455,
+    'thermal correction': 4.1754,
+    'symbols': 'CClClClCl',
+    'magmoms': None,
+    'positions': [[ 0.     ,  0.     ,  0.     ],
+                  [ 1.02134,  1.02134,  1.02134],
+                  [-1.02134, -1.02134,  1.02134],
+                  [-1.02134,  1.02134, -1.02134],
+                  [ 1.02134, -1.02134, -1.02134]]},
+'OCS': {
+    'description': "O=C=S, Linear, C*v symm.",
+    'name': "COS",
+    'enthalpy': -33.1,
+    'ZPE': 5.7706,
+    'thermal correction': 2.3663,
+    'symbols': 'OCS',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.699243],
+                  [ 0.      ,  0.      , -0.520492],
+                  [ 0.      ,  0.      ,  1.044806]]},
+'CS2': {
+    'description': "CS2, Linear, D*h symm.",
+    'name': "CS_2",
+    'enthalpy': 28.0,
+    'ZPE': 4.3380,
+    'thermal correction': 2.5326,
+    'symbols': 'SCS',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.561117],
+                  [ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  0.      , -1.561117]]},
+'COF2': {
+    'description': "COF2, C2v symm.",
+    'name': "COF_2",
+    'enthalpy': -149.1,
+    'ZPE': 8.8215,
+    'thermal correction': 2.6619,
+    'symbols': 'OCFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.330715],
+                  [ 0.      ,  0.      ,  0.144358],
+                  [ 0.      ,  1.06949 , -0.639548],
+                  [ 0.      , -1.06949 , -0.639548]]},
+'SiF4': {
+    'description': "SiF4, Td symm.",
+    'name': "SiF_4",
+    'enthalpy': -386.0,
+    'ZPE': 7.8771,
+    'thermal correction': 3.7054,
+    'symbols': 'SiFFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.912806,  0.912806,  0.912806],
+                  [-0.912806, -0.912806,  0.912806],
+                  [-0.912806,  0.912806, -0.912806],
+                  [ 0.912806, -0.912806, -0.912806]]},
+'SiCl4': {
+    'description': "SiCl4, Td symm.",
+    'name': "SiCl_4",
+    'enthalpy': -158.4,
+    'ZPE': 4.4396,
+    'thermal correction': 4.7182,
+    'symbols': 'SiClClClCl',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 1.169349,  1.169349,  1.169349],
+                  [-1.169349, -1.169349,  1.169349],
+                  [ 1.169349, -1.169349, -1.169349],
+                  [-1.169349,  1.169349, -1.169349]]},
+'N2O': {
+    'description': "N2O, Cs symm.",
+    'name': "N_2O",
+    'enthalpy': 19.6,
+    'ZPE': 6.9748,
+    'thermal correction': 2.2710,
+    'symbols': 'NNO',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.231969],
+                  [ 0.      ,  0.      , -0.060851],
+                  [ 0.      ,  0.      ,  1.131218]]},
+'ClNO': {
+    'description': "ClNO, Cs symm.",
+    'name': "ClNO",
+    'enthalpy': 12.4,
+    'ZPE': 4.0619,
+    'thermal correction': 2.7039,
+    'symbols': 'ClNO',
+    'magmoms': None,
+    'positions': [[-0.537724, -0.961291,  0.      ],
+                  [ 0.      ,  0.997037,  0.      ],
+                  [ 1.142664,  1.170335,  0.      ]]},
+'NF3': {
+    'description': "NF3, C3v symm.",
+    'name': "NF_3",
+    'enthalpy': -31.6,
+    'ZPE': 6.4477,
+    'thermal correction': 2.8301,
+    'symbols': 'NFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.489672],
+                  [ 0.      ,  1.238218, -0.126952],
+                  [ 1.072328, -0.619109, -0.126952],
+                  [-1.072328, -0.619109, -0.126952]]},
+'PF3': {
+    'description': "PF3, C3v symm.",
+    'name': "PF_3",
+    'enthalpy': -229.1,
+    'ZPE': 5.2981,
+    'thermal correction': 3.1288,
+    'symbols': 'PFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.506767],
+                  [ 0.      ,  1.383861, -0.281537],
+                  [ 1.198459, -0.691931, -0.281537],
+                  [-1.198459, -0.691931, -0.281537]]},
+'O3': {
+    'description': "O3 (Ozone), C2v symm.",
+    'name': "O_3",
+    'enthalpy': 34.1,
+    'ZPE': 4.6178,
+    'thermal correction': 2.4479,
+    'symbols': 'OOO',
+    'magmoms': None,
+    'positions': [[ 0.      ,  1.10381 , -0.228542],
+                  [ 0.      ,  0.      ,  0.457084],
+                  [ 0.      , -1.10381 , -0.228542]]},
+'F2O': {
+    'description': "F2O, C2v symm.",
+    'name': "F_2O",
+    'enthalpy': 5.9,
+    'ZPE': 3.4362,
+    'thermal correction': 2.5747,
+    'symbols': 'FOF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  1.110576, -0.273729],
+                  [ 0.      ,  0.      ,  0.61589 ],
+                  [ 0.      , -1.110576, -0.273729]]},
+'ClF3': {
+    'description': "ClF3, C2v symm.",
+    'name': "ClF_3",
+    'enthalpy': -38.0,
+    'ZPE': 4.2922,
+    'thermal correction': 3.3289,
+    'symbols': 'ClFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.376796],
+                  [ 0.      ,  0.      , -1.258346],
+                  [ 0.      ,  1.714544,  0.27331 ],
+                  [ 0.      , -1.714544,  0.27331 ]]},
+'C2F4': {
+    'description': "C2F4 (F2C=CF2), D2H symm.",
+    'name': "C_2F_4",
+    'enthalpy': -157.4,
+    'ZPE': 13.4118,
+    'thermal correction': 3.9037,
+    'symbols': 'CCFFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.66323 ],
+                  [ 0.      ,  0.      , -0.66323 ],
+                  [ 0.      ,  1.112665,  1.385652],
+                  [ 0.      , -1.112665,  1.385652],
+                  [ 0.      ,  1.112665, -1.385652],
+                  [ 0.      , -1.112665, -1.385652]]},
+'C2Cl4': {
+    'description': "C2Cl4 (Cl2C=CCl2), D2h symm.",
+    'name': "C_2Cl_4",
+    'enthalpy': -3.0,
+    'ZPE': 9.4628,
+    'thermal correction': 4.7132,
+    'symbols': 'CCClClClCl',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.675402],
+                  [ 0.      ,  0.      , -0.675402],
+                  [ 0.      ,  1.448939,  1.589701],
+                  [ 0.      , -1.448939,  1.589701],
+                  [ 0.      , -1.448939, -1.589701],
+                  [ 0.      ,  1.448939, -1.589701]]},
+'CF3CN': {
+    'description': "CF3CN, C3v symm.",
+    'name': "CF_3CN",
+    'enthalpy': -118.4,
+    'ZPE': 14.1020,
+    'thermal correction': 3.7996,
+    'symbols': 'CCFFFN',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -0.32635 ],
+                  [ 0.      ,  0.      ,  1.15083 ],
+                  [ 0.      ,  1.257579, -0.787225],
+                  [ 1.089096, -0.62879 , -0.787225],
+                  [-1.089096, -0.62879 , -0.787225],
+                  [ 0.      ,  0.      ,  2.329741]]},
+'C3H4_C3v': {
+    'description': "Propyne (C3H4), C3v symm.",
+    'name': "CH_3CCH (propyne)",
+    'enthalpy': 44.2,
+    'ZPE': 34.2614,
+    'thermal correction': 3.1193,
+    'symbols': 'CCCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.214947],
+                  [ 0.      ,  0.      ,  1.43313 ],
+                  [ 0.      ,  0.      , -1.246476],
+                  [ 0.      ,  0.      ,  2.498887],
+                  [ 0.      ,  1.021145, -1.636167],
+                  [ 0.884337, -0.510572, -1.636167],
+                  [-0.884337, -0.510572, -1.636167]]},
+'C3H4_D2d': {
+    'description': "Allene (C3H4), D2d symm.",
+    'name': "CH_2=C=CH_2 (allene)",
+    'enthalpy': 45.5,
+    'ZPE': 34.1189,
+    'thermal correction': 2.9744,
+    'symbols': 'CCCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  0.      ,  1.31119 ],
+                  [ 0.      ,  0.      , -1.31119 ],
+                  [ 0.      ,  0.926778,  1.876642],
+                  [ 0.      , -0.926778,  1.876642],
+                  [ 0.926778,  0.      , -1.876642],
+                  [-0.926778,  0.      , -1.876642]]},
+'C3H4_C2v': {
+    'description': "Cyclopropene (C3H4), C2v symm.",
+    'name': "C_3H_4 (cyclopropene)",
+    'enthalpy': 66.2,
+    'ZPE': 34.7603,
+    'thermal correction': 2.6763,
+    'symbols': 'CCCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.858299],
+                  [ 0.      , -0.650545, -0.498802],
+                  [ 0.      ,  0.650545, -0.498802],
+                  [ 0.912438,  0.      ,  1.456387],
+                  [-0.912438,  0.      ,  1.456387],
+                  [ 0.      , -1.584098, -1.038469],
+                  [ 0.      ,  1.584098, -1.038469]]},
+'C3H6_Cs': {
+    'description': "Propene (C3H6), Cs symm.",
+    'name': "CH_3CH=CH_2 (propylene)",
+    'enthalpy': 4.8,
+    'ZPE': 49.1836,
+    'thermal correction': 3.1727,
+    'symbols': 'CCHHHCHHH',
+    'magmoms': None,
+    'positions': [[ 1.29129 ,  0.133682,  0.      ],
+                  [ 0.      ,  0.479159,  0.      ],
+                  [ 1.60116 , -0.90742 ,  0.      ],
+                  [ 2.0808  ,  0.877337,  0.      ],
+                  [-0.263221,  1.536098,  0.      ],
+                  [-1.139757, -0.492341,  0.      ],
+                  [-0.776859, -1.523291,  0.      ],
+                  [-1.77554 , -0.352861,  0.88042 ],
+                  [-1.77554 , -0.352861, -0.88042 ]]},
+'C3H6_D3h': {
+    'description': "Cyclopropane (C3H6), D3h symm.",
+    'name': "C_3H_6 (cyclopropane)",
+    'enthalpy': 12.7,
+    'ZPE': 50.2121,
+    'thermal correction': 2.7272,
+    'symbols': 'CCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.866998,  0.      ],
+                  [ 0.750842, -0.433499,  0.      ],
+                  [-0.750842, -0.433499,  0.      ],
+                  [ 0.      ,  1.455762,  0.910526],
+                  [ 0.      ,  1.455762, -0.910526],
+                  [ 1.260727, -0.727881, -0.910526],
+                  [ 1.260727, -0.727881,  0.910526],
+                  [-1.260727, -0.727881,  0.910526],
+                  [-1.260727, -0.727881, -0.910526]]},
+'C3H8': {
+    'description': "Propane (C3H8), C2v symm.",
+    'name': "C_3H_8 (propane)",
+    'enthalpy': -25.0,
+    'ZPE': 63.8008,
+    'thermal correction': 3.4632,
+    'symbols': 'CCCHHHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.587716],
+                  [ 0.      ,  1.266857, -0.260186],
+                  [ 0.      , -1.266857, -0.260186],
+                  [-0.876898,  0.      ,  1.244713],
+                  [ 0.876898,  0.      ,  1.244713],
+                  [ 0.      ,  2.16615 ,  0.362066],
+                  [ 0.      , -2.16615 ,  0.362066],
+                  [ 0.883619,  1.304234, -0.904405],
+                  [-0.883619,  1.304234, -0.904405],
+                  [-0.883619, -1.304234, -0.904405],
+                  [ 0.883619, -1.304234, -0.904405]]},
+'butadiene': {
+    'description': "Trans-1,3-butadiene (C4H6), C2h symm.",
+    'name': "CH_2CHCHCH_2 (butadiene)",
+    'enthalpy': 26.3,
+    'ZPE': 52.6273,
+    'thermal correction': 3.5341,
+    'symbols': 'CCCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.605711,  1.74655 ,  0.      ],
+                  [ 0.605711,  0.404083,  0.      ],
+                  [-0.605711, -0.404083,  0.      ],
+                  [-0.605711, -1.74655 ,  0.      ],
+                  [ 1.527617,  2.317443,  0.      ],
+                  [-0.321132,  2.313116,  0.      ],
+                  [ 1.553503, -0.13364 ,  0.      ],
+                  [-1.553503,  0.13364 ,  0.      ],
+                  [ 0.321132, -2.313116,  0.      ],
+                  [-1.527617, -2.317443,  0.      ]]},
+'2-butyne': {
+    'description': "Dimethylacetylene (2-butyne, C4H6), D3h symm (eclipsed).",
+    'name': "C_4H_6 (2-butyne)",
+    'enthalpy': 34.8,
+    'ZPE': 51.8731,
+    'thermal correction': 4.2344,
+    'symbols': 'CCCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  2.071955],
+                  [ 0.      ,  0.      ,  0.60997 ],
+                  [ 0.      ,  0.      , -0.60997 ],
+                  [ 0.      ,  0.      , -2.071955],
+                  [ 0.      ,  1.020696,  2.464562],
+                  [-0.883949, -0.510348,  2.464562],
+                  [ 0.883949, -0.510348,  2.464562],
+                  [ 0.      ,  1.020696, -2.464562],
+                  [ 0.883949, -0.510348, -2.464562],
+                  [-0.883949, -0.510348, -2.464562]]},
+'methylenecyclopropane': {
+    'description': "Methylenecyclopropane (C4H6), C2v symm.",
+    'name': "C_4H_6 (methylene cyclopropane)",
+    'enthalpy': 47.9,
+    'ZPE': 52.6230,
+    'thermal correction': 3.2881,
+    'symbols': 'CCCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.315026],
+                  [ 0.      , -0.76792 , -0.932032],
+                  [ 0.      ,  0.76792 , -0.932032],
+                  [ 0.      ,  0.      ,  1.640027],
+                  [-0.912794, -1.271789, -1.239303],
+                  [ 0.912794, -1.271789, -1.239303],
+                  [ 0.912794,  1.271789, -1.239303],
+                  [-0.912794,  1.271789, -1.239303],
+                  [ 0.      , -0.926908,  2.20564 ],
+                  [ 0.      ,  0.926908,  2.20564 ]]},
+'bicyclobutane': {
+    'description': "Bicyclo[1.1.0]butane (C4H6), C2v symm.",
+    'name': "C_4H_6 (bicyclobutane)",
+    'enthalpy': 51.9,
+    'ZPE': 53.3527,
+    'thermal correction': 2.9637,
+    'symbols': 'CCCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  1.131343,  0.310424],
+                  [ 0.      , -1.131343,  0.310424],
+                  [ 0.747952,  0.      , -0.311812],
+                  [-0.747952,  0.      , -0.311812],
+                  [ 0.      ,  1.237033,  1.397617],
+                  [ 0.      ,  2.077375, -0.227668],
+                  [ 0.      , -1.237033,  1.397617],
+                  [ 0.      , -2.077375, -0.227668],
+                  [ 1.41441 ,  0.      , -1.161626],
+                  [-1.41441 ,  0.      , -1.161626]]},
+'cyclobutene': {
+    'description': "Cyclobutene (C4H6), C2v symm.",
+    'name': "C_4H_6 (cyclobutene)",
+    'enthalpy': 37.4,
+    'ZPE': 53.4105,
+    'thermal correction': 3.0108,
+    'symbols': 'CCCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      , -0.672762,  0.811217],
+                  [ 0.      ,  0.672762,  0.811217],
+                  [ 0.      , -0.78198 , -0.696648],
+                  [ 0.      ,  0.78198 , -0.696648],
+                  [ 0.      , -1.422393,  1.597763],
+                  [ 0.      ,  1.422393,  1.597763],
+                  [-0.88931 , -1.239242, -1.142591],
+                  [ 0.88931 , -1.239242, -1.142591],
+                  [ 0.88931 ,  1.239242, -1.142591],
+                  [-0.88931 ,  1.239242, -1.142591]]},
+'cyclobutane': {
+    'description': "Cyclobutane (C4H8), D2d symm.",
+    'name': "C_4H_8 (cyclobutane)",
+    'enthalpy': 6.8,
+    'ZPE': 68.3314,
+    'thermal correction': 3.2310,
+    'symbols': 'CCCCHHHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  1.071142,  0.147626],
+                  [ 0.      , -1.071142,  0.147626],
+                  [-1.071142,  0.      , -0.147626],
+                  [ 1.071142,  0.      , -0.147626],
+                  [ 0.      ,  1.986858, -0.450077],
+                  [ 0.      ,  1.342921,  1.20752 ],
+                  [ 0.      , -1.986858, -0.450077],
+                  [ 0.      , -1.342921,  1.20752 ],
+                  [-1.986858,  0.      ,  0.450077],
+                  [-1.342921,  0.      , -1.20752 ],
+                  [ 1.986858,  0.      ,  0.450077],
+                  [ 1.342921,  0.      , -1.20752 ]]},
+'isobutene': {
+    'description': "Isobutene (C4H8), Single bonds trans, C2v symm.",
+    'name': "C_4H_8 (isobutene)",
+    'enthalpy': -4.0,
+    'ZPE': 66.5693,
+    'thermal correction': 3.9495,
+    'symbols': 'CCHHCHHHCHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.458807],
+                  [ 0.      ,  0.      ,  0.119588],
+                  [ 0.      ,  0.924302,  2.028409],
+                  [ 0.      , -0.924302,  2.028409],
+                  [ 0.      ,  1.272683, -0.678803],
+                  [ 0.      ,  2.153042, -0.031588],
+                  [ 0.880211,  1.323542, -1.329592],
+                  [-0.880211,  1.323542, -1.329592],
+                  [ 0.      , -1.272683, -0.678803],
+                  [ 0.      , -2.153042, -0.031588],
+                  [-0.880211, -1.323542, -1.329592],
+                  [ 0.880211, -1.323542, -1.329592]]},
+'trans-butane': {
+    'description': "Trans-butane (C4H10), C2h symm.",
+    'name': "C_4H_{10} (trans butane)",
+    'enthalpy': -30.0,
+    'ZPE': 81.3980,
+    'thermal correction': 4.2633,
+    'symbols': 'CCCCHHHHHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.702581,  1.820873,  0.      ],
+                  [ 0.702581,  0.296325,  0.      ],
+                  [-0.702581, -0.296325,  0.      ],
+                  [-0.702581, -1.820873,  0.      ],
+                  [ 1.719809,  2.22234 ,  0.      ],
+                  [-1.719809, -2.22234 ,  0.      ],
+                  [ 0.188154,  2.210362,  0.883614],
+                  [ 0.188154,  2.210362, -0.883614],
+                  [-0.188154, -2.210362,  0.883614],
+                  [-0.188154, -2.210362, -0.883614],
+                  [ 1.247707, -0.07266 , -0.877569],
+                  [ 1.247707, -0.07266 ,  0.877569],
+                  [-1.247707,  0.07266 , -0.877569],
+                  [-1.247707,  0.07266 ,  0.877569]]},
+'isobutane': {
+    'description': "Isobutane (C4H10), C3v symm.",
+    'name': "C_4H_{10} (isobutane)",
+    'enthalpy': -32.1,
+    'ZPE': 81.1050,
+    'thermal correction': 4.2282,
+    'symbols': 'CHCHHHCHHHCHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.376949],
+                  [ 0.      ,  0.      ,  1.475269],
+                  [ 0.      ,  1.45029 , -0.096234],
+                  [ 0.      ,  1.493997, -1.190847],
+                  [-0.885482,  1.984695,  0.261297],
+                  [ 0.885482,  1.984695,  0.261297],
+                  [ 1.255988, -0.725145, -0.096234],
+                  [ 1.293839, -0.746998, -1.190847],
+                  [ 2.161537, -0.225498,  0.261297],
+                  [ 1.276055, -1.759198,  0.261297],
+                  [-1.255988, -0.725145, -0.096234],
+                  [-1.293839, -0.746998, -1.190847],
+                  [-1.276055, -1.759198,  0.261297],
+                  [-2.161537, -0.225498,  0.261297]]},
+'C5H8': {
+    'description': "Spiropentane (C5H8), D2d symm.",
+    'name': "C_5H_8 (spiropentane)",
+    'enthalpy': 44.3,
+    'ZPE': 70.9964,
+    'thermal correction': 3.7149,
+    'symbols': 'CCCCCHHHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.      ],
+                  [ 0.      ,  0.762014,  1.265752],
+                  [ 0.      , -0.762014,  1.265752],
+                  [ 0.762014,  0.      , -1.265752],
+                  [-0.762014,  0.      , -1.265752],
+                  [-0.914023,  1.265075,  1.56809 ],
+                  [ 0.914023,  1.265075,  1.56809 ],
+                  [-0.914023, -1.265075,  1.56809 ],
+                  [ 0.914023, -1.265075,  1.56809 ],
+                  [ 1.265075, -0.914023, -1.56809 ],
+                  [ 1.265075,  0.914023, -1.56809 ],
+                  [-1.265075, -0.914023, -1.56809 ],
+                  [-1.265075,  0.914023, -1.56809 ]]},
+'C6H6': {
+    'description': "Benzene (C6H6), D6h symm.",
+    'name': "C_6H_6 (benzene)",
+    'enthalpy': 19.7,
+    'ZPE': 61.9252,
+    'thermal correction': 3.3886,
+    'ionization energy': 9.24,
+    'vertical ionization energy': 9.25,
+    'symbols': 'CCCCCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  1.395248,  0.      ],
+                  [ 1.20832 ,  0.697624,  0.      ],
+                  [ 1.20832 , -0.697624,  0.      ],
+                  [ 0.      , -1.395248,  0.      ],
+                  [-1.20832 , -0.697624,  0.      ],
+                  [-1.20832 ,  0.697624,  0.      ],
+                  [ 0.      ,  2.48236 ,  0.      ],
+                  [ 2.149787,  1.24118 ,  0.      ],
+                  [ 2.149787, -1.24118 ,  0.      ],
+                  [ 0.      , -2.48236 ,  0.      ],
+                  [-2.149787, -1.24118 ,  0.      ],
+                  [-2.149787,  1.24118 ,  0.      ]]},
+'H2CF2': {
+    'description': "Difluoromethane (H2CF2), C2v symm.",
+    'name': "CH_2F_2",
+    'enthalpy': -107.7,
+    'ZPE': 20.2767,
+    'thermal correction': 2.5552,
+    'symbols': 'CFFHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.502903],
+                  [ 0.      ,  1.109716, -0.290601],
+                  [ 0.      , -1.109716, -0.290601],
+                  [-0.908369,  0.      ,  1.106699],
+                  [ 0.908369,  0.      ,  1.106699]]},
+'HCF3': {
+    'description': "Trifluoromethane (HCF3), C3v symm.",
+    'name': "CHF_3",
+    'enthalpy': -166.6,
+    'ZPE': 15.7072,
+    'thermal correction': 2.7717,
+    'symbols': 'CHFFF',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.341023],
+                  [ 0.      ,  0.      ,  1.429485],
+                  [ 0.      ,  1.2582  , -0.128727],
+                  [ 1.089633, -0.6291  , -0.128727],
+                  [-1.089633, -0.6291  , -0.128727]]},
+'H2CCl2': {
+    'description': "Dichloromethane (H2CCl2), C2v symm.",
+    'name': "CH_2Cl_2",
+    'enthalpy': -22.8,
+    'ZPE': 18.0930,
+    'thermal correction': 2.8527,
+    'symbols': 'CClClHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.759945],
+                  [ 0.      ,  1.4742  , -0.215115],
+                  [ 0.      , -1.4742  , -0.215115],
+                  [-0.894585,  0.      ,  1.377127],
+                  [ 0.894585,  0.      ,  1.377127]]},
+'HCCl3': {
+    'description': "Chloroform (HCCl3), C3v symm.",
+    'name': "CHCl_3",
+    'enthalpy': -24.7,
+    'ZPE': 12.1975,
+    'thermal correction': 3.4262,
+    'symbols': 'CHClClCl',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.451679],
+                  [ 0.      ,  0.      ,  1.537586],
+                  [ 0.      ,  1.681723, -0.083287],
+                  [ 1.456415, -0.840862, -0.083287],
+                  [-1.456415, -0.840862, -0.083287]]},
+'H3CNH2': {
+    'description': "Methylamine (H3C-NH2), Cs symm.",
+    'name': "CH_3NH_2 (methylamine)",
+    'enthalpy': -5.5,
+    'ZPE': 39.5595,
+    'thermal correction': 2.7428,
+    'symbols': 'CNHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.051736,  0.704422,  0.      ],
+                  [ 0.051736, -0.759616,  0.      ],
+                  [-0.941735,  1.176192,  0.      ],
+                  [-0.458181, -1.099433,  0.81237 ],
+                  [-0.458181, -1.099433, -0.81237 ],
+                  [ 0.592763,  1.056727,  0.88067 ],
+                  [ 0.592763,  1.056727, -0.88067 ]]},
+'CH3CN': {
+    'description': "Acetonitrile (CH3-CN), C3v symm.",
+    'name': "CH_3CN (methyl cyanide)",
+    'enthalpy': 18.0,
+    'ZPE': 28.0001,
+    'thermal correction': 2.8552,
+    'symbols': 'CCNHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.18693 ],
+                  [ 0.      ,  0.      ,  0.273874],
+                  [ 0.      ,  0.      ,  1.452206],
+                  [ 0.      ,  1.024986, -1.56237 ],
+                  [ 0.887664, -0.512493, -1.56237 ],
+                  [-0.887664, -0.512493, -1.56237 ]]},
+'CH3NO2': {
+    'description': "Nitromethane (CH3-NO2), Cs symm.",
+    'name': "CH_3NO_2 (nitromethane)",
+    'enthalpy': -17.8,
+    'ZPE': 30.7568,
+    'thermal correction': 2.7887,
+    'symbols': 'CNHHHOO',
+    'magmoms': None,
+    'positions': [[-0.114282, -1.314565,  0.      ],
+                  [ 0.      ,  0.16648 ,  0.      ],
+                  [ 0.899565, -1.715256,  0.      ],
+                  [-0.640921, -1.607212,  0.904956],
+                  [-0.640921, -1.607212, -0.904956],
+                  [ 0.066748,  0.728232, -1.103775],
+                  [ 0.066748,  0.728232,  1.103775]]},
+'CH3ONO': {
+    'description': "Methylnitrite (CH3-O-N=O), NOCH trans, ONOC cis, Cs symm.",
+    'name': "CH_3ONO (methyl nitrite)",
+    'enthalpy': -15.9,
+    'ZPE': 29.9523,
+    'thermal correction': 3.3641,
+    'symbols': 'COHHHNO',
+    'magmoms': None,
+    'positions': [[-1.316208,  0.309247,  0.      ],
+                  [ 0.      ,  0.896852,  0.      ],
+                  [-1.985538,  1.166013,  0.      ],
+                  [-1.464336, -0.304637,  0.890672],
+                  [-1.464336, -0.304637, -0.890672],
+                  [ 1.045334, -0.022815,  0.      ],
+                  [ 0.686764, -1.178416,  0.      ]]},
+'CH3SiH3': {
+    'description': "Methylsilane (CH3-SiH3), C3v symm.",
+    'name': "CH_3SiH_3 (methyl silane)",
+    'enthalpy': -7.0,
+    'ZPE': 37.6606,
+    'thermal correction': 3.2486,
+    'symbols': 'CSiHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.244466],
+                  [ 0.      ,  0.      ,  0.635703],
+                  [ 0.      , -1.019762, -1.636363],
+                  [-0.88314 ,  0.509881, -1.636363],
+                  [ 0.88314 ,  0.509881, -1.636363],
+                  [ 0.      ,  1.391234,  1.158682],
+                  [-1.204844, -0.695617,  1.158682],
+                  [ 1.204844, -0.695617,  1.158682]]},
+'HCOOH': {
+    'description': "Formic Acid (HCOOH), HOCO cis, Cs symm.",
+    'name': "HCOOH (formic acid)",
+    'enthalpy': -90.5,
+    'ZPE': 20.9525,
+    'thermal correction': 2.5853,
+    'symbols': 'OCOHH',
+    'magmoms': None,
+    'positions': [[-1.040945, -0.436432,  0.      ],
+                  [ 0.      ,  0.423949,  0.      ],
+                  [ 1.169372,  0.103741,  0.      ],
+                  [-0.64957 , -1.335134,  0.      ],
+                  [-0.377847,  1.452967,  0.      ]]},
+'HCOOCH3': {
+    'description': "Methyl formate (HCOOCH3), Cs symm.",
+    'name': "HCOOCH_3 (methyl formate)",
+    'enthalpy': -85.0,
+    'ZPE': 38.3026,
+    'thermal correction': 3.4726,
+    'symbols': 'COOHCHHH',
+    'magmoms': None,
+    'positions': [[-0.931209, -0.083866,  0.      ],
+                  [-0.711019, -1.278209,  0.      ],
+                  [ 0.      ,  0.886841,  0.      ],
+                  [-1.92836 ,  0.374598,  0.      ],
+                  [ 1.356899,  0.397287,  0.      ],
+                  [ 1.980134,  1.288164,  0.      ],
+                  [ 1.541121, -0.206172,  0.889397],
+                  [ 1.541121, -0.206172, -0.889397]]},
+'CH3CONH2': {
+    'description': "Acetamide (CH3CONH2), C1 symm.",
+    'name': "CH_3CONH_2 (acetamide)",
+    'enthalpy': -57.0,
+    'ZPE': 45.2566,
+    'thermal correction': 3.9313,
+    'symbols': 'OCNCHHHHH',
+    'magmoms': None,
+    'positions': [[  4.24546000e-01,   1.32702400e+00,   8.03400000e-03],
+                  [  7.71580000e-02,   1.49789000e-01,  -4.24900000e-03],
+                  [  9.85518000e-01,  -8.78537000e-01,  -4.89100000e-02],
+                  [ -1.37147500e+00,  -2.88665000e-01,  -1.44000000e-04],
+                  [  7.07952000e-01,  -1.82424900e+00,   1.69942000e-01],
+                  [ -1.99722900e+00,   5.84922000e-01,  -1.75477000e-01],
+                  [ -1.56084200e+00,  -1.03927000e+00,  -7.71686000e-01],
+                  [ -1.63211300e+00,  -7.23007000e-01,   9.69814000e-01],
+                  [  1.95313300e+00,  -6.31574000e-01,   1.11866000e-01]]},
+'CH2NHCH2': {
+    'description': "Aziridine (cyclic CH2-NH-CH2 ring), C2v symm.",
+    'name': "C_2H_4NH (aziridine)",
+    'enthalpy': 30.2,
+    'ZPE': 43.3728,
+    'thermal correction': 2.6399,
+    'symbols': 'CNCHHHHH',
+    'magmoms': None,
+    'positions': [[-0.03845 , -0.397326,  0.739421],
+                  [-0.03845 ,  0.875189,  0.      ],
+                  [-0.03845 , -0.397326, -0.739421],
+                  [ 0.903052,  1.268239,  0.      ],
+                  [-0.955661, -0.604926,  1.280047],
+                  [-0.955661, -0.604926, -1.280047],
+                  [ 0.869409, -0.708399,  1.249033],
+                  [ 0.869409, -0.708399, -1.249033]]},
+'NCCN': {
+    'description': "Cyanogen (NCCN). D*h symm.",
+    'name': "NCCN (cyanogen)",
+    'enthalpy': 73.3,
+    'ZPE': 10.2315,
+    'thermal correction': 2.9336,
+    'symbols': 'NCCN',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.875875],
+                  [ 0.      ,  0.      ,  0.690573],
+                  [ 0.      ,  0.      , -0.690573],
+                  [ 0.      ,  0.      , -1.875875]]},
+'C2H6NH': {
+    'description': "Dimethylamine, (CH3)2NH, Cs symm.",
+    'name': "(CH_3)_2NH (dimethylamine)",
+    'enthalpy': -4.4,
+    'ZPE': 57.0287,
+    'thermal correction': 3.3760,
+    'symbols': 'CNCHHHHHHH',
+    'magmoms': None,
+    'positions': [[-0.02753 , -0.224702,  1.20488 ],
+                  [-0.02753 ,  0.59247 ,  0.      ],
+                  [-0.02753 , -0.224702, -1.20488 ],
+                  [ 0.791501, -0.962742,  1.248506],
+                  [ 0.039598,  0.421182,  2.083405],
+                  [-0.97222 , -0.772987,  1.26175 ],
+                  [ 0.805303,  1.17822 ,  0.      ],
+                  [ 0.791501, -0.962742, -1.248506],
+                  [ 0.039598,  0.421182, -2.083405],
+                  [-0.97222 , -0.772987, -1.26175 ]]},
+'CH3CH2NH2': {
+    'description': "Trans-Ethylamine (CH3-CH2-NH2), Cs symm.",
+    'name': "CH_3CH_2NH_2 (trans ethylamine)",
+    'enthalpy': -11.3,
+    'ZPE': 57.2420,
+    'thermal correction': 3.3678,
+    'symbols': 'CCNHHHHHHH',
+    'magmoms': None,
+    'positions': [[ 1.210014, -0.353598,  0.      ],
+                  [ 0.      ,  0.575951,  0.      ],
+                  [-1.305351, -0.087478,  0.      ],
+                  [ 2.14931 ,  0.208498,  0.      ],
+                  [ 1.201796, -0.99776 ,  0.884909],
+                  [ 1.201796, -0.99776 , -0.884909],
+                  [ 0.034561,  1.230963, -0.876478],
+                  [ 0.034561,  1.230963,  0.876478],
+                  [-1.372326, -0.69834 ,  0.813132],
+                  [-1.372326, -0.69834 , -0.813132]]},
+'H2CCO': {
+    'description': "Ketene (H2C=C=O), C2v symm.",
+    'name': "CH_2CO (ketene)",
+    'enthalpy': -11.4,
+    'ZPE': 19.5984,
+    'thermal correction': 2.8075,
+    'symbols': 'CCHHO',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      , -1.21934 ],
+                  [ 0.      ,  0.      ,  0.09892 ],
+                  [ 0.      ,  0.938847, -1.753224],
+                  [ 0.      , -0.938847, -1.753224],
+                  [ 0.      ,  0.      ,  1.27862 ]]},
+'CH2OCH2': {
+    'description': "Oxirane (cyclic CH2-O-CH2 ring), C2v symm.",
+    'name': "C_2H_4O (oxirane)",
+    'enthalpy': -12.6,
+    'ZPE': 35.4204,
+    'thermal correction': 2.5816,
+    'symbols': 'COCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.73158 , -0.375674],
+                  [ 0.      ,  0.      ,  0.86095 ],
+                  [ 0.      , -0.73158 , -0.375674],
+                  [ 0.919568,  1.268821, -0.594878],
+                  [-0.919568,  1.268821, -0.594878],
+                  [-0.919568, -1.268821, -0.594878],
+                  [ 0.919568, -1.268821, -0.594878]]},
+'CH3CHO': {
+    'description': "Acetaldehyde (CH3CHO), Cs symm.",
+    'name': "CH_3CHO (acetaldehyde)",
+    'enthalpy': -39.7,
+    'ZPE': 34.2288,
+    'thermal correction': 3.0428,
+    'symbols': 'OCHCHHH',
+    'magmoms': None,
+    'positions': [[ 1.218055,  0.36124 ,  0.      ],
+                  [ 0.      ,  0.464133,  0.      ],
+                  [-0.477241,  1.465295,  0.      ],
+                  [-0.948102, -0.700138,  0.      ],
+                  [-0.385946, -1.634236,  0.      ],
+                  [-1.596321, -0.652475,  0.880946],
+                  [-1.596321, -0.652475, -0.880946]]},
+'OCHCHO': {
+    'description': "Glyoxal (O=CH-CH=O). Trans, C2h symm.",
+    'name': "HCOCOH (glyoxal)",
+    'enthalpy': -50.7,
+    'ZPE': 22.8426,
+    'thermal correction': 3.2518,
+    'symbols': 'CCOHOH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.75643 ,  0.      ],
+                  [ 0.      , -0.75643 ,  0.      ],
+                  [ 1.04609 ,  1.389916,  0.      ],
+                  [-0.99994 ,  1.228191,  0.      ],
+                  [-1.04609 , -1.389916,  0.      ],
+                  [ 0.99994 , -1.228191,  0.      ]]},
+'CH3CH2OH': {
+    'description': "Ethanol (trans, CH3CH2OH), Cs symm.",
+    'name': "CH_3CH_2OH (ethanol)",
+    'enthalpy': -56.2,
+    'ZPE': 49.3072,
+    'thermal correction': 3.3252,
+    'symbols': 'CCOHHHHHH',
+    'magmoms': None,
+    'positions': [[ 1.168181, -0.400382,  0.      ],
+                  [ 0.      ,  0.559462,  0.      ],
+                  [-1.190083, -0.227669,  0.      ],
+                  [-1.946623,  0.381525,  0.      ],
+                  [ 0.042557,  1.207508,  0.886933],
+                  [ 0.042557,  1.207508, -0.886933],
+                  [ 2.115891,  0.1448  ,  0.      ],
+                  [ 1.128599, -1.037234,  0.885881],
+                  [ 1.128599, -1.037234, -0.885881]]},
+'CH3OCH3': {
+    'description': "DimethylEther (CH3-O-CH3), C2v symm.",
+    'name': "CH_3OCH_3 (dimethylether)",
+    'enthalpy': -44.0,
+    'ZPE': 49.1911,
+    'thermal correction': 3.3139,
+    'symbols': 'COCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  1.165725, -0.19995 ],
+                  [ 0.      ,  0.      ,  0.60011 ],
+                  [ 0.      , -1.165725, -0.19995 ],
+                  [ 0.      ,  2.017769,  0.480203],
+                  [ 0.891784,  1.21432 , -0.840474],
+                  [-0.891784,  1.21432 , -0.840474],
+                  [ 0.      , -2.017769,  0.480203],
+                  [-0.891784, -1.21432 , -0.840474],
+                  [ 0.891784, -1.21432 , -0.840474]]},
+'CH2SCH2': {
+    'description': "Thiooxirane (cyclic CH2-S-CH2 ring), C2v symm.",
+    'name': "C_2H_4S (thiirane)",
+    'enthalpy': 19.6,
+    'ZPE': 33.9483,
+    'thermal correction': 2.7290,
+    'symbols': 'CSCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      , -0.739719, -0.792334],
+                  [ 0.      ,  0.      ,  0.863474],
+                  [ 0.      ,  0.739719, -0.792334],
+                  [-0.91394 , -1.250142, -1.076894],
+                  [ 0.91394 , -1.250142, -1.076894],
+                  [ 0.91394 ,  1.250142, -1.076894],
+                  [-0.91394 ,  1.250142, -1.076894]]},
+'C2H6SO': {
+    'description': "Dimethylsulfoxide (CH3)2SO, Cs symm.",
+    'name': "(CH_3)_2SO (dimethyl sulfoxide)",
+    'enthalpy': -36.2,
+    'ZPE': 48.8479,
+    'thermal correction': 4.1905,
+    'symbols': 'SOCCHHHHHH',
+    'magmoms': None,
+    'positions': [[  2.00000000e-06,   2.31838000e-01,  -4.38643000e-01],
+                  [  2.00000000e-05,   1.50074200e+00,   3.79819000e-01],
+                  [  1.33952800e+00,  -8.09022000e-01,   1.80717000e-01],
+                  [ -1.33954800e+00,  -8.08992000e-01,   1.80718000e-01],
+                  [  1.25583500e+00,  -8.96385000e-01,   1.26682500e+00],
+                  [ -2.27940400e+00,  -3.13924000e-01,  -6.86740000e-02],
+                  [  1.30440700e+00,  -1.79332700e+00,  -2.92589000e-01],
+                  [  2.27939500e+00,  -3.13974000e-01,  -6.86740000e-02],
+                  [ -1.30444700e+00,  -1.79329800e+00,  -2.92587000e-01],
+                  [ -1.25585700e+00,  -8.96355000e-01,   1.26682600e+00]]},
+'CH3CH2SH': {
+    'description': "ThioEthanol (CH3-CH2-SH), Cs symm.",
+    'name': "C_2H_5SH (ethanethiol)",
+    'enthalpy': -11.1,
+    'ZPE': 46.1583,
+    'thermal correction': 3.5900,
+    'symbols': 'CCSHHHHHH',
+    'magmoms': None,
+    'positions': [[ 1.514343,  0.679412,  0.      ],
+                  [ 0.      ,  0.826412,  0.      ],
+                  [-0.756068, -0.831284,  0.      ],
+                  [-2.035346, -0.427738,  0.      ],
+                  [-0.32497 ,  1.376482,  0.885793],
+                  [-0.32497 ,  1.376482, -0.885793],
+                  [ 1.986503,  1.665082,  0.      ],
+                  [ 1.854904,  0.137645,  0.885494],
+                  [ 1.854904,  0.137645, -0.885494]]},
+'CH3SCH3': {
+    'description': "Dimethyl ThioEther (CH3-S-CH3), C2v symm.",
+    'name': "CH_3SCH_3 (dimethyl sulfide)",
+    'enthalpy': -8.9,
+    'ZPE': 46.6760,
+    'thermal correction': 3.6929,
+    'symbols': 'CSCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  1.366668, -0.513713],
+                  [ 0.      ,  0.      ,  0.664273],
+                  [ 0.      , -1.366668, -0.513713],
+                  [ 0.      ,  2.296687,  0.057284],
+                  [ 0.891644,  1.34568 , -1.144596],
+                  [-0.891644,  1.34568 , -1.144596],
+                  [ 0.      , -2.296687,  0.057284],
+                  [-0.891644, -1.34568 , -1.144596],
+                  [ 0.891644, -1.34568 , -1.144596]]},
+'H2CCHF': {
+    'description': "Vinyl fluoride (H2C=CHF), Cs symm.",
+    'name': "CH_2=CHF (vinyl fluoride)",
+    'enthalpy': -33.2,
+    'ZPE': 27.2785,
+    'thermal correction': 2.7039,
+    'symbols': 'CCFHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.437714,  0.      ],
+                  [ 1.191923, -0.145087,  0.      ],
+                  [-1.148929, -0.278332,  0.      ],
+                  [-0.186445,  1.505778,  0.      ],
+                  [ 1.291348, -1.222833,  0.      ],
+                  [ 2.083924,  0.466279,  0.      ]]},
+'CH3CH2Cl': {
+    'description': "Ethyl chloride (CH3-CH2-Cl), Cs symm.",
+    'name': "C_2H_5Cl (ethyl chloride)",
+    'enthalpy': -26.8,
+    'ZPE': 41.0686,
+    'thermal correction': 3.1488,
+    'symbols': 'CCClHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.807636,  0.      ],
+                  [ 1.505827,  0.647832,  0.      ],
+                  [-0.823553, -0.77997 ,  0.      ],
+                  [-0.344979,  1.341649,  0.885248],
+                  [-0.344979,  1.341649, -0.885248],
+                  [ 1.976903,  1.634877,  0.      ],
+                  [ 1.839246,  0.10425 ,  0.885398],
+                  [ 1.839246,  0.10425 , -0.885398]]},
+'H2CCHCl': {
+    'description': "Vinyl chloride, H2C=CHCl, Cs symm.",
+    'name': "CH_2=CHCl (vinyl chloride)",
+    'enthalpy': 8.9,
+    'ZPE': 26.3554,
+    'thermal correction': 2.8269,
+    'symbols': 'CCClHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.756016,  0.      ],
+                  [ 1.303223,  1.028507,  0.      ],
+                  [-0.631555, -0.85498 ,  0.      ],
+                  [-0.771098,  1.516963,  0.      ],
+                  [ 2.056095,  0.249427,  0.      ],
+                  [ 1.632096,  2.061125,  0.      ]]},
+'H2CCHCN': {
+    'description': "CyanoEthylene (H2C=CHCN), Cs symm.",
+    'name': "CH_2=CHCN (acrylonitrile)",
+    'enthalpy': 43.2,
+    'ZPE': 31.4081,
+    'thermal correction': 3.2034,
+    'symbols': 'CCCHHHN',
+    'magmoms': None,
+    'positions': [[-0.161594, -1.638625,  0.      ],
+                  [ 0.584957, -0.524961,  0.      ],
+                  [ 0.      ,  0.782253,  0.      ],
+                  [-1.245203, -1.598169,  0.      ],
+                  [ 0.305973, -2.616405,  0.      ],
+                  [ 1.669863, -0.572107,  0.      ],
+                  [-0.467259,  1.867811,  0.      ]]},
+'CH3COCH3': {
+    'description': "Acetone (CH3-CO-CH3), C2v symm.",
+    'name': "CH_3COCH_3 (acetone)",
+    'enthalpy': -51.9,
+    'ZPE': 51.5587,
+    'thermal correction': 3.9878,
+    'symbols': 'OCCCHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.405591],
+                  [ 0.      ,  0.      ,  0.17906 ],
+                  [ 0.      ,  1.28549 , -0.616342],
+                  [ 0.      , -1.28549 , -0.616342],
+                  [ 0.      ,  2.134917,  0.066535],
+                  [ 0.      , -2.134917,  0.066535],
+                  [-0.881086,  1.331548, -1.264013],
+                  [ 0.881086,  1.331548, -1.264013],
+                  [ 0.881086, -1.331548, -1.264013],
+                  [-0.881086, -1.331548, -1.264013]]},
+'CH3COOH': {
+    'description': "Acetic Acid (CH3COOH), Single bonds trans, Cs symm.",
+    'name': "CH_3COOH (acetic acid)",
+    'enthalpy': -103.4,
+    'ZPE': 38.1670,
+    'thermal correction': 3.4770,
+    'symbols': 'COOHCHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.15456 ,  0.      ],
+                  [ 0.166384,  1.360084,  0.      ],
+                  [-1.236449, -0.415036,  0.      ],
+                  [-1.867646,  0.333582,  0.      ],
+                  [ 1.073776, -0.892748,  0.      ],
+                  [ 2.048189, -0.408135,  0.      ],
+                  [ 0.968661, -1.528353,  0.881747],
+                  [ 0.968661, -1.528353, -0.881747]]},
+'CH3COF': {
+    'description': "Acetyl fluoride (CH3COF), HCCO cis, Cs symm.",
+    'name': "CH_3COF (acetyl fluoride)",
+    'enthalpy': -105.7,
+    'ZPE': 30.2742,
+    'thermal correction': 3.3126,
+    'symbols': 'COFCHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.186396,  0.      ],
+                  [ 0.126651,  1.377199,  0.      ],
+                  [-1.24395 , -0.382745,  0.      ],
+                  [ 1.049454, -0.876224,  0.      ],
+                  [ 2.035883, -0.417099,  0.      ],
+                  [ 0.924869, -1.508407,  0.881549],
+                  [ 0.924869, -1.508407, -0.881549]]},
+'CH3COCl': {
+    'description': "Acetyl,Chloride (CH3COCl), HCCO cis, Cs symm.",
+    'name': "CH_3COCl (acetyl chloride)",
+    'enthalpy': -58.0,
+    'ZPE': 29.1855,
+    'thermal correction': 3.5235,
+    'symbols': 'CCClOHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.523878,  0.      ],
+                  [ 1.486075,  0.716377,  0.      ],
+                  [-0.452286, -1.217999,  0.      ],
+                  [-0.845539,  1.37494 ,  0.      ],
+                  [ 1.701027,  1.784793,  0.      ],
+                  [ 1.917847,  0.240067,  0.882679],
+                  [ 1.917847,  0.240067, -0.882679]]},
+'C3H7Cl': {
+    'description': "Propyl chloride (CH3CH2CH2Cl), Cs symm.",
+    'name': "CH_3CH_2CH_2Cl (propyl chloride)",
+    'enthalpy': -31.5,
+    'ZPE': 58.6696,
+    'thermal correction': 3.9885,
+    'symbols': 'CCCHHHClHHHH',
+    'magmoms': None,
+    'positions': [[ 0.892629, -0.642344,  0.      ],
+                  [ 2.365587, -0.245168,  0.      ],
+                  [ 0.      ,  0.582921,  0.      ],
+                  [ 0.663731, -1.252117,  0.879201],
+                  [ 0.663731, -1.252117, -0.879201],
+                  [ 3.005476, -1.130924,  0.      ],
+                  [-1.73281 ,  0.139743,  0.      ],
+                  [ 2.614882,  0.347704, -0.88473 ],
+                  [ 2.614882,  0.347704,  0.88473 ],
+                  [ 0.172881,  1.195836,  0.88646 ],
+                  [ 0.172881,  1.195836, -0.88646 ]]},
+'C2H6CHOH': {
+    'description': "Isopropyl alcohol, (CH3)2CH-OH, Gauche isomer, C1 symm.",
+    'name': "(CH_3)_2CHOH (isopropanol)",
+    'enthalpy': -65.2,
+    'ZPE': 66.5612,
+    'thermal correction': 4.0732,
+    'symbols': 'OCHHCCHHHHHH',
+    'magmoms': None,
+    'positions': [[  2.71910000e-02,   1.36369100e+00,  -1.67516000e-01],
+                  [ -9.26000000e-04,   3.64590000e-02,   3.70128000e-01],
+                  [  8.59465000e-01,   1.77564700e+00,   1.21307000e-01],
+                  [  7.37100000e-03,   8.21450000e-02,   1.47050600e+00],
+                  [ -1.31327500e+00,  -5.63514000e-01,  -8.89790000e-02],
+                  [  1.20072100e+00,  -7.64480000e-01,  -1.04920000e-01],
+                  [ -1.33400500e+00,  -6.07253000e-01,  -1.18100900e+00],
+                  [  1.20284300e+00,  -8.07817000e-01,  -1.19718900e+00],
+                  [ -2.14781200e+00,   5.49930000e-02,   2.47676000e-01],
+                  [  2.13646200e+00,  -2.99324000e-01,   2.23164000e-01],
+                  [ -1.43870900e+00,  -1.57427500e+00,   3.08340000e-01],
+                  [  1.17773600e+00,  -1.78443600e+00,   2.89967000e-01]]},
+'CH3CH2OCH3': {
+    'description': "Methyl ethyl ether (CH3-CH2-O-CH3), Trans, Cs symm.",
+    'name': "C_2H_5OCH_3 (methyl ethyl ether)",
+    'enthalpy': -51.7,
+    'ZPE': 66.6936,
+    'thermal correction': 4.1058,
+    'symbols': 'OCCCHHHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.006429, -0.712741,  0.      ],
+                  [ 0.      ,  0.705845,  0.      ],
+                  [ 1.324518, -1.226029,  0.      ],
+                  [-1.442169,  1.160325,  0.      ],
+                  [ 0.530962,  1.086484,  0.886881],
+                  [ 0.530962,  1.086484, -0.886881],
+                  [ 1.241648, -2.313325,  0.      ],
+                  [ 1.881329, -0.905925, -0.89171 ],
+                  [ 1.881329, -0.905925,  0.89171 ],
+                  [-1.954863,  0.780605, -0.885855],
+                  [-1.954863,  0.780605,  0.885855],
+                  [-1.502025,  2.252083,  0.      ]]},
+'C3H9N': {
+    'description': "Trimethyl Amine, (CH3)3N, C3v symm.",
+    'name': "(CH_3)_3N (trimethylamine)",
+    'enthalpy': -5.7,
+    'ZPE': 74.1584,
+    'thermal correction': 4.0631,
+    'symbols': 'NCCCHHHHHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.395846],
+                  [ 0.      ,  1.378021, -0.065175],
+                  [ 1.193401, -0.689011, -0.065175],
+                  [-1.193401, -0.689011, -0.065175],
+                  [ 0.      ,  1.461142, -1.167899],
+                  [ 0.886156,  1.891052,  0.317655],
+                  [-0.886156,  1.891052,  0.317655],
+                  [ 1.265386, -0.730571, -1.167899],
+                  [ 1.194621, -1.71296 ,  0.317655],
+                  [ 2.080777, -0.178092,  0.317655],
+                  [-1.265386, -0.730571, -1.167899],
+                  [-2.080777, -0.178092,  0.317655],
+                  [-1.194621, -1.71296 ,  0.317655]]},
+'C4H4O': {
+    'description': "Furan (cyclic C4H4O), C2v symm.",
+    'name': "C_4H_4O (furan)",
+    'enthalpy': -8.3,
+    'ZPE': 43.2116,
+    'thermal correction': 2.9480,
+    'symbols': 'OCCCCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.163339],
+                  [ 0.      ,  1.0947  ,  0.348039],
+                  [ 0.      , -1.0947  ,  0.348039],
+                  [ 0.      ,  0.7132  , -0.962161],
+                  [ 0.      , -0.7132  , -0.962161],
+                  [ 0.      ,  2.049359,  0.851113],
+                  [ 0.      , -2.049359,  0.851113],
+                  [ 0.      ,  1.370828, -1.819738],
+                  [ 0.      , -1.370828, -1.819738]]},
+'C4H4S': {
+    'description': "Thiophene (cyclic C4H4S), C2v symm.",
+    'name': "C_4H_4S (thiophene)",
+    'enthalpy': 27.5,
+    'ZPE': 41.2029,
+    'thermal correction': 3.1702,
+    'symbols': 'SCCCCHHHH',
+    'magmoms': None,
+    'positions': [[  0.00000000e+00,   0.00000000e+00,   1.18975300e+00],
+                  [  0.00000000e+00,   1.23387600e+00,  -1.47400000e-03],
+                  [  0.00000000e+00,  -1.23387600e+00,  -1.47400000e-03],
+                  [  0.00000000e+00,   7.09173000e-01,  -1.27232200e+00],
+                  [  0.00000000e+00,  -7.09173000e-01,  -1.27232200e+00],
+                  [  0.00000000e+00,   2.27534300e+00,   2.91984000e-01],
+                  [  0.00000000e+00,  -2.27534300e+00,   2.91984000e-01],
+                  [  0.00000000e+00,   1.32193400e+00,  -2.16723100e+00],
+                  [  0.00000000e+00,  -1.32193400e+00,  -2.16723100e+00]]},
+'C4H4NH': {
+    'description': "Pyrrole (Planar cyclic C4H4NH), C2v symm.",
+    'name': "C_4H_5N (pyrrole)",
+    'enthalpy': 25.9,
+    'ZPE': 50.9688,
+    'thermal correction': 3.1156,
+    'symbols': 'HNCCCCHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  2.129296],
+                  [ 0.      ,  0.      ,  1.118684],
+                  [ 0.      ,  1.124516,  0.333565],
+                  [ 0.      , -1.124516,  0.333565],
+                  [ 0.      ,  0.708407, -0.983807],
+                  [ 0.      , -0.708407, -0.983807],
+                  [ 0.      ,  2.112872,  0.770496],
+                  [ 0.      , -2.112872,  0.770496],
+                  [ 0.      ,  1.357252, -1.849085],
+                  [ 0.      , -1.357252, -1.849085]]},
+'C5H5N': {
+    'description': "Pyridine (cyclic C5H5N), C2v symm.",
+    'name': "C_5H_5N (pyridine)",
+    'enthalpy': 33.6,
+    'ZPE': 54.8230,
+    'thermal correction': 3.3007,
+    'symbols': 'NCCCCCHHHHH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  1.424672],
+                  [ 0.      ,  0.      , -1.386178],
+                  [ 0.      ,  1.144277,  0.720306],
+                  [ 0.      , -1.144277,  0.720306],
+                  [ 0.      , -1.196404, -0.672917],
+                  [ 0.      ,  1.196404, -0.672917],
+                  [ 0.      ,  0.      , -2.473052],
+                  [ 0.      ,  2.060723,  1.307477],
+                  [ 0.      , -2.060723,  1.307477],
+                  [ 0.      , -2.155293, -1.183103],
+                  [ 0.      ,  2.155293, -1.183103]]},
+'H2': {
+    'description': "H2. D*h symm.",
+    'name': "H_2",
+    'enthalpy': 0.0,
+    'ZPE': 6.2908,
+    'thermal correction': 2.0739,
+    'ionization energy': 15.43,
+    'symbols': 'HH',
+    'magmoms': None,
+    'positions': [[ 0.      ,  0.      ,  0.368583],
+                  [ 0.      ,  0.      , -0.368583]]},
+'SH': {
+    'description': "SH radical, C*v symm.",
+    'name': "HS",
+    'enthalpy': 34.2,
+    'ZPE': 3.7625,
+    'thermal correction': 2.0739,
+    'symbols': 'SH',
+    'magmoms': [ 1.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.079083],
+                  [ 0.      ,  0.      , -1.26533 ]]},
+'CCH': {
+    'description': "CCH radical, C*v symm.",
+    'name': "CCH",
+    'enthalpy': 135.1,
+    'ZPE': 7.8533,
+    'thermal correction': 2.7830,
+    'symbols': 'CCH',
+    'magmoms': [ 0.,  1.,  0.],
+    'positions': [[ 0.      ,  0.      , -0.462628],
+                  [ 0.      ,  0.      ,  0.717162],
+                  [ 0.      ,  0.      , -1.527198]]},
+'C2H3': {
+    'description': "C2H3 radical, Cs symm, 2-A'.",
+    'name': "C_2H_3 (2A')",
+    'enthalpy': 71.6,
+    'ZPE': 22.5747,
+    'thermal correction': 2.5483,
+    'symbols': 'CCHHH',
+    'magmoms': [ 0.,  1.,  0.,  0.,  0.],
+    'positions': [[ 0.049798, -0.576272,  0.      ],
+                  [ 0.049798,  0.710988,  0.      ],
+                  [-0.87675 , -1.151844,  0.      ],
+                  [ 0.969183, -1.154639,  0.      ],
+                  [-0.690013,  1.498185,  0.      ]]},
+'CH3CO': {
+    'description': "CH3CO radical, HCCO cis, Cs symm, 2-A'.",
+    'name': "CH_3CO (2A')",
+    'enthalpy': -2.4,
+    'ZPE': 26.6070,
+    'thermal correction': 3.0842,
+    'symbols': 'CCHHHO',
+    'magmoms': [ 0.1,  0.6,  0. ,  0. ,  0. ,  0.3],
+    'positions': [[-0.978291, -0.647814,  0.      ],
+                  [ 0.      ,  0.506283,  0.      ],
+                  [-0.455551, -1.607837,  0.      ],
+                  [-1.617626, -0.563271,  0.881061],
+                  [-1.617626, -0.563271, -0.881061],
+                  [ 1.195069,  0.447945,  0.      ]]},
+'H2COH': {
+    'description': "H2COH radical, C1 symm.",
+    'name': "H_2COH (2A)",
+    'enthalpy': -4.1,
+    'ZPE': 23.1294,
+    'thermal correction': 2.6726,
+    'symbols': 'COHHH',
+    'magmoms': [ 0.7,  0.3,  0. ,  0. ,  0. ],
+    'positions': [[ 0.687448,  0.029626, -0.082014],
+                  [-0.672094, -0.125648,  0.030405],
+                  [-1.09185 ,  0.740282, -0.095167],
+                  [ 1.122783,  0.975263,  0.225993],
+                  [ 1.221131, -0.888116,  0.118015]]},
+'CH3O': {
+    'description': "CH3O radical, Cs symm, 2-A'.",
+    'name': "CH_3O CS (2A')",
+    'enthalpy': 4.1,
+    'ZPE': 22.4215,
+    'thermal correction': 2.4969,
+    'symbols': 'COHHH',
+    'magmoms': [ 0.,  1.,  0.,  0.,  0.],
+    'positions': [[-0.008618, -0.586475,  0.      ],
+                  [-0.008618,  0.799541,  0.      ],
+                  [ 1.055363, -0.868756,  0.      ],
+                  [-0.467358, -1.004363,  0.903279],
+                  [-0.467358, -1.004363, -0.903279]]},
+'CH3CH2O': {
+    'description': "CH3CH2O radical, Cs symm, 2-A''.",
+    'name': "CH_3CH_2O (2A'')",
+    'enthalpy': -3.7,
+    'ZPE': 39.4440,
+    'thermal correction': 3.0158,
+    'symbols': 'CCOHHHHH',
+    'magmoms': [ 0.,  0.,  1.,  0.,  0.,  0.,  0.,  0.],
+    'positions': [[  1.00475700e+00,  -5.68263000e-01,   0.00000000e+00],
+                  [  0.00000000e+00,   5.88691000e-01,   0.00000000e+00],
+                  [ -1.26006200e+00,   7.29000000e-04,   0.00000000e+00],
+                  [  1.46956000e-01,   1.20468100e+00,   8.96529000e-01],
+                  [  1.46956000e-01,   1.20468100e+00,  -8.96529000e-01],
+                  [  2.01936300e+00,  -1.64100000e-01,   0.00000000e+00],
+                  [  8.69340000e-01,  -1.18683200e+00,   8.88071000e-01],
+                  [  8.69340000e-01,  -1.18683200e+00,  -8.88071000e-01]]},
+'CH3S': {
+    'description': "CH3S radical, Cs symm, 2-A'.",
+    'name': "CH_3S (2A')",
+    'enthalpy': 29.8,
+    'ZPE': 21.9415,
+    'thermal correction': 2.6054,
+    'symbols': 'CSHHH',
+    'magmoms': [ 0.,  1.,  0.,  0.,  0.],
+    'positions': [[-0.003856,  1.106222,  0.      ],
+                  [-0.003856, -0.692579,  0.      ],
+                  [ 1.043269,  1.427057,  0.      ],
+                  [-0.479217,  1.508437,  0.895197],
+                  [-0.479217,  1.508437, -0.895197]]},
+'C2H5': {
+    'description': "C2H5 radical, Staggered, Cs symm, 2-A'.",
+    'name': "C_2H_5 (2A')",
+    'enthalpy': 28.9,
+    'ZPE': 36.5675,
+    'thermal correction': 3.0942,
+    'symbols': 'CCHHHHH',
+    'magmoms': [ 0.,  1.,  0.,  0.,  0.,  0.,  0.],
+    'positions': [[-0.014359, -0.694617,  0.      ],
+                  [-0.014359,  0.794473,  0.      ],
+                  [ 1.006101, -1.104042,  0.      ],
+                  [-0.517037, -1.093613,  0.884839],
+                  [-0.517037, -1.093613, -0.884839],
+                  [ 0.100137,  1.346065,  0.923705],
+                  [ 0.100137,  1.346065, -0.923705]]},
+'C3H7': {
+    'description': "(CH3)2CH radical, Cs symm, 2-A'.",
+    'name': "(CH_3)_2CH (2A')",
+    'enthalpy': 21.5,
+    'ZPE': 54.2928,
+    'thermal correction': 3.8435,
+    'symbols': 'CCCHHHHHHH',
+    'magmoms': [ 1.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.,  0.],
+    'positions': [[ 0.014223,  0.54385 ,  0.      ],
+                  [ 0.014223, -0.199742,  1.291572],
+                  [ 0.014223, -0.199742, -1.291572],
+                  [-0.32289 ,  1.575329,  0.      ],
+                  [ 0.221417,  0.459174,  2.138477],
+                  [ 0.221417,  0.459174, -2.138477],
+                  [-0.955157, -0.684629,  1.484633],
+                  [ 0.767181, -0.995308,  1.286239],
+                  [ 0.767181, -0.995308, -1.286239],
+                  [-0.955157, -0.684629, -1.484633]]},
+'C3H9C': {
+    'description': "t-Butyl radical, (CH3)3C, C3v symm.",
+    'name': "(CH_3)_3C (t-butyl radical)",
+    'enthalpy': 12.3,
+    'ZPE': 71.7833,
+    'thermal correction': 4.6662,
+    'symbols': 'CCCCHHHHHHHHH',
+    'magmoms': [1., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
+    'positions': [[ 0.      ,  0.      ,  0.191929],
+                  [ 0.      ,  1.478187, -0.020866],
+                  [ 1.280147, -0.739093, -0.020866],
+                  [-1.280147, -0.739093, -0.020866],
+                  [ 0.      ,  1.731496, -1.093792],
+                  [-0.887043,  1.945769,  0.417565],
+                  [ 0.887043,  1.945769,  0.417565],
+                  [ 1.49952 , -0.865748, -1.093792],
+                  [ 2.128607, -0.204683,  0.417565],
+                  [ 1.241564, -1.741086,  0.417565],
+                  [-1.49952 , -0.865748, -1.093792],
+                  [-1.241564, -1.741086,  0.417565],
+                  [-2.128607, -0.204683,  0.417565]]},
+'NO2': {
+    'description': "NO2 radical, C2v symm, 2-A1.",
+    'name': "NO_2",
+    'enthalpy': 7.9,
+    'ZPE': 5.4631,
+    'thermal correction': 2.4366,
+    'symbols': 'NOO',
+    'magmoms': [ 1.,  0.,  0.],
+    'positions': [[ 0.      ,  0.      ,  0.332273],
+                  [ 0.      ,  1.118122, -0.14537 ],
+                  [ 0.      , -1.118122, -0.14537 ]]},
+}
+
+
+def get_ionization_energy(name, vertical=True):
+    """Return the experimental ionization energy.
+
+    If vertical is True, the vertical ionization energy is returned if
+    available.
+    """
+    if name not in data:
+        raise KeyError('System %s not in database.' % name)
+    elif 'ionization energy' not in data[name]:
+        raise KeyError('No data on ionization energy for system %s.' % name)
+    else:
+        if vertical and 'vertical ionization energy' in data[name]:
+            return data[name]['vertical ionization energy']
+        else:
+            return data[name]['ionization energy']
+
+
+def get_atomization_energy(name):
+    """Determine extrapolated experimental atomization energy.
+
+    The atomization energy is extrapolated from experimental heats of
+    formation at room temperature, using calculated zero-point energies
+    and thermal corrections.
+
+    The atomization energy is returned in kcal/mol = 43.36 meV:
+
+    >>> from ase.units import *; print kcal / mol
+    0.0433641146392
+
+    """
+    assert name in extra or name in g2
+    d = data[name]
+    e = d['enthalpy']
+    z = d['ZPE']
+    dh = d['thermal correction']
+    ae = -e + z + dh
+    for a in string2symbols(d['symbols']):
+        h = data[a]['enthalpy']
+        dh = data[a]['thermal correction']
+        ae += h - dh
+    return ae
+
+
+def molecule(name, **kwargs):
+    """Create molecule."""
+    if name in atoms:
+        if 'magmoms' not in kwargs:
+            kwargs['magmoms'] = [data[name]['magmom']]
+        return  Atoms(name, **kwargs)
+    if name not in extra and name not in g2:
+        raise NotImplementedError('System %s not in database.' % name)
+    d = data[name]
+    if 'magmoms' not in kwargs:
+        kwargs['magmoms'] = d['magmoms']
+    return Atoms(d['symbols'], d['positions'], **kwargs)
+
+
+def latex(name):
+    """Convert name to LaTeX"""
+    s = '$'
+    last = False
+    for i in name:
+        if i.isalpha():
+            if not last:
+                s = s + r'\rm{'
+                last = True
+        elif last:
+            s = s + '}'
+            last = False
+        s = s + i
+    if i.isalpha():
+        s = s + '}'
+    s = s.replace(' ', r'\ ') + '$'
+    return s
+
+
+def rest(name):
+    """Convert name to reStructuredText."""
+    s = ''
+    while name:
+        c = name[0]
+        if c == '_':
+            s += r'\ :sub:`%s`\ ' % name[1]
+            name = name[2:]
+        elif c == '^':
+            s += r'\ :sup:`%s`\ ' % name[1]
+            name = name[2:]
+        else:
+            s += c
+            name = name[1:]
+    return s
+
+
+if __name__ == '__main__':
+    # Compare experimental values from the Kresse/VASP article with those from
+    # the Argonne NL homepage    
+    from gpaw.testing.atomization_data import atomization_vasp
+    print 'Name      Kresse   ANL Diff'
+    err = []
+    for name in g1:
+        anl = get_atomization_energy(name)
+        kresse = atomization_vasp[name][0]
+        err.append(kresse - anl)
+        print '%-10s %5.1f %5.1f % 3.1f' % (name, kresse, anl, kresse - anl)
+
+    import numpy as np
+    mae = np.abs(err).mean()
+    print 'MAE:', mae
Index: ase/atoms.py
===================================================================
--- ase/atoms.py	(revision 0)
+++ ase/atoms.py	(revision 1)
@@ -0,0 +1,1308 @@
+# Copyright 2008, 2009 CAMd
+# (see accompanying license files for details).
+
+"""Definition of the Atoms class.
+
+This module defines the central object in the ASE package: the Atoms
+object.
+"""
+
+from math import cos, sin
+
+import numpy as np
+
+from ase.atom import Atom
+from ase.data import atomic_numbers, chemical_symbols, atomic_masses
+import ase.units as units
+
+class Atoms(object):
+    """Atoms object.
+    
+    The Atoms object can represent an isolated molecule, or a
+    periodically repeated structure.  It has a unit cell and
+    there may be periodic boundary conditions along any of the three
+    unit cell axes.
+
+    Information about the atoms (atomic numbers and position) is
+    stored in ndarrays.  Optionally, there can be information about
+    tags, momenta, masses, magnetic moments and charges.
+
+    In order to calculate energies, forces and stresses, a calculator
+    object has to attached to the atoms object.
+
+    Parameters:
+
+    symbols: str (formula) or list of str
+        Can be a string formula, a list of symbols or a list of
+        Atom objects.  Examples: 'H2O', 'COPt12', ['H', 'H', 'O'],
+        [Atom('Ne', (x, y, z)), ...].
+    positions: list of xyz-positions
+        Atomic positions.  Anything that can be converted to an
+        ndarray of shape (n, 3) will do: [(x1,y1,z1), (x2,y2,z2),
+        ...].
+    scaled_positions: list of scaled-positions
+        Like positions, but given in units of the unit cell.
+        Can not be set at the same time as positions.
+    numbers: list of int
+        Atomic numbers (use only one of symbols/numbers).
+    tags: list of int
+        Special purpose tags.
+    momenta: list of xyz-momenta
+        Momenta for all atoms.
+    masses: list of float
+        Atomic masses in atomic units.
+    magmoms: list of float
+        Magnetic moments.
+    charges: list of float
+        Atomic charges.
+    cell: 3x3 matrix
+        Unit cell vectors.  Can also be given as just three
+        numbers for orthorhombic cells.  Default value: [1, 1, 1].
+    pbc: one or three bool
+        Periodic boundary conditions flags.  Examples: True,
+        False, 0, 1, (1, 1, 0), (True, False, False).  Default
+        value: False.
+    constraint: constraint object(s)
+        Used for applying one or more constraints during structure
+        optimization.
+    calculator: calculator object
+        Used to attach a calculator for calulating energies and atomic
+        forces.
+
+    Examples:
+
+    These three are equivalent:
+
+    >>> d = 1.104  # N2 bondlength
+    >>> a = Atoms('N2', [(0, 0, 0), (0, 0, d)])
+    >>> a = Atoms(numbers=[7, 7], positions=[(0, 0, 0), (0, 0, d)])
+    >>> a = Atoms([Atom('N', (0, 0, 0)), Atom('N', (0, 0, d)])
+
+    FCC gold:
+
+    >>> a = 4.05  # Gold lattice constant
+    >>> b = a / 2
+    >>> fcc = Atoms('Au',
+    ...             cell=[(0, b, b), (b, 0, b), (b, b, 0)],
+    ...             pbc=True)
+
+    Hydrogen wire:
+    
+    >>> d = 0.9  # H-H distance
+    >>> L = 7.0
+    >>> h = Atoms('H', positions=[(0, L / 2, L / 2)],
+    ...           cell=(d, L, L),
+    ...           pbc=(1, 0, 0))
+    """
+
+    def __init__(self, symbols=None,
+                 positions=None, numbers=None,
+                 tags=None, momenta=None, masses=None,
+                 magmoms=None, charges=None,
+                 scaled_positions=None,
+                 cell=None, pbc=None,
+                 constraint=None,
+                 calculator=None):
+
+        atoms = None
+
+        if hasattr(symbols, 'GetUnitCell'):
+            from ase.old import OldASEListOfAtomsWrapper
+            atoms = OldASEListOfAtomsWrapper(symbols)
+            symbols = None
+        elif hasattr(symbols, "get_positions"):
+            atoms = symbols
+            symbols = None    
+        elif (isinstance(symbols, (list, tuple)) and
+              len(symbols) > 0 and isinstance(symbols[0], Atom)):
+            # Get data from a list or tuple of Atom objects:
+            data = zip(*[atom.get_data() for atom in symbols])
+            atoms = self.__class__(None, *data)
+            symbols = None    
+                
+        if atoms is not None:
+            # Get data from another Atoms object:
+            if scaled_positions is not None:
+                raise NotImplementedError
+            if symbols is None and numbers is None:
+                numbers = atoms.get_atomic_numbers()
+            if positions is None:
+                positions = atoms.get_positions()
+            if tags is None and atoms.has('tags'):
+                tags = atoms.get_tags()
+            if momenta is None and atoms.has('momenta'):
+                momenta = atoms.get_momenta()
+            if magmoms is None and atoms.has('magmoms'):
+                magmoms = atoms.get_initial_magnetic_moments()
+            if masses is None and atoms.has('masses'):
+                masses = atoms.get_masses()
+            if charges is None and atoms.has('charges'):
+                charges = atoms.get_charges()
+            if cell is None:
+                cell = atoms.get_cell()
+            if pbc is None:
+                pbc = atoms.get_pbc()
+            if constraint is None:
+                constraint = [c.copy() for c in atoms.constraints]
+            if calculator is None:
+                calculator = atoms.get_calculator()
+
+        self.arrays = {}
+        
+        if symbols is None:
+            if numbers is None:
+                if positions is not None:
+                    natoms = len(positions)
+                elif scaled_positions is not None:
+                    natoms = len(scaled_positions)
+                else:
+                    natoms = 0
+                numbers = np.zeros(natoms, int)
+            self.new_array('numbers', numbers, int)
+        else:
+            if numbers is not None:
+                raise ValueError(
+                    'Use only one of "symbols" and "numbers".')
+            else:
+                self.new_array('numbers', symbols2numbers(symbols), int)
+
+        if cell is None:
+            cell = np.eye(3)
+        self.set_cell(cell)
+
+        if positions is None:
+            if scaled_positions is None:
+                positions = np.zeros((len(self.arrays['numbers']), 3))
+            else:
+                positions = np.dot(scaled_positions, self._cell)
+        else:
+            if scaled_positions is not None:
+                raise RuntimeError, 'Both scaled and cartesian positions set!'
+        self.new_array('positions', positions, float, (3,))
+
+        self.set_constraint(constraint)
+        self.set_tags(default(tags, 0))
+        self.set_momenta(default(momenta, (0.0, 0.0, 0.0)))
+        self.set_masses(default(masses, None))
+        self.set_initial_magnetic_moments(default(magmoms, 0.0))
+        self.set_charges(default(charges, 0.0))
+        if pbc is None:
+            pbc = False
+        self.set_pbc(pbc)
+
+        self.adsorbate_info = {}
+
+        self.set_calculator(calculator)
+
+    def set_calculator(self, calc=None):
+        """Attach calculator object."""
+        if hasattr(calc, '_SetListOfAtoms'):
+            from ase.old import OldASECalculatorWrapper
+            calc = OldASECalculatorWrapper(calc, self)
+        if hasattr(calc, 'set_atoms'):
+            calc.set_atoms(self)
+        self.calc = calc
+
+    def get_calculator(self):
+        """Get currently attached calculator object."""
+        return self.calc
+
+    def set_constraint(self, constraint=None):
+        """Apply one or more constrains.
+
+        The *constraint* argument must be one constraint object or a
+        list of constraint objects."""
+        if constraint is None:
+            self._constraints = []
+        else:
+            if isinstance(constraint, (list, tuple)):
+                self._constraints = constraint
+            else:
+                self._constraints = [constraint]
+
+    def _get_constraints(self):
+        return self._constraints
+
+    def _del_constraints(self):
+        self._constraints = []
+
+    constraints = property(_get_constraints, set_constraint, _del_constraints,
+                           "Constraints of the atoms.")
+    
+    def set_cell(self, cell, scale_atoms=False, fix=None):
+        """Set unit cell vectors.
+
+        Parameters:
+
+        cell : 
+            Unit cell.  A 3x3 matrix (the three unit cell vectors) or
+            just three numbers for an orthorhombic cell.
+        scale_atoms : bool
+            Fix atomic positions or move atoms with the unit cell?
+            Default behavior is to *not* move the atoms (scale_atoms=False).
+
+        Examples:
+
+        Two equivalent ways to define an orthorhombic cell:
+        
+        >>> a.set_cell([a, b, c])
+        >>> a.set_cell([(a, 0, 0), (0, b, 0), (0, 0, c)])
+
+        FCC unit cell:
+
+        >>> a.set_cell([(0, b, b), (b, 0, b), (b, b, 0)])
+        """
+
+        if fix is not None:
+            raise TypeError('Please use scale_atoms=%s' % (not fix))
+
+        cell = np.array(cell, float)
+        if cell.shape == (3,):
+            cell = np.diag(cell)
+        elif cell.shape != (3, 3):
+            raise ValueError('Cell must be length 3 sequence or '
+                             '3x3 matrix!')
+        if scale_atoms:
+            M = np.linalg.solve(self._cell, cell)
+            self.arrays['positions'][:] = np.dot(self.arrays['positions'], M)
+        self._cell = cell
+
+    def get_cell(self):
+        """Get the three unit cell vectors as a 3x3 ndarray."""
+        return self._cell.copy()
+
+    def set_pbc(self, pbc):
+        """Set periodic boundary condition flags."""
+        if isinstance(pbc, int):
+            pbc = (pbc,) * 3
+        self._pbc = np.array(pbc, bool)
+        
+    def get_pbc(self):
+        """Get periodic boundary condition flags."""
+        return self._pbc.copy()
+
+    def new_array(self, name, a, dtype=None, shape=None):
+        """Add new array.
+
+        If *shape* is not *None*, the shape of *a* will be checked."""
+        
+        if dtype is not None:
+            a = np.array(a, dtype)
+        else:
+            a = a.copy()
+            
+        if name in self.arrays:
+            raise RuntimeError
+
+        for b in self.arrays.values():
+            if len(a) != len(b):
+                raise ValueError('Array has wrong length: %d != %d.' %
+                                 (len(a), len(b)))
+            break
+
+        if shape is not None and a.shape[1:] != shape:
+            raise ValueError('Array has wrong shape %s != %s.' %
+                             (a.shape, (a.shape[0:1] + shape)))
+        
+        self.arrays[name] = a
+
+    def get_array(self, name, copy=True):
+        """Get an array.
+
+        Returns a copy unless the optional argument copy is false.
+        """
+        if copy:
+            return self.arrays[name].copy()
+        else:
+            return self.arrays[name]
+    
+    def set_array(self, name, a, dtype=None, shape=None):
+        """Update array.
+
+        If *shape* is not *None*, the shape of *a* will be checked.
+        If *a* is *None*, then the array is deleted."""
+        
+        b = self.arrays.get(name)
+        if b is None:
+            if a is not None:
+                self.new_array(name, a, dtype, shape)
+        else:
+            if a is None:
+                del self.arrays[name]
+            else:
+                a = np.asarray(a)
+                if a.shape != b.shape:
+                    raise ValueError('Array has wrong shape %s != %s.' %
+                                     (a.shape, b.shape))
+                b[:] = a
+
+    def has(self, name):
+        """Check for existance of array.
+
+        name must be one of: 'tags', 'momenta', 'masses', 'magmoms',
+        'charges'."""
+        return name in self.arrays
+    
+    def set_atomic_numbers(self, numbers):
+        """Set atomic numbers."""
+        self.set_array('numbers', numbers, int, ())
+
+    def get_atomic_numbers(self):
+        """Get integer array of atomic numbers."""
+        return self.arrays['numbers']
+
+    def set_chemical_symbols(self, symbols):
+        """Set chemical symbols."""
+        self.set_array('numbers', symbols2numbers(symbols), int, ())
+
+    def get_chemical_symbols(self, reduce=False):
+        """Get list of chemical symbol strings.
+
+        If reduce is True, a single string is returned, where repeated
+        elements have been contracted to a single symbol and a number.
+        E.g. instead of ['C', 'O', 'O', 'H'], the string 'CO2H' is returned.
+        """
+        if not reduce:
+            return [chemical_symbols[Z] for Z in self.arrays['numbers']]
+        else:
+            num = self.get_atomic_numbers()
+            N = len(num)
+            dis = np.concatenate(([0], np.arange(1, N)[num[1:] != num[:-1]]))
+            repeat = np.append(dis[1:], N) - dis
+            symbols = ''.join([chemical_symbols[num[d]] + str(r) * (r != 1)
+                               for r, d in zip(repeat, dis)])
+            return symbols
+
+    def set_tags(self, tags):
+        """Set tags for all atoms."""
+        self.set_array('tags', tags, int, ())
+        
+    def get_tags(self):
+        """Get integer array of tags."""
+        if 'tags' in self.arrays:
+            return self.arrays['tags'].copy()
+        else:
+            return np.zeros(len(self), int)
+
+    def set_momenta(self, momenta):
+        """Set momenta."""
+        if len(self.constraints) > 0 and momenta is not None:
+            momenta = np.array(momenta)  # modify a copy
+            for constraint in self.constraints:
+                constraint.adjust_forces(self.arrays['positions'], momenta)
+        self.set_array('momenta', momenta, float, (3,))
+
+    def set_velocities(self, velocities):
+        """Set the momenta by specifying the velocities."""
+        self.set_momenta(self.get_masses()[:,np.newaxis] * velocities)
+        
+    def get_momenta(self):
+        """Get array of momenta."""
+        if 'momenta' in self.arrays:
+            return self.arrays['momenta'].copy()
+        else:
+            return np.zeros((len(self), 3))
+        
+    def set_masses(self, masses='defaults'):
+        """Set atomic masses.
+
+        The array masses should contain the a list masses.  In case
+        the masses argument is not given or for those elements of the
+        masses list that are None, standard values are set."""
+        
+        if masses == 'defaults':
+            masses = atomic_masses[self.arrays['numbers']]
+        elif isinstance(masses, (list, tuple)):
+            newmasses = []
+            for m, Z in zip(masses, self.arrays['numbers']):
+                if m is None:
+                    newmasses.append(atomic_masses[Z])
+                else:
+                    newmasses.append(m)
+            masses = newmasses
+        self.set_array('masses', masses, float, ())
+
+    def get_masses(self):
+        """Get array of masses."""
+        if 'masses' in self.arrays:
+            return self.arrays['masses'].copy()
+        else:
+            return atomic_masses[self.arrays['numbers']]
+        
+    def set_initial_magnetic_moments(self, magmoms):
+        """Set the initial magnetic moments."""
+        self.set_array('magmoms', magmoms, float, ())
+
+    def set_magnetic_moments(self, magmoms):
+        print 'Please use set_initial_magnetic_moments() instead!'
+        self.set_initial_magnetic_moments(magmoms)
+
+    def get_initial_magnetic_moments(self):
+        """Get array of initial magnetic moments."""
+        if 'magmoms' in self.arrays:
+            return self.arrays['magmoms'].copy()
+        else:
+            return np.zeros(len(self))
+
+    def get_magnetic_moments(self):
+        """Get calculated local magnetic moments."""
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        if self.calc.get_spin_polarized():
+            return self.calc.get_magnetic_moments(self)
+        else:
+            return np.zeros(len(self))
+        
+    def get_magnetic_moment(self):
+        """Get calculated total magnetic moment."""
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        if self.calc.get_spin_polarized():
+            return self.calc.get_magnetic_moment(self)
+        else:
+            return 0.0
+
+    def set_charges(self, charges):
+        """Set charges."""
+        self.set_array('charges', charges, float, ())
+
+    def get_charges(self):
+        """Get array of charges."""
+        if 'charges' in self.arrays:
+            return self.arrays['charges'].copy()
+        else:
+            return np.zeros(len(self))
+
+    def set_positions(self, newpositions):
+        """Set positions."""
+        positions = self.arrays['positions']
+        if self.constraints:
+            newpositions = np.asarray(newpositions, float)
+            for constraint in self.constraints:
+                constraint.adjust_positions(positions, newpositions)
+                
+        self.set_array('positions', newpositions, shape=(3,))
+
+    def get_positions(self):
+        """Get array of positions."""
+        return self.arrays['positions'].copy()
+
+    def get_potential_energy(self):
+        """Calculate potential energy."""
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        return self.calc.get_potential_energy(self)
+
+    def get_potential_energies(self):
+        """Calculate the potential energies of all the atoms.
+
+        Only available with calculators supporting per-atom energies
+        (e.g. classical potentials).
+        """
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        return self.calc.get_potential_energies(self)
+
+    def get_kinetic_energy(self):
+        """Get the kinetic energy."""
+        momenta = self.arrays.get('momenta')
+        if momenta is None:
+            return 0.0
+        return 0.5 * np.vdot(momenta, self.get_velocities())
+
+    def get_velocities(self):
+        """Get array of velocities."""
+        momenta = self.arrays.get('momenta')
+        if momenta is None:
+            return None
+        m = self.arrays.get('masses')
+        if m is None:
+            m = atomic_masses[self.arrays['numbers']]
+        return momenta / m.reshape(-1, 1)
+
+    def get_total_energy(self):
+        """Get the total energy - potential plus kinetic energy."""
+        return self.get_potential_energy() + self.get_kinetic_energy()
+
+    def get_forces(self, apply_constraint=True):
+        """Calculate atomic forces.
+
+        Ask the attached calculator to calculate the forces and apply
+        constraints.  Use *apply_constraint=False* to get the raw
+        forces."""
+
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        forces = self.calc.get_forces(self)
+        if apply_constraint:
+            for constraint in self.constraints:
+                constraint.adjust_forces(self.arrays['positions'], forces)
+        return forces
+
+    def get_stress(self):
+        """Calculate stress tensor.
+
+        Returns an array of the six independent components of the
+        symmetric stress tensor, in the traditional order
+        (s_xx, s_yy, s_zz, s_yz, s_xz, s_xy).
+        """
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        stress = self.calc.get_stress(self)
+        shape = getattr(stress, "shape", None)
+        if shape == (3,3):
+            return np.array([stress[0,0], stress[1,1], stress[2,2],
+                             stress[1,2], stress[0,2], stress[0,1]])
+        else:
+            # Hopefully a 6-vector, but don't check in case some weird
+            # calculator does something else.
+            return stress
+    
+    def get_stresses(self):
+        """Calculate the stress-tensor of all the atoms.
+
+        Only available with calculators supporting per-atom energies and
+        stresses (e.g. classical potentials).  Even for such calculators
+        there is a certain arbitrariness in defining per-atom stresses.
+        """
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        return self.calc.get_stresses(self)
+
+    def get_dipole_moment(self):
+        """Calculate the electric dipole moment for the atoms object.
+
+        Only available for calculators which has a get_dipole_moment() method."""
+        if self.calc is None:
+            raise RuntimeError('Atoms object has no calculator.')
+        try:
+            dipole = self.calc.get_dipole_moment(self)
+        except AttributeError:
+            raise AttributeError('Calculator object has no get_dipole_moment method.')
+        return dipole
+    
+    def copy(self):
+        """Return a copy."""
+        import copy
+        atoms = self.__class__(cell=self._cell, pbc=self._pbc)
+
+        atoms.arrays = {}
+        for name, a in self.arrays.items():
+            atoms.arrays[name] = a.copy()
+        atoms.constraints = copy.deepcopy(self.constraints)
+        atoms.adsorbate_info = copy.deepcopy(self.adsorbate_info)
+        return atoms
+
+    def __len__(self):
+        return len(self.arrays['positions'])
+
+    def get_number_of_atoms(self):
+        """Returns the number of atoms.
+
+        Equivalent to len(atoms) in the standard ASE Atoms class.
+        """
+        return len(self)
+    
+    def __repr__(self):
+        num = self.get_atomic_numbers()
+        N = len(num)
+        if N == 0:
+            symbols = ''
+        elif N <= 60:
+            symbols = self.get_chemical_symbols(reduce=True)
+        else:
+            symbols = ''.join([chemical_symbols[Z] for Z in num[:15]]) + '...'
+        s = "%s(symbols='%s', " %(self.__class__.__name__, symbols)
+        for name in self.arrays:
+            if name == 'numbers':
+                continue
+            s += '%s=..., ' % name
+        if (self._cell - np.diag(self._cell.diagonal())).any():
+            s += 'cell=%s, ' % self._cell.tolist()            
+        else:
+            s += 'cell=%s, ' % self._cell.diagonal().tolist()
+        s += 'pbc=%s, ' % self._pbc.tolist()
+        if len(self.constraints) == 1:
+            s += 'constraint=%s, ' % repr(self.constraints[0])
+        if len(self.constraints) > 1:
+            s += 'constraint=%s, ' % repr(self.constraints)
+        if self.calc is not None:
+            s += 'calculator=%s(...), ' % self.calc.__class__.__name__
+        return s[:-2] + ')'
+
+    def __add__(self, other):
+        atoms = self.copy()
+        atoms += other
+        return atoms
+
+    def extend(self, other):
+        """Extend atoms object by appending atoms from *other*."""
+        if isinstance(other, Atom):
+            other = self.__class__([other])
+            
+        n1 = len(self)
+        n2 = len(other)
+        
+        for name, a1 in self.arrays.items():
+            a = np.zeros((n1 + n2,) + a1.shape[1:], a1.dtype)
+            a[:n1] = a1
+            a2 = other.arrays.get(name)
+            if a2 is not None:
+                a[n1:] = a2
+            self.arrays[name] = a
+
+        for name, a2 in other.arrays.items():
+            if name in self.arrays:
+                continue
+            a = np.zeros((n1 + n2,) + a2.shape[1:], a2.dtype)
+            a[n1:] = a2
+            self.set_array(name, a)
+
+        return self
+
+    __iadd__ = extend
+
+    def append(self, atom):
+        """Append atom to end."""
+        self.extend(self.__class__([atom]))
+
+    def __getitem__(self, i):
+        """Return a subset of the atoms.
+
+        i -- scalar integer, list of integers, or slice object
+        describing which atoms to return.
+
+        If i is a scalar, return an Atom object. If i is a list or a
+        slice, return an Atoms object with the same cell, pbc, and
+        other associated info as the original Atoms object. The
+        indices of the constraints will be shuffled so that they match
+        the indexing in the subset returned.
+
+        """
+        if isinstance(i, int):
+            natoms = len(self)
+            if i < -natoms or i >= natoms:
+                raise IndexError('Index out of range.')
+
+            return Atom(atoms=self, index=i)
+        
+        import copy
+        from ase.constraints import FixConstraint
+        
+        atoms = self.__class__(cell=self._cell, pbc=self._pbc)
+        # TODO: Do we need to shuffle indices in adsorbate_info too?
+        atoms.adsorbate_info = self.adsorbate_info
+        
+        atoms.arrays = {}
+        for name, a in self.arrays.items():
+            atoms.arrays[name] = a[i].copy()
+        
+        # Constraints need to be deepcopied, since we need to shuffle
+        # the indices
+        atoms.constraints = copy.deepcopy(self.constraints)
+        condel = []
+        for con in atoms.constraints:
+            if isinstance(con, FixConstraint):
+                try:
+                    con.index_shuffle(i)
+                except IndexError:
+                    condel.append(con)
+        for con in condel:
+            atoms.constraints.remove(con)
+        return atoms
+
+    def __delitem__(self, i):
+        if len(self._constraints) > 0:
+            raise RuntimeError('Remove constraint using set_constraint() ' +
+                               'before deleting atoms.')
+        mask = np.ones(len(self), bool)
+        mask[i] = False
+        for name, a in self.arrays.items():
+            self.arrays[name] = a[mask]
+
+    def pop(self, i=-1):
+        """Remove and return atom at index *i* (default last)."""
+        atom = self[i]
+        atom.cut_reference_to_atoms()
+        del self[i]
+        return atom
+    
+    def __imul__(self, m):
+        """In-place repeat of atoms."""
+        if isinstance(m, int):
+            m = (m, m, m)
+
+        M = np.product(m)
+        n = len(self)
+        
+        for name, a in self.arrays.items():
+            self.arrays[name] = np.tile(a, (M,) + (1,) * (len(a.shape) - 1))
+
+        positions = self.arrays['positions']
+        i0 = 0
+        for m2 in range(m[2]):
+            for m1 in range(m[1]):
+                for m0 in range(m[0]):
+                    i1 = i0 + n
+                    positions[i0:i1] += np.dot((m0, m1, m2), self._cell)
+                    i0 = i1
+
+        if self.constraints is not None:
+            self.constraints = [c.repeat(m, n) for c in self.constraints]
+
+        self._cell = np.array([m[c] * self._cell[c] for c in range(3)])
+
+        return self
+
+    def repeat(self, rep):
+        """Create new repeated atoms object.
+
+        The *rep* argument should be a sequence of three positive
+        integers like *(2,3,1)* or a single integer (*r*) equivalent
+        to *(r,r,r)*."""
+
+        atoms = self.copy()
+        atoms *= rep
+        return atoms
+
+    __mul__ = repeat
+    
+    def translate(self, displacement):
+        """Translate atomic positions.
+
+        The displacement argument can be a float an xyz vector or an
+        nx3 array (where n is the number of atoms)."""
+
+        self.arrays['positions'] += np.array(displacement)
+
+    def center(self, vacuum=None, axis=None):
+        """Center atoms in unit cell.
+
+        Centers the atoms in the unit cell, so there is the same
+        amount of vacuum on all sides.
+
+        Parameters:
+
+        vacuum (default: None): If specified adjust the amount of
+        vacuum when centering.  If vacuum=10.0 there will thus be 10
+        Angstrom of vacuum on each side.
+
+        axis (default: None): If specified, only act on the specified
+        axis.  Default: Act on all axes.
+        """
+        # Find the orientations of the faces of the unit cell
+        c = self.get_cell()
+        dirs = np.zeros_like(c)
+        for i in range(3):
+            dirs[i] = np.cross(c[i-1], c[i-2])
+            dirs[i] /= np.sqrt(np.dot(dirs[i], dirs[i])) #Normalize
+            if np.dot(dirs[i], c[i]) < 0.0:      
+                dirs[i] *= -1
+
+        # Now, decide how much each basis vector should be made longer
+        if axis is None:
+            axes = (0,1,2)
+        else:
+            axes = (axis,)
+        p = self.arrays['positions']
+        longer = np.zeros(3)
+        shift = np.zeros(3)
+        for i in axes:
+            p0 = np.dot(p, dirs[i]).min()
+            p1 = np.dot(p, dirs[i]).max()
+            height = np.dot(c[i], dirs[i])
+            if vacuum is not None:
+                lng = (p1 - p0 + 2*vacuum) - height
+            else:
+                lng = 0.0  # Do not change unit cell size!
+            top = lng + height - p1
+            shf = 0.5 * (top - p0)
+            cosphi = np.dot(c[i], dirs[i]) / np.sqrt(np.dot(c[i], c[i]))
+            longer[i] = lng / cosphi
+            shift[i] = shf / cosphi
+
+        # Now, do it!
+        translation = np.zeros(3)
+        for i in axes:
+            nowlen = np.sqrt(np.dot(c[i], c[i]))
+            self._cell[i] *= 1 + longer[i] / nowlen
+            translation += shift[i] * c[i] / nowlen
+        self.arrays['positions'] += translation
+
+    def get_center_of_mass(self, scaled = False):
+        """Get the center of mass.
+
+        If scaled=True the center of mass in scaled coordinates
+        is returned."""
+        m = self.arrays.get('masses')
+        if m is None:
+            m = atomic_masses[self.arrays['numbers']]
+        com = np.dot(m, self.arrays['positions']) / m.sum()
+        if scaled:
+            return np.dot(np.linalg.inv(self._cell), com)
+        else:
+            return com
+
+    def get_moments_of_inertia(self):
+        '''Get the moments of inertia
+
+        The three principal moments of inertia are computed from the
+        eigenvalues of the inertial tensor. periodic boundary
+        conditions are ignored. Units of the moments of inertia are
+        amu*angstrom**2.
+        '''
+        
+        com = self.get_center_of_mass()
+        positions = self.get_positions()
+        positions -= com #translate center of mass to origin
+        masses = self.get_masses()
+
+        #initialize elements of the inertial tensor
+        I11 = I22 = I33 = I12 = I13 = I23 = 0.0
+        for i in range(len(self)):
+            x,y,z = positions[i]
+            m = masses[i]
+
+            I11 += m*(y**2 + z**2)
+            I22 += m*(x**2 + z**2)
+            I33 += m*(x**2 + y**2)
+            I12 += -m*x*y
+            I13 += -m*x*z
+            I23 += -m*y*z
+
+        I = np.array([[I11, I12, I13],
+                      [I12, I22, I23],
+                      [I13, I23, I33]])
+
+        evals, evecs = np.linalg.eig(I)
+        return evals
+
+    def rotate(self, v, a=None, center=(0, 0, 0), rotate_cell=False):
+        """Rotate atoms.
+
+        Rotate the angle *a* around the vector *v*.  If *a* is not
+        given, the length of *v* is used as the angle.  If *a* is a
+        vector, then *v* is rotated into *a*.  The point at *center*
+        is fixed.  Use *center='COM'* to fix the center of mass.
+        Vectors can also be strings: 'x', '-x', 'y', ... .
+        If both *v* and *a* are vectors: rotate *v* into *a*.
+
+        Examples:
+
+        Rotate 90 degrees around the z-axis, so that the x-axis is
+        rotated into the y-axis:
+
+        >>> a = pi / 2
+        >>> atoms.rotate('z', a)
+        >>> atoms.rotate((0, 0, 1), a)
+        >>> atoms.rotate('-z', -a)
+        >>> atoms.rotate((0, 0, a))
+        >>> atoms.rotate('x', 'y')
+        """
+
+        norm = np.linalg.norm
+        v = string2vector(v)
+        if a is None:
+            a = norm(v)
+        if isinstance(a, (float, int)):
+            v /= norm(v)
+            c = cos(a)
+            s = sin(a)
+        else:
+            v2 = string2vector(a)
+            v /= norm(v)
+            v2 /= norm(v2)
+            c = np.dot(v, v2)
+            v = np.cross(v, v2)
+            s = norm(v)
+            # In case *v* and *a* are parallel, np.cross(v, v2) vanish
+            # and can't be used as a rotation axis. However, in this
+            # case any rotation axis perpendicular to v2 will do.
+            eps = 1e-7
+            if s < eps:
+                v = np.cross((0, 0, 1), v2)
+            if norm(v) < eps:
+                v = np.cross((1, 0, 0), v2)
+            assert norm(v) >= eps
+            if s > 0: v /= s
+        
+        if isinstance(center, str) and center.lower() == 'com':
+            center = self.get_center_of_mass()
+
+        p = self.arrays['positions'] - center
+        self.arrays['positions'][:] = (c * p - 
+                                       np.cross(p, s * v) + 
+                                       np.outer(np.dot(p, v), (1.0 - c) * v)+
+                                       center)
+        if rotate_cell:
+            rotcell = self.get_cell()
+            rotcell[:] = (c * rotcell - 
+                          np.cross(rotcell, s * v) + 
+                          np.outer(np.dot(rotcell, v), (1.0 - c) * v))
+            self.set_cell(rotcell)
+                
+    def rotate_euler(self, center=(0, 0, 0), phi=0.0, theta=0.0, psi=0.0):
+        """Rotate atoms via Euler angles.
+        
+        See e.g http://mathworld.wolfram.com/EulerAngles.html for explanation.
+        
+        Parameters:
+        
+        center :
+            The point to rotate about. A sequence of length 3 with the
+            coordinates, or 'COM' to select the center of mass.
+        phi :
+            The 1st rotation angle around the z axis.
+        theta :
+            Rotation around the x axis.
+        psi :
+            2nd rotation around the z axis.
+        
+        """
+        if isinstance(center, str) and center.lower() == 'com':
+            center = self.get_center_of_mass()
+        else:
+            center = np.array(center)
+        # First move the molecule to the origin In contrast to MATLAB,
+        # numpy broadcasts the smaller array to the larger row-wise,
+        # so there is no need to play with the Kronecker product.
+        rcoords = self.positions - center
+        # First Euler rotation about z in matrix form
+        D = np.array(((cos(phi), sin(phi), 0.),
+                      (-sin(phi), cos(phi), 0.),
+                      (0., 0., 1.)))
+        # Second Euler rotation about x:
+        C = np.array(((1., 0., 0.),
+                      (0., cos(theta), sin(theta)),
+                      (0., -sin(theta), cos(theta))))
+        # Third Euler rotation, 2nd rotation about z:
+        B = np.array(((cos(psi), sin(psi), 0.),
+                      (-sin(psi), cos(psi), 0.),
+                      (0., 0., 1.)))
+        # Total Euler rotation
+        A = np.dot(B, np.dot(C, D))
+        # Do the rotation
+        rcoords = np.dot(A, np.transpose(rcoords))
+        # Move back to the rotation point
+        self.positions = np.transpose(rcoords) + center
+
+    def get_dihedral(self,list):
+        """
+        calculate dihedral angle between the vectors list[0]->list[1] and list[2]->list[3], 
+        where list contains the atomic indexes in question. 
+        """
+        # vector 0->1, 1->2, 2->3 and their normalized cross products:
+        a    = self.positions[list[1]]-self.positions[list[0]]
+        b    = self.positions[list[2]]-self.positions[list[1]]
+        c    = self.positions[list[3]]-self.positions[list[2]]
+        bxa  = np.cross(b,a)
+        bxa /= np.sqrt(np.vdot(bxa,bxa))
+        cxb  = np.cross(c,b)
+        cxb /= np.sqrt(np.vdot(cxb,cxb))
+        angle = np.vdot(bxa,cxb)
+        # check for numerical trouble due to finite precision:
+        if angle < -1: angle = -1
+        if angle >  1: angle =  1
+        angle = np.arccos(angle)
+        if (np.vdot(bxa,c)) > 0: angle = 2*np.pi-angle
+        return angle
+
+    def set_dihedral(self,list,angle,mask=None):
+        """
+        set the dihedral angle between vectors list[0]->list[1] and 
+        list[2]->list[3] by changing the atom indexed by list[3]
+        if mask is not None, all the atoms described in mask 
+        (read: the entire subgroup) are moved
+        
+        example: the following defines a very crude 
+        ethane-like molecule and twists one half of it by 30 degrees.
+
+        >>> atoms = Atoms('HHCCHH',[[-1,1,0],[-1,-1,0],[0,0,0],[1,0,0],[2,1,0],[2,-1,0]])
+        >>> atoms.set_dihedral([1,2,3,4],7*pi/6,mask=[0,0,0,1,1,1])
+        """
+        # if not provided, set mask to the last atom in the dihedral description
+        if mask is None:
+            mask = np.zeros(len(self))
+            mask[list[3]] = 1
+        # compute necessary in dihedral change, from current value
+        current =self.get_dihedral(list)
+        diff    = angle - current
+        # do rotation of subgroup by copying it to temporary atoms object and then rotating that
+        axis   = self.positions[list[2]]-self.positions[list[1]]
+        center = self.positions[list[2]]
+        # recursive object definition might not be the most elegant thing, more generally useful might be a rotation function with a mask?
+        group  = self.__class__()
+        for i in range(len(self)):
+            if mask[i]:
+                group += self[i]
+        group.translate(-center)
+        group.rotate(axis,diff)
+        group.translate(center)
+        # set positions in original atoms object
+        j = 0
+        for i in range(len(self)):
+            if mask[i]:
+                self.positions[i] = group[j].get_position()
+                j += 1
+        
+    def rotate_dihedral(self,list,angle,mask=None):
+        """ complementing the two routines above: rotate a group by a predefined dihedral angle, 
+        starting from its current configuration
+        """
+        start = self.get_dihedral(list)
+        self.set_dihedral(list,angle+start,mask)
+
+    def rattle(self, stdev=0.001, seed=42):
+        """Randomly displace atoms.
+
+        This method adds random displacements to the atomic positions,
+        taking a possible constraint into account.  The random numbers are
+        drawn from a normal distribution of standard deviation stdev.
+
+        For a parallel calculation, it is important to use the same
+        seed on all processors!  """
+        
+        rs = np.random.RandomState(seed)
+        positions = self.arrays['positions']
+        self.set_positions(positions +
+                           rs.normal(scale=stdev, size=positions.shape))
+        
+    def get_distance(self, a0, a1, mic=False):
+        """Return distance between two atoms.
+
+        Use mic=True to use the Minimum Image Convention.
+        """
+
+        R = self.arrays['positions']
+        D = R[a1] - R[a0]
+        if mic:
+            raise NotImplemented  # XXX
+        return np.linalg.norm(D)
+
+    def set_distance(self, a0, a1, distance, fix=0.5):
+        """Set the distance between two atoms.
+
+        Set the distance between atoms *a0* and *a1* to *distance*.
+        By default, the center of the two atoms will be fixed.  Use
+        *fix=0* to fix the first atom, *fix=1* to fix the second
+        atom and *fix=0.5* (default) to fix the center of the bond."""
+
+        R = self.arrays['positions']
+        D = R[a1] - R[a0]
+        x = 1.0 - distance / np.linalg.norm(D)
+        R[a0] += (x * fix) * D
+        R[a1] -= (x * (1.0 - fix)) * D
+
+    def get_scaled_positions(self):
+        """Get positions relative to unit cell.
+
+        Atoms outside the unit cell will be wrapped into the cell in
+        those directions with periodic boundary conditions so that the
+        scaled coordinates are beween zero and one."""
+
+        scaled = np.linalg.solve(self._cell.T, self.arrays['positions'].T).T
+        for i in range(3):
+            if self._pbc[i]:
+                # Yes, we need to do it twice.
+                # See the scaled_positions.py test
+                scaled[:, i] %= 1.0
+                scaled[:, i] %= 1.0
+        return scaled
+
+    def set_scaled_positions(self, scaled):
+        """Set positions relative to unit cell."""
+        self.arrays['positions'][:] = np.dot(scaled, self._cell)
+
+    def get_temperature(self):
+        """Get the temperature. in Kelvin"""
+        ekin = self.get_kinetic_energy() / len(self)
+        return ekin /(1.5*units.kB)
+
+    def get_isotropic_pressure(self, stress):
+        """ get the current calculated pressure, assume isotropic medium.
+            in Bar
+        """
+        if type(stress) == type(1.0) or type(stress) == type(1):
+            return -stress * 1e-5 / units.Pascal
+        elif stress.shape == (3,3):
+            return (-(stress[0, 0] + stress[1, 1] + stress[2, 2]) / 3.0) * \
+                    1e-5 / units.Pascal
+        elif stress.shape == (6,):
+            return (-(stress[0] + stress[1] + stress[2]) / 3.0) * \
+                   1e-5 / units.Pascal
+        else:
+            raise ValueError, "The external stress has the wrong shape."
+
+
+
+    def __eq__(self, other):
+        """Check for identity of two atoms objects.
+
+        Identity means: same positions, atomic numbers, unit cell and
+        periodic boundary conditions."""
+        try:
+            a = self.arrays
+            b = other.arrays
+            return (len(self) == len(other) and
+                    (a['positions'] == b['positions']).all() and
+                    (a['numbers'] == b['numbers']).all() and
+                    (self._cell == other.cell).all() and
+                    (self._pbc == other.pbc).all())
+        except AttributeError:
+            return NotImplemented
+
+    def __ne__(self, other):
+        eq = self.__eq__(other)
+        if eq is NotImplemented:
+            return eq
+        else:
+            return not eq
+
+    __hash__ = None
+
+    def get_volume(self):
+        """Get volume of unit cell."""
+        return abs(np.linalg.det(self._cell))
+    
+    def _get_positions(self):
+        """Return reference to positions-array for inplace manipulations."""
+        return self.arrays['positions']
+
+    def _set_positions(self, pos):
+        """Set positions directly, bypassing constraints."""
+        self.arrays['positions'][:] = pos
+
+    positions = property(_get_positions, _set_positions,
+                         doc='Attribute for direct ' +
+                         'manipulation of the positions.')
+
+    def _get_atomic_numbers(self):
+        """Return reference to atomic numbers for inplace manipulations."""
+        return self.arrays['numbers']
+
+    numbers = property(_get_atomic_numbers, set_atomic_numbers,
+                       doc='Attribute for direct ' +
+                       'manipulation of the atomic numbers.')
+
+    def _get_cell(self):
+        """Return reference to unit cell for inplace manipulations."""
+        return self._cell
+    
+    cell = property(_get_cell, set_cell, doc='Attribute for direct ' +
+                       'manipulation of the unit cell.')
+
+    def _get_pbc(self):
+        """Return reference to pbc-flags for inplace manipulations."""
+        return self._pbc
+    
+    pbc = property(_get_pbc, set_pbc,
+                   doc='Attribute for direct manipulation ' +
+                   'of the periodic boundary condition flags.')
+
+    def get_name(self):
+        """Return a name extracted from the elements."""
+        elements = {}
+        for a in self:
+            try:
+                elements[a.symbol] += 1
+            except:
+                elements[a.symbol] = 1
+        name = ''
+        for element in elements:
+            name += element
+            if elements[element] > 1:
+                name += str(elements[element])
+        return name
+
+    def write(self, filename, format=None):
+        """Write yourself to a file."""
+        from ase.io import write
+        write(filename, self, format)
+        
+def string2symbols(s):
+    """Convert string to list of chemical symbols."""
+    n = len(s)
+
+    if n == 0:
+        return []
+    
+    c = s[0]
+    
+    if c.isdigit():
+        i = 1
+        while i < n and s[i].isdigit():
+            i += 1
+        return int(s[:i]) * string2symbols(s[i:])
+
+    if c == '(':
+        p = 0
+        for i, c in enumerate(s):
+            if c == '(':
+                p += 1
+            elif c == ')':
+                p -= 1
+                if p == 0:
+                    break
+        j = i + 1
+        while j < n and s[j].isdigit():
+            j += 1
+        if j > i + 1:
+            m = int(s[i + 1:j])
+        else:
+            m = 1
+        return m * string2symbols(s[1:i]) + string2symbols(s[j:])
+
+    if c.isupper():
+        i = 1
+        if 1 < n and s[1].islower():
+            i += 1
+        j = i
+        while j < n and s[j].isdigit():
+            j += 1
+        if j > i:
+            m = int(s[i:j])
+        else:
+            m = 1
+        return m * [s[:i]] + string2symbols(s[j:])
+    else:
+        raise ValueError
+
+def symbols2numbers(symbols):
+    if isinstance(symbols, str):
+        symbols = string2symbols(symbols)
+    numbers = []
+    for s in symbols:
+        if isinstance(s, str):
+            numbers.append(atomic_numbers[s])
+        else:
+            numbers.append(s)
+    return numbers
+
+def string2vector(v):
+    if isinstance(v, str):
+        if v[0] == '-':
+            return -string2vector(v[1:])
+        w = np.zeros(3)
+        w['xyz'.index(v)] = 1.0
+        return w
+    return np.array(v, float)
+
+def default(data, dflt):
+    """Helper function for setting default values."""
+    if data is None:
+        return None
+    elif isinstance(data, (list, tuple)):
+        newdata = []
+        allnone = True
+        for x in data:
+            if x is None:
+                newdata.append(dflt)
+            else:
+                newdata.append(x)
+                allnone = False
+        if allnone:
+            return None
+        return newdata
+    else:
+        return data
+                               
Index: ase/visualize/fieldplotter.py
===================================================================
--- ase/visualize/fieldplotter.py	(revision 0)
+++ ase/visualize/fieldplotter.py	(revision 1)
@@ -0,0 +1,272 @@
+"""plotting fields defined on atoms during a simulation."""
+
+from ase.visualize.primiplotter import PostScriptFile, PnmFile, GifFile, JpegFile, X11Window
+from ase.visualize.primiplotter import PrimiPlotter as _PrimiPlotter
+import numpy
+import time
+
+class FieldPlotter(_PrimiPlotter):
+    def __init__(self, atoms, datasource=None, verbose=0, timing=0,
+                 interval=1, initframe=0):
+        _PrimiPlotter.__init__(self, atoms, verbose=verbose, timing=timing,
+                               interval=interval, initframe=initframe)
+        self.datasource = datasource
+        self.dims = (100,100)
+        self.set_plot_plane("xy")
+        self.set_data_range("plot")
+        self.set_background(0.0)
+        self.set_red_yellow_colors()
+        
+    def set_plot_plane(self, plane):
+        """Set the plotting plane to xy, xz or yz (default: xy)"""
+        if plane in ("xy", "xz", "yz"):
+            self.plane = plane
+        else:
+            raise ValueError, "The argument to plotPlane must be 'xy', 'xz' or 'yz'."
+
+    def set_data_range(self, range1, range2=None):
+        """Set the range of the data used when coloring.
+
+        This function sets the range of data values mapped unto colors
+        in the final plot.
+        
+        Three possibilities:
+
+        'data':        Autoscale using the data on visible atoms.
+                       The range goes from the lowest to the highest
+                       value present on the atoms.  If only a few atoms
+                       have extreme values, the entire color range may not
+                       be used on the plot, as many values may be averaged
+                       on each point in the plot.
+
+        'plot':        Autoscale using the data on the plot.  Unlike 'data'
+                       this guarantees that the entire color range is used.
+
+        min, max:      Use the range [min, max]
+                       
+        """
+        if (range1 == "data" or range1 == "plot") and range2 == None:
+            self.autorange = range1
+        elif range2 != None:
+            self.autorange = None
+            self.range = (range1, range2)
+        else:
+            raise ValueError, "Illegal argument(s) to set_data_range"
+
+    def set_background(self, value):
+        """Set the data value of the background.  See also set_background_color
+
+        Set the value of the background (parts of the plot without atoms) to
+        a specific value, or to 'min' or 'max' representing the minimal or
+        maximal data values on the atoms.
+
+        Calling set_background cancels previous calls to set_background_color.
+        """
+        self.background = value
+        self.backgroundcolor = None
+
+    def set_background_color(self, color):
+        """Set the background color.  See also set_background.
+
+        Set the background color.  Use a single value in the range [0, 1[
+        for gray values, or a tuple of three such values as an RGB color.
+
+        Calling set_background_color cancels previous calls to set_background.
+        """
+        self.background = None
+        self.backgroundcolor = color
+
+    def set_red_yellow_colors(self, reverse=False):
+        """Set colors to Black-Red-Yellow-White (a.k.a. STM colors)"""
+        self.set_colors([(0.0, 0, 0, 0),
+                        (0.33, 1, 0, 0),
+                        (0.66, 1, 1, 0),
+                        (1.0, 1, 1, 1)],
+                       reverse)
+        
+    def set_black_white_colors(self, reverse=False):
+        """Set the color to Black-White (greyscale)"""
+        self.set_colors([(0.0, 0),  (1.0, 1)], reverse)
+
+    def set_colors(self, colors, reverse=False):
+        colors = numpy.array(colors, numpy.float)
+        if len(colors.shape) != 2:
+            raise ValueError, "Colors must be a 2D array."
+        if reverse:
+            colors[:,0] = 1 - colors[:,0]
+            colors = numpy.array(colors[::-1,:])
+            #print colors
+        if colors[0,0] != 0.0 or colors[-1,0] != 1.0:
+            raise ValueError, "First row must define the value 0 and last row must define the value 1"
+        if colors.shape[1] == 2:
+            self.colormode = 1
+        elif colors.shape[1] == 4:
+            self.colormode = 3
+        else:
+            raise ValueError, "Color specification must be Nx2 (grey) or Nx4 (rgb) matrix."
+        self.colorfunction = InterpolatingFunction(colors[:,0], colors[:,1:])
+        
+    def plot(self, data=None):
+        """Create a plot now.  Does not respect the interval timer.
+
+        This method makes a plot unconditionally.  It does not look at
+        the interval variable, nor is this plot taken into account in
+        the counting done by the update() method if an interval
+        variable was specified.
+
+        If data is specified, it must be an array of numbers with the
+        same length as the atoms.  That data will then be plotted.  If
+        no data is given, the data source specified when creating the
+        plotter is used.
+        
+        """
+        if self.timing:
+            self._starttimer()
+        self.log("FieldPlotter: Starting plot at "
+                 + time.strftime("%a, %d %b %Y %H:%M:%S"))
+        if data is None:
+            data = self.datasource()
+        if len(data) != len(self.atoms):
+            raise ValueError, ("Data has wrong length: %d instead of %d."
+                               % (len(data), len(self.atoms)))
+        
+        invisible = self._getinvisible()
+        coords = self._rotate(self._getpositions())
+        radii = self._getradii()
+        if self.autoscale:
+            self._autoscale(coords,radii)
+        scale = self.scale * self.relativescale
+        coords = scale * coords
+        center = self._getcenter(coords)
+        offset = numpy.array(self.dims + (0.0,))/2.0 - center
+        coords = coords + offset
+        radii = radii * scale
+        self.log("Scale is %f and size is (%d, %d)"
+                 % (scale, self.dims[0], self.dims[1]))
+        self.log("Physical size of plot is %f Angstrom times %f Angstrom"
+                 % (self.dims[0] / scale, self.dims[1] / scale))
+
+        # Remove invisible atoms
+        selector = numpy.logical_not(invisible)
+        coords = numpy.compress(selector, coords, 0)
+        radii = numpy.compress(selector, radii)
+        data = numpy.compress(selector, data)
+
+        self.log("plotting data in the range [%f,%f]" %
+                   (data.min(), data.max()))
+        # Now create the output array
+        sumarray = numpy.zeros(self.dims, numpy.float)
+        weight = numpy.zeros(self.dims)
+
+        # Loop over all atoms, and plot them
+        nmiss = 0
+        if self.plane == "xy":
+            xy = coords[:,:2]
+        elif self.plane == "xz":
+            xy = coords[:,::2]
+        elif self.plane == "yz":
+            xy = coords[:,1:]
+        else:
+            raise RuntimeError, "self.plane is bogus: "+str(self.plane)
+        assert xy.shape[1] == 2
+
+        self.log("plotting %d atoms on %d * %d (= %d) grid" %
+                   (len(xy), sumarray.shape[0], sumarray.shape[1],
+                    len(sumarray.flat)))
+                                                            
+        xy = xy.astype(numpy.int)
+        for i in xrange(len(xy)):
+            (x, y) = xy[i]
+            d = data[i]
+            if (x >= 0 and x < self.dims[0] and y >= 0 and y < self.dims[1]):
+                sumarray[x,y] += d
+                weight[x,y] += 1
+            else:
+                nmiss += 1
+        print "... %d atoms fell outside plot." % (nmiss,)
+
+        datamap = self._makedatamap(sumarray, weight, data.min(), data.max())
+        self.log("Range of data map: [%f, %f]" %
+                   (datamap.min(), datamap.max()))
+        plot = self._makeplotmap(datamap, weight)
+        #self.log("Range of plot: [%f, %f]" %
+        #           (min(plot.flat), max(plot.flat)))
+        examinplot = plot[:]
+        examinplot.shape = (plot.shape[0] * plot.shape[1],) + plot.shape[2:]
+        self.log("Range of plot: %s -> %s" %
+                 (str(examinplot.min(0)), str(examinplot.max(0))))
+        del examinplot
+        for device in self.outputdevice:
+            device.inform_about_scale(scale)
+            device.plotArray(self.n, numpy.swapaxes(plot,0,1))
+        self.n = self.n + 1
+        self.log("FieldPlotter: Finished plotting at "
+                 + time.strftime("%a, %d %b %Y %H:%M:%S"))
+        self.log("\n\n")
+
+        
+    def _makedatamap(self, sumarray, weight, minimum, maximum):
+        background = numpy.equal(weight, 0)
+        print "Number of background points:", sum(background.flat)
+        datamap = sumarray / numpy.where(background, 1, weight)
+        
+        if self.background is not None:
+            if self.background == "min":
+                bg = minimum
+            elif self.background == "max":
+                bg = maximum
+            else:
+                bg = self.background
+            datamap = numpy.where(background, bg, datamap)
+            
+        if self.autorange == "data":
+            datamap = (datamap - minimum) / (maximum - minimum)
+            self.log("Autorange using data.  Data range is [%f, %f]"
+                     % (minimum, maximum))
+        elif self.autorange == "plot":
+            ma = numpy.where(background, minimum, datamap).max()
+            mi = numpy.where(background, maximum, datamap).min()
+            datamap = (datamap - mi) / (ma - mi)
+            self.log("Autorange using plot.  Data range is [%f, %f]"
+                     % (mi, ma))
+        else:
+            assert self.autorange == None
+            datamap = (datamap - self.range[0]) / (self.range[1]
+                                                   - self.range[0])
+            datamap = numpy.clip(datamap, 0.0, 1.0)
+            self.log("Data range specified by user: [%f, %f]" % self.range)
+        datamap = numpy.where(background, bg, datamap)
+        assert datamap.min() >= 0 and datamap.max() <= 1.0
+        
+        return datamap
+
+    def _makeplotmap(self, datamap, weight):
+        plot = numpy.zeros(self.dims + (self.colormode,), numpy.float)
+        for i in range(self.dims[0]):
+            for j in range(self.dims[1]):
+                if self.backgroundcolor is not None and weight[i,j] == 0:
+                    plot[i,j,:] = self.backgroundcolor
+                else:
+                    x = datamap[i,j]
+                    plot[i,j,:] = self.colorfunction(x)
+        return plot
+    
+class InterpolatingFunction:
+    def __init__(self, xpoints, ypoints):
+        if len(xpoints) != len(ypoints):
+            raise ValueError, "Length of x and y arrays should be the same."
+        idx = xpoints.argsort()
+        self.xpoints = xpoints[idx]
+        self.ypoints = ypoints[idx]
+    def __call__(self, x):
+        n = self.xpoints.searchsorted(x)
+        if n == 0:
+            return self.ypoints[0]
+        if n == len(self.xpoints):
+            return self.xpoints[-1]
+        x0 = self.xpoints[n-1]
+        x1 = self.xpoints[n]
+        y0 = self.ypoints[n-1]
+        y1 = self.ypoints[n]
+        return y0 + (y1 - y0) / (x1 - x0) * (x - x0)
+    
Index: ase/visualize/vtk/atoms.py
===================================================================
--- ase/visualize/vtk/atoms.py	(revision 0)
+++ ase/visualize/vtk/atoms.py	(revision 1)
@@ -0,0 +1,127 @@
+
+import numpy as np
+
+from ase import Atoms
+
+from ase.visualize.vtk.sources import vtkAtomSource, vtkForceSource, \
+                                      vtkVelocitySource
+from ase.visualize.vtk.cell import vtkUnitCellModule, vtkAxesModule
+from ase.visualize.vtk.grid import vtkAtomicPositions
+from ase.visualize.vtk.module import vtkModuleAnchor, vtkGlyphModule
+
+# -------------------------------------------------------------------
+
+class vtkAtoms(vtkModuleAnchor, vtkAtomicPositions):
+    """Provides fundamental representation for ``Atoms``-specific data in VTK.
+
+    The ``vtkAtoms`` class plots atoms during simulations, extracting the
+    relevant information from the list of atoms. It is created using
+    the list of atoms as an argument to the constructor. Then one or more
+    visualization modules can be attached using add_module(name, module).
+
+    Example:
+
+    >>> va = vtkAtoms(atoms)
+    >>> va.add_forces()
+    >>> va.add_axes()
+    >>> XXX va.add_to_renderer(vtk_ren)
+
+    """
+    def __init__(self, atoms, scale=1):
+        """Construct a fundamental VTK-representation of atoms.
+
+        atoms: Atoms object or list of Atoms objects
+            The atoms to be plotted.
+
+        scale = 1: float or int
+            Relative scaling of all Atoms-specific visualization.
+
+        """
+        assert isinstance(atoms, Atoms)
+        self.atoms = atoms
+
+        self.scale = scale
+
+        vtkModuleAnchor.__init__(self)
+        vtkAtomicPositions.__init__(self, self.atoms.get_positions(),
+                                    vtkUnitCellModule(self.atoms))
+
+        self.force = None
+        self.velocity = None
+
+        symbols = self.atoms.get_chemical_symbols()
+        for symbol in np.unique(symbols):
+            # Construct mask for all atoms with this symbol
+            mask = np.array(symbols) == symbol
+            if mask.all():
+                subset = None
+            else:
+                subset = np.argwhere(mask).ravel()
+
+            # Get relevant VTK unstructured grid
+            vtk_ugd = self.get_unstructured_grid(subset)
+
+            # Create atomic glyph source for this symbol
+            glyph_source = vtkAtomSource(symbol, self.scale)
+
+            # Create glyph module and anchor it
+            self.add_module(symbol, vtkGlyphModule(vtk_ugd, glyph_source))
+
+    def has_forces(self):
+        return self.force is not None
+
+    def has_velocities(self):
+        return self.velocity is not None
+
+    def add_cell(self):
+        """Add a box outline of the cell using atoms.get_cell(). The existing
+        ``vtkUnitCellModule`` is added to the module anchor under ``cell``."""
+        self.add_module('cell', self.cell)
+
+    def add_axes(self):
+        """Add an orientation indicator for the cartesian axes. An appropriate
+        ``vtkAxesModule`` is added to the module anchor under ``axes``."""
+        self.add_module('axes', vtkAxesModule(self.cell))
+
+    def add_forces(self):
+        """Add force vectors for the atoms using atoms.get_forces(). A
+        ``vtkGlyphModule`` is added to the module anchor under ``force``."""
+        if self.has_forces():
+            raise RuntimeError('Forces already present.')
+        elif self.has_velocities():
+            raise NotImplementedError('Can\'t add forces due to velocities.')
+
+        # Add forces to VTK unstructured grid as vector data
+        vtk_fda = self.add_vector_property(self.atoms.get_forces(), 'force')
+
+        # Calculate max norm of the forces
+        fmax = vtk_fda.GetMaxNorm()
+
+        # Get relevant VTK unstructured grid
+        vtk_ugd = self.get_unstructured_grid()
+
+        self.force = vtkGlyphModule(vtk_ugd, vtkForceSource(fmax, self.scale),
+                                    scalemode='vector', colormode=None)
+        self.add_module('force', self.force)
+
+    def add_velocities(self):
+        """Add velocity vectors for the atoms using atoms.get_velocities(). A
+        ``vtkGlyphModule`` is added to the module anchor under ``velocity``."""
+        if self.has_velocities():
+            raise RuntimeError('Velocities already present.')
+        elif self.has_forces():
+            raise NotImplementedError('Can\'t add velocities due to forces.')
+
+        # Add velocities to VTK unstructured grid as vector data
+        vtk_vda = self.add_vector_property(self.atoms.get_velocities(), 'velocity')
+
+        # Calculate max norm of the velocities
+        vmax = vtk_vda.GetMaxNorm()
+
+        # Get relevant VTK unstructured grid
+        vtk_ugd = self.get_unstructured_grid()
+
+        self.velocity = vtkGlyphModule(vtk_ugd, vtkVelocitySource(vmax, self.scale),
+                                       scalemode='vector', colormode=None)
+        self.add_module('velocity', self.velocity)
+
Index: ase/visualize/vtk/sources.py
===================================================================
--- ase/visualize/vtk/sources.py	(revision 0)
+++ ase/visualize/vtk/sources.py	(revision 1)
@@ -0,0 +1,104 @@
+
+import numpy as np
+
+from vtk import vtkProperty, vtkSphereSource, vtkArrowSource, vtkConeSource
+
+from ase.data import atomic_numbers
+from ase.data import covalent_radii as atomic_radii
+from ase.data.colors import jmol_colors as atomic_colors
+#from ase.data.colors import cpk_colors as atomic_colors
+
+avg_radius = np.mean(atomic_radii[np.isfinite(atomic_radii)])
+
+# -------------------------------------------------------------------
+
+class vtkCustomGlyphSource:
+    def __init__(self, scale, vtk_glyph_source):
+        self.scale = scale
+        self.vtk_glyph_source = vtk_glyph_source
+        self.vtk_property = vtkProperty()
+
+    def get_scale(self):
+        return self.scale
+
+    def get_property(self):
+        return self.vtk_property
+
+    def get_output(self):
+        return self.vtk_glyph_source.GetOutput()
+
+class vtkAtomSource(vtkCustomGlyphSource):
+    def __init__(self, name, scale=1, fraction=0.25):
+        vtkCustomGlyphSource.__init__(self, scale, vtkSphereSource())
+
+        self.number = atomic_numbers[name]
+        self.radius = atomic_radii[self.number]
+        self.color = atomic_colors[self.number]
+
+        self.vtk_property.SetColor(self.color[0],self.color[1],self.color[2])
+        self.vtk_property.SetInterpolationToPhong()
+        self.vtk_property.SetDiffuse(0.7)
+        self.vtk_property.SetSpecular(0.4)
+        self.vtk_property.SetSpecularPower(20)
+
+        self.vtk_glyph_source.SetPhiResolution(16)
+        self.vtk_glyph_source.SetThetaResolution(16)
+        self.vtk_glyph_source.SetRadius(fraction*self.radius)
+
+# -------------------------------------------------------------------
+
+class vtkClampedGlyphSource(vtkCustomGlyphSource):
+    def __init__(self, scale, vtk_glyph_source, range_min, range_max):
+        vtkCustomGlyphSource.__init__(self, scale, vtk_glyph_source)
+        self.range = (range_min, range_max,)
+
+    def get_range(self):
+        return self.range
+
+class vtkForceSource(vtkClampedGlyphSource):
+    def __init__(self, maxnorm, scale=1):
+        vtkClampedGlyphSource.__init__(self, scale, vtkArrowSource(),
+                                       range_min=0.0, range_max=maxnorm)
+
+        self.vtk_property.SetColor(1.0, 0.25, 0.25) # forces are red
+        self.vtk_property.SetInterpolationToPhong()
+        self.vtk_property.SetDiffuse(0.7)
+        self.vtk_property.SetSpecular(0.4)
+        self.vtk_property.SetSpecularPower(20)
+
+        self.vtk_glyph_source.SetShaftResolution(12)
+        self.vtk_glyph_source.SetShaftRadius(0.03*avg_radius) #default 0.03
+        self.vtk_glyph_source.SetTipResolution(20)
+        self.vtk_glyph_source.SetTipLength(0.3*avg_radius) #default 0.35
+        self.vtk_glyph_source.SetTipRadius(0.1*avg_radius) #default 0.1
+
+class vtkVelocitySource(vtkClampedGlyphSource):
+    def __init__(self, maxnorm, scale=1):
+        vtkClampedGlyphSource.__init__(self, scale, vtkConeSource(),
+                                       range_min=0.0, range_max=maxnorm)
+
+        self.vtk_property.SetColor(0.25, 0.25, 1.0) # velocities blue
+        self.vtk_property.SetInterpolationToPhong()
+        self.vtk_property.SetDiffuse(0.9)
+        self.vtk_property.SetSpecular(1.0)
+        self.vtk_property.SetSpecularPower(50)
+
+        self.vtk_glyph_source.SetAngle(6)
+        self.vtk_glyph_source.SetHeight(avg_radius)
+        self.vtk_glyph_source.SetResolution(16)
+        self.vtk_glyph_source.SetCenter(0.05*avg_radius, 0.0, 0.0)
+
+# -------------------------------------------------------------------
+
+class vtkBondSource(vtkCustomGlyphSource):
+    def __init__(self, width, scale=1):
+        vtkCustomGlyphSource.__init__(self, scale, vtkCylinderSource())
+
+        self.width = width
+
+        self.vtk_property.SetColor(0.25, 1.0, 0.25) # bonds are green
+                                                    # (and so are you)
+
+        self.vtk_glyph_source.SetRadius(self.scale*self.width)
+        self.vtk_glyph_source.SetResolution(16)
+
Index: ase/visualize/vtk/grid.py
===================================================================
--- ase/visualize/vtk/grid.py	(revision 0)
+++ ase/visualize/vtk/grid.py	(revision 1)
@@ -0,0 +1,315 @@
+
+import numpy as np
+
+from vtk import vtkPointData, vtkDataArray, vtkUnstructuredGrid, vtkPoints, \
+                vtkIdList, vtkStructuredPoints
+from ase.visualize.vtk.cell import vtkUnitCellModule
+from ase.visualize.vtk.data import vtkDataArrayFromNumPyBuffer, \
+                                   vtkDoubleArrayFromNumPyArray, \
+                                   vtkDoubleArrayFromNumPyMultiArray
+
+# -------------------------------------------------------------------
+
+class vtkBaseGrid:
+    def __init__(self, npoints, cell):
+        self.npoints = npoints
+
+        # Make sure cell argument is correct type
+        assert isinstance(cell, vtkUnitCellModule)
+        self.cell = cell
+
+        self.vtk_pointdata = None
+
+    def set_point_data(self, vtk_pointdata):
+        if self.vtk_pointdata is not None:
+            raise RuntimeError('VTK point data already present.')
+
+        assert isinstance(vtk_pointdata, vtkPointData)
+        self.vtk_pointdata = vtk_pointdata
+        #self.vtk_pointdata.SetCopyScalars(False)
+        #self.vtk_pointdata.SetCopyVectors(False)
+        #self.vtk_pointdata.SetCopyNormals(False)
+
+    def get_point_data(self):
+        if self.vtk_pointdata is None:
+            raise RuntimeError('VTK point data missing.')
+
+        return self.vtk_pointdata
+
+    def get_number_of_points(self):
+        return self.npoints
+
+    def add_scalar_data_array(self, data, name=None, active=True):
+
+        # Are we converting from NumPy buffer to VTK array?
+        if isinstance(data, vtkDataArray):
+            vtk_sda = data
+        elif isinstance(data, vtkDataArrayFromNumPyBuffer):
+            vtk_sda = data.get_output()
+        else:
+            raise ValueError('Data is not a valid scalar data array.')
+
+        del data
+
+        assert vtk_sda.GetNumberOfComponents() == 1
+        assert vtk_sda.GetNumberOfTuples() == self.npoints
+
+        if name is not None:
+            vtk_sda.SetName(name)
+
+        # Add VTK array to VTK point data
+        self.vtk_pointdata.AddArray(vtk_sda)
+
+        if active:
+            self.vtk_pointdata.SetActiveScalars(name)
+
+        return vtk_sda
+
+    def add_vector_data_array(self, data, name=None, active=True):
+
+        # Are we converting from NumPy buffer to VTK array?
+        if isinstance(data, vtkDataArray):
+            vtk_vda = data
+        elif isinstance(data, vtkDataArrayFromNumPyBuffer):
+            vtk_vda = data.get_output()
+        else:
+            raise ValueError('Data is not a valid vector data array.')
+
+        del data
+
+        assert vtk_vda.GetNumberOfComponents() == 3
+        assert vtk_vda.GetNumberOfTuples() == self.npoints
+
+        if name is not None:
+            vtk_vda.SetName(name)
+
+        # Add VTK array to VTK point data
+        self.vtk_pointdata.AddArray(vtk_vda)
+
+        if active:
+            self.vtk_pointdata.SetActiveVectors(name)
+
+        return vtk_vda
+
+# -------------------------------------------------------------------
+
+class vtkAtomicPositions(vtkBaseGrid):
+    """Provides an interface for adding ``Atoms``-centered data to VTK
+    modules. Atomic positions, e.g. obtained using atoms.get_positions(),
+    constitute an unstructured grid in VTK, to which scalar and vector
+    can be added as point data sets.
+
+    Just like ``Atoms``, instances of ``vtkAtomicPositions`` can be divided
+    into subsets, which makes it easy to select atoms and add properties.
+
+    Example:
+
+    >>> cell = vtkUnitCellModule(atoms)
+    >>> apos = vtkAtomicPositions(atoms.get_positions(), cell)
+    >>> apos.add_scalar_property(atoms.get_charges(), 'charges')
+    >>> apos.add_vector_property(atoms.get_forces(), 'forces')
+
+    """
+    def __init__(self, pos, cell):
+        """Construct basic VTK-representation of a set of atomic positions.
+
+        pos: NumPy array of dtype float and shape ``(n,3)``
+            Cartesian positions of the atoms.
+        cell: Instance of vtkUnitCellModule of subclass thereof
+            Holds information equivalent to that of atoms.get_cell().
+
+        """
+        # Make sure position argument is a valid array
+        if not isinstance(pos, np.ndarray):
+            pos = np.array(pos)
+
+        assert pos.dtype == float and pos.shape[1:] == (3,)
+
+        vtkBaseGrid.__init__(self, len(pos), cell)
+
+        # Convert positions to VTK array
+        npy2da = vtkDoubleArrayFromNumPyArray(pos)
+        vtk_pda = npy2da.get_output()
+        del npy2da
+
+        # Transfer atomic positions to VTK points
+        self.vtk_pts = vtkPoints()
+        self.vtk_pts.SetData(vtk_pda)
+
+        # Create a VTK unstructured grid of these points
+        self.vtk_ugd = vtkUnstructuredGrid()
+        self.vtk_ugd.SetWholeBoundingBox(self.cell.get_bounding_box())
+        self.vtk_ugd.SetPoints(self.vtk_pts)
+
+        # Extract the VTK point data set
+        self.set_point_data(self.vtk_ugd.GetPointData())
+
+    def get_points(self, subset=None):
+        """Return (subset of) vtkPoints containing atomic positions.
+
+        subset=None: list of int
+            A list of indices into the atomic positions; ignored if None.
+
+        """
+        if subset is None:
+            return self.vtk_pts
+
+        # Create a list of indices from the subset
+        vtk_il = vtkIdList()
+        for i in subset:
+            vtk_il.InsertNextId(i)
+
+        # Allocate VTK points for subset
+        vtk_subpts = vtkPoints()
+        vtk_subpts.SetDataType(self.vtk_pts.GetDataType())
+        vtk_subpts.SetNumberOfPoints(vtk_il.GetNumberOfIds())
+
+        # Transfer subset of VTK points
+        self.vtk_pts.GetPoints(vtk_il, vtk_subpts)
+
+        return vtk_subpts
+
+    def get_unstructured_grid(self, subset=None):
+        """Return (subset of) an unstructured grid of the atomic positions.
+
+        subset=None: list of int
+            A list of indices into the atomic positions; ignored if None.
+
+        """
+        if subset is None:
+            return self.vtk_ugd
+
+        # Get subset of VTK points
+        vtk_subpts = self.get_points(subset)
+
+        # Create a VTK unstructured grid of these points
+        vtk_subugd = vtkUnstructuredGrid()
+        vtk_subugd.SetWholeBoundingBox(self.cell.get_bounding_box())
+        vtk_subugd.SetPoints(vtk_subpts)
+
+        return vtk_subugd
+
+    def add_scalar_property(self, data, name=None, active=True):
+        """Add VTK-representation of scalar data at the atomic positions.
+
+        data: NumPy array of dtype float and shape ``(n,)``
+            Scalar values corresponding to the atomic positions.
+        name=None: str
+            Unique identifier for the scalar data.
+        active=True: bool
+            Flag indicating whether to use as active scalar data.
+
+        """
+        # Make sure data argument is a valid array
+        if not isinstance(data, np.ndarray):
+            data = np.array(data)
+
+        assert data.dtype == float and data.shape == (self.npoints,)
+
+        # Convert scalar properties to VTK array
+        npa2da = vtkDoubleArrayFromNumPyArray(data)
+        return vtkBaseGrid.add_scalar_data_array(self, npa2da, name, active)
+
+    def add_vector_property(self, data, name=None, active=True):
+        """Add VTK-representation of vector data at the atomic positions.
+
+        data: NumPy array of dtype float and shape ``(n,3)``
+            Vector components corresponding to the atomic positions.
+        name=None: str
+            Unique identifier for the vector data.
+        active=True: bool
+            Flag indicating whether to use as active vector data.
+
+        """
+        # Make sure data argument is a valid array
+        if not isinstance(data, np.ndarray):
+            data = np.array(data)
+
+        assert data.dtype == float and data.shape == (self.npoints,3,)
+
+        # Convert vector properties to VTK array
+        npa2da = vtkDoubleArrayFromNumPyArray(data)
+        return vtkBaseGrid.add_vector_data_array(self, npa2da, name, active)
+
+# -------------------------------------------------------------------
+
+class vtkVolumeGrid(vtkBaseGrid):
+    def __init__(self, elements, cell, origin=None):
+
+        # Make sure element argument is a valid array
+        if not isinstance(elements, np.ndarray):
+            elements = np.array(elements)
+
+        assert elements.dtype == int and elements.shape == (3,)
+        self.elements = elements
+
+        vtkBaseGrid.__init__(self, np.prod(self.elements), cell)
+
+        # Create a VTK grid of structured points
+        self.vtk_spts = vtkStructuredPoints()
+        self.vtk_spts.SetWholeBoundingBox(self.cell.get_bounding_box())
+        self.vtk_spts.SetDimensions(self.elements)
+        self.vtk_spts.SetSpacing(self.get_grid_spacing())
+
+        if origin is not None:
+            self.vtk_spts.SetOrigin(origin)
+
+        # Extract the VTK point data set
+        self.set_point_data(self.vtk_spts.GetPointData())
+
+    def get_grid_spacing(self):
+        # Periodic boundary conditions leave out one boundary along an axis
+        # Zero/fixed boundary conditions leave out both boundaries of an axis
+        return self.cell.get_size()/(self.elements+1.0-self.cell.get_pbc())
+
+    def get_relaxation_factor(self):
+        # The relaxation factor is a floating point value between zero and one.
+        # It expresses the need for smoothening (relaxation) e.g. of isosurfaces
+        # due to coarse grid spacings. Larger grid spacing -> larger relaxation.
+        x = self.get_grid_spacing().mean()/self.cell.get_characteristic_length()
+
+        # The relaxation function f(x) satisfies the following requirements
+        # f(x) -> 0 for x -> 0+   and   f(x) -> b for x -> inf
+        # f'(x) -> a for x -> 0+  and   f'(x) -> 0 for x -> inf
+
+        # Furthermore, it is a rescaling of arctan, hence we know
+        # f(x) = 2 b arctan(a pi x / 2 b) / pi
+
+        # Our reference point is x = r for which medium relaxion is needed
+        # f(r) = b/2   <=>   r = 2 b / a pi   <=>   a = 2 b / r pi
+        r = 0.025 # corresponding to 0.2 Ang grid spacing in 8 Ang cell
+        b = 0.5
+        f = 2*b*np.arctan(x/r)/np.pi
+
+        if f > 0.1:
+           return f.round(1)
+        else:
+           return None
+
+    def get_structured_points(self):
+        return self.vtk_spts
+
+    def add_scalar_field(self, data, name=None, active=True):
+
+        # Make sure data argument is a valid array
+        if not isinstance(data, np.ndarray):
+            data = np.array(data)
+
+        assert data.dtype == float and data.shape == tuple(self.elements)
+
+        # Convert scalar field to VTK array
+        npa2da = vtkDoubleArrayFromNumPyMultiArray(data[...,np.newaxis])
+        return vtkBaseGrid.add_scalar_data_array(self, npa2da, name, active)
+
+    def add_vector_field(self, data, name=None, active=True):
+
+        # Make sure data argument is a valid array
+        if not isinstance(data, np.ndarray):
+            data = np.array(data)
+
+        assert data.dtype == float and data.shape == tuple(self.elements)+(3,)
+
+        # Convert vector field to VTK array
+        npa2da = vtkDoubleArrayFromNumPyMultiArray(data)
+        return vtkBaseGrid.add_vector_data_array(self, npa2da, name, active)
+
Index: ase/visualize/vtk/module.py
===================================================================
--- ase/visualize/vtk/module.py	(revision 0)
+++ ase/visualize/vtk/module.py	(revision 1)
@@ -0,0 +1,211 @@
+
+import numpy as np
+
+from vtk import vtkProp3D, vtkPolyDataMapper, vtkActor, vtkLODActor, \
+                vtkPointSet, vtkGlyph3D, vtkRenderer
+from ase.visualize.vtk.sources import vtkCustomGlyphSource, \
+                                      vtkClampedGlyphSource
+
+# -------------------------------------------------------------------
+
+class vtkModule:
+    """Modules represent a unified collection of VTK objects needed for
+    introducing basic visualization concepts such as surfaces or shapes.
+
+    A common trait of all modules is the need for an actor representation and
+    corresponding generic properties such as lighting, color and transparency.
+
+    """ 
+    def __init__(self, vtk_act, vtk_property=None):
+        """Construct basic VTK-representation of a module.
+
+        vtk_act: Instance of vtkActor or subclass thereof
+            A vtkActor represents an entity in a rendering scene.
+
+        vtk_property = None: Instance of vtkProperty or subclass thereof
+            A vtkProperty represents e.g. lighting and other surface
+            properties of a geometric object, in this case the actor.
+
+        """
+        self.vtk_act = None
+        self.set_actor(vtk_act)
+
+        if vtk_property is not None:
+            self.set_property(vtk_property)
+
+    def set_actor(self, vtk_act):
+        """Set the actor representing this module in a rendering scene."""
+        assert isinstance(vtk_act, vtkActor)
+        self.vtk_act = vtk_act
+
+    def set_property(self, vtk_property):
+        """Set the property of the actor representing this module."""
+        self.vtk_act.SetProperty(vtk_property)
+
+    def get_actor(self):
+        """Return the actor representing this module in a rendering scene."""
+        return self.vtk_act
+
+# -------------------------------------------------------------------
+
+class vtkLODModule(vtkModule):
+    _vtk_actor_class = vtkLODActor
+
+    def get_lod(self):
+        return 100
+
+    def set_actor(self, vtk_act):
+        vtkModule.set_actor(self, vtk_act)
+
+        if isinstance(vtk_act, vtkLODActor):
+            vtk_act.SetNumberOfCloudPoints(self.get_lod())
+
+class vtkPolyDataModule(vtkModule):
+    _vtk_actor_class = vtkActor
+
+    __doc__ = vtkModule.__doc__ + """
+    Poly data modules are based on polygonal data sets, which can be mapped
+    into graphics primitives suitable for rendering within the VTK framework.
+
+    """
+    def __init__(self, vtk_polydata, vtk_property=None):
+        """Construct VTK-representation of a module containing polygonals.
+
+        vtk_polydata: Instance of vtkPolyData, subclass thereof or
+            vtkPolyDataAlgorithm, which produces vtkPolyData as output.
+            A vtkPolyData represents a polygonal data set consisting of
+            point and cell attributes, which can be mapped to graphics
+            primitives for subsequent rendering.
+
+        vtk_property = None: Instance of vtkProperty or subclass thereof
+            A vtkProperty represents e.g. lighting and other surface
+            properties of a geometric object, in this case the polydata.
+
+        """
+        vtkModule.__init__(self, self._vtk_actor_class(), vtk_property)
+
+        self.vtk_dmap = vtkPolyDataMapper()
+        self.vtk_dmap.SetInputConnection(vtk_polydata.GetOutputPort())
+        self.vtk_act.SetMapper(self.vtk_dmap)
+
+class vtkGlyphModule(vtkPolyDataModule):
+    __doc__ = vtkPolyDataModule.__doc__ + """
+    Glyph modules construct these polygonal data sets by replicating a glyph
+    source across a specific set of points, using available scalar or vector
+    point data to scale and orientate the glyph source if desired.
+
+    Example:
+
+    >>> atoms = molecule('C60')
+    >>> cell = vtkUnitCellModule(atoms)
+    >>> apos = vtkAtomicPositions(atoms.get_positions(), cell)
+    >>> vtk_ugd = apos.get_unstructured_grid()
+    >>> glyph_source = vtkAtomSource('C')
+    >>> glyph_module = vtkGlyphModule(vtk_ugd, glyph_source)
+
+    """
+    def __init__(self, vtk_pointset, glyph_source,
+                 scalemode=None, colormode=None):
+        """Construct VTK-representation of a module containing glyphs.
+        These glyphs share a common source, defining their geometrical
+        shape, which is cloned and oriented according to the input data.
+
+        vtk_pointset: Instance of vtkPointSet or subclass thereof
+            A vtkPointSet defines a set of positions, which may then be
+            assigned specific scalar of vector data across the entire set.
+
+        glyph_source: Instance of ~vtk.vtkCustomGlyphSource or subclass thereof
+            Provides the basic shape to distribute over the point set.
+
+        """
+        assert isinstance(vtk_pointset, vtkPointSet)
+        assert isinstance(glyph_source, vtkCustomGlyphSource)
+
+        # Create VTK Glyph3D based on unstructured grid
+        self.vtk_g3d = vtkGlyph3D()
+        self.vtk_g3d.SetInput(vtk_pointset)
+        self.vtk_g3d.SetSource(glyph_source.get_output())
+        self.vtk_g3d.SetScaleFactor(glyph_source.get_scale())
+
+        # Clamping normalizes the glyph scaling to within the range [0,1].
+        # Setting a scale factor will then scale these by the given factor.
+        if isinstance(glyph_source, vtkClampedGlyphSource):
+            self.vtk_g3d.SetClamping(True)
+            self.vtk_g3d.SetRange(glyph_source.get_range())
+
+        if scalemode is 'off':
+            self.vtk_g3d.SetScaleModeToDataScalingOff()
+        elif scalemode is 'scalar':
+            self.vtk_g3d.SetScaleModeToScaleByScalar()
+        elif scalemode is 'vector':
+            self.vtk_g3d.SetScaleModeToScaleByVector()
+        elif scalemode is not None:
+            raise ValueError('Unrecognized scale mode \'%s\'.' % scalemode)
+
+        if colormode is 'scale':
+            self.vtk_g3d.SetColorModeToColorByScale()
+        elif colormode is 'scalar':
+            self.vtk_g3d.SetColorModeToColorByScalar()
+        elif colormode is 'vector':
+            self.vtk_g3d.SetColorModeToColorByVector()
+        elif colormode is not None:
+            raise ValueError('Unrecognized scale mode \'%s\'.' % scalemode)
+
+        vtkPolyDataModule.__init__(self, self.vtk_g3d, glyph_source.get_property())
+
+# -------------------------------------------------------------------
+
+class vtkLabelModule(vtkModule):
+    def __init__(self, vtk_pointset, vtk_property=None):
+
+        vtk_Module.__init__(self, vtkActor(), vtk_property)
+
+        assert isinstance(vtk_pointset, vtkPointSet)
+
+        self.vtk_dmap = vtkLabeledDataMapper()
+        #self.vtk_dmap.SetLabelModeToLabelIds() #TODO XXX strings!!!
+        self.vtk_dmap.GetLabelTextProperty().SetFontSize(12)
+        self.vtk_dmap.GetLabelTextProperty().SetJustificationToRight()
+        self.vtk_dmap.SetInputConnection(vtk_pointset.GetOutputPort())
+        self.vtk_act.SetMapper(self.vtk_dmap)
+
+        #vtk_g3d.GetPointIdsName...
+
+# -------------------------------------------------------------------
+
+class vtkModuleAnchor:
+    """
+    TODO XXX
+    """
+    def __init__(self):
+        """Construct an anchoring point for various VTK modules.
+
+        """
+        self.modules = {}
+
+    def get_module(self, name):
+        if name not in self.modules.keys():
+            raise RuntimeError('Module \'%s\' does not exists.' % name)
+
+        return self.modules[name]
+
+    def add_module(self, name, module):
+        if not isinstance(module, vtkModule):
+            raise ValueError('Module must be instance of vtkModule.')
+
+        if name in self.modules.keys():
+            raise RuntimeError('Module \'%s\' already exists.' % name)
+
+        self.modules[name] = module
+
+    def get_actor(self, name):
+        return self.get_module(name).get_actor()
+
+    def add_actors_to_renderer(self, vtk_renderer, name=None):
+        assert isinstance(vtk_renderer, vtkRenderer)
+        if name is None:
+            for module in self.modules.values():
+                vtk_renderer.AddActor(module.get_actor())
+        else:
+            vtk_renderer.AddActor(self.get_actor(name))
+
Index: ase/visualize/vtk/pipeline.py
===================================================================
--- ase/visualize/vtk/pipeline.py	(revision 0)
+++ ase/visualize/vtk/pipeline.py	(revision 1)
@@ -0,0 +1,213 @@
+
+import numpy as np
+
+from vtk import vtkPolyDataMapper, vtkPolyDataNormals, \
+                vtkLinearSubdivisionFilter, vtkSmoothPolyDataFilter, \
+                vtkDepthSortPolyData, vtkAlgorithm, vtkAlgorithmOutput
+from ase.visualize.vtk.grid import vtkVolumeGrid
+
+# -------------------------------------------------------------------
+
+class vtkPipeline:
+    _branchable = False
+
+    def __init__(self, vtkish_data=None):
+
+        self.vtkish_data = None
+        self.closed = False
+        self.pending = False
+        self.filters = tuple()
+
+        if vtkish_data is not None:
+            self.set_data(vtkish_data)
+
+    def hasdata(self):
+        return (self.vtkish_data is not None)
+
+    def hasfilters(self):
+        return len(self.filters)>0
+
+    def set_data(self, vtkish_data):
+
+        assert vtkish_data is not None
+
+        if self.hasdata():
+            raise RuntimeError('Pipeline already has input.')
+        elif vtkish_data in self:
+            raise ValueError('Pipeline loop detected.')
+
+        if isinstance(vtkish_data, vtkPipeline):
+            # The embedded pipeline takes over
+            vtkish_data.signal_close()
+
+        self.vtkish_data = vtkish_data
+
+        if not self.hasfilters():
+            return
+
+        vtkish_inner = self.filters[0]
+        
+        if isinstance(vtkish_inner, vtkPipeline):
+            vtkish_inner.set_data(vtkish_data) #TODO does this work?
+        else:
+            assert isinstance(vtkish_inner, vtkAlgorithm)
+
+            if isinstance(vtkish_data, vtkPipeline):
+                # The embedded pipeline takes over
+                vtkish_data.connect(vtkish_inner)
+            else:
+                assert isinstance(vtkish_data, vtkAlgorithm)
+                vtkish_inner.SetInputConnection(vtkish_data.GetOutputPort())
+
+    def isempty(self):
+        return not self.hasdata() and not self.hasfilters()
+
+    def isclosed(self):
+        if self.pending:
+            raise RuntimeError('Pipeline output port state is pending.')
+        return self.closed
+
+    def signal_close(self):
+        if self.closed:
+            raise RuntimeError('Pipeline output port is already closed.')
+        elif not self._branchable:
+            self.pending = True
+
+    def get_output_port(self):
+        if self.closed:
+            raise RuntimeError('Pipeline output port is closed.')
+        elif self.pending:
+            self.closed = True
+            self.pending = False
+
+        if self.hasfilters():
+            vtkish_outer = self.filters[-1]
+        elif self.hasdata():
+            vtkish_outer = self.vtkish_data
+        else:
+            raise RuntimeError('Pipeline output port unavailable.')
+
+        if isinstance(vtkish_outer, vtkPipeline):
+            return vtkish_outer.get_output_port()
+        else:
+            assert isinstance(vtkish_outer, vtkAlgorithm)
+            return vtkish_outer.GetOutputPort()
+
+    def set_input_connection(self, vtk_port):
+        assert isinstance(vtk_port, vtkAlgorithmOutput)
+
+        vtkish_inner = self.filters[0]
+
+        if isinstance(vtkish_inner, vtkPipeline):
+            # Connect must be passed down
+            vtkish_inner.set_input_connection(vtk_port)
+        else:
+            vtkish_inner.SetInputConnection(vtk_port)
+
+    def connect(self, vtkish_filter):
+        if vtkish_filter in self:
+            raise ValueError('Pipeline loop detected.')
+
+        if isinstance(vtkish_filter, vtkPipeline):
+            # Connection must be passed down
+            if not self.isempty():
+                vtkish_filter.set_input_connection(self.get_output_port())
+
+            # The containing pipeline takes over
+            vtkish_filter.signal_close()
+
+        elif not self.isempty():
+            assert isinstance(vtkish_filter, vtkAlgorithm)
+            vtkish_filter.SetInputConnection(self.get_output_port())
+
+    def append(self, vtkish_filter):
+        self.connect(vtkish_filter)
+        self.filters += (vtkish_filter,)
+
+    def extend(self, vtkish_filters):
+        map(self.append, vtkish_filters)
+
+    def __contains__(self, vtkish_candidate):
+        if vtkish_candidate == self:
+            return True
+
+        if self.hasdata():
+            if isinstance(self.vtkish_data, vtkPipeline):
+                if vtkish_candidate in self.vtkish_data:
+                    return True
+            else:
+                if vtkish_candidate == self.vtkish_data:
+                    return True
+
+        if vtkish_candidate in self.filters:
+            return True
+
+        for vtkish_filter in self.filters:
+            if isinstance(vtkish_filter, vtkPipeline) \
+                and vtkish_candidate in vtkish_filter:
+                return True
+
+        return False
+
+    def __getitem__(self, i): #TODO XXX this is a hack
+        return self.filters[i]
+
+# -------------------------------------------------------------------
+
+class vtkPolyDataPipeline(vtkPipeline):
+    def __init__(self, vtkish_polydata=None):
+        vtkPipeline.__init__(self, vtkish_polydata)
+
+    def connect(self, vtkish_filter):
+        if isinstance(vtkish_filter, vtkPolyDataMapper):
+            self.signal_close()
+        vtkPipeline.connect(self, vtkish_filter)
+
+class vtkSurfaceSmootherPipeline(vtkPolyDataPipeline):
+    def __init__(self, grid, vtkish_polydata=None, angle=15):
+
+        vtkPolyDataPipeline.__init__(self, vtkish_polydata)
+
+        # Make sure grid argument is correct type
+        assert isinstance(grid, vtkVolumeGrid)
+        self.grid = grid
+
+        # Split polys with intersection angles greater than angle
+        vtk_dnorm = vtkPolyDataNormals()
+        vtk_dnorm.SetFeatureAngle(angle)
+        vtk_dnorm.SplittingOn()
+        vtk_dnorm.ComputeCellNormalsOff()
+        vtk_dnorm.ComputePointNormalsOff()
+        self.append(vtk_dnorm)
+
+        relax = self.grid.get_relaxation_factor()
+
+        if relax is not None:
+            print 'relax=',relax
+            #vtk_subdiv = vtkButterflySubdivisionFilter()
+            vtk_subdiv = vtkLinearSubdivisionFilter()
+            self.append(vtk_subdiv)
+    
+            # Smooth out some of the sharp points.
+            vtk_smooth = vtkSmoothPolyDataFilter()
+            vtk_smooth.SetRelaxationFactor(relax)
+            self.append(vtk_smooth)
+
+class vtkDepthSortPipeline(vtkPolyDataPipeline):
+    def __init__(self, vtk_renderer, vtkish_polydata=None):
+
+        vtkPolyDataPipeline.__init__(self, vtkish_polydata)
+
+        # The depht sort object is set up to generate scalars representing
+        # the sort depth. A camera is assigned for the sorting. The camera
+        # defines the sort vector (position and focal point).
+        vtk_ds = vtkDepthSortPolyData()
+        vtk_ds.SetCamera(vtk_renderer.GetActiveCamera())
+        vtk_ds.SetDirectionToBackToFront()
+        #vtk_ds.SetVector(1, 1, 1)
+        #vtk_ds.SortScalarsOn()
+        #vtk_ds.Update()
+        self.append(vtk_ds)
+
+        vtk_renderer.ResetCamera()
+
Index: ase/visualize/vtk/volume.py
===================================================================
--- ase/visualize/vtk/volume.py	(revision 0)
+++ ase/visualize/vtk/volume.py	(revision 1)
@@ -0,0 +1,62 @@
+
+import numpy as np
+
+from vtk import vtkContourFilter, vtkDepthSortPolyData
+from ase.visualize.vtk.grid import vtkVolumeGrid
+from ase.visualize.vtk.module import vtkPolyDataModule
+from ase.visualize.vtk.pipeline import vtkSurfaceSmootherPipeline, \
+                                       vtkDepthSortPipeline
+
+# -------------------------------------------------------------------
+
+class vtkIsoSurfaceModule(vtkVolumeGrid, vtkPolyDataModule):
+    def __init__(self, data, cell, vtk_renderer, contours=1, depthsort=True):
+        #TODO don't require vtk_renderer... implement vtkScene
+        #TODO contour values from list or autocontours if int
+
+        # Make sure data argument is a valid array
+        if not isinstance(data, np.ndarray):
+            data = np.array(data)
+
+        vtkVolumeGrid.__init__(self, data.shape, cell)
+
+        self.vtk_iso = vtkContourFilter() #vtkMarchingContourFilter?
+        self.vtk_iso.SetInput(self.get_structured_points()) #TODO non-orthogonal
+
+        self.vtk_iso.SetValue(0, 0.25) 
+        self.vtk_iso.SetValue(1, -0.25)
+
+        self.smoothpipe = vtkSurfaceSmootherPipeline(self, vtk_iso)
+
+        #TODO use vtkDepthSortPipeline - but vtkPolyDataModule isn't a pipeline
+
+        self.depthsort = depthsort
+
+        if self.depthsort:
+            # The depht sort object is set up to generate scalars representing
+            # the sort depth. A camera is assigned for the sorting. The camera
+            # defines the sort vector (position and focal point).
+            self.vtk_ds = vtkDepthSortPolyData()
+            self.vtk_ds.SetCamera(vtk_renderer.GetActiveCamera())
+            self.vtk_ds.SetInputConnection(self.vtk_iso.GetOutputPort())
+            self.vtk_ds.SetDirectionToBackToFront()
+            #vtk_ds.SetVector(1, 1, 1)
+            #vtk_ds.SortScalarsOn()
+            #vtk_ds.Update()
+
+            vtk_renderer.ResetCamera()
+
+            vtkPolyDataModule.__init__(self, self.vtk_ds)
+        else:
+            vtkPolyDataModule.__init__(self, self.vtk_iso)
+
+        #TODO add color function
+        """
+        vtk_dmap = vtk.vtkPolyDataMapper()
+        vtk_dmap.ScalarVisibilityOn()
+        vtk_dmap.SetScalarRange(0, vtk_sda_x.GetMaxNorm()) #TODO GetMinNorm non-existing!
+        vtk_dmap.SetScalarModeToUsePointFieldData()
+        vtk_dmap.SelectColorArray("x")
+        #vtk_dmap.ColorByArrayComponent("x", 0)
+        """
+
Index: ase/visualize/vtk/data.py
===================================================================
--- ase/visualize/vtk/data.py	(revision 0)
+++ ase/visualize/vtk/data.py	(revision 1)
@@ -0,0 +1,194 @@
+
+import numpy as np
+from numpy.ctypeslib import ctypes
+
+from vtk import vtkDataArray, vtkFloatArray, vtkDoubleArray
+
+if ctypes is None:
+    class CTypesEmulator:
+        def __init__(self):
+            self._SimpleCData = np.number
+            self.c_float = np.float32
+            self.c_double = np.float64
+    try:
+        import ctypes
+    except ImportError:
+        ctypes = CTypesEmulator()
+
+# -------------------------------------------------------------------
+
+class vtkNumPyBuffer:
+    def __init__(self, data):
+        self.strbuf = data.tostring()
+        self.nitems = len(data.flat)
+
+    def __len__(self):
+        return self.nitems
+
+    def get_pointer(self):
+        # Any C/C++ method that requires a void * can be passed a Python
+        # string. No check is done to ensure that the string is the correct
+        # size, and the string's reference count is not incremented. Extreme
+        # caution should be applied when using this feature.
+        return self.strbuf
+
+    def notify(self, obj, event):
+        if event == 'DeleteEvent':
+            del self.strbuf
+        else:
+            raise RuntimeError('Event not recognized.')
+
+class vtkDataArrayFromNumPyBuffer:
+    def __init__(self, vtk_class, ctype, data=None):
+
+        assert issubclass(ctype, ctypes._SimpleCData)
+        self.ctype = ctype
+
+        self.vtk_da = vtk_class()
+        assert isinstance(self.vtk_da, vtkDataArray)
+        assert self.vtk_da.GetDataTypeSize() == np.nbytes[np.dtype(self.ctype)]
+
+        if data is not None:
+            self.read_numpy_array(data)
+
+    def read_numpy_array(self, data):
+
+        if not isinstance(data, np.ndarray):
+            data = np.array(data, dtype=self.ctype)
+
+        if data.dtype != self.ctype: # NB: "is not" gets it wrong
+            data = data.astype(self.ctype)
+
+        self.vtk_da.SetNumberOfComponents(data.shape[-1])
+
+        # Passing the void* buffer to the C interface does not increase
+        # its reference count, hence the buffer is deleted by Python when
+        # the reference count of the string from tostring reaches zero.
+        # Also, the boolean True tells VTK to save (not delete) the buffer
+        # when the VTK data array is deleted - we want Python to do this.
+        npybuf = vtkNumPyBuffer(data)
+        self.vtk_da.SetVoidArray(npybuf.get_pointer(), len(npybuf), True)
+        self.vtk_da.AddObserver('DeleteEvent', npybuf.notify)
+
+    def get_output(self):
+        return self.vtk_da
+
+    def copy(self):
+        vtk_da_copy = self.vtk_da.NewInstance()
+        vtk_da_copy.SetNumberOfComponents(self.vtk_da.GetNumberOfComponents())
+        vtk_da_copy.SetNumberOfTuples(self.vtk_da.GetNumberOfTuples())
+
+        assert vtk_da_copy.GetSize() == self.vtk_da.GetSize()
+
+        vtk_da_copy.DeepCopy(self.vtk_da)
+
+        return vtk_da_copy
+
+# -------------------------------------------------------------------
+
+class vtkDataArrayFromNumPyArray(vtkDataArrayFromNumPyBuffer):
+    """Class for reading vtkDataArray from 1D or 2D NumPy array.
+
+    This class can be used to generate a vtkDataArray from a NumPy array.
+    The NumPy array should be of the form <entries> x <number of components>
+    where 'number of components' indicates the number of components in 
+    each entry in the vtkDataArray. Note that this form is also expected
+    even in the case of only a single component.
+    """
+    def __init__(self, vtk_class, ctype, data=None, buffered=True):
+
+        self.buffered = buffered
+
+        vtkDataArrayFromNumPyBuffer.__init__(self, vtk_class, ctype, data)
+
+    def read_numpy_array(self, data):
+        """Read vtkDataArray from NumPy array"""
+
+        if not isinstance(data, np.ndarray):
+            data = np.array(data, dtype=self.ctype)
+
+        if data.dtype != self.ctype: # NB: "is not" gets it wrong
+            data = data.astype(self.ctype)
+
+        if data.ndim == 1:
+            data = data[:, np.newaxis]
+        elif data.ndim != 2:
+            raise ValueError('Data must be a 1D or 2D NumPy array.')
+
+        if self.buffered:
+            vtkDataArrayFromNumPyBuffer.read_numpy_array(self, data)
+        else:
+            self.vtk_da.SetNumberOfComponents(data.shape[-1])
+            self.vtk_da.SetNumberOfTuples(data.shape[0])
+
+            for i, d_c in enumerate(data):
+                for c, d in enumerate(d_c):
+                    self.vtk_da.SetComponent(i, c, d)
+
+class vtkFloatArrayFromNumPyArray(vtkDataArrayFromNumPyArray):
+    def __init__(self, data):
+        vtkDataArrayFromNumPyArray.__init__(self, vtkFloatArray,
+                                            ctypes.c_float, data)
+
+class vtkDoubleArrayFromNumPyArray(vtkDataArrayFromNumPyArray):
+    def __init__(self, data):
+        vtkDataArrayFromNumPyArray.__init__(self, vtkDoubleArray,
+                                            ctypes.c_double, data)
+
+# -------------------------------------------------------------------
+
+class vtkDataArrayFromNumPyMultiArray(vtkDataArrayFromNumPyBuffer):
+    """Class for reading vtkDataArray from a multi-dimensional NumPy array.
+
+    This class can be used to generate a vtkDataArray from a NumPy array.
+    The NumPy array should be of the form <gridsize> x <number of components>
+    where 'number of components' indicates the number of components in 
+    each gridpoint in the vtkDataArray. Note that this form is also expected
+    even in the case of only a single component.
+    """
+    def __init__(self, vtk_class, ctype, data=None, buffered=True):
+
+        self.buffered = buffered
+
+        vtkDataArrayFromNumPyBuffer.__init__(self, vtk_class, ctype, data)
+
+    def read_numpy_array(self, data):
+        """Read vtkDataArray from NumPy array"""
+
+        if not isinstance(data, np.ndarray):
+            data = np.array(data, dtype=self.ctype)
+
+        if data.dtype != self.ctype: # NB: "is not" gets it wrong
+            data = data.astype(self.ctype)
+
+        if data.ndim <=2:
+            raise Warning('This is inefficient for 1D and 2D NumPy arrays. ' +
+                          'Use a vtkDataArrayFromNumPyArray subclass instead.')
+
+        if self.buffered:
+            # This is less than ideal, but will not copy data (uses views).
+            # To get the correct ordering, the grid dimensions have to be
+            # transposed without moving the last dimension (the components).
+            n = data.ndim-1
+            for c in range(n//2):
+                data = data.swapaxes(c,n-1-c)
+
+            vtkDataArrayFromNumPyBuffer.read_numpy_array(self, data)
+        else:
+            self.vtk_da.SetNumberOfComponents(data.shape[-1])
+            self.vtk_da.SetNumberOfTuples(np.prod(data.shape[:-1]))
+
+            for c, d_T in enumerate(data.T):
+                for i, d in enumerate(d_T.flat):
+                    self.vtk_da.SetComponent(i, c, d)
+
+class vtkFloatArrayFromNumPyMultiArray(vtkDataArrayFromNumPyMultiArray):
+    def __init__(self, data):
+        vtkDataArrayFromNumPyMultiArray.__init__(self, vtkFloatArray,
+                                                 ctypes.c_float, data)
+
+class vtkDoubleArrayFromNumPyMultiArray(vtkDataArrayFromNumPyMultiArray):
+    def __init__(self, data):
+        vtkDataArrayFromNumPyMultiArray.__init__(self, vtkDoubleArray,
+                                                 ctypes.c_double, data)
+
Index: ase/visualize/vtk/__init__.py
===================================================================
--- ase/visualize/vtk/__init__.py	(revision 0)
+++ ase/visualize/vtk/__init__.py	(revision 1)
@@ -0,0 +1,36 @@
+try:
+    import vtk
+    hasvtk = True
+    hasmpi = hasattr(vtk, 'vtkMPIController')
+except ImportError:
+    hasvtk = False
+    hasmpi = False
+
+def requirevtk(code=0, parallel=False):
+    from ase.test import NotAvailable
+    if not hasvtk:
+        # VTK required but not installed, force termination
+        # with exit status determined by the code argument.
+        raise NotAvailable('VTK is not installed.', code)
+    if parallel and not hasmpi:
+        # VTK MPI required but not installed, force termination
+        # with exit status determined by the code argument.
+        raise NotAvailable('VTK is not MPI compatible.', code)
+
+def probe_vtk_kilobyte(default=None):
+    if not hasvtk:
+        return default
+
+    from vtk import vtkCharArray
+    vtk_da = vtkCharArray()
+    vtk_da.SetNumberOfComponents(1)
+    vtk_da.SetNumberOfTuples(1024**2)
+
+    # Size of 1 MB = 1024**2 bytes in "VTK kilobytes"
+    size = vtk_da.GetActualMemorySize()
+    if size == 1024:
+        return 1024
+    elif round(abs(size-1024**2/1e3)) == 0:
+        return 1e3
+    else:
+        return default
Index: ase/visualize/vtk/cell.py
===================================================================
--- ase/visualize/vtk/cell.py	(revision 0)
+++ ase/visualize/vtk/cell.py	(revision 1)
@@ -0,0 +1,116 @@
+
+import numpy as np
+
+from ase import Atoms
+
+from vtk import vtkOutlineSource, vtkAxesActor, vtkProperty2D, vtkTextProperty
+from ase.visualize.vtk.module import vtkModule, vtkPolyDataModule
+
+# -------------------------------------------------------------------
+
+class vtkUnitCellModule(vtkPolyDataModule):
+    def __init__(self, atoms):
+
+        assert isinstance(atoms, Atoms)
+        self.pbc = atoms.get_pbc()
+
+        cell = atoms.get_cell()
+
+        """
+        if not isinstance(cell, np.ndarray):
+            cell = np.array(cell)
+
+        if cell.shape == (3,):
+            cell = np.diag(cell)
+
+        assert cell.dtype == float and cell.shape == (3, 3)
+        """
+
+        self.vtk_outline = vtkOutlineSource()
+
+        if (cell - np.diag(cell.diagonal())).any():
+            corners = np.empty((8,3), dtype=float)
+            # edges = [map(int,[(i-1)%2==0,i%4>=2,i>=4]) for i in range(8)]
+            for c,a in enumerate([(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 0), \
+                                  (0, 0, 1), (1, 0, 1), (0, 1, 1), (1, 1, 1)]):
+                corners[c] = np.dot(a, cell)
+            self.bbox = np.array(zip(np.min(corners, axis=0), \
+                                     np.max(corners, axis=0))).ravel()
+            self.vtk_outline.SetCorners(corners.ravel())
+            self.vtk_outline.SetBoxTypeToOriented()
+        else:
+            self.bbox = np.array(zip(np.zeros(3),cell.diagonal())).ravel()
+        self.vtk_outline.SetBounds(self.bbox)
+
+        vtkPolyDataModule.__init__(self, self.vtk_outline)
+
+    def get_bounding_box(self):
+        return self.bbox
+
+    def get_size(self):
+        return self.bbox[1::2]-self.bbox[0::2]
+
+    def get_pbc(self):
+        return self.pbc
+
+    def get_characteristic_length(self):
+        return np.prod(self.get_size())**(1.0/3.0)
+
+# -------------------------------------------------------------------
+
+class vtkAxesModule(vtkModule):
+    def __init__(self, cell):
+
+        assert isinstance(cell, vtkUnitCellModule)
+        self.cell = cell
+
+        l0 = self.cell.get_characteristic_length()
+
+        # Create VTK axes actor (not really a VTK actor though)
+        self.vtk_ax = vtkAxesActor()
+        self.vtk_ax.SetTipTypeToCone()
+        self.vtk_ax.SetConeRadius(5e-2*l0)
+        self.vtk_ax.SetShaftTypeToCylinder()
+        self.vtk_ax.SetCylinderRadius(5e-3*l0)
+
+        # Create VTK two-dimensional property
+        p2d = vtkProperty2D()
+        p2d.SetDisplayLocationToBackground()
+
+        vtkModule.__init__(self, self.vtk_ax, p2d)
+
+        # Create VTK text property and apply to axes
+        vtk_textproperty = vtkTextProperty()
+        vtk_textproperty.SetFontSize(14)
+        vtk_textproperty.SetBold(True)
+        vtk_textproperty.SetItalic(True)
+        vtk_textproperty.SetShadow(True)
+        vtk_textproperty.SetJustificationToRight()
+        vtk_textproperty.SetVerticalJustificationToCentered()
+
+        self.set_text_property(vtk_textproperty)
+
+    def set_actor(self, vtk_act):
+        assert isinstance(vtk_act, vtkAxesActor) #fix for non-vtkActor actor
+        self.vtk_act = vtk_act
+
+    def set_property(self, vtk_property):
+        assert isinstance(vtk_property, vtkProperty2D)
+        for vtk_cap in [self.vtk_ax.GetXAxisCaptionActor2D(),
+                        self.vtk_ax.GetYAxisCaptionActor2D(),
+                        self.vtk_ax.GetZAxisCaptionActor2D()]:
+            #vtk_cap.ThreeDimensionalLeaderOn()
+            #vtk_cap.LeaderOn()
+            vtk_cap.SetProperty(vtk_property)
+            vtk_txt = vtk_cap.GetTextActor()
+            vtk_txt.SetProperty(vtk_property)
+
+    def set_text_property(self, vtk_textproperty, scaled=False):
+        assert isinstance(vtk_textproperty, vtkTextProperty)
+        for vtk_cap in [self.vtk_ax.GetXAxisCaptionActor2D(),
+                        self.vtk_ax.GetYAxisCaptionActor2D(),
+                        self.vtk_ax.GetZAxisCaptionActor2D()]:
+            vtk_txt = vtk_cap.GetTextActor()
+            vtk_txt.SetScaledText(scaled)
+            vtk_txt.SetTextProperty(vtk_textproperty)
+
Index: ase/visualize/__init__.py
===================================================================
--- ase/visualize/__init__.py	(revision 0)
+++ ase/visualize/__init__.py	(revision 1)
@@ -0,0 +1,57 @@
+import os
+import tempfile
+
+from ase.io import write
+import ase.parallel as parallel
+from ase.old import OldASEListOfAtomsWrapper
+
+def view(atoms, data=None, viewer='ag', repeat=None, block=False):
+    # Ignore for parallel calculations:
+    if parallel.size != 1:
+        return
+
+    if hasattr(atoms, 'GetUnitCell'):
+        # Convert old ASE ListOfAtoms to new style.
+        atoms = OldASEListOfAtomsWrapper(atoms).copy()
+
+    vwr = viewer.lower()
+    
+    if vwr == 'ag':
+        format = 'traj'
+        if repeat is None:
+            command = 'ag'
+        else:
+            command = 'ag --repeat=%d,%d,%d' % tuple(repeat)
+            repeat = None
+    elif vwr == 'vmd':
+        format = 'cube'
+        command = 'vmd'
+    elif vwr == 'rasmol':
+        format = 'pdb'
+        command = 'rasmol -pdb'
+    elif vwr == 'xmakemol':
+        format = 'xyz'
+        command = 'xmakemol -f'
+    elif vwr == 'gopenmol':
+        format = 'xyz'
+        command = 'rungOpenMol'
+    elif vwr == 'avogadro':
+        format = 'cube'
+        command = 'avogadro'
+    else:
+        raise RuntimeError('Unknown viewer: ' + viewer)
+
+    fd, filename = tempfile.mkstemp('.' + format, 'ase-')
+    fd = os.fdopen(fd, 'w')
+    if repeat is not None:
+        atoms = atoms.repeat()
+    if data is None:
+        write(fd, atoms, format=format)
+    else:
+        write(fd, atoms, format=format, data=data)
+    fd.close()
+    if block:
+        os.system('%s %s' % (command, filename))
+    else:
+        os.system('%s %s &' % (command, filename))
+    os.system('(sleep 60; rm %s) &' % filename)
Index: ase/visualize/colortable.py
===================================================================
--- ase/visualize/colortable.py	(revision 0)
+++ ase/visualize/colortable.py	(revision 1)
@@ -0,0 +1,797 @@
+"""Defines a table with X11 colors.
+
+The key to the dictionary is a string with an X11 name for the color,
+the value is either a number (a grayscale value) between 0.0 and 1.0,
+or an array of three numbers (RGB values).
+
+The table was generated by the _MakeColorTable function
+(defined in this module).
+"""
+
+from numpy import array
+
+color_table = {
+    'snow':              array([1.0000, 0.9804, 0.9804]),
+    'ghost white':       array([0.9725, 0.9725, 1.0000]),
+    'GhostWhite':        array([0.9725, 0.9725, 1.0000]),
+    'white smoke':       0.9608,
+    'WhiteSmoke':        0.9608,
+    'gainsboro':         0.8627,
+    'floral white':      array([1.0000, 0.9804, 0.9412]),
+    'FloralWhite':       array([1.0000, 0.9804, 0.9412]),
+    'old lace':          array([0.9922, 0.9608, 0.9020]),
+    'OldLace':           array([0.9922, 0.9608, 0.9020]),
+    'linen':             array([0.9804, 0.9412, 0.9020]),
+    'antique white':     array([0.9804, 0.9216, 0.8431]),
+    'AntiqueWhite':      array([0.9804, 0.9216, 0.8431]),
+    'papaya whip':       array([1.0000, 0.9373, 0.8353]),
+    'PapayaWhip':        array([1.0000, 0.9373, 0.8353]),
+    'blanched almond':   array([1.0000, 0.9216, 0.8039]),
+    'BlanchedAlmond':    array([1.0000, 0.9216, 0.8039]),
+    'bisque':            array([1.0000, 0.8941, 0.7686]),
+    'peach puff':        array([1.0000, 0.8549, 0.7255]),
+    'PeachPuff':         array([1.0000, 0.8549, 0.7255]),
+    'navajo white':      array([1.0000, 0.8706, 0.6784]),
+    'NavajoWhite':       array([1.0000, 0.8706, 0.6784]),
+    'moccasin':          array([1.0000, 0.8941, 0.7098]),
+    'cornsilk':          array([1.0000, 0.9725, 0.8627]),
+    'ivory':             array([1.0000, 1.0000, 0.9412]),
+    'lemon chiffon':     array([1.0000, 0.9804, 0.8039]),
+    'LemonChiffon':      array([1.0000, 0.9804, 0.8039]),
+    'seashell':          array([1.0000, 0.9608, 0.9333]),
+    'honeydew':          array([0.9412, 1.0000, 0.9412]),
+    'mint cream':        array([0.9608, 1.0000, 0.9804]),
+    'MintCream':         array([0.9608, 1.0000, 0.9804]),
+    'azure':             array([0.9412, 1.0000, 1.0000]),
+    'alice blue':        array([0.9412, 0.9725, 1.0000]),
+    'AliceBlue':         array([0.9412, 0.9725, 1.0000]),
+    'lavender':          array([0.9020, 0.9020, 0.9804]),
+    'lavender blush':    array([1.0000, 0.9412, 0.9608]),
+    'LavenderBlush':     array([1.0000, 0.9412, 0.9608]),
+    'misty rose':        array([1.0000, 0.8941, 0.8824]),
+    'MistyRose':         array([1.0000, 0.8941, 0.8824]),
+    'white':             1.0000,
+    'black':             0.0000,
+    'dark slate gray':   array([0.1843, 0.3098, 0.3098]),
+    'DarkSlateGray':     array([0.1843, 0.3098, 0.3098]),
+    'dark slate grey':   array([0.1843, 0.3098, 0.3098]),
+    'DarkSlateGrey':     array([0.1843, 0.3098, 0.3098]),
+    'dim gray':          0.4118,
+    'DimGray':           0.4118,
+    'dim grey':          0.4118,
+    'DimGrey':           0.4118,
+    'slate gray':        array([0.4392, 0.5020, 0.5647]),
+    'SlateGray':         array([0.4392, 0.5020, 0.5647]),
+    'slate grey':        array([0.4392, 0.5020, 0.5647]),
+    'SlateGrey':         array([0.4392, 0.5020, 0.5647]),
+    'light slate gray':  array([0.4667, 0.5333, 0.6000]),
+    'LightSlateGray':    array([0.4667, 0.5333, 0.6000]),
+    'light slate grey':  array([0.4667, 0.5333, 0.6000]),
+    'LightSlateGrey':    array([0.4667, 0.5333, 0.6000]),
+    'gray':              0.7451,
+    'grey':              0.7451,
+    'light grey':        0.8275,
+    'LightGrey':         0.8275,
+    'light gray':        0.8275,
+    'LightGray':         0.8275,
+    'midnight blue':     array([0.0980, 0.0980, 0.4392]),
+    'MidnightBlue':      array([0.0980, 0.0980, 0.4392]),
+    'navy':              array([0.0000, 0.0000, 0.5020]),
+    'navy blue':         array([0.0000, 0.0000, 0.5020]),
+    'NavyBlue':          array([0.0000, 0.0000, 0.5020]),
+    'cornflower blue':   array([0.3922, 0.5843, 0.9294]),
+    'CornflowerBlue':    array([0.3922, 0.5843, 0.9294]),
+    'dark slate blue':   array([0.2824, 0.2392, 0.5451]),
+    'DarkSlateBlue':     array([0.2824, 0.2392, 0.5451]),
+    'slate blue':        array([0.4157, 0.3529, 0.8039]),
+    'SlateBlue':         array([0.4157, 0.3529, 0.8039]),
+    'medium slate blue': array([0.4824, 0.4078, 0.9333]),
+    'MediumSlateBlue':   array([0.4824, 0.4078, 0.9333]),
+    'light slate blue':  array([0.5176, 0.4392, 1.0000]),
+    'LightSlateBlue':    array([0.5176, 0.4392, 1.0000]),
+    'medium blue':       array([0.0000, 0.0000, 0.8039]),
+    'MediumBlue':        array([0.0000, 0.0000, 0.8039]),
+    'royal blue':        array([0.2549, 0.4118, 0.8824]),
+    'RoyalBlue':         array([0.2549, 0.4118, 0.8824]),
+    'blue':              array([0.0000, 0.0000, 1.0000]),
+    'dodger blue':       array([0.1176, 0.5647, 1.0000]),
+    'DodgerBlue':        array([0.1176, 0.5647, 1.0000]),
+    'deep sky blue':     array([0.0000, 0.7490, 1.0000]),
+    'DeepSkyBlue':       array([0.0000, 0.7490, 1.0000]),
+    'sky blue':          array([0.5294, 0.8078, 0.9216]),
+    'SkyBlue':           array([0.5294, 0.8078, 0.9216]),
+    'light sky blue':    array([0.5294, 0.8078, 0.9804]),
+    'LightSkyBlue':      array([0.5294, 0.8078, 0.9804]),
+    'steel blue':        array([0.2745, 0.5098, 0.7059]),
+    'SteelBlue':         array([0.2745, 0.5098, 0.7059]),
+    'light steel blue':  array([0.6902, 0.7686, 0.8706]),
+    'LightSteelBlue':    array([0.6902, 0.7686, 0.8706]),
+    'light blue':        array([0.6784, 0.8471, 0.9020]),
+    'LightBlue':         array([0.6784, 0.8471, 0.9020]),
+    'powder blue':       array([0.6902, 0.8784, 0.9020]),
+    'PowderBlue':        array([0.6902, 0.8784, 0.9020]),
+    'pale turquoise':    array([0.6863, 0.9333, 0.9333]),
+    'PaleTurquoise':     array([0.6863, 0.9333, 0.9333]),
+    'dark turquoise':    array([0.0000, 0.8078, 0.8196]),
+    'DarkTurquoise':     array([0.0000, 0.8078, 0.8196]),
+    'medium turquoise':  array([0.2824, 0.8196, 0.8000]),
+    'MediumTurquoise':   array([0.2824, 0.8196, 0.8000]),
+    'turquoise':         array([0.2510, 0.8784, 0.8157]),
+    'cyan':              array([0.0000, 1.0000, 1.0000]),
+    'light cyan':        array([0.8784, 1.0000, 1.0000]),
+    'LightCyan':         array([0.8784, 1.0000, 1.0000]),
+    'cadet blue':        array([0.3725, 0.6196, 0.6275]),
+    'CadetBlue':         array([0.3725, 0.6196, 0.6275]),
+    'medium aquamarine': array([0.4000, 0.8039, 0.6667]),
+    'MediumAquamarine':  array([0.4000, 0.8039, 0.6667]),
+    'aquamarine':        array([0.4980, 1.0000, 0.8314]),
+    'dark green':        array([0.0000, 0.3922, 0.0000]),
+    'DarkGreen':         array([0.0000, 0.3922, 0.0000]),
+    'dark olive green':  array([0.3333, 0.4196, 0.1843]),
+    'DarkOliveGreen':    array([0.3333, 0.4196, 0.1843]),
+    'dark sea green':    array([0.5608, 0.7373, 0.5608]),
+    'DarkSeaGreen':      array([0.5608, 0.7373, 0.5608]),
+    'sea green':         array([0.1804, 0.5451, 0.3412]),
+    'SeaGreen':          array([0.1804, 0.5451, 0.3412]),
+    'medium sea green':  array([0.2353, 0.7020, 0.4431]),
+    'MediumSeaGreen':    array([0.2353, 0.7020, 0.4431]),
+    'light sea green':   array([0.1255, 0.6980, 0.6667]),
+    'LightSeaGreen':     array([0.1255, 0.6980, 0.6667]),
+    'pale green':        array([0.5961, 0.9843, 0.5961]),
+    'PaleGreen':         array([0.5961, 0.9843, 0.5961]),
+    'spring green':      array([0.0000, 1.0000, 0.4980]),
+    'SpringGreen':       array([0.0000, 1.0000, 0.4980]),
+    'lawn green':        array([0.4863, 0.9882, 0.0000]),
+    'LawnGreen':         array([0.4863, 0.9882, 0.0000]),
+    'green':             array([0.0000, 1.0000, 0.0000]),
+    'chartreuse':        array([0.4980, 1.0000, 0.0000]),
+    'medium spring green': array([0.0000, 0.9804, 0.6039]),
+    'MediumSpringGreen': array([0.0000, 0.9804, 0.6039]),
+    'green yellow':      array([0.6784, 1.0000, 0.1843]),
+    'GreenYellow':       array([0.6784, 1.0000, 0.1843]),
+    'lime green':        array([0.1961, 0.8039, 0.1961]),
+    'LimeGreen':         array([0.1961, 0.8039, 0.1961]),
+    'yellow green':      array([0.6039, 0.8039, 0.1961]),
+    'YellowGreen':       array([0.6039, 0.8039, 0.1961]),
+    'forest green':      array([0.1333, 0.5451, 0.1333]),
+    'ForestGreen':       array([0.1333, 0.5451, 0.1333]),
+    'olive drab':        array([0.4196, 0.5569, 0.1373]),
+    'OliveDrab':         array([0.4196, 0.5569, 0.1373]),
+    'dark khaki':        array([0.7412, 0.7176, 0.4196]),
+    'DarkKhaki':         array([0.7412, 0.7176, 0.4196]),
+    'khaki':             array([0.9412, 0.9020, 0.5490]),
+    'pale goldenrod':    array([0.9333, 0.9098, 0.6667]),
+    'PaleGoldenrod':     array([0.9333, 0.9098, 0.6667]),
+    'light goldenrod yellow': array([0.9804, 0.9804, 0.8235]),
+    'LightGoldenrodYellow': array([0.9804, 0.9804, 0.8235]),
+    'light yellow':      array([1.0000, 1.0000, 0.8784]),
+    'LightYellow':       array([1.0000, 1.0000, 0.8784]),
+    'yellow':            array([1.0000, 1.0000, 0.0000]),
+    'gold':              array([1.0000, 0.8431, 0.0000]),
+    'light goldenrod':   array([0.9333, 0.8667, 0.5098]),
+    'LightGoldenrod':    array([0.9333, 0.8667, 0.5098]),
+    'goldenrod':         array([0.8549, 0.6471, 0.1255]),
+    'dark goldenrod':    array([0.7216, 0.5255, 0.0431]),
+    'DarkGoldenrod':     array([0.7216, 0.5255, 0.0431]),
+    'rosy brown':        array([0.7373, 0.5608, 0.5608]),
+    'RosyBrown':         array([0.7373, 0.5608, 0.5608]),
+    'indian red':        array([0.8039, 0.3608, 0.3608]),
+    'IndianRed':         array([0.8039, 0.3608, 0.3608]),
+    'saddle brown':      array([0.5451, 0.2706, 0.0745]),
+    'SaddleBrown':       array([0.5451, 0.2706, 0.0745]),
+    'sienna':            array([0.6275, 0.3216, 0.1765]),
+    'peru':              array([0.8039, 0.5216, 0.2471]),
+    'burlywood':         array([0.8706, 0.7216, 0.5294]),
+    'beige':             array([0.9608, 0.9608, 0.8627]),
+    'wheat':             array([0.9608, 0.8706, 0.7020]),
+    'sandy brown':       array([0.9569, 0.6431, 0.3765]),
+    'SandyBrown':        array([0.9569, 0.6431, 0.3765]),
+    'tan':               array([0.8235, 0.7059, 0.5490]),
+    'chocolate':         array([0.8235, 0.4118, 0.1176]),
+    'firebrick':         array([0.6980, 0.1333, 0.1333]),
+    'brown':             array([0.6471, 0.1647, 0.1647]),
+    'dark salmon':       array([0.9137, 0.5882, 0.4784]),
+    'DarkSalmon':        array([0.9137, 0.5882, 0.4784]),
+    'salmon':            array([0.9804, 0.5020, 0.4471]),
+    'light salmon':      array([1.0000, 0.6275, 0.4784]),
+    'LightSalmon':       array([1.0000, 0.6275, 0.4784]),
+    'orange':            array([1.0000, 0.6471, 0.0000]),
+    'dark orange':       array([1.0000, 0.5490, 0.0000]),
+    'DarkOrange':        array([1.0000, 0.5490, 0.0000]),
+    'coral':             array([1.0000, 0.4980, 0.3137]),
+    'light coral':       array([0.9412, 0.5020, 0.5020]),
+    'LightCoral':        array([0.9412, 0.5020, 0.5020]),
+    'tomato':            array([1.0000, 0.3882, 0.2784]),
+    'orange red':        array([1.0000, 0.2706, 0.0000]),
+    'OrangeRed':         array([1.0000, 0.2706, 0.0000]),
+    'red':               array([1.0000, 0.0000, 0.0000]),
+    'hot pink':          array([1.0000, 0.4118, 0.7059]),
+    'HotPink':           array([1.0000, 0.4118, 0.7059]),
+    'deep pink':         array([1.0000, 0.0784, 0.5765]),
+    'DeepPink':          array([1.0000, 0.0784, 0.5765]),
+    'pink':              array([1.0000, 0.7529, 0.7961]),
+    'light pink':        array([1.0000, 0.7137, 0.7569]),
+    'LightPink':         array([1.0000, 0.7137, 0.7569]),
+    'pale violet red':   array([0.8588, 0.4392, 0.5765]),
+    'PaleVioletRed':     array([0.8588, 0.4392, 0.5765]),
+    'maroon':            array([0.6902, 0.1882, 0.3765]),
+    'medium violet red': array([0.7804, 0.0824, 0.5216]),
+    'MediumVioletRed':   array([0.7804, 0.0824, 0.5216]),
+    'violet red':        array([0.8157, 0.1255, 0.5647]),
+    'VioletRed':         array([0.8157, 0.1255, 0.5647]),
+    'magenta':           array([1.0000, 0.0000, 1.0000]),
+    'violet':            array([0.9333, 0.5098, 0.9333]),
+    'plum':              array([0.8667, 0.6275, 0.8667]),
+    'orchid':            array([0.8549, 0.4392, 0.8392]),
+    'medium orchid':     array([0.7294, 0.3333, 0.8275]),
+    'MediumOrchid':      array([0.7294, 0.3333, 0.8275]),
+    'dark orchid':       array([0.6000, 0.1961, 0.8000]),
+    'DarkOrchid':        array([0.6000, 0.1961, 0.8000]),
+    'dark violet':       array([0.5804, 0.0000, 0.8275]),
+    'DarkViolet':        array([0.5804, 0.0000, 0.8275]),
+    'blue violet':       array([0.5412, 0.1686, 0.8863]),
+    'BlueViolet':        array([0.5412, 0.1686, 0.8863]),
+    'purple':            array([0.6275, 0.1255, 0.9412]),
+    'medium purple':     array([0.5765, 0.4392, 0.8588]),
+    'MediumPurple':      array([0.5765, 0.4392, 0.8588]),
+    'thistle':           array([0.8471, 0.7490, 0.8471]),
+    'snow1':             array([1.0000, 0.9804, 0.9804]),
+    'snow2':             array([0.9333, 0.9137, 0.9137]),
+    'snow3':             array([0.8039, 0.7882, 0.7882]),
+    'snow4':             array([0.5451, 0.5373, 0.5373]),
+    'seashell1':         array([1.0000, 0.9608, 0.9333]),
+    'seashell2':         array([0.9333, 0.8980, 0.8706]),
+    'seashell3':         array([0.8039, 0.7725, 0.7490]),
+    'seashell4':         array([0.5451, 0.5255, 0.5098]),
+    'AntiqueWhite1':     array([1.0000, 0.9373, 0.8588]),
+    'AntiqueWhite2':     array([0.9333, 0.8745, 0.8000]),
+    'AntiqueWhite3':     array([0.8039, 0.7529, 0.6902]),
+    'AntiqueWhite4':     array([0.5451, 0.5137, 0.4706]),
+    'bisque1':           array([1.0000, 0.8941, 0.7686]),
+    'bisque2':           array([0.9333, 0.8353, 0.7176]),
+    'bisque3':           array([0.8039, 0.7176, 0.6196]),
+    'bisque4':           array([0.5451, 0.4902, 0.4196]),
+    'PeachPuff1':        array([1.0000, 0.8549, 0.7255]),
+    'PeachPuff2':        array([0.9333, 0.7961, 0.6784]),
+    'PeachPuff3':        array([0.8039, 0.6863, 0.5843]),
+    'PeachPuff4':        array([0.5451, 0.4667, 0.3961]),
+    'NavajoWhite1':      array([1.0000, 0.8706, 0.6784]),
+    'NavajoWhite2':      array([0.9333, 0.8118, 0.6314]),
+    'NavajoWhite3':      array([0.8039, 0.7020, 0.5451]),
+    'NavajoWhite4':      array([0.5451, 0.4745, 0.3686]),
+    'LemonChiffon1':     array([1.0000, 0.9804, 0.8039]),
+    'LemonChiffon2':     array([0.9333, 0.9137, 0.7490]),
+    'LemonChiffon3':     array([0.8039, 0.7882, 0.6471]),
+    'LemonChiffon4':     array([0.5451, 0.5373, 0.4392]),
+    'cornsilk1':         array([1.0000, 0.9725, 0.8627]),
+    'cornsilk2':         array([0.9333, 0.9098, 0.8039]),
+    'cornsilk3':         array([0.8039, 0.7843, 0.6941]),
+    'cornsilk4':         array([0.5451, 0.5333, 0.4706]),
+    'ivory1':            array([1.0000, 1.0000, 0.9412]),
+    'ivory2':            array([0.9333, 0.9333, 0.8784]),
+    'ivory3':            array([0.8039, 0.8039, 0.7569]),
+    'ivory4':            array([0.5451, 0.5451, 0.5137]),
+    'honeydew1':         array([0.9412, 1.0000, 0.9412]),
+    'honeydew2':         array([0.8784, 0.9333, 0.8784]),
+    'honeydew3':         array([0.7569, 0.8039, 0.7569]),
+    'honeydew4':         array([0.5137, 0.5451, 0.5137]),
+    'LavenderBlush1':    array([1.0000, 0.9412, 0.9608]),
+    'LavenderBlush2':    array([0.9333, 0.8784, 0.8980]),
+    'LavenderBlush3':    array([0.8039, 0.7569, 0.7725]),
+    'LavenderBlush4':    array([0.5451, 0.5137, 0.5255]),
+    'MistyRose1':        array([1.0000, 0.8941, 0.8824]),
+    'MistyRose2':        array([0.9333, 0.8353, 0.8235]),
+    'MistyRose3':        array([0.8039, 0.7176, 0.7098]),
+    'MistyRose4':        array([0.5451, 0.4902, 0.4824]),
+    'azure1':            array([0.9412, 1.0000, 1.0000]),
+    'azure2':            array([0.8784, 0.9333, 0.9333]),
+    'azure3':            array([0.7569, 0.8039, 0.8039]),
+    'azure4':            array([0.5137, 0.5451, 0.5451]),
+    'SlateBlue1':        array([0.5137, 0.4353, 1.0000]),
+    'SlateBlue2':        array([0.4784, 0.4039, 0.9333]),
+    'SlateBlue3':        array([0.4118, 0.3490, 0.8039]),
+    'SlateBlue4':        array([0.2784, 0.2353, 0.5451]),
+    'RoyalBlue1':        array([0.2824, 0.4627, 1.0000]),
+    'RoyalBlue2':        array([0.2627, 0.4314, 0.9333]),
+    'RoyalBlue3':        array([0.2275, 0.3725, 0.8039]),
+    'RoyalBlue4':        array([0.1529, 0.2510, 0.5451]),
+    'blue1':             array([0.0000, 0.0000, 1.0000]),
+    'blue2':             array([0.0000, 0.0000, 0.9333]),
+    'blue3':             array([0.0000, 0.0000, 0.8039]),
+    'blue4':             array([0.0000, 0.0000, 0.5451]),
+    'DodgerBlue1':       array([0.1176, 0.5647, 1.0000]),
+    'DodgerBlue2':       array([0.1098, 0.5255, 0.9333]),
+    'DodgerBlue3':       array([0.0941, 0.4549, 0.8039]),
+    'DodgerBlue4':       array([0.0627, 0.3059, 0.5451]),
+    'SteelBlue1':        array([0.3882, 0.7216, 1.0000]),
+    'SteelBlue2':        array([0.3608, 0.6745, 0.9333]),
+    'SteelBlue3':        array([0.3098, 0.5804, 0.8039]),
+    'SteelBlue4':        array([0.2118, 0.3922, 0.5451]),
+    'DeepSkyBlue1':      array([0.0000, 0.7490, 1.0000]),
+    'DeepSkyBlue2':      array([0.0000, 0.6980, 0.9333]),
+    'DeepSkyBlue3':      array([0.0000, 0.6039, 0.8039]),
+    'DeepSkyBlue4':      array([0.0000, 0.4078, 0.5451]),
+    'SkyBlue1':          array([0.5294, 0.8078, 1.0000]),
+    'SkyBlue2':          array([0.4941, 0.7529, 0.9333]),
+    'SkyBlue3':          array([0.4235, 0.6510, 0.8039]),
+    'SkyBlue4':          array([0.2902, 0.4392, 0.5451]),
+    'LightSkyBlue1':     array([0.6902, 0.8863, 1.0000]),
+    'LightSkyBlue2':     array([0.6431, 0.8275, 0.9333]),
+    'LightSkyBlue3':     array([0.5529, 0.7137, 0.8039]),
+    'LightSkyBlue4':     array([0.3765, 0.4824, 0.5451]),
+    'SlateGray1':        array([0.7765, 0.8863, 1.0000]),
+    'SlateGray2':        array([0.7255, 0.8275, 0.9333]),
+    'SlateGray3':        array([0.6235, 0.7137, 0.8039]),
+    'SlateGray4':        array([0.4235, 0.4824, 0.5451]),
+    'LightSteelBlue1':   array([0.7922, 0.8824, 1.0000]),
+    'LightSteelBlue2':   array([0.7373, 0.8235, 0.9333]),
+    'LightSteelBlue3':   array([0.6353, 0.7098, 0.8039]),
+    'LightSteelBlue4':   array([0.4314, 0.4824, 0.5451]),
+    'LightBlue1':        array([0.7490, 0.9373, 1.0000]),
+    'LightBlue2':        array([0.6980, 0.8745, 0.9333]),
+    'LightBlue3':        array([0.6039, 0.7529, 0.8039]),
+    'LightBlue4':        array([0.4078, 0.5137, 0.5451]),
+    'LightCyan1':        array([0.8784, 1.0000, 1.0000]),
+    'LightCyan2':        array([0.8196, 0.9333, 0.9333]),
+    'LightCyan3':        array([0.7059, 0.8039, 0.8039]),
+    'LightCyan4':        array([0.4784, 0.5451, 0.5451]),
+    'PaleTurquoise1':    array([0.7333, 1.0000, 1.0000]),
+    'PaleTurquoise2':    array([0.6824, 0.9333, 0.9333]),
+    'PaleTurquoise3':    array([0.5882, 0.8039, 0.8039]),
+    'PaleTurquoise4':    array([0.4000, 0.5451, 0.5451]),
+    'CadetBlue1':        array([0.5961, 0.9608, 1.0000]),
+    'CadetBlue2':        array([0.5569, 0.8980, 0.9333]),
+    'CadetBlue3':        array([0.4784, 0.7725, 0.8039]),
+    'CadetBlue4':        array([0.3255, 0.5255, 0.5451]),
+    'turquoise1':        array([0.0000, 0.9608, 1.0000]),
+    'turquoise2':        array([0.0000, 0.8980, 0.9333]),
+    'turquoise3':        array([0.0000, 0.7725, 0.8039]),
+    'turquoise4':        array([0.0000, 0.5255, 0.5451]),
+    'cyan1':             array([0.0000, 1.0000, 1.0000]),
+    'cyan2':             array([0.0000, 0.9333, 0.9333]),
+    'cyan3':             array([0.0000, 0.8039, 0.8039]),
+    'cyan4':             array([0.0000, 0.5451, 0.5451]),
+    'DarkSlateGray1':    array([0.5922, 1.0000, 1.0000]),
+    'DarkSlateGray2':    array([0.5529, 0.9333, 0.9333]),
+    'DarkSlateGray3':    array([0.4745, 0.8039, 0.8039]),
+    'DarkSlateGray4':    array([0.3216, 0.5451, 0.5451]),
+    'aquamarine1':       array([0.4980, 1.0000, 0.8314]),
+    'aquamarine2':       array([0.4627, 0.9333, 0.7765]),
+    'aquamarine3':       array([0.4000, 0.8039, 0.6667]),
+    'aquamarine4':       array([0.2706, 0.5451, 0.4549]),
+    'DarkSeaGreen1':     array([0.7569, 1.0000, 0.7569]),
+    'DarkSeaGreen2':     array([0.7059, 0.9333, 0.7059]),
+    'DarkSeaGreen3':     array([0.6078, 0.8039, 0.6078]),
+    'DarkSeaGreen4':     array([0.4118, 0.5451, 0.4118]),
+    'SeaGreen1':         array([0.3294, 1.0000, 0.6235]),
+    'SeaGreen2':         array([0.3059, 0.9333, 0.5804]),
+    'SeaGreen3':         array([0.2627, 0.8039, 0.5020]),
+    'SeaGreen4':         array([0.1804, 0.5451, 0.3412]),
+    'PaleGreen1':        array([0.6039, 1.0000, 0.6039]),
+    'PaleGreen2':        array([0.5647, 0.9333, 0.5647]),
+    'PaleGreen3':        array([0.4863, 0.8039, 0.4863]),
+    'PaleGreen4':        array([0.3294, 0.5451, 0.3294]),
+    'SpringGreen1':      array([0.0000, 1.0000, 0.4980]),
+    'SpringGreen2':      array([0.0000, 0.9333, 0.4627]),
+    'SpringGreen3':      array([0.0000, 0.8039, 0.4000]),
+    'SpringGreen4':      array([0.0000, 0.5451, 0.2706]),
+    'green1':            array([0.0000, 1.0000, 0.0000]),
+    'green2':            array([0.0000, 0.9333, 0.0000]),
+    'green3':            array([0.0000, 0.8039, 0.0000]),
+    'green4':            array([0.0000, 0.5451, 0.0000]),
+    'chartreuse1':       array([0.4980, 1.0000, 0.0000]),
+    'chartreuse2':       array([0.4627, 0.9333, 0.0000]),
+    'chartreuse3':       array([0.4000, 0.8039, 0.0000]),
+    'chartreuse4':       array([0.2706, 0.5451, 0.0000]),
+    'OliveDrab1':        array([0.7529, 1.0000, 0.2431]),
+    'OliveDrab2':        array([0.7020, 0.9333, 0.2275]),
+    'OliveDrab3':        array([0.6039, 0.8039, 0.1961]),
+    'OliveDrab4':        array([0.4118, 0.5451, 0.1333]),
+    'DarkOliveGreen1':   array([0.7922, 1.0000, 0.4392]),
+    'DarkOliveGreen2':   array([0.7373, 0.9333, 0.4078]),
+    'DarkOliveGreen3':   array([0.6353, 0.8039, 0.3529]),
+    'DarkOliveGreen4':   array([0.4314, 0.5451, 0.2392]),
+    'khaki1':            array([1.0000, 0.9647, 0.5608]),
+    'khaki2':            array([0.9333, 0.9020, 0.5216]),
+    'khaki3':            array([0.8039, 0.7765, 0.4510]),
+    'khaki4':            array([0.5451, 0.5255, 0.3059]),
+    'LightGoldenrod1':   array([1.0000, 0.9255, 0.5451]),
+    'LightGoldenrod2':   array([0.9333, 0.8627, 0.5098]),
+    'LightGoldenrod3':   array([0.8039, 0.7451, 0.4392]),
+    'LightGoldenrod4':   array([0.5451, 0.5059, 0.2980]),
+    'LightYellow1':      array([1.0000, 1.0000, 0.8784]),
+    'LightYellow2':      array([0.9333, 0.9333, 0.8196]),
+    'LightYellow3':      array([0.8039, 0.8039, 0.7059]),
+    'LightYellow4':      array([0.5451, 0.5451, 0.4784]),
+    'yellow1':           array([1.0000, 1.0000, 0.0000]),
+    'yellow2':           array([0.9333, 0.9333, 0.0000]),
+    'yellow3':           array([0.8039, 0.8039, 0.0000]),
+    'yellow4':           array([0.5451, 0.5451, 0.0000]),
+    'gold1':             array([1.0000, 0.8431, 0.0000]),
+    'gold2':             array([0.9333, 0.7882, 0.0000]),
+    'gold3':             array([0.8039, 0.6784, 0.0000]),
+    'gold4':             array([0.5451, 0.4588, 0.0000]),
+    'goldenrod1':        array([1.0000, 0.7569, 0.1451]),
+    'goldenrod2':        array([0.9333, 0.7059, 0.1333]),
+    'goldenrod3':        array([0.8039, 0.6078, 0.1137]),
+    'goldenrod4':        array([0.5451, 0.4118, 0.0784]),
+    'DarkGoldenrod1':    array([1.0000, 0.7255, 0.0588]),
+    'DarkGoldenrod2':    array([0.9333, 0.6784, 0.0549]),
+    'DarkGoldenrod3':    array([0.8039, 0.5843, 0.0471]),
+    'DarkGoldenrod4':    array([0.5451, 0.3961, 0.0314]),
+    'RosyBrown1':        array([1.0000, 0.7569, 0.7569]),
+    'RosyBrown2':        array([0.9333, 0.7059, 0.7059]),
+    'RosyBrown3':        array([0.8039, 0.6078, 0.6078]),
+    'RosyBrown4':        array([0.5451, 0.4118, 0.4118]),
+    'IndianRed1':        array([1.0000, 0.4157, 0.4157]),
+    'IndianRed2':        array([0.9333, 0.3882, 0.3882]),
+    'IndianRed3':        array([0.8039, 0.3333, 0.3333]),
+    'IndianRed4':        array([0.5451, 0.2275, 0.2275]),
+    'sienna1':           array([1.0000, 0.5098, 0.2784]),
+    'sienna2':           array([0.9333, 0.4745, 0.2588]),
+    'sienna3':           array([0.8039, 0.4078, 0.2235]),
+    'sienna4':           array([0.5451, 0.2784, 0.1490]),
+    'burlywood1':        array([1.0000, 0.8275, 0.6078]),
+    'burlywood2':        array([0.9333, 0.7725, 0.5686]),
+    'burlywood3':        array([0.8039, 0.6667, 0.4902]),
+    'burlywood4':        array([0.5451, 0.4510, 0.3333]),
+    'wheat1':            array([1.0000, 0.9059, 0.7294]),
+    'wheat2':            array([0.9333, 0.8471, 0.6824]),
+    'wheat3':            array([0.8039, 0.7294, 0.5882]),
+    'wheat4':            array([0.5451, 0.4941, 0.4000]),
+    'tan1':              array([1.0000, 0.6471, 0.3098]),
+    'tan2':              array([0.9333, 0.6039, 0.2863]),
+    'tan3':              array([0.8039, 0.5216, 0.2471]),
+    'tan4':              array([0.5451, 0.3529, 0.1686]),
+    'chocolate1':        array([1.0000, 0.4980, 0.1412]),
+    'chocolate2':        array([0.9333, 0.4627, 0.1294]),
+    'chocolate3':        array([0.8039, 0.4000, 0.1137]),
+    'chocolate4':        array([0.5451, 0.2706, 0.0745]),
+    'firebrick1':        array([1.0000, 0.1882, 0.1882]),
+    'firebrick2':        array([0.9333, 0.1725, 0.1725]),
+    'firebrick3':        array([0.8039, 0.1490, 0.1490]),
+    'firebrick4':        array([0.5451, 0.1020, 0.1020]),
+    'brown1':            array([1.0000, 0.2510, 0.2510]),
+    'brown2':            array([0.9333, 0.2314, 0.2314]),
+    'brown3':            array([0.8039, 0.2000, 0.2000]),
+    'brown4':            array([0.5451, 0.1373, 0.1373]),
+    'salmon1':           array([1.0000, 0.5490, 0.4118]),
+    'salmon2':           array([0.9333, 0.5098, 0.3843]),
+    'salmon3':           array([0.8039, 0.4392, 0.3294]),
+    'salmon4':           array([0.5451, 0.2980, 0.2235]),
+    'LightSalmon1':      array([1.0000, 0.6275, 0.4784]),
+    'LightSalmon2':      array([0.9333, 0.5843, 0.4471]),
+    'LightSalmon3':      array([0.8039, 0.5059, 0.3843]),
+    'LightSalmon4':      array([0.5451, 0.3412, 0.2588]),
+    'orange1':           array([1.0000, 0.6471, 0.0000]),
+    'orange2':           array([0.9333, 0.6039, 0.0000]),
+    'orange3':           array([0.8039, 0.5216, 0.0000]),
+    'orange4':           array([0.5451, 0.3529, 0.0000]),
+    'DarkOrange1':       array([1.0000, 0.4980, 0.0000]),
+    'DarkOrange2':       array([0.9333, 0.4627, 0.0000]),
+    'DarkOrange3':       array([0.8039, 0.4000, 0.0000]),
+    'DarkOrange4':       array([0.5451, 0.2706, 0.0000]),
+    'coral1':            array([1.0000, 0.4471, 0.3373]),
+    'coral2':            array([0.9333, 0.4157, 0.3137]),
+    'coral3':            array([0.8039, 0.3569, 0.2706]),
+    'coral4':            array([0.5451, 0.2431, 0.1843]),
+    'tomato1':           array([1.0000, 0.3882, 0.2784]),
+    'tomato2':           array([0.9333, 0.3608, 0.2588]),
+    'tomato3':           array([0.8039, 0.3098, 0.2235]),
+    'tomato4':           array([0.5451, 0.2118, 0.1490]),
+    'OrangeRed1':        array([1.0000, 0.2706, 0.0000]),
+    'OrangeRed2':        array([0.9333, 0.2510, 0.0000]),
+    'OrangeRed3':        array([0.8039, 0.2157, 0.0000]),
+    'OrangeRed4':        array([0.5451, 0.1451, 0.0000]),
+    'red1':              array([1.0000, 0.0000, 0.0000]),
+    'red2':              array([0.9333, 0.0000, 0.0000]),
+    'red3':              array([0.8039, 0.0000, 0.0000]),
+    'red4':              array([0.5451, 0.0000, 0.0000]),
+    'DeepPink1':         array([1.0000, 0.0784, 0.5765]),
+    'DeepPink2':         array([0.9333, 0.0706, 0.5373]),
+    'DeepPink3':         array([0.8039, 0.0627, 0.4627]),
+    'DeepPink4':         array([0.5451, 0.0392, 0.3137]),
+    'HotPink1':          array([1.0000, 0.4314, 0.7059]),
+    'HotPink2':          array([0.9333, 0.4157, 0.6549]),
+    'HotPink3':          array([0.8039, 0.3765, 0.5647]),
+    'HotPink4':          array([0.5451, 0.2275, 0.3843]),
+    'pink1':             array([1.0000, 0.7098, 0.7725]),
+    'pink2':             array([0.9333, 0.6627, 0.7216]),
+    'pink3':             array([0.8039, 0.5686, 0.6196]),
+    'pink4':             array([0.5451, 0.3882, 0.4235]),
+    'LightPink1':        array([1.0000, 0.6824, 0.7255]),
+    'LightPink2':        array([0.9333, 0.6353, 0.6784]),
+    'LightPink3':        array([0.8039, 0.5490, 0.5843]),
+    'LightPink4':        array([0.5451, 0.3725, 0.3961]),
+    'PaleVioletRed1':    array([1.0000, 0.5098, 0.6706]),
+    'PaleVioletRed2':    array([0.9333, 0.4745, 0.6235]),
+    'PaleVioletRed3':    array([0.8039, 0.4078, 0.5373]),
+    'PaleVioletRed4':    array([0.5451, 0.2784, 0.3647]),
+    'maroon1':           array([1.0000, 0.2039, 0.7020]),
+    'maroon2':           array([0.9333, 0.1882, 0.6549]),
+    'maroon3':           array([0.8039, 0.1608, 0.5647]),
+    'maroon4':           array([0.5451, 0.1098, 0.3843]),
+    'VioletRed1':        array([1.0000, 0.2431, 0.5882]),
+    'VioletRed2':        array([0.9333, 0.2275, 0.5490]),
+    'VioletRed3':        array([0.8039, 0.1961, 0.4706]),
+    'VioletRed4':        array([0.5451, 0.1333, 0.3216]),
+    'magenta1':          array([1.0000, 0.0000, 1.0000]),
+    'magenta2':          array([0.9333, 0.0000, 0.9333]),
+    'magenta3':          array([0.8039, 0.0000, 0.8039]),
+    'magenta4':          array([0.5451, 0.0000, 0.5451]),
+    'orchid1':           array([1.0000, 0.5137, 0.9804]),
+    'orchid2':           array([0.9333, 0.4784, 0.9137]),
+    'orchid3':           array([0.8039, 0.4118, 0.7882]),
+    'orchid4':           array([0.5451, 0.2784, 0.5373]),
+    'plum1':             array([1.0000, 0.7333, 1.0000]),
+    'plum2':             array([0.9333, 0.6824, 0.9333]),
+    'plum3':             array([0.8039, 0.5882, 0.8039]),
+    'plum4':             array([0.5451, 0.4000, 0.5451]),
+    'MediumOrchid1':     array([0.8784, 0.4000, 1.0000]),
+    'MediumOrchid2':     array([0.8196, 0.3725, 0.9333]),
+    'MediumOrchid3':     array([0.7059, 0.3216, 0.8039]),
+    'MediumOrchid4':     array([0.4784, 0.2157, 0.5451]),
+    'DarkOrchid1':       array([0.7490, 0.2431, 1.0000]),
+    'DarkOrchid2':       array([0.6980, 0.2275, 0.9333]),
+    'DarkOrchid3':       array([0.6039, 0.1961, 0.8039]),
+    'DarkOrchid4':       array([0.4078, 0.1333, 0.5451]),
+    'purple1':           array([0.6078, 0.1882, 1.0000]),
+    'purple2':           array([0.5686, 0.1725, 0.9333]),
+    'purple3':           array([0.4902, 0.1490, 0.8039]),
+    'purple4':           array([0.3333, 0.1020, 0.5451]),
+    'MediumPurple1':     array([0.6706, 0.5098, 1.0000]),
+    'MediumPurple2':     array([0.6235, 0.4745, 0.9333]),
+    'MediumPurple3':     array([0.5373, 0.4078, 0.8039]),
+    'MediumPurple4':     array([0.3647, 0.2784, 0.5451]),
+    'thistle1':          array([1.0000, 0.8824, 1.0000]),
+    'thistle2':          array([0.9333, 0.8235, 0.9333]),
+    'thistle3':          array([0.8039, 0.7098, 0.8039]),
+    'thistle4':          array([0.5451, 0.4824, 0.5451]),
+    'gray0':             0.0000,
+    'grey0':             0.0000,
+    'gray1':             0.0118,
+    'grey1':             0.0118,
+    'gray2':             0.0196,
+    'grey2':             0.0196,
+    'gray3':             0.0314,
+    'grey3':             0.0314,
+    'gray4':             0.0392,
+    'grey4':             0.0392,
+    'gray5':             0.0510,
+    'grey5':             0.0510,
+    'gray6':             0.0588,
+    'grey6':             0.0588,
+    'gray7':             0.0706,
+    'grey7':             0.0706,
+    'gray8':             0.0784,
+    'grey8':             0.0784,
+    'gray9':             0.0902,
+    'grey9':             0.0902,
+    'gray10':            0.1020,
+    'grey10':            0.1020,
+    'gray11':            0.1098,
+    'grey11':            0.1098,
+    'gray12':            0.1216,
+    'grey12':            0.1216,
+    'gray13':            0.1294,
+    'grey13':            0.1294,
+    'gray14':            0.1412,
+    'grey14':            0.1412,
+    'gray15':            0.1490,
+    'grey15':            0.1490,
+    'gray16':            0.1608,
+    'grey16':            0.1608,
+    'gray17':            0.1686,
+    'grey17':            0.1686,
+    'gray18':            0.1804,
+    'grey18':            0.1804,
+    'gray19':            0.1882,
+    'grey19':            0.1882,
+    'gray20':            0.2000,
+    'grey20':            0.2000,
+    'gray21':            0.2118,
+    'grey21':            0.2118,
+    'gray22':            0.2196,
+    'grey22':            0.2196,
+    'gray23':            0.2314,
+    'grey23':            0.2314,
+    'gray24':            0.2392,
+    'grey24':            0.2392,
+    'gray25':            0.2510,
+    'grey25':            0.2510,
+    'gray26':            0.2588,
+    'grey26':            0.2588,
+    'gray27':            0.2706,
+    'grey27':            0.2706,
+    'gray28':            0.2784,
+    'grey28':            0.2784,
+    'gray29':            0.2902,
+    'grey29':            0.2902,
+    'gray30':            0.3020,
+    'grey30':            0.3020,
+    'gray31':            0.3098,
+    'grey31':            0.3098,
+    'gray32':            0.3216,
+    'grey32':            0.3216,
+    'gray33':            0.3294,
+    'grey33':            0.3294,
+    'gray34':            0.3412,
+    'grey34':            0.3412,
+    'gray35':            0.3490,
+    'grey35':            0.3490,
+    'gray36':            0.3608,
+    'grey36':            0.3608,
+    'gray37':            0.3686,
+    'grey37':            0.3686,
+    'gray38':            0.3804,
+    'grey38':            0.3804,
+    'gray39':            0.3882,
+    'grey39':            0.3882,
+    'gray40':            0.4000,
+    'grey40':            0.4000,
+    'gray41':            0.4118,
+    'grey41':            0.4118,
+    'gray42':            0.4196,
+    'grey42':            0.4196,
+    'gray43':            0.4314,
+    'grey43':            0.4314,
+    'gray44':            0.4392,
+    'grey44':            0.4392,
+    'gray45':            0.4510,
+    'grey45':            0.4510,
+    'gray46':            0.4588,
+    'grey46':            0.4588,
+    'gray47':            0.4706,
+    'grey47':            0.4706,
+    'gray48':            0.4784,
+    'grey48':            0.4784,
+    'gray49':            0.4902,
+    'grey49':            0.4902,
+    'gray50':            0.4980,
+    'grey50':            0.4980,
+    'gray51':            0.5098,
+    'grey51':            0.5098,
+    'gray52':            0.5216,
+    'grey52':            0.5216,
+    'gray53':            0.5294,
+    'grey53':            0.5294,
+    'gray54':            0.5412,
+    'grey54':            0.5412,
+    'gray55':            0.5490,
+    'grey55':            0.5490,
+    'gray56':            0.5608,
+    'grey56':            0.5608,
+    'gray57':            0.5686,
+    'grey57':            0.5686,
+    'gray58':            0.5804,
+    'grey58':            0.5804,
+    'gray59':            0.5882,
+    'grey59':            0.5882,
+    'gray60':            0.6000,
+    'grey60':            0.6000,
+    'gray61':            0.6118,
+    'grey61':            0.6118,
+    'gray62':            0.6196,
+    'grey62':            0.6196,
+    'gray63':            0.6314,
+    'grey63':            0.6314,
+    'gray64':            0.6392,
+    'grey64':            0.6392,
+    'gray65':            0.6510,
+    'grey65':            0.6510,
+    'gray66':            0.6588,
+    'grey66':            0.6588,
+    'gray67':            0.6706,
+    'grey67':            0.6706,
+    'gray68':            0.6784,
+    'grey68':            0.6784,
+    'gray69':            0.6902,
+    'grey69':            0.6902,
+    'gray70':            0.7020,
+    'grey70':            0.7020,
+    'gray71':            0.7098,
+    'grey71':            0.7098,
+    'gray72':            0.7216,
+    'grey72':            0.7216,
+    'gray73':            0.7294,
+    'grey73':            0.7294,
+    'gray74':            0.7412,
+    'grey74':            0.7412,
+    'gray75':            0.7490,
+    'grey75':            0.7490,
+    'gray76':            0.7608,
+    'grey76':            0.7608,
+    'gray77':            0.7686,
+    'grey77':            0.7686,
+    'gray78':            0.7804,
+    'grey78':            0.7804,
+    'gray79':            0.7882,
+    'grey79':            0.7882,
+    'gray80':            0.8000,
+    'grey80':            0.8000,
+    'gray81':            0.8118,
+    'grey81':            0.8118,
+    'gray82':            0.8196,
+    'grey82':            0.8196,
+    'gray83':            0.8314,
+    'grey83':            0.8314,
+    'gray84':            0.8392,
+    'grey84':            0.8392,
+    'gray85':            0.8510,
+    'grey85':            0.8510,
+    'gray86':            0.8588,
+    'grey86':            0.8588,
+    'gray87':            0.8706,
+    'grey87':            0.8706,
+    'gray88':            0.8784,
+    'grey88':            0.8784,
+    'gray89':            0.8902,
+    'grey89':            0.8902,
+    'gray90':            0.8980,
+    'grey90':            0.8980,
+    'gray91':            0.9098,
+    'grey91':            0.9098,
+    'gray92':            0.9216,
+    'grey92':            0.9216,
+    'gray93':            0.9294,
+    'grey93':            0.9294,
+    'gray94':            0.9412,
+    'grey94':            0.9412,
+    'gray95':            0.9490,
+    'grey95':            0.9490,
+    'gray96':            0.9608,
+    'grey96':            0.9608,
+    'gray97':            0.9686,
+    'grey97':            0.9686,
+    'gray98':            0.9804,
+    'grey98':            0.9804,
+    'gray99':            0.9882,
+    'grey99':            0.9882,
+    'gray100':           1.0000,
+    'grey100':           1.0000,
+    'dark grey':         0.6627,
+    'DarkGrey':          0.6627,
+    'dark gray':         0.6627,
+    'DarkGray':          0.6627,
+    'dark blue':         array([0.0000, 0.0000, 0.5451]),
+    'DarkBlue':          array([0.0000, 0.0000, 0.5451]),
+    'dark cyan':         array([0.0000, 0.5451, 0.5451]),
+    'DarkCyan':          array([0.0000, 0.5451, 0.5451]),
+    'dark magenta':      array([0.5451, 0.0000, 0.5451]),
+    'DarkMagenta':       array([0.5451, 0.0000, 0.5451]),
+    'dark red':          array([0.5451, 0.0000, 0.0000]),
+    'DarkRed':           array([0.5451, 0.0000, 0.0000]),
+    'light green':       array([0.5647, 0.9333, 0.5647]),
+    'LightGreen':        array([0.5647, 0.9333, 0.5647]),
+}
+
+del array   # Prevent namespace pollution.
+
+def _MakeColorTable(outfile = None, infile = "/usr/lib/X11/rgb.txt"):
+    import string, sys
+    
+    if type(infile) == type("string"):
+        infile = open(infile)
+    if outfile is None:
+        outfile = sys.stdout
+    if type(outfile) == type("string"):
+        outfile = open(outfile, "w")
+        
+    outfile.write("ColorTable = {\n");
+    for line in infile.readlines():
+        line = string.strip(line)
+        if not line or (line[0] in "%#!;"):
+            continue
+        # This is not a comment or an empty line
+        words = string.split(line)
+        name = "'" + string.join(words[3:], " ") + "':"
+        if words[0] == words[1] and words[0] == words[2]:
+            # Gray color
+            clr = float(words[0])/255.0
+            outfile.write("    %-20s %6.4f,\n" % (name,clr))
+        else:
+            clr = (name, float(words[0])/255.0, float(words[1])/255.0,
+                   float(words[2])/255.0)
+            outfile.write("    %-20s array([%6.4f, %6.4f, %6.4f]),\n" % clr)
+    outfile.write("}\n")
+    outfile.flush()
Index: ase/visualize/primiplotter.py
===================================================================
--- ase/visualize/primiplotter.py	(revision 0)
+++ ase/visualize/primiplotter.py	(revision 1)
@@ -0,0 +1,928 @@
+"""An experimental package for making plots during a simulation.
+
+A PrimiPlotter can plot a list of atoms on one or more output devices.
+"""
+
+from numpy import *
+from ase.visualize.colortable import color_table
+import ase.data
+import sys, os, time, weakref
+
+class PrimiPlotterBase:
+    "Base class for PrimiPlotter and Povrayplotter."
+    #def set_dimensions(self, dims):
+    #    "Set the size of the canvas (a 2-tuple)."
+    #    self.dims = dims
+        
+    def set_rotation(self, rotation):
+        "Set the rotation angles (in degrees)."
+        self.angles[:] = array(rotation) * (pi/180)
+        
+    def set_radii(self, radii):
+        """Set the atomic radii.  Give an array or a single number."""
+        self.radius = radii
+
+    def set_colors(self, colors):
+        """Explicitly set the colors of the atoms."""
+        self.colors = colors
+
+    def set_color_function(self, colors):
+        """Set a color function, to be used to color the atoms."""
+        if callable(colors):
+            self.colorfunction = colors
+        else:
+            raise TypeError, "The color function is not callable."
+
+    def set_invisible(self, inv):
+        """Choose invisible atoms."""
+        self.invisible = inv
+
+    def set_invisibility_function(self, invfunc):
+        """Set an invisibility function."""
+        if callable(invfunc):
+            self.invisibilityfunction = invfunc
+        else:
+            raise TypeError, "The invisibility function is not callable."
+
+    def set_cut(self, xmin=None, xmax=None, ymin=None, ymax=None,
+               zmin=None, zmax=None):
+        self.cut = {"xmin":xmin, "xmax":xmax, "ymin":ymin, "ymax":ymax,
+                    "zmin":zmin, "zmax":zmax}
+    
+    def update(self, newatoms = None):
+        """Cause a plot (respecting the interval setting).
+
+        update causes a plot to be made.  If the interval variable was
+        specified when the plotter was create, it will only produce a
+        plot with that interval.  update takes an optional argument,
+        newatoms, which can be used to replace the list of atoms with
+        a new one.
+        """
+        if newatoms is not None:
+            self.atoms = newatoms
+        if self.skipnext <= 0:
+            self.plot()
+            self.skipnext = self.interval
+        self.skipnext -= 1
+        
+    def set_log(self, log):
+        """Sets a file for logging.
+
+        log may be an open file or a filename.
+        """
+        if hasattr(log, "write"):
+            self.logfile = log
+            self.ownlogfile = False
+        else:
+            self.logfile = open(log, "w")
+            self.ownlogfile = True
+
+    def log(self, message):
+        """logs a message to the file set by set_log."""
+        if self.logfile is not None:
+            self.logfile.write(message+"\n")
+            self.logfile.flush()
+        self._verb(message)
+        
+    def _verb(self, txt):
+        if self.verbose:
+            sys.stderr.write(txt+"\n")
+    
+    def _starttimer(self):
+        self.starttime = time.time()
+
+    def _stoptimer(self):
+        elapsedtime = time.time() - self.starttime
+        self.totaltime = self.totaltime + elapsedtime
+        print "plotting time %s sec (total %s sec)" % (elapsedtime,
+                                                       self.totaltime)
+
+    def _getpositions(self):
+        return self.atoms.get_positions()
+
+    def _getradii(self):
+        if self.radius is not None:
+            if hasattr(self.radius, "shape"):
+                return self.radius   # User has specified an array
+            else:
+                return self.radius * ones(len(self.atoms), float)
+        # No radii specified.  Try getting them from the atoms.
+        try:
+            return self.atoms.get_atomic_radii()
+        except AttributeError:
+            try:
+                z = self._getatomicnumbers()
+            except AttributeError:
+                pass
+            else:
+                return ase.data.covalent_radii[z]
+        # No radius available.  Defaulting to 1.0
+        return ones(len(self.atoms), float)
+
+    def _getatomicnumbers(self):
+        return self.atoms.get_atomic_numbers()
+    
+    def _getcolors(self):
+        # Try any explicitly given colors
+        if self.colors is not None:
+            if type(self.colors) == type({}):
+                self.log("Explicit colors dictionary")
+                return _colorsfromdict(self.colors,
+                                       asarray(self.atoms.get_tags(),int))
+            else:
+                self.log("Explicit colors")
+                return self.colors
+        # Try the color function, if given
+        if self.colorfunction is not None:
+            self.log("Calling color function.")
+            return self.colorfunction(self.atoms)
+        # Maybe the atoms know their own colors
+        try:
+            c = self.atoms.get_colors()
+        except AttributeError:
+            c = None
+        if c is not None:
+            if type(c) == type({}):
+                self.log("Color dictionary from atoms.get_colors()")
+                return _colorsfromdict(c, asarray(self.atoms.get_tags(),int))
+            else:
+                self.log("Colors from atoms.get_colors()")
+                return c
+        # Default to white atoms
+        self.log("No colors: using white")
+        return ones(len(self.atoms), float)
+
+    def _getinvisible(self):
+        if self.invisible is not None:
+            inv = self.invisible
+        else:
+            inv = zeros(len(self.atoms))
+        if self.invisibilityfunction:
+            inv = logical_or(inv, self.invisibilityfunction(self.atoms))
+        r = self._getpositions()
+        if len(r) > len(inv):
+            # This will happen in parallel simulations due to ghost atoms.
+            # They are invisible.  Hmm, this may cause trouble.
+            i2 = ones(len(r))
+            i2[:len(inv)] = inv
+            inv = i2
+            del i2
+        if self.cut["xmin"] is not None:
+            inv = logical_or(inv, less(r[:,0], self.cut["xmin"]))
+        if self.cut["xmax"] is not None:
+            inv = logical_or(inv, greater(r[:,0], self.cut["xmax"]))
+        if self.cut["ymin"] is not None:
+            inv = logical_or(inv, less(r[:,1], self.cut["ymin"]))
+        if self.cut["ymax"] is not None:
+            inv = logical_or(inv, greater(r[:,1], self.cut["ymax"]))
+        if self.cut["zmin"] is not None:
+            inv = logical_or(inv, less(r[:,2], self.cut["zmin"]))
+        if self.cut["zmax"] is not None:
+            inv = logical_or(inv, greater(r[:,2], self.cut["zmax"]))
+        return inv        
+
+    def __del__(self):
+        if self.ownlogfile:
+            self.logfile.close()
+            
+class PrimiPlotter(PrimiPlotterBase):
+    """Primitive PostScript-based plots during a simulation.
+
+    The PrimiPlotter plots atoms during simulations, extracting the
+    relevant information from the list of atoms.  It is created using
+    the list of atoms as an argument to the constructor.  Then one or
+    more output devices must be attached using set_output(device).  The
+    list of supported output devices is at the end.
+
+    The atoms are plotted as circles.  The system is first rotated
+    using the angles specified by set_rotation([vx, vy, vz]).  The
+    rotation is vx degrees around the x axis (positive from the y
+    toward the z axis), then vy degrees around the y axis (from x
+    toward z), then vz degrees around the z axis (from x toward y).
+    The rotation matrix is the same as the one used by RasMol.
+
+    Per default, the system is scaled so it fits within the canvas
+    (autoscale mode).  Autoscale mode is enabled and disables using
+    autoscale("on") or autoscale("off").  A manual scale factor can be
+    set with set_scale(scale), this implies autoscale("off").  The
+    scale factor (from the last autoscale event or from set_scale) can
+    be obtained with get_scale().  Finally, an explicit autoscaling can
+    be triggered with autoscale("now"), this is mainly useful before
+    calling get_scale or before disabling further autoscaling.
+    Finally, a relative scaling factor can be set with
+    SetRelativeScaling(), it is multiplied to the usual scale factor
+    (from autoscale or from set_scale).  This is probably only useful in
+    connection with autoscaling.
+
+    The radii of the atoms are obtained from the first of the following
+    methods which work:
+    
+    1.  If the radii are specified using PrimiPlotter.set_radii(r),
+        they are used.  Must be an array, or a single number.
+
+    2.  If the atoms has a get_atomic_radii() method, it is used.  This is
+        unlikely.
+
+    3.  If the atoms has a get_atomic_numbers() method, the
+        corresponding covalent radii are extracted from the
+        ASE.ChemicalElements module.
+
+    4.  If all else fails, the radius is set to 1.0 Angstrom.
+
+    The atoms are colored using the first of the following methods
+    which work.
+
+    1.  If colors are explicitly set using PrimiPlotter.set_colors(),
+        they are used.
+
+    2.  If these colors are specified as a dictionary, the tags
+        (from atoms.get_tags()) are used as an index into the
+        dictionary to get the actual colors of the atoms.
+
+    3.  If a color function has been set using
+        PrimiPlotter.set_color_function(), it is called with the atoms
+        as an argument, and is expected to return an array of colors.
+
+    4.  If the atoms have a get_colors() method, it is used to get the
+        colors.
+
+    5.  If these colors are specified as a dictionary, the tags
+        (from atoms.get_tags()) are used as an index into the
+        dictionary to get the actual colors of the atoms.
+
+    6.  If all else fails, the atoms will be white.
+
+    The colors are specified as an array of colors, one color per
+    atom.  Each color is either a real number from 0.0 to 1.0,
+    specifying a grayscale (0.0 = black, 1.0 = white), or an array of
+    three numbers from 0.0 to 1.0, specifying RGB values.  The colors
+    of all atoms are thus a Numerical Python N-vector or a 3xN matrix.
+
+    In cases 1a and 3a above, the keys of the dictionary are integers,
+    and the values are either numbers (grayscales) or 3-vectors (RGB
+    values), or strings with X11 color names, which are then
+    translated to RGB values.  Only in case 1a and 3a are strings
+    recognized as colors.
+
+    Some atoms may be invisible, and thus left out of the plot.
+    Invisible atoms are determined from the following algorithm.
+    Unlike the radius or the coloring, all points below are tried and
+    if an atom is invisible by any criterion, it is left out of the plot.
+
+    1.  All atoms are visible.
+    
+    2.  If PrimiPlotter.set_invisible() has be used to specify invisible
+        atoms, any atoms for which the value is non-zero becomes invisible.
+
+    3.  If an invisiblility function has been set with
+        PrimiPlotter.set_invisibility_function(), it is called with the
+        atoms as argument.  It is expected to return an integer per
+        atom, any non-zero value makes that atom invisible.
+
+    4.  If a cut has been specified using set_cut, any atom outside the
+        cut is made invisible.
+
+    Note that invisible atoms are still included in the algorithm for
+    positioning and scaling the plot.
+
+    
+    The following output devices are implemented.
+    
+    PostScriptFile(prefix):  Create PS files names prefix0000.ps etc.
+
+    PnmFile(prefix):  Similar, but makes PNM files.
+
+    GifFile(prefix):  Similar, but makes GIF files.
+
+    JpegFile(prefix):  Similar, but makes JPEG files.
+
+    X11Window():  Show the plot in an X11 window using ghostscript.
+
+    Output devices writing to files take an extra optional argument to
+    the constructor, compress, specifying if the output file should be
+    gzipped.  This is not allowed for some (already compressed) file
+    formats.
+
+    Instead of a filename prefix, a filename containing a % can be
+    used.  In that case the filename is expected to expand to a real
+    filename when used with the Python string formatting operator (%)
+    with the frame number as argument.  Avoid generating spaces in the
+    file names: use e.g. %03d instead of %3d.  
+    """
+    def __init__(self, atoms, verbose=0, timing=0, interval=1, initframe=0):
+        """
+
+        Parameters to the constructor:
+
+        atoms: The atoms to be plottet.
+
+        verbose = 0:  Write progress information to stderr.
+
+        timing = 0:  Collect timing information.
+
+        interval = 1: If specified, a plot is only made every
+        interval'th time update() is called.  Deprecated, normally you
+        should use the interval argument when attaching the plotter to
+        e.g. the dynamics.
+
+        initframe = 0: Initial frame number, i.e. the number of the
+        first plot.
+        
+        """
+        self.atoms = atoms
+        self.outputdevice = []
+        self.angles = zeros(3, float)
+        self.dims = (512, 512)
+        self.verbose = verbose
+        self.timing = timing
+        self.totaltime = 0.0
+        self.radius = None
+        self.colors = None
+        self.colorfunction = None
+        self.n = initframe
+        self.interval = interval
+        self.skipnext = 0 # Number of calls to update before anything happens.
+        self.a_scale = 1
+        self.relativescale = 1.0
+        self.invisible = None
+        self.invisibilityfunction = None
+        self.set_cut()   # No cut
+        self.isparallel = 0
+        self.logfile = None
+        self.ownlogfile = False
+        
+    def set_output(self, device):
+        self.outputdevice.append(device)
+        device.set_dimensions(self.dims)
+        device.set_owner(weakref.proxy(self))
+
+    def set_dimensions(self, dims):
+        "Set the size of the canvas (a 2-tuple)."
+        if self.outputdevice:
+            raise RuntimeError("Cannot set dimensions after an output device has been specified.")
+        self.dims = dims
+        
+    def autoscale(self, mode):
+        if mode == "on":
+            self.a_scale = 1
+        elif mode == "off":
+            self.a_scale = 0
+        elif mode == "now":
+            coords = self._rotate(self.atoms.get_positions())
+            radii = self._getradii()
+            self._autoscale(coords, radii)
+        else:
+            raise ValueError, "Unknown autoscale mode: ",+str(mode)
+
+    def set_scale(self, scale):
+        self.autoscale("off")
+        self.scale = scale
+
+    def get_scale(self):
+        return self.scale
+
+    def set_relative_scale(self, rscale = 1.0):
+        self.relativescale = rscale
+
+    def plot(self):
+        """Create a plot now.  Does not respect the interval timer.
+
+        This method makes a plot unconditionally.  It does not look at
+        the interval variable, nor is this plot taken into account in
+        the counting done by the update() method if an interval
+        variable was specified.
+        """
+        if self.timing:
+            self._starttimer()
+        self.log("PrimiPlotter: Starting plot at "
+                 + time.strftime("%a, %d %b %Y %H:%M:%S"))
+        colors = self._getcolors()
+        invisible = self._getinvisible()
+        coords = self._rotate(self._getpositions())
+        radii = self._getradii()
+        if self.a_scale:
+            self._autoscale(coords,radii)
+        scale = self.scale * self.relativescale
+        coords = scale * coords
+        center = self._getcenter(coords)
+        offset = array(self.dims + (0.0,))/2.0 - center
+        coords = coords + offset
+        self.log("Scale is %f and size is (%d, %d)"
+                 % (scale, self.dims[0], self.dims[1]))
+        self.log("Physical size of plot is %f Angstrom times %f Angstrom"
+                 % (self.dims[0] / scale, self.dims[1] / scale))
+
+        self._verb("Sorting.")
+        order = argsort(coords[:,2])
+        coords = coords[order]  ### take(coords, order)
+        radii = radii[order]    ### take(radii, order)
+        colors = colors[order]  ### take(colors, order)
+        invisible = invisible[order]  ### take(invisible, order)
+        if self.isparallel:
+            id = arange(len(coords))[order] ### take(arange(len(coords)), order)
+        else:
+            id = None
+            
+        radii = radii * scale
+        selector = self._computevisibility(coords, radii, invisible, id)
+        coords = compress(selector, coords, 0)
+        radii = compress(selector, radii)
+        colors = compress(selector, colors, 0)
+        self._makeoutput(scale, coords, radii, colors)
+        self.log("PrimiPlotter: Finished plotting at "
+                 + time.strftime("%a, %d %b %Y %H:%M:%S"))
+        self.log("\n\n")
+        if self.timing:
+            self._stoptimer()
+
+    def _computevisibility(self, coords, rad, invisible, id, zoom = 1):
+        xy = coords[:,:2]
+        typradius = sum(rad) / len(rad)
+        if typradius < 4.0:
+            self.log("Refining visibility check.")
+            if zoom >= 16:
+                raise RuntimeError, "Cannot check visibility - too deep recursion."
+            return self._computevisibility(xy*2, rad*2, invisible, id, zoom*2)
+        else:
+            self.log("Visibility(r_typ = %.1f pixels)" % (typradius,))
+        dims = array(self.dims) * zoom
+        maxr = int(ceil(max(rad))) + 2
+        canvas = zeros((dims[0] + 4*maxr, dims[1] + 4*maxr), int8)
+        # Atoms are only invisible if they are within the canvas, or closer
+        # to its edge than their radius
+        visible = (greater(xy[:,0], -rad) * less(xy[:,0], dims[0]+rad)
+                   * greater(xy[:,1], -rad) * less(xy[:,1], dims[1]+rad)
+                   * logical_not(invisible))
+        # Atoms are visible if not hidden behind other atoms
+        xy = floor(xy + 2*maxr + 0.5).astype(int)
+        masks = {}
+        for i in xrange(len(rad)-1, -1, -1):
+            if (i % 100000) == 0 and i:
+                self._verb(str(i))
+            if not visible[i]:
+                continue
+            x, y = xy[i]
+            r = rad[i]
+            try:
+                mask, invmask, rn = masks[r]
+            except KeyError:
+                rn = int(ceil(r))
+                nmask = 2*rn+1
+                mask = (arange(nmask) - rn)**2
+                mask = less(mask[:,newaxis]+mask[newaxis,:], r*r).astype(int8)
+                invmask = equal(mask, 0).astype(int8)
+                masks[r] = (mask, invmask, rn)
+            window = logical_or(canvas[x-rn:x+rn+1, y-rn:y+rn+1], invmask)
+            hidden = alltrue(window.flat)
+            if hidden:
+                visible[i] = 0
+            else:
+                canvas[x-rn:x+rn+1, y-rn:y+rn+1] = logical_or(canvas[x-rn:x+rn+1, y-rn:y+rn+1], mask)
+        self.log("%d visible, %d hidden out of %d" %
+                   (sum(visible), len(visible) - sum(visible), len(visible)))
+        return visible
+        
+    def _rotate(self, positions):
+        self.log("Rotation angles: %f %f %f" % tuple(self.angles))
+        mat = dot(dot(_rot(self.angles[2], 2),
+                      _rot(self.angles[1], 1)),
+                  _rot(self.angles[0]+pi, 0))
+        return dot(positions, mat)
+
+    def _getcenter(self, coords):
+        return array((max(coords[:,0]) + min(coords[:,0]),
+                      max(coords[:,1]) + min(coords[:,1]), 0.0)) / 2.0
+
+    def _autoscale(self, coords, radii):
+        x = coords[:,0]
+        y = coords[:,1]
+        maxradius = max(radii)
+        deltax = max(x) - min(x) + 2*maxradius
+        deltay = max(y) - min(y) + 2*maxradius
+        scalex = self.dims[0] / deltax
+        scaley = self.dims[1] / deltay
+        self.scale = 0.95 * min(scalex, scaley)
+        self.log("Autoscale: %f" % self.scale)
+
+    def _makeoutput(self, scale, coords, radii, colors):
+        for device in self.outputdevice:
+            device.inform_about_scale(scale)
+            device.plot(self.n, coords, radii, colors)
+        self.n = self.n + 1
+
+
+class ParallelPrimiPlotter(PrimiPlotter):
+    """A version of PrimiPlotter for parallel ASAP simulations.
+
+    Used like PrimiPlotter, but only the output devices on the master
+    node are used.  Most of the processing is distributed on the
+    nodes, but the actual output is only done on the master.  See the
+    PrimiPlotter docstring for details.
+    """
+    def __init__(self, *args, **kwargs):
+        apply(PrimiPlotter.__init__, (self,)+args, kwargs)
+        self.isparallel = 1
+        import Scientific.MPI
+        self.MPI = Scientific.MPI
+        self.mpi = Scientific.MPI.world
+        if self.mpi is None:
+            raise RuntimeError, "MPI is not available."
+        self.master = self.mpi.rank == 0
+        self.mpitag = 42   # Reduce chance of collision with other modules.
+        
+    def set_output(self, device):
+        if self.master:
+            PrimiPlotter.set_output(self, device)
+
+    def set_log(self, log):
+        if self.master:
+            PrimiPlotter.set_log(self, log)
+
+    def _getpositions(self):
+        realpos = self.atoms.get_positions()
+        ghostpos = self.atoms.GetGhostCartesianPositions()
+        self.numberofrealatoms = len(realpos)
+        self.numberofghostatoms = len(ghostpos)
+        return concatenate((realpos, ghostpos))
+
+    def _getatomicnumbers(self):
+        realz = self.atoms.get_atomic_numbers()
+        ghostz = self.atoms.GetGhostAtomicNumbers()
+        return concatenate((realz, ghostz))
+
+    def _getradius(self):
+        r = PrimiPlotter._getradius(self)
+        if len(r) == self.numberofrealatoms + self.numberofghostatoms:
+            # Must have calculated radii from atomic numbers
+            return r
+        else:
+            assert len(r) == self.numberofrealatoms
+            # Heuristic: use minimum r for the ghosts
+            ghostr = min(r) * ones(self.numberofghostatoms, float)
+            return concatenate((r, ghostr))
+
+    def _getcenter(self, coords):
+        # max(x) and min(x) only works for rank-1 arrays in Numeric version 17.
+        maximal = maximum.reduce(coords[:,0:2])
+        minimal = minimum.reduce(coords[:,0:2])
+        recvmax = zeros(2, maximal.typecode())
+        recvmin = zeros(2, minimal.typecode())
+        self.mpi.allreduce(maximal, recvmax, self.MPI.max)
+        self.mpi.allreduce(minimal, recvmin, self.MPI.min)
+        maxx, maxy = recvmax
+        minx, miny = recvmin
+        return array([maxx + minx, maxy + miny, 0.0]) / 2.0
+
+    def _computevisibility(self, xy, rad, invisible, id, zoom = 1):
+        # Find visible atoms, allowing ghost atoms to hide real atoms.
+        v = PrimiPlotter._computevisibility(self, xy, rad, invisible, id, zoom)
+        # Then remove ghost atoms
+        return v * less(id, self.numberofrealatoms)
+
+    def _autoscale(self, coords, radii):
+        self._verb("Autoscale")
+        n = len(self.atoms)
+        x = coords[:n,0]
+        y = coords[:n,1]
+        assert len(x) == len(self.atoms)
+        maximal = array([max(x), max(y), max(radii[:n])])
+        minimal = array([min(x), min(y)])
+        recvmax = zeros(3, maximal.typecode())
+        recvmin = zeros(2, minimal.typecode())
+        self.mpi.allreduce(maximal, recvmax, self.MPI.max)
+        self.mpi.allreduce(minimal, recvmin, self.MPI.min)
+        maxx, maxy, maxradius = recvmax
+        minx, miny = recvmin
+        deltax = maxx - minx + 2*maxradius
+        deltay = maxy - miny + 2*maxradius
+        scalex = self.dims[0] / deltax
+        scaley = self.dims[1] / deltay
+        self.scale = 0.95 * min(scalex, scaley)
+        self.log("Autoscale: %f" % self.scale)
+
+    def _getcolors(self):
+        col = PrimiPlotter._getcolors(self)
+        nghost = len(self.atoms.GetGhostCartesianPositions())
+        newcolshape = (nghost + col.shape[0],) + col.shape[1:]
+        newcol = zeros(newcolshape, col.typecode())
+        newcol[:len(col)] = col
+        return newcol
+    
+    def _makeoutput(self, scale, coords, radii, colors):
+        if len(colors.shape) == 1:
+            # Greyscales
+            ncol = 1
+        else:
+            ncol = colors.shape[1]  # 1 or 3.
+            assert ncol == 3  # RGB values
+        # If one processor says RGB, all must convert
+        ncolthis = array([ncol])
+        ncolmax = zeros((1,), ncolthis.typecode())
+        self.mpi.allreduce(ncolthis, ncolmax, self.MPI.max)
+        ncolmax = ncolmax[0]
+        if ncolmax > ncol:
+            assert ncol == 1
+            colors = colors[:,newaxis] + zeros(ncolmax)[newaxis,:]
+            ncol = ncolmax
+            assert colors.shape == (len(coords), ncol)
+        # Now send data from slaves to master
+        data = zeros((len(coords)+1, 4+ncol), float)
+        data[:-1,:3] = coords
+        data[:-1,3] = radii
+        data[-1,-1] = 4+ncol  # Used to communicate shape
+        if ncol == 1:
+            data[:-1,4] = colors
+        else:
+            data[:-1,4:] = colors
+        if not self.master:
+            self.mpi.send(data, 0, self.mpitag)
+        else:
+            total = [data[:-1]]  # Last row is the dimensions.
+            n = len(coords)
+            colsmin = colsmax = 4+ncol
+            for proc in range(1, self.mpi.size):
+                self._verb("Receiving from processor "+str(proc))
+                fdat = self.mpi.receive(float, proc, self.mpitag)[0]
+                fdat.shape = (-1, fdat[-1])
+                fdat = fdat[:-1]  # Last row is the dimensions.
+                total.append(fdat)
+                n = n + len(fdat)
+                if fdat.shape[1] < colsmin:
+                    colsmin = fdat.shape[1]
+                if fdat.shape[1] > colsmax:
+                    colsmax = fdat.shape[1]
+            self._verb("Merging data")
+            # Some processors may have only greyscales whereas others
+            # may have RGB.  That will cause difficulties.
+            trouble = colsmax != colsmin
+            data = zeros((n, colsmax), float)
+            if trouble:
+                assert data.shape[1] == 7
+            else:
+                assert data.shape[1] == 7 or data.shape[1] == 5
+            i = 0
+            for d in total:
+                if not trouble or d.shape[1] == 7:
+                    data[i:i+len(d)] = d
+                else:
+                    assert d.shape[1] == 5
+                    data[i:i+len(d), :5] = d
+                    data[i:i+len(d), 5] = d[4]
+                    data[i:i+len(d), 6] = d[4]
+                i = i + len(d)
+            assert i == len(data)
+            # Now all data is on the master
+            self._verb("Sorting merged data")
+            order = argsort(data[:,2])
+            data = data[order]   ### take(data, order)
+            coords = data[:,:3]
+            radii = data[:,3]
+            if data.shape[1] == 5:
+                colors = data[:,4]
+            else:
+                colors = data[:,4:]
+            PrimiPlotter._makeoutput(self, scale, coords, radii, colors)
+    
+class _PostScriptDevice:
+    """PostScript based output device."""
+    offset = (0,0)   # Will be changed by some classes
+    def __init__(self):
+        self.scale = 1
+        self.linewidth = 1
+        self.outline = 1
+        
+    def set_dimensions(self, dims):
+        self.dims = dims
+
+    def set_owner(self, owner):
+        self.owner = owner
+        
+    def inform_about_scale(self, scale):
+        self.linewidth = 0.1 * scale
+
+    def set_outline(self, value):
+        self.outline = value
+        return self   # Can chain these calls in set_output()
+        
+    def plot(self, *args, **kargs):
+        self.Doplot(self.PSplot, *args, **kargs)
+        
+    def plotArray(self, *args, **kargs):
+        self.Doplot(self.PSplotArray, *args, **kargs)
+        
+    def PSplot(self, file, n, coords, r, colors, noshowpage=0):
+        xy = coords[:,:2]
+        assert(len(xy) == len(r) and len(xy) == len(colors))
+        if len(colors.shape) == 1:
+            gray = 1
+        else:
+            gray = 0
+            assert(colors.shape[1] == 3)
+        file.write("%!PS-Adobe-2.0\n")
+        file.write("%%Creator: Primiplot\n")
+        file.write("%%Pages: 1\n")        
+        file.write("%%%%BoundingBox: %d %d %d %d\n" %
+                   (self.offset + (self.offset[0] + self.dims[0],
+                                   self.offset[1] + self.dims[1])))
+        file.write("%%EndComments\n")
+        file.write("\n")
+        file.write("% Enforce BoundingBox\n")
+        file.write("%d %d moveto %d 0 rlineto 0 %d rlineto -%d 0 rlineto\n" %
+                   ((self.offset + self.dims + (self.dims[0],))))
+        file.write("closepath clip newpath\n\n")
+        file.write("%f %f scale\n" % (2*(1.0/self.scale,)))
+        file.write("%d %d translate\n" % (self.scale * self.offset[0],
+                                          self.scale * self.offset[1]))
+        file.write("\n")
+        if gray:
+            if self.outline:
+                file.write("/circ { 0 360 arc gsave setgray fill grestore stroke } def\n")
+            else:
+                file.write("/circ { 0 360 arc setgray fill } def\n")
+        else:
+            if self.outline:
+                file.write("/circ { 0 360 arc gsave setrgbcolor fill grestore stroke } def\n")
+            else:
+                file.write("/circ { 0 360 arc setrgbcolor fill } def\n")
+        file.write("%f setlinewidth 0.0 setgray\n" %
+                   (self.linewidth * self.scale,))
+        
+        if gray:
+            data = zeros((len(xy), 4), float)
+            data[:,0] = colors
+            data[:,1:3] = (self.scale * xy)
+            data[:,3] = (self.scale * r)
+            for point in data:
+                file.write("%.3f %.2f %.2f %.2f circ\n" % tuple(point))
+        else:
+            data = zeros((len(xy), 6), float)
+            data[:,0:3] = colors
+            data[:,3:5] = (self.scale * xy)
+            data[:,5] = (self.scale * r)
+            for point in data:
+                file.write("%.3f %.3f %.3f %.2f %.2f %.2f circ\n" % tuple(point))
+        if not noshowpage:
+            file.write("showpage\n")
+            
+    def PSplotArray(self, file, n, data, noshowpage=0):
+        assert(len(data.shape) == 3)
+        assert(data.shape[0] == self.dims[1] and data.shape[1] == self.dims[0])
+        data = clip((256*data).astype(int), 0, 255)
+        file.write("%!PS-Adobe-2.0\n")
+        file.write("%%Creator: Fieldplotter\n")
+        file.write("%%Pages: 1\n")        
+        file.write("%%%%BoundingBox: %d %d %d %d\n" %
+                   (self.offset + (self.offset[0] + self.dims[0],
+                                   self.offset[1] + self.dims[1])))
+        file.write("%%EndComments\n")
+        file.write("\n")
+        file.write("%d %d translate\n" % self.offset)
+        file.write("%f %f scale\n" % self.dims)
+        file.write("\n")
+        file.write("% String holding a single line\n")
+        file.write("/pictline %d string def\n" %(data.shape[1]*data.shape[2],))
+        file.write("\n")
+        file.write("%d %d 8\n" % self.dims)
+        file.write("[%d 0 0 %d 0 0]\n" % self.dims)
+        file.write("{currentfile pictline readhexstring pop}\n")
+        file.write("false %d colorimage\n" % (data.shape[2],))
+        file.write("\n")
+        s = ""
+        for d in data.flat:
+            s += ("%02X" % d)
+            if len(s) >= 72:
+                file.write(s+"\n")
+                s = ""
+        file.write(s+"\n")
+        file.write("\n")
+        if not noshowpage:
+            file.write("showpage\n")
+            
+class _PostScriptToFile(_PostScriptDevice):
+    """Output device for PS files."""
+    compr_suffix = None
+    def __init__(self, prefix, compress = 0):
+        self.compress = compress
+        if "'" in prefix:
+            raise ValueError, "Filename may not contain a quote ('): "+prefix
+        if "%" in prefix:
+            # Assume the user knows what (s)he is doing
+            self.filenames = prefix
+        else:
+            self.filenames = prefix + "%04d" + self.suffix
+            if compress:
+                if self.compr_suffix is None:
+                    raise RuntimeError, "Compression not supported."
+                self.filenames = self.filenames + self.compr_suffix
+        _PostScriptDevice.__init__(self)
+
+class PostScriptFile(_PostScriptToFile):
+    suffix = ".ps"
+    compr_suffix = ".gz"
+    offset = (50,50)
+    # Inherits __init__
+
+    def Doplot(self, plotmethod, n, *args, **kargs):
+        filename = self.filenames % (n,)
+        self.owner.log("Output to PostScript file "+filename)
+        if self.compress:
+            file = os.popen("gzip > '"+filename+"'", "w")
+        else:
+            file = open(filename, "w")
+        apply(plotmethod, (file, n)+args, kargs)
+        file.close()
+
+class _PS_via_PnmFile(_PostScriptToFile):
+    gscmd = "gs -q -sDEVICE=pnmraw -sOutputFile=- -dDEVICEWIDTH=%d -dDEVICEHEIGHT=%d - "
+    # Inherits __init__
+
+    def Doplot(self, plotmethod, n, *args, **kargs):
+        filename = self.filenames % (n,)
+        self.owner.log("Output to bitmapped file " + filename)
+        cmd = self.gscmd + self.converter
+        if self.compress:
+            cmd = cmd + "| gzip "
+            
+        cmd = (cmd+" > '%s'") % (self.dims[0], self.dims[1], filename)
+        file = os.popen(cmd, "w")
+        apply(plotmethod, (file, n)+args, kargs)
+        file.close()
+
+class PnmFile(_PS_via_PnmFile):
+    suffix = ".pnm"
+    compr_suffix = ".gz"
+    converter = ""
+
+class GifFile(_PS_via_PnmFile):
+    suffix = ".gif"
+    converter = "| ppmquant -floyd 256 2>/dev/null | ppmtogif 2>/dev/null"
+
+class JpegFile(_PS_via_PnmFile):
+    suffix = ".jpeg"
+    converter = "| ppmtojpeg --smooth=5"
+    
+class X11Window(_PostScriptDevice):
+    """Shows the plot in an X11 window."""
+    #Inherits __init__
+    gscmd = "gs -q -sDEVICE=x11 -dDEVICEWIDTH=%d -dDEVICEHEIGHT=%d -r72x72 -"
+    def Doplot(self, plotmethod, n, *args, **kargs):
+        self.owner.log("Output to X11 window")
+        try:
+            file = self.pipe
+            self.pipe.write("showpage\n")
+        except AttributeError:
+            filename = self.gscmd % tuple(self.dims)
+            file = os.popen(filename, "w")
+            self.pipe = file
+        kargs["noshowpage"] = 1
+        apply(plotmethod, (file, n)+args, kargs)
+        file.write("flushpage\n")
+        file.flush()
+
+# Helper functions
+def _rot(v, axis):
+    ax1, ax2 = ((1, 2), (0, 2), (0, 1))[axis]
+    c, s = cos(v), sin(v)
+    m = zeros((3,3), float)
+    m[axis,axis] = 1.0
+    m[ax1,ax1] = c
+    m[ax2,ax2] = c
+    m[ax1,ax2] = s
+    m[ax2,ax1] = -s
+    return m
+
+def _colorsfromdict(dict, cls):
+    """Extract colors from dictionary using cls as key."""
+    assert(type(dict) == type({}))
+    # Allow local modifications, to replace strings with rgb values.
+    dict = dict.copy()  
+    isgray, isrgb = 0, 0
+    for k in dict.keys():
+        v = dict[k]
+        if type(v) == type("string"):
+            v = color_table[v]
+            dict[k] = v
+        try:
+            if len(v) == 3:
+                isrgb = 1 # Assume it is an RGB value
+                if not hasattr(v, "shape"):
+                    dict[k] = array(v)   # Convert to array
+            else:
+                raise RuntimeError, "Unrecognized color object "+repr(v)
+        except TypeError:
+            isgray = 1 # Assume it is a number
+    if isgray and isrgb:
+        # Convert all to RGB
+        for k in dict.keys():
+            v = dict[k]
+            if not hasattr(v, "shape"):
+                dict[k] = v * ones(3, float)
+    # Now the dictionary is ready
+    if isrgb:
+        colors = zeros((len(cls),3), float)
+    else:
+        colors = zeros((len(cls),), float)
+    for i in xrange(len(cls)):
+        colors[i] = dict[cls[i]]
+    return colors
+
Index: ase/version.py
===================================================================
--- ase/version.py	(revision 0)
+++ ase/version.py	(revision 1)
@@ -0,0 +1,11 @@
+# Copyright (C) 2003  CAMP
+# Please see the accompanying LICENSE file for further information.
+
+version_base = '3.4.1'
+
+try:
+    from ase.svnversion import svnversion
+except ImportError:
+    version = version_base
+else:
+    version = version_base + '.' + svnversion
Index: ase/calculators/emt.py
===================================================================
--- ase/calculators/emt.py	(revision 0)
+++ ase/calculators/emt.py	(revision 1)
@@ -0,0 +1,287 @@
+"""Effective medium theory potential."""
+
+from math import sqrt, exp, log, pi
+
+import numpy as np
+import sys
+
+from ase.data import atomic_numbers, chemical_symbols
+from ase.units import Bohr
+
+
+parameters = {
+#          E0     s0    V0     eta2    kappa   lambda  n0
+#          eV     bohr  eV     bohr^-1 bohr^-1 bohr^-1 bohr^-3
+    'H':  (-2.21, 0.71, 2.132, 1.652,  2.790,  1.892,  0.00547, 'dimer'),
+    'Al': (-3.28, 3.00, 1.493, 1.240,  2.000,  1.169,  0.00700, 'fcc'),
+    'Cu': (-3.51, 2.67, 2.476, 1.652,  2.740,  1.906,  0.00910, 'fcc'),
+    'Ag': (-2.96, 3.01, 2.132, 1.652,  2.790,  1.892,  0.00547, 'fcc'),
+    'Au': (-3.80, 3.00, 2.321, 1.674,  2.873,  2.182,  0.00703, 'fcc'),
+    'Ni': (-4.44, 2.60, 3.673, 1.669,  2.757,  1.948,  0.01030, 'fcc'),
+    'Pd': (-3.90, 2.87, 2.773, 1.818,  3.107,  2.155,  0.00688, 'fcc'),
+    'Pt': (-5.85, 2.90, 4.067, 1.812,  3.145,  2.192,  0.00802, 'fcc'),
+    'C':  (-1.97, 1.18, 0.132, 3.652,  5.790,  2.892,  0.01322, 'dimer'),
+    'N':  (-4.97, 1.18, 0.132, 2.652,  3.790,  2.892,  0.01222, 'dimer'),
+    'O':  (-2.97, 1.25, 2.132, 3.652,  5.790,  4.892,  0.00850, 'dimer')}
+
+beta = 1.809     #(16 * pi / 3)**(1.0 / 3) / 2**0.5
+#                 but preserve historical rounding.
+
+#eta1 = 0.5 / Bohr
+
+class EMT:
+    disabled = False  # Set to True to disable (asap does this).
+    
+    def __init__(self, fix_alloys=False):
+        self.energy = None
+        self.fix_alloys = fix_alloys
+        if self.disabled:
+            print >>sys.stderr, """
+            ase.EMT has been disabled by Asap.  Most likely, you
+            intended to use Asap's EMT calculator, but accidentally
+            imported ase's EMT calculator after Asap's.  This could
+            happen if your script contains the lines
+
+              from asap3 import *
+              from ase.calculators.emt import EMT
+            Swap the two lines to solve the problem.
+
+            (or 'from ase import *' in older versions of ASE.) Swap 
+            the two lines to solve the problem.
+
+            In the UNLIKELY event that you actually wanted to use
+            ase.calculators.emt.EMT although asap3 is loaded into memory, please
+            reactivate it with the command
+              ase.calculators.emt.EMT.disabled = False
+            """
+            raise RuntimeError('ase.EMT has been disabled.  ' +
+                               'See message printed above.')
+        
+    def get_spin_polarized(self):
+        return False
+    
+    def initialize(self, atoms):
+        self.par = {}
+        self.rc = 0.0
+        self.numbers = atoms.get_atomic_numbers()
+        maxseq = 0.0
+        seen = {}
+        for Z in self.numbers:
+            if Z not in seen:
+                seen[Z] = True
+                ss = parameters[chemical_symbols[Z]][1] * Bohr
+                if maxseq < ss:
+                    maxseq = ss
+        rc = self.rc = beta * maxseq * 0.5 * (sqrt(3) + sqrt(4))
+        rr = rc * 2 * sqrt(4) / (sqrt(3) + sqrt(4))
+        self.acut = np.log(9999.0) / (rr - rc)
+        for Z in self.numbers:
+            if Z not in self.par:
+                p = parameters[chemical_symbols[Z]]
+                s0 = p[1] * Bohr
+                eta2 = p[3] / Bohr
+                kappa = p[4] / Bohr
+                x = eta2 * beta * s0
+                gamma1 = 0.0
+                gamma2 = 0.0
+                if p[7] == 'fcc':
+                    for i, n in enumerate([12, 6, 24, 12]):
+                        r = s0 * beta * sqrt(i + 1)
+                        x = n / (12 * (1.0 + exp(self.acut * (r - rc))))
+                        gamma1 += x * exp(-eta2 * (r - beta * s0))
+                        gamma2 += x * exp(-kappa / beta * (r - beta * s0))
+                elif p[7] == 'dimer':
+                    r = s0 * beta
+                    n = 1
+                    x = n / (12 * (1.0 + exp(self.acut * (r - rc))))
+                    gamma1 += x * exp(-eta2 * (r - beta * s0))
+                    gamma2 += x * exp(-kappa / beta * (r - beta * s0))
+                else:
+                    raise RuntimeError
+
+                self.par[Z] = {'E0': p[0],
+                               's0': s0,
+                               'V0': p[2],
+                               'eta2': eta2,
+                               'kappa': kappa,
+                               'lambda': p[5] / Bohr,
+                               'n0': p[6] / Bohr**3,
+                               'rc': rc,
+                               'gamma1': gamma1,
+                               'gamma2': gamma2}
+                #if rc + 0.5 > self.rc:
+                #    self.rc = rc + 0.5
+
+        self.ksi = {}
+        for s1, p1 in self.par.items():
+            self.ksi[s1] = {}
+            for s2, p2 in self.par.items():
+                #self.ksi[s1][s2] = (p2['n0'] / p1['n0'] *
+                #                    exp(eta1 * (p1['s0'] - p2['s0'])))
+                self.ksi[s1][s2] = p2['n0'] / p1['n0']
+                
+        self.forces = np.empty((len(atoms), 3))
+        self.sigma1 = np.empty(len(atoms))
+        self.deds = np.empty(len(atoms))
+                    
+    def update(self, atoms):
+        if (self.energy is None or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def calculation_required(self, atoms, quantities):
+        if len(quantities) == 0:
+            return False
+
+        return (self.energy is None or
+                len(self.numbers) != len(atoms) or
+                (self.numbers != atoms.get_atomic_numbers()).any() or
+                (self.positions != atoms.get_positions()).any() or
+                (self.pbc != atoms.get_pbc()).any() or
+                (self.cell != atoms.get_cell()).any())
+                
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.energy
+
+    def get_numeric_forces(self, atoms):
+        self.update(atoms)
+        p = atoms.positions
+        p0 = p.copy()
+        forces = np.empty_like(p)
+        eps = 0.0001
+        for a in range(len(p)):
+            for c in range(3):
+                p[a, c] += eps
+                self.calculate(atoms)
+                de = self.energy
+                p[a, c] -= 2 * eps
+                self.calculate(atoms)
+                de -= self.energy
+                p[a, c] += eps
+                forces[a, c] = -de / (2 * eps)
+        p[:] = p0
+        return forces
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+    
+    def get_stress(self, atoms):
+        raise NotImplementedError
+    
+    def calculate(self, atoms):
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+        
+        icell = np.linalg.inv(self.cell)
+        scaled = np.dot(self.positions, icell)
+        N = []
+        for i in range(3):
+            if self.pbc[i]:
+                scaled[:, i] %= 1.0
+                v = icell[:, i]
+                h = 1 / sqrt(np.dot(v, v))
+                N.append(int(self.rc / h) + 1)
+            else:
+                N.append(0)
+
+        R = np.dot(scaled, self.cell)
+        
+        self.energy = 0.0
+        self.sigma1[:] = 0.0
+        self.forces[:] = 0.0
+        
+        N1, N2, N3 = N
+        natoms = len(atoms)
+        for i1 in range(-N1, N1 + 1):
+            for i2 in range(-N2, N2 + 1):
+                for i3 in range(-N3, N3 + 1):
+                    C = np.dot((i1, i2, i3), self.cell)
+                    Q = R + C
+                    c = (i1 == 0 and i2 == 0 and i3 == 0)
+                    for a1 in range(natoms):
+                        Z1 = self.numbers[a1]
+                        p1 = self.par[Z1]
+                        ksi = self.ksi[Z1]
+                        for a2 in range(natoms):
+                            if c and a2 == a1:
+                                continue
+                            d = Q[a2] - R[a1]
+                            r = sqrt(np.dot(d, d))
+                            if r < self.rc + 0.5:
+                                Z2 = self.numbers[a2]
+                                if self.fix_alloys:
+                                    p2 = self.par[Z2]
+                                else:
+                                    p2 = p1  # Old buggy behaviour
+                                self.interact1(a1, a2, d, r, p1, p2, ksi[Z2])
+                                
+        for a in range(natoms):
+            Z = self.numbers[a]
+            p = self.par[Z]
+            try:
+                ds = -log(self.sigma1[a] / 12) / (beta * p['eta2'])
+            except (OverflowError, ValueError):
+                self.deds[a] = 0.0
+                self.energy -= p['E0']
+                continue
+            x = p['lambda'] * ds
+            y = exp(-x)
+            z = 6 * p['V0'] * exp(-p['kappa'] * ds)
+            self.deds[a] = ((x * y * p['E0'] * p['lambda'] + p['kappa'] * z) /
+                            (self.sigma1[a] * beta * p['eta2']))
+            e = p['E0'] * ((1 + x) * y - 1) + z
+            self.energy += p['E0'] * ((1 + x) * y - 1) + z
+
+        for i1 in range(-N1, N1 + 1):
+            for i2 in range(-N2, N2 + 1):
+                for i3 in range(-N3, N3 + 1):
+                    C = np.dot((i1, i2, i3), self.cell)
+                    Q = R + C
+                    c = (i1 == 0 and i2 == 0 and i3 == 0)
+                    for a1 in range(natoms):
+                        Z1 = self.numbers[a1]
+                        p1 = self.par[Z1]
+                        ksi = self.ksi[Z1]
+                        for a2 in range(natoms):
+                            if c and a2 == a1:
+                                continue
+                            d = Q[a2] - R[a1]
+                            r = sqrt(np.dot(d, d))
+                            if r < self.rc + 0.5:
+                                Z2 = self.numbers[a2]
+                                if self.fix_alloys:
+                                    p2 = self.par[Z2]
+                                else:
+                                    p2 = p1  # Old buggy behaviour
+                                self.interact2(a1, a2, d, r, p1, p2, ksi[Z2])
+
+    def interact1(self, a1, a2, d, r, p1, p2, ksi):
+        x = exp(self.acut * (r - self.rc))
+        theta = 1.0 / (1.0 + x)
+        y = (0.5 * p1['V0'] * exp(-p2['kappa'] * (r / beta - p2['s0'])) *
+             ksi / p1['gamma2'] * theta)
+        self.energy -= y
+        f = y * (p2['kappa'] / beta + self.acut * theta * x) * d / r
+        self.forces[a1] += f
+        self.forces[a2] -= f
+        self.sigma1[a1] += (exp(-p2['eta2'] * (r - beta * p2['s0'])) *
+                            ksi * theta / p1['gamma1'])
+
+    def interact2(self, a1, a2, d, r, p1, p2, ksi):
+        x = exp(self.acut * (r - self.rc))
+        theta = 1.0 / (1.0 + x)
+        y = (exp(-p2['eta2'] * (r - beta * p2['s0'])) *
+             ksi / p1['gamma1'] * theta * self.deds[a1])
+        f = y * (p2['eta2'] + self.acut * theta * x) * d / r
+        self.forces[a1] -= f
+        self.forces[a2] += f
+
Index: ase/calculators/morse.py
===================================================================
--- ase/calculators/morse.py	(revision 0)
+++ ase/calculators/morse.py	(revision 1)
@@ -0,0 +1,46 @@
+from math import exp, sqrt
+import numpy as np
+
+class MorsePotential:
+    """Morse potential.
+
+    Default values chosen to be similar as Lennard-Jones.
+    """
+    def __init__(self, rho0=6.0, epsilon=1.0, r0=1.0):
+        self.epsilon = epsilon
+        self.rho0 = rho0
+        self.r0 = r0
+        self.positions = None
+
+    def update(self, atoms):
+        assert not atoms.get_pbc().any()
+        if (self.positions is None or
+            (self.positions != atoms.get_positions()).any()):
+            self.calculate(atoms)
+
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.energy
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self._forces
+
+    def get_stress(self, atoms):
+        return np.zeros((3, 3))
+    
+    def calculate(self, atoms):
+        positions = atoms.get_positions()
+        self.energy = 0.0
+        self._forces = np.zeros((len(atoms), 3))
+        preF = 2 * self.epsilon * self.rho0 / self.r0
+        for i1, p1 in enumerate(positions):
+            for i2, p2 in enumerate(positions[:i1]):
+                diff = p2 - p1
+                r = sqrt(np.dot(diff, diff))
+                expf = exp(self.rho0 * (1.0 - r / self.r0))
+                self.energy += self.epsilon * expf * (expf - 2)
+                F = preF * expf * (expf - 1) * diff / r
+                self._forces[i1] -= F
+                self._forces[i2] += F
+        self.positions = positions.copy()
Index: ase/calculators/dacapo.py
===================================================================
--- ase/calculators/dacapo.py	(revision 0)
+++ ase/calculators/dacapo.py	(revision 1)
@@ -0,0 +1,232 @@
+import numpy as np
+
+from ase.old import OldASEListOfAtomsWrapper
+
+try:
+    import Numeric as num
+except ImportError:
+    pass
+
+def np2num(a, typecode=None):
+    if num.__version__ > '23.8':
+        return num.array(a, typecode)
+    if typecode is None:
+        typecode = num.Float
+    b = num.fromstring(a.tostring(), typecode)
+    b.shape = a.shape
+    return b
+
+def restart(filename, **kwargs):
+    calc = Dacapo(filename, **kwargs)
+    atoms = calc.get_atoms()
+    return atoms, calc
+
+class Dacapo:
+    def __init__(self, filename=None, stay_alive=False, stress=False,
+                 **kwargs):
+
+        self.kwargs = kwargs
+        self.stay_alive = stay_alive
+        self.stress = stress
+        
+        if filename is not None:
+            from Dacapo import Dacapo
+            self.loa = Dacapo.ReadAtoms(filename, **kwargs)
+            self.calc = self.loa.GetCalculator()
+        else:
+            self.loa = None
+            self.calc = None
+
+        self.pps = []
+        
+    def set_pp(self, Z, path):
+        self.pps.append((Z, path))
+
+    def set_txt(self, txt):
+        if self.calc is None:
+            self.kwargs['txtout'] = txt
+        else:
+            self.calc.SetTxtFile(txt)
+
+    def set_nc(self, nc):
+        if self.calc is None:
+            self.kwargs['out'] = nc
+        else:
+            self.calc.SetNetCDFFile(nc)
+
+    def update(self, atoms):
+        from Dacapo import Dacapo
+        if self.calc is None:
+            if 'nbands' not in self.kwargs:
+                n = sum([valence[atom.symbol] for atom in atoms])
+                self.kwargs['nbands'] = int(n * 0.65) + 4
+
+            magmoms = atoms.get_initial_magnetic_moments()
+            if magmoms.any():
+                self.kwargs['spinpol'] = True
+
+            self.calc = Dacapo(**self.kwargs)
+
+            if self.stay_alive:
+                self.calc.StayAliveOn()
+            else:
+                self.calc.StayAliveOff()
+
+            if self.stress:
+                self.calc.CalculateStress()
+
+            for Z, path in self.pps:
+                self.calc.SetPseudoPotential(Z, path)
+
+        if self.loa is None:
+            from ASE import Atom, ListOfAtoms
+            numbers = atoms.get_atomic_numbers()
+            positions = atoms.get_positions()
+            magmoms = atoms.get_initial_magnetic_moments()
+            self.loa = ListOfAtoms([Atom(Z=numbers[a],
+                                         position=positions[a],
+                                         magmom=magmoms[a])
+                                    for a in range(len(atoms))],
+                                   cell=np2num(atoms.get_cell()),
+                                   periodic=tuple(atoms.get_pbc()))
+            self.loa.SetCalculator(self.calc)
+        else:
+            self.loa.SetCartesianPositions(np2num(atoms.get_positions()))
+            self.loa.SetUnitCell(np2num(atoms.get_cell()), fix=True)
+            
+    def get_atoms(self):
+        atoms = OldASEListOfAtomsWrapper(self.loa).copy()
+        atoms.set_calculator(self)
+        return atoms
+    
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.calc.GetPotentialEnergy()
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return np.array(self.calc.GetCartesianForces())
+
+    def get_stress(self, atoms):
+        self.update(atoms)
+        stress = np.array(self.calc.GetStress())
+        if stress.ndim == 2:
+            return stress.ravel()[[0, 4, 8, 5, 2, 1]]
+        else:
+            return stress
+
+    def calculation_required(self, atoms, quantities):
+        if self.calc is None:
+            return True
+
+        if atoms != self.get_atoms():
+            return True
+
+        return False
+        
+    def get_number_of_bands(self):
+        return self.calc.GetNumberOfBands()
+
+    def get_k_point_weights(self):
+        return np.array(self.calc.GetIBZKPointWeights())
+
+    def get_number_of_spins(self):
+        return 1 + int(self.calc.GetSpinPolarized())
+
+    def get_eigenvalues(self, kpt=0, spin=0):
+        return np.array(self.calc.GetEigenvalues(kpt, spin))
+
+    def get_fermi_level(self):
+        return self.calc.GetFermiLevel()
+
+    def get_number_of_grid_points(self):
+        return np.array(self.get_pseudo_wave_function(0, 0, 0).shape)
+
+    def get_pseudo_density(self, spin=0):
+        return np.array(self.calc.GetDensityArray(s))
+    
+    def get_pseudo_wave_function(self, band=0, kpt=0, spin=0, pad=True):
+        kpt_c = self.get_bz_k_points()[kpt]
+        state = self.calc.GetElectronicStates().GetState(band=band, spin=spin,
+                                                         kptindex=kpt)
+
+        # Get wf, without bloch phase (Phase = True doesn't do anything!)
+        wave = state.GetWavefunctionOnGrid(phase=False)
+
+        # Add bloch phase if this is not the Gamma point
+        if np.all(kpt_c == 0):
+            return wave
+        coord = state.GetCoordinates()
+        phase = coord[0] * kpt_c[0] + coord[1] * kpt_c[1] + coord[2] * kpt_c[2]
+        return np.array(wave) * np.exp(-2.j * np.pi * phase) # sign! XXX
+
+        #return np.array(self.calc.GetWaveFunctionArray(n, k, s)) # No phase!
+
+    def get_bz_k_points(self):
+        return np.array(self.calc.GetBZKPoints())
+
+    def get_ibz_k_points(self):
+        return np.array(self.calc.GetIBZKPoints())
+
+    def get_wannier_localization_matrix(self, nbands, dirG, kpoint,
+                                        nextkpoint, G_I, spin):
+        return np.array(self.calc.GetWannierLocalizationMatrix(
+            G_I=G_I.tolist(), nbands=nbands, dirG=dirG.tolist(),
+            kpoint=kpoint, nextkpoint=nextkpoint, spin=spin))
+    
+    def initial_wannier(self, initialwannier, kpointgrid, fixedstates,
+                        edf, spin):
+        # Use initial guess to determine U and C
+        init = self.calc.InitialWannier(initialwannier, self.atoms,
+                                        np2num(kpointgrid, num.Int))
+
+        states = self.calc.GetElectronicStates()
+        waves = [[state.GetWaveFunction()
+                  for state in states.GetStatesKPoint(k, spin)]
+                 for k in self.calc.GetIBZKPoints()] 
+
+        init.SetupMMatrix(waves, self.calc.GetBZKPoints())
+        c, U = init.GetListOfCoefficientsAndRotationMatrices(
+            (self.calc.GetNumberOfBands(), fixedstates, edf))
+        U = np.array(U)
+        for k in range(len(c)):
+            c[k] = np.array(c[k])
+        return c, U
+
+valence = {
+'H':   1,
+'B':   3,
+'C':   4,
+'N':   5,
+'O':   6,
+'Li':  1,
+'Na':  1,
+'K':   9,
+'Mg':  8,
+'Ca': 10,
+'Sr': 10,
+'Al':  3,
+'Ga': 13,
+'Sc': 11,
+'Ti': 12,
+'V':  13,
+'Cr': 14,
+'Mn':  7,
+'Fe':  8,
+'Co':  9,
+'Ni': 10,
+'Cu': 11,
+'Zn': 12,
+'Y':  11,
+'Zr': 12,
+'Nb': 13,
+'Mo':  6,
+'Ru':  8,
+'Rh':  9,
+'Pd': 10,
+'Ag': 11,
+'Cd': 12,
+'Ir': 9,
+'Pt': 10,
+'Au': 11,
+}
Index: ase/calculators/siesta.py
===================================================================
--- ase/calculators/siesta.py	(revision 0)
+++ ase/calculators/siesta.py	(revision 1)
@@ -0,0 +1,734 @@
+"""This module defines an ASE interface to SIESTA.
+
+http://www.uam.es/departamentos/ciencias/fismateriac/siesta
+"""
+
+import os
+from os.path import join, isfile, islink
+from cmath import exp
+import array
+
+import numpy as np
+
+from ase.data import chemical_symbols
+from ase.units import Rydberg, fs
+
+class Siesta:
+    """Class for doing SIESTA calculations.
+
+    The default parameters are very close to those that the SIESTA
+    Fortran code would use.  These are the exceptions::
+    
+      calc = Siesta(label='siesta', xc='LDA', pulay=5, mix=0.1)
+
+    Use the set_fdf method to set extra FDF parameters::
+    
+      calc.set_fdf('PAO.EnergyShift', 0.01 * Rydberg)
+
+    """
+    def __init__(self, label='siesta', xc='LDA', kpts=None, nbands=None,
+                 width=None, meshcutoff=None, charge=None,
+                 pulay=5, mix=0.1, maxiter=120,
+                 basis=None, ghosts=[],
+                 write_fdf=True):
+        """Construct SIESTA-calculator object.
+
+        Parameters
+        ==========
+        label: str
+            Prefix to use for filenames (label.fdf, label.txt, ...).
+            Default is 'siesta'.
+        xc: str
+            Exchange-correlation functional.  Must be one of LDA, PBE,
+            revPBE, RPBE.
+        kpts: list of three int
+            Monkhost-Pack sampling.
+        nbands: int
+            Number of bands.
+        width: float
+            Fermi-distribution width in eV.
+        meshcutoff: float
+            Cutoff energy in eV for grid.
+        charge: float
+            Total charge of the system.
+        pulay: int
+            Number of old densities to use for Pulay mixing.
+        mix: float
+            Mixing parameter between zero and one for density mixing.
+        write_fdf: bool
+            Use write_fdf=False to use your own fdf-file.
+
+        Examples
+        ========
+        Use default values:
+        
+        >>> h = Atoms('H', calculator=Siesta())
+        >>> h.center(vacuum=3.0)
+        >>> e = h.get_potential_energy()
+        
+        """
+        
+        self.label = label#################### != out
+        self.xc = xc
+        self.kpts = kpts
+        self.nbands = nbands
+        self.width = width
+        self.meshcutoff = meshcutoff
+        self.charge = charge
+        self.pulay = pulay
+        self.mix = mix
+        self.maxiter = maxiter
+        self.basis = basis
+        self.ghosts = ghosts
+        self.write_fdf_file = write_fdf
+    
+        self.converged = False
+        self.fdf = {}
+        self.e_fermi = None
+        
+    def update(self, atoms):
+        if (not self.converged or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        self.numbers = atoms.get_atomic_numbers().copy()
+        self.species = []
+        for a, Z in enumerate(self.numbers):
+            if a in self.ghosts:
+                Z = -Z
+            if Z not in self.species:
+                self.species.append(Z)
+
+        if 'SIESTA_PP_PATH' in os.environ:
+            pppaths = os.environ['SIESTA_PP_PATH'].split(':')
+        else:
+            pppaths = []
+
+        for Z in self.species:
+            symbol = chemical_symbols[abs(Z)]
+            name = symbol + '.vps'
+            name1 = symbol + '.psf'
+            found = False
+            for path in pppaths:
+                filename = join(path, name)
+                filename1 = join(path, name1)
+                if isfile(filename) or islink(filename):
+                    found = True
+                    if path != '.':
+                        if islink(name) or isfile(name):
+                            os.remove(name)
+                        os.symlink(filename, name)
+
+                elif isfile(filename1) or islink(filename1):
+                    found = True
+                    if path != '.':
+                        if islink(name1) or isfile(name1):
+                            os.remove(name1)
+                        os.symlink(filename1, name1)
+            if not found:
+                raise RuntimeError('No pseudopotential for %s!' % symbol)
+
+        self.converged = False
+        
+    def get_potential_energy(self, atoms, force_consistent=False):
+        self.update(atoms)
+
+        if force_consistent:
+            return self.efree
+        else:
+            # Energy extrapolated to zero Kelvin:
+            return  (self.etotal + self.efree) / 2
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+    
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress.copy()
+
+    def get_dipole_moment(self, atoms):
+        """Returns total dipole moment of the system."""
+        self.update(atoms)
+        return self.dipole
+
+    def read_dipole(self):
+        dipolemoment = np.zeros([1, 3])
+        for line in open(self.label + '.txt', 'r'):
+            if line.rfind('Electric dipole (Debye)') > -1:
+                dipolemoment = np.array([float(f) for f in line.split()[5:8]])
+        #debye to e*Ang (the units of VASP)
+        dipolemoment = dipolemoment*0.2081943482534
+        return dipolemoment
+
+    def calculate(self, atoms):
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+
+        if self.write_fdf_file:
+            self.write_fdf(atoms)
+
+        siesta = os.environ['SIESTA_SCRIPT']
+        locals = {'label': self.label}
+        execfile(siesta, {}, locals)
+        exitcode = locals['exitcode']
+        if exitcode != 0:
+            raise RuntimeError(('Siesta exited with exit code: %d.  ' +
+                                'Check %s.txt for more information.') %
+                               (exitcode, self.label))
+        
+        self.dipole = self.read_dipole()
+        self.read()
+
+        self.converged = True
+        
+    def set_fdf(self, key, value):
+        """Set FDF parameter."""
+        self.fdf[key] = value
+                
+    def write_fdf(self, atoms):
+        """Write input parameters to fdf-file."""
+        fh = open(self.label + '.fdf', 'w')
+
+        fdf = {
+            'SystemLabel': self.label,
+            'AtomicCoordinatesFormat': 'Ang',
+            'LatticeConstant': 1.0,
+            'NumberOfAtoms': len(atoms),
+            'MeshCutoff': self.meshcutoff,
+            'NetCharge': self.charge,
+            'ElectronicTemperature': self.width,
+            'NumberOfEigenStates': self.nbands,
+            'DM.UseSaveDM': self.converged,
+            'PAO.BasisSize': self.basis,
+            'SolutionMethod': 'diagon',
+            'DM.NumberPulay': self.pulay,
+            'DM.MixingWeight': self.mix,
+            'MaxSCFIterations': self.maxiter
+            }
+        
+        if self.xc != 'LDA':
+            fdf['xc.functional'] = 'GGA'
+            fdf['xc.authors'] = self.xc
+
+        magmoms = atoms.get_initial_magnetic_moments()
+        if magmoms.any():
+            fdf['SpinPolarized'] = True
+            fh.write('%block InitSpin\n')
+            for n, M in enumerate(magmoms):
+                if M != 0:
+                    fh.write('%d %.14f\n' % (n + 1, M))
+            fh.write('%endblock InitSpin\n')
+        
+        fdf['Number_of_species'] = len(self.species)
+
+        fdf.update(self.fdf)
+
+        for key, value in fdf.items():
+            if value is None:
+                continue
+
+            if isinstance(value, list):
+                fh.write('%%block %s\n' % key)
+                for line in value:
+                    fh.write(line + '\n')
+                fh.write('%%endblock %s\n' % key)
+            else:
+                unit = keys_with_units.get(fdfify(key))
+                if unit is None:
+                    fh.write('%s %s\n' % (key, value))
+                else:
+                    if 'fs**2' in unit:
+                        value /= fs**2
+                    elif 'fs' in unit:
+                        value /= fs
+                    fh.write('%s %f %s\n' % (key, value, unit))
+
+        fh.write('%block LatticeVectors\n')
+        for v in self.cell:
+            fh.write('%.14f %.14f %.14f\n' % tuple(v))
+        fh.write('%endblock LatticeVectors\n')
+
+        fh.write('%block Chemical_Species_label\n')
+        for n, Z in enumerate(self.species):
+            fh.write('%d %s %s\n' % (n + 1, Z, chemical_symbols[abs(Z)]))
+        fh.write('%endblock Chemical_Species_label\n')
+
+        fh.write('%block AtomicCoordinatesAndAtomicSpecies\n')
+        a = 0
+        for pos, Z in zip(self.positions, self.numbers):
+            if a in self.ghosts:
+                Z = -Z
+            a += 1
+            fh.write('%.14f %.14f %.14f' %  tuple(pos))
+            fh.write(' %d\n' % (self.species.index(Z) + 1))
+        fh.write('%endblock AtomicCoordinatesAndAtomicSpecies\n')
+
+        if self.kpts is not None:
+            fh.write('%block kgrid_Monkhorst_Pack\n')
+            for i in range(3):
+                for j in range(3):
+                    if i == j:
+                        fh.write('%d ' % self.kpts[i])
+                    else:
+                        fh.write('0 ')
+                fh.write('%.1f\n' % (((self.kpts[i] + 1) % 2) * 0.5))
+            fh.write('%endblock kgrid_Monkhorst_Pack\n')
+        
+        fh.close()
+        
+    def read(self):
+        """Read results from SIESTA's text-output file."""
+        text = open(self.label + '.txt', 'r').read().lower()
+        assert 'error' not in text
+        lines = iter(text.split('\n'))
+
+        # Get the number of grid points used:
+        for line in lines:
+            if line.startswith('initmesh: mesh ='):
+                self.grid = [int(word) for word in line.split()[3:8:2]]
+                break
+
+        # Stress (fixed so it's compatible with a MD run from siesta):
+        for line in lines:
+            if line.startswith('siesta: stress tensor '):
+                self.stress = np.empty((3, 3))
+                for i in range(3):
+                    tmp = lines.next().split()
+                    if len(tmp) == 4:
+                        self.stress[i] = [float(word) for word in tmp[1:]]
+                    else:
+                        self.stress[i] = [float(word) for word in tmp]
+                break
+        else:
+            raise RuntimeError
+
+        text = open(self.label + '.txt', 'r').read().lower()
+        lines = iter(text.split('\n'))
+        # Energy (again a fix to make it compatible with a MD run from siesta):
+        counter = 0
+        for line in lines:
+            if line.startswith('siesta: etot    =') and counter == 0:
+                counter += 1
+            elif line.startswith('siesta: etot    ='):
+                self.etotal = float(line.split()[-1])
+                self.efree = float(lines.next().split()[-1])
+                break
+        else:
+            raise RuntimeError
+
+        # Forces (changed so forces smaller than -999eV/A can be fetched):
+        lines = open(self.label + '.FA', 'r').readlines()
+        assert int(lines[0]) == len(self.numbers)
+        assert len(lines) == len(self.numbers) + 1
+        lines = lines[1:]
+        self.forces = np.zeros((len(lines), 3))
+        for i in range(len(lines)):
+            self.forces[i, 0] = float(lines[i][6:18].strip())
+            self.forces[i, 1] = float(lines[i][18:30].strip())
+            self.forces[i, 2] = float(lines[i][30:42].strip())
+
+    def read_eig(self):
+        if self.e_fermi is not None:
+            return
+
+        assert os.access(self.label + '.EIG', os.F_OK)
+        assert os.access(self.label + '.KP', os.F_OK)
+
+        # Read k point weights
+        text = open(self.label + '.KP', 'r').read()
+        lines = text.split('\n')
+        n_kpts = int(lines[0].strip())
+        self.weights = np.zeros((n_kpts,))
+        for i in range(n_kpts):
+            l = lines[i + 1].split()
+            self.weights[i] = float(l[4])
+
+        # Read eigenvalues and fermi-level
+        text = open(self.label+'.EIG','r').read()
+        lines = text.split('\n')
+        self.e_fermi = float(lines[0].split()[0])
+        tmp = lines[1].split()
+        self.n_bands = int(tmp[0])
+        n_spin_bands = int(tmp[1])
+        self.spin_pol = n_spin_bands == 2
+        lines = lines[2:-1]
+        lines_per_kpt = (self.n_bands * n_spin_bands / 10 +
+                         int((self.n_bands * n_spin_bands) % 10 != 0))
+        self.eig = dict()
+        for i in range(len(self.weights)):
+            tmp = lines[i * lines_per_kpt:(i + 1) * lines_per_kpt]
+            v = [float(v) for v in tmp[0].split()[1:]]
+            for l in tmp[1:]:
+                v.extend([float(t) for t in l.split()])
+            if self.spin_pol:
+                self.eig[(i, 1)] = np.array(v[0:self.n_bands])
+                self.eig[(i, -1)] = np.array(v[self.n_bands:])
+            else:
+                self.eig[(i, 1)] = np.array(v)
+        
+    def get_k_point_weights(self):
+        self.read_eig()
+        return self.weights
+
+    def get_fermi_level(self):
+        self.read_eig()
+        return self.e_fermi
+
+    def get_eigenvalues(self, kpt=0, spin=1):
+        self.read_eig()
+        return self.eig[(kpt, spin)]
+
+    def get_number_of_spins(self):
+        self.read_eig()
+        if self.spin_pol:
+            return 2
+        else:
+            return 1
+
+    def read_hs(self, filename, is_gamma_only=False, magnus=False):
+        """Read the Hamiltonian and overlap matrix from a Siesta 
+           calculation in sparse format. 
+
+        Parameters
+        ==========
+        filename: str
+            The filename should be on the form jobname.HS  
+        is_gamma_only: {False, True), optional
+            Is it a gamma point calculation?
+        magnus: bool
+            The fileformat was changed by Magnus in Siesta at some
+            point around version 2.xxx. 
+            Use mangus=False, to use the old file format.
+
+        Note
+        ====
+        Data read in is put in self._dat.
+
+        Examples
+        ========
+            >>> calc = Siesta()
+            >>> calc.read_hs('jobname.HS')
+            >>> print calc._dat.fermi_level
+            >>> print 'Number of orbitals: %i' % calc._dat.nuotot 
+        """
+        assert not magnus, 'Not implemented; changes by Magnus to file io' 
+        assert not is_gamma_only, 'Not implemented. Only works for k-points.'
+        class Dummy:
+            pass
+        self._dat = dat = Dummy()
+        # Try to read supercell and atom data from a jobname.XV file
+        filename_xv = filename[:-2] + 'XV'
+        #assert isfile(filename_xv), 'Missing jobname.XV file'
+        if isfile(filename_xv):
+            print 'Reading supercell and atom data from ' + filename_xv
+            fd = open(filename_xv, 'r')
+            dat.cell = np.zeros((3, 3)) # Supercell
+            for a_vec in dat.cell:
+                a_vec[:] = np.array(fd.readline().split()[:3], float)
+            dat.rcell = 2 * np.pi * np.linalg.inv(dat.cell.T)
+            dat.natoms = int(fd.readline().split()[0])
+            dat.symbols = []
+            dat.pos_ac = np.zeros((dat.natoms, 3))
+            for a in range(dat.natoms):
+                line = fd.readline().split()
+                dat.symbols.append(chemical_symbols[int(line[1])])
+                dat.pos_ac[a, :] = [float(line[i]) for i in range(2, 2 + 3)]
+        # Read in the jobname.HS file
+        fileobj = file(filename, 'rb')
+        fileobj.seek(0)
+        dat.fermi_level = float(open(filename[:-3] + '.EIG', 'r').readline())
+        dat.is_gammay_only = is_gamma_only 
+        dat.nuotot, dat.ns, dat.mnh = getrecord(fileobj, 'l')
+        nuotot, ns, mnh = dat.nuotot, dat.ns, dat.mnh
+        print 'Number of orbitals found: %i' % nuotot
+        dat.numh = numh = np.array([getrecord(fileobj, 'l')
+                                    for i in range(nuotot)], 'l')
+        dat.maxval = max(numh)
+        dat.listhptr = listhptr = np.zeros(nuotot, 'l')
+        listhptr[0] = 0
+        for oi in xrange(1, nuotot):
+            listhptr[oi] = listhptr[oi - 1] + numh[oi - 1]
+        dat.listh = listh = np.zeros(mnh, 'l')
+        
+        print 'Reading sparse info'
+        for oi in xrange(nuotot):
+            for mi in xrange(numh[oi]):
+                listh[listhptr[oi] + mi] = getrecord(fileobj, 'l')
+
+        dat.nuotot_sc = max(listh)
+        dat.h_sparse = h_sparse = np.zeros((mnh, ns), float)
+        dat.s_sparse = s_sparse = np.zeros(mnh, float)
+        print 'Reading H'
+        for si in xrange(ns):
+            for oi in xrange(nuotot):
+                for mi in xrange(numh[oi]):
+                    h_sparse[listhptr[oi] + mi, si] = getrecord(fileobj, 'd')
+        print 'Reading S'
+        for oi in xrange(nuotot):
+            for mi in xrange(numh[oi]):
+                s_sparse[listhptr[oi] + mi] = getrecord(fileobj, 'd')
+
+        dat.qtot, dat.temperature = getrecord(fileobj, 'd')
+        if not is_gamma_only:
+            print 'Reading X'
+            dat.xij_sparse = xij_sparse = np.zeros([3, mnh], float)
+            for oi in xrange(nuotot):
+                for mi in xrange(numh[oi]):
+                    xij_sparse[:, listhptr[oi] + mi] = getrecord(fileobj, 'd')
+        fileobj.close()
+
+    def get_hs(self, kpt=(0, 0, 0), spin=0, remove_pbc=None, kpt_scaled=True):
+        """Hamiltonian and overlap matrices for an arbitrary k-point.
+       
+        The default values corresponds to the Gamma point for 
+        spin 0 and periodic boundary conditions.
+
+        Parameters
+        ==========
+        kpt : {(0, 0, 0), (3,) array_like}, optional
+            k-point in scaled or absolute coordinates.
+            For the latter the units should be Bohr^-1.
+        spin : {0, 1}, optional
+            Spin index 
+        remove_pbc : {None, ({'x', 'y', 'z'}, basis)}, optional
+            Use remove_pbc to truncate h and s along a cartesian
+            axis. 
+        basis: {str, dict}
+            The basis specification as either a string or a dictionary.
+        kpt_scaled : {True, bool}, optional
+            Use kpt_scaled=False if `kpt` is in absolute units (Bohr^-1).
+
+        Note
+        ====
+        read_hs should be called before get_hs gets called.
+
+        Examples
+        ========
+        >>> calc = Siesta()
+        >>> calc.read_hs('jobname.HS')
+        >>> h, s = calc.get_hs((0.0, 0.375, 0.375))
+        >>> h -= s * calc._dat.fermi_level # fermi level is now at 0.0
+        >>> basis = 'szp'
+        >>> h, s = calc.get_hs((0.0, 0.375, 0.375), remove_pbc=('x', basis))
+        >>> basis = {'Au:'sz}', 'C':'dzp', None:'szp'}
+        >>> h, s = calc.get_hs((0.0, 0.375, 0.375), remove_pbc=('x', basis))
+
+        """
+        if not hasattr(self, '_dat'):# XXX Crude check if data is avail.
+            print 'Please read in data first by calling the method read_hs.'
+            return None, None
+        dot = np.dot
+        dat = self._dat            
+        kpt_c = np.array(kpt, float)
+        if kpt_scaled:
+            kpt_c = dot(kpt_c, dat.rcell)
+
+        h_MM = np.zeros((dat.nuotot, dat.nuotot), complex)
+        s_MM = np.zeros((dat.nuotot, dat.nuotot), complex)
+        h_sparse, s_sparse = dat.h_sparse, dat.s_sparse
+        x_sparse = dat.xij_sparse
+        numh, listhptr, listh = dat.numh, dat.listhptr, dat.listh
+        indxuo = np.mod(np.arange(dat.nuotot_sc), dat.nuotot)
+        
+        for iuo in xrange(dat.nuotot):
+            for j in range(numh[iuo]):
+                ind =  listhptr[iuo] + j 
+                jo = listh[ind] - 1
+                juo = indxuo[jo]
+                kx = dot(kpt_c, x_sparse[:, ind])
+                phasef = exp(1.0j * kx)
+                h_MM[iuo, juo] += phasef * h_sparse[ind, spin] 
+                s_MM[iuo, juo] += phasef * s_sparse[ind]
+        
+        if remove_pbc is not None:
+            direction, basis = remove_pbc
+            centers_ic = get_bf_centers(dat.symbols, dat.pos_ac, basis)
+            d = 'xyz'.index(direction)
+            cutoff = dat.cell[d, d] * 0.5
+            truncate_along_axis(h_MM, s_MM, direction, centers_ic, cutoff)
+        
+        h_MM *= complex(Rydberg)
+        return h_MM, s_MM
+
+
+def getrecord(fileobj, dtype):
+    """Used to read in binary files. 
+    """
+    typetosize = {'l':4, 'f':4, 'd':8}# XXX np.int, np.float32, np.float64
+    assert dtype in typetosize # XXX
+    size = typetosize[dtype]
+    record = array.array('l')
+    trunk = array.array(dtype)
+    record.fromfile(fileobj, 1)
+    nofelements = int(record[-1]) / size
+    trunk.fromfile(fileobj, nofelements)
+    record.fromfile(fileobj, 1)
+    data = np.array(trunk, dtype=dtype)
+    if len(data)==1:
+        data = data[0]
+    return data
+
+def truncate_along_axis(h, s, direction, centers_ic, cutoff):
+    """Truncate h and s such along a cartesian axis.
+
+    Parameters:
+
+    h: (N, N) ndarray
+        Hamiltonian matrix.
+    s: (N, N) ndarray
+        Overlap matrix.
+    direction: {'x', 'y', 'z'} 
+        Truncate allong a cartesian axis.
+    centers_ic: (N, 3) ndarray
+        Centers of the basis functions.
+    cutoff: float
+        The (direction-axis projected) cutoff distance.
+    """
+    dtype = h.dtype
+    ni = len(centers_ic)
+    d = 'xyz'.index(direction)
+    pos_i = centers_ic[:, d]
+    for i in range(ni):
+        dpos_i = abs(pos_i - pos_i[i])
+        mask_i = (dpos_i < cutoff).astype(dtype)
+        h[i, :] *= mask_i
+        h[:, i] *= mask_i
+        s[i, :] *= mask_i
+        s[:, i] *= mask_i
+
+def get_nao(symbol, basis):
+    """Number of basis functions. 
+
+    Parameters
+    ==========
+    symbol: str
+        The chemical symbol.
+    basis: str
+        Basis function type.
+    """
+    ls = valence_config[symbol]
+    nao = 0
+    zeta = {'s':1, 'd':2, 't':3, 'q':4}
+    nzeta = zeta[basis[0]]
+    is_pol = 'p' in basis
+    for l in ls: 
+        nao += (2 * l + 1) * nzeta
+    if is_pol:
+        l_pol = None
+        l = -1 
+        while l_pol is None:
+            l += 1
+            if not l in ls:
+                l_pol = l
+        nao += 2 * l_pol + 1
+    return nao        
+
+def get_bf_centers(symbols, positions, basis):
+    """Centers of basis functions.
+
+    Parameters
+    ==========
+    symbols: str, (N, ) array_like 
+        chemical symbol for each atom. 
+    positions: float, (N, 3) array_like
+        Positions of the atoms.
+    basis: {str,  dict}
+        Basis set specification as either a string or a dictionary
+
+    Examples
+    ========
+    >>> symbols = ['O', 'H']
+    >>> positions = [(0, 0, 0), (0, 0, 1)]
+    >>> basis = 'sz'
+    >>> print get_bf_centers(symbols, positions, basis)
+    [[0 0 0]
+     [0 0 0]
+     [0 0 0]
+     [0 0 0]
+     [0 0 1]]
+    >>> basis = {'H':'dz', None:'sz'}
+    >>> print get_bf_centers(symbols, positions, basis)
+    [[0 0 0]
+     [0 0 0]
+     [0 0 0]
+     [0 0 0]
+     [0 0 1]
+     [0 0 1]]
+
+    """
+    centers_ic = []
+    dict_basis = False
+    if type(basis)==dict:
+        dict_basis = True
+    for symbol, pos in zip(symbols, positions):
+        if dict_basis:
+            if symbol not in basis:
+                bas = basis[None]
+            else:
+                bas = basis[symbol]
+        else:
+            bas = basis
+        for i in range(get_nao(symbol, bas)):
+            centers_ic.append(pos)
+    return np.asarray(centers_ic)
+
+def fdfify(key):
+    return key.lower().replace('_', '').replace('.', '').replace('-', '')
+
+valence_config = {
+    'H': (0,),
+    'C': (0, 1),
+    'N': (0, 1),
+    'O': (0, 1),
+    'S': (0, 1), 
+    'Li': (0,),
+    'Na': (0,),
+    'Ni': (0, 2),
+    'Cu': (0, 2), 
+    'Pd': (0, 2), 
+    'Ag': (0, 2), 
+    'Pt': (0, 2), 
+    'Au': (0, 2)}
+
+keys_with_units = {
+    'paoenergyshift': 'eV',
+    'zmunitslength': 'Bohr',
+    'zmunitsangle': 'rad',
+    'zmforcetollength': 'eV/Ang',
+    'zmforcetolangle': 'eV/rad',
+    'zmmaxdispllength': 'Ang',
+    'zmmaxdisplangle': 'rad',
+    'meshcutoff': 'eV',
+    'dmenergytolerance': 'eV',
+    'electronictemperature': 'eV',
+    'oneta': 'eV',
+    'onetaalpha': 'eV',
+    'onetabeta': 'eV',
+    'onrclwf': 'Ang',
+    'onchemicalpotentialrc': 'Ang',
+    'onchemicalpotentialtemperature': 'eV',
+    'mdmaxcgdispl': 'Ang',
+    'mdmaxforcetol': 'eV/Ang',
+    'mdmaxstresstol': 'eV/Ang**3',
+    'mdlengthtimestep': 'fs',
+    'mdinitialtemperature': 'eV',
+    'mdtargettemperature': 'eV',
+    'mdtargetpressure': 'eV/Ang**3',
+    'mdnosemass': 'eV*fs**2',
+    'mdparrinellorahmanmass': 'eV*fs**2',
+    'mdtaurelax': 'fs',
+    'mdbulkmodulus': 'eV/Ang**3',
+    'mdfcdispl': 'Ang',
+    'warningminimumatomicdistance': 'Ang',
+    'rcspatial': 'Ang',
+    'kgridcutoff': 'Ang',
+    'latticeconstant': 'Ang'}
Index: ase/calculators/aims.py
===================================================================
--- ase/calculators/aims.py	(revision 0)
+++ ase/calculators/aims.py	(revision 1)
@@ -0,0 +1,471 @@
+"""This module defines an ASE interface to FHI-aims.
+
+Felix Hanke hanke@liverpool.ac.uk
+Jonas Bjork j.bjork@liverpool.ac.uk
+"""
+
+from general import Calculator
+import os
+import sys
+from os.path import join, isfile, islink
+
+import numpy as np
+
+import ase
+
+float_keys = [
+    'charge',
+    'charge_mix_param',
+    'hartree_convergence_parameter',
+    'ini_linear_mix_param',
+    'ini_spin_mix_parma',
+    'initial_moment',
+    'MD_MB_init',
+    'MD_time_step',
+    'prec_mix_param',
+    'set_vacuum_level',
+    'spin_mix_param',
+]
+
+exp_keys = [
+    'sc_accuracy_eev',
+    'sc_accuracy_etot',
+    'sc_accuracy_forces',
+    'sc_accuracy_rho',
+]
+
+string_keys = [
+    'communication_type',
+    'KS_method',
+    'mixer',
+    'output_level',
+    'packed_matrix_format',
+    'restart',
+    'restart_read_only',
+    'restart_write_only',
+    'spin',
+    'total_energy_method',
+    'qpe_calc',
+    'xc',
+]
+
+int_keys = [
+    'empty_states',
+    'ini_linear_mixing',
+    'max_relaxation_steps',    
+    'n_max_pulay',   
+    'sc_iter_limit',
+    'walltime',
+]
+
+bool_keys = [
+    'collect_eigenvectors',
+    'compute_forces',
+    'compute_kinetic',
+    'distributed_spline_storage',
+    'evaluate_work_function',
+    'final_forces_cleaned',
+    'hessian_to_restart_geometry',
+    'MD_clean_rotations',
+    'restart_relaxations',
+    'squeeze_memory',
+    'use_density_matrix',
+    'use_dipole_correction',
+    'use_local_index',
+    'vdw_correction_hirshfeld',
+]
+
+list_keys = [
+    'k_grid',
+    'k_offset',
+    'MD_run',
+    'MD_schedule',
+    'MD_segment',
+    'mixer_threshold',
+    'occupation_type',
+    'output',
+    'preconditioner',
+    'relativistic',
+    'relax_geometry',
+]
+
+input_keys = [
+    'run_command',
+    'run_dir',
+    'species_dir',
+    'cubes',
+    'output_template',
+    'track_output',
+] 
+
+input_parameters_default = {'run_command':None,
+                            'run_dir':None,
+                            'species_dir':None,
+                            'cubes':None,
+                            'output_template':'aims',
+                            'track_output':False}
+
+class Aims(Calculator):
+    def __init__(self, **kwargs):
+        self.name = 'Aims'
+        self.float_params = {}
+        self.exp_params = {}
+        self.string_params = {}
+        self.int_params = {}
+        self.bool_params = {}
+        self.list_params = {}
+        self.input_parameters = {}
+        for key in float_keys:
+            self.float_params[key] = None
+        for key in exp_keys:
+            self.exp_params[key] = None
+        for key in string_keys:
+            self.string_params[key] = None
+        for key in int_keys:
+            self.int_params[key] = None
+        for key in bool_keys:
+            self.bool_params[key] = None
+        for key in list_keys:
+            self.list_params[key] = None
+        for key in input_keys:
+            self.input_parameters[key] = input_parameters_default[key]
+        if os.environ.has_key('AIMS_SPECIES_DIR'):
+            self.input_parameters['species_dir'] = os.environ['AIMS_SPECIES_DIR']
+        if os.environ.has_key('AIMS_COMMAND'):
+            self.input_parameters['run_command'] = os.environ['AIMS_COMMAND']
+
+        self.positions = None
+        self.atoms = None
+        self.run_counts = 0
+        self.set(**kwargs)
+
+    def set(self, **kwargs):
+        if 'control' in kwargs:
+            fname = kwargs['control']
+            from ase.io.aims import read_aims_calculator
+            file = open(fname, 'r')
+            calc_temp = None
+            while True:
+                line = file.readline()
+                if "List of parameters used to initialize the calculator:" in line:
+                   file.readline()
+                   calc_temp = read_aims_calculator(file)
+                   break
+            if calc_temp is not None:
+                self.float_params      = calc_temp.float_params
+                self.exp_params        = calc_temp.exp_params
+                self.string_params     = calc_temp.string_params
+                self.int_params        = calc_temp.int_params
+                self.bool_params       = calc_temp.bool_params
+                self.list_params       = calc_temp.list_params
+                self.input_parameters  = calc_temp.input_parameters
+            else:
+                raise TypeError("Control file to be imported can not be read by ASE = " + fname)
+        for key in kwargs:
+            if self.float_params.has_key(key):
+                self.float_params[key] = kwargs[key]
+            elif self.exp_params.has_key(key):
+                self.exp_params[key] = kwargs[key]
+            elif self.string_params.has_key(key):
+                self.string_params[key] = kwargs[key]
+            elif self.int_params.has_key(key):
+                self.int_params[key] = kwargs[key]
+            elif self.bool_params.has_key(key):
+                self.bool_params[key] = kwargs[key]
+            elif self.list_params.has_key(key):
+                self.list_params[key] = kwargs[key]
+            elif self.input_parameters.has_key(key):
+                self.input_parameters[key] = kwargs[key]
+            elif key is not 'control':
+                raise TypeError('Parameter not defined: ' + key)
+
+    def update(self, atoms):
+        if self.calculation_required(atoms,[]):
+            self.calculate(atoms)
+
+    def calculation_required(self, atoms,quantities):
+        if (self.positions is None or
+            (self.atoms != atoms) or
+            (self.atoms != self.old_atoms) or 
+            (self.float_params != self.old_float_params) or
+            (self.exp_params != self.old_exp_params) or
+            (self.string_params != self.old_string_params) or
+            (self.int_params != self.old_int_params) or
+            (self.bool_params != self.old_bool_params) or
+            (self.list_params != self.old_list_params) or
+            (self.input_parameters != self.old_input_parameters)):
+            return True
+        else:
+            return False
+
+    def calculate(self, atoms):
+        """Generate necessary files in the working directory.
+        
+        If the directory does not exist it will be created.
+
+        """
+        positions = atoms.get_positions()
+        have_lattice_vectors = atoms.get_pbc().any()        
+        have_k_grid = self.list_params['k_grid']
+        if have_lattice_vectors and not have_k_grid:
+            raise RuntimeError("Found lattice vectors but no k-grid!")
+        if not have_lattice_vectors and have_k_grid:
+            raise RuntimeError("Found k-grid but no lattice vectors!")
+        from ase.io.aims import write_aims
+        write_aims('geometry.in', atoms) 
+        self.write_control()
+        self.write_species()
+        self.run()
+        self.converged = self.read_convergence()
+        if not self.converged:
+            os.system("tail -20 "+self.out)
+            raise RuntimeError("FHI-aims did not converge!\n"+
+                               "The last lines of output are printed above "+
+                               "and should give an indication why.")
+
+        self.set_results(atoms)
+
+    def set_results(self,atoms):
+        self.read(atoms)
+        self.old_float_params = self.float_params.copy()
+        self.old_exp_params = self.exp_params.copy()
+        self.old_string_params = self.string_params.copy()
+        self.old_int_params = self.int_params.copy()
+        self.old_input_parameters = self.input_parameters.copy()
+        self.old_bool_params = self.bool_params.copy()
+        self.old_list_params = self.list_params.copy()
+        self.old_atoms = self.atoms.copy()
+
+    def run(self):
+        if self.input_parameters['track_output']:
+            self.out = self.input_parameters['output_template']+str(self.run_counts)+'.out'
+            self.run_counts += 1
+        else:
+            self.out = self.input_parameters['output_template']+'.out'            
+        if self.input_parameters['run_command']:
+            aims_command = self.input_parameters['run_command'] 
+        elif os.environ.has_key('AIMS_COMMAND'):
+            aims_command = os.environ['AIMS_COMMAND']
+        else:
+            raise RuntimeError("No specification for running FHI-aims. Aborting!")
+        aims_command = aims_command + ' >> ' 
+        if self.input_parameters['run_dir']:
+            aims_command = aims_command + self.input_parameters['run_dir'] + '/'
+        aims_command = aims_command + self.out
+        self.write_parameters('#',self.out)
+        exitcode = os.system(aims_command)
+        if exitcode != 0:
+            raise RuntimeError('FHI-aims exited with exit code: %d.  ' % exitcode)
+        if self.input_parameters['cubes'] and self.input_parameters['track_output']:
+            self.input_parameters['cubes'].move_to_base_name(self.input_parameters['output_template']+str(self.run_counts-1))
+
+    def write_parameters(self,prefix,filename):
+        output = open(filename,'w')
+        output.write(prefix+'=======================================================\n')
+        output.write(prefix+'FHI-aims file: '+filename+'\n')
+        output.write(prefix+'Created using the Atomic Simulation Environment (ASE)\n'+prefix+'\n')
+        output.write(prefix+'List of parameters used to initialize the calculator:\n')
+        output.write(prefix+'=======================================================\n')
+        for key, val in self.float_params.items():
+            if val is not None:
+                output.write('%-35s%5.6f\n' % (key, val))        
+        for key, val in self.exp_params.items():
+            if val is not None:
+                output.write('%-35s%5.2e\n' % (key, val))
+        for key, val in self.string_params.items():
+            if val is not None:
+                output.write('%-35s%s\n' % (key, val))
+        for key, val in self.int_params.items():
+            if val is not None:
+                output.write('%-35s%d\n' % (key, val))
+        for key, val in self.bool_params.items():
+            if val is not None:
+                if key == 'vdw_correction_hirshfeld' and val:
+                    output.write('%-35s\n' % (key))
+                elif val:
+                    output.write('%-35s.true.\n' % (key))
+                elif key != 'vdw_correction_hirshfeld':
+                    output.write('%-35s.false.\n' % (key))
+        for key, val in self.list_params.items():
+            if val is not None:
+                if key == 'output':
+                    if not isinstance(val,(list,tuple)): 
+                        val = [val]
+                    for output_type in val:
+                        output.write('%-35s%s\n' % (key,str(output_type)))
+                else:
+                    output.write('%-35s' % (key))
+                    if isinstance(val,str): 
+                        output.write(val)
+                    else:
+                        for sub_value in val:
+                            output.write(str(sub_value)+' ')
+                    output.write('\n')
+        for key, val in self.input_parameters.items():
+            if key is  'cubes':
+                if val:
+                    val.write(output)
+            elif val and val != input_parameters_default[key]:
+                output.write(prefix+'%-34s%s\n' % (key,val))
+        output.write(prefix+'=======================================================\n\n')
+        output.close()
+
+    def write_control(self, file = 'control.in'):
+        """Writes the control.in file."""
+        self.write_parameters('#',file)
+
+    def write_species(self, file = 'control.in'):
+        from ase.data import atomic_numbers
+
+        if not self.input_parameters['species_dir']:
+            raise RuntimeError('Missing species directory, THIS MUST BE SPECIFIED!')
+
+        control = open(file, 'a')
+        species_path = self.input_parameters['species_dir']
+        symbols = self.atoms.get_chemical_symbols()
+        symbols2 = []
+        for n, symbol in enumerate(symbols):
+            if symbol not in symbols2:
+                symbols2.append(symbol)
+        for symbol in symbols2:
+            fd = join(species_path, '%02i_%s_default' % (atomic_numbers[symbol], symbol))
+            for line in open(fd, 'r'):
+                control.write(line)
+        control.close()
+
+    def get_dipole_moment(self, atoms):
+        if self.list_params['output'] is None or 'dipole' not in self.list_params['output']:
+            raise RuntimeError('output=[\'dipole\'] has to be set.')
+        elif atoms.get_pbc().any():
+            raise RuntimeError('FHI-aims does not allow this for systems with periodic boundary conditions.')
+        self.update(atoms)
+        return self.dipole
+
+    def read_dipole(self):
+        """Method that reads the electric dipole moment from the output file."""
+
+        dipolemoment=np.zeros([1,3])
+        for line in open(self.out, 'r'):
+            if line.rfind('Total dipole moment [eAng]') > -1:
+                dipolemoment=np.array([float(f) for f in line.split()[6:10]])
+        return dipolemoment
+
+    def read_energy(self, all=None):
+        for line in open(self.out, 'r'):
+            if line.rfind('Total energy corrected') > -1:
+                E0 = float(line.split()[-2])
+            elif line.rfind('Total energy uncorrected') > -1:
+                F = float(line.split()[-2])
+        energy_free, energy_zero = F, E0
+        return [energy_free, energy_zero]
+
+    def read_forces(self, atoms, all=False):
+        """Method that reads forces from the output file.
+
+        If 'all' is switched on, the forces for all ionic steps
+        in the output file will be returned, in other case only the
+        forces for the last ionic configuration are returned."""
+        lines = open(self.out, 'r').readlines()
+        forces = np.zeros([len(atoms), 3])
+        for n, line in enumerate(lines):
+            if line.rfind('Total atomic forces') > -1:
+                for iatom in range(len(atoms)):
+                    data = lines[n+iatom+1].split()
+                    for iforce in range(3):
+                        forces[iatom, iforce] = float(data[2+iforce])
+        return forces
+
+    def get_stress(self, atoms):
+        raise NotImplementedError('Stresses are not currently available in FHI-aims, sorry. ')
+
+# methods that should be quickly implemented some time, haven't had time yet:
+    def read_fermi(self):
+        """Method that reads Fermi energy from output file"""
+        return
+
+    def read_magnetic_moment(self):
+        return
+
+    def read_convergence(self):
+        converged = False
+        lines = open(self.out, 'r').readlines()
+        for n, line in enumerate(lines):
+            if line.rfind('Have a nice day') > -1:
+                converged = True
+        return converged
+
+    def read_eigenvalues(self, kpt=0, spin=0):
+        return 
+
+class AimsCube:
+    """ object to ensure the output of cube files, can be attached to Aims object"""
+    def __init__(self,origin=(0,0,0),
+                 edges=[(0.1,0.0,0.0),(0.0,0.1,0.0),(0.0,0.0,0.1)],
+                 points=(50,50,50),plots=None):
+        """ parameters: 
+        origin, edges, points = same as in the FHI-aims output
+        plots: what to print, same names as in FHI-aims """
+
+        self.name   = 'AimsCube'
+        self.origin = origin
+        self.edges  = edges
+        self.points = points
+        self.plots  = plots
+         
+    def ncubes(self):
+        """returns the number of cube files to output """
+        if self.plots:
+            number = len(self.plots)
+        else:
+            number = 0
+        return number
+
+    def set(self,**kwargs):
+        """ set any of the parameters ... """
+        # NOT IMPLEMENTED AT THE MOMENT!
+
+    def move_to_base_name(self,basename):
+        """ when output tracking is on or the base namem is not standard,
+        this routine will rename add the base to the cube file output for 
+        easier tracking """
+        for plot in self.plots:
+            found = False
+            cube = plot.split()
+            if cube[0] == 'total_density' or cube[0] == 'spin_density' or cube[0] == 'delta_density':
+                found = True
+                old_name = cube[0]+'.cube'
+                new_name = basename+'.'+old_name
+            if cube[0] == 'eigenstate' or cube[0] == 'eigenstate_density':
+                found = True
+                state = int(cube[1])
+                s_state = cube[1]
+                for i in [10,100,1000,10000]:
+                    if state < i:
+                        s_state = '0'+s_state
+                old_name = cube[0]+'_'+s_state+'_spin_1.cube'
+                new_name = basename+'.'+old_name
+            if found:
+                os.system("mv "+old_name+" "+new_name)
+
+    def add_plot(self,name):
+        """ in case you forgot one ... """
+        plots += [name]
+
+    def write(self,file):
+        """ write the necessary output to the already opened control.in """
+        file.write('output cube '+self.plots[0]+'\n')
+        file.write('   cube origin ')
+        for ival in self.origin:
+            file.write(str(ival)+' ')
+        file.write('\n')
+        for i in range(3):
+            file.write('   cube edge '+str(self.points[i])+' ')
+            for ival in self.edges[i]:
+                file.write(str(ival)+' ')
+            file.write('\n')
+        if self.ncubes() > 1:
+            for i in range(self.ncubes()-1):
+                file.write('output cube '+self.plots[i+1]+'\n')
+
+                    
+                
Index: ase/calculators/neighborlist.py
===================================================================
--- ase/calculators/neighborlist.py	(revision 0)
+++ ase/calculators/neighborlist.py	(revision 1)
@@ -0,0 +1,138 @@
+from math import sqrt
+
+import numpy as np
+
+
+class NeighborList:
+    """Neighbor list object.
+
+    cutoffs: list of float
+        List of cutoff radii - one for each atom.
+    skin: float
+        If no atom has moved more than the skin-distance since the
+        last call to the ``update()`` method, then the neighbor list
+        can be reused.  This will save some expensive rebuilds of
+        the list, but extra neighbors outside the cutoff will be
+        returned.
+    self_interaction: bool
+        Should an atom return itself as a neighbor?
+
+    Example::
+
+      nl = NeighborList([2.3, 1.7])
+      nl.update(atoms)
+      indices, offsets = nl.get_neighbors(0)
+      
+    """
+    
+    def __init__(self, cutoffs, skin=0.3, sorted=False, self_interaction=True):
+        self.cutoffs = np.asarray(cutoffs) + skin
+        self.skin = skin
+        self.sorted = sorted
+        self.self_interaction = self_interaction
+        self.nupdates = 0
+
+    def update(self, atoms):
+        """Make sure the list is up to date."""
+        if self.nupdates == 0:
+            self.build(atoms)
+            return True
+        
+        if ((self.pbc != atoms.get_pbc()).any() or
+            (self.cell != atoms.get_cell()).any() or
+            ((self.positions - atoms.get_positions())**2).sum(1).max() >
+            self.skin**2):
+            self.build(atoms)
+            return True
+        
+        return False
+    
+    def build(self, atoms):
+        """Build the list."""
+        self.positions = atoms.get_positions()
+        self.pbc = atoms.get_pbc()
+        self.cell = atoms.get_cell()
+        rcmax = self.cutoffs.max()
+        
+        icell = np.linalg.inv(self.cell)
+        scaled = np.dot(self.positions, icell)
+        scaled0 = scaled.copy()
+
+        N = []
+        for i in range(3):
+            if self.pbc[i]:
+                scaled0[:, i] %= 1.0
+                v = icell[:, i]
+                h = 1 / sqrt(np.dot(v, v))
+                n =  int(2 * rcmax / h) + 1
+            else:
+                n = 0
+            N.append(n)
+            
+        offsets = np.empty((len(atoms), 3), int)
+        (scaled0 - scaled).round(out=offsets)
+        positions0 = np.dot(scaled0, self.cell)
+        natoms = len(atoms)
+        indices = np.arange(natoms)
+
+        self.nneighbors = 0
+        self.npbcneighbors = 0
+        self.neighbors = [np.empty(0, int) for a in range(natoms)]
+        self.displacements = [np.empty((0, 3), int) for a in range(natoms)]
+        for n1 in range(0, N[0] + 1):
+            for n2 in range(-N[1], N[1] + 1):
+                for n3 in range(-N[2], N[2] + 1):
+                    if n1 == 0 and (n2 < 0 or n2 == 0 and n3 < 0):
+                        continue
+                    displacement = np.dot((n1, n2, n3), self.cell)
+                    for a in range(natoms):
+                        d = positions0 + displacement - positions0[a]
+                        i = indices[(d**2).sum(1) <
+                                    (self.cutoffs + self.cutoffs[a])**2]
+                        if n1 == 0 and n2 == 0 and n3 == 0:
+                            if self.self_interaction:
+                                i = i[i >= a]
+                            else:
+                                i = i[i > a]
+                        self.nneighbors += len(i)
+                        self.neighbors[a] = np.concatenate(
+                            (self.neighbors[a], i))
+                        disp = np.empty((len(i), 3), int)
+                        disp[:] = (n1, n2, n3)
+                        disp += offsets[i] - offsets[a]
+                        self.npbcneighbors += disp.any(1).sum()
+                        self.displacements[a] = np.concatenate(
+                            (self.displacements[a], disp))
+
+        if self.sorted:
+            for a, i in enumerate(self.neighbors):
+                mask = (i < a)
+                if mask.any():
+                    j = i[mask]
+                    offsets = self.displacements[a][mask]
+                    for b, offset in zip(j, offsets):
+                        self.neighbors[b] = np.concatenate(
+                            (self.neighbors[b], [a]))
+                        self.displacements[b] = np.concatenate(
+                                (self.displacements[b], [-offset]))
+                    mask = np.logical_not(mask)
+                    self.neighbors[a] = self.neighbors[a][mask]
+                    self.displacements[a] = self.displacements[a][mask]
+                
+        self.nupdates += 1
+
+    def get_neighbors(self, a):
+        """Return neighbors of atom number a.
+
+        A list of indices and offsets to neighboring atoms is
+        returned.  The positions of the neighbor atoms can be
+        calculated like this::
+
+          indices, offsets = nl.get_neighbors(42)
+          for i, offset in zip(indices, offsets):
+              print atoms.positions[i] + dot(offset, atoms.get_cell())
+
+        Notice that if get_neighbors(a) gives atom b as a neighbor,
+        then get_neighbors(b) will not return a as a neighbor!"""
+        
+        return self.neighbors[a], self.displacements[a]
Index: ase/calculators/singlepoint.py
===================================================================
--- ase/calculators/singlepoint.py	(revision 0)
+++ ase/calculators/singlepoint.py	(revision 1)
@@ -0,0 +1,63 @@
+import numpy as np
+
+
+class SinglePointCalculator:
+    """Special calculator for a single configuration.
+
+    Used to remember the energy, force and stress for a given
+    configuration.  If the positions, atomic numbers, unit cell, or
+    boundary conditions are changed, then asking for
+    energy/forces/stress will raise an exception."""
+    
+    def __init__(self, energy, forces, stress, magmoms, atoms):
+        """Save energy, forces and stresses for the current configuration."""
+        self.energy = energy
+        if forces is not None:
+            forces = np.array(forces, float)
+        self.forces = forces
+        if stress is not None:
+            stress = np.array(stress, float)
+        self.stress = stress
+        if magmoms is not None:
+            magmoms = np.array(magmoms, float)
+        self.magmoms = magmoms
+        self.atoms = atoms.copy()
+
+    def calculation_required(self, atoms, quantities):
+        ok = self.atoms == atoms
+        return ('forces' in quantities and (self.forces is None or not ok) or
+                'energy' in quantities and (self.energy is None or not ok) or
+                'stress' in quantities and (self.stress is None or not ok) or
+                'magmoms' in quantities and (self.magmoms is None or not ok))
+
+    def update(self, atoms):
+        if self.atoms != atoms:
+            raise RuntimeError('Energy, forces and stress no longer correct.')
+
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        if self.energy is None:
+            raise RuntimeError('No energy.')
+        return self.energy
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        if self.forces is None:
+            raise RuntimeError('No forces.')
+        return self.forces
+
+    def get_stress(self, atoms):
+        self.update(atoms)
+        if self.stress is None:
+            raise NotImplementedError
+        return self.stress
+
+    def get_spin_polarized(self):
+        return self.magmoms is not None and self.magmoms.any()
+
+    def get_magnetic_moments(self, atoms):
+        self.update(atoms)
+        if self.magmoms is not None:
+            return self.magmoms
+        else:
+            return np.zeros(len(self.positions))
Index: ase/calculators/tip3p.py
===================================================================
--- ase/calculators/tip3p.py	(revision 0)
+++ ase/calculators/tip3p.py	(revision 1)
@@ -0,0 +1,181 @@
+"""TIP3P potential, constraints and dynamics."""
+from math import pi, sin, cos
+
+import numpy as np
+
+import ase.units as units
+from ase.parallel import world
+from ase.md.md import MolecularDynamics
+
+qH = 0.417
+sigma0 = 3.15061
+epsilon0 = 0.1521 * units.kcal / units.mol
+rOH = 0.9572
+thetaHOH = 104.52 / 180 * pi
+
+
+class TIP3P:
+    def __init__(self, rc=9.0, width=1.0):
+        self.energy = None
+        self.forces = None
+
+        self.rc1 = rc - width
+        self.rc2 = rc
+        
+    def get_spin_polarized(self):
+        return False
+    
+    def update(self, atoms):
+        if (self.energy is None or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def calculation_required(self, atoms, quantities):
+        if len(quantities) == 0:
+            return False
+
+        return (self.energy is None or
+                len(self.numbers) != len(atoms) or
+                (self.numbers != atoms.get_atomic_numbers()).any() or
+                (self.positions != atoms.get_positions()).any() or
+                (self.pbc != atoms.get_pbc()).any() or
+                (self.cell != atoms.get_cell()).any())
+                
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.energy
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+    
+    def get_stress(self, atoms):
+        raise NotImplementedError
+    
+    def calculate(self, atoms):
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+        natoms = len(atoms)
+        nH2O = natoms // 3
+
+        assert self.pbc.all()
+        C = self.cell.diagonal()
+        assert not (self.cell - np.diag(C)).any()
+        assert (C >= 2 * self.rc2).all()
+        self.numbers = atoms.get_atomic_numbers()
+        Z = self.numbers.reshape((-1, 3))
+        assert (Z[:, 1:] == 1).all() and (Z[:, 0] == 8).all()
+
+        R = self.positions.reshape((nH2O, 3, 3))
+        RO = R[:, 0]
+        
+        self.energy = 0.0
+        self.forces = np.zeros((natoms, 3))
+        
+        if world is None:
+            mya = range(nH2O - 1)
+        else:
+            rank = world.rank
+            size = world.size
+            assert nH2O // (2 * size) == 0
+            mynH2O = nH2O // 2 // size
+            mya = (range(rank * n, (rank + 1) * n) +
+                   range((size - rank - 1) * n, (size - rank) * n))
+
+        q = np.empty(3)
+        q[:] = qH * (units.Hartree * units.Bohr)**0.5
+        q[0] *= -2
+        
+        for a in mya:
+            DOO = (RO[a + 1:] - RO[a] + 0.5 * C) % C - 0.5 * C
+            dOO = (DOO**2).sum(axis=1)**0.5
+            x1 = dOO > self.rc1
+            x2 = dOO < self.rc2
+            f = np.zeros(nH2O - a - 1)
+            f[x2] = 1.0
+            dfdd = np.zeros(nH2O - a - 1)
+            x12 = np.logical_and(x1, x2)
+            d = (dOO[x12] - self.rc1) / (self.rc2 - self.rc1)
+            f[x12] -= d**2 * (3.0 - 2.0 * d)
+            dfdd[x12] -= 6.0 / (self.rc2 - self.rc1) * d * (1.0 - d)
+
+            y = (sigma0 / dOO)**6
+            y2 = y**2
+            e = 4 * epsilon0 * (y2 - y)
+            self.energy += np.dot(e, f)
+            dedd = 24 * epsilon0 * (2 * y2 - y) / dOO * f - e * dfdd
+            F = (dedd / dOO)[:, np.newaxis] * DOO
+            self.forces[(a + 1) * 3::3] += F
+            self.forces[a * 3] -= F.sum(axis=0)
+
+            for i in range(3):
+                D = (R[a + 1:] - R[a, i] + 0.5 * C) % C - 0.5 * C
+                d = (D**2).sum(axis=2)**0.5
+                e = q[i] * q / d
+                self.energy += np.dot(f, e).sum()
+                F = (e / d**2 * f[:, np.newaxis])[:, :, np.newaxis] * D
+                F[:, 0] -= (e.sum(axis=1) * dfdd / dOO)[:, np.newaxis] * DOO 
+                self.forces[(a + 1) * 3:] += F.reshape((-1, 3))
+                self.forces[a * 3 + i] -= F.sum(axis=0).sum(axis=0)
+
+        if world is not None:
+            self.energy = world.sum(self.energy)
+            world.sum(self.forces)
+
+
+class H2OConstraint:
+    """Constraint object for a rigid H2O molecule."""
+    def __init__(self, r=rOH, theta=thetaHOH, iterations=23, masses=None):
+        self.r = r
+        self.theta = theta
+        self.iterations = iterations
+        self.m = masses
+
+    def set_masses(self, masses):
+        self.m = masses
+
+    def adjust_positions(self, old, new):
+        bonds = [(0, 1, self.r), (0, 2, self.r)]
+        if self.theta:
+            bonds.append((1, 2, sin(self.theta / 2) * self.r * 2))
+        for iter in range(self.iterations):
+            for i, j, r in bonds:
+                D = old[i::3] - old[j::3]
+                m1 = self.m[i]
+                m2 = self.m[j]
+                a = new[i::3]
+                b = new[j::3]
+                B = a - b
+                x = (D**2).sum(axis=1)
+                y = (D * B).sum(axis=1)
+                z = (B**2).sum(axis=1) - r**2
+                k = m1 * m2 / (m1 + m2) * ((y**2 - x * z)**0.5 - y) / x
+                k.shape = (-1, 1)
+                a += k / m1 * D
+                b -= k / m2 * D
+
+    def adjust_forces(self, positions, forces):
+        pass
+        
+    def copy(self):
+        return H2OConstraint(self.r, self.theta, self.iterations, self.m)
+
+
+class Verlet(MolecularDynamics):
+    def step(self, f):
+        atoms = self.atoms
+        m = atoms.get_masses()[:, np.newaxis]
+        v = self.atoms.get_velocities()
+        r0 = atoms.get_positions()
+        r = r0 + self.dt * v + self.dt**2 * f / m
+        atoms.set_positions(r)
+        r = atoms.get_positions()
+        v = (r - r0) / self.dt
+        self.atoms.set_velocities(v)
+        return atoms.get_forces()
Index: ase/calculators/__init__.py
===================================================================
--- ase/calculators/__init__.py	(revision 0)
+++ ase/calculators/__init__.py	(revision 1)
@@ -0,0 +1,26 @@
+"""Interfaces to different ASE compatible force-calculators."""
+
+import numpy as np
+
+from ase import _deprecate_things_from_ase_module
+from ase.calculators.lj import LennardJones
+from ase.calculators.emt import EMT
+from ase.calculators.siesta import Siesta
+from ase.calculators.dacapo import Dacapo
+from ase.calculators.vasp import Vasp
+from ase.calculators.aims import Aims, AimsCube
+from ase.calculators.turbomole import Turbomole
+from ase.calculators.exciting import Exciting
+from ase.calculators.dftb import Dftb
+from ase.calculators.singlepoint import SinglePointCalculator
+from ase.calculators.test import numeric_force, numeric_forces, TestPotential
+
+if _deprecate_things_from_ase_module:
+    from ase.utils.deprecate import Deprecate
+    _locals = locals()
+    for name in ['LennardJones', 'EMT', 'Siesta', 'Dacapo', 'Vasp',
+                 'Aims', 'AimsCube', 'Turbomole', 'Exciting', 'Dftb',
+                 'SinglePointCalculator', 'numeric_force', 'numeric_forces',
+                 'TestPotential']:
+        obj = _locals[name]
+        _locals[name] = Deprecate(obj, name, obj.__module__, 'ase.calculators')
Index: ase/calculators/interface.py
===================================================================
--- ase/calculators/interface.py	(revision 0)
+++ ase/calculators/interface.py	(revision 1)
@@ -0,0 +1,147 @@
+import numpy as np
+
+
+class Calculator:
+    """Class for demonstrating the ASE-calculator interface.
+
+    A good implementation of a calculator should store a copy of the
+    atoms object used for the last calculation.  When one of the
+    *get_potential_energy*, *get_forces*, or *get_stress* methods is
+    called, the calculator should check if anything has changed since
+    the last calculation and only do the calculation if it's really
+    needed.  The Atoms class implements the methods *__eq__* and
+    *__ne__* that can be used for checking identity (using *==* and
+    *!=*): Two sets of atoms are considered identical if they have the
+    same positions, atomic numbers, unit cell and periodic boundary
+    conditions."""
+
+    def get_potential_energy(self, atoms=None, force_consistent=False):
+        """Return total energy.
+        
+        Both the energy extrapolated to zero Kelvin and the energy
+        consistent with the forces (the free energy) can be
+        returned."""
+        return 0.0
+        
+    def get_forces(self, atoms):
+        """Return the forces."""
+        return np.zeros((len(atoms), 3))
+                        
+    def get_stress(self, atoms):
+        """Return the stress."""
+        return np.zeros((3, 3))
+
+    def calculation_required(self, atoms, quantities):
+        """Check if a calculation is required.
+
+        Check if the quantities in the *quantities* list have already
+        been calculated for the atomic configuration *atoms*.  The
+        quantities can be one or more of: 'energy', 'forces', 'stress',
+        and 'magmoms'."""
+        return False
+
+    def set_atoms(self, atoms):
+        """Let the calculator know the atoms.
+
+        This method is optional.  If it exists, it will be called when
+        the *Atoms.set_calculator()* method is called.
+
+        *Don't* store a reference to *atoms* - that will create a
+         cyclic reference loop!  Store a copy instead."""
+        self.atoms = atoms.copy()
+
+
+class DFTCalculator(Calculator):
+    """Class for demonstrating the ASE interface to DFT-calculators."""
+    
+    def get_number_of_bands(self):
+        """Return the number of bands."""
+        return 42
+  
+    def get_xc_functional(self):
+        """Return the XC-functional identifier.
+        
+        'LDA', 'PBE', ..."""
+        return 'LDA'
+ 
+    def get_bz_k_points(self):
+        """Return all the k-points in the 1. Brillouin zone.
+
+        The coordinates are relative to reciprocal latice vectors."""
+        return np.zeros((1, 3))
+ 
+    def get_number_of_spins(self):
+        """Return the number of spins in the calculation.
+
+        Spin-paired calculations: 1, spin-polarized calculation: 2."""
+        return 1
+
+    def get_spin_polarized(self):
+        """Is it a spin-polarized calculation?"""
+        return False
+    
+    def get_ibz_k_points(self):
+        """Return k-points in the irreducible part of the Brillouin zone.
+
+        The coordinates are relative to reciprocal latice vectors."""
+        return np.zeros((1, 3))
+
+    def get_k_point_weights(self):
+        """Weights of the k-points. 
+        
+        The sum of all weights is one."""
+        return np.ones(1)
+
+    def get_pseudo_density(self, spin=None, pad=True):
+        """Return pseudo-density array.
+        
+        If *spin* is not given, then the total density is returned.
+        Otherwise, the spin up or down density is returned (spin=0 or
+        1)."""
+        return np.zeros((40, 40, 40))
+
+    def get_effective_potential(self, spin=0, pad=True):
+        """Return pseudo-effective-potential array."""
+        return np.zeros((40, 40, 40))
+
+    def get_pseudo_wave_function(self, band=0, kpt=0, spin=0, broadcast=True,
+                                 pad=True):
+        """Return pseudo-wave-function array."""
+        return np.zeros((40, 40, 40))
+
+    def get_eigenvalues(self, kpt=0, spin=0):
+        """Return eigenvalue array."""
+        return np.arange(42, float)
+    
+    def get_occupation_numbers(self, kpt=0, spin=0):
+        """Return occupation number array."""
+        return np.ones(42)
+        
+    def get_fermi_level(self):
+        """Return the Fermi level."""
+        return 0.0
+
+    def initial_wannier(self, initialwannier, kpointgrid, fixedstates,
+                        edf, spin):
+        """Initial guess for the shape of wannier functions.
+
+        Use initial guess for wannier orbitals to determine rotation
+        matrices U and C.
+        """
+        raise NotImplementedError
+
+        return c, U
+
+    def get_wannier_localization_matrix(self, nbands, dirG, kpoint,
+                                        nextkpoint, G_I, spin):
+        """Calculate integrals for maximally localized Wannier functions."""
+
+    def get_magnetic_moment(self, atoms=None):
+        """Return the total magnetic moment."""
+        return self.occupation.magmom
+
+    def get_number_of_grid_points(self):
+        """Return the shape of arrays."""
+        return self.gd.N_c
+
+
Index: ase/calculators/lj.py
===================================================================
--- ase/calculators/lj.py	(revision 0)
+++ ase/calculators/lj.py	(revision 1)
@@ -0,0 +1,41 @@
+import numpy as np
+
+
+class LennardJones:
+    def __init__(self, epsilon=1.0, sigma=1.0):
+        self.epsilon = epsilon
+        self.sigma = sigma
+        self.positions = None
+
+    def update(self, atoms):
+        assert not atoms.get_pbc().any()
+        if (self.positions is None or
+            (self.positions != atoms.get_positions()).any()):
+            self.calculate(atoms)
+
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.energy
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self._forces
+
+    def get_stress(self, atoms):
+        return np.zeros((3, 3))
+    
+    def calculate(self, atoms):
+        positions = atoms.get_positions()
+        self.energy = 0.0
+        self._forces = np.zeros((len(atoms), 3))
+        for i1, p1 in enumerate(positions):
+            for i2, p2 in enumerate(positions[:i1]):
+                diff = p2 - p1
+                d2 = np.dot(diff, diff)
+                c6 = (self.sigma**2 / d2)**3
+                c12 = c6**2
+                self.energy += 4 * self.epsilon * (c12 - c6)
+                F = 24 * self.epsilon * (2 * c12 - c6) / d2 * diff
+                self._forces[i1] -= F
+                self._forces[i2] += F
+        self.positions = positions.copy()
Index: ase/calculators/abinit.py
===================================================================
--- ase/calculators/abinit.py	(revision 0)
+++ ase/calculators/abinit.py	(revision 1)
@@ -0,0 +1,601 @@
+"""This module defines an ASE interface to ABINIT.
+
+http://www.abinit.org/
+"""
+
+import os
+from glob import glob
+from os.path import join, isfile, islink
+
+import numpy as np
+
+from ase.data import chemical_symbols
+from ase.data import atomic_numbers
+from ase.units import Bohr, Hartree
+
+
+class Abinit:
+    """Class for doing ABINIT calculations.
+
+    The default parameters are very close to those that the ABINIT
+    Fortran code would use.  These are the exceptions::
+
+      calc = Abinit(label='abinit', xc='LDA', pulay=5, mix=0.1)
+
+    Use the set_inp method to set extra INPUT parameters::
+
+      calc.set_inp('nstep', 30)
+
+    """
+    def __init__(self, label='abinit', xc='LDA', kpts=None, nbands=0,
+                 width=0.04*Hartree, ecut=None, charge=0,
+                 pulay=5, mix=0.1, pps='fhi', toldfe=1.0e-6
+                 ):
+        """Construct ABINIT-calculator object.
+
+        Parameters
+        ==========
+        label: str
+            Prefix to use for filenames (label.in, label.txt, ...).
+            Default is 'abinit'.
+        xc: str
+            Exchange-correlation functional.  Must be one of LDA, PBE,
+            revPBE, RPBE.
+        kpts: list of three int
+            Monkhost-Pack sampling.
+        nbands: int
+            Number of bands.
+            Default is 0.
+        width: float
+            Fermi-distribution width in eV.
+            Default is 0.04 Hartree.
+        ecut: float
+            Planewave cutoff energy in eV.
+            No default.
+        charge: float
+            Total charge of the system.
+            Default is 0.
+        pulay: int
+            Number of old densities to use for Pulay mixing.
+        mix: float
+            Mixing parameter between zero and one for density mixing.
+
+        Examples
+        ========
+        Use default values:
+
+        >>> h = Atoms('H', calculator=Abinit())
+        >>> h.center(vacuum=3.0)
+        >>> e = h.get_potential_energy()
+
+        """
+
+        if not nbands > 0:
+            raise ValueError('Number of bands (nbands) not set')
+
+        if ecut is None:
+            raise ValueError('Planewave cutoff energy in eV (ecut) not set')
+
+        self.label = label#################### != out
+        self.xc = xc
+        self.kpts = kpts
+        self.nbands = nbands
+        self.width = width
+        self.ecut = ecut
+        self.charge = charge
+        self.pulay = pulay
+        self.mix = mix
+        self.pps = pps
+        self.toldfe = toldfe
+        if not pps in ['fhi', 'hgh', 'hgh.sc']:
+            raise ValueError('Unexpected PP identifier %s' % pps)
+
+        self.converged = False
+        self.inp = {}
+        self.n_entries_int = 20 # integer entries per line
+        self.n_entries_float = 8 # float entries per line
+
+    def update(self, atoms):
+        if (not self.converged or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        self.numbers = atoms.get_atomic_numbers().copy()
+        self.species = []
+        for a, Z in enumerate(self.numbers):
+            if Z not in self.species:
+                self.species.append(Z)
+
+        if 'ABINIT_PP_PATH' in os.environ:
+            pppaths = os.environ['ABINIT_PP_PATH'].split(':')
+        else:
+            pppaths = []
+
+        self.ppp_list = []
+        if self.xc != 'LDA':
+            xcname = 'GGA'
+        else:
+            xcname = 'LDA'
+
+        for Z in self.species:
+            symbol = chemical_symbols[abs(Z)]
+            number = atomic_numbers[symbol]
+
+            pps = self.pps
+            if pps == 'fhi':
+                name = '%02d-%s.%s.fhi' % (number, symbol, xcname)
+            elif pps in ('hgh', 'hgh.sc'):
+                hghtemplate = '%d%s.%s.hgh' # E.g. "42mo.6.hgh"
+                # There might be multiple files with different valence
+                # electron counts, so we must choose between
+                # the ordinary and the semicore versions for some elements.
+                #
+                # Therefore we first use glob to get all relevant files,
+                # then pick the correct one afterwards.
+                name = hghtemplate % (number, symbol.lower(), '*')
+
+            found = False
+            for path in pppaths:
+                if pps.startswith('hgh'):
+                    filenames = glob(join(path, name))
+                    if not filenames:
+                        continue
+                    assert len(filenames) in [0, 1, 2]
+                    if pps == 'hgh':
+                        selector = min # Lowest possible valence electron count
+                    else:
+                        assert pps == 'hgh.sc'
+                        selector = max # Semicore - highest electron count
+                    Z = selector([int(os.path.split(name)[1].split('.')[1])
+                                  for name in filenames])
+                    name = hghtemplate % (number, symbol.lower(), str(Z))
+                filename = join(path, name)
+                if isfile(filename) or islink(filename):
+                    found = True
+                    self.ppp_list.append(filename)
+                    break
+            if not found:
+                raise RuntimeError('No pseudopotential for %s!' % symbol)
+
+        self.converged = False
+
+    def get_potential_energy(self, atoms, force_consistent=False):
+        self.update(atoms)
+
+        if force_consistent:
+            return self.efree
+        else:
+            # Energy extrapolated to zero Kelvin:
+            return  (self.etotal + self.efree) / 2
+
+    def get_number_of_bands(self):
+        return self.nbands
+
+    def get_kpts_info(self, kpt=0, spin=0, mode='eigenvalues'):
+        return self.read_kpts_info(kpt, spin, mode)
+
+    def get_k_point_weights(self):
+        return self.get_kpts_info(kpt=0, spin=0, mode='k_point_weights')
+
+    def get_bz_k_points(self):
+        raise NotImplementedError
+
+    def get_ibz_k_points(self):
+        return self.get_kpts_info(kpt=0, spin=0, mode='ibz_k_points')
+
+    def get_fermi_level(self):
+        return self.read_fermi()
+
+    def get_eigenvalues(self, kpt=0, spin=0):
+        return self.get_kpts_info(kpt, spin, 'eigenvalues')
+
+    def get_occupations(self, kpt=0, spin=0):
+        return self.get_kpts_info(kpt, spin, 'occupations')
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress.copy()
+
+    def calculate(self, atoms):
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+
+        self.write_files()
+
+        self.write_inp(atoms)
+
+        abinit = os.environ['ABINIT_SCRIPT']
+        locals = {'label': self.label}
+
+        # Now, because (stupidly) abinit when it finds a name it uses nameA
+        # and when nameA exists it uses nameB, etc.
+        # we need to rename our *.txt file to *.txt.bak
+        filename = self.label + '.txt'
+        if islink(filename) or isfile(filename):
+            os.rename(filename, filename+'.bak')
+
+        execfile(abinit, {}, locals)
+        exitcode = locals['exitcode']
+        if exitcode != 0:
+            raise RuntimeError(('Abinit exited with exit code: %d.  ' +
+                                'Check %s.log for more information.') %
+                               (exitcode, self.label))
+
+        self.read()
+
+        self.converged = True
+
+    def write_files(self):
+        """Write input parameters to files-file."""
+        fh = open(self.label + '.files', 'w')
+
+        import getpass
+        #find a suitable default scratchdir (should be writeable!)
+        username=getpass.getuser()
+
+        if os.access("/scratch/"+username,os.W_OK):
+                scratch = "/scratch/"+username
+        elif os.access("/scratch/",os.W_OK):
+                scratch = "/scratch/"
+        else:
+                if os.access(os.curdir,os.W_OK):
+                        scratch = os.curdir #if no /scratch use curdir
+                else:
+                        raise IOError,"No suitable scratch directory and no write access to current dir"
+
+        fh.write('%s\n' % (self.label+'.in')) # input
+        fh.write('%s\n' % (self.label+'.txt')) # output
+        fh.write('%s\n' % (self.label+'i')) # input
+        fh.write('%s\n' % (self.label+'o')) # output
+        # scratch files
+        fh.write('%s\n' % (os.path.join(scratch, self.label+'.abinit')))
+        # Provide the psp files
+        for ppp in self.ppp_list:
+            fh.write('%s\n' % (ppp)) # psp file path
+
+        fh.close()
+
+    def set_inp(self, key, value):
+        """Set INPUT parameter."""
+        self.inp[key] = value
+
+    def write_inp(self, atoms):
+        """Write input parameters to in-file."""
+        fh = open(self.label + '.in', 'w')
+
+        inp = {
+            #'SystemLabel': self.label,
+            #'LatticeConstant': 1.0,
+            'natom': len(atoms),
+            'charge': self.charge,
+            'nband': self.nbands,
+            #'DM.UseSaveDM': self.converged,
+            #'SolutionMethod': 'diagon',
+            'npulayit': self.pulay, # default 7
+            'diemix': self.mix
+            }
+
+        if self.ecut is not None:
+            inp['ecut'] = str(self.ecut)+' eV' # default Ha
+
+        if self.width is not None:
+            inp['tsmear'] = str(self.width)+' eV' # default Ha
+            fh.write('occopt 3 # Fermi-Dirac smearing\n')
+
+        inp['ixc'] = { # default 1
+            'LDA':     7,
+            'PBE':    11,
+            'revPBE': 14,
+            'RPBE':   15,
+            'WC':     23
+            }[self.xc]
+
+        magmoms = atoms.get_initial_magnetic_moments()
+        if magmoms.any():
+            inp['nsppol'] = 2
+            fh.write('spinat\n')
+            for n, M in enumerate(magmoms):
+                if M != 0:
+                    fh.write('%.14f %.14f %.14f\n' % (0, 0, M))
+        else:
+            inp['nsppol'] = 1
+            #fh.write('spinat\n')
+            #for n, M in enumerate(magmoms):
+            #    if M != 0:
+            #        fh.write('%.14f\n' % (M))
+
+        inp.update(self.inp)
+
+        for key, value in inp.items():
+            if value is None:
+                continue
+
+            if isinstance(value, list):
+                fh.write('%block %s\n' % key)
+                for line in value:
+                    fh.write(' '.join(['%s' % x for x in line]) + '\n')
+                fh.write('%endblock %s\n' % key)
+
+            unit = keys_with_units.get(inpify(key))
+            if unit is None:
+                fh.write('%s %s\n' % (key, value))
+            else:
+                if 'fs**2' in unit:
+                    value /= fs**2
+                elif 'fs' in unit:
+                    value /= fs
+                fh.write('%s %f %s\n' % (key, value, unit))
+
+        fh.write('#Definition of the unit cell\n')
+        fh.write('acell\n')
+        fh.write('%.14f %.14f %.14f Angstrom\n' %  (1.0, 1.0, 1.0))
+        fh.write('rprim\n')
+        for v in self.cell:
+            fh.write('%.14f %.14f %.14f\n' %  tuple(v))
+
+        fh.write('chkprim 0 # Allow non-primitive cells\n')
+
+        fh.write('#Definition of the atom types\n')
+        fh.write('ntypat %d\n' % (len(self.species)))
+        fh.write('znucl')
+        for n, Z in enumerate(self.species):
+            fh.write(' %d' % (Z))
+        fh.write('\n')
+        fh.write('#Enumerate different atomic species\n')
+        fh.write('typat')
+        fh.write('\n')
+        self.types = []
+        for Z in self.numbers:
+            for n, Zs in enumerate(self.species):
+                if Z == Zs:
+                    self.types.append(n+1)
+        for n, type in enumerate(self.types):
+            fh.write(' %d' % (type))
+            if n > 1 and ((n % self.n_entries_int) == 1):
+                fh.write('\n')
+        fh.write('\n')
+
+        fh.write('#Definition of the atoms\n')
+        fh.write('xangst\n')
+        a = 0
+        for pos, Z in zip(self.positions, self.numbers):
+            a += 1
+            fh.write('%.14f %.14f %.14f\n' %  tuple(pos))
+
+        if self.kpts is not None:
+            fh.write('kptopt %d\n' % (1))
+            fh.write('ngkpt ')
+            fh.write('%d %d %d\n' %  tuple(self.kpts))
+
+        fh.write('#Definition of the SCF procedure\n')
+        fh.write('toldfe %.1g\n' %  self.toldfe)
+        fh.write('chkexit 1 # abinit.exit file in the running directory terminates after the current SCF\n')
+
+        fh.close()
+
+    def read_fermi(self):
+        """Method that reads Fermi energy in Hartree from the output file
+        and returns it in eV"""
+        E_f=None
+        filename = self.label + '.txt'
+        text = open(filename).read().lower()
+        assert 'error' not in text
+        for line in iter(text.split('\n')):
+            if line.rfind('fermi (or homo) energy (hartree) =') > -1:
+                E_f = float(line.split('=')[1].strip().split()[0])
+        return E_f*Hartree
+
+    def read_kpts_info(self, kpt=0, spin=0, mode='eigenvalues'):
+        """ Returns list of eigenvalues, occupations, kpts weights, or
+        kpts coordinates for given kpt and spin"""
+        # output may look like this (or without occupation entries); 8 entries per line:
+        #
+        #  Eigenvalues (hartree) for nkpt=  20  k points:
+        # kpt#   1, nband=  3, wtk=  0.01563, kpt=  0.0625  0.0625  0.0625 (reduced coord)
+        #  -0.09911   0.15393   0.15393
+        #      occupation numbers for kpt#   1
+        #   2.00000   0.00000   0.00000
+        # kpt#   2, nband=  3, wtk=  0.04688, kpt=  0.1875  0.0625  0.0625 (reduced coord)
+        # ...
+        #
+        assert mode in ['eigenvalues' , 'occupations', 'ibz_k_points', 'k_point_weights'], 'mode not in [\'eigenvalues\' , \'occupations\', \'ibz_k_points\', \'k_point_weights\']'
+        # number of lines of eigenvalues/occupations for a kpt
+        n_entry_lines = max(1, int(self.nbands/self.n_entries_float))
+        #
+        filename = self.label + '.txt'
+        text = open(filename).read().lower()
+        assert 'error' not in text
+        lines = iter(text.split('\n'))
+        text_list = []
+        # find the begining line of eigenvalues
+        contains_eigenvalues = False
+        for line in lines:
+            #if line.rfind('eigenvalues (hartree) for nkpt') > -1: #MDTMP
+            if line.rfind('eigenvalues (   ev  ) for nkpt') > -1:
+                n_kpts = int(line.split('nkpt=')[1].strip().split()[0])
+                contains_eigenvalues = True
+                break
+        # find the end line of eigenvalues starting from linenum
+        for line in lines:
+            text_list.append(line)
+            if not line.strip(): # find a blank line
+                break
+        # remove last (blank) line
+        text_list = text_list[:-1]
+
+        assert contains_eigenvalues, 'No eigenvalues found in the output'
+
+        # join text eigenvalues description with eigenvalues
+        # or occupation numbers for kpt# with occupations
+        contains_occupations = False
+        for line in text_list:
+            if line.rfind('occupation numbers') > -1:
+                contains_occupations = True
+                break
+        if mode == 'occupations':
+            assert contains_occupations, 'No occupations found in the output'
+
+        if contains_occupations:
+            range_kpts = 2*n_kpts
+        else:
+            range_kpts = n_kpts
+        #
+        values_list = []
+        offset = 0
+        for kpt_entry in range(range_kpts):
+            full_line = ''
+            for entry_line in range(n_entry_lines+1):
+                full_line = full_line+str(text_list[offset+entry_line])
+            first_line = text_list[offset]
+            if mode == 'occupations':
+                if first_line.rfind('occupation numbers') > -1:
+                    # extract numbers
+                    full_line = [float(v) for v in full_line.split('#')[1].strip().split()[1:]]
+                    values_list.append(full_line)
+            elif mode in ['eigenvalues', 'ibz_k_points', 'k_point_weights']:
+                if first_line.rfind('reduced coord') > -1:
+                    # extract numbers
+                    if mode == 'eigenvalues':
+                        #full_line = [Hartree*float(v) for v in full_line.split(')')[1].strip().split()[:]] # MDTMP
+                        full_line = [float(v) for v in full_line.split(')')[1].strip().split()[:]]
+                    elif mode == 'ibz_k_points':
+                        full_line = [float(v) for v in full_line.split('kpt=')[1].strip().split('(')[0].split()]
+                    else:
+                        full_line = float(full_line.split('wtk=')[1].strip().split(',')[0].split()[0])
+                    values_list.append(full_line)
+            offset = offset+n_entry_lines+1
+        #
+        if mode in ['occupations', 'eigenvalues']:
+            return np.array(values_list[kpt])
+        else:
+            return np.array(values_list)
+
+    def read(self):
+        """Read results from ABINIT's text-output file."""
+        filename = self.label + '.txt'
+        text = open(filename).read().lower()
+        assert 'error' not in text
+        assert 'was not enough scf cycles to converge' not in text
+        # some consistency ckecks
+        for line in iter(text.split('\n')):
+            if line.rfind('natom  ') > -1:
+                natom = int(line.split()[-1])
+                assert natom == len(self.numbers)
+        for line in iter(text.split('\n')):
+            if line.rfind('znucl  ') > -1:
+                znucl = [float(Z) for Z in line.split()[1:]]
+                for n, Z in enumerate(self.species):
+                    assert Z == znucl[n]
+        lines = text.split('\n')
+        for n, line in enumerate(lines):
+            if line.rfind(' typat  ') > -1:
+                nlines = len(self.numbers) / self.n_entries_int
+                typat = [int(t) for t in line.split()[1:]] # first line
+                for nline in range(nlines): # remaining lines
+                    for t in lines[1 + n + nline].split()[:]:
+                        typat.append(int(t))
+        for n, t in enumerate(self.types):
+            assert t == typat[n]
+
+        lines = iter(text.split('\n'))
+        # Stress:
+        # Printed in the output in the following format [Hartree/Bohr^3]:
+        # sigma(1 1)=  4.02063464E-04  sigma(3 2)=  0.00000000E+00
+        # sigma(2 2)=  4.02063464E-04  sigma(3 1)=  0.00000000E+00
+        # sigma(3 3)=  4.02063464E-04  sigma(2 1)=  0.00000000E+00
+        for line in lines:
+            if line.rfind('cartesian components of stress tensor (hartree/bohr^3)') > -1:
+                self.stress = np.empty((3, 3))
+                for i in range(3):
+                    entries = lines.next().split()
+                    self.stress[i,i] = float(entries[2])
+                    self.stress[min(3, 4-i)-1, max(1, 2-i)-1] = float(entries[5])
+                    self.stress[max(1, 2-i)-1, min(3, 4-i)-1] = float(entries[5])
+                self.stress = self.stress*Hartree/Bohr**3
+                break
+        else:
+            raise RuntimeError
+
+        # Energy [Hartree]:
+        # Warning: Etotal could mean both electronic energy and free energy!
+        for line in iter(text.split('\n')):
+            if line.rfind('>>>>> internal e=') > -1:
+                self.etotal = float(line.split('=')[-1])*Hartree
+                for line1 in iter(text.split('\n')):
+                    if line1.rfind('>>>>>>>>> etotal=') > -1:
+                        self.efree = float(line1.split('=')[-1])*Hartree
+                        break
+                else:
+                    raise RuntimeError
+                break
+        else:
+            for line2 in iter(text.split('\n')):
+                if line2.rfind('>>>>>>>>> etotal=') > -1:
+                    self.etotal = float(line2.split('=')[-1])*Hartree
+                    self.efree = self.etotal
+                    break
+            else:
+                raise RuntimeError
+
+        # Forces:
+        for line in lines:
+            if line.rfind('cartesian forces (ev/angstrom) at end:') > -1:
+                forces = []
+                for i in range(len(self.numbers)):
+                    forces.append(np.array([float(f) for f in lines.next().split()[1:]]))
+                self.forces = np.array(forces)
+                break
+        else:
+            raise RuntimeError
+
+def inpify(key):
+    return key.lower().replace('_', '').replace('.', '').replace('-', '')
+
+
+keys_with_units = {
+    }
+#keys_with_units = {
+#    'paoenergyshift': 'eV',
+#    'zmunitslength': 'Bohr',
+#    'zmunitsangle': 'rad',
+#    'zmforcetollength': 'eV/Ang',
+#    'zmforcetolangle': 'eV/rad',
+#    'zmmaxdispllength': 'Ang',
+#    'zmmaxdisplangle': 'rad',
+#    'ecut': 'eV',
+#    'dmenergytolerance': 'eV',
+#    'electronictemperature': 'eV',
+#    'oneta': 'eV',
+#    'onetaalpha': 'eV',
+#    'onetabeta': 'eV',
+#    'onrclwf': 'Ang',
+#    'onchemicalpotentialrc': 'Ang',
+#    'onchemicalpotentialtemperature': 'eV',
+#    'mdmaxcgdispl': 'Ang',
+#    'mdmaxforcetol': 'eV/Ang',
+#    'mdmaxstresstol': 'eV/Ang**3',
+#    'mdlengthtimestep': 'fs',
+#    'mdinitialtemperature': 'eV',
+#    'mdtargettemperature': 'eV',
+#    'mdtargetpressure': 'eV/Ang**3',
+#    'mdnosemass': 'eV*fs**2',
+#    'mdparrinellorahmanmass': 'eV*fs**2',
+#    'mdtaurelax': 'fs',
+#    'mdbulkmodulus': 'eV/Ang**3',
+#    'mdfcdispl': 'Ang',
+#    'warningminimumatomicdistance': 'Ang',
+#    'rcspatial': 'Ang',
+#    'kgridcutoff': 'Ang',
+#    'latticeconstant': 'Ang'}
Index: ase/calculators/jacapo/version.py
===================================================================
--- ase/calculators/jacapo/version.py	(revision 0)
+++ ase/calculators/jacapo/version.py	(revision 1)
@@ -0,0 +1,3 @@
+version = '0.6.7'
+
+
Index: ase/calculators/jacapo/pbe_psp.py
===================================================================
--- ase/calculators/jacapo/pbe_psp.py	(revision 0)
+++ ase/calculators/jacapo/pbe_psp.py	(revision 1)
@@ -0,0 +1,11 @@
+# Copyright (C) 2005 jrk
+
+""" Default pseudopotential paths are defined here
+"""
+
+__docformat__ = 'reStructuredText'
+
+defaultpseudopotentials = {'O':'008-O-gpbe-rc_1_3_nlc.uspp',
+                           'Pd':'046-Pd-gpe-n-6projectors-floc.uspp',
+                           'Ag':'047-Ag-gpe-n-floc.uspp'}
+
Index: ase/calculators/jacapo/changed.py
===================================================================
--- ase/calculators/jacapo/changed.py	(revision 0)
+++ ase/calculators/jacapo/changed.py	(revision 1)
@@ -0,0 +1,200 @@
+import numpy as np
+
+import logging
+log = logging.getLogger('Jacapo')
+
+'''
+provides functions to determine if an input parameter has changed.
+'''
+
+#######################################################################
+#### changed functions
+
+def kpts_changed(calc, x):
+    '''
+    check if kpt grid has changed.
+
+    we have to take care to generate teh right k-points from x if
+    needed. if a user provides (4,4,4) we need to generate the MP
+    grid, etc...
+
+    Since i changed the MP code in set_kpts, there is some
+    incompatibility with old jacapo calculations and their MP
+    grids.
+    '''
+    #chadi-cohen
+    if isinstance(x, str):
+        exec('from ase.dft.kpoints import %s' % kpts)
+        listofkpts = eval(kpts)
+    #monkhorst-pack grid
+    elif np.array(x).shape == (3,):
+        from ase.dft.kpoints import monkhorst_pack
+        N1, N2, N3 = x
+        listofkpts = monkhorst_pack((N1, N2, N3))
+    #user-defined list is provided
+    elif len(np.array(x).shape) == 2:
+        listofkpts = np.array(x)
+    else:
+        raise Exception, 'apparent invalid setting for kpts'
+
+    grid = calc.get_kpts()
+
+    if grid.shape != listofkpts.shape:
+        return True
+
+    if (abs(listofkpts - grid) < 1e-6).all():
+        return False
+    else:
+        return True
+
+def electronic_minimization_changed(calc, x):
+    myx = calc.get_electronic_minimization()
+
+    for key in myx:
+        if myx[key] != x[key]:
+            print key, myx[key], ' changed to ', x[key]
+            return True
+    return False
+
+def spinpol_changed(calc, x):
+    if x != calc.get_spinpol():
+        return True
+    else:
+        return False
+
+def symmetry_changed(calc, x):
+    if x != calc.get_symmetry():
+        return True
+    else:
+        return False
+
+def xc_changed(calc, x):
+    if x != calc.get_xc():
+        return True
+    return False
+
+def calculate_stress_changed(calc, x):
+    if x != calc.get_calculate_stress():
+        return True
+    return False
+
+def ados_changed(calc, x):
+    ados = calc.get_ados()
+
+    #ados may not be defined, and then None is returned
+    if ados is None and x is None:
+        return False
+    elif ados is None and x is not None:
+        return True
+    elif ados is not None and x is None:
+        return True
+
+    #getting here means ados and x are not none so we compare them
+    for key in x:
+        try:
+            if x[key] != ados[key]:
+                return True
+        except ValueError:
+            if (x[key] != ados[key]).all():
+                return True
+    return False
+
+def convergence_changed(calc, x):
+    conv = calc.get_convergence()
+    for key in x:
+        if x[key] != conv[key]:
+            return True
+    return False
+
+def charge_mixing_changed(calc, x):
+    cm = calc.get_charge_mixing()
+    if x is None and cm is None:
+        return False
+    else:
+        return True
+        
+    for key in x:
+        if x[key] != cm[key]:
+            return True
+    return False
+
+def decoupling_changed(calc, x):
+    pars = calc.get_decoupling()
+    for key in x:
+        if x[key] != pars[key]:
+            return True
+    return False
+
+def dipole_changed(calc, x):
+    pars = calc.get_dipole()
+    if pars is False and x is False:
+        return False
+    elif pars is not False:
+        for key in x:
+            if x[key] != pars[key]:
+                return True
+        return False
+
+def extpot_changed(calc, x):
+    extpot = calc.get_extpot()
+    if (x == extpot).all():
+        return False
+    return True
+
+def fftgrid_changed(calc, x):
+    validkeys = ['soft', 'hard']
+
+    myx = calc.get_fftgrid()
+    if (myx['soft'] == x['soft'] and myx['hard'] == x['hard']):
+        return False
+    else:
+        return True
+
+
+def nbands_changed(calc, x):
+    if calc.get_nbands() == x:
+        return False
+    else:
+        return True
+
+def occupationstatistics_changed(calc, x):
+    if calc.get_occupationstatistics() == x:
+        return False
+    else:
+        return True
+
+def pw_changed(calc, x):
+    if calc.get_pw() == x:
+        return False
+    else:
+        return True
+
+def dw_changed(calc, x):
+    if calc.get_dw() == x:
+        return False
+    else:
+        return True
+
+def ft_changed(calc, x):
+    if calc.get_ft() == x:
+        return False
+    else:
+        return True
+    
+def mdos_changed(calc,x):
+
+    myx = calc.get_mdos()
+
+    log.debug('myx = %s' % str(myx))
+    log.debug('x = %s' % str(x))
+
+    if x is None and myx is None:
+        return False
+    elif ((x is None and myx is not None)
+        or (x is not None and myx is None)):
+        return True
+    else:
+        for key in x:
+            if x[key] != myx[key]:
+                return True
+    return False
Index: ase/calculators/jacapo/mayavis.py
===================================================================
--- ase/calculators/jacapo/mayavis.py	(revision 0)
+++ ase/calculators/jacapo/mayavis.py	(revision 1)
@@ -0,0 +1,80 @@
+'''
+mayavi interface to plot atoms, unit cells, and volumetric data
+'''
+import numpy as np
+from enthought.mayavi import mlab
+mlab.figure(1, bgcolor=(1,1,1), size=(350, 350))
+mlab.clf()
+
+def plot_cylinder(start,end,tube_radius=0.1,color=(0,0,0)):
+    
+    mlab.plot3d([start[0],end[0]],[start[1],end[1]],[start[2],end[2]],
+                tube_radius=tube_radius,color=color)
+    
+    
+def plot_atoms(atoms):
+
+    for atom in atoms:
+        pos = atom.get_position()
+        mlab.points3d([pos[0]],[pos[1]],[pos[2]],
+                      scale_factor=4,
+                      resolution=20,
+                      color=(1,0,0),        #this should get species specifuc
+                      scale_mode='none')
+
+    (u0,u1,u2) = atoms.get_cell()
+    origin = np.array([0.0,0.0,0.0])
+    
+    plot_cylinder(origin,u0)
+    plot_cylinder(origin,u1)
+    plot_cylinder(origin,u2)
+
+    plot_cylinder(u0,u0+u1)
+    plot_cylinder(u0,u0+u2)
+
+    plot_cylinder(u1,u1+u0)
+    plot_cylinder(u1,u1+u2)
+
+    plot_cylinder(u2,u2+u0)
+    plot_cylinder(u2,u2+u1)
+
+    plot_cylinder(u0+u1,u0+u1+u2)
+    plot_cylinder(u1+u2,u0+u1+u2)
+    plot_cylinder(u0+u2,u0+u1+u2)
+    mlab.show()
+
+
+    
+
+
+
+if __name__ == '__main__':
+    from ase.lattice.cubic import *
+
+    from ase.lattice.bravais import cross
+    import numpy as np
+
+    a = np.array([0.5,0,0])
+    c = np.array([0,1,0],dtype=np.float)
+    b1 = c - a
+
+    a = np.array([0,1,0],np.float)
+    c = np.array([0,0.5,0.5])
+    b2 = c - a
+
+    a3 = np.array([2,1,1],np.float)
+
+    a1 = cross(b1,a3)
+    a2 = cross(b2,a3)
+    v211 = FaceCenteredCubic(directions=[a1,a2,a3],
+                             miller=(None,None,[2,1,1]),
+                             symbol='Pd',    
+                             size=(1,1,2),  
+                             debug=0)
+
+    uc = v211.get_cell()
+    uc[2][2] += 10.0
+    v211.set_cell(uc)
+
+    plot_atoms(v211.repeat((2,2,1)))
+    
Index: ase/calculators/jacapo/validate.py
===================================================================
--- ase/calculators/jacapo/validate.py	(revision 0)
+++ ase/calculators/jacapo/validate.py	(revision 1)
@@ -0,0 +1,338 @@
+import os
+import numpy as np
+'''
+input validation module
+
+provides functions to validate all input variables to the Jacapo calculator.
+'''
+
+###########################################3
+### Validation functions
+##########################################
+def valid_int(x):
+    return isinstance(x, int)
+
+def valid_float(x):
+    return isinstance(x, float)
+
+def valid_int_or_float(x):
+    return (isinstance(x, int) or isinstance(x, float))
+
+def valid_boolean(x):
+    return isinstance(x, bool)
+
+def valid_str(x):
+    return isinstance(x, str)
+
+def valid_atoms(x):
+    import ase
+    return isinstance(x, ase.Atoms)
+
+def valid_pw(x):
+    return (valid_int_or_float(x) and x>0 and x<2000)
+
+def valid_dw(x):
+    return (valid_int_or_float(x) and x>0 and x<2000)
+
+def valid_xc(x):
+    return (x in ['PW91', 'PBE', 'revPBE', 'RPBE', 'VWN'])
+
+def valid_nbands(x):
+    return valid_int(x)
+
+def valid_ft(x):
+    return(valid_float, x)
+
+def valid_spinpol(x):
+    return valid_boolean(x)
+
+def valid_fixmagmom(x):
+    return valid_float(x)
+
+def valid_symmetry(x):
+    return valid_boolean(x)
+
+def valid_calculate_stress(x):
+    return valid_boolean(x)
+
+def valid_kpts(x):
+    if isinstance(x, str):
+        return x in ['cc-6-1x1',
+                     'cc-12-2x3',
+                     'cc-18-sq3xsq3',
+                     'cc-18-1x1',
+                     'cc-54-sq3xsq3',
+                     'cc-54-1x1',
+                     'cc-162-1x1']
+    x = np.array(x)
+    #empty arg is no good
+    if x.shape == ():
+        return False
+    #monkhorst-pack
+    elif x.shape == (3,) and ((x.dtype == 'int32') or (x.dtype == 'int64')):
+        return True
+    #user-defined list
+    elif x.shape[1] == 3 and (str(x.dtype))[0:7] == 'float64':
+        return True
+    else:
+        return False
+
+def valid_dipole(x):
+    if valid_boolean(x):
+        return True
+    #dictionary passed in. we need to check the keys
+    valid_keys = {'status':valid_boolean,
+                  'mixpar':valid_float,
+                  'initval':valid_float,
+                  'adddipfield':valid_float,
+                  'position':valid_float}
+    for key in x:
+        if key not in valid_keys:
+            return False
+        else:
+            if x[key] is not None:
+                if not valid_keys[key](x[key]):
+                    return False
+    return True
+
+def valid_nc(x):
+    #todo check for read/write access?
+    return valid_str(x)
+
+def valid_status(x):
+    return valid_str(x)
+
+def valid_pseudopotentials(x):
+    #todo check that keys are symbols or numbers
+    #todo check that psp files exist
+
+    DACAPOPATH = os.environ.get('DACAPOPATH', None)
+    if DACAPOPATH is None:
+        raise Exception, 'No $DACAPOPATH found. please set it in .cshrc or .bashrc'
+
+    from ase.data import chemical_symbols
+    for key in x:
+        if valid_str(key):
+            if key not in chemical_symbols:
+                return False
+        elif not (valid_int(key) and key > 0 and key < 112):
+            return False
+
+        #now check for existence of psp files
+        psp = x[key]
+        if not (os.path.exists(psp)
+                or os.path.exists(os.path.join(DACAPOPATH, psp))):
+            return False
+    return True
+
+def valid_extracharge(x):
+    return valid_float(x)
+
+def valid_extpot(x):
+    grids = get_fftgrid()
+    if (x.shape == np.array(grids['soft'])).all():
+        return True
+    else:
+        return False
+
+def valid_ascii_debug(x):
+    return (x in ['Off', 'MediumLevel', 'HighLevel'])
+
+def valid_ncoutput(x):
+    if x is None:
+        return
+    valid_keys = ['wf', 'cd', 'efp', 'esp']
+
+    for key in x:
+        if key not in valid_keys:
+            return False
+        else:
+            if x[key] not in ['Yes', 'No']:
+                return False
+    return True
+
+def valid_ados(x):
+    if x is None:
+        return
+    valid_keys = ['energywindow',
+                  'energywidth',
+                  'npoints',
+                  'cutoff']
+    for key in x:
+        if key not in valid_keys:
+            print '%s not in %s' % (key, str(valid_keys))
+            return False
+        if key == 'energywindow':
+            if not len(x['energywindow']) == 2:
+                print '%s is bad' % key
+                return False
+        if key == 'energywidth':
+            if not valid_float(x['energywidth']):
+                print key, ' is bad'
+                return False
+        elif key == 'npoints':
+            if not valid_int(x['npoints']):
+                print key, ' is bad'
+                return False
+        elif key == 'cutoff':
+            if not valid_float(x['cutoff']):
+                print key, ' is bad'
+                return False
+    return True
+
+
+def valid_decoupling(x):
+    if x is None:
+        return
+    valid_keys = ['ngaussians', 'ecutoff', 'gausswidth']
+    for key in x:
+        if key not in valid_keys:
+            return False
+        elif key == 'ngaussians':
+            if not valid_int(x[key]):
+                print key
+                return False
+        elif key == 'ecutoff':
+            if not valid_int_or_float(x[key]):
+                return False
+        elif key == 'gausswidth':
+            if not valid_float(x[key]):
+                print key, x[key]
+                return False
+    return True
+
+def valid_external_dipole(x):
+    if x is None:
+        return
+    if valid_float(x):
+        return True
+
+    valid_keys = ['value', 'position']
+
+    for key in x:
+        if key not in valid_keys:
+            return False
+        if key == 'value':
+            if not valid_float(x['value']):
+                return False
+        elif key == 'position':
+            if not valid_float(x['position']):
+                return False
+    return True
+
+def valid_stay_alive(x):
+    return valid_boolean(x)
+
+def valid_fftgrid(x):
+    valid_keys = ['soft', 'hard']
+    for key in x:
+        if key not in valid_keys:
+            return False
+        if x[key] is None:
+            continue
+
+        grid = np.array(x[key])
+        if (grid.shape != (3,) and grid.dtype != 'int32'):
+            return False
+    return True
+
+def valid_convergence(x):
+    valid_keys = ['energy',
+                  'density',
+                  'occupation',
+                  'maxsteps',
+                  'maxtime']
+    for key in x:
+        if key not in valid_keys:
+            return False
+        if x[key] is None:
+            continue
+        if key == 'energy':
+            if not valid_float(x[key]):
+                return False
+        elif key == 'density':
+            if not valid_float(x[key]):
+                return False
+        elif key == 'occupation':
+            if not valid_float(x[key]):
+                return False
+        elif key == 'maxsteps':
+            if not valid_int(x[key]):
+                return False
+        elif key == 'maxtime':
+            if not valid_int(x[key]):
+                return False
+    return True
+
+def valid_charge_mixing(x):
+    valid_keys = ['method',
+                  'mixinghistory',
+                  'mixingcoeff',
+                  'precondition',
+                  'updatecharge']
+
+    for key in x:
+        if key not in valid_keys:
+            return False
+        elif key == 'method':
+            if x[key] not in ['Pulay']:
+                return False
+        elif key == 'mixinghistory':
+            if not valid_int(x[key]):
+                return False
+        elif key == 'mixingcoeff':
+            if not valid_float(x[key]):
+                return False
+        elif key == 'precondition':
+            if x[key] not in ['Yes', 'No']:
+                return False
+        elif key == 'updatecharge':
+            if x[key] not in ['Yes', 'No']:
+                return False
+    return True
+
+def valid_electronic_minimization(x):
+    valid_keys = ['method', 'diagsperband']
+    for key in x:
+        if key not in valid_keys:
+            return False
+        elif key == 'method':
+            if x[key] not in ['resmin',
+                              'eigsolve',
+                              'rmm-diis']:
+                return False
+        elif key == 'diagsperband':
+            if not valid_int(x[key]):
+                return False
+    return True
+
+def valid_occupationstatistics(x):
+    return (x in ['FermiDirac', 'MethfesselPaxton'])
+
+
+def valid_mdos(x):
+    return True
+
+def valid_psp(x):
+    valid_keys = ['sym','psp']
+    if x is None:
+        return True
+    for key in x:
+        if key not in valid_keys:
+            return False
+        if not valid_str(x[key]):
+            return False
+        if key == 'sym':
+            from ase.data import chemical_symbols
+            if key not in chemical_symbols:
+                return False
+        if key == 'psp':
+            
+            if os.path.exists(x['psp']):
+                return True
+
+            if os.path.exists(os.path.join(os.environ['DACAPOPATH'],
+                                           x['psp'])):
+                return True
+            #psp not found
+            return False
Index: ase/calculators/jacapo/jacapo.py
===================================================================
--- ase/calculators/jacapo/jacapo.py	(revision 0)
+++ ase/calculators/jacapo/jacapo.py	(revision 1)
@@ -0,0 +1,4236 @@
+'''
+python module for ASE2-free and Numeric-free dacapo
+
+U{John Kitchin<mailto:jkitchin@andrew.cmu.edu>} December 25, 2008
+
+This module supports numpy directly.
+
+* ScientificPython2.8 is required
+
+ - this is the first version to use numpy by default.
+
+see https://wiki.fysik.dtu.dk/stuff/nc/ for dacapo netcdf variable
+documentation
+'''
+
+__docformat__ = 'restructuredtext'
+
+import exceptions, glob, os, pickle, string
+from Scientific.IO.NetCDF import NetCDFFile as netCDF
+import numpy as np
+import subprocess as sp
+
+import validate
+import changed 
+
+import logging
+log = logging.getLogger('Jacapo')
+
+handler = logging.StreamHandler()
+formatter = logging.Formatter('''\
+%(levelname)-10s function: %(funcName)s lineno: %(lineno)-4d \
+%(message)s''')
+handler.setFormatter(formatter)
+log.addHandler(handler)
+
+
+class DacapoRunning(exceptions.Exception):
+    """Raised when ncfile.status = 'running'"""
+    pass
+
+class DacapoAborted(exceptions.Exception):
+    """Raised when ncfile.status = 'aborted'"""
+    pass
+
+class DacapoInput(exceptions.Exception):
+    ''' raised for bad input variables'''
+    pass
+
+class DacapoAbnormalTermination(exceptions.Exception):
+    """Raised when text file does not end correctly"""
+    pass
+
+def read(ncfile):
+    '''return atoms and calculator from ncfile
+
+    >>> atoms, calc = read('co.nc')
+    '''
+    calc = Jacapo(ncfile)
+    atoms = calc.get_atoms() #this returns a copy
+    return (atoms, calc)
+
+class Jacapo:
+    '''
+    Python interface to the Fortran DACAPO code
+    '''
+    
+    __name__ = 'Jacapo'
+    __version__ = 0.4
+    
+    #dictionary of valid input variables and default settings
+    default_input = {'atoms':None,
+                     'pw':350,
+                     'dw':350,
+                     'xc':'PW91',
+                     'nbands':None,
+                     'ft':0.1,
+                     'kpts':(1,1,1),
+                     'spinpol':False,
+                     'fixmagmom':None,
+                     'symmetry':False,
+                     'calculate_stress':False,
+                     'dipole':{'status':False,
+                               'mixpar':0.2,
+                               'initval':0.0,
+                               'adddipfield':0.0,
+                               'position':None},                         
+                     'status':'new',
+                     'pseudopotentials':None,
+                     'extracharge':None,
+                     'extpot':None,
+                     'fftgrid':None,
+                     'ascii_debug':'Off',
+                     'ncoutput':{'wf':'Yes',
+                                 'cd':'Yes',
+                                 'efp':'Yes',
+                                 'esp':'Yes'},
+                     'ados':None,
+                     'decoupling':None,
+                     'external_dipole':None,
+                     'convergence':{'energy':0.00001,
+                                    'density':0.0001,
+                                    'occupation':0.001,
+                                    'maxsteps':None,
+                                    'maxtime':None},
+                     'charge_mixing':{'method':'Pulay',
+                                      'mixinghistory':10,
+                                      'mixingcoeff':0.1,
+                                      'precondition':'No',
+                                      'updatecharge':'Yes'},
+                     'electronic_minimization':{'method':'eigsolve',
+                                                'diagsperband':2},
+                     'occupationstatistics':'FermiDirac',
+                     'fftgrid':{'soft':None,
+                                'hard':None},
+                     'mdos':None,
+                     'psp':None
+                   }
+    
+    def __init__(self,
+                 nc='out.nc',
+                 outnc=None,
+                 debug=logging.WARN,
+                 stay_alive=False,
+                 **kwargs):
+        '''
+        Initialize the Jacapo calculator
+
+        :Parameters:
+
+          nc : string
+           output netcdf file, or input file if nc already exists
+
+          outnc : string
+           output file. by default equal to nc
+
+          debug : integer
+           logging debug level.
+           
+        Valid kwargs:
+
+          atoms : ASE.Atoms instance
+            atoms is an ase.Atoms object that will be attached
+            to this calculator.
+        
+          pw : integer
+            sets planewave cutoff
+
+          dw : integer
+            sets density cutoff
+
+          kpts : iterable
+            set chadi-cohen, monkhorst-pack kpt grid,
+            e.g. kpts = (2,2,1) or explicit list of kpts
+            
+          spinpol : Boolean
+            sets whether spin-polarization is used or not.
+
+          fixmagmom : float
+            set the magnetic moment of the unit cell. only used
+            in spin polarize calculations
+
+          ft : float
+            set the Fermi temperature used in occupation smearing
+
+          xc : string
+            set the exchange-correlation functional.
+            one of ['PZ','VWN','PW91','PBE','RPBE','revPBE'],
+
+          dipole
+            boolean
+            turn the dipole correction on (True) or off (False)
+
+            or:
+            dictionary of parameters to fine-tune behavior
+            {'status':False,
+            'mixpar':0.2,
+            'initval':0.0,
+            'adddipfield':0.0,
+            'position':None}
+            
+          nbands : integer
+            set the number of bands
+
+          symmetry : Boolean
+            Turn symmetry reduction on (True) or off (False)
+
+          stress : Boolean
+            Turn stress calculation on (True) or off (False)
+
+          debug : level for logging
+            could be something like
+            logging.DEBUG or an integer 0-50. The higher the integer,
+            the less information you see set debug level (0 = off, 10 =
+            extreme)
+        
+        Modification of the nc file only occurs at calculate time if needed
+
+        >>> calc = Jacapo('CO.nc')
+        
+        reads the calculator from CO.nc if it exists or
+        minimally initializes CO.nc with dimensions if it does not exist. 
+
+        >>> calc = Jacapo('CO.nc', pw=300)
+
+        reads the calculator from CO.nc or initializes it if
+        it does not exist and changes the planewave cutoff energy to
+        300eV
+
+         >>> atoms = Jacapo.read_atoms('CO.nc')
+
+        returns the atoms in the netcdffile CO.nc, with the calculator
+        attached to it.
+
+        >>> atoms, calc = read('CO.nc')
+        
+        '''
+        self.debug = debug
+        log.setLevel(debug)
+
+        self.pars = Jacapo.default_input.copy()
+        self.pars_uptodate = {}
+
+        log.debug(self.pars)
+        
+        for key in self.pars:
+            self.pars_uptodate[key] = False
+            
+        self.kwargs = kwargs
+        self.set_psp_database()
+
+        self.set_nc(nc)
+        #assume not ready at init, rely on code later to change this 
+        self.ready = False  
+
+        # need to set a default value for stay_alive
+        self.stay_alive = stay_alive
+        
+        # Jacapo('out.nc') should return a calculator with atoms in
+        # out.nc attached or initialize out.nc
+        if os.path.exists(nc):
+
+            # for correct updating, we need to set the correct frame number
+            # before setting atoms or calculator
+
+            self._set_frame_number()
+
+            self.atoms = self.read_only_atoms(nc)
+
+            #if atoms object is passed to
+            #__init__ we assume the user wants the atoms object
+            # updated to the current state in the file.
+            if 'atoms' in kwargs:
+                log.debug('Updating the atoms in kwargs')
+
+                atoms = kwargs['atoms']
+                atoms.set_cell(self.atoms.get_cell())
+                atoms.set_positions(self.atoms.get_positions())
+                atoms.calc = self
+                
+            #update the parameter list from the ncfile
+            self.update_input_parameters()
+    
+            self.ready = True
+                   
+        #change output file if needed
+        if outnc:
+            self.set_nc(outnc)          
+            
+        if len(kwargs) > 0:
+
+            if 'stress' in kwargs:
+                raise DacapoInput, '''\
+                stress keyword is deprecated.
+                you must use calculate_stress instead'''
+            
+            #make sure to set calculator on atoms if it was in kwargs
+            #and do this first, since some parameters need info from atoms
+            if 'atoms' in kwargs:
+                #we need to set_atoms here so the atoms are written to
+                #the ncfile
+                self.set_atoms(kwargs['atoms'])
+                kwargs['atoms'].calc = self
+                del kwargs['atoms'] #so we don't call it in the next
+                                    #line. we don't want to do that
+                                    #because it will update the _frame
+                                    #counter, and that should not be
+                                    #done here.
+                                
+            self.set(**kwargs) #if nothing changes, nothing will be done
+                    
+    def set(self, **kwargs):
+        '''set a parameter
+
+        parameter is stored in dictionary that is processed later if a
+        calculation is need.
+        '''
+        for key in kwargs:
+            if key not in self.default_input:
+                raise DacapoInput, '%s is not valid input' % key
+
+            if kwargs[key] is None:
+                continue
+            
+            #now check for valid input
+            validf = 'validate.valid_%s' % key
+            valid = eval('%s(kwargs[key])' % validf)
+            if not valid:
+                s = 'Warning invalid input detected for key "%s" %s'
+                log.warn(s % (key,
+                              kwargs[key]))
+                raise DacapoInput, s % (key, kwargs[key])
+                                       
+            #now see if key has changed
+            if key in self.pars:
+                changef = 'changed.%s_changed' % key
+                if os.path.exists(self.get_nc()):
+                    notchanged = not eval('%s(self,kwargs[key])' % changef)
+                else:
+                    notchanged = False
+                log.debug('%s notchanged = %s' % (key, notchanged))
+
+                if notchanged:
+                    continue
+
+            log.debug('setting: %s. self.ready = False ' % key)
+            
+            self.pars[key] = kwargs[key]
+            self.pars_uptodate[key] = False
+            self.ready = False
+            log.debug('exiting set function')
+
+    def write_input(self):
+        '''write out input parameters as needed
+
+        you must define a self._set_keyword function that does all the
+        actual writing.
+        '''
+        log.debug('Writing input variables out')
+        log.debug(self.pars)
+        
+        if 'DACAPO_READONLY' in os.environ:
+            raise Exception, 'DACAPO_READONLY set and you tried to write!'
+        
+        if self.ready:
+            return
+        # Only write out changed parameters. this function does not do
+        # the writing, that is done for each variable in private
+        # functions.
+        for key in self.pars:
+            if self.pars_uptodate[key] is False:
+                setf = 'set_%s' % key
+
+                if self.pars[key] is None:
+                    continue
+                
+                log.debug('trying to call: %s' % setf)
+                log.debug('self.%s(self.pars[key])' % setf)
+                log.debug('key = %s' % str(self.pars[key]))
+
+                if isinstance(self.pars[key], dict):
+                    eval('self.%s(**self.pars[key])' % setf)
+                else:
+                    eval('self.%s(self.pars[key])' % setf)
+                
+                self.pars_uptodate[key] = True #update the changed flag
+
+                log.debug('wrote %s: %s' % (key, str(self.pars[key])))
+
+        #set Jacapo version
+        ncf = netCDF(self.get_nc(), 'a')
+        ncf.Jacapo_version = Jacapo.__version__
+        ncf.sync()
+        ncf.close()
+
+    def update_input_parameters(self):
+        '''read in all the input parameters from the netcdfile'''
+
+        log.debug('Updating parameters')
+        
+        for key in self.default_input:
+            getf = 'self.get_%s()' % key
+            log.debug('getting key: %s' % key)
+            self.pars[key] = eval(getf)
+            self.pars_uptodate[key] = True
+        return self.pars
+    
+    def initnc(self, ncfile=None):
+        '''create an ncfile with minimal dimensions in it
+
+        this makes sure the dimensions needed for other set functions
+        exist when needed.'''
+        
+        if ncfile is None:
+            ncfile = self.get_nc()
+        else:
+            self.set_nc(ncfile)
+            
+        log.debug('initializing %s' % ncfile)
+                
+        base = os.path.split(ncfile)[0]
+        if base is not '' and not os.path.isdir(base):
+            os.makedirs(base)
+        
+        ncf = netCDF(ncfile, 'w')
+        #first, we define some dimensions we always need
+        #unlimited
+        ncf.createDimension('number_ionic_steps', None)
+        ncf.createDimension('dim1', 1)
+        ncf.createDimension('dim2', 2)
+        ncf.createDimension('dim3', 3)
+        ncf.createDimension('dim4', 4)
+        ncf.createDimension('dim5', 5)
+        ncf.createDimension('dim6', 6)
+        ncf.createDimension('dim7', 7)
+        ncf.createDimension('dim20', 20) #for longer strings
+        ncf.status  = 'new'
+        ncf.history = 'Dacapo'
+        ncf.jacapo_version = Jacapo.__version__
+        ncf.close()
+        
+        self.ready = False
+        self._frame = 0
+        
+    def __del__(self):
+        '''If calculator is deleted try to stop dacapo program
+        '''
+        
+        if hasattr(self, '_dacapo'):
+            if self._dacapo.poll()==None:
+                self.execute_external_dynamics(stopprogram=True)
+        #and clean up after Dacapo
+        if os.path.exists('stop'):
+            os.remove('stop')
+        #remove slave files
+        txt = self.get_txt()
+        if txt is not None:
+            slv = txt + '.slave*'
+            for slvf in glob.glob(slv):
+                os.remove(slvf)
+        
+    def __str__(self):
+        '''
+        pretty-print the calculator and atoms.
+
+        we read everything directly from the ncfile to prevent
+        triggering any calculations
+        '''
+        s = []
+        if self.nc is None:
+            return 'No netcdf file attached to this calculator'
+        if not os.path.exists(self.nc):
+            return 'ncfile does not exist yet'
+        
+        nc = netCDF(self.nc, 'r')
+        s.append('  ---------------------------------')
+        s.append('  Dacapo calculation from %s' % self.nc)
+        if hasattr(nc, 'status'):
+            s.append('  status = %s' % nc.status)
+        if hasattr(nc, 'version'):
+            s.append('  version = %s' % nc.version)
+        if hasattr(nc, 'Jacapo_version'):
+            s.append('  Jacapo version = %s' % nc.Jacapo_version[0])
+        
+        energy = nc.variables.get('TotalEnergy', None)
+        
+        if energy and energy[:][-1] < 1E36: # missing values get
+                                              # returned at 9.3E36
+            s.append('  Energy = %1.6f eV' % energy[:][-1])
+        else:
+            s.append('  Energy = None')
+            
+        s.append('')
+        
+        atoms = self.get_atoms()
+        
+        if atoms is None:
+            s.append('  no atoms defined')
+        else:
+            uc = atoms.get_cell()
+            #a, b, c = uc
+            s.append("  Unit Cell vectors (angstroms)")
+            s.append("         x        y       z   length")
+
+            for i, v in enumerate(uc):
+                L = (np.sum(v**2))**0.5 #vector length
+                s.append("  a%i [% 1.4f % 1.4f % 1.4f] %1.2f" % (i,
+                                                                 v[0],
+                                                                 v[1],
+                                                                 v[2],
+                                                                 L))
+                                                                 
+            stress = nc.variables.get('TotalStress', None)
+            if stress is not None:
+                stress = np.take(stress[:].ravel(), [0, 4, 8, 5, 2, 1])
+                s.append('  Stress: xx,   yy,    zz,    yz,    xz,    xy')
+                s1 = '       % 1.3f % 1.3f % 1.3f % 1.3f % 1.3f % 1.3f'
+                s.append(s1 % tuple(stress))
+            else:
+                s.append('  No stress calculated.')
+            s.append('  Volume = %1.2f A^3' % atoms.get_volume())
+            s.append('')
+
+            z = "  Atom,  sym, position (in x,y,z),     tag, rmsForce and psp"
+            s.append(z)
+
+            #this is just the ncvariable
+            forces = nc.variables.get('DynamicAtomForces', None)
+           
+            for i, atom in enumerate(atoms):
+                sym = atom.get_symbol()
+                pos = atom.get_position()
+                tag = atom.get_tag()
+                if forces is not None and (forces[:][-1][i] < 1E36).all():
+                    f = forces[:][-1][i]
+                    # Lars Grabow: this seems to work right for some
+                    # reason, but I would expect this to be the right
+                    # index order f=forces[-1][i][:]
+                    # frame,atom,direction
+                    rmsforce = (np.sum(f**2))**0.5
+                else:
+                    rmsforce = None
+                    
+                st = "  %2i   %3.12s  " % (i, sym)
+                st += "[% 7.3f%7.3f% 7.3f] " % tuple(pos)
+                st += " %2s  " % tag
+                if rmsforce is not None:
+                    st += " %4.3f " % rmsforce
+                else:
+                    st += ' None '
+                st += " %s"  % (self.get_psp(sym))        
+                s.append(st)
+                
+        s.append('')
+        s.append('  Details:')
+        xc = self.get_xc()
+        if xc is not None:
+            s.append('  XCfunctional        = %s' % self.get_xc())
+        else:
+            s.append('  XCfunctional        = Not defined')
+        s.append('  Planewavecutoff     = %i eV' % int(self.get_pw()))
+        dw = self.get_dw()
+        if dw:
+            s.append('  Densitywavecutoff   = %i eV' % int(self.get_dw()))
+        else:
+            s.append('  Densitywavecutoff   = None')
+        ft = self.get_ft()
+        if ft is not None:
+            s.append('  FermiTemperature    = %f kT' % ft)
+        else:
+            s.append('  FermiTemperature    = not defined')
+        nelectrons = self.get_valence()
+        if nelectrons is not None:
+            s.append('  Number of electrons = %1.1f'  % nelectrons)
+        else:
+            s.append('  Number of electrons = None')
+        s.append('  Number of bands     = %s'  % self.get_nbands())
+        s.append('  Kpoint grid         = %s' % str(self.get_kpts_type()))
+        s.append('  Spin-polarized      = %s' % self.get_spin_polarized())
+        s.append('  Dipole correction   = %s' % self.get_dipole())
+        s.append('  Symmetry            = %s' % self.get_symmetry())
+        s.append('  Constraints         = %s' % str(atoms._get_constraints()))
+        s.append('  ---------------------------------')
+        nc.close()
+        return string.join(s, '\n')            
+
+    #todo figure out other xc psp databases
+    def set_psp_database(self, xc=None):
+        '''
+        get the xc-dependent psp database
+
+        :Parameters:
+
+         xc : string
+           one of 'PW91', 'PBE', 'revPBE', 'RPBE', 'PZ'
+
+        
+        not all the databases are complete, and that means
+        some psp do not exist.
+
+        note: this function is not supported fully. only pw91 is
+        imported now. Changing the xc at this point results in loading
+        a nearly empty database, and I have not thought about how to
+        resolve that
+        '''
+        
+        if xc == 'PW91' or xc is None:
+            from pw91_psp import defaultpseudopotentials
+        else:
+            log.warn('PW91 pseudopotentials are being used!')
+            #todo build other xc psp databases
+            from pw91_psp import defaultpseudopotentials
+
+        self.psp = defaultpseudopotentials
+
+    def _set_frame_number(self, frame=None):
+        'set framenumber in the netcdf file'
+        
+        if frame is None:
+            nc = netCDF(self.nc, 'r')
+            if 'TotalEnergy' in nc.variables:
+                frame = nc.variables['TotalEnergy'].shape[0]
+                # make sure the last energy is reasonable. Sometime
+                # the field is empty if the calculation ran out of
+                # walltime for example. Empty values get returned as
+                # 9.6E36.  Dacapos energies should always be negative,
+                # so if the energy is > 1E36, there is definitely
+                # something wrong and a restart is required.
+                if nc.variables.get('TotalEnergy', None)[-1] > 1E36:
+                    log.warn("Total energy > 1E36. NC file is incomplete. \
+                    calc.restart required")
+                    self.restart()
+            else:
+                frame = 1
+            nc.close()
+            log.info("Current frame number is: %i" % (frame-1))
+        self._frame = frame-1  #netCDF starts counting with 1
+
+    def _increment_frame(self):
+        'increment the framenumber'
+        
+        log.debug('incrementing frame')
+        self._frame += 1
+
+    def set_pw(self, pw):
+        '''set the planewave cutoff.
+
+        :Parameters:
+
+         pw : integer
+           the planewave cutoff in eV
+           
+        this function checks to make sure the density wave cutoff is
+        greater than or equal to the planewave cutoff.'''
+        
+        nc = netCDF(self.nc, 'a')
+        if 'PlaneWaveCutoff' in nc.variables:
+            vpw = nc.variables['PlaneWaveCutoff']
+            vpw.assignValue(pw)
+        else:
+            vpw = nc.createVariable('PlaneWaveCutoff', 'd', ('dim1',))
+            vpw.assignValue(pw)
+
+        if 'Density_WaveCutoff' in nc.variables:
+            vdw = nc.variables['Density_WaveCutoff']
+            dw = vdw.getValue()
+            if pw > dw:
+                vdw.assignValue(pw) #make them equal
+        else:
+            vdw = nc.createVariable('Density_WaveCutoff', 'd', ('dim1',))
+            vdw.assignValue(pw) 
+        nc.close()
+        self.restart() #nc dimension change for number_plane_Wave dimension
+        self.set_status('new')
+        self.ready = False
+
+    def set_dw(self, dw):
+        '''set the density wave cutoff energy.
+
+        :Parameters:
+
+          dw : integer
+            the density wave cutoff
+
+        The function checks to make sure it is not less than the
+        planewave cutoff.
+
+        Density_WaveCutoff describes the kinetic energy neccesary to
+        represent a wavefunction associated with the total density,
+        i.e. G-vectors for which $\vert G\vert^2$ $<$
+        4*Density_WaveCutoff will be used to describe the total
+        density (including augmentation charge and partial core
+        density). If Density_WaveCutoff is equal to PlaneWaveCutoff
+        this implies that the total density is as soft as the
+        wavefunctions described by the kinetic energy cutoff
+        PlaneWaveCutoff. If a value of Density_WaveCutoff is specified
+        (must be larger than or equal to PlaneWaveCutoff) the program
+        will run using two grids, one for representing the
+        wavefunction density (softgrid_dim) and one representing the
+        total density (hardgrid_dim). If the density can be
+        reprensented on the same grid as the wavefunction density
+        Density_WaveCutoff can be chosen equal to PlaneWaveCutoff
+        (default).
+        '''
+
+        pw = self.get_pw()
+        if pw > dw:
+            log.warn('Planewave cutoff %i is greater \
+than density cutoff %i' % (pw, dw))
+        
+        ncf = netCDF(self.nc, 'a')
+        if 'Density_WaveCutoff' in ncf.variables:
+            vdw = ncf.variables['Density_WaveCutoff']
+            vdw.assignValue(dw)
+        else:
+            vdw = ncf.createVariable('Density_WaveCutoff', 'i', ('dim1',))
+            vdw.assignValue(dw)
+        ncf.close()
+        self.restart() #nc dimension change
+        self.set_status('new')
+        self.ready = False
+
+    def set_xc(self, xc):
+        '''Set the self-consistent exchange-correlation functional
+
+        :Parameters:
+
+         xc : string
+           Must be one of 'PZ', 'VWN', 'PW91', 'PBE', 'revPBE', 'RPBE'
+
+        Selects which density functional to use for
+        exchange-correlation when performing electronic minimization
+        (the electronic energy is minimized with respect to this
+        selected functional) Notice that the electronic energy is also
+        evaluated non-selfconsistently by DACAPO for other
+        exchange-correlation functionals Recognized options :
+
+        * "PZ" (Perdew Zunger LDA-parametrization)
+        * "VWN" (Vosko Wilk Nusair LDA-parametrization)
+        * "PW91" (Perdew Wang 91 GGA-parametrization)
+        * "PBE" (Perdew Burke Ernzerhof GGA-parametrization)
+        * "revPBE" (revised PBE/1 GGA-parametrization)
+        * "RPBE" (revised PBE/2 GGA-parametrization)
+
+        option "PZ" is not allowed for spin polarized
+        calculation; use "VWN" instead.
+        '''
+        nc = netCDF(self.nc, 'a')
+        v = 'ExcFunctional'
+        if v in nc.variables:
+            nc.variables[v][:] = np.array('%7s' % xc, 'c') 
+        else:
+            vxc = nc.createVariable('ExcFunctional', 'c', ('dim7',))
+            vxc[:] = np.array('%7s' % xc, 'c')
+        nc.close()
+        self.set_status('new')
+        self.ready = False    
+
+    def set_nbands(self, nbands=None):
+        '''Set the number of bands. a few unoccupied bands are
+        recommended.
+
+        :Parameters:
+
+          nbands : integer
+            the number of bands.
+            
+        if nbands = None the function returns with nothing done. At
+        calculate time, if there are still no bands, they will be set
+        by:
+
+        the number of bands is calculated as
+        $nbands=nvalence*0.65 + 4$
+        '''
+        if nbands is None:
+            return
+
+        self.delete_ncattdimvar(self.nc,
+                                ncdims=['number_of_bands'],
+                                ncvars=[])
+                       
+        nc = netCDF(self.nc, 'a')
+        v = 'ElectronicBands'
+        if v in nc.variables:
+            vnb = nc.variables[v]
+        else:
+            vnb = nc.createVariable('ElectronicBands', 'c', ('dim1',))
+            
+        vnb.NumberOfBands = nbands
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def set_kpts(self, kpts):
+        '''
+        set the kpt grid.
+
+        Parameters:
+
+        kpts: (n1,n2,n3) or [k1,k2,k3,...] or one of these
+        chadi-cohen sets:
+         
+        * cc6_1x1
+        * cc12_2x3
+        * cc18_sq3xsq3
+        * cc18_1x1
+        * cc54_sq3xsq3
+        * cc54_1x1
+        * cc162_sq3xsq3
+        * cc162_1x1
+        
+        (n1,n2,n3) creates an n1 x n2 x n3 monkhorst-pack grid,
+        [k1,k2,k3,...] creates a kpt-grid based on the kpoints
+        defined in k1,k2,k3,...
+        
+        There is also a possibility to have Dacapo (fortran) create
+        the Kpoints in chadi-cohen or monkhorst-pack form. To do this
+        you need to set the KpointSetup.gridtype attribute, and
+        KpointSetup.
+
+        KpointSetup = [3,0,0]
+        KpointSetup.gridtype = 'ChadiCohen'
+
+        KpointSetup(1) 	Chadi-Cohen k-point set
+        1 	6 k-points 1x1
+        2 	18-kpoints sqrt(3)*sqrt(3)
+        3 	18-kpoints 1x1
+        4 	54-kpoints sqrt(3)*sqrt(3)
+        5 	54-kpoints 1x1
+        6 	162-kpoints 1x1
+        7 	12-kpoints 2x3
+        8 	162-kpoints 3xsqrt 3
+
+        or
+        KpointSetup = [4,4,4]
+        KpointSetup.gridtype = 'MonkhorstPack'
+        we do not use this functionality.
+        '''
+
+        #chadi-cohen
+        if isinstance(kpts, str):
+            exec('from ase.dft.kpoints import %s' % kpts)
+            listofkpts = eval(kpts)
+            gridtype = kpts #stored in ncfile
+            #uc = self.get_atoms().get_cell()
+            #listofkpts = np.dot(ccgrid,np.linalg.inv(uc.T))
+
+        #monkhorst-pack grid
+        if np.array(kpts).shape == (3,):
+            from ase.dft.kpoints import monkhorst_pack
+            N1, N2, N3 = kpts
+            listofkpts = monkhorst_pack((N1, N2, N3))
+            gridtype = 'Monkhorst-Pack %s' % str(tuple(kpts))
+
+        #user-defined list is provided
+        if len(np.array(kpts).shape) == 2:
+            listofkpts = kpts
+            gridtype = 'user_defined_%i_kpts' % len(kpts)  #stored in ncfile
+                    
+        nbzkpts = len(listofkpts)
+
+        #we need to get dimensions stored temporarily so
+        #we can delete all dimensions and variables associated with
+        #kpoints before we save them back out.
+        nc2 = netCDF(self.nc, 'r')
+        ncdims = nc2.dimensions
+        nc2.close()
+
+        if 'number_BZ_kpoints' in ncdims:
+            self.delete_ncattdimvar(self.nc,
+                                    ncdims=['number_plane_waves',
+                                            'number_BZ_kpoints',
+                                            'number_IBZ_kpoints'])
+
+        # now define dim and var
+        nc = netCDF(self.nc, 'a')
+        nc.createDimension('number_BZ_kpoints', nbzkpts)
+        bv = nc.createVariable('BZKpoints', 'd', ('number_BZ_kpoints',
+                                                  'dim3'))
+
+        bv[:] = listofkpts
+        bv.gridtype = gridtype
+        nc.sync()
+        nc.close()
+
+        log.debug('kpts = %s' % str(self.get_kpts()))
+        
+        self.set_status('new')
+        self.ready = False
+
+    def atoms_are_equal(self, atoms):
+        '''
+        comparison of atoms to self.atoms using tolerances to account
+        for float/double differences and float math.
+        '''
+        
+        TOL = 1.0e-6 #angstroms
+
+        a = self.atoms.arrays
+        b = atoms.arrays
+
+        #match number of atoms in cell
+        lenmatch = len(atoms) == len(self.atoms)
+        if lenmatch is not True:
+            return False #the next two comparisons fail in this case.
+        
+        #match positions in cell
+        posmatch = (abs(a['positions'] - b['positions']) <= TOL).all()
+        #match cell
+        cellmatch = (abs(self.atoms.get_cell()
+                         - atoms.get_cell()) <= TOL).all()
+        
+        if lenmatch and posmatch and cellmatch:
+            return True
+        else:
+            return False
+                
+    def set_atoms(self, atoms):
+        '''attach an atoms to the calculator and update the ncfile
+
+        :Parameters:
+
+          atoms
+            ASE.Atoms instance
+          
+        '''
+
+        log.debug('setting atoms to: %s' % str(atoms))
+        
+        if hasattr(self, 'atoms') and self.atoms is not None:
+            #return if the atoms are the same. no change needs to be made
+            if self.atoms_are_equal(atoms):
+                log.debug('No change to atoms in set_atoms, returning')
+                return
+            
+            # some atoms already exist. Test if new atoms are
+            # different from old atoms.
+            # this is redundant
+            if atoms != self.atoms:
+                # the new atoms are different from the old ones. Start
+                # a new frame.
+                log.debug('atoms != self.atoms, incrementing')
+                self._increment_frame()
+                
+        self.atoms = atoms.copy()
+        self.ready = False
+        log.debug('self.atoms = %s' % str(self.atoms))
+
+    def set_ft(self, ft):
+        '''set the Fermi temperature for occupation smearing
+
+        :Parameters:
+
+          ft : float
+            Fermi temperature in kT (eV)
+
+        Electronic temperature, corresponding to gaussian occupation
+        statistics. Device used to stabilize the convergence towards
+        the electronic ground state. Higher values stabilizes the
+        convergence. Values in the range 0.1-1.0 eV are recommended,
+        depending on the complexity of the Fermi surface (low values
+        for d-metals and narrow gap semiconducters, higher for free
+        electron-like metals).
+        '''
+        
+        nc = netCDF(self.nc, 'a')
+        v = 'ElectronicBands'
+        if v in nc.variables:
+            vnb = nc.variables[v]
+        else:
+            vnb = nc.createVariable('ElectronicBands', 'c', ('dim1',))
+
+        vnb.OccupationStatistics_FermiTemperature = ft
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def set_status(self, status):
+        '''set the status flag in the netcdf file
+
+        :Parameters:
+
+          status : string
+            status flag, e.g. 'new', 'finished'
+        '''
+        
+        nc = netCDF(self.nc, 'a')
+        nc.status = status
+        nc.sync()
+        nc.close()
+        log.debug('set status to %s' % status)
+
+    def get_spinpol(self):
+        'Returns the spin polarization setting, either True or False'
+        
+        nc = netCDF(self.nc, 'r')
+        v = 'ElectronicBands'
+        if v in nc.variables:
+            vnb = nc.variables[v]
+            if hasattr(vnb, 'SpinPolarization'):
+                spinpol = vnb.SpinPolarization
+            else:
+                spinpol = 1
+        else:
+            spinpol = 1
+
+        nc.close()
+        if spinpol == 1:
+            return False
+        else:
+            return True
+
+    def set_spinpol(self, spinpol=False):
+        '''set Spin polarization.
+
+        :Parameters:
+
+         spinpol : Boolean
+           set_spinpol(True)  spin-polarized.
+           set_spinpol(False) no spin polarization, default
+
+        Specify whether to perform a spin polarized or unpolarized
+        calculation.
+        '''
+        
+        nc = netCDF(self.nc, 'a')
+        v = 'ElectronicBands'
+        if v in nc.variables:
+            vnb = nc.variables[v]
+        else:
+            vnb = nc.createVariable('ElectronicBands', 'c', ('dim1',))
+
+        if spinpol is True:
+            vnb.SpinPolarization = 2
+        else:
+            vnb.SpinPolarization = 1
+
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def set_fixmagmom(self, fixmagmom=None):
+        '''set a fixed magnetic moment for a spin polarized calculation
+
+        :Parameters:
+
+          fixmagmom : float
+            the magnetic moment of the cell in Bohr magnetons
+        '''
+        
+        if fixmagmom is None:
+            return
+        
+        nc = netCDF(self.nc,'a')
+        v = 'ElectronicBands'
+        if v in nc.variables:
+            vnb = nc.variables[v]
+        else:
+            vnb = nc.createVariable('ElectronicBands', 'c', ('dim1',))
+
+        vnb.SpinPolarization = 2 #You must want spin-polarized
+        vnb.FixedMagneticMoment = fixmagmom
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def get_fixmagmom(self):
+        'returns the value of FixedMagneticMoment'
+        
+        nc = netCDF(self.nc,'r')
+        if 'ElectronicBands' in nc.variables:
+            v = nc.variables['ElectronicBands']
+            if hasattr(v,'FixedMagneticMoment'):
+                fixmagmom = v.FixedMagneticMoment
+            else:
+                fixmagmom = None
+        else:
+            fixmagmom = None
+        nc.close()
+        return fixmagmom
+            
+    def set_calculate_stress(self, stress=True):
+        '''Turn on stress calculation
+
+        :Parameters:
+
+          stress : boolean
+            set_calculate_stress(True) calculates stress
+            set_calculate_stress(False) do not calculate stress
+        '''
+        
+        nc = netCDF(self.get_nc(),'a')
+        vs = 'NetCDFOutputControl'
+        if vs in nc.variables:
+            v = nc.variables[vs]
+        else:
+            v = nc.createVariable('NetCDFOutputControl', 'c', ('dim1',))
+
+        if stress is True:
+            v.PrintTotalStress = 'Yes'
+        else:
+            v.PrintTotalStress = 'No'
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def set_nc(self, nc='out.nc'):
+        '''
+        set filename for the netcdf and text output for this calculation
+
+        :Parameters:
+
+          nc : string
+            filename for netcdf file
+                
+        if the ncfile attached to the calculator is changing, the old
+        file will be copied to the new file if it doesn not exist so
+        that all the calculator details are preserved. Otherwise, the 
+
+        if the ncfile does not exist, it will get initialized.
+
+        the text file will have the same basename as the ncfile, but
+        with a .txt extension.
+        '''
+        
+        #the first time this is called, there may be no self.nc defined
+        if not hasattr(self, 'nc'):
+            self.nc = nc    
+            
+        #check if the name is changing and if so, copy the old ncfile
+        #to the new one.  This is necessary to ensure all the
+        #calculator details are copied over. if the file already
+        #exists we use the contents of the existing file
+        if nc != self.nc and not os.path.exists(nc):
+            log.debug('copying %s to %s' % (self.nc, nc))
+            #import shutil
+            #shutil.copy(self.nc,nc)
+            base = os.path.split(nc)[0]
+            if not os.path.isdir(base) and base is not '':
+                os.makedirs(base)
+            status = os.system('cp %s %s' % (self.nc, nc))
+            if status != 0:
+                raise Exception, 'Copying ncfile failed.'
+            self.nc = nc
+        
+        elif os.path.exists(nc):
+            self._set_frame_number()
+            self.set_psp_database()
+            self.atoms = self.read_only_atoms(nc)
+            self.nc = nc
+            self.update_input_parameters()
+                
+        #I always want the text file set based on the ncfile
+        #and I never want to set this myself.
+        base = os.path.splitext(self.nc)[0]
+        self.txt = base + '.txt'
+                 
+    def set_psp(self,
+                sym=None,
+                z=None,
+                psp=None):
+        '''
+        set the pseudopotential file for a species or an atomic number.
+
+        :Parameters:
+
+         sym : string
+           chemical symbol of the species
+
+          z : integer
+            the atomic number of the species
+
+          psp : string
+            filename of the pseudopotential
+
+        
+        you can only set sym or z.
+
+        examples::
+        
+          set_psp('N',psp='pspfile')
+          set_psp(z=6,psp='pspfile')
+        '''
+        log.debug(str([sym, z, psp]))
+        if (sym, z, psp) == (None, None, None):
+            return
+        
+        if (sym is None and z is not None):
+            from ase.data import chemical_symbols
+            sym = chemical_symbols[z]
+        elif (sym is not None and z is None):
+            pass
+        else:
+            raise Exception, 'You can only specify Z or sym!'
+
+        if not hasattr(self, 'psp'):
+            self.set_psp_database()
+
+        #only make change if needed
+        if sym not in self.psp:
+            self.psp[sym] = psp
+            self.ready = False
+            self.set_status('new')
+        elif self.psp[sym] != psp:
+            self.psp[sym] = psp
+            self.ready = False
+            self.set_status('new')
+
+        if not self.ready:
+            #now we update the netcdf file
+            ncf = netCDF(self.nc, 'a')
+            vn = 'AtomProperty_%s' % sym
+            if vn not in ncf.variables:
+                p = ncf.createVariable(vn, 'c', ('dim20',))
+            else:
+                p = ncf.variables[vn]
+
+            ppath = self.get_psp(sym=sym)
+            p.PspotFile = ppath
+            ncf.close()
+
+    def get_pseudopotentials(self):
+        'get pseudopotentials set for atoms attached to calculator'
+        
+        if self.atoms is None:
+            return None
+        
+        psp = {}
+        for atom in self.atoms:
+            psp[atom.symbol] = self.psp[atom.symbol]
+        return psp
+
+    def get_symmetry(self):
+        '''return the type of symmetry used'''
+        
+        nc = netCDF(self.nc, 'r')
+        if 'UseSymmetry' in nc.variables:
+            sym = string.join(nc.variables['UseSymmetry'][:],'').strip()
+        else:
+            sym = None      
+        nc.close()
+        if sym in ['Off', None]:
+            return False
+        elif sym == 'Maximum':
+            return True
+        else:
+            raise Exception, 'Type of symmetry not recognized: %s' % sym
+       
+    def set_symmetry(self, val=False):
+        '''set how symmetry is used to reduce k-points
+
+        :Parameters:
+
+         val : Boolean
+           set_sym(True) Maximum symmetry is used
+           set_sym(False) No symmetry is used
+
+        This variable controls the if and how DACAPO should attempt
+        using symmetry in the calculation. Imposing symmetry generally
+        speeds up the calculation and reduces numerical noise to some
+        extent. Symmetry should always be applied to the maximum
+        extent, when ions are not moved. When relaxing ions, however,
+        the symmetry of the equilibrium state may be lower than the
+        initial state. Such an equilibrium state with lower symmetry
+        is missed, if symmetry is imposed. Molecular dynamics-like
+        algorithms for ionic propagation will generally not break the
+        symmetry of the initial state, but some algorithms, like the
+        BFGS may break the symmetry of the initial state. Recognized
+        options:
+
+        "Off": No symmetry will be imposed, apart from time inversion
+        symmetry in recipical space. This is utilized to reduce the
+        k-point sampling set for Brillouin zone integration and has no
+        influence on the ionic forces/motion.
+
+        "Maximum": DACAPO will look for symmetry in the supplied
+        atomic structure and extract the highest possible symmetry
+        group. During the calculation, DACAPO will impose the found
+        spatial symmetry on ionic forces and electronic structure,
+        i.e. the symmetry will be conserved during the calculation.
+        '''
+        
+        if val:
+            symval = 'Maximum'
+        else:
+            symval = 'Off'
+        
+        ncf = netCDF(self.get_nc(), 'a')
+        if 'UseSymmetry' not in ncf.variables:
+            sym = ncf.createVariable('UseSymmetry', 'c', ('dim7',))
+        else:
+            sym = ncf.variables['UseSymmetry']
+            
+        sym[:] = np.array('%7s' % symval, 'c')
+        ncf.sync()
+        ncf.close()
+        self.set_status('new')
+        self.ready = False
+
+    def set_extracharge(self, val):
+        '''add extra charge to unit cell
+
+        :Parameters:
+
+          val : float
+            extra electrons to add or subtract from the unit cell
+
+        Fixed extra charge in the unit cell (i.e. deviation from
+        charge neutrality). This assumes a compensating, positive
+        constant backgound charge (jellium) to forge overall charge
+        neutrality.
+        '''
+        
+        nc = netCDF(self.get_nc(), 'a')
+        if 'ExtraCharge' in nc.variables:
+            v = nc.variables['ExtraCharge']
+        else:
+            v = nc.createVariable('ExtraCharge', 'd', ('dim1',))
+
+        v.assignValue(val)
+        nc.sync()
+        nc.close()
+
+    def get_extracharge(self):
+        'Return the extra charge set in teh calculator'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        if 'ExtraCharge' in nc.variables:
+            v = nc.variables['ExtraCharge']
+            exchg = v.getValue()
+        else:
+            exchg = None
+        nc.close()
+        return exchg
+
+    def get_extpot(self):
+        'return the external potential set in teh calculator'
+        
+        nc = netCDF(self.get_nc(), 'a')
+        if 'ExternalPotential' in nc.variables:
+            v = nc.variables['ExternalPotential']
+            extpot = v[:]
+        else:
+            extpot = None
+
+        nc.close()
+        return extpot
+    
+    def set_extpot(self, potgrid):
+        '''add external potential of value
+
+        see this link before using this
+        https://listserv.fysik.dtu.dk/pipermail/campos/2003-August/000657.html
+        
+        :Parameters:
+
+          potgrid : np.array with shape (nx,ny,nz)
+            the shape must be the same as the fft soft grid
+            the value of the potential to add
+
+        
+        you have to know both of the fft grid dimensions ahead of time!
+        if you know what you are doing, you can set the fft_grid you want
+        before hand with:
+        calc.set_fftgrid((n1,n2,n3))
+        '''
+        
+        nc = netCDF(self.get_nc(), 'a')
+        if 'ExternalPotential' in nc.variables:
+            v = nc.variables['ExternalPotential']
+        else:
+            # I assume here you have the dimensions of potgrid correct
+            # and that the soft and hard grids are the same. 
+            # if softgrid is defined, Dacapo requires hardgrid to be
+            # defined too.
+            s1, s2, s3 = potgrid.shape
+            if 'softgrid_dim1' not in nc.dimensions:
+                nc.createDimension('softgrid_dim1', s1)
+                nc.createDimension('softgrid_dim2', s2)
+                nc.createDimension('softgrid_dim3', s3)
+                nc.createDimension('hardgrid_dim1', s1)
+                nc.createDimension('hardgrid_dim2', s2)
+                nc.createDimension('hardgrid_dim3', s3)
+                
+            v = nc.createVariable('ExternalPotential',
+                                  'd',
+                                  ('softgrid_dim1',
+                                   'softgrid_dim2',
+                                   'softgrid_dim3',))
+        v[:] = potgrid
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+        
+    def set_fftgrid(self, soft=None, hard=None):
+        '''
+        sets the dimensions of the FFT grid to be used
+
+        :Parameters:
+
+          soft : (n1,n2,n3) integers
+            make a n1 x n2 x n3 grid
+
+          hard : (n1,n2,n3) integers
+            make a n1 x n2 x n3 grid
+
+        
+        >>> calc.set_fftgrid(soft=[42,44,46])
+        sets the soft and hard grid dimensions to 42,44,46
+
+        >>> calc.set_fftgrid(soft=[42,44,46],hard=[80,84,88])
+        sets the soft grid dimensions to 42,44,46 and the hard
+        grid dimensions to 80,84,88
+        
+        These are the fast FFt grid numbers listed in fftdimensions.F
+        
+        data list_of_fft /2,  4,  6,  8, 10, 12, 14, 16, 18, 20, &
+        22,24, 28, 30,32, 36, 40, 42, 44, 48, &
+        56,60, 64, 66, 70, 72, 80, 84, 88, 90, &
+        96,108,110,112,120,126,128,132,140,144,154, &
+        160,168,176,180,192,198,200, &
+        216,240,264,270,280,288,324,352,360,378,384,400,432, &
+        450,480,540,576,640/
+        
+        otherwise you will get some errors from mis-dimensioned variables.
+        
+        this is usually automatically set by Dacapo.
+        '''
+        
+        if soft is not None:
+            self.delete_ncattdimvar(self.nc,
+                                    ncdims=['softgrid_dim1',
+                                            'softgrid_dim2',
+                                            'softgrid_dim3'
+                                            ],
+                                    ncvars=[])
+        
+            
+            nc = netCDF(self.get_nc(), 'a')
+            nc.createDimension('softgrid_dim1', soft[0])
+            nc.createDimension('softgrid_dim2', soft[1])
+            nc.createDimension('softgrid_dim3', soft[2])
+            nc.sync()
+            nc.close()
+
+            if hard is None:
+                hard = soft
+
+        if hard is not None:
+            self.delete_ncattdimvar(self.nc,
+                                    ncdims=['hardgrid_dim1',
+                                            'hardgrid_dim2',
+                                            'hardgrid_dim3'
+                                            ],
+                                    ncvars=[])
+            nc = netCDF(self.get_nc(),'a')
+            nc.createDimension('hardgrid_dim1', hard[0])
+            nc.createDimension('hardgrid_dim2', hard[1])
+            nc.createDimension('hardgrid_dim3', hard[2])
+            nc.sync()
+            nc.close()
+
+        self.set_status('new')
+        self.ready = False
+
+    def get_ascii_debug(self):
+        'Return the debug settings in Dacapo'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        if 'PrintDebugInfo' in nc.variables:
+            v = nc.variables['PrintDebugInfo']
+            debug = string.join(v[:], '')
+        else:
+            debug = None
+        nc.close()
+        return debug
+    
+    def set_ascii_debug(self, level):
+        '''set the debug level for Dacapo
+
+        :Parameters:
+        
+          level : string
+            one of 'Off', 'MediumLevel', 'HighLevel'
+        '''
+        
+        nc = netCDF(self.get_nc(), 'a')
+        if 'PrintDebugInfo' in nc.variables:
+            v = nc.variables['PrintDebugInfo']
+        else:
+            if 'dim20' not in nc.dimensions:
+                nc.createDimension('dim20', 20)
+            v = nc.createVariable('PrintDebugInfo', 'c', ('dim20',))
+
+        v[:] = np.array('%20s' % level, dtype='c')
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def get_ncoutput(self):
+        'returns the control variables for the ncfile'
+        
+        nc = netCDF(self.get_nc(), 'a')
+        if 'NetCDFOutputControl' in nc.variables:
+            v = nc.variables['NetCDFOutputControl']
+            ncoutput = {}
+            if hasattr(v, 'PrintWaveFunction'):
+                ncoutput['wf'] = v.PrintWaveFunction
+            if hasattr(v, 'PrintChargeDensity'):
+                ncoutput['cd'] = v.PrintChargeDensity
+            if hasattr(v, 'PrintEffPotential'):
+                ncoutput['efp'] = v.PrintEffPotential
+            if hasattr(v, 'PrintElsPotential'):
+                ncoutput['esp'] = v.PrintElsPotential
+        else:
+            ncoutput = None
+        nc.close()
+        return ncoutput
+        
+    def set_ncoutput(self,
+                     wf=None,
+                     cd=None,
+                     efp=None,
+                     esp=None):
+        '''set the output of large variables in the netcdf output file
+
+        :Parameters:
+
+          wf : string
+            controls output of wavefunction. values can
+            be 'Yes' or 'No'
+
+          cd : string
+            controls output of charge density. values can
+            be 'Yes' or 'No'
+
+          efp : string
+            controls output of effective potential. values can
+            be 'Yes' or 'No'
+
+          esp : string
+            controls output of electrostatic potential. values can
+            be 'Yes' or 'No'
+        '''
+        nc = netCDF(self.get_nc(), 'a')
+        if 'NetCDFOutputControl' in nc.variables:
+            v = nc.variables['NetCDFOutputControl']
+        else:
+            v = nc.createVariable('NetCDFOutputControl', 'c', ())
+
+        if wf is not None:
+            v.PrintWaveFunction = wf
+        if cd is not None:
+            v.PrintChargeDensity = cd
+        if efp is not None:
+            v.PrintEffPotential = efp
+        if esp is not None:
+            v.PrintElsPotential = esp
+
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def get_ados(self, **kwargs):
+        '''
+        attempt at maintaining backward compatibility with get_ados
+        returning data
+
+        Now when we call calc.get_ados() it will return settings,
+
+        and calc.get_ados(atoms=[],...) should return data
+
+        '''
+        
+        if len(kwargs) != 0:
+            return self.get_ados_data(**kwargs)
+        
+        nc = netCDF(self.get_nc(),'r')
+        if 'PrintAtomProjectedDOS' in nc.variables:
+            v = nc.variables['PrintAtomProjectedDOS']
+            ados = {}
+            if hasattr(v, 'EnergyWindow'):
+                ados['energywindow'] = v.EnergyWindow
+            if hasattr(v, 'EnergyWidth'):
+                ados['energywidth'] = v.EnergyWidth
+            if hasattr(v, 'NumberEnergyPoints'):
+                ados['npoints'] = v.NumberEnergyPoints
+            if hasattr(v, 'CutoffRadius'):
+                ados['cutoff'] = v.CutoffRadius
+        else:
+            ados = None
+
+        nc.close()
+        return ados
+        
+    def set_ados(self,
+                 energywindow=(-15,5),
+                 energywidth=0.2,
+                 npoints=250,
+                 cutoff=1.0):
+        '''
+        setup calculation of atom-projected density of states
+
+        :Parameters:
+
+          energywindow : (float, float)
+            sets (emin,emax) in eV referenced to the Fermi level
+
+          energywidth : float
+            the gaussian used in smearing
+
+          npoints : integer
+            the number of points to sample the DOS at
+
+          cutoff : float
+            the cutoff radius in angstroms for the integration.
+        '''
+        
+        nc = netCDF(self.get_nc(), 'a')
+        if 'PrintAtomProjectedDOS' in nc.variables:
+            v = nc.variables['PrintAtomProjectedDOS']
+        else:
+            v = nc.createVariable('PrintAtomProjectedDOS', 'c', ())
+
+        v.EnergyWindow = energywindow
+        v.EnergyWidth  = energywidth
+        v.NumberEnergyPoints = npoints
+        v.CutoffRadius = cutoff
+
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def get_mdos(self):
+        'return multicentered projected dos parameters'
+        nc = netCDF(self.get_nc(),'r')
+
+        mdos = {}
+        
+        if 'MultiCenterProjectedDOS' in nc.variables:
+            v = nc.variables['MultiCenterProjectedDOS']
+            mdos['energywindow'] = v.EnergyWindow
+            mdos['energywidth'] = v.EnergyWidth
+            mdos['numberenergypoints'] = v.NumberEnergyPoints
+            mdos['cutoffradius'] = v.CutoffRadius
+            mdos['mcenters'] = eval(v.mcenters)
+                
+        nc.close()
+
+        return mdos
+
+    def get_mdos_data(self,
+                      spin=0,
+                      cutoffradius='infinite'):
+        '''returns data from multicentered projection
+
+
+        returns (mdos, rotmat)
+
+        the rotation matrices are retrieved from the text file. I am
+        not sure what you would do with these, but there was a note
+        about them in the old documentation so I put the code to
+        retrieve them here. the syntax for the return value is:
+        rotmat[atom#][label] returns the rotation matrix for the
+        center on the atom# for label. I do not not know what the
+        label refers to.
+        '''
+        
+        if self.calculation_required():
+            self.calculate()
+        
+        nc = netCDF(self.get_nc(),'r')
+        icut = 1 #short
+        if cutoffradius == "infinite":
+            icut = 0
+            
+        #var = nc.variables['MultiCenterProjectedDOS']
+        integrated = nc.variables['MultiCenterProjectedDOS_IntegratedDOS'][:]
+        tz = 'MultiCenterProjectedDOS_EnergyResolvedDOS'
+        energyresolved = nc.variables[tz][:]
+        energygrid = nc.variables['MultiCenterProjectedDOS_EnergyGrid'][:]
+
+        number_of_multicenters  = integrated.shape[0]
+        #number_of_cutoff = integrated.shape[1]
+        #number_of_spin = integrated.shape[2]
+
+        multicenterprojections = []
+        for multicenter in range(number_of_multicenters): 
+            #orbitals = var[multicenter]
+            energyresolveddata = energyresolved[multicenter, icut, spin, :]
+            #integrateddata     = integrated[multicenter, icut, spin]
+            multicenterprojections.append([energygrid, energyresolveddata])
+
+        log.info('Found %d multicenters' % len(multicenterprojections))
+        nc.close()
+
+        #now parse the text file for the rotation matrices
+        rot_mat_lines = []
+        txt = self.get_txt()
+        if os.path.exists(txt):
+            f = open(txt,'r')
+            for line in f:
+                if 'MUL: Rmatrix' in line:
+                    rot_mat_lines.append(line)
+            f.close()
+
+            rotmat = []
+            for line in rot_mat_lines:
+                fields = line.split()
+                novl = int(fields[2])
+                ncen = int(fields[3])
+                row = [float(x) for x in fields[4:]]
+
+                try:
+                    rotmat[novl-1][ncen-1].append(row)
+                except IndexError:
+                    try:
+                        rotmat[novl-1].append([])
+                        rotmat[novl-1][ncen-1].append(row)
+                    except IndexError:
+                        rotmat.append([])
+                        rotmat[novl-1].append([])
+                    rotmat[novl-1][ncen-1].append(row)
+        else:
+            rotmat = None
+                    
+        return (multicenterprojections, rotmat)
+            
+    def set_mdos(self,
+                 mcenters=None,
+                 energywindow=(-15,5),
+                 energywidth=0.2,
+                 numberenergypoints=250,
+                 cutoffradius=1.0):
+        '''Setup multicentered projected DOS.
+
+        mcenters
+           a list of tuples containing (atom#,l,m,weight)
+           (0,0,0,1.0) specifies (atom 0, l=0, m=0, weight=1.0) an s orbital
+           on atom 0
+           
+           (1,1,1,1.0) specifies (atom 1, l=1, m=1, weight=1.0) a p orbital
+           with m = +1 on atom 0
+
+           l=0 s-orbital
+           l=1 p-orbital
+           l=2 d-orbital
+
+           m in range of ( -l ... 0 ... +l )
+
+           The direction cosines for which the spherical harmonics are
+           set up are using the next different atom in the list
+           (cyclic) as direction pointer, so the z-direction is chosen
+           along the direction to this next atom. At the moment the
+           rotation matrices is only given in the text file, you can
+           use grep'MUL: Rmatrix' out_o2.txt to get this information.
+           
+        adapated from old MultiCenterProjectedDOS.py
+        '''
+        if mcenters is None:
+            return
+        
+        nc = netCDF(self.get_nc(), 'a')
+        
+        _listofmcenters_ = mcenters
+        
+        # get number of multi centers
+        ncenters = len(_listofmcenters_)
+        # get max number of orbitals any center 
+        max_orbitals = max(map(len, _listofmcenters_))
+
+        mmatrix = np.zeros([ncenters, max_orbitals, 4], np.float)
+        ncenter = 0
+        for multicenter in _listofmcenters_: 
+            norbital = 0
+            for orbital in multicenter: 
+                mmatrix[ncenter, norbital] = orbital 
+                norbital = norbital + 1 
+
+            # signal that this multicenter contains less than
+            # max_orbital orbitals
+            if len(multicenter) < max_orbitals: 
+                mmatrix[ncenter, len(multicenter):max_orbitals] = (-1.0, 0,
+                                                                   0, 0)
+
+            ncenter = ncenter + 1
+
+        nc.createDimension('max_orbitals', max_orbitals)
+        nc.createDimension('number_of_multicenters', ncenters)
+
+        if 'MultiCenterProjectedDOS' in nc.variables:
+            v = nc.variables['MultiCenterProjectedDOS']
+        else:
+            v = nc.createVariable('MultiCenterProjectedDOS',
+                                  'd',
+                                  ('number_of_multicenters',
+                                   'max_orbitals',
+                                   'dim4'))
+
+        v.EnergyWindow = energywindow
+        v.EnergyWidth = energywidth
+        v.NumberEnergyPoints = numberenergypoints
+        v.CutoffRadius = cutoffradius
+
+        #this is kind of hacky, but it is needed for get_mdos so you
+        #can tell if the input is changed.
+        v.mcenters = str(mcenters)
+
+        v[:] = mmatrix
+
+        nc.sync()
+        nc.close()        
+
+    def set_debug(self, debug):
+        '''
+        set debug level for python logging
+
+        debug should be an integer from 0-100 or one of the logging
+        constants like logging.DEBUG, logging.WARN, etc...
+
+        '''
+        
+        self.debug = debug
+        log.setLevel(debug)
+
+    def get_debug(self):
+        'Return the python logging level'
+        
+        return self.debug
+
+    def get_decoupling(self):
+        'return the electrostatic decoupling parameters'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        if 'Decoupling' in nc.variables:
+            v = nc.variables['Decoupling']
+            decoupling = {}
+            if hasattr(v,'NumberOfGaussians'):
+                decoupling['ngaussians'] = v.NumberOfGaussians
+            if hasattr(v,'ECutoff'):
+                decoupling['ecutoff'] = v.ECutoff
+            if hasattr(v,'WidthOfGaussian'):
+                decoupling['gausswidth'] = v.WidthOfGaussian
+        else:
+            decoupling = None
+        nc.close()
+        return decoupling
+        
+    def set_decoupling(self,
+                       ngaussians=3,
+                       ecutoff=100,
+                       gausswidth=0.35):
+        '''
+        Decoupling activates the three dimensional electrostatic
+        decoupling. Based on paper by Peter E. Bloechl: JCP 103
+        page7422 (1995).
+
+        :Parameters:
+
+          ngaussians : int
+            The number of gaussian functions per atom
+            used for constructing the model charge of the system
+
+          ecutoff : int
+            The cut off energy (eV) of system charge density in
+            g-space used when mapping constructing the model change of
+            the system, i.e. only charge density components below
+            ECutoff enters when constructing the model change.
+
+          gausswidth : float
+            The width of the Gaussians defined by
+            $widthofgaussian*1.5^(n-1)$  $n$=(1 to numberofgaussians)
+            
+        '''
+
+        nc = netCDF(self.get_nc(), 'a')
+        if 'Decoupling' in nc.variables:
+            v = nc.variables['Decoupling']
+        else:
+            v = nc.createVariable('Decoupling', 'c', ())
+
+        v.NumberOfGaussians = ngaussians
+        v.ECutoff = ecutoff
+        v.WidthOfGaussian = gausswidth
+
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def set_external_dipole(self,
+                            value,
+                            position=None):
+        '''
+        Externally imposed dipole potential. This option overwrites
+        DipoleCorrection if set. 
+
+        :Parameters:
+
+          value : float
+            units of volts
+
+          position : float
+            scaled coordinates along third unit cell direction.
+            if None, the compensation dipole layer plane in the
+            vacuum position farthest from any other atoms on both
+            sides of the slab. Do not set to 0.0.
+        '''
+        
+        var = 'ExternalDipolePotential'
+        nc = netCDF(self.get_nc(), 'a')
+        if var in nc.variables:
+            v = nc.variables[var]
+        else:
+            v = nc.createVariable('ExternalDipolePotential', 'd', ())
+        
+        v.assignValue(value)
+        if position is not None:
+            v.DipoleLayerPosition = position
+
+        nc.sync()
+        nc.close()
+        self.set_status('new')
+        self.ready = False
+
+    def get_external_dipole(self):
+        'return the External dipole settings'
+
+        var = 'ExternalDipolePotential'
+        nc = netCDF(self.get_nc(),'r')
+        if var in nc.variables:
+            v = nc.variables[var]
+            value = v.getValue()
+            if hasattr(v, 'DipoleLayerPosition'):
+                position = v.DipoleLayerPosition
+            else:
+                position = None
+
+            ed = {'value':value, 'position':position}
+        else:
+            ed = None
+        nc.close()
+        return ed
+            
+    def set_dipole(self,
+                   status=True,
+                   mixpar=0.2,
+                   initval=0.0,
+                   adddipfield=0.0,
+                   position=None):
+        '''turn on and set dipole correction scheme
+
+        :Parameters:
+
+          status : Boolean
+            True turns dipole on. False turns Dipole off
+
+          mixpar : float
+            Mixing Parameter for the the dipole correction field
+            during the electronic minimization process. If instabilities
+            occur during electronic minimization, this value may be
+            decreased.
+
+          initval : float
+            initial value to start at
+
+          adddipfield : float
+            additional dipole field to add
+            units : V/ang
+            External additive, constant electrostatic field along
+            third unit cell vector, corresponding to an external
+            dipole layer. The field discontinuity follows the position
+            of the dynamical dipole correction, i.e. if
+            DipoleCorrection:DipoleLayerPosition is set, the field
+            discontinuity is at this value, otherwise it is at the
+            vacuum position farthest from any other atoms on both
+            sides of the slab.
+
+          position : float
+            scaled coordinates along third unit cell direction.
+            If this attribute is set, DACAPO will position the
+            compensation dipole layer plane in at the provided value.
+            If this attribute is not set, DACAPO will put the compensation
+            dipole layer plane in the vacuum position farthest from any
+            other atoms on both sides of the slab. Do not set this to
+            0.0
+
+        
+        calling set_dipole() sets all default values.
+            
+        '''
+        if status == False:
+            self.delete_ncattdimvar(self.nc, ncvars=['DipoleCorrection'])
+            return
+        
+        ncf = netCDF(self.get_nc(), 'a')
+        if 'DipoleCorrection' not in ncf.variables:
+            dip = ncf.createVariable('DipoleCorrection', 'c', ())
+        else:
+            dip = ncf.variables['DipoleCorrection']
+        dip.MixingParameter = mixpar
+        dip.InitialValue = initval
+        dip.AdditiveDipoleField = adddipfield
+
+        if position is not None:
+            dip.DipoleLayerPosition = position
+            
+        ncf.sync()
+        ncf.close()
+        self.set_status('new')
+        self.ready = False
+       
+    def set_stay_alive(self, value):
+        'set the stay alive setting'
+        
+        self.delete_ncattdimvar(self.nc,
+                                ncvars=['Dynamics'])
+
+        if value in [True, False]:
+            self.stay_alive = value
+            #self._dacapo_is_running = False
+        else:
+            log.debug("stay_alive must be boolean. Value was not changed.")
+
+    def get_stay_alive(self):
+        'return the stay alive settings'
+        
+        return self.stay_alive
+
+    def get_fftgrid(self):
+        'return soft and hard fft grids'
+        
+        nc = netCDF(self.nc, 'r')
+        soft = []
+        hard = []
+        for d in [1, 2, 3]:
+            sd = 'softgrid_dim%i' % d
+            hd = 'hardgrid_dim%i' % d
+            if sd in nc.dimensions:
+                soft.append(nc.dimensions[sd])
+                hard.append(nc.dimensions[hd])
+        nc.close()
+        return ({'soft':soft,
+                 'hard':hard})
+
+    def get_kpts_type(self):
+        'return the kpt grid type'
+        
+        nc = netCDF(self.nc, 'r')
+
+        if 'BZKpoints' in nc.variables:
+            bv = nc.variables['BZKpoints']
+            if hasattr(bv, 'gridtype'):
+                kpts_type = bv.gridtype #string saved in jacapo
+            else:
+                #no grid attribute, this ncfile was created pre-jacapo
+                kpts_type = 'pre-Jacapo: %i kpts' % len(bv[:])
+        else:
+            kpts_type = 'BZKpoints not defined. [[0,0,0]] used by default.'
+
+        nc.close()
+        return kpts_type
+    
+    def get_kpts(self):
+        'return the BZ kpts'
+        nc = netCDF(self.nc, 'r')
+
+        if 'BZKpoints' in nc.variables:
+            bv = nc.variables['BZKpoints']
+            kpts = bv[:]
+        else:
+            kpts = ([0, 0, 0]) #default Gamma point used in Dacapo when
+                             #BZKpoints not defined
+
+        nc.close()
+        return kpts
+        
+    def get_nbands(self):
+        'return the number of bands used in the calculation'
+        nc = netCDF(self.nc, 'r')
+
+        if 'ElectronicBands' in nc.variables:
+            v = nc.variables['ElectronicBands']
+            if hasattr(v, 'NumberOfBands'):
+                nbands = v.NumberOfBands[0]
+            else:
+                nbands = None
+        else:
+            nbands = None
+            
+        nc.close()
+        return nbands
+    
+    def get_ft(self):
+        'return the FermiTemperature used in the calculation'
+        nc = netCDF(self.nc, 'r')
+
+        if 'ElectronicBands' in nc.variables:
+            v = nc.variables['ElectronicBands']
+            if hasattr(v, 'OccupationStatistics_FermiTemperature'):
+                ft = v.OccupationStatistics_FermiTemperature
+            else:
+                ft = None
+        else:
+            ft = None
+        nc.close()
+        return ft
+    
+    def get_dipole(self):
+        'return dictionary of parameters if the DipoleCorrection was used'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        pars = {}
+        if 'DipoleCorrection' in nc.variables:
+            v = nc.variables['DipoleCorrection']
+            pars['status'] = True
+            if hasattr(v, 'MixingParameter'):
+                pars['mixpar'] = v.MixingParameter
+            if hasattr(v, 'InitialValue'):
+                pars['initval'] = v.InitialValue
+            if hasattr(v, 'AdditiveDipoleField'):
+                pars['adddipfield'] = v.AdditiveDipoleField
+            if hasattr(v, 'DipoleLayerPosition'):
+                pars['position'] = v.DipoleLayerPosition
+            
+        else:
+            pars = False
+        nc.close()
+        return pars
+        
+    def get_pw(self):
+        'return the planewave cutoff used'
+        
+        ncf = netCDF(self.nc, 'r')
+        if 'PlaneWaveCutoff' in ncf.variables:
+            pw = ncf.variables['PlaneWaveCutoff'].getValue()
+        else:
+            pw = None
+        ncf.close()
+        
+        if isinstance(pw, int) or isinstance(pw, float):
+            return pw
+        elif pw is None:
+            return None
+        else:
+            return pw[0]
+
+    def get_dw(self):
+        'return the density wave cutoff'
+        
+        ncf = netCDF(self.nc, 'r')
+        if 'Density_WaveCutoff' in ncf.variables:
+            dw = ncf.variables['Density_WaveCutoff'].getValue()
+        else:
+            dw = None
+        ncf.close()
+
+        #some old calculations apparently store ints, while newer ones
+        #are lists
+        if isinstance(dw, int) or isinstance(dw, float):
+            return dw
+        else:
+            if dw is None:
+                return None
+            else:
+                return dw[0]
+    
+    def get_xc(self):
+        '''return the self-consistent exchange-correlation functional used
+
+        returns a string'''
+        
+        nc = netCDF(self.nc, 'r')
+        v = 'ExcFunctional'
+        if v in nc.variables:
+            xc = nc.variables[v][:].tostring().strip()
+        else:
+            xc = None
+
+        nc.close()
+        return xc
+
+    def get_potential_energy(self,
+                             atoms=None,
+                             force_consistent=False):
+        '''
+        return the potential energy.
+        '''
+        
+        if self.calculation_required(atoms):
+            log.debug('calculation required for energy')
+            self.calculate()
+        else:
+            log.debug('no calculation required for energy')
+                        
+        nc = netCDF(self.get_nc(), 'r')
+        try:
+            if force_consistent:
+                e = nc.variables['TotalFreeEnergy'][-1]
+            else:
+                e = nc.variables['TotalEnergy'][-1]
+            nc.close()
+            return e 
+        except (TypeError, KeyError):
+            raise RuntimeError('Error in calculating the total energy\n' +
+                               'Check ascii out file for error messages')
+
+    def get_forces(self, atoms=None):
+        """Calculate atomic forces"""
+        
+        if atoms is None:
+            atoms = self.atoms
+        if self.calculation_required(atoms):
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        forces = nc.variables['DynamicAtomForces'][-1]
+        nc.close()
+        return forces
+
+    def get_atoms(self):
+        'return the atoms attached to a calculator()'
+        
+        if hasattr(self, 'atoms'):
+            if self.atoms is None:
+                return None
+            atoms = self.atoms.copy()
+            #it is not obvious the copy of atoms should have teh same
+            #calculator
+            atoms.set_calculator(self)
+        else:
+            atoms = None
+        return atoms
+
+    def get_nc(self):
+        'return the ncfile used for output'
+        
+        return self.nc
+
+    def get_txt(self):
+        'return the txt file used for output'
+        
+        if hasattr(self,'txt'):
+            return self.txt
+        else:
+            return None
+        
+    def get_psp(self, sym=None, z=None):
+        '''get the pseudopotential filename from the psp database
+
+        :Parameters:
+
+          sym : string
+            the chemical symbol of the species
+
+          z : integer
+            the atomic number of the species
+
+        
+        you can only specify sym or z. Returns the pseudopotential
+        filename, not the full path.
+        '''
+        
+        if (sym is None and z is not None):
+            from ase.data import chemical_symbols
+            sym = chemical_symbols[z]
+        elif (sym is not None and z is None):
+            pass
+        else:
+            raise Exception, 'You can only specify Z or sym!'
+        psp = self.psp[sym]
+        return psp
+    
+    def get_spin_polarized(self):
+        'Return True if calculate is spin-polarized or False if not'
+        
+        #self.calculate() #causes recursion error with get_magnetic_moments
+        nc = netCDF(self.nc, 'r')
+        if 'ElectronicBands' in nc.variables:
+            v = nc.variables['ElectronicBands']
+            if hasattr(v, 'SpinPolarization'):
+                if v.SpinPolarization == 1:
+                    spinpol = False
+                elif v.SpinPolarization == 2:
+                    spinpol = True
+            else:
+                spinpol = False
+        else:
+            spinpol = 'Not defined'
+
+        nc.close()
+        return spinpol
+
+    def get_magnetic_moments(self, atoms=None):
+        '''return magnetic moments on each atom after the calculation is
+        run'''
+
+        if self.calculation_required(atoms):
+            self.calculate()
+        nc = netCDF(self.nc, 'r')
+        if 'InitialAtomicMagneticMoment' in nc.variables:
+            mom = nc.variables['InitialAtomicMagneticMoment'][:]
+        else:
+            mom = [0.0]*len(self.atoms)
+
+        nc.close()
+        return mom
+
+    def get_status(self):
+        '''get status of calculation from ncfile. usually one of:
+        'new',
+        'aborted'
+        'running'
+        'finished'
+        None
+        '''
+        
+        nc = netCDF(self.nc, 'r')
+        if hasattr(nc, 'status'):
+            status = nc.status
+        else:
+            status = None
+        nc.close()
+        return status
+
+    def get_calculate_stress(self):
+        'return whether stress is calculated or not'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        if 'TotalStress' in nc.variables:
+            calcstress = True
+        else:
+            calcstress = False
+        nc.close()
+        return calcstress
+        
+    def get_stress(self, atoms=None):
+        '''get stress on the atoms.
+
+        you should have set up the calculation
+        to calculate stress first.
+
+        returns [sxx, syy, szz, syz, sxz, sxy]'''
+        
+        if self.calculation_required(atoms):
+            self.calculate()
+
+        nc = netCDF(self.get_nc(), 'r')
+        if 'TotalStress' in nc.variables:
+            stress = nc.variables['TotalStress'][:]
+            #ase expects the 6-element form
+            stress = np.take(stress.ravel(), [0, 4, 8, 5, 2, 1])
+        else:
+            #stress will not be here if you did not set it up by
+            #calling set_stress() or in the __init__
+            stress = None
+        
+        nc.close()
+        
+        return stress
+
+    def get_psp_valence(self, psp):
+        '''
+        get the psp valence charge on an atom from the pspfile.
+        '''
+        
+        from struct import unpack
+        dacapopath = os.environ.get('DACAPOPATH')
+
+        if os.path.exists(psp):
+            #the pspfile may be in the current directory
+            #or defined by an absolute path
+            fullpsp = psp
+        else:
+            #or, it is in the default psp path
+            fullpsp = os.path.join(dacapopath, psp)
+
+        if os.path.exists(fullpsp.strip()):
+            f = open(fullpsp)
+            # read past version numbers and text information
+            buf = f.read(64)
+            # read number valence electrons
+            buf = f.read(8)
+            fmt = ">d"
+            nvalence = unpack(fmt, buf)[0]
+            f.close()
+
+        else:
+            raise Exception, "%s does not exist" % fullpsp
+
+        return nvalence
+
+    def get_psp_nuclear_charge(self, psp):
+        '''
+        get the nuclear charge of the atom from teh psp-file.
+
+        This is not the same as the atomic number, nor is it
+        necessarily the negative of the number of valence electrons,
+        since a psp may be an ion. this function is needed to compute
+        centers of ion charge for the dipole moment calculation.
+
+        We read in the valence ion configuration from the psp file and
+        add up the charges in each shell.
+        '''
+        
+        from struct import unpack
+        dacapopath = os.environ.get('DACAPOPATH')
+
+        if os.path.exists(psp):
+            #the pspfile may be in the current directory
+            #or defined by an absolute path
+            fullpsp = psp
+
+        else:
+            #or, it is in the default psp path
+            fullpsp = os.path.join(dacapopath, psp)
+
+        if os.path.exists(fullpsp.strip()):
+            f = open(fullpsp)
+            unpack('>i', f.read(4))[0]
+            for i in range(3):
+                f.read(4)
+            for i in range(3):
+                f.read(4)
+            f.read(8)
+            f.read(20)
+            f.read(8)
+            f.read(8)
+            f.read(8)
+            nvalps = unpack('>i', f.read(4))[0]
+            f.read(4)
+            f.read(8)
+            f.read(8)
+            wwnlps = []
+            for i in range(nvalps):
+                f.read(4)
+                wwnlps.append(unpack('>d', f.read(8))[0])
+                f.read(8)
+            f.close()
+
+        else:
+            raise Exception, "%s does not exist" % fullpsp
+
+        return np.array(wwnlps).sum()
+       
+    def get_valence(self, atoms=None):
+        '''return the total number of valence electrons for the
+        atoms. valence electrons are read directly from the
+        pseudopotentials.
+
+        the psp filenames are stored in the ncfile. They may be just
+        the name of the file, in which case the psp may exist in the
+        same directory as the ncfile, or in $DACAPOPATH, or the psp
+        may be defined by an absolute or relative path. This function
+        deals with all these possibilities.
+        '''
+        
+        from struct import unpack
+        
+        #do not use get_atoms() or recursion occurs
+        if atoms is None:
+            if hasattr(self, 'atoms'):
+                atoms = self.atoms
+            else:
+                return None
+
+        dacapopath = os.environ.get('DACAPOPATH')
+        totval = 0.0
+        for sym in atoms.get_chemical_symbols():
+            psp = self.get_psp(sym)
+            
+            if os.path.exists(psp):
+                #the pspfile may be in the current directory
+                #or defined by an absolute path
+                fullpsp = psp
+
+            #let's also see if we can construct an absolute path to a
+            #local or relative path psp.
+            abs_path_to_nc = os.path.abspath(self.get_nc())
+            base = os.path.split(abs_path_to_nc)[0]
+            possible_path_to_psp = os.path.join(base, psp)
+            if os.path.exists(possible_path_to_psp):
+                fullpsp = possible_path_to_psp
+                
+            else:
+                #or, it is in the default psp path
+                fullpsp = os.path.join(dacapopath, psp)
+            if os.path.exists(fullpsp.strip()):
+                f = open(fullpsp)
+                # read past version numbers and text information
+                buf = f.read(64)
+                # read number valence electrons
+                buf = f.read(8)
+                fmt = ">d"
+                nvalence = unpack(fmt, buf)[0]
+                f.close()
+                totval += float(nvalence)
+            else:
+                raise Exception, "%s does not exist" % fullpsp
+            
+        return totval 
+
+    def calculation_required(self, atoms=None, quantities=None):
+        '''
+        determines if a calculation is needed.
+
+        return True if a calculation is needed to get up to date data.
+        return False if no calculation is needed.
+
+        quantities is here because of the ase interface.
+        '''
+        
+        # first, compare if the atoms is the same as the stored atoms
+        # if anything has changed, we need to run a calculation
+        log.debug('running calculation_required')
+
+        if self.nc is None:
+            raise Exception, 'No output ncfile specified!'
+                
+        if atoms is not None:
+            if not self.atoms_are_equal(atoms):
+                log.debug('found that atoms != self.atoms')
+                tol = 1.0e-6 #tolerance that the unit cell is the same
+                new = atoms.get_cell()
+                old = self.atoms.get_cell()
+                #float comparison of equality
+                if not np.all(abs(old-new) < tol): 
+                    #this often changes the number of planewaves
+                    #which requires a complete restart
+                    log.debug('restart required! because cell changed')
+                    self.restart()
+                else:
+                    log.debug('Unitcells apparently the same')
+                    
+                self.set_atoms(atoms) #we have to update the atoms in any case
+                return True
+            
+        #if we make it past the atoms check, we look in the
+        #nc file. if parameters have been changed the status
+        #will tell us if a calculation is needed
+
+        #past this point, atoms was None or equal, so there is nothing to
+        #update in the calculator
+
+        log.debug('atoms tested equal')
+        if os.path.exists(self.nc):
+            nc = netCDF(self.nc, 'r')
+            if hasattr(nc, 'status'):
+                if nc.status == 'finished' and self.ready:
+                    nc.close()
+                    return False
+                elif nc.status == 'running':
+                    nc.close()
+                    raise DacapoRunning('Dacapo is Running')
+                elif nc.status == 'aborted':
+                    nc.close()
+                    raise DacapoAborted('Dacapo aborted. see txt file!')
+                else:
+                    log.debug('ncfile exists, but is not ready')
+                    nc.close()
+                    return True
+            else:
+                #legacy calculations do not have a status flag in them.
+                #let us guess that if the TotalEnergy is there
+                #no calculation needs to be run?
+                if 'TotalEnergy' in nc.variables:
+                    runflag = False
+                else:
+                    runflag = True
+                nc.close()
+                log.debug('Legacy calculation')
+                return runflag #if no status run calculation
+            nc.close()
+            
+        #default, a calculation is required
+        return True
+
+    def get_scratch(self):
+        '''finds an appropriate scratch directory for the calculation'''
+        
+        import getpass
+        username = getpass.getuser()
+
+        scratch_dirs = []
+        if os.environ.has_key('SCRATCH'):
+            scratch_dirs.append(os.environ['SCRATCH'])
+        if os.environ.has_key('SCR'):
+            scratch_dirs.append(os.environ['SCR'])
+        scratch_dirs.append('/scratch/'+username)
+        scratch_dirs.append('/scratch/')
+        scratch_dirs.append(os.curdir)
+        for scratch_dir in scratch_dirs:
+            if os.access(scratch_dir, os.W_OK):
+                return scratch_dir
+        raise IOError, "No suitable scratch directory and no write access \
+        to current dir."
+
+    def calculate(self):
+        '''run a calculation.
+
+        you have to be a little careful with code in here. Use the
+        calculation_required function to tell if a calculation is
+        required. It is assumed here that if you call this, you mean
+        it.'''
+
+        #provide a way to make no calculation get run
+        if os.environ.get('DACAPO_DRYRUN', None) is not None:
+            raise Exception, '$DACAPO_DRYRUN detected, and a calculation \
+            attempted'
+    
+        if not self.ready:
+            log.debug('Calculator is not ready.')
+            if not os.path.exists(self.get_nc()):
+                self.initnc()
+
+            log.debug('writing atoms out')
+            log.debug(self.atoms)
+            
+            self.write_nc() #write atoms to ncfile
+
+            log.debug('writing input out')
+            self.write_input() #make sure input is uptodate
+            
+
+            #check that the bands get set
+            if self.get_nbands() is None:
+                nelectrons = self.get_valence()
+                nbands = int(nelectrons * 0.65 + 4)
+                self.set_nbands(nbands) 
+            
+        log.debug('running a calculation')
+
+        nc = self.get_nc()
+        txt = self.get_txt()
+        scratch = self.get_scratch()
+
+        if self.stay_alive:
+            self.execute_external_dynamics(nc, txt)
+            self.ready = True
+            self.set_status('finished')
+        else:
+            cmd = 'dacapo.run  %(innc)s -out %(txt)s -scratch %(scratch)s'
+            cmd = cmd % {'innc':nc,
+                         'txt':txt,
+                         'scratch':scratch}
+
+            log.debug(cmd)
+            # using subprocess instead of commands subprocess is more
+            # flexible and works better for stay_alive
+            self._dacapo = sp.Popen(cmd,
+                                    stdout=sp.PIPE,
+                                    stderr=sp.PIPE,
+                                    shell=True)
+            status = self._dacapo.wait()
+            [stdout, stderr] = self._dacapo.communicate()
+            output = stdout+stderr
+            
+            if status is 0: #that means it ended fine!
+                self.ready = True
+                self.set_status('finished')
+            else:
+                log.debug('Status was not 0')
+                log.debug(output)
+                self.ready = False
+            # directory cleanup has been moved to self.__del__()
+            del self._dacapo
+
+            #Sometimes dacapo dies or is killed abnormally, and in this
+            #case an exception should be raised to prevent a geometry
+            #optimization from continuing for example. The best way to
+            #detect this right now is actually to check the end of the
+            #text file to make sure it ends with the right line. The
+            #line differs if the job was run in parallel or in serial.
+            f = open(txt, 'r')
+            lines = f.readlines()
+            f.close()
+
+            if 'PAR: msexit halting Master' in lines[-1]:
+                pass #standard parallel end
+            elif ('TIM' in lines[-2]
+                  and 'clexit: exiting the program' in lines[-1]):
+                pass #standard serial end
+            else:
+                # text file does not end as expected, print the last
+                # 10 lines and raise exception
+                log.debug(string.join(lines[-10:-1], ''))
+                s = 'Dacapo output txtfile (%s) did not end normally.' 
+                raise DacapoAbnormalTermination(s % txt)
+
+    def execute_external_dynamics(self,
+                                  nc=None,
+                                  txt=None,
+                                  stoppfile='stop',
+                                  stopprogram=None):
+        '''
+        Implementation of the stay alive functionality with socket
+        communication between dacapo and python.  Known limitations:
+        It is not possible to start 2 independent Dacapo calculators
+        from the same python process, since the python PID is used as
+        identifier for the script[PID].py file.
+        '''
+        
+        from socket import socket, AF_INET, SOCK_STREAM, timeout
+        import tempfile
+        
+        if hasattr(self, "_dacapo"):
+            msg = "Starting External Dynamics while Dacapo is runnning: %s"
+            msg = msg % str(self._dacapo.poll())
+            log.debug(msg)
+        else:
+            log.debug("No dacapo instance has been started yet")
+        log.debug("Stopprogram: %s" % stopprogram)
+
+        if not nc:
+            nc = self.get_nc()
+        if not txt:
+            txt = self.get_txt()
+        tempfile.tempdir = os.curdir
+
+        if stopprogram:
+            # write stop file
+            stfile = open(stoppfile, 'w')
+            stfile.write('1 \n')
+            stfile.close()
+
+            # signal to dacapo that positions are ready
+            # let dacapo continue, it is up to the python mainloop 
+            # to allow dacapo enough time to finish properly.
+            self._client.send('ok too proceed')
+
+            # Wait for dacapo to acknowledge that netcdf file has
+            # been updated, and analysis part of the code has been
+            # terminated. Dacapo sends a signal at the end of call
+            # clexit().
+            log.info("waiting for dacapo to exit...")
+            self.s.settimeout(1200.0) # if dacapo exits with an
+                                       # error, self.s.accept()
+                                       # should time out,
+                                      # but we need to give it
+                                      # enough time to write the
+                                      # wave function to the nc
+                                      # file.
+            try:
+                self._client, self._addr = self.s.accept() # Last
+                                                          # mumble
+                                                          # before
+                                                          # Dacapo
+                                                          # dies.
+                os.system("sleep 5") # 5 seconds of silence
+                                                         # mourning
+                                                         # dacapo.
+            except timeout:
+                print '''Socket connection timed out.'''
+                print '''This usually means Dacapo crashed.'''
+                
+            # close the socket s
+            self.s.close()
+            self._client.close()
+
+            # remove the script???? file
+            ncfile = netCDF(nc, 'r')
+            vdyn = ncfile.variables['Dynamics']
+            os.system('rm -f '+vdyn.ExternalIonMotion_script)
+            ncfile.close()
+            os.system('rm -f '+stoppfile)
+
+            if self._dacapo.poll()==None:  # dacapo is still not dead!
+                # but this should do it!
+                sp.Popen("kill -9 "+str(self._dacapo.pid), shell=True)
+                #if Dacapo dies for example because of too few
+                #bands, subprocess never returns an exitcode.
+                #very strange, but at least the program is
+                #terminated.  print self._dacapo.returncode
+            del self._dacapo
+            return
+
+        if hasattr(self, '_dacapo') and self._dacapo.poll()==None: 
+            # returns  None if  dacapo is running self._dacapo_is_running:
+
+            # calculation_required already updated the positions in
+            # the nc file
+            self._client.send('ok too proceed')
+
+        else:
+
+            # get process pid that will be used as communication
+            # channel
+            pid = os.getpid()
+
+            # setup communication channel to dacapo
+            from sys    import version
+            from string import split
+            effpid = (pid)%(2**16-1025)+1025 # This translate pid
+                                               # [0;99999] to a number
+                                               # in [1025;65535] (the
+                                               # allowed socket
+                                               # numbers)
+
+            self.s = socket(AF_INET, SOCK_STREAM)
+            foundafreesocket = 0
+            while not foundafreesocket:
+                try:
+                    if split(version)[0] > "2":     # new interface
+                        self.s.bind(("", effpid))
+                    else:                           # old interface
+                        self.s.bind("", effpid)
+                    foundafreesocket = 1
+                except:
+                    effpid = effpid + 1
+
+            # write script file that will be used by dacapo
+            scriptname = 'script%s.py' % str(pid)
+            scriptfile = open(scriptname, 'w')
+            scriptfile.write(
+"""#!/usr/bin/env python
+from socket import *
+from sys    import version
+from string import split  
+s = socket(AF_INET,SOCK_STREAM)
+# tell python that dacapo has finished
+if split(version)[0] > "2":     # new interface 
+     s.connect(("",%(effpid)s))
+else:                           # old interface
+     s.connect("",%(effpid)s)
+# wait for python main loop
+s.recv(14)
+""" % {'effpid':str(effpid)})
+            scriptfile.close()
+            os.system('chmod +x ' + scriptname)
+
+            # setup dynamics as external and set the script name
+            ncfile = netCDF(nc, 'a')
+            if 'Dynamics' not in ncfile.variables:
+                vdyn = ncfile.createVariable('Dynamics', 'c', ())
+            else:
+                vdyn = ncfile.variables['Dynamics']
+            vdyn.Type = "ExternalIonMotion" 
+            vdyn.ExternalIonMotion_script = './'+ scriptname
+            ncfile.close()
+
+            # dacapo is not running start dacapo non blocking
+            scratch_in_nc = tempfile.mktemp()
+            os.system('mv '+nc+' '+scratch_in_nc)
+            os.system('rm -f '+stoppfile)
+            scratch = self.get_scratch()
+            cmd = 'dacapo.run'
+            cmd += ' %(innc)s %(outnc)s -out %(txt)s -scratch %(scratch)s'
+            cmd = cmd % {'innc':scratch_in_nc,
+                         'outnc':nc,
+                         'txt':txt,
+                         'scratch':scratch}
+
+            log.debug(cmd)
+            self._dacapo = sp.Popen(cmd,
+                                    stdout=sp.PIPE,
+                                    stderr=sp.PIPE,
+                                    shell=True)
+
+            self.s.listen(1)
+
+        # wait for dacapo  
+        self._client, self._addr = self.s.accept()
+
+    def write_nc(self, nc=None, atoms=None):
+        '''
+        write out atoms to a netcdffile.
+
+        This does not write out the calculation parameters!
+
+        :Parameters:
+
+          nc : string
+            ncfilename to write to. this file will get clobbered
+            if it already exists.
+
+          atoms : ASE.Atoms
+            atoms to write. if None use the attached atoms
+            if no atoms are attached only the calculator is
+            written out. 
+
+        the ncfile is always opened in 'a' mode.
+
+        note: it is good practice to use the atoms argument to make
+        sure that the geometry you mean gets written! Otherwise, the
+        atoms in the calculator is used, which may be different than
+        the external copy of the atoms.
+
+        '''
+
+        log.debug('writing atoms to ncfile')
+        #no filename was provided to function, use the current ncfile
+        if nc is None: 
+            nc = self.get_nc()
+        
+        if nc != self.nc:
+            #this means we are writing a new file, and we should copy
+            #the old file to it first.  this makes sure the old
+            #calculator settings are preserved
+            new = nc
+            old = self.nc
+            log.debug('Copying old ncfile to new ncfile')
+            log.debug('cp %s %s' % (old, new))
+            os.system('cp %s %s' % (old, new))
+              
+        if atoms is None:
+            atoms = self.get_atoms()
+
+        log.debug('self.atoms = %s' % str(self.atoms))
+        log.debug('atoms = %s' % str(atoms))
+
+        if atoms is not None: #there may still be no atoms attached
+            log.debug('about to write to %s' % nc)
+            ncf = netCDF(nc, 'a')
+
+            if 'number_of_dynamic_atoms' not in ncf.dimensions:
+                ncf.createDimension('number_of_dynamic_atoms',
+                                    len(atoms))
+            else:
+                # number of atoms is already a dimension, but we might
+                # be setting new atoms here
+                # check for same atom symbols (implicitly includes
+                # a length check)
+                symbols = np.array(['%2s' % s for s in
+                                    atoms.get_chemical_symbols()], dtype='c')
+                ncsym = ncf.variables['DynamicAtomSpecies'][:]
+                if (symbols.size != ncsym.size) or (np.any(ncsym != symbols)):
+                    # the number of atoms or their order has changed.
+                    # Treat this as a new calculation and reset
+                    # number_of_ionic_steps and
+                    # number_of_dynamic_atoms.
+                    ncf.close() #nc file must be closed for
+                                 #delete_ncattdimvar to work correctly
+                    self.delete_ncattdimvar(nc, ncattrs=[],
+                                            ncdims=['number_of_dynamic_atoms',
+                                                    'number_ionic_steps'])
+                    ncf = netCDF(nc, 'a')
+                    ncf.createDimension('number_of_dynamic_atoms',
+                                                  len(atoms)) 
+                    ncf.createDimension('number_ionic_steps', None)
+                    self._set_frame_number(0)
+                    ncf.close() #nc file must be closed for restart to
+                                #work correctly
+                    self.restart()
+                    ncf = netCDF(nc, 'a')
+
+            #now, create variables
+            if 'DynamicAtomSpecies' not in ncf.variables:
+                sym = ncf.createVariable('DynamicAtomSpecies',
+                                         'c',
+                                         ('number_of_dynamic_atoms',
+                                          'dim2',))
+            else:
+                sym = ncf.variables['DynamicAtomSpecies']
+
+            #note explicit array casting was required here
+            symbols = atoms.get_chemical_symbols()
+            sym[:] = np.array(['%2s' % s for s in symbols], dtype='c')
+
+            if 'DynamicAtomPositions' not in ncf.variables:
+                pos = ncf.createVariable('DynamicAtomPositions',
+                                         'd',
+                                         ('number_ionic_steps',
+                                          'number_of_dynamic_atoms',
+                                          'dim3'))
+            else:
+                pos = ncf.variables['DynamicAtomPositions']
+
+            spos = atoms.get_scaled_positions()
+            if pos.typecode() == 'f':
+                spos = np.array(spos, dtype=np.float32)
+            pos[self._frame, :] = spos
+
+            if 'UnitCell' not in ncf.variables:
+                uc = ncf.createVariable('UnitCell', 'd',
+                                        ('number_ionic_steps',
+                                         'dim3', 'dim3'))
+            else:
+                uc = ncf.variables['UnitCell']
+
+            cell = atoms.get_cell()
+            if uc.typecode() == 'f':
+                cell = np.array(cell, dtype=np.float32)
+                
+            uc[self._frame, :] = cell
+
+            if 'AtomTags' not in ncf.variables:
+                tags = ncf.createVariable('AtomTags', 'i',
+                                          ('number_of_dynamic_atoms',))
+            else:
+                tags = ncf.variables['AtomTags']
+
+            tags[:] = np.array(atoms.get_tags(), np.int32)
+
+            if 'InitialAtomicMagneticMoment' not in ncf.variables:
+                mom = ncf.createVariable('InitialAtomicMagneticMoment',
+                                         'd',
+                                         ('number_of_dynamic_atoms',))
+            else:
+                mom = ncf.variables['InitialAtomicMagneticMoment']
+
+            #explain why we have to use get_initial_magnetic_moments()
+            moms = atoms.get_initial_magnetic_moments()
+            if mom.typecode() == 'f':
+                moms = np.array(moms, dtype=np.float32)
+            mom[:] = moms
+            
+            #finally the atom pseudopotentials
+            for sym in atoms.get_chemical_symbols():
+                vn = 'AtomProperty_%s' % sym
+                if vn not in ncf.variables:
+                    p = ncf.createVariable(vn, 'c', ('dim20',))
+                else:
+                    p = ncf.variables[vn]
+
+                ppath = self.get_psp(sym=sym)
+                p.PspotFile = ppath
+
+            ncf.sync()
+            ncf.close()
+               
+            #store constraints if they exist
+            constraints = atoms._get_constraints()
+            if constraints != []:
+                nc = netCDF(self.get_nc(), 'a')
+                if 'constraints' not in nc.variables:
+                    if 'dim1' not in nc.dimensions:
+                        nc.createDimension('dim1', 1)
+                    c = nc.createVariable('constraints', 'c', ('dim1',))
+                else:
+                    c = nc.variables['constraints']
+                #we store the pickle string as an attribute of a
+                #netcdf variable because that way we do not have to
+                #know how long the string is.  with a character
+                #variable you have to specify the dimension of the
+                #string ahead of time.
+                c.data = pickle.dumps(constraints)
+                nc.close()
+            else:
+                # getting here means there where no constraints on the
+                # atoms just written we should check if there are any
+                # old constraints left in the ncfile
+                # from a previous atoms, and delete them if so
+                delete_constraints = False
+                nc = netCDF(self.get_nc())
+                if 'constraints' in nc.variables:
+                    delete_constraints = True
+                nc.close()
+
+                if delete_constraints:
+                    log.debug('deleting old constraints')
+                    self.delete_ncattdimvar(self.nc,
+                                            ncvars=['constraints'])
+        
+    def read_atoms(filename):
+        '''read atoms and calculator from an existing netcdf file.
+
+        :Parameters:
+
+          filename : string
+            name of file to read from.
+
+        static method
+
+        example::
+        
+          >>> atoms = Jacapo.read_atoms(ncfile)
+          >>> calc = atoms.get_calculator()
+
+        this method is here for legacy purposes. I used to use it alot.
+        '''
+        
+        calc = Jacapo(filename)
+        atoms = calc.get_atoms()
+        return atoms
+    
+    read_atoms = staticmethod(read_atoms)
+
+    def read_only_atoms(self, ncfile):
+        '''read only the atoms from an existing netcdf file. Used to
+        initialize a calculator from a ncfilename.
+
+        :Parameters:
+
+          ncfile : string
+            name of file to read from.
+
+        return ASE.Atoms with no calculator attached or None if no
+        atoms found
+        '''
+        
+        from ase import Atoms
+        
+        nc = netCDF(ncfile, 'r')
+        #some ncfiles do not have atoms in them
+        if 'UnitCell' not in nc.variables:
+            log.debug('no unit cell found in ncfile')
+            nc.close()
+            return None
+        
+        cell = nc.variables['UnitCell'][:][-1]
+        sym = nc.variables['DynamicAtomSpecies'][:]
+        symbols = [x.tostring().strip() for x in sym]
+        spos = nc.variables['DynamicAtomPositions'][:][-1]
+
+        pos = np.dot(spos, cell)
+        
+        atoms = Atoms(symbols=symbols,
+                      positions=pos,
+                      cell=cell)
+
+        if 'AtomTags' in nc.variables:
+            tags = nc.variables['AtomTags'][:]
+            atoms.set_tags(tags)
+
+        if 'InitialAtomicMagneticMoment' in nc.variables:
+            mom = nc.variables['InitialAtomicMagneticMoment'][:]
+            atoms.set_initial_magnetic_moments(mom)
+
+        #update psp database
+        for sym in symbols:
+            vn = 'AtomProperty_%s' % sym
+            if vn in nc.variables:
+                var = nc.variables[vn]
+                pspfile = var.PspotFile
+                self.psp[sym] = pspfile
+
+        #get constraints if they exist
+        c = nc.variables.get('constraints', None)
+        if c is not None:
+            constraints = pickle.loads(c.data)
+            atoms.set_constraint(constraints)
+                    
+        nc.close()
+        
+        return atoms
+        
+    def delete_ncattdimvar(self, ncf, ncattrs=None, ncdims=None, ncvars=None):
+        '''
+        helper function to delete attributes,
+        dimensions and variables in a netcdffile
+
+        this functionality is not implemented for some reason in
+        netcdf, so the only way to do this is to copy all the
+        attributes, dimensions, and variables to a new file, excluding
+        the ones you want to delete and then rename the new file.
+
+        if you delete a dimension, all variables with that dimension
+        are also deleted.
+        '''
+
+        if ncattrs is None:
+            ncattrs = []
+        if ncdims is None:
+            ncdims = []
+        if ncvars is None:
+            ncvars = []
+        
+        log.debug('beginning: going to delete dims: %s' % str(ncdims))
+        log.debug('beginning: going to delete vars: %s' % str(ncvars))
+            
+        oldnc = netCDF(ncf, 'r')
+
+        #h,tempnc = tempfile.mkstemp(dir='.',suffix='.nc')
+        tempnc = ncf+'.temp'
+        
+        newnc = netCDF(tempnc, 'w')
+
+        for attr in dir(oldnc):
+            if attr in ['close', 'createDimension',
+                        'createVariable', 'flush', 'sync']:
+                continue
+            if attr in ncattrs:
+                continue #do not copy this attribute
+            setattr(newnc, attr, getattr(oldnc, attr))
+           
+        #copy dimensions
+        for dim in oldnc.dimensions:
+            if dim in ncdims:
+                log.debug('deleting %s of %s' % (dim, str(ncdims)))
+                continue #do not copy this dimension
+            size = oldnc.dimensions[dim]
+            
+            newnc.createDimension(dim, size)
+
+        # we need to delete all variables that depended on a deleted dimension
+        for v in oldnc.variables:
+            dims1 = oldnc.variables[v].dimensions
+            for dim in ncdims:
+                if dim in dims1:
+                    s = 'deleting "%s" because it depends on dim "%s"'
+                    log.debug(s %(v, dim))
+                    ncvars.append(v)
+
+        #copy variables, except the ones to delete
+        for v in oldnc.variables:
+            if v in ncvars:
+                log.debug('vars to delete: %s ' % ncvars)
+                log.debug('deleting ncvar: %s' % v)
+                continue #we do not copy this v over
+
+            ncvar = oldnc.variables[v]
+            tcode = ncvar.typecode()
+            #char typecodes do not come out right apparently
+            if tcode == " ":
+                tcode = 'c'
+            
+            ncvar2 = newnc.createVariable(v, tcode, ncvar.dimensions)
+            try:
+                ncvar2[:] = ncvar[:]
+            except TypeError:
+                #this exception occurs for scalar variables
+                #use getValue and assignValue instead
+                ncvar2.assignValue(ncvar.getValue())
+
+            #and variable attributes
+            #print dir(ncvar)
+            for att in dir(ncvar):
+                if att in ['assignValue', 'getValue', 'typecode']:
+                    continue
+                setattr(ncvar2, att, getattr(ncvar, att))
+
+        oldnc.close()
+        newnc.close()
+
+        s = 'looking for .nfs files before copying: %s'
+        log.debug(s % glob.glob('.nfs*'))
+        
+        #ack!!! this makes .nfsxxx files!!!
+        #os.close(h) #this avoids the stupid .nfsxxx file
+        #import shutil
+        #shutil.move(tempnc,ncf)
+
+        #this seems to avoid making the .nfs files 
+        os.system('cp %s %s' % (tempnc, ncf))
+        os.system('rm %s' % tempnc)
+
+        s = 'looking for .nfs files after copying: %s'
+        log.debug(s %  glob.glob('.nfs*'))
+        
+    def restart(self):
+        '''
+        Restart the calculator by deleting nc dimensions that will
+        be rewritten on the next calculation. This is sometimes required
+        when certain dimensions change related to unitcell size changes
+        planewave/densitywave cutoffs and kpt changes. These can cause
+        fortran netcdf errors if the data does not match the pre-defined
+        dimension sizes.
+
+        also delete all the output from previous calculation.
+        '''
+        
+        log.debug('restarting!')
+        
+        ncdims = ['number_plane_waves',
+                  'number_IBZ_kpoints',
+                  'softgrid_dim1',
+                  'softgrid_dim2',
+                  'softgrid_dim3',
+                  'hardgrid_dim1',
+                  'hardgrid_dim2',
+                  'hardgrid_dim3',
+                  'max_projectors_per_atom',
+                  'atomdos_energygrid_size',
+                  'atomdos_angular_channels',
+                  'atomdos_radial_orbs']
+
+        ncvars = ['TotalEnergy',
+                  'TotalFreeEnergy',
+                  'EvaluateTotalEnergy',
+                  'DynamicAtomForces',
+                  'FermiLevel',
+                  'EnsembleXCEnergies',
+                  'AtomProjectedDOS_IntegratedDOS',
+                  'AtomProjectedDOS_OrdinalMap',
+                  'NumberPlaneWavesKpoint',
+                  'AtomProjectedDOS_EnergyResolvedDOS',
+                  'AtomProjectedDOS_EnergyGrid',
+                  'EvaluateCorrelationEnergy',
+                  'DynamicAtomVelocities',
+                  'KpointWeight',
+                  'EvaluateExchangeEnergy',
+                  'EffectivePotential',
+                  'TotalStress',
+                  'ChargeDensity',
+                  'WaveFunction',
+                  'WaveFunctionFFTindex',
+                  'NumberOfNLProjectors',
+                  'NLProjectorPsi',
+                  'TypeNLProjector1',
+                  'NumberofNLProjectors',
+                  'PartialCoreDensity',
+                  'ChargeDensity',
+                  'ElectrostaticPotential',
+                  'StructureFactor',
+                  'EigenValues',
+                  'OccupationNumbers']
+                  
+        self.delete_ncattdimvar(self.nc,
+                                ncattrs=[],
+                                ncdims=ncdims,
+                                ncvars=ncvars)
+
+        self.set_status('new')
+        self.ready = False
+
+    def get_convergence(self):
+        'return convergence settings for Dacapo'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        vname = 'ConvergenceControl'
+        if vname in nc.variables:
+            v = nc.variables[vname]
+            convergence = {}
+            if hasattr(v, 'AbsoluteEnergyConvergence'):
+                convergence['energy'] = v.AbsoluteEnergyConvergence[0]
+            if hasattr(v, 'DensityConvergence'):
+                convergence['density'] = v.DensityConvergence[0]
+            if hasattr(v, 'OccupationConvergence'):
+                convergence['occupation'] = v.OccupationConvergence[0]
+            if hasattr(v, 'MaxNumberOfSteps'):
+                convergence['maxsteps'] = v.MaxNumberOfSteps[0]
+            if hasattr(v, 'CPUTimeLimit'):
+                convergence['cputime'] = v.CPUTimeLimit[0]
+        else:
+            convergence = None
+
+        nc.close()
+        return convergence
+
+    def set_convergence(self,
+                        energy=0.00001,
+                        density=0.0001,
+                        occupation=0.001,
+                        maxsteps=None,
+                        maxtime=None
+                        ):
+        '''set convergence criteria for stopping the dacapo calculator.
+
+        :Parameters:
+
+          energy : float
+            set total energy change (eV) required for stopping
+
+          density : float
+            set density change required for stopping
+
+          occupation : float
+            set occupation change required for stopping
+
+          maxsteps : integer
+            specify maximum number of steps to take
+
+          maxtime : integer
+            specify maximum number of hours to run.
+
+        Autopilot not supported here.
+        '''
+        
+        nc = netCDF(self.get_nc(), 'a')
+        vname = 'ConvergenceControl'
+        if vname in nc.variables:
+            v = nc.variables[vname]
+        else:
+            v = nc.createVariable(vname, 'c', ('dim1',))
+
+        if energy is not None:
+            v.AbsoluteEnergyConvergence = energy
+        if density is not None:
+            v.DensityConvergence = density
+        if occupation is not None:
+            v.OccupationConvergence = occupation
+        if maxsteps is not None:
+            v.MaxNumberOfSteps = maxsteps
+        if maxtime is not None:
+            v.CPUTimeLimit = maxtime
+
+        nc.sync()
+        nc.close()
+
+    def get_charge_mixing(self):
+        'return charge mixing parameters'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        vname = 'ChargeMixing'
+        if vname in nc.variables:
+            v = nc.variables[vname]
+            charge_mixing = {}
+            if hasattr(v, 'Method'):
+                charge_mixing['method'] = v.Method
+            if hasattr(v, 'UpdateCharge'):
+                charge_mixing['updatecharge'] = v.UpdateCharge
+            if hasattr(v, 'Pulay_MixingHistory'):
+                charge_mixing['mixinghistory'] = v.Pulay_MixingHistory[0]
+            if hasattr(v, 'Pulay_DensityMixingCoeff'):
+                charge_mixing['mixingcoeff'] = v.Pulay_DensityMixingCoeff[0]
+            if hasattr(v, 'Pulay_KerkerPrecondition'):
+                charge_mixing['precondition'] = v.Pulay_KerkerPrecondition
+        else:
+            charge_mixing = None
+
+        nc.close()
+        return charge_mixing
+
+    def set_charge_mixing(self,
+                          method='Pulay',
+                          mixinghistory=10,
+                          mixingcoeff=0.1,
+                          precondition='No',
+                          updatecharge='Yes'):
+        '''set density mixing method and parameters
+
+        :Parameters:
+
+          method : string
+            'Pulay' for Pulay mixing. only one supported now
+
+          mixinghistory : integer
+            number of iterations to mix
+            Number of charge residual vectors stored for generating
+            the Pulay estimate on the self-consistent charge density,
+            see Sec. 4.2 in Kresse/Furthmuller:
+            Comp. Mat. Sci. 6 (1996) p34ff
+
+          mixingcoeff : float
+            Mixing coefficient for Pulay charge mixing, corresponding
+            to A in G$^1$ in Sec. 4.2 in Kresse/Furthmuller:
+            Comp. Mat. Sci. 6 (1996) p34ff
+                        
+          precondition : string
+            'Yes' or 'No'
+            
+            * "Yes" : Kerker preconditiong is used,
+               i.e. q$_0$ is different from zero, see eq. 82
+               in Kresse/Furthmuller: Comp. Mat. Sci. 6 (1996).
+               The value of q$_0$ is fix to give a damping of 20
+               of the lowest q vector.
+            
+            * "No" : q$_0$ is zero and mixing is linear (default).
+
+          updatecharge : string
+            'Yes' or 'No'
+            
+            * "Yes" : Perform charge mixing according to
+               ChargeMixing:Method setting
+              
+            * "No" : Freeze charge to initial value.
+               This setting is useful when evaluating the Harris-Foulkes
+               density functional
+              
+        '''
+        
+        if method == 'Pulay':
+            nc = netCDF(self.get_nc(), 'a')
+            vname = 'ChargeMixing'
+            if vname in nc.variables:
+                v = nc.variables[vname]
+            else:
+                v = nc.createVariable(vname, 'c', ('dim1',))
+
+            v.Method = 'Pulay'
+            v.UpdateCharge = updatecharge
+            v.Pulay_MixingHistory = mixinghistory
+            v.Pulay_DensityMixingCoeff = mixingcoeff
+            v.Pulay_KerkerPrecondition = precondition
+
+            nc.sync()
+            nc.close()
+
+        self.ready = False
+
+    def set_electronic_minimization(self,
+                                    method='eigsolve',
+                                    diagsperband=2):
+        '''set the eigensolver method
+
+        Selector for which subroutine to use for electronic
+        minimization
+
+        Recognized options : "resmin", "eigsolve" and "rmm-diis".
+
+        * "resmin" : Power method (Lennart Bengtson), can only handle
+           k-point parallization.
+
+        * "eigsolve : Block Davidson algorithm
+           (Claus Bendtsen et al).
+
+        * "rmm-diis : Residual minimization
+           method (RMM), using DIIS (direct inversion in the iterate
+           subspace) The implementaion follows closely the algorithm
+           outlined in Kresse and Furthmuller, Comp. Mat. Sci, III.G/III.H
+        
+        :Parameters:
+
+          method : string
+            should be 'resmin', 'eigsolve' or 'rmm-diis'
+
+          diagsperband : int
+            The number of diagonalizations per band for
+            electronic minimization algorithms (maps onto internal
+            variable ndiapb). Applies for both
+            ElectronicMinimization:Method = "resmin" and "eigsolve".
+            default value = 2
+        '''
+        
+        nc = netCDF(self.get_nc(), 'a')
+        
+        vname = 'ElectronicMinimization'
+        if vname in nc.variables:
+            v = nc.variables[vname]
+        else:
+            log.debug('Creating ElectronicMinimization')
+            v = nc.createVariable(vname, 'c', ('dim1',))
+
+        log.debug('setting method for ElectronicMinimization: % s' % method)
+        v.Method = method
+        log.debug('setting DiagonalizationsBand for ElectronicMinimization')
+        if diagsperband is not None:
+            v.DiagonalizationsPerBand = diagsperband
+
+        log.debug('synchronizing ncfile')
+        nc.sync()
+        
+        nc.close()
+
+    def get_electronic_minimization(self):
+        '''get method and diagonalizations per band for electronic
+        minimization algorithms'''
+
+        log.debug('getting electronic minimization parameters')
+        
+        nc = netCDF(self.get_nc(), 'a')
+        vname = 'ElectronicMinimization'
+        if vname in nc.variables:
+            v = nc.variables[vname]
+            method = v.Method
+            if hasattr(v, 'DiagonalizationsPerBand'):
+                diagsperband = v.DiagonalizationsPerBand[0]
+            else:
+                diagsperband = None
+        else:
+            method = None
+            diagsperband = None
+        nc.close()
+        return {'method':method,
+                'diagsperband':diagsperband}
+
+    def get_occupationstatistics(self):
+        'return occupation statistics method'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        if 'ElectronicBands' in nc.variables:
+            v = nc.variables['ElectronicBands']
+            if hasattr(v, 'OccupationStatistics'):
+                occstat = v.OccupationStatistics
+            else:
+                occstat = None
+        else:
+            occstat = None
+        nc.close()
+        return occstat
+
+    def set_occupationstatistics(self, method):
+        '''
+        set the method used for smearing the occupations.
+
+        :Parameters:
+
+          method : string
+            one of 'FermiDirac' or 'MethfesselPaxton'
+            Currently, the Methfessel-Paxton scheme (PRB 40, 3616 (1989).)
+            is implemented to 1th order (which is recommemded by most authors).
+            'FermiDirac' is the default
+        '''
+            
+        nc = netCDF(self.get_nc(), 'a')
+        if 'ElectronicBands' in nc.variables:
+            v = nc.variables['ElectronicBands']
+            v.OccupationStatistics = method
+        
+        nc.sync()
+        nc.close()
+
+    def get_fermi_level(self):
+        'return Fermi level'
+        
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        ef = nc.variables['FermiLevel'][-1]
+        nc.close()
+        return ef
+
+    def get_occupation_numbers(self, kpt=0, spin=0):
+        '''return occupancies of eigenstates for a kpt and spin
+
+        :Parameters:
+
+          kpt : integer
+            index of the IBZ kpoint you want the occupation of
+
+          spin : integer
+            0 or 1
+        '''
+        
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        occ = nc.variables['OccupationNumbers'][:][-1][kpt, spin]
+        nc.close()
+        return occ
+
+    def get_xc_energies(self, *functional):
+        """
+        Get energies for different functionals self-consistent and
+        non-self-consistent.
+
+        :Parameters:
+
+          functional : strings
+            some set of 'PZ','VWN','PW91','PBE','revPBE', 'RPBE'
+
+        This function returns the self-consistent energy and/or
+	energies associated with various functionals. 
+        The functionals are currently PZ,VWN,PW91,PBE,revPBE, RPBE.
+        The different energies may be useful for calculating improved
+	adsorption energies as in B. Hammer, L.B. Hansen and
+	J.K. Norskov, Phys. Rev. B 59,7413. 
+        Examples: 
+        get_xcenergies() #returns all the energies
+        get_xcenergies('PBE') # returns the PBE total energy
+        get_xcenergies('PW91','PBE','revPBE') # returns a
+	# list of energies in the order asked for
+        """
+        
+        if self.calculation_required():
+            self.calculate()
+
+        nc = netCDF(self.get_nc(), 'r')
+
+        funcenergies = nc.variables['EvaluateTotalEnergy'][:][-1]
+        xcfuncs = nc.variables['EvalFunctionalOfDensity_XC'][:]
+
+        nc.close()
+        
+        xcfuncs = [xc.tostring().strip() for xc in xcfuncs]
+        edict = dict(zip(xcfuncs, funcenergies))
+
+        if len(functional) == 0:
+            #get all energies by default
+            functional = xcfuncs
+
+        return [edict[xc] for xc in functional]
+
+    # break of compatibility
+    def get_ados_data(self,
+                      atoms,
+                      orbitals,
+                      cutoff,
+                      spin):
+        '''get atom projected data
+
+        :Parameters:
+
+          atoms  
+              list of atom indices (integers)
+
+          orbitals
+              list of strings
+              ['s','p','d'],
+              ['px','py','pz']
+              ['d_zz', 'dxx-yy', 'd_xy', 'd_xz', 'd_yz']
+
+          cutoff : string
+            cutoff radius you want the results for 'short' or 'infinite'
+
+          spin
+            : list of integers
+            spin you want the results for
+            [0] or [1] or [0,1] for both
+
+        returns (egrid, ados)
+        egrid has the fermi level at 0 eV
+        '''
+              
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        omapvar = nc.variables['AtomProjectedDOS_OrdinalMap']
+        omap = omapvar[:] #indices
+        c = omapvar.AngularChannels
+        channels = [x.strip() for x in c.split(',')] #channel names
+        #this has dimensions(nprojections, nspins, npoints)
+        ados = nc.variables['AtomProjectedDOS_EnergyResolvedDOS'][:]
+        #this is the energy grid for all the atoms
+        egrid = nc.variables['AtomProjectedDOS_EnergyGrid'][:]
+        nc.close()
+
+        #it is apparently not necessary to normalize the egrid to
+        #the Fermi level. the data is already for ef = 0.
+
+        #get list of orbitals, replace 'p' and 'd' in needed
+        orbs = []
+        for o in orbitals:
+            if o == 'p':
+                orbs += ['p_x', 'p_y', 'p_z']
+            elif o == 'd':
+                orbs += ['d_zz', 'dxx-yy', 'd_xy', 'd_xz', 'd_yz']
+            else:
+                orbs += [o]
+
+        orbinds = [channels.index(x) for x in orbs]
+
+        cutdict = {'infinite':0,
+                   'short':1}
+
+        icut = cutdict[cutoff]
+
+        ydata = np.zeros(len(egrid), np.float)
+        
+        for atomind in atoms:
+            for oi in orbinds:
+                ind = omap[atomind, icut, oi]
+
+                for si in spin:
+                    ydata += ados[ind, si]
+
+        return (egrid, ydata)
+
+    def get_all_eigenvalues(self, spin=0):
+        '''return all the eigenvalues at all the kpoints for a spin.
+
+        :Parameters:
+
+          spin : integer
+            which spin the eigenvalues are for'''
+        
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        ev = nc.variables['EigenValues'][:][-1][:, spin]
+        nc.close()
+        return ev
+    
+    def get_eigenvalues(self, kpt=0, spin=0):
+        '''return the eigenvalues for a kpt and spin
+
+        :Parameters:
+
+          kpt : integer
+            index of the IBZ kpoint
+
+          spin : integer
+            which spin the eigenvalues are for'''
+        
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        ev = nc.variables['EigenValues'][:][-1][kpt, spin]
+        nc.close()
+        return ev
+    
+    def get_k_point_weights(self):
+        'return the weights on the IBZ kpoints'
+        
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        kw = nc.variables['KpointWeight'][:]
+        nc.close()
+        return kw
+
+    def get_magnetic_moment(self):
+        'calculates the magnetic moment (Bohr-magnetons) of the supercell'
+
+        if not self.get_spin_polarized():
+            return None
+        
+        if self.calculation_required():
+            self.calculate()
+
+        nibzk = len(self.get_ibz_kpoints())
+        ibzkw = self.get_k_point_weights()
+        spinup, spindn = 0.0, 0.0
+
+        for k in range(nibzk):
+
+            spinup += self.get_occupation_numbers(k, 0).sum()*ibzkw[k]
+            spindn += self.get_occupation_numbers(k, 1).sum()*ibzkw[k]
+
+        return (spinup - spindn)
+
+    def get_number_of_spins(self):
+        'if spin-polarized returns 2, if not returns 1'
+        
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        spv = nc.variables['ElectronicBands']
+        nc.close()
+
+        if hasattr(spv, 'SpinPolarization'):
+            return spv.SpinPolarization
+        else:
+            return 1
+
+    def get_ibz_kpoints(self):
+        'return list of kpoints in the irreducible brillouin zone'
+        
+        if self.calculation_required():
+            self.calculate()
+        nc = netCDF(self.get_nc(), 'r')
+        ibz = nc.variables['IBZKpoints'][:]
+        nc.close()
+        return ibz
+
+    get_ibz_k_points = get_ibz_kpoints
+
+    def get_bz_k_points(self):
+        'return list of kpoints in the Brillouin zone'
+        
+        nc = netCDF(self.get_nc(), 'r')
+        if 'BZKpoints' in nc.variables:
+            bz = nc.variables['BZKpoints'][:]
+        else:
+            bz = None
+        nc.close()
+        return bz
+    
+    def get_effective_potential(self, spin=1):
+        '''
+        returns the realspace local effective potential for the spin.
+        the units of the potential are eV
+
+        :Parameters:
+
+          spin : integer
+             specify which spin you want, 0 or 1
+          
+        '''
+        
+        if self.calculation_required():
+            self.calculate()
+            
+        nc = netCDF(self.get_nc(), 'r')
+        efp = np.transpose(nc.variables['EffectivePotential'][:][spin])
+        nc.close()
+        fftgrids = self.get_fftgrid()
+        hardgrid = fftgrids['hard']
+        x, y, z = self.get_ucgrid(hardgrid)
+        return (x, y, z, efp)
+        
+    def get_electrostatic_potential(self, spin=0):
+        '''get electrostatic potential
+
+        Netcdf documentation::
+        
+          double ElectrostaticPotential(number_of_spin,
+                                        hardgrid_dim3,
+                                        hardgrid_dim2,
+                                        hardgrid_dim1) ;
+                 ElectrostaticPotential:
+                     Description = "realspace local effective potential" ;
+                     unit = "eV" ;
+                     
+        '''
+        
+        if self.calculation_required():
+            self.calculate()
+            
+        nc = netCDF(self.get_nc(), 'r')
+        esp = np.transpose(nc.variables['ElectrostaticPotential'][:][spin])
+        nc.close()
+        fftgrids = self.get_fftgrid()
+
+        x, y, z = self.get_ucgrid(fftgrids['hard'])
+        
+        return (x, y, z, esp)
+    
+    def get_charge_density(self, spin=0):
+        '''
+        return x,y,z,charge density data
+        
+        x,y,z are grids sampling the unit cell
+        cd is the charge density data
+
+        netcdf documentation::
+        
+          ChargeDensity(number_of_spin,
+                        hardgrid_dim3,
+                        hardgrid_dim2,
+                        hardgrid_dim1)
+          ChargeDensity:Description = "realspace charge density" ;
+                  ChargeDensity:unit = "-e/A^3" ;
+
+        '''
+        
+        if self.calculation_required():
+            self.calculate()
+            
+        nc = netCDF(self.get_nc(), 'r')
+     
+        cd = np.transpose(nc.variables['ChargeDensity'][:][spin])
+
+        #I am not completely sure why this has to be done
+        #it does give units of electrons/ang**3
+        vol = self.get_atoms().get_volume()
+        cd /= vol
+        nc.close()
+        grids = self.get_fftgrid()
+
+        x, y, z = self.get_ucgrid(grids['hard'])
+        return x, y, z, cd
+
+    def get_ucgrid(self, dims):
+        '''Return X,Y,Z grids for uniform sampling of the unit cell
+
+        dims = (n0,n1,n2)
+
+        n0 points along unitcell vector 0
+        n1 points along unitcell vector 1
+        n2 points along unitcell vector 2
+        '''
+        
+        n0, n1, n2 = dims
+        
+        s0 = 1.0/n0
+        s1 = 1.0/n1
+        s2 = 1.0/n2
+
+        X, Y, Z = np.mgrid[0.0:1.0:s0,
+                          0.0:1.0:s1,
+                          0.0:1.0:s2]
+        
+        C = np.column_stack([X.ravel(),
+                             Y.ravel(),
+                             Z.ravel()])
+        
+        atoms = self.get_atoms()
+        uc = atoms.get_cell()
+        real = np.dot(C, uc)
+
+        #now convert arrays back to unitcell shape
+        RX = np.reshape(real[:, 0], (n0, n1, n2))
+        RY = np.reshape(real[:, 1], (n0, n1, n2))
+        RZ = np.reshape(real[:, 2], (n0, n1, n2))
+        return (RX, RY, RZ)
+
+    def get_number_of_grid_points(self):
+        'return soft fft grid'
+        
+        # needed by ase.dft.wannier
+        fftgrids = self.get_fftgrid()
+        return np.array(fftgrids['soft'])
+    
+    def get_wannier_localization_matrix(self, nbands, dirG, kpoint,
+                                        nextkpoint, G_I, spin):
+        'return wannier localization  matrix'
+
+        if self.calculation_required():
+            self.calculate()
+
+        if not hasattr(self, 'wannier'):
+            from utils.wannier import Wannier
+            self.wannier = Wannier(self)
+            self.wannier.set_bands(nbands)
+            self.wannier.set_spin(spin)
+        locmat = self.wannier.get_zi_bloch_matrix(dirG,
+                                                  kpoint,
+                                                  nextkpoint,
+                                                  G_I)
+        return locmat
+
+    def initial_wannier(self,
+                        initialwannier,
+                        kpointgrid,
+                        fixedstates,
+                        edf,
+                        spin):
+        'return initial wannier'
+
+        if self.calculation_required():
+            self.calculate()
+
+        if not hasattr(self, 'wannier'):
+            from utils.wannier import Wannier
+            self.wannier = Wannier(self)
+
+        self.wannier.set_data(initialwannier)
+        self.wannier.set_k_point_grid(kpointgrid)
+        self.wannier.set_spin(spin)
+
+        waves = [[self.get_reciprocal_bloch_function(band=band,
+                                                     kpt=kpt,
+                                                     spin=spin)
+                  for band in range(self.get_nbands())]
+                  for kpt in range(len(self.get_ibz_k_points()))]
+
+        self.wannier.setup_m_matrix(waves, self.get_bz_k_points())
+
+        #lfn is too keep line length below 78 characters
+        lfn = self.wannier.get_list_of_coefficients_and_rotation_matrices
+        c, U = lfn((self.get_nbands(), fixedstates, edf))
+
+        U = np.array(U)
+        for k in range(len(c)):
+            c[k] = np.array(c[k])
+        return c, U
+
+    def get_dipole_moment(self,atoms=None):
+        '''
+        return dipole moment of unit cell
+
+        Defined by the vector connecting the center of electron charge
+        density to the center of nuclear charge density.
+
+        Units = eV*angstrom
+
+        1 Debye = 0.208194 eV*angstrom
+
+        '''
+        if self.calculation_required():
+            self.calculate()
+
+        if atoms is None:
+            atoms = self.get_atoms()
+        
+        #center of electron charge density
+        x, y, z, cd = self.get_charge_density()
+
+        n1, n2, n3 = cd.shape
+        nelements = n1*n2*n3
+        voxel_volume = atoms.get_volume()/nelements
+        total_electron_charge = -cd.sum()*voxel_volume
+
+        
+        electron_density_center = np.array([(cd*x).sum(),
+                                            (cd*y).sum(),
+                                            (cd*z).sum()])
+        electron_density_center *= voxel_volume
+        electron_density_center /= total_electron_charge
+       
+        electron_dipole_moment = electron_density_center*total_electron_charge
+        electron_dipole_moment *= -1.0 #we need the - here so the two
+                                        #negatives don't cancel
+        # now the ion charge center
+        psps = self.get_pseudopotentials()
+        ion_charge_center = np.array([0.0, 0.0, 0.0])
+        total_ion_charge = 0.0
+        for atom in atoms:
+            Z = self.get_psp_nuclear_charge(psps[atom.symbol])
+            total_ion_charge += Z
+            pos = atom.get_position()
+            ion_charge_center += Z*pos
+
+        ion_charge_center /= total_ion_charge
+        ion_dipole_moment = ion_charge_center*total_ion_charge
+
+        dipole_vector = (ion_dipole_moment + electron_dipole_moment)
+        return dipole_vector
+
+    
+    def get_reciprocal_bloch_function(self, band=0, kpt=0, spin=0):
+        '''return the reciprocal bloch function. Need for Jacapo
+        Wannier class.'''
+        
+        if self.calculation_required():
+            self.calculate()
+
+        nc = netCDF(self.get_nc(), 'r')
+
+        # read reciprocal bloch function
+        npw = nc.variables['NumberPlaneWavesKpoint'][:]
+        bf = nc.variables['WaveFunction'][kpt, spin, band]
+        wflist = np.zeros(npw[kpt], np.complex)
+        wflist.real = bf[0:npw[kpt], 1]
+        wflist.imag = bf[0:npw[kpt], 0]
+
+        nc.close()
+
+        return wflist
+
+    def get_reciprocal_fft_index(self, kpt=0):
+        '''return the Wave Function FFT Index'''
+        
+        nc = netCDF(self.get_nc(), 'r')
+        recind = nc.variables['WaveFunctionFFTindex'][kpt, :, :]
+        nc.close()
+        return recind
+
+    def get_ensemble_coefficients(self):
+        'returns exchange correlation ensemble coefficients'
+        
+        # adapted from ASE/dacapo.py
+        # def GetEnsembleCoefficients(self):
+        #     self.Calculate()
+        #     E = self.GetPotentialEnergy()
+        #     xc = self.GetNetCDFEntry('EnsembleXCEnergies')
+        #     Exc = xc[0]
+        #     exc_c = self.GetNetCDFEntry('EvaluateCorrelationEnergy')
+        #     exc_e =  self.GetNetCDFEntry('EvaluateExchangeEnergy')
+        #     exc = exc_c + exc_e
+        #     if self.GetXCFunctional() == 'RPBE':
+        #             Exc = exc[-1][-1]
+        # 
+        #     E0 = xc[1]       # Fx = 0
+        # 
+        #     diff0 = xc[2] # - Exc
+        #     diff1 = xc[3] # - Exc
+        #     diff2 = xc[4] # - Exc
+        #     coefs = (E + E0 - Exc,diff0-E0 ,diff1-E0,diff2-E0)
+        #     print 'ensemble: (%.9f, %.9f, %.9f, %.9f)'% coefs
+        #     return num.array(coefs)
+        if self.calculation_required():
+            self.calculate()
+
+        E = self.get_potential_energy()
+        nc = netCDF(self.get_nc(), 'r')
+        if 'EnsembleXCEnergies' in nc.variables:
+            v = nc.variables['EnsembleXCEnergies']
+            xc = v[:]
+
+        EXC = xc[0]
+
+        if 'EvaluateCorrelationEnergy' in nc.variables:
+            v = nc.variables['EvaluateCorrelationEnergy']
+            exc_c = v[:]
+            
+        if 'EvaluateExchangeEnergy' in nc.variables:
+            v = nc.variables['EvaluateExchangeEnergy']
+            exc_e = v[:]
+
+        exc = exc_c + exc_e
+
+        if self.get_xc == 'RPBE':
+            EXC = exc[-1][-1]
+            
+        E0 = xc[1]    # Fx = 0
+
+        diff0 = xc[2] # - Exc
+        diff1 = xc[3] # - Exc
+        diff2 = xc[4] # - Exc
+        coefs = (E + E0 - EXC, diff0-E0, diff1-E0, diff2-E0)
+        log.info('ensemble: (%.9f, %.9f, %.9f, %.9f)'% coefs)
+        return np.array(coefs)
+
+    def get_pseudo_wave_function(self, band=0, kpt=0, spin=0, pad=True):
+
+        '''return the pseudo wavefunction'''
+        
+        # pad=True does nothing here.
+        if self.calculation_required():
+            self.calculate()
+
+        ibz = self.get_ibz_kpoints()
+
+        #get the reciprocal bloch function
+        wflist = self.get_reciprocal_bloch_function(band=band,
+                                                    kpt=kpt,
+                                                    spin=spin)
+        # wflist == Reciprocal Bloch Function
+ 
+        recind = self. get_reciprocal_fft_index(kpt)
+        grids = self.get_fftgrid()
+        softgrid = grids['soft']
+        
+        # GetReciprocalBlochFunctionGrid
+        wfrec = np.zeros((softgrid), np.complex) 
+
+        for i in xrange(len(wflist)):
+            wfrec[recind[0, i]-1,
+                  recind[1, i]-1,
+                  recind[2, i]-1] = wflist[i]
+
+        # calculate Bloch Function
+        wf = wfrec.copy() 
+        dim = wf.shape
+        for i in range(len(dim)):
+            wf = np.fft.fft(wf, dim[i], axis=i)
+
+        #now the phase function to get the bloch phase
+        basis = self.get_atoms().get_cell()
+        kpoint = np.dot(ibz[kpt], basis) #coordinates of relevant
+                                         #kpoint in cartesian
+                                         #coordinates
+        def phasefunction(coor):
+            'return phasefunction'
+            pf = np.exp(1.0j*np.dot(kpoint, coor))
+            return pf
+
+        # Calculating the Bloch phase at the origin (0,0,0) of the grid
+        origin = np.array([0., 0., 0.])
+        blochphase = phasefunction(origin)
+        spatialshape = wf.shape[-len(basis):]
+        gridunitvectors = np.array(map(lambda unitvector,
+                                       shape:unitvector/shape,
+                                       basis,
+                                       spatialshape))
+
+        for dim in range(len(spatialshape)):
+            # Multiplying with the phase at the origin
+            deltaphase = phasefunction(gridunitvectors[dim])
+            # and calculating phase difference between each point
+            newphase = np.fromfunction(lambda i, phase=deltaphase:phase**i,
+                                     (spatialshape[dim],))
+            blochphase = np.multiply.outer(blochphase, newphase)
+
+        return blochphase*wf
+
+    def get_wave_function(self, band=0, kpt=0, spin=0):
+        '''return the wave function. This is the pseudo wave function
+        divided by volume.'''
+        
+        pwf = self.get_pseudo_wave_function(band=band,
+                                            kpt=kpt,
+                                            spin=spin,
+                                            pad=True)
+        vol = self.get_atoms().get_volume()
+        fftgrids = self.get_fftgrid()
+        softgrid = fftgrids['soft']
+    
+        x, y, z = self.get_ucgrid((softgrid))
+
+        return x, y, z, pwf/np.sqrt(vol)
+
+    def strip(self):
+        '''remove all large memory nc variables not needed for
+        anything I use very often. 
+        '''
+        self.delete_ncattdimvar(self.nc,
+                                ncdims=['max_projectors_per_atom'],
+                                ncvars=['WaveFunction',
+                                        'WaveFunctionFFTindex',
+                                        'NumberOfNLProjectors',
+                                        'NLProjectorPsi',
+                                        'TypeNLProjector1',
+                                        'NumberofNLProjectors',
+                                        'PartialCoreDensity',
+                                        'ChargeDensity',
+                                        'ElectrostaticPotential',
+                                        'StructureFactor'])
+
+# shortcut function names
+Jacapo.get_cd = Jacapo.get_charge_density
+Jacapo.get_wf = Jacapo.get_wave_function
+Jacapo.get_esp = Jacapo.get_electrostatic_potential
+Jacapo.get_occ = Jacapo.get_occupation_numbers
+Jacapo.get_ef = Jacapo.get_fermi_level
+Jacapo.get_number_of_bands = Jacapo.get_nbands
+Jacapo.set_pseudopotentials = Jacapo.set_psp
Index: ase/calculators/jacapo/__init__.py
===================================================================
--- ase/calculators/jacapo/__init__.py	(revision 0)
+++ ase/calculators/jacapo/__init__.py	(revision 1)
@@ -0,0 +1,16 @@
+import Scientific
+import string
+
+from ase.test import NotAvailable
+try:
+    version = string.split(Scientific.__version__,".")
+    if map(int,version) < [2,8]:
+        print 'your ScientifPython version is: ',Scientific.__version__ 
+        print 'ScientificPython 2.8 or greater required for numpy support in NetCDF'
+        raise NotAvailable('ScientificPython version 2.8 or greater is required')
+except AttributeError:
+    print 'It appears your ScientificPython has no version.'
+    print 'That probably means it is not 2.8, which is required'
+    raise Exception
+
+from jacapo import *
Index: ase/calculators/jacapo/setup.py
===================================================================
--- ase/calculators/jacapo/setup.py	(revision 0)
+++ ase/calculators/jacapo/setup.py	(revision 1)
@@ -0,0 +1,39 @@
+#!/usr/bin/env python
+
+from distutils.core import setup
+from glob import glob
+from os.path import join
+
+import os
+import sys
+
+long_description = """\
+Jacapo is a python package providing an interface to Dacapo that
+is compatible with the open source Atomic Simulation
+Environment in the python scripting language."""
+
+if sys.version_info < (2, 3, 0, 'final', 0):
+    raise SystemExit, 'Python 2.3 or later is required!'
+
+packages = ['Jacapo']
+
+tools = ['tools/ncsum',
+         'tools/plotnetcdf',
+         'tools/pysub',
+         'tools/qn_relax',
+         'tools/stripnetcdf']
+
+# Get the current version number:
+execfile('version.py')
+
+setup(name = 'python-Jacapo',
+      version=version,
+      description='Jacapo - ase + Dacapo',
+      url='http://www.fysik.dtu.dk/Campos/ase',
+      maintainer='John Kitchin',
+      maintainer_email='jkitchin@andrew.cmu.edu',
+      license='LGPL',
+      platforms=['linux'],
+      packages=packages,
+      scripts=tools,
+      long_description=long_description)
Index: ase/calculators/jacapo/lda_psp.py
===================================================================
--- ase/calculators/jacapo/lda_psp.py	(revision 0)
+++ ase/calculators/jacapo/lda_psp.py	(revision 1)
@@ -0,0 +1,9 @@
+# Copyright (C) 2005 jrk
+
+""" Default pseudopotential paths are defined here
+"""
+
+__docformat__ = 'reStructuredText'
+
+defaultpseudopotentials = {}
+
Index: ase/calculators/jacapo/pw91_psp.py
===================================================================
--- ase/calculators/jacapo/pw91_psp.py	(revision 0)
+++ ase/calculators/jacapo/pw91_psp.py	(revision 1)
@@ -0,0 +1,61 @@
+""" Default pseudopotential paths are defined here
+"""
+
+__docformat__ = 'reStructuredText'
+
+defaultpseudopotentials = {'H':'ch_e9g4.pseudo', #  H
+                           'Li':'Li_us_cc.pseudo', #  Li
+                           'Be':'Be_us_cc.pseudo', #  Be
+                           'B':'B_us_cc.pseudo', #  B
+                           'C':'C_us_gga.pseudo', #  C
+                           'N':'N_us.pseudo', #  N
+                           'O':'co_gef_e13_gga.pseudo', #  O
+                           'F':'F_pw91_us_7.3.4.pseudo', #  F
+                           'Na':'Na_tm_lda_cc.pseudo', #  Na
+                           'Mg':'mg_us_gga.pseudo', #  Mg
+                           'Al':'Al_us_gga_org.pseudo', #  Al
+                           'Si':'csi_e8ag4.pseudo', #  Si
+                           'P':'P_us.pseudo', #  P
+                           'S':'S_tm.pseudo', #  S
+                           'Cl':'Cl_us_gga.pseudo', #  Cl
+                           'K':'k_us_gga.pseudo', #  K
+                           'Ca':'Ca_us_cc_pw91.pseudo', #  Ca
+                           'Sc':'Sc_us_cc_pw91.pseudo', #  Sc
+                           'Ti':'ti_us_gga.pseudo', #  Ti
+                           'V':'V_us_pw91_13elec.pseudo', #  V
+                           'Cr':'Cr_us_pw91_14elec.pseudo', #  Cr
+                           'Mn':'Mn_us_gga.pseudo', #  Mn
+                           'Fe':'Fe_us_gga_d2.1.8.pseudo', #  Fe
+                           'Co':'Co_us_gga.pseudo', #  Co
+                           'Ni':'Ni_us_gga.pseudo', #  Ni
+                           'Cu':'Cu_us_gga.pseudo', #  Cu
+                           'Zn':'zn_us_gga.pseudo', #  Zn
+                           'Ga':'ga_pw91_us_13elec.pseudo', #  Ga
+                           'Ge':'ge_pw91_us_14elec.pseudo', #  Ge
+                           'As':'as_pw91_us_15elec.pseudo', #  As
+                           'Kr':'Kr_us_gga.pseudo', #  Kr
+                           'Sr':'Sr_us_cc_pw91.pseudo', #  Sr
+                           'Y':'Y_us_cc_pw91.pseudo', #  Y
+                           'Zr':'Zr_us_gga.pseudo', #  Zr
+                           'Nb':'Nb_us_pw91_13elec.pseudo', #  Nb
+                           'Mo':'Mo_us.pseudo', #  Mo
+                           'Ru':'Ru_us_gga.pseudo', #  Ru
+                           'Rh':'Rh_us_gga_fl.pseudo', #  Rh
+                           'Pd':'pd_us_gga.pseudo', #  Pd
+                           'Ag':'ag_us.pseudo', #  Ag
+                           'Cd':'Cd_us_gga.pseudo', #  Cd
+                           'Sn':'sn_us_f.pseudo', #  Sn
+                           'Sb':'sb_us_gga.pseudo', #  Sb
+                           'Te':'te_tm.pseudo', #  Te
+                           'Xe':'Xe_us_gga.pseudo', #  Xe
+                           'Cs':'cs_tm_7el.pseudo', #  Cs
+                           'Ba':'Ba_us_cc_pw91.pseudo', #  Ba
+                           'La':'La_us_cc_pw91.pseudo', #  La
+                           'Ta':'Ta_us_pw91_13elec.pseudo', #  Ta
+                           'W':'W_us_pw91_6elec.pseudo', #  W
+                           'Re':'re_us_gga_7elec.pseudo', #  Re
+                           'Os':'os_us_gga_7elec_7.3.4.pseudo', #  Os
+                           'Ir':'ir_us_gga_flocal.pseudo', #  Ir
+                           'Pt':'pt_us_gga.pseudo', #  Pt
+                           'Au':'Au_us_gga.pseudo', #  Au
+                           'Bi':'Bi_us_gga.pseudo'}
Index: ase/calculators/turbomole.py
===================================================================
--- ase/calculators/turbomole.py	(revision 0)
+++ ase/calculators/turbomole.py	(revision 1)
@@ -0,0 +1,216 @@
+"""This module defines an ASE interface to Turbomole
+
+http://www.turbomole.com/
+"""
+
+
+import os, sys, string
+
+from ase.data import chemical_symbols
+from ase.units import Hartree, Bohr
+from ase.io.turbomole import write_turbomole
+
+import numpy as np
+
+class Turbomole:
+    """Class for doing Turbomole calculations.
+    """
+    def __init__(self, label='turbomole'):
+        """Construct TURBOMOLE-calculator object.
+
+        Parameters
+        ==========
+        label: str
+            Prefix to use for filenames (label.txt, ...).
+            Default is 'turbomole'.
+
+        Examples
+        ========
+        This is poor man's version of ASE-Turbomole:
+
+        First you do a normal turbomole preparation using turbomole's
+        define-program for the initial and final states.
+
+        (for instance in subdirectories Initial and Final)
+
+        Then relax the initial and final coordinates with desired constraints
+        using standard turbomole.
+
+        Copy the relaxed initial and final coordinates 
+        (initial.coord and final.coord)
+        and the turbomole related files (from the subdirectory Initial) 
+        control, coord, alpha, beta, mos, basis, auxbasis etc to the directory
+        you do the diffusion run.
+        
+        For instance:
+        cd $My_Turbomole_diffusion_directory
+        cd Initial; cp control alpha beta mos basis auxbasis ../;
+        cp coord ../initial.coord;
+        cd ../;
+        cp Final/coord ./final.coord;
+        mkdir Gz ; cp * Gz ; gzip -r Gz
+        
+        from ase.io import read
+        from ase.calculators.turbomole import Turbomole
+        a = read('coord', index=-1, format='turbomole')
+        calc = Turbomole()
+        a.set_calculator(calc)
+        e = a.get_potential_energy()
+        
+        """
+        
+        # get names of executables for turbomole energy and forces
+        # get the path
+
+        os.system('rm -f sysname.file; sysname > sysname.file')
+        f = open('sysname.file')
+        architechture = f.readline()[:-1]
+        f.close()
+        tmpath = os.environ['TURBODIR']
+        pre = tmpath + '/bin/' + architechture + '/'
+
+
+        if os.path.isfile('control'):
+            f = open('control')
+        else:
+            print 'File control is missing'
+            raise RuntimeError, \
+                'Please run Turbomole define and come thereafter back'
+        lines = f.readlines()
+        f.close()
+        self.tm_program_energy=pre+'dscf'
+        self.tm_program_forces=pre+'grad'        
+        for line in lines:
+            if line.startswith('$ricore'):
+                self.tm_program_energy=pre+'ridft'
+                self.tm_program_forces=pre+'rdgrad'
+
+        self.label = label
+        self.converged = False
+        #clean up turbomole energy file
+        os.system('rm -f energy; touch energy')
+
+        #turbomole has no stress
+        self.stress = np.empty((3, 3))
+        
+
+    def update(self, atoms):
+        """Energy and forces are calculated when atoms have moved
+        by calling self.calculate
+        """
+        if (not self.converged or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        self.numbers = atoms.get_atomic_numbers().copy()
+        self.species = []
+        for a, Z in enumerate(self.numbers):
+            self.species.append(Z)
+        self.converged = False
+        
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.etotal
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+    
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress.copy()
+
+    def calculate(self, atoms):
+        """Total Turbomole energy is calculated (to file 'energy'
+        also forces are calculated (to file 'gradient')
+        """
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+
+        #write current coordinates to file 'coord' for Turbomole
+        write_turbomole('coord', atoms)
+
+
+        #Turbomole energy calculation
+        os.system('rm -f output.energy.dummy; \
+                      '+ self.tm_program_energy +'> output.energy.dummy')
+
+        #check that the energy run converged
+        if os.path.isfile('dscf_problem'):
+            print 'Turbomole scf energy calculation did not converge'
+            print 'issue command t2x -c > last.xyz'
+            print 'and check geometry last.xyz and job.xxx or statistics'
+            raise RuntimeError, \
+                'Please run Turbomole define and come thereafter back'
+
+
+        self.read_energy()
+
+        #Turbomole atomic forces calculation
+        #killing the previous gradient file because 
+        #turbomole gradients are affected by the previous values
+        os.system('rm -f gradient; rm -f output.forces.dummy; \
+                      '+ self.tm_program_forces +'> output.forces.dummy')
+
+        self.read_forces(atoms)
+
+        self.converged = True
+
+        
+    def read_energy(self):
+        """Read Energy from Turbomole energy file."""
+        text = open('energy', 'r').read().lower()
+        lines = iter(text.split('\n'))
+
+        # Energy:
+        for line in lines:
+            if line.startswith('$end'):
+                break
+            elif line.startswith('$'):
+                pass
+            else:
+                #print 'LINE',line
+                energy_tmp = float(line.split()[1])
+        #print 'energy_tmp',energy_tmp
+        self.etotal = energy_tmp * Hartree
+
+
+    def read_forces(self,atoms):
+        """Read Forces from Turbomole gradient file."""
+
+        file = open('gradient','r')
+        line=file.readline()
+        line=file.readline()
+        tmpforces = np.array([[0, 0, 0]])
+        while line:
+            if 'cycle' in line:
+                for i, dummy in enumerate(atoms):
+                            line=file.readline()
+                forces_tmp=[]
+                for i, dummy in enumerate(atoms):
+                            line=file.readline()
+                            line2=string.replace(line,'D','E')
+                            #tmp=np.append(forces_tmp,np.array\
+                            #      ([[float(f) for f in line2.split()[0:3]]]))
+                            tmp=np.array\
+                                ([[float(f) for f in line2.split()[0:3]]])
+                            tmpforces=np.concatenate((tmpforces,tmp))  
+            line=file.readline()
+            
+
+        #note the '-' sign for turbomole, to get forces
+        self.forces = (-np.delete(tmpforces, np.s_[0:1], axis=0))*Hartree/Bohr
+
+        #print 'forces', self.forces
+
+    def read(self):
+        """Dummy stress for turbomole"""
+        self.stress = np.empty((3, 3))
Index: ase/calculators/vasp.py
===================================================================
--- ase/calculators/vasp.py	(revision 0)
+++ ase/calculators/vasp.py	(revision 1)
@@ -0,0 +1,1394 @@
+# Copyright (C) 2008 CSC - Scientific Computing Ltd.
+"""This module defines an ASE interface to VASP.
+
+Developed on the basis of modules by Jussi Enkovaara and John
+Kitchin.  The path of the directory containing the pseudopotential 
+directories (potpaw,potpaw_GGA, potpaw_PBE, ...) should be set 
+by the environmental flag $VASP_PP_PATH. 
+
+The user should also set the environmental flag $VASP_SCRIPT pointing
+to a python script looking something like::
+
+   import os
+   exitcode = os.system('vasp')
+
+Alternatively, user can set the environmental flag $VASP_COMMAND pointing
+to the command use the launch vasp e.g. 'vasp' or 'mpirun -n 16 vasp'
+
+http://cms.mpi.univie.ac.at/vasp/
+
+-Jonas Bjork j.bjork@liverpool.ac.uk
+"""
+
+import os
+import sys
+from general import Calculator
+from os.path import join, isfile, islink
+
+import numpy as np
+
+import ase
+
+# Parameters that can be set in INCAR. The values which are None
+# are not written and default parameters of VASP are used for them.
+
+float_keys = [
+    'aexx',       # Fraction of exact/DFT exchange
+    'aggac',      # Fraction of gradient correction to correlation
+    'aggax',      # Fraction of gradient correction to exchange
+    'aldac',      # Fraction of LDA correlation energy
+    'amix',       #
+    'bmix',       # tags for mixing
+    'emax',       # energy-range for DOSCAR file
+    'emin',       #
+    'enaug',      # Density cutoff
+    'encut',      # Planewave cutoff
+    'encutfock',  # FFT grid in the HF related routines 
+    'hfscreen',   # attribute to change from PBE0 to HSE
+    'potim',      # time-step for ion-motion (fs)
+    'nelect',     # total number of electrons
+    'pomass',     # mass of ions in am
+    'sigma',      # broadening in eV
+    'time',       # special control tag
+    'zval',       # ionic valence
+    ]
+
+exp_keys = [
+    'ediff',      # stopping-criterion for electronic upd.
+    'ediffg',     # stopping-criterion for ionic upd.
+    'symprec',    # precession in symmetry routines
+]
+
+string_keys = [
+    'algo',       # algorithm: Normal (Davidson) | Fast | Very_Fast (RMM-DIIS)
+    'gga',        # xc-type: PW PB LM or 91
+    'prec',       # Precission of calculation (Low, Normal, Accurate)
+    'system',     # name of System
+    'tebeg',      #
+    'teend',      # temperature during run
+]
+
+int_keys = [
+    'ialgo',      # algorithm: use only 8 (CG) or 48 (RMM-DIIS)
+    'ibrion',     # ionic relaxation: 0-MD 1-quasi-New 2-CG
+    'idipol',     # monopol/dipol and quadropole corrections
+    'iniwav',     # initial electr wf. : 0-lowe 1-rand
+    'isif',       # calculate stress and what to relax
+    'ismear',     # part. occupancies: -5 Blochl -4-tet -1-fermi 0-gaus >0 MP
+    'ispin',      # spin-polarized calculation
+    'istart',     # startjob: 0-new 1-cont 2-samecut
+    'isym',       # symmetry: 0-nonsym 1-usesym
+    'iwavpr',     # prediction of wf.: 0-non 1-charg 2-wave 3-comb
+    'lmaxmix',    # 
+    'lorbit',     # create PROOUT
+    'ngx',        # FFT mesh for wavefunctions, x
+    'ngxf',       # FFT mesh for charges x
+    'ngy',        # FFT mesh for wavefunctions, y
+    'ngyf',       # FFT mesh for charges y
+    'ngz',        # FFT mesh for wavefunctions, z
+    'ngzf',       # FFT mesh for charges z
+    'nbands',     # Number of bands
+    'nblk',       # blocking for some BLAS calls (Sec. 6.5)
+    'nbmod',      # specifies mode for partial charge calculation
+    'nelm',       #
+    'nelmdl',     # nr. of electronic steps
+    'nelmin',
+    'nfree',      # number of steps per DOF when calculting Hessian using finitite differences
+    'nkred',      # define sub grid of q-points for HF with nkredx=nkredy=nkredz 
+    'nkredx',      # define sub grid of q-points in x direction for HF 
+    'nkredy',      # define sub grid of q-points in y direction for HF 
+    'nkredz',      # define sub grid of q-points in z direction for HF 
+    'npar',       # parallelization over bands
+    'nsim',       # evaluate NSIM bands simultaneously if using RMM-DIIS
+    'nsw',        # number of steps for ionic upd.
+    'nupdown',    # fix spin moment to specified value
+    'nwrite',     # verbosity write-flag (how much is written)
+    'smass',      # Nose mass-parameter (am)
+    'voskown',    # use Vosko, Wilk, Nusair interpolation
+]
+
+bool_keys = [
+    'addgrid',    # finer grid for augmentation charge density
+    'icharg',     # charge: 1-file 2-atom 10-const
+    'lasync',     # overlap communcation with calculations
+    'lcharg',     #
+    'lcorr',      # Harris-correction to forces
+    'ldipol',     # potential correction mode
+    'lelf',       # create ELFCAR
+    'lhfcalc',    # switch to turn on Hartree Fock calculations
+    'lpard',      # evaluate partial (band and/or k-point) decomposed charge density
+    'lplane',     # parallelisation over the FFT grid
+    'lscalapack', # switch off scaLAPACK
+    'lscalu',     # switch of LU decomposition
+    'lsepb',      # write out partial charge of each band seperately?
+    'lsepk',      # write out partial charge of each k-point seperately?
+    'lthomas',    # 
+    'lvtot',      # create WAVECAR/CHGCAR/LOCPOT
+    'lwave',      #
+]
+
+list_keys = [
+    'dipol',      # center of cell for dipol
+    'eint',       # energy range to calculate partial charge for
+    'ferwe',      # Fixed band occupation
+    'iband',      # bands to calculate partial charge for
+    'magmom',     # initial magnetic moments
+    'kpuse',      # k-point to calculate partial charge for
+    'ropt',       # number of grid points for non-local proj in real space
+    'rwigs',      # Wigner-Seitz radii
+]
+
+special_keys = [
+    'lreal',      # non-local projectors in real space
+]
+
+keys = [    
+    # 'NBLOCK' and KBLOCK       inner block; outer block
+    # 'NPACO' and APACO         distance and nr. of slots for P.C.
+    # 'WEIMIN, EBREAK, DEPER    special control tags
+]
+
+class Vasp(Calculator):
+    def __init__(self, restart=None, output_template='vasp', track_output=False, 
+                 **kwargs):
+        self.name = 'Vasp'
+        self.float_params = {}
+        self.exp_params = {}
+        self.string_params = {}
+        self.int_params = {}
+        self.bool_params = {}
+        self.list_params = {}
+        self.special_params = {}
+        for key in float_keys:
+            self.float_params[key] = None
+        for key in exp_keys:
+            self.exp_params[key] = None
+        for key in string_keys:
+            self.string_params[key] = None
+        for key in int_keys:
+            self.int_params[key] = None
+        for key in bool_keys:
+            self.bool_params[key] = None
+        for key in list_keys:
+            self.list_params[key] = None
+        for key in special_keys:
+            self.special_params[key] = None
+        self.string_params['prec'] = 'Normal'
+
+        self.input_params = {
+            'xc':     'PW91',  # exchange correlation potential 
+            'setups': None,    # Special setups (e.g pv, sv, ...)
+            'txt':    '-',     # Where to send information
+            'kpts':   (1,1,1), # k-points
+            'gamma':  False,   # Option to use gamma-sampling instead
+                               # of Monkhorst-Pack
+            }
+
+        self.restart = restart
+        self.track_output = track_output
+        self.output_template = output_template
+        if restart:
+            self.restart_load()
+            return
+
+        if self.input_params['xc'] not in ['PW91','LDA','PBE']:
+            raise ValueError(
+                '%s not supported for xc! use one of: PW91, LDA or PBE.' %
+                kwargs['xc'])
+        self.nbands = self.int_params['nbands']
+        self.atoms = None
+        self.positions = None
+        self.run_counts = 0
+        self.set(**kwargs)
+
+    def set(self, **kwargs):
+        for key in kwargs:
+            if self.float_params.has_key(key):
+                self.float_params[key] = kwargs[key]
+            elif self.exp_params.has_key(key):
+                self.exp_params[key] = kwargs[key]
+            elif self.string_params.has_key(key):
+                self.string_params[key] = kwargs[key]
+            elif self.int_params.has_key(key):
+                self.int_params[key] = kwargs[key]
+            elif self.bool_params.has_key(key):
+                self.bool_params[key] = kwargs[key]
+            elif self.list_params.has_key(key):
+                self.list_params[key] = kwargs[key]
+            elif self.special_params.has_key(key):
+                self.special_params[key] = kwargs[key]
+            elif self.input_params.has_key(key):
+                self.input_params[key] = kwargs[key]
+            else:
+                raise TypeError('Parameter not defined: ' + key)
+
+    def update(self, atoms):
+        if self.calculation_required(atoms, ['energy']):
+            if (self.atoms is None or
+                self.atoms.positions.shape != atoms.positions.shape):
+                # Completely new calculation just reusing the same
+                # calculator, so delete any old VASP files found.
+                self.clean()
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        """Initialize a VASP calculation
+
+        Constructs the POTCAR file. User should specify the PATH
+        to the pseudopotentials in VASP_PP_PATH environment variable"""
+
+        p = self.input_params
+
+        self.all_symbols = atoms.get_chemical_symbols()
+        self.natoms = len(atoms)
+        self.spinpol = atoms.get_initial_magnetic_moments().any()
+        atomtypes = atoms.get_chemical_symbols()
+
+        # Determine the number of atoms of each atomic species
+        # sorted after atomic species
+        special_setups = []
+        symbols = {}
+        if self.input_params['setups']:
+            for m in self.input_params['setups']:
+                try : 
+                    #special_setup[self.input_params['setups'][m]] = int(m)
+                    special_setups.append(int(m))
+                except:
+                    #print 'setup ' + m + ' is a groups setup'
+                    continue
+            #print 'special_setups' , special_setups
+
+        for m,atom in enumerate(atoms):
+            symbol = atom.get_symbol()
+            if m in special_setups:
+                pass
+            else:
+                if not symbols.has_key(symbol):
+                    symbols[symbol] = 1
+                else:
+                    symbols[symbol] += 1
+        
+        # Build the sorting list
+        self.sort = []
+        self.sort.extend(special_setups)
+
+        for symbol in symbols:
+            for m,atom in enumerate(atoms):
+                if m in special_setups: 
+                    pass
+                else:
+                    if atom.get_symbol() == symbol:
+                        self.sort.append(m)
+        self.resort = range(len(self.sort))
+        for n in range(len(self.resort)):
+            self.resort[self.sort[n]] = n
+        self.atoms_sorted = atoms[self.sort]
+
+        # Check if the necessary POTCAR files exists and
+        # create a list of their paths.
+        self.symbol_count = []
+        for m in special_setups:
+            self.symbol_count.append([atomtypes[m],1])
+        for m in symbols:
+            self.symbol_count.append([m,symbols[m]])
+        #print 'self.symbol_count',self.symbol_count 
+        sys.stdout.flush()
+        xc = '/'
+        #print 'p[xc]',p['xc']
+        if p['xc'] == 'PW91':
+            xc = '_gga/'
+        elif p['xc'] == 'PBE':
+            xc = '_pbe/'
+        if 'VASP_PP_PATH' in os.environ:
+            pppaths = os.environ['VASP_PP_PATH'].split(':')
+        else:
+            pppaths = []
+        self.ppp_list = []
+        # Setting the pseudopotentials, first special setups and 
+        # then according to symbols
+        for m in special_setups:
+            name = 'potpaw'+xc.upper() + p['setups'][str(m)] + '/POTCAR'
+            found = False
+            for path in pppaths:
+                filename = join(path, name)
+                #print 'filename', filename
+                if isfile(filename) or islink(filename):
+                    found = True
+                    self.ppp_list.append(filename)
+                    break
+                elif isfile(filename + '.Z') or islink(filename + '.Z'):
+                    found = True
+                    self.ppp_list.append(filename+'.Z')
+                    break
+            if not found:
+                raise RuntimeError('No pseudopotential for %s!' % symbol)    
+        #print 'symbols', symbols 
+        for symbol in symbols:
+            try:
+                name = 'potpaw'+xc.upper()+symbol + p['setups'][symbol]
+            except (TypeError, KeyError):
+                name = 'potpaw' + xc.upper() + symbol
+            name += '/POTCAR'
+            found = False
+            for path in pppaths:
+                filename = join(path, name)
+                #print 'filename', filename
+                if isfile(filename) or islink(filename):
+                    found = True
+                    self.ppp_list.append(filename)
+                    break
+                elif isfile(filename + '.Z') or islink(filename + '.Z'):
+                    found = True
+                    self.ppp_list.append(filename+'.Z')
+                    break
+            if not found:
+                raise RuntimeError('No pseudopotential for %s!' % symbol)
+        self.converged = None
+        self.setups_changed = None
+
+    def calculate(self, atoms):
+        """Generate necessary files in the working directory and run VASP.
+
+        The method first write VASP input files, then calls the method
+        which executes VASP. When the VASP run is finished energy, forces, 
+        etc. are read from the VASP output.
+        """
+        
+        # Write input
+        from ase.io.vasp import write_vasp
+        self.initialize(atoms)
+        write_vasp('POSCAR', self.atoms_sorted, symbol_count = self.symbol_count)
+        self.write_incar(atoms)
+        self.write_potcar()
+        self.write_kpoints()
+        self.write_sort_file()
+        
+        # Execute VASP
+        self.run()
+        # Read output
+        atoms_sorted = ase.io.read('CONTCAR', format='vasp')
+        if self.int_params['ibrion']>-1 and self.int_params['nsw']>0:
+            # Replace entire atoms object with the one read from CONTCAR.
+            atoms.__dict__ = atoms_sorted[self.resort].__dict__
+        self.converged = self.read_convergence()
+        self.set_results(atoms)
+
+    def set_results(self, atoms):
+        self.read(atoms)
+        if self.spinpol:
+            self.magnetic_moment = self.read_magnetic_moment()
+            if self.int_params['lorbit']>=10 or (self.int_params['lorbit']!=None and self.list_params['rwigs']):
+                self.magnetic_moments = self.read_magnetic_moments(atoms)
+        self.old_float_params = self.float_params.copy()
+        self.old_exp_params = self.exp_params.copy()
+        self.old_string_params = self.string_params.copy()
+        self.old_int_params = self.int_params.copy()
+        self.old_input_params = self.input_params.copy()
+        self.old_bool_params = self.bool_params.copy()
+        self.old_list_params = self.list_params.copy()
+        self.atoms = atoms.copy()
+        
+    def run(self):
+        """Method which explicitely runs VASP."""
+
+        if self.track_output:
+            self.out = self.output_template+str(self.run_counts)+'.out'
+            self.run_counts += 1
+        else:
+            self.out = self.output_template+'.out'
+        stderr = sys.stderr
+        p=self.input_params
+        if p['txt'] is None:
+            sys.stderr = devnull
+        elif p['txt'] == '-':
+            pass
+        elif isinstance(p['txt'], str):
+            sys.stderr = open(p['txt'], 'w')
+        if os.environ.has_key('VASP_COMMAND'):
+            vasp = os.environ['VASP_COMMAND']
+            exitcode = os.system('%s > %s' % (vasp, self.out))
+        elif os.environ.has_key('VASP_SCRIPT'):
+            vasp = os.environ['VASP_SCRIPT']
+            locals={}
+            execfile(vasp, {}, locals)
+            exitcode = locals['exitcode']
+        else:
+            raise RuntimeError('Please set either VASP_COMMAND or VASP_SCRIPT environment variable')
+        sys.stderr = stderr
+        if exitcode != 0:
+            raise RuntimeError('Vasp exited with exit code: %d.  ' % exitcode)
+        
+    def restart_load(self):
+        """Method which is called upon restart."""
+        
+        # Try to read sorting file
+        if os.path.isfile('ase-sort.dat'):
+            self.sort = []
+            self.resort = []
+            file = open('ase-sort.dat', 'r')
+            lines = file.readlines()
+            file.close()
+            for line in lines:
+                data = line.split()
+                self.sort.append(int(data[0]))
+                self.resort.append(int(data[1]))
+            atoms = ase.io.read('CONTCAR', format='vasp')[self.resort]
+        else:
+            atoms = ase.io.read('CONTCAR', format='vasp')
+            self.sort = range(len(atoms))
+            self.resort = range(len(atoms))
+        self.atoms = atoms.copy()
+        self.read_incar()
+        self.read_outcar()
+        self.set_results(atoms)
+        self.read_kpoints()
+        self.read_potcar()
+#        self.old_incar_params = self.incar_params.copy()
+        self.old_input_params = self.input_params.copy()
+        self.converged = self.read_convergence()
+
+    def clean(self):
+        """Method which cleans up after a calculation.
+        
+        The default files generated by Vasp will be deleted IF this
+        method is called.
+
+        """
+        files = ['CHG', 'CHGCAR', 'POSCAR', 'INCAR', 'CONTCAR', 'DOSCAR',
+                 'EIGENVAL', 'IBZKPT', 'KPOINTS', 'OSZICAR', 'OUTCAR', 'PCDAT',
+                 'POTCAR', 'vasprun.xml', 'WAVECAR', 'XDATCAR',
+                 'PROCAR', 'ase-sort.dat']
+        for f in files:
+            try:
+                os.remove(f)
+            except OSError:
+                pass
+
+    def set_atoms(self, atoms):
+        if (atoms != self.atoms):
+            self.converged = None
+        self.atoms = atoms.copy()
+
+    def get_atoms(self):
+        atoms = self.atoms.copy()
+        atoms.set_calculator(self)
+        return atoms
+
+    def get_potential_energy(self, atoms, force_consistent=False):
+        self.update(atoms)
+        if force_consistent:
+            return self.energy_free
+        else:
+            return self.energy_zero
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces
+
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress
+
+    def read_stress(self):
+        for line in open('OUTCAR'):
+            if line.find(' in kB  ') != -1:
+                stress = -np.array([float(a) for a in line.split()[2:]]) \
+                         [[0, 1, 2, 4, 5, 3]] \
+                         * 1e-1 * ase.units.GPa
+        return stress
+
+    def calculation_required(self, atoms, quantities):
+        if (self.positions is None or 
+            (self.atoms != atoms) or
+            (self.float_params != self.old_float_params) or
+            (self.exp_params != self.old_exp_params) or
+            (self.string_params != self.old_string_params) or
+            (self.int_params != self.old_int_params) or
+            (self.bool_params != self.old_bool_params) or
+            (self.list_params != self.old_list_params) or
+            (self.input_params != self.old_input_params)
+            or not self.converged):
+            return True
+        if 'magmom' in quantities:
+            return not hasattr(self, 'magnetic_moment')
+        return False
+
+    def get_number_of_bands(self):
+        return self.nbands
+
+    def get_k_point_weights(self):
+        self.update(self.atoms)
+        return self.read_k_point_weights()
+
+    def get_number_of_spins(self):
+        return 1 + int(self.spinpol)
+
+    def get_eigenvalues(self, kpt=0, spin=0):
+        self.update(self.atoms)
+        return self.read_eigenvalues(kpt, spin)
+
+    def get_fermi_level(self):
+        return self.fermi
+
+    def get_number_of_grid_points(self):
+        raise NotImplementedError
+
+    def get_pseudo_density(self):
+        raise NotImplementedError
+
+    def get_pseudo_wavefunction(self, n=0, k=0, s=0, pad=True):
+        raise NotImplementedError
+
+    def get_bz_k_points(self):
+        raise NotImplementedError
+
+    def get_ibz_kpoints(self):
+        self.update(self.atoms)
+        return self.read_ibz_kpoints()
+
+    def get_spin_polarized(self):
+        if not hasattr(self, 'spinpol'):
+            self.spinpol = self.atoms.get_initial_magnetic_moments().any()
+        return self.spinpol            
+
+    def get_magnetic_moment(self, atoms):
+        self.update(atoms)
+        return self.magnetic_moment
+        
+    def get_magnetic_moments(self, atoms):
+        if self.int_params['lorbit']>=10 or self.list_params['rwigs']:
+            self.update(atoms)
+            return self.magnetic_moments
+        else:
+            raise RuntimeError(
+                "The combination %s for lorbit with %s for rwigs not supported to calculate magnetic moments" % (p['lorbit'], p['rwigs']))
+
+    def get_dipole_moment(self, atoms):
+        """Returns total dipole moment of the system."""
+        self.update(atoms)
+        return self.dipole
+
+    def get_xc_functional(self):
+        return self.input_params['xc']
+
+    def write_incar(self, atoms, **kwargs):
+        """Writes the INCAR file."""
+        incar = open('INCAR', 'w')
+        incar.write('INCAR created by Atomic Simulation Environment\n')
+        for key, val in self.float_params.items():
+            if val is not None:
+                incar.write(' %s = %5.6f\n' % (key.upper(), val))
+        for key, val in self.exp_params.items():
+            if val is not None:
+                incar.write(' %s = %5.2e\n' % (key.upper(), val))
+        for key, val in self.string_params.items():
+            if val is not None:
+                incar.write(' %s = %s\n' % (key.upper(), val))
+        for key, val in self.int_params.items():
+            if val is not None:
+                incar.write(' %s = %d\n' % (key.upper(), val))
+        for key, val in self.list_params.items():
+            if val is not None:
+                incar.write(' %s = ' % key.upper())
+                if key in ('dipol', 'eint', 'ferwe', 'ropt', 'rwigs'):
+                    [incar.write('%.4f ' % x) for x in val]
+                elif key in ('iband', 'kpuse'):
+                    [incar.write('%i ' % x) for x in val]
+                elif key == 'magmom':
+                    list = [[1, val[0]]]
+                    for n in range(1, len(val)):
+                        if val[n] == val[n-1]:
+                            list[-1][0] += 1
+                        else:
+                            list.append([1, val[n]])
+                    [incar.write('%i*%.4f ' % (mom[0], mom[1])) for mom in list]
+                incar.write('\n')
+        for key, val in self.bool_params.items():
+            if val is not None:
+                incar.write(' %s = ' % key.upper())
+                if val:
+                    incar.write('.TRUE.\n')
+                else:
+                    incar.write('.FALSE.\n')
+        for key, val in self.special_params.items():
+            if val is not None:
+                incar.write(' %s = ' % key.upper())
+                if key is 'lreal':
+                    if type(val)==type('str'):
+                        incar.write(val+'\n')
+                    elif type(val)==type(True):
+                       if val:
+                           incar.write('.TRUE.\n')
+                       else:
+                           incar.write('.FALSE.\n') 
+        if self.spinpol and not self.int_params['ispin']:
+            incar.write(' ispin = 2\n'.upper())
+            # Write out initial magnetic moments
+            magmom = atoms.get_initial_magnetic_moments()[self.sort]
+            list = [[1, magmom[0]]]
+            for n in range(1, len(magmom)):
+                if magmom[n] == magmom[n-1]:
+                    list[-1][0] += 1
+                else:
+                    list.append([1, magmom[n]])
+            incar.write(' magmom = '.upper())
+            [incar.write('%i*%.4f ' % (mom[0], mom[1])) for mom in list]
+            incar.write('\n')
+        incar.close()
+
+    def write_kpoints(self, **kwargs):
+        """Writes the KPOINTS file."""
+        p = self.input_params
+        kpoints = open('KPOINTS', 'w')
+        kpoints.write('KPOINTS created by Atomic Simulation Environemnt\n')
+        shape=np.array(p['kpts']).shape
+        if len(shape)==1:
+            kpoints.write('0\n')
+            if p['gamma']:
+                kpoints.write('Gamma\n')
+            else:
+                kpoints.write('Monkhorst-Pack\n')
+            [kpoints.write('%i ' % kpt) for kpt in p['kpts']]
+            kpoints.write('\n0 0 0')
+        elif len(shape)==2:
+            kpoints.write('%i \n' % (len(p['kpts'])))
+            kpoints.write('Cartesian\n')
+            for n in range(len(p['kpts'])):
+                [kpoints.write('%f ' % kpt) for kpt in p['kpts'][n]]
+                if shape[1]==4:
+                    kpoints.write('\n')
+                elif shape[1]==3:
+                    kpoints.write('1.0 \n')
+        kpoints.close()
+
+    def write_potcar(self,suffix = ""):
+        """Writes the POTCAR file."""
+        import tempfile
+        potfile = open('POTCAR'+suffix,'w')
+        for filename in self.ppp_list:
+            if filename.endswith('R'):
+                for line in open(filename, 'r'):
+                    potfile.write(line)
+            elif filename.endswith('.Z'):
+                file_tmp = tempfile.NamedTemporaryFile()
+                os.system('gunzip -c %s > %s' % (filename, file_tmp.name))
+                for line in file_tmp.readlines():
+                    potfile.write(line)
+                file_tmp.close()
+        potfile.close()
+
+    def write_sort_file(self):
+        """Writes a sortings file.
+
+        This file contains information about how the atoms are sorted in
+        the first column and how they should be resorted in the second
+        column. It is used for restart purposes to get sorting right
+        when reading in an old calculation to ASE."""
+
+        file = open('ase-sort.dat', 'w')
+        for n in range(len(self.sort)):
+            file.write('%5i %5i \n' % (self.sort[n], self.resort[n]))
+
+    # Methods for reading information from OUTCAR files:
+    def read_energy(self, all=None):
+        [energy_free, energy_zero]=[0, 0]
+        if all:
+            energy_free = []
+            energy_zero = []
+        for line in open('OUTCAR', 'r'):
+            # Free energy
+            if line.startswith('  free  energy   toten'):
+                if all:
+                    energy_free.append(float(line.split()[-2]))
+                else:
+                    energy_free = float(line.split()[-2])
+            # Extrapolated zero point energy
+            if line.startswith('  energy  without entropy'):
+                if all:
+                    energy_zero.append(float(line.split()[-1]))
+                else:
+                    energy_zero = float(line.split()[-1])
+        return [energy_free, energy_zero]
+
+    def read_forces(self, atoms, all=False):
+        """Method that reads forces from OUTCAR file.
+
+        If 'all' is switched on, the forces for all ionic steps
+        in the OUTCAR file be returned, in other case only the
+        forces for the last ionic configuration is returned."""
+
+        file = open('OUTCAR','r')
+        lines = file.readlines()
+        file.close()
+        n=0
+        if all:
+            all_forces = []
+        for line in lines:
+            if line.rfind('TOTAL-FORCE') > -1:
+                forces=[]
+                for i in range(len(atoms)):
+                    forces.append(np.array([float(f) for f in lines[n+2+i].split()[3:6]]))
+                if all:
+                    all_forces.append(np.array(forces)[self.resort])
+            n+=1
+        if all:
+            return np.array(all_forces)
+        else:
+            return np.array(forces)[self.resort]
+
+    def read_fermi(self):
+        """Method that reads Fermi energy from OUTCAR file"""
+        E_f=None
+        for line in open('OUTCAR', 'r'):
+            if line.rfind('E-fermi') > -1:
+                E_f=float(line.split()[2])
+        return E_f
+
+    def read_dipole(self):
+        dipolemoment=np.zeros([1,3])
+        for line in open('OUTCAR', 'r'):
+            if line.rfind('dipolmoment') > -1:
+                dipolemoment=np.array([float(f) for f in line.split()[1:4]])
+        return dipolemoment
+
+    def read_magnetic_moments(self, atoms):
+        magnetic_moments = np.zeros(len(atoms))
+        n = 0
+        lines = open('OUTCAR', 'r').readlines()
+        for line in lines:
+            if line.rfind('magnetization (x)') > -1:
+                for m in range(len(atoms)):
+                    magnetic_moments[m] = float(lines[n + m + 4].split()[4])
+            n += 1
+        return np.array(magnetic_moments)[self.resort]
+
+    def read_magnetic_moment(self):
+        n=0
+        for line in open('OUTCAR','r'):
+            if line.rfind('number of electron  ') > -1:
+                magnetic_moment=float(line.split()[-1])
+            n+=1
+        return magnetic_moment
+
+    def read_nbands(self):
+        for line in open('OUTCAR', 'r'):
+            if line.rfind('NBANDS') > -1:
+                return int(line.split()[-1])
+
+    def read_convergence(self):
+        """Method that checks whether a calculation has converged."""
+        converged = None
+        # First check electronic convergence
+        for line in open('OUTCAR', 'r'):
+            if line.rfind('EDIFF  ') > -1:
+                ediff = float(line.split()[2])
+            if line.rfind('total energy-change')>-1:
+                split = line.split(':')
+                a = float(split[1].split('(')[0])
+                b = float(split[1].split('(')[1][0:-2])
+                if [abs(a), abs(b)] < [ediff, ediff]:
+                    converged = True
+                else:
+                    converged = False
+                    continue
+        # Then if ibrion > 0, check whether ionic relaxation condition been fulfilled
+        if self.int_params['ibrion'] > 0:
+            if not self.read_relaxed():
+                converged = False
+            else:
+                converged = True
+        return converged
+
+    def read_ibz_kpoints(self):
+        lines = open('OUTCAR', 'r').readlines()
+        ibz_kpts = []
+        n = 0
+        i = 0
+        for line in lines:
+            if line.rfind('Following cartesian coordinates')>-1:
+                m = n+2
+                while i==0:
+                    ibz_kpts.append([float(lines[m].split()[p]) for p in range(3)])
+                    m += 1
+                    if lines[m]==' \n':
+                        i = 1
+            if i == 1:
+                continue
+            n += 1
+        ibz_kpts = np.array(ibz_kpts)
+        return np.array(ibz_kpts)
+
+    def read_k_point_weights(self):
+        file = open('IBZKPT')
+        lines = file.readlines()
+        file.close()
+        kpt_weights = []
+        for n in range(3, len(lines)):
+            kpt_weights.append(float(lines[n].split()[3]))
+        kpt_weights = np.array(kpt_weights)
+        kpt_weights /= np.sum(kpt_weights)
+        return kpt_weights
+
+    def read_eigenvalues(self, kpt=0, spin=0):
+        file = open('EIGENVAL', 'r')
+        lines = file.readlines()
+        file.close()
+        eigs = []
+        for n in range(8+kpt*(self.nbands+2), 8+kpt*(self.nbands+2)+self.nbands):
+            eigs.append(float(lines[n].split()[spin+1]))
+        return np.array(eigs)
+
+    def read_relaxed(self):
+        for line in open('OUTCAR', 'r'):
+            if line.rfind('reached required accuracy') > -1:
+                return True
+        return False
+
+# The below functions are used to restart a calculation and are under early constructions
+
+    def read_incar(self, filename='INCAR'):
+        """Method that imports settings from INCAR file."""
+
+        self.spinpol = False
+        file=open(filename, 'r')
+        file.readline()
+        lines=file.readlines()
+        for line in lines:
+            try:
+                data = line.split()
+                # Skip empty and commented lines. 
+                if len(data) == 0:
+                    continue
+                elif data[0][0] in ['#', '!']:
+                    continue
+                key = data[0].lower()
+                if key in float_keys:
+                    self.float_params[key] = float(data[2])
+                elif key in exp_keys:
+                    self.exp_params[key] = float(data[2])
+                elif key in string_keys:
+                    self.string_params[key] = str(data[2])
+                elif key in int_keys:
+                    if key == 'ispin':
+                        if int(data[2]) == 2:
+                            self.spinpol = True
+                    else:
+                        self.int_params[key] = int(data[2])
+                elif key in bool_keys:
+                    if 'true' in data[2].lower():
+                        self.bool_params[key] = True
+                    elif 'false' in data[2].lower():
+                        self.bool_params[key] = False
+                elif key in list_keys:
+                    list = []
+                    if key in ('dipol', 'eint', 'ferwe', 'ropt', 'rwigs'):
+                        for a in data[2:]:
+                            list.append(float(a))
+                    elif key in ('iband', 'kpuse'):
+                        for a in data[2:]:
+                            list.append(int(a))
+                    self.list_params[key] = list
+                    if key == 'magmom':
+                        done = False
+                        for a in data[2:]:
+                            if '!' in a or done:
+                                done = True
+                            elif '*' in a: 
+                                a = a.split('*')
+                                for b in range(int(a[0])):
+                                    list.append(float(a[1]))
+                            else:
+                                list.append(float(a))
+                        list = np.array(list)
+                        self.atoms.set_initial_magnetic_moments(list[self.resort])
+            except KeyError:
+                raise IOError('Keyword "%s" in INCAR is not known by calculator.' % key)
+            except IndexError:
+                raise IOError('Value missing for keyword "%s".' % key)
+
+    def read_outcar(self):
+        # Spin polarized calculation?
+        file = open('OUTCAR', 'r')
+        lines = file.readlines()
+        file.close()
+        for line in lines:
+            if line.rfind('ISPIN') > -1:
+                if int(line.split()[2])==2:
+                    self.spinpol = True
+                else:
+                    self.spinpol = None
+        self.energy_free, self.energy_zero = self.read_energy()
+        self.forces = self.read_forces(self.atoms)
+        self.dipole = self.read_dipole()
+        self.fermi = self.read_fermi()
+        self.stress = self.read_stress()
+        self.nbands = self.read_nbands()
+        p=self.int_params
+        q=self.list_params
+        if self.spinpol:
+            self.magnetic_moment = self.read_magnetic_moment()
+            if p['lorbit']>=10 or (p['lorbit']!=None and q['rwigs']):
+                self.magnetic_moments = self.read_magnetic_moments(self.atoms)
+        self.set(nbands=self.nbands)
+
+    def read_kpoints(self, filename='KPOINTS'):
+        file = open(filename, 'r')
+        lines = file.readlines()
+        file.close()
+        type = lines[2].split()[0].lower()[0]
+        if type in ['g', 'm']:
+            if type=='g':
+                self.set(gamma=True)
+            kpts = np.array([int(lines[3].split()[i]) for i in range(3)])
+            self.set(kpts=kpts)
+        elif type in ['c', 'k']:
+            raise NotImplementedError('Only Monkhorst-Pack and gamma centered grid supported for restart.')
+        else:
+            raise NotImplementedError('Only Monkhorst-Pack and gamma centered grid supported for restart.')
+    
+    def read_potcar(self):
+        """ Method that reads the Exchange Correlation functional from POTCAR file.
+        """
+        file = open('POTCAR', 'r')
+        lines = file.readlines()
+        file.close()
+
+        # Search for key 'LEXCH' in POTCAR
+        xc_flag = None
+        for line in lines:
+            key = line.split()[0].upper()
+            if key == 'LEXCH':
+                xc_flag = line.split()[-1].upper()
+                break
+
+        if xc_flag is None:
+            raise ValueError('LEXCH flag not found in POTCAR file.')
+
+        # Values of parameter LEXCH and corresponding XC-functional
+        xc_dict = {'PE':'PBE', '91':'PW91', 'CA':'LDA'}
+
+        if xc_flag not in xc_dict.keys():
+            raise ValueError(
+                'Unknown xc-functional flag found in POTCAR, LEXCH=%s' % xc_flag)
+
+        self.input_params['xc'] = xc_dict[xc_flag]
+
+
+class VaspChargeDensity(object):
+    """Class for representing VASP charge density"""
+
+    def __init__(self, filename='CHG'):
+        # Instance variables
+        self.atoms = []   # List of Atoms objects
+        self.chg = []     # Charge density
+        self.chgdiff = [] # Charge density difference, if spin polarized
+        self.aug = ''     # Augmentation charges, not parsed just a big string
+        self.augdiff = '' # Augmentation charge differece, is spin polarized
+        
+        # Note that the augmentation charge is not a list, since they
+        # are needed only for CHGCAR files which store only a single
+        # image.
+        if filename != None:
+            self.read(filename)
+
+    def is_spin_polarized(self):
+        if len(self.chgdiff) > 0:
+            return True
+        return False
+
+    def _read_chg(self, fobj, chg, volume):
+        """Read charge from file object
+
+        Utility method for reading the actual charge density (or
+        charge density difference) from a file object. On input, the
+        file object must be at the beginning of the charge block, on
+        output the file position will be left at the end of the
+        block. The chg array must be of the correct dimensions.
+
+        """
+        # VASP writes charge density as
+        # WRITE(IU,FORM) (((C(NX,NY,NZ),NX=1,NGXC),NY=1,NGYZ),NZ=1,NGZC)
+        # Fortran nested implied do loops; innermost index fastest
+        # First, just read it in
+        for zz in range(chg.shape[2]):
+            for yy in range(chg.shape[1]):
+                chg[:, yy, zz] = np.fromfile(fobj, count = chg.shape[0],
+                                             sep=' ')
+        chg /= volume
+
+    def read(self, filename='CHG'):
+        """Read CHG or CHGCAR file.
+
+        If CHG contains charge density from multiple steps all the
+        steps are read and stored in the object. By default VASP
+        writes out the charge density every 10 steps.
+
+        chgdiff is the difference between the spin up charge density
+        and the spin down charge density and is thus only read for a
+        spin-polarized calculation.
+
+        aug is the PAW augmentation charges found in CHGCAR. These are
+        not parsed, they are just stored as a string so that they can
+        be written again to a CHGCAR format file.
+
+        """
+        import ase.io.vasp as aiv
+        f = open(filename)
+        self.atoms = []
+        self.chg = []
+        self.chgdiff = []
+        self.aug = ''
+        self.augdiff = ''
+        while True:
+            try:
+                atoms = aiv.read_vasp(f)
+            except ValueError, e:
+                # Probably an empty line, or we tried to read the 
+                # augmentation occupancies in CHGCAR
+                break 
+            f.readline()
+            ngr = f.readline().split()
+            ng = (int(ngr[0]), int(ngr[1]), int(ngr[2]))
+            chg = np.empty(ng)
+            self._read_chg(f, chg, atoms.get_volume())
+            self.chg.append(chg)
+            self.atoms.append(atoms)
+            # Check if the file has a spin-polarized charge density part, and
+            # if so, read it in.
+            fl = f.tell()
+            # First check if the file has an augmentation charge part (CHGCAR file.)
+            line1 = f.readline()
+            if line1=='':
+                break
+            elif line1.find('augmentation') != -1:
+                augs = [line1]
+                while True:
+                    line2 = f.readline()
+                    if line2.split() == ngr:
+                        self.aug = ''.join(augs)
+                        augs = []
+                        chgdiff = np.empty(ng)
+                        self._read_chg(f, chgdiff, atoms.get_volume())
+                        self.chgdiff.append(chgdiff)
+                    elif line2 == '':
+                        break
+                    else:
+                        augs.append(line2)
+                if len(self.aug) == 0:
+                    self.aug = ''.join(augs)
+                    augs = []
+                else:
+                    self.augdiff = ''.join(augs)
+                    augs = []
+            elif line1.split() == ngr:
+                chgdiff = np.empty(ng)
+                self._read_chg(f, chgdiff, atoms.get_volume())
+                self.chgdiff.append(chgdiff)
+            else:
+                f.seek(fl)
+        f.close()
+
+    def _write_chg(self, fobj, chg, volume, format='chg'):
+        """Write charge density
+
+        Utility function similar to _read_chg but for writing.
+
+        """
+        # Make a 1D copy of chg, must take transpose to get ordering right
+        chgtmp=chg.T.ravel()
+        # Multiply by volume
+        chgtmp=chgtmp*volume
+        # Must be a tuple to pass to string conversion
+        chgtmp=tuple(chgtmp)
+        # CHG format - 10 columns
+        if format.lower() == 'chg':
+            # Write all but the last row
+            for ii in range((len(chgtmp)-1)/10):
+                fobj.write(' %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G\
+ %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G\n' % chgtmp[ii*10:(ii+1)*10]
+                           )
+            # If the last row contains 10 values then write them without a newline
+            if len(chgtmp)%10==0:
+                fobj.write(' %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G\
+ %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G' % chgtmp[len(chgtmp)-10:len(chgtmp)])
+            # Otherwise write fewer columns without a newline
+            else:
+                for ii in range(len(chgtmp)%10):
+                    fobj.write((' %#11.5G') % chgtmp[len(chgtmp)-len(chgtmp)%10+ii])
+        # Other formats - 5 columns
+        else:
+            # Write all but the last row
+            for ii in range((len(chgtmp)-1)/5):
+                fobj.write(' %17.10E %17.10E %17.10E %17.10E %17.10E\n' % chgtmp[ii*5:(ii+1)*5])
+            # If the last row contains 5 values then write them without a newline
+            if len(chgtmp)%5==0:
+                fobj.write(' %17.10E %17.10E %17.10E %17.10E %17.10E' % chgtmp[len(chgtmp)-5:len(chgtmp)])
+            # Otherwise write fewer columns without a newline
+            else:
+                for ii in range(len(chgtmp)%5):
+                    fobj.write((' %17.10E') % chgtmp[len(chgtmp)-len(chgtmp)%5+ii])
+        # Write a newline whatever format it is
+        fobj.write('\n')
+        # Clean up
+        del chgtmp
+
+    def write(self, filename='CHG', format=None):
+        """Write VASP charge density in CHG format.
+
+        filename: str
+            Name of file to write to.
+        format: str
+            String specifying whether to write in CHGCAR or CHG
+            format.
+
+        """
+        import ase.io.vasp as aiv
+        if format == None:
+            if filename.lower().find('chgcar') != -1:
+                format = 'chgcar'
+            elif filename.lower().find('chg') != -1:
+                format = 'chg'
+            elif len(self.chg) == 1:
+                format = 'chgcar'
+            else:
+                format = 'chg'
+        f = open(filename, 'w')
+        for ii, chg in enumerate(self.chg):
+            if format == 'chgcar' and ii != len(self.chg) - 1:
+                continue # Write only the last image for CHGCAR
+            aiv.write_vasp(f, self.atoms[ii], direct=True)
+            f.write('\n')
+            for dim in chg.shape:
+                f.write(' %4i' % dim)
+            f.write('\n')
+            vol = self.atoms[ii].get_volume()
+            self._write_chg(f, chg, vol, format)
+            if format == 'chgcar':
+                f.write(self.aug)
+            if self.is_spin_polarized():
+                if format == 'chg':
+                    f.write('\n')
+                for dim in chg.shape:
+                    f.write(' %4i' % dim)
+                self._write_chg(f, self.chgdiff[ii], vol, format)
+                if format == 'chgcar':
+                    f.write('\n')
+                    f.write(self.augdiff)
+            if format == 'chg' and len(self.chg) > 1:
+                f.write('\n')
+        f.close()
+
+
+class VaspDos(object):
+    """Class for representing density-of-states produced by VASP
+
+    The energies are in property self.energy
+
+    Site-projected DOS is accesible via the self.site_dos method.
+
+    Total and integrated DOS is accessible as numpy.ndarray's in the
+    properties self.dos and self.integrated_dos. If the calculation is
+    spin polarized, the arrays will be of shape (2, NDOS), else (1,
+    NDOS).
+
+    The self.efermi property contains the currently set Fermi
+    level. Changing this value shifts the energies.
+    
+    """
+
+    def __init__(self, doscar='DOSCAR', efermi=0.0):
+        """Initialize"""
+        self._efermi = 0.0
+        self.read_doscar(doscar)
+        self.efermi = efermi
+
+    def _set_efermi(self, efermi):
+        """Set the Fermi level."""
+        ef = efermi - self._efermi
+        self._efermi = efermi
+        self._total_dos[0, :] = self._total_dos[0, :] - ef
+        try:
+            self._site_dos[:, 0, :] = self._site_dos[:, 0, :] - ef
+        except IndexError:
+            pass
+
+    def _get_efermi(self):
+        return self._efermi
+
+    efermi = property(_get_efermi, _set_efermi, None, "Fermi energy.")
+
+    def _get_energy(self):
+        """Return the array with the energies."""
+        return self._total_dos[0, :]
+    energy = property(_get_energy, None, None, "Array of energies")
+
+    def site_dos(self, atom, orbital):
+        """Return an NDOSx1 array with dos for the chosen atom and orbital.
+
+        atom: int
+            Atom index
+        orbital: int or str
+            Which orbital to plot
+
+        If the orbital is given as an integer:
+        If spin-unpolarized calculation, no phase factors:
+        s = 0, p = 1, d = 2
+        Spin-polarized, no phase factors:
+        s-up = 0, s-down = 1, p-up = 2, p-down = 3, d-up = 4, d-down = 5
+        If phase factors have been calculated, orbitals are
+        s, py, pz, px, dxy, dyz, dz2, dxz, dx2
+        double in the above fashion if spin polarized.
+
+        """
+        # Integer indexing for orbitals starts from 1 in the _site_dos array
+        # since the 0th column contains the energies
+        if isinstance(orbital, int):
+            return self._site_dos[atom, orbital + 1, :]
+        n = self._site_dos.shape[1]
+        if n == 4:
+            norb = {'s':1, 'p':2, 'd':3}
+        elif n == 7:
+            norb = {'s+':1, 's-up':1, 's-':2, 's-down':2,
+                    'p+':3, 'p-up':3, 'p-':4, 'p-down':4,
+                    'd+':5, 'd-up':5, 'd-':6, 'd-down':6}
+        elif n == 10:
+            norb = {'s':1, 'py':2, 'pz':3, 'px':4,
+                    'dxy':5, 'dyz':6, 'dz2':7, 'dxz':8,
+                    'dx2':9}
+        elif n == 19:
+            norb = {'s+':1, 's-up':1, 's-':2, 's-down':2,
+                    'py+':3, 'py-up':3, 'py-':4, 'py-down':4,
+                    'pz+':5, 'pz-up':5, 'pz-':6, 'pz-down':6,
+                    'px+':7, 'px-up':7, 'px-':8, 'px-down':8,
+                    'dxy+':9, 'dxy-up':9, 'dxy-':10, 'dxy-down':10,
+                    'dyz+':11, 'dyz-up':11, 'dyz-':12, 'dyz-down':12,
+                    'dz2+':13, 'dz2-up':13, 'dz2-':14, 'dz2-down':14,
+                    'dxz+':15, 'dxz-up':15, 'dxz-':16, 'dxz-down':16,
+                    'dx2+':17, 'dx2-up':17, 'dx2-':18, 'dx2-down':18}
+        return self._site_dos[atom, norb[orbital.lower()], :]
+
+    def _get_dos(self):
+        if self._total_dos.shape[0] == 3:
+            return self._total_dos[1, :]
+        elif self._total_dos.shape[0] == 5:
+            return self._total_dos[1:3, :]
+    dos = property(_get_dos, None, None, 'Average DOS in cell')
+
+    def _get_integrated_dos(self):
+        if self._total_dos.shape[0] == 3:
+            return self._total_dos[2, :]
+        elif self._total_dos.shape[0] == 5:
+            return self._total_dos[3:5, :]
+    integrated_dos = property(_get_integrated_dos, None, None,
+                              'Integrated average DOS in cell')
+
+    def read_doscar(self, fname="DOSCAR"):
+        """Read a VASP DOSCAR file"""
+        f = open(fname)
+        natoms = int(f.readline().split()[0])
+        [f.readline() for nn in range(4)]  # Skip next 4 lines.
+        # First we have a block with total and total integrated DOS
+        ndos = int(f.readline().split()[2])
+        dos = []
+        for nd in xrange(ndos):
+            dos.append(np.array([float(x) for x in f.readline().split()]))
+        self._total_dos = np.array(dos).T
+        # Next we have one block per atom, if INCAR contains the stuff
+        # necessary for generating site-projected DOS
+        dos = []
+        for na in xrange(natoms):
+            line = f.readline()
+            if line == '':
+                # No site-projected DOS
+                break
+            ndos = int(line.split()[2])
+            line = f.readline().split()
+            cdos = np.empty((ndos, len(line)))
+            cdos[0] = np.array(line)
+            for nd in xrange(1, ndos):
+                line = f.readline().split()
+                cdos[nd] = np.array([float(x) for x in line])
+            dos.append(cdos.T)
+        self._site_dos = np.array(dos)
+
+
+import pickle
+
+class xdat2traj:
+    def __init__(self, trajectory=None, atoms=None, poscar=None, 
+                 xdatcar=None, sort=None, calc=None):
+        if not poscar:
+            self.poscar = 'POSCAR'
+        else:
+            self.poscar = poscar
+        if not atoms:
+            self.atoms = ase.io.read(self.poscar, format='vasp')
+        else:
+            self.atoms = atoms
+        if not xdatcar:
+            self.xdatcar = 'XDATCAR'
+        else:
+            self.xdatcar = xdatcar
+        if not trajectory:
+            self.trajectory = 'out.traj'
+        else:
+            self.trajectory = trajectory
+        if not calc:
+            self.calc = Vasp()
+        else:
+            self.calc = calc
+        if not sort: 
+            if not hasattr(self.calc, 'sort'):
+                self.calc.sort = range(len(self.atoms))
+        else:
+            self.calc.sort = sort
+        self.calc.resort = range(len(self.calc.sort))
+        for n in range(len(self.calc.resort)):
+            self.calc.resort[self.calc.sort[n]] = n
+        self.out = ase.io.trajectory.PickleTrajectory(self.trajectory, mode='w')
+        self.energies = self.calc.read_energy(all=True)[1]
+        self.forces = self.calc.read_forces(self.atoms, all=True)
+
+    def convert(self):
+        lines = open(self.xdatcar).readlines()
+        if len(lines[5].split())==0:
+            del(lines[0:6])
+        elif len(lines[4].split())==0:
+            del(lines[0:5])
+        step = 0
+        iatom = 0
+        scaled_pos = []
+        for line in lines:
+            if iatom == len(self.atoms):
+                if step == 0:
+                    self.out.write_header(self.atoms[self.calc.resort])
+                scaled_pos = np.array(scaled_pos)
+                self.atoms.set_scaled_positions(scaled_pos)
+                d = {'positions': self.atoms.get_positions()[self.calc.resort],
+                     'cell': self.atoms.get_cell(),
+                     'momenta': None,
+                     'energy': self.energies[step],
+                     'forces': self.forces[step],
+                     'stress': None}
+                pickle.dump(d, self.out.fd, protocol=-1)
+                scaled_pos = []
+                iatom = 0
+                step += 1
+            else:
+                
+                iatom += 1
+                scaled_pos.append([float(line.split()[n]) for n in range(3)])
+
+        # Write also the last image
+        # I'm sure there is also more clever fix...
+        scaled_pos = np.array(scaled_pos)
+        self.atoms.set_scaled_positions(scaled_pos)
+        d = {'positions': self.atoms.get_positions()[self.calc.resort],
+             'cell': self.atoms.get_cell(),
+             'momenta': None,
+             'energy': self.energies[step],
+             'forces': self.forces[step],
+             'stress': None}
+        pickle.dump(d, self.out.fd, protocol=-1)
+
+        self.out.fd.close()
Index: ase/calculators/exciting.py
===================================================================
--- ase/calculators/exciting.py	(revision 0)
+++ ase/calculators/exciting.py	(revision 1)
@@ -0,0 +1,130 @@
+import os
+import numpy as np
+from ase.units import Bohr, Hartree
+
+
+class Exciting:
+    """exciting calculator object."""
+   
+    def __init__(self, dir='.', template=None, speciespath=None,
+                 bin='excitingser', kpts=(1, 1, 1), **kwargs):
+        """Exciting calculator object constructor
+        
+        Parameters
+        ----------
+        dir: string
+            directory in which to excecute exciting
+        template: string
+            Path to XSLT templat if it schould be used
+            default: none
+        bin: string
+            Path or executable name of exciting 
+            default: ``excitingser`` 
+        kpts: integer list length 3
+            Number of kpoints
+        kwargs: dictionary like
+            list of key value pairs to be converted into groundstate attributes
+        
+        """
+        self.dir = dir
+        self.energy = None
+        self.template = template
+        if speciespath is None:
+            self.speciespath = os.environ.get('EXCITING_SPECIES_PATH', './')
+        self.converged = False
+        self.excitingbinary = bin
+        self.groundstate_attributes = kwargs
+        if not 'ngridk' in kwargs.keys():
+            self.groundstate_attributes['ngridk'] = ' '.join(map(str, kpts))
+
+    def update(self, atoms):
+        if (not self.converged or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        self.numbers = atoms.get_atomic_numbers().copy()
+        self.write(atoms)
+
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.energy
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress.copy()
+
+    def calculate(self, atoms):
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+
+        self.initialize(atoms)
+        syscall = ('cd %(dir)s; %(bin)s;' %
+                   {'dir': self.dir, 'bin': self.excitingbinary})
+        print syscall
+        assert os.system(syscall ) == 0
+        self.read()
+
+    def write(self, atoms):
+        from lxml import etree as ET
+        from ase.io.exciting import  atoms2etree
+        if not os.path.isdir(self.dir):
+            os.mkdir(self.dir)
+        root = atoms2etree(atoms)
+        root.find('structure').attrib['speciespath'] = self.speciespath
+        root.find('structure').attrib['autormt'] = 'false'
+        groundstate = ET.SubElement(root, 'groundstate', tforce='true')
+        for key, value in self.groundstate_attributes.items():
+            groundstate.attrib[key] = str(value)
+        if self.template:
+            xslf = open(self.template, 'r')
+            xslt_doc = ET.parse(xslf)
+            transform = ET.XSLT(xslt_doc)
+            result = transform(root)
+            fd = open('%s/input.xml' % self.dir, 'w')
+            fd.write(ET.tostring(result, method='xml', pretty_print=True,
+                                 xml_declaration=True, encoding='UTF-8'))
+            fd.close()
+        else:
+            fd = open('%s/input.xml' % self.dir, 'w')
+            fd.write(ET.tostring(root, method='xml', pretty_print=True,
+                                 xml_declaration=True, encoding='UTF-8'))
+            fd.close()
+        
+    def read(self):
+        """ reads Total energy and forces from info.xml
+        """
+        from lxml import etree as ET
+        INFO_file = '%s/info.xml' % self.dir
+   
+        try:
+            fd = open(INFO_file)
+        except IOError:
+            print "file doesn't exist"
+        info = ET.parse(fd)
+        self.energy = float(info.xpath('//@totalEnergy')[-1]) * Hartree
+        forces = []
+        forcesnodes = info.xpath(
+            '//structure[last()]/species/atom/forces/totalforce/@*')
+        for force in forcesnodes:
+            forces.append(np.array(float(force)))
+        self.forces = np.reshape(forces, (-1, 3))
+        
+        if str(info.xpath('//groundstate/@status')[0]) == 'finished':
+            self.converged = True
+        else:
+            raise RuntimeError('calculation did not finish correctly')
+  
+        # Stress
+        self.stress = np.empty((3, 3))
Index: ase/calculators/elk.py
===================================================================
--- ase/calculators/elk.py	(revision 0)
+++ ase/calculators/elk.py	(revision 1)
@@ -0,0 +1,149 @@
+import os
+
+import numpy as np
+
+from ase.units import Bohr, Hartree
+
+elk_parameters = {
+    'swidth': Hartree,
+    }
+
+class ELK:
+    def __init__(self, dir='.', xc=None, kpts=None, tasks=[0], **kwargs):
+        for key, value in kwargs.items():
+            if key in elk_parameters:
+                kwargs[key] /= elk_parameters[key]
+
+        if xc is not None:
+            if 'xctype' in kwargs:
+                raise ValueError("You can't use both 'xc' and 'xctype'!")
+            else:
+                kwargs['xctype'] = {'LDA': 3, # PW92
+                                    'PBE': 20,
+                                    'REVPBE': 21,
+                                    'PBESOL': 22,
+                                    'WC06': 26,
+                                    'AM05': 30}[xc.upper()]
+
+        if kpts is not None:
+            if 'autokpt' in kwargs:
+                if kwargs['autokpt']:
+                    raise ValueError("You can't use both 'kpts' and 'autokpt'!")
+            if 'ngridk' in kwargs:
+                raise ValueError("You can't use both 'kpts' and 'ngridk'!")
+            if 'vkloff' in kwargs:
+                raise ValueError("You can't use both 'kpts' and 'vkloff'!")
+            else:
+                kwargs['ngridk'] = kpts
+                vkloff = []
+                for nk in kpts:
+                    if nk % 2 == 0:  # shift kpoint away from gamma point
+                        vkloff.append(0.5 / nk)
+                    else:
+                        vkloff.append(0)
+                kwargs['vkloff'] = vkloff
+
+        kwargs['tasks'] = tasks
+
+        self.parameters = kwargs
+
+        self.dir = dir
+        self.energy = None
+
+        self.converged = False
+
+    def update(self, atoms):
+        if (not self.converged or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        self.numbers = atoms.get_atomic_numbers().copy()
+        self.write(atoms)
+
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.energy
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress.copy()
+
+    def calculate(self, atoms):
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+
+        self.initialize(atoms)
+        assert os.system('cd %s; elk' % self.dir) == 0
+        self.read()
+
+        self.converged = True
+
+    def write(self, atoms):
+        if not os.path.isdir(self.dir):
+            os.mkdir(self.dir)
+        fd = open('%s/elk.in' % self.dir, 'w')
+        for key, value in self.parameters.items():
+            fd.write('%s\n' % key)
+            if isinstance(value, bool):
+                fd.write('.%s.\n\n' % ('false', 'true')[value])
+            elif isinstance(value, (int, float)):
+                fd.write('%s\n\n' % value)
+            else:
+                fd.write('%s\n\n' % ' '.join([str(x) for x in value]))
+
+        fd.write('avec\n')
+        for vec in atoms.cell:
+            fd.write('%.14f %.14f %.14f\n' % tuple(vec / Bohr))
+        fd.write('\n')
+
+        fd.write("sppath\n'%s'\n\n" % os.environ['ELK_SPECIES_PATH'])
+
+        species = {}
+        symbols = []
+        for a, symbol in enumerate(atoms.get_chemical_symbols()):
+            if symbol in species:
+                species[symbol].append(a)
+            else:
+                species[symbol] = [a]
+                symbols.append(symbol)
+        fd.write('atoms\n%d\n' % len(species))
+        scaled = atoms.get_scaled_positions()
+        for symbol in symbols:
+            fd.write("'%s.in'\n" % symbol)
+            fd.write('%d\n' % len(species[symbol]))
+            for a in species[symbol]:
+                fd.write('%.14f %.14f %.14f 0.0 0.0 0.0\n' % tuple(scaled[a]))
+
+    def read(self):
+        fd = open('%s/TOTENERGY.OUT' % self.dir, 'r')
+        self.energy = float(fd.readlines()[-1]) * Hartree
+        # Forces:
+        INFO_file = '%s/INFO.OUT' % self.dir
+        if os.path.isfile(INFO_file) or os.path.islink(INFO_file):
+            text = open(INFO_file).read().lower()
+            assert 'convergence targets achieved' in text
+            assert not 'reached self-consistent loops maximum' in text
+            lines = iter(text.split('\n'))
+            forces = []
+            atomnum = 0
+            for line in lines:
+                if line.rfind('total force') > -1:
+                    forces.append(np.array([float(f) for f in line.split(':')[1].split()]))
+                    atomnum =+ 1
+            self.forces = np.array(forces)
+        else:
+            raise RuntimeError
+        # Stress
+        self.stress = np.empty((3, 3))
Index: ase/calculators/general.py
===================================================================
--- ase/calculators/general.py	(revision 0)
+++ ase/calculators/general.py	(revision 1)
@@ -0,0 +1,46 @@
+
+class Calculator:
+    def __init__(self):
+        return
+
+    def set_atoms(self, atoms):
+        self.atoms = atoms.copy()
+
+    def get_atoms(self):
+        atoms = self.atoms.copy()
+        atoms.set_calculator(self)
+        return atoms
+
+    def get_potential_energy(self, atoms, force_consistent=False):
+        self.update(atoms)
+        if force_consistent:
+            return self.energy_free
+        else:
+            return self.energy_zero
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces
+
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress
+
+    def read(self, atoms):
+        self.positions = atoms.get_positions()
+        self.energy_free, self.energy_zero = self.read_energy()
+        self.forces = self.read_forces(atoms)
+        self.dipole = self.read_dipole()
+        self.fermi = self.read_fermi()
+        self.atoms = atoms.copy()
+        try:
+            self.nbands = self.read_nbands()
+        except NotImplementedError:
+            return
+        except AttributeError:
+            return
+        try:
+            self.stress = self.read_stress()
+        except NotImplementedError:
+            return
+        return
Index: ase/calculators/fleur.py
===================================================================
--- ase/calculators/fleur.py	(revision 0)
+++ ase/calculators/fleur.py	(revision 1)
@@ -0,0 +1,427 @@
+"""This module defines an ASE interface to FLAPW code FLEUR.
+
+http://www.flapw.de
+"""
+
+import os
+try:
+    from subprocess import Popen, PIPE
+except ImportError:
+    from os import popen3
+else:
+    def popen3(cmd):
+        p = Popen(cmd, shell=True, close_fds=True,
+                  stdin=PIPE, stdout=PIPE, stderr=PIPE)
+        return p.stdin, p.stdout, p.stderr
+import re
+
+import numpy as np
+
+from ase.units import Hartree, Bohr
+
+class FLEUR:
+    """Class for doing FLEUR calculations.
+
+    In order to use fleur one has to define the following environment
+    variables:
+
+    FLEUR_INPGEN path to the input generator (inpgen.x) of fleur
+
+    FLEUR path to the fleur executable. Note that fleur uses different
+    executable for real and complex cases (systems with/without inversion
+    symmetry), so FLEUR must point to the correct executable.
+
+    It is probable that user needs to tune manually the input file before
+    the actual calculation, so in addition to the standard
+    get_potential_energy function this class defines the following utility
+    functions:
+
+    write_inp
+        generate the input file `inp`
+    initialize_density
+        creates the initial density after possible manual edits of `inp`
+    calculate
+        convergence the total energy. With fleur, one specifies always
+        only the number of SCF-iterations so this function launches
+        the executable several times and monitors the convergence.
+    relax
+        Uses fleur's internal algorithm for structure
+        optimization. Requires that the proper optimization parameters
+        (atoms to optimize etc.) are specified by hand in `inp`
+    
+    """
+    def __init__(self, xc='LDA', kpts=None, nbands=None, convergence=None,
+                 width=None, kmax=None, mixer=None, maxiter=40, 
+                 maxrelax=20, workdir=None):
+
+        """Construct FLEUR-calculator object.
+
+        Parameters
+        ==========
+        xc: str
+            Exchange-correlation functional. Must be one of LDA, PBE,
+            RPBE.
+        kpts: list of three int
+            Monkhost-Pack sampling.
+        nbands: int
+            Number of bands. (not used at the moment)
+        convergence: dictionary
+            Convergence parameters (currently only energy in eV)
+            {'energy' : float}
+        width: float
+            Fermi-distribution width in eV.
+        kmax: float
+            Plane wave cutoff in a.u.
+        mixer: dictionary
+            Mixing parameters imix, alpha, spinf 
+            {'imix' : int, 'alpha' : float, 'spinf' : float}
+        maxiter: int
+            Maximum number of SCF iterations
+        maxrelax: int
+            Maximum number of relaxation steps
+        workdir: str
+            Working directory for the calculation
+        """
+        
+        self.xc = xc
+        self.kpts = kpts
+        self.nbands = nbands
+        self.width = width
+        self.kmax = kmax
+        self.maxiter = maxiter
+        self.maxrelax = maxrelax
+        self.mixer = mixer
+
+        if convergence:
+            self.convergence = convergence
+            self.convergence['energy'] /= Hartree
+        else:
+            self.convergence = {'energy' : 0.0001}
+
+
+
+        self.start_dir = None
+        self.workdir = workdir
+        if self.workdir:
+            self.start_dir = os.getcwd()
+            if not os.path.isdir(workdir):
+                os.mkdir(workdir)
+        else:
+            self.workdir = '.'
+            self.start_dir = '.'
+        
+        self.converged = False
+
+    def update(self, atoms):
+        """Update a FLEUR calculation."""
+        
+        if (not self.converged or
+            len(self.numbers) != len(atoms) or
+            (self.numbers != atoms.get_atomic_numbers()).any()):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.converged = False
+            self.initialize(atoms)
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        """Create an input file inp and generate starting density."""
+
+        self.converged = False
+        self.initialize_inp(atoms)
+        self.initialize_density()
+
+    def initialize_inp(self, atoms):
+        """Create a inp file"""
+        os.chdir(self.workdir)
+
+        self.numbers = atoms.get_atomic_numbers().copy()
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+
+        # create the input
+        self.write_inp(atoms)
+        
+        os.chdir(self.start_dir)
+
+    def initialize_density(self):
+        """Creates a new starting density."""
+
+        os.chdir(self.workdir)
+        # remove possible conflicting files
+        files2remove = ['cdn1', 'fl7para', 'stars', 'wkf2',
+                        'kpts', 'broyd', 'broyd.7', 'tmat', 'tmas']
+
+        for f in files2remove:
+            if os.path.isfile(f):
+                os.remove(f)
+
+        # generate the starting density
+        os.system("sed -i -e 's/strho=./strho=T/' inp")
+        try:
+            fleur_exe = os.environ['FLEUR']
+        except KeyError:
+            raise RuntimeError('Please set FLEUR')
+        cmd = popen3(fleur_exe)[2]
+        stat = cmd.read()
+        if '!' in stat:
+            raise RuntimeError('FLEUR exited with a code %s' % stat)
+        os.system("sed -i -e 's/strho=./strho=F/' inp")
+
+        os.chdir(self.start_dir)
+        
+    def get_potential_energy(self, atoms, force_consistent=False):
+        self.update(atoms)
+
+        if force_consistent:
+            return self.efree * Hartree
+        else:
+            # Energy extrapolated to zero Kelvin:
+            return  (self.etotal + self.efree) / 2 * Hartree
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        # electronic structure is converged, so let's calculate forces:
+        # TODO
+        return np.array((0.0, 0.0, 0.0))
+    
+    def get_stress(self, atoms):
+        raise NotImplementedError
+
+    def get_dipole_moment(self, atoms):
+        """Returns total dipole moment of the system."""
+        raise NotImplementedError
+
+    def calculate(self, atoms):
+        """Converge a FLEUR calculation to self-consistency.
+
+           Input files should be generated before calling this function
+           FLEUR performs always fixed number of SCF steps. This function
+           reduces the number of iterations gradually, however, a minimum
+           of five SCF steps is always performed.
+        """
+                      
+        os.chdir(self.workdir)
+        try:
+            fleur_exe = os.environ['FLEUR']
+        except KeyError:
+            raise RuntimeError('Please set FLEUR')
+
+        self.niter = 0
+        out = ''
+        err = ''
+        while not self.converged:
+            if self.niter > self.maxiter:
+                raise RuntimeError('FLEUR failed to convergence in %d iterations' % self.maxiter)
+            
+            p = Popen(fleur_exe, shell=True, stdin=PIPE, stdout=PIPE,
+                      stderr=PIPE)
+            stat = p.wait()
+            out = p.stdout.read()
+            err = p.stderr.read()
+            print err.strip()
+            if stat != 0:
+                raise RuntimeError('FLEUR exited with a code %d' % stat)
+            # catenate new output with the old one
+            os.system('cat out >> out.old')
+            self.read()
+            self.check_convergence()
+
+        os.rename('out.old', 'out')
+        # After convergence clean up broyd* files
+        os.system('rm -f broyd*')
+        os.chdir(self.start_dir)
+        return out, err
+
+    def relax(self, atoms):
+        """Currently, user has to manually define relaxation parameters
+           (atoms to relax, relaxation directions, etc.) in inp file
+           before calling this function."""
+
+        nrelax = 0
+        relaxed = False
+        while not relaxed:
+            # Calculate electronic structure
+            self.calculate(atoms)
+            # Calculate the Pulay forces
+            os.system("sed -i -e 's/l_f=./l_f=T/' inp")
+            while True:
+                self.converged = False
+                out, err = self.calculate(atoms)
+                if 'GEO new' in err:
+                    os.chdir(self.workdir)
+                    os.rename('inp_new', 'inp')
+                    os.chdir(self.start_dir)
+                    break
+            if 'GEO: Des woas' in err:
+                relaxed = True
+                break
+            nrelax += 1
+            # save the out and cdn1 files
+            os.system('cp out out_%d' % nrelax)
+            os.system('cp cdn1 cdn1_%d' % nrelax)
+            if nrelax > self.maxrelax:
+                raise RuntimeError('Failed to relax in %d iterations' % self.maxrelax)
+            self.converged = False
+                
+
+    def write_inp(self, atoms):
+        """Write the `inp` input file of FLEUR.
+
+        First, the information from Atoms is written to the simple input
+        file and the actual input file `inp` is then generated with the
+        FLEUR input generator. The location of input generator is specified
+        in the environment variable FLEUR_INPGEN.
+
+        Finally, the `inp` file is modified according to the arguments of
+        the FLEUR calculator object.
+        """
+
+        fh = open('inp_simple', 'w')
+        fh.write('FLEUR input generated with ASE\n')
+        fh.write('\n')
+        
+        if atoms.pbc[2]:
+            film = 'f'
+        else:
+            film = 't'
+        fh.write('&input film=%s /' % film)
+        fh.write('\n')
+
+        for vec in atoms.get_cell():
+            fh.write(' ')
+            for el in vec:
+                fh.write(' %21.16f' % (el/Bohr))
+            fh.write('\n')
+        fh.write(' %21.16f\n' % 1.0)
+        fh.write(' %21.16f %21.16f %21.16f\n' % (1.0, 1.0, 1.0))
+        fh.write('\n')
+        
+        natoms = len(atoms)
+        fh.write(' %6d\n' % natoms)
+        positions = atoms.get_scaled_positions()
+        if not atoms.pbc[2]:
+            # in film calculations z position has to be in absolute
+            # coordinates and symmetrical
+            cart_pos = atoms.get_positions()
+            cart_pos[:, 2] -= atoms.get_cell()[2, 2]/2.0
+            positions[:, 2] = cart_pos[:, 2] / Bohr
+        atomic_numbers = atoms.get_atomic_numbers()
+        for Z, pos in zip(atomic_numbers, positions):
+            fh.write('%3d' % Z)
+            for el in pos:
+                fh.write(' %21.16f' % el)
+            fh.write('\n')
+
+        fh.close()
+        try:
+            inpgen = os.environ['FLEUR_INPGEN']
+        except KeyError:
+            raise RuntimeError('Please set FLEUR_INPGEN')
+
+        # rename the previous inp if it exists
+        if os.path.isfile('inp'):
+            os.rename('inp', 'inp.bak')
+        os.system('%s < inp_simple' % inpgen)
+
+        # read the whole inp-file for possible modifications
+        fh = open('inp', 'r')
+        lines = fh.readlines()
+        fh.close()
+
+
+        for ln, line in enumerate(lines):
+            # XC potential
+            if line.startswith('pbe'):
+                if self.xc == 'PBE':
+                    pass
+                elif self.xc == 'RPBE':
+                    lines[ln] = 'rpbe   non-relativi\n'
+                elif self.xc == 'LDA':
+                    lines[ln] = 'mjw    non-relativic\n'
+                    del lines[ln+1]
+                else:
+                    raise RuntimeError('XC-functional %s is not supported' % self.xc)
+            # kmax
+            if self.kmax and line.startswith('Window'):
+                line = '%10.5f\n' % self.kmax
+                lines[ln+2] = line
+            
+            # Fermi width
+            if self.width and line.startswith('gauss'):
+                line = 'gauss=F   %7.5ftria=F\n' % (self.width / Hartree)
+                lines[ln] = line
+            # kpts
+            if self.kpts and line.startswith('nkpt'):
+                line = 'nkpt=      nx=%2d,ny=%2d,nz=%2d\n' % (self.kpts[0],
+                                                              self.kpts[1],
+                                                              self.kpts[2])
+                lines[ln] = line
+            # Mixing
+            if self.mixer and line.startswith('itmax'):
+                imix = self.mixer['imix']
+                alpha = self.mixer['alpha']
+                spinf = self.mixer['spinf']
+                line_end = 'imix=%2d,alpha=%6.2f,spinf=%6.2f\n' % (imix,
+                                                                   alpha,
+                                                                   spinf)
+                line = line[:21] + line_end
+                lines[ln] = line
+                
+        # write everything back to inp
+        fh = open('inp', 'w')
+        for line in lines:
+            fh.write(line)
+        fh.close()
+
+    def read(self):
+        """Read results from FLEUR's text-output file `out`."""
+
+        lines = open('out', 'r').readlines()
+
+        # total energies
+        self.total_energies = []
+        pat = re.compile('(.*total energy=)(\s)*([-0-9.]*)')
+        for line in lines:
+            m = pat.match(line)
+            if m:
+                self.total_energies.append(float(m.group(3)))
+        self.etotal = self.total_energies[-1]
+        
+        # free_energies
+        self.free_energies = []
+        pat = re.compile('(.*free energy=)(\s)*([-0-9.]*)')
+        for line in lines:
+            m = pat.match(line)
+            if m:
+                self.free_energies.append(float(m.group(3)))
+        self.efree = self.free_energies[-1]
+
+        # TODO forces, charge density difference...
+
+    def check_convergence(self):
+        """Check the convergence of calculation"""
+        energy_error = np.ptp(self.total_energies[-3:])
+        self.converged = energy_error < self.convergence['energy']
+
+        # TODO check charge convergence
+
+        # reduce the itmax in inp
+        lines = open('inp', 'r').readlines()
+        pat = re.compile('(itmax=)([ 0-9]*)')
+        fh = open('inp', 'w')
+        for line in lines:
+            m = pat.match(line)
+            if m:
+                itmax = int(m.group(2))
+                self.niter += itmax
+                itmax_new = itmax / 2
+                itmax = max(5, itmax_new)
+                line = 'itmax=%2d' % itmax + line[8:]
+            fh.write(line)
+        fh.close()
+                
Index: ase/calculators/test.py
===================================================================
--- ase/calculators/test.py	(revision 0)
+++ ase/calculators/test.py	(revision 1)
@@ -0,0 +1,142 @@
+from math import pi
+
+import numpy as np
+
+from ase.atoms import Atoms
+
+
+def make_test_dft_calculation():
+    a = b = 2.0
+    c = 6.0
+    atoms = Atoms(positions=[(0, 0, c / 2)],
+                  symbols='H',
+                  pbc=(1, 1, 0),
+                  cell=(a, b, c),
+                  calculator=TestCalculator())
+    return atoms
+
+
+class TestCalculator:
+    def __init__(self, nk=8):
+        assert nk % 2 == 0
+        bzk = []
+        weights = []
+        ibzk = []
+        w = 1.0 / nk**2
+        for i in range(-nk + 1, nk, 2):
+            for j in range(-nk + 1, nk, 2):
+                k = (0.5 * i / nk, 0.5 * j / nk, 0)
+                bzk.append(k)
+                if i >= j > 0:
+                    ibzk.append(k)
+                    if i == j:
+                        weights.append(4 * w)
+                    else:
+                        weights.append(8 * w)
+        assert abs(sum(weights) - 1.0) < 1e-12
+        self.bzk = np.array(bzk)
+        self.ibzk = np.array(ibzk)
+        self.weights = np.array(weights)
+
+        # Calculate eigenvalues and wave functions:
+        self.init()
+
+    def init(self):
+        nibzk = len(self.weights)
+        nbands = 1
+
+        V = -1.0
+        self.eps = 2 * V * (np.cos(2 * pi * self.ibzk[:, 0]) +
+                            np.cos(2 * pi * self.ibzk[:, 1]))
+        self.eps.shape = (nibzk, nbands)
+
+        self.psi = np.zeros((nibzk, 20, 20, 60), complex)
+        phi = np.empty((2, 2, 20, 20, 60))
+        z = np.linspace(-1.5, 1.5, 60, endpoint=False)
+        for i in range(2):
+            x = np.linspace(0, 1, 20, endpoint=False) - i
+            for j in range(2):
+                y = np.linspace(0, 1, 20, endpoint=False) - j
+                r = (((x[:, None]**2 +
+                       y**2)[:, :, None] +
+                      z**2)**0.5).clip(0, 1)
+                phi = 1.0 - r**2 * (3.0 - 2.0 * r)
+                phase = np.exp(pi * 2j * np.dot(self.ibzk, (i, j, 0)))
+                self.psi += phase[:, None, None, None] * phi
+
+    def get_pseudo_wave_function(self, band=0, kpt=0, spin=0):
+        assert spin == 0 and band == 0
+        return self.psi[kpt]
+
+    def get_eigenvalues(self, kpt=0, spin=0):
+        assert spin == 0
+        return self.eps[kpt]
+
+    def get_number_of_bands(self):
+        return 1
+
+    def get_k_point_weights(self):
+        return self.weights
+
+    def get_number_of_spins(self):
+        return 1
+
+    def get_fermi_level(self):
+        return 0.0
+
+
+class TestPotential:
+    def get_forces(self, atoms):
+        E = 0.0
+        R = atoms.positions
+        F = np.zeros_like(R)
+        for a, r in enumerate(R):
+            D = R - r
+            d = (D**2).sum(1)**0.5
+            x = d - 1.0
+            E += np.vdot(x, x)
+            d[a] = 1
+            F -= (x / d)[:, None] * D
+        self.energy = 0.25 * E
+        return F
+
+    def get_potential_energy(self, atoms):
+        self.get_forces(atoms)
+        return self.energy
+
+    def get_stress(self, atoms):
+        raise NotImplementedError
+
+
+def numeric_force(atoms, a, i, d=0.001):
+    """Evaluate force along i'th axis on a'th atom using finite difference.
+
+    This will trigger two calls to get_potential_energy(), with atom a moved
+    plus/minus d in the i'th axial direction, respectively.
+    """
+    p0 = atoms.positions[a, i]
+    atoms.positions[a, i] += d
+    eplus = atoms.get_potential_energy()
+    atoms.positions[a, i] -= 2 * d
+    eminus = atoms.get_potential_energy()
+    atoms.positions[a, i] = p0
+    return (eminus - eplus) / (2 * d)
+
+
+def numeric_forces(atoms, indices=None, axes=(0, 1, 2), d=0.001):
+    """Evaluate finite-difference forces on several atoms.
+
+    Returns an array of forces for each specified atomic index and
+    each specified axis, calculated using finite difference on each
+    atom and direction separately.  Array has same shape as if
+    returned from atoms.get_forces(); uncalculated elements are zero.
+
+    Calculates all forces by default."""
+
+    if indices is None:
+        indices = range(len(atoms))
+    F_ai = np.zeros_like(atoms.positions)
+    for a in indices:
+        for i in axes:
+            F_ai[a, i] = numeric_force(atoms, a, i, d)
+    return F_ai
Index: ase/calculators/dftb.py
===================================================================
--- ase/calculators/dftb.py	(revision 0)
+++ ase/calculators/dftb.py	(revision 1)
@@ -0,0 +1,481 @@
+"""This module defines an ASE interface to DftbPlus
+
+http://http://www.dftb-plus.info//
+http://www.dftb.org/
+
+-Markus Kaukonen markus.kaukonen@iki.fi
+"""
+
+
+import numpy as np
+import os, string
+
+#from ase.data import chemical_symbols
+from ase.units import Hartree, Bohr
+
+
+class Dftb:
+    """Class for doing DFTB+ calculations.
+    """
+    def __init__(self, label='dftb', write_dftb=False,
+                 charge=0.0, include_dispersion=False,
+                 do_spin_polarized=False, 
+                 unpaired_electrons=0.0,
+                 fermi_temperature=0.0, scc=False):
+        """Construct DFTB-calculator object.
+
+
+        For example:
+        calc = Dftb(label='dftb',write_dftb=True,include_dispersion=True )
+
+        Parameters
+        ==========
+        label: str
+            Prefix to use for filenames (label.txt, ...).
+            Default is 'dftb'.
+        write_dftb: boolean
+            True: a minimal input file (name of which is always 'dftb_in.hsd')
+            is written based on values given here.
+            False: input file for dftb+ is not written. User must have
+            generated file 'dftb_in.hsd' in the working directory.
+            Use write_dftb=False to use your own 'dftb_in.hsd'-file.
+        charge: float
+            Total charge of the system.
+        include_dispersion: boolean
+            True: Default dispersion parameters are written in the 
+            file 'dftb_in.hsd' (requires that also write_dftb_input_file==True)
+            False: dispersion parameters are not written here.
+        do_spin_polarized: boolean
+            True: Spin polarized calculation
+            (requires that also write_dftb_input_file==True)
+            False: Spin unpolarized calculation
+        unpaired_electrons: float
+            Number of spin unpaired electrons in the system.
+            Relevant only if do_spin_polarized==True
+        fermi_temperature: float
+            Fermi temperature for electrons.
+        scc: boolean
+            True: Do charge self consistent dftb+
+            False: No SCC, charges on atoms are not iterated
+            
+        Input file for DFTB+ file is 'dftb_in.hsd'. Keywords in it are
+        written here or read from an existing file. The atom positions
+        in file 'dftb_in.hsd' are updated during ASE geometry
+        optimization.
+        """
+
+
+        if not(write_dftb):
+            if os.path.isfile('dftb_in.hsd'):
+                f = open('dftb_in.hsd')
+            else:
+                print 'Input file for DFTB+ dftb_in.hsd is missing'
+                raise RuntimeError, \
+                    'Provide it or set write_dftb=True '
+            #lines = f.readlines()
+            f.close()
+
+        self.label = label
+        self.write_dftb = write_dftb
+        self.charge = charge
+        self.include_dispersion = include_dispersion
+        self.do_spin_polarized = do_spin_polarized
+        self.unpaired_electrons = unpaired_electrons
+        #if (do_spin_polarized):
+        #    print 'Sorry, generation of file "dftb_in.hsd" with spin '
+        #    print 'polarization is not inplemented for DFTB+'
+        #    print 'Set write_dftb=False and'
+        #    raise RuntimeError, \
+        #        'Generate file "dftb_in.hsd by hand"'
+        
+        self.etotal = 0.0
+        self.cell = None
+        self.fermi_temperature = fermi_temperature
+        self.scc = scc 
+        self.converged = False
+
+        #dftb has no stress
+        self.stress = np.empty((3, 3))
+        
+
+    def update(self, atoms):
+        """Energy and forces are calculated when atoms have moved
+        by calling self.calculate
+        """
+        if (not self.converged or
+            len(self.typenumber) != len(atoms)):
+            self.initialize(atoms)
+            self.calculate(atoms)
+        elif ((self.positions != atoms.get_positions()).any() or
+              (self.pbc != atoms.get_pbc()).any() or
+              (self.cell != atoms.get_cell()).any()):
+            self.calculate(atoms)
+
+    def initialize(self, atoms):
+        #from ase.io.dftb import read_dftb
+
+        atomtypes = atoms.get_chemical_symbols()
+        self.allspecies = []
+        self.typenumber = []
+        self.max_angular_momentum = []
+        for species in (atomtypes):
+            if species not in self.allspecies:
+                self.allspecies.append(species)
+        for species in (atomtypes):
+            myindex = 1 + self.allspecies.index(species)
+            self.typenumber.append(myindex)
+        for i in self.allspecies:
+            if i == 'H':
+                self.max_angular_momentum.append('s')
+            elif i in ['C','N','O']:
+                self.max_angular_momentum.append('p')
+            elif i in ['Si','S','Fe','Ni']:
+                self.max_angular_momentum.append('d')
+            else:
+                print 'anglular momentum is not imlemented in ASE-DFTB+'
+                print 'for species '+i
+                raise RuntimeError('Use option write_dftb=False')
+        self.converged = False
+        
+        #write DFTB input file if desired 
+        self.positions = atoms.get_positions().copy()
+        if self.write_dftb:
+            self.write_dftb_input_file(atoms)
+        
+        
+    def get_potential_energy(self, atoms):
+        self.update(atoms)
+        return self.etotal
+
+    def get_forces(self, atoms):
+        self.update(atoms)
+        return self.forces.copy()
+    
+    def get_stress(self, atoms):
+        self.update(atoms)
+        return self.stress.copy()
+
+    def calculate(self, atoms):
+        """Total DFTB energy is calculated (to file 'energy'
+        also forces are calculated (to file 'gradient')
+        """
+        
+        self.positions = atoms.get_positions().copy()
+        self.cell = atoms.get_cell().copy()
+        self.pbc = atoms.get_pbc().copy()
+
+        if self.write_dftb:
+            self.write_dftb_input_file(atoms)
+        else:
+            #write current coordinates to file 'dftb_in.hsd' for DFTB+
+            self.change_atom_positions_dftb(atoms)
+
+
+        #DFTB energy&forces calculation
+        if (os.environ.has_key('DFTB_COMMAND') and
+            (os.environ.has_key('DFTB_PREFIX'))):
+            dftb = os.environ['DFTB_COMMAND']
+            exitcode = os.system(dftb+ '> dftb.output' )
+        elif not(os.environ.has_key('DFTB_COMMAND')):
+            raise RuntimeError('Please set DFTB_COMMAND environment variable')
+        elif not(os.environ.has_key('DFTB_PREFIX')):
+            print 'Path for DFTB+ slater koster files is missing'    
+            raise RuntimeError('Please set DFTB_PREFIX environment variable')
+        else:
+            pass
+        if exitcode != 0:
+            raise RuntimeError('Dftb exited with exit code: %d.  ' % exitcode)
+
+        self.read_energy()
+
+        #DFTB atomic forces calculation, to be read in file detailed.out
+        #os.system(self.dftb_program_forces +'> output.forces.dummy')
+
+        self.read_forces(atoms)
+
+        self.converged = True
+
+        
+    def read_energy(self):
+        """Read Energy from DFTB energy file."""
+        text = open('dftb.output', 'r').read().lower()
+        lines = iter(text.split('\n'))
+
+        # Energy:
+        for line in lines:
+            if 'total energy' in line:
+                energy_tmp = float(line.split()[2])
+                #print 'energy_tmp', energy_tmp
+        self.etotal = energy_tmp * Hartree
+
+
+    def read_forces(self, atoms):
+        """Read Forces from DFTB+ detailed.out output file"""
+
+        myfile = open('detailed.out','r')
+        line = myfile.readline()
+        line = myfile.readline()
+        tmpforces = np.array([[0, 0, 0]])
+        while line:
+            if 'Total Forces' in line:
+                for i, dummy in enumerate(atoms):
+                    line = myfile.readline()
+                    line2 = string.replace(line, 'D', 'E')
+                    tmp = np.array\
+                        ([[float(f) for f in line2.split()[0:3]]])
+                    tmpforces = np.concatenate((tmpforces, tmp))  
+            line = myfile.readline()
+            
+
+        self.forces = (np.delete(tmpforces, np.s_[0:1], axis=0))*Hartree/Bohr
+
+        #print 'forces', self.forces
+
+    def read(self):
+        """Dummy stress for dftb"""
+        self.stress = np.empty((3, 3))
+
+    def write_dftb_input_file(self, atoms):
+        """Write input parameters to DFTB+ input file 'dftb_in.hsd'."""
+        import sys
+        fh = open('dftb_in.hsd', 'w')
+        # geometry
+        fh.write('Geometry = {\n')
+        fh.write('TypeNames = {')
+        for i in self.allspecies:
+            fh.write(' "'+i+'"')
+        fh.write(' }\n')
+        fh.write('TypesAndCoordinates [Angstrom] = {\n')
+        self.positions = atoms.get_positions().copy()
+        for i, pos in zip(self.typenumber, self.positions):
+            fh.write('%6d ' % (i))
+            fh.write('%20.14f %20.14f %20.14f' %  tuple(pos))
+            fh.write('\n')
+        fh.write(' }\n')
+
+        #is it periodic and when is write also lattice vectors
+        periodic = atoms.get_pbc().any()
+        if periodic:
+            fh.write('Periodic = Yes\n')
+        else:
+            fh.write('Periodic = No\n')
+        if periodic:
+            cell = atoms.get_cell().copy()
+            fh.write('LatticeVectors [Angstrom] = {\n')
+            for v in cell:
+                fh.write('%20.14f %20.14f %20.14f \n' %  tuple(v))
+            fh.write('  }\n')
+
+        #end of geometry session
+        fh.write('}\n')
+
+        #zero step CG relaxation to get forces and energies
+        # these are dummies because ASE takes care of these things
+        fh.write('\n') 
+        fh.write('Driver = ConjugateGradient {\n')
+        fh.write('MovedAtoms = Range { 1 -1 }\n')
+        fh.write('  MaxForceComponent = 1.0e-4\n')
+        fh.write('  MaxSteps = 0\n')
+        fh.write('  OutputPrefix = '+self.label+ '\n')
+        fh.write('}\n')
+
+        #Hamiltonian
+        fh.write('\n') 
+        fh.write('Hamiltonian = DFTB { # DFTB Hamiltonian\n')
+        if (self.scc):
+            fh.write('  SCC = Yes')
+            fh.write(' # Use self consistent charges\n')               
+            fh.write('  SCCTolerance = 1.0e-5')
+            fh.write(' # Tolerance for charge consistence\n')            
+            fh.write('  MaxSCCIterations = 1000')
+            fh.write(' # Nr. of maximal SCC iterations\n')          
+            fh.write('  Mixer = Broyden {') 
+            fh.write(' # Broyden mixer for charge mixing\n')          
+            fh.write('    MixingParameter = 0.2')  
+            fh.write(' # Mixing parameter\n')
+            fh.write('  }\n')
+        else:
+            fh.write('  SCC = No # NO self consistent charges\n')
+        fh.write('  SlaterKosterFiles = Type2FileNames {')
+        fh.write(' # File names from two atom type names\n')
+        sk_prefix = os.environ['DFTB_PREFIX']
+        fh.write('    Prefix = "'+sk_prefix+'"')
+        fh.write(' # Path as prefix\n')
+        fh.write('    Separator = "-"')
+        fh.write(' # Dash between type names\n')
+        fh.write('    Suffix = ".skf"')
+        fh.write(' # Suffix after second type name\n')
+        fh.write('  }\n')
+        fh.write('  MaxAngularMomentum = {')
+        fh.write(' # Maximal l-value of the various species\n')
+        for i, j in zip(self.allspecies, self.max_angular_momentum):
+            fh.write('   '+i+' = "'+j+'"\n')
+        fh.write('  }\n')
+        fh.write('  Charge = ')
+        fh.write('%10.6f' % (self.charge))
+        fh.write(' # System neutral\n')
+        if self.do_spin_polarized:
+            fh.write('  SpinPolarisation = Colinear {\n') 
+            fh.write('  UnpairedElectrons = '+str(self.unpaired_electrons)+'\n')
+            fh.write('  } \n')
+            fh.write('  SpinConstants = {\n') 
+            for i in self.allspecies:
+                if i == 'H':
+                    fh.write('   H={\n') 
+                    fh.write('    # Wss\n') 
+                    fh.write('    -0.072\n') 
+                    fh.write('    }\n')
+                elif i == 'C': 
+                    fh.write('   C={\n') 
+                    fh.write('    # Wss Wsp Wps Wpp\n') 
+                    fh.write('    -0.031 -0.025 -0.025 -0.023\n') 
+                    fh.write('    }\n') 
+                elif i == 'N': 
+                    fh.write('   N={\n') 
+                    fh.write('    # Wss Wsp Wps Wpp\n')
+                    fh.write('    -0.033 -0.027 -0.027 -0.026\n') 
+                    fh.write('     }\n') 
+                elif i == 'O':
+                    fh.write('   O={\n') 
+                    fh.write('    # Wss Wsp Wps Wpp\n') 
+                    fh.write('    -0.035 -0.030 -0.030 -0.028\n') 
+                    fh.write('     }\n') 
+                elif (i == 'Si' or i == 'SI'):
+                    fh.write('   Si={\n') 
+                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n')
+                    fh.write('    -0.020 -0.015 0.000 -0.015 -0.014 0.000 0.002 0.002 -0.032\n')
+                    fh.write('    }\n')
+                elif (i == 'S'):
+                    fh.write('   S={\n') 
+                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n') 
+                    fh.write('    -0.021 -0.017 0.000 -0.017 -0.016 0.000 0.000 0.000 -0.080\n')
+                    fh.write('    }\n')
+                elif (i == 'Fe' or i == 'FE'):
+                    fh.write('   Fe={\n') 
+                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n') 
+                    fh.write('    -0.016 -0.012 -0.003 -0.012 -0.029 -0.001 -0.003 -0.001 -0.015\n')
+                    fh.write('    }\n')
+                elif (i == 'Ni' or i == 'NI'):
+                    fh.write('   Ni={\n') 
+                    fh.write('    # Wss Wsp Wsd Wps Wpp Wpd Wds Wdp Wdd\n') 
+                    fh.write('    -0.016 -0.012 -0.003 -0.012 -0.022 -0.001 -0.003 -0.001 -0.018\n')
+                    fh.write('    }\n')
+                else:
+                    print 'Missing spin polarisation parameters for species'+i
+                    raise RuntimeError, \
+                        'Run spin unpolarised calculation'
+                fh.write('   }\n') 
+        else:
+            fh.write('  SpinPolarisation = {}')
+            fh.write(' # No spin polarisation\n')
+        fh.write('  Filling = Fermi {\n')
+        fh.write('    Temperature [Kelvin] = ')
+        fh.write('%10.6f\n' % (self.fermi_temperature))
+        fh.write('  }\n')
+        if periodic:
+            fh.write('# gamma only\n')
+            fh.write('KPointsAndWeights = { \n')
+            fh.write('  0.000000    0.000000    0.000000       1.000000 \n')
+            fh.write('}\n')
+
+        #Dispersion parameters
+        if (self.include_dispersion):
+            fh.write('Dispersion = SlaterKirkwood {\n')
+            fh.write(' PolarRadiusCharge = HybridDependentPol {\n')
+            fh.write('\n')
+            fh.write('  C={\n')
+            fh.write('    CovalentRadius [Angstrom] = 0.8\n')
+            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
+            fh.write('      1.382 1.382 1.382 1.064 1.064 1.064 3.8 3.8 3.8 3.8 3.8 3.8 2.5\n')
+            fh.write('    }\n')
+            fh.write('  }\n')
+            fh.write('\n')
+            fh.write('  N={\n')
+            fh.write('    CovalentRadius [Angstrom] = 0.8\n')
+            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
+            fh.write('      1.030 1.030 1.090 1.090 1.090 1.090 3.8 3.8 3.8 3.8 3.8 3.8 2.82\n')
+            fh.write('    }\n')
+            fh.write('  }\n')
+            fh.write('\n')
+            fh.write('  O={\n')
+            fh.write('    CovalentRadius [Angstrom] = 0.8\n')
+            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
+            fh.write('    # All polarisabilities and radii set the same\n')
+            fh.write('      0.560 0.560 0.000 0.000 0.000 0.000 3.8 3.8 3.8 3.8 3.8 3.8 3.15\n')
+            fh.write('    }\n')
+            fh.write('  }\n')
+            fh.write('\n')
+
+
+            fh.write('  H={\n')
+            fh.write('    CovalentRadius [Angstrom] = 0.4\n')
+            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
+            fh.write('    # Different polarisabilities depending on the hybridisation\n')
+            fh.write('      0.386 0.396 0.000 0.000 0.000 0.000 3.5 3.5 3.5 3.5 3.5 3.5 0.8\n')
+            fh.write('    }\n')
+            fh.write('  }\n')
+            fh.write('\n')
+
+            fh.write('  P={\n')
+            fh.write('    CovalentRadius [Angstrom] = 0.9\n')
+            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
+            fh.write('    # Different polarisabilities depending on the hybridisation\n')
+            fh.write('      1.600 1.600 1.600 1.600 1.600 1.600 4.7 4.7 4.7 4.7 4.7 4.7 4.50\n')
+            fh.write('    }\n')
+            fh.write('  }\n')
+            fh.write('\n')
+
+            fh.write('  S={\n')
+            fh.write('    CovalentRadius [Angstrom] = 0.9\n')
+            fh.write('    HybridPolarisations [Angstrom^3,Angstrom,] = {\n')
+            fh.write('    # Different polarisabilities depending on the hybridisation\n')
+            fh.write('      3.000 3.000 3.000 3.000 3.000 3.000 4.7 4.7 4.7 4.7 4.7 4.7 4.80\n')
+            fh.write('    }\n')
+            fh.write('  }\n')
+            fh.write(' }\n')        
+            fh.write('}\n') 
+        fh.write('}\n') 
+        fh.write('Options = {}\n')
+        fh.write('ParserOptions = {\n')
+        fh.write('  ParserVersion = 3\n')
+        fh.write('}\n')
+ 
+        fh.close()
+
+    def change_atom_positions_dftb(self, atoms):
+        """Write coordinates in DFTB+ input file dftb_in.hsd
+        """
+
+        filename = 'dftb_in.hsd'
+        if isinstance(filename, str):
+            myfile = open(filename)
+
+        lines = myfile.readlines()
+
+        if type(filename) == str:
+            myfile.close()
+
+        if isinstance(filename, str):
+            myfile = open(filename, 'w')
+        else: # Assume it's a 'file-like object'
+            myfile = filename
+
+        coord = atoms.get_positions()
+
+        start_writing_coords = False
+        stop_writing_coords = False
+        i = 0
+        for line in lines:
+            if ('TypesAndCoordinates' in line):
+                start_writing_coords = True
+            if (start_writing_coords and not(stop_writing_coords)):
+                if ('}' in line):
+                    stop_writing_coords = True
+            if (start_writing_coords and not(stop_writing_coords)and 
+                not ('TypesAndCoordinates' in line)):
+                atom_type_index = line.split()[0]
+                myfile.write('%6s  %20.14f  %20.14f  %20.14f\n'
+                        % (atom_type_index,coord[i][0],coord[i][1],coord[i][2]))
+                i = i + 1
+            else:
+                myfile.write(line)
Index: ase/thermochemistry.py
===================================================================
--- ase/thermochemistry.py	(revision 0)
+++ ase/thermochemistry.py	(revision 1)
@@ -0,0 +1,397 @@
+#!/usr/bin/env python
+"""Modules for calculating thermochemical information from computational
+outputs."""
+
+import os
+import sys
+import numpy as np
+
+from ase import units
+
+def rotationalinertia(atoms):
+    """Calculates the three principle moments of inertia for an ASE atoms
+    object. This uses the atomic masses from ASE, which (if not explicitly
+    specified by the user) gives an inexact approximation of an isotopically
+    averaged result. Units are in amu*angstroms**2."""
+
+    # Calculate the center of mass.
+    xcm, ycm, zcm = atoms.get_center_of_mass()
+    masses = atoms.get_masses()
+
+    # Calculate moments of inertia in the current frame of reference.
+    Ixx = 0.
+    Iyy = 0.
+    Izz = 0.
+    Ixy = 0.
+    Ixz = 0.
+    Iyz = 0.
+    for index, atom in enumerate(atoms):
+        m = masses[index]
+        x = atom.get_x() - xcm
+        y = atom.get_y() - ycm
+        z = atom.get_z() - zcm
+        Ixx += m * (y**2. + z**2.)
+        Iyy += m * (x**2. + z**2.)
+        Izz += m * (x**2. + y**2.)
+        Ixy += m * x * y
+        Ixz += m * x * z
+        Iyz += m * y * z
+    # Create the inertia tensor in the current frame of reference.
+    I_ = np.matrix([[ Ixx, -Ixy, -Ixz],
+                    [-Ixy,  Iyy, -Iyz],
+                    [-Ixz, -Iyz,  Izz]])
+    # Find the eigenvalues, which are the principle moments of inertia.
+    I = np.linalg.eigvals(I_)
+    return I
+
+class ThermoChem:
+    """Base class containing common methods used in thermochemistry
+    calculations."""
+
+    def get_ZPE_correction(self):
+        """Returns the zero-point vibrational energy correction in eV."""
+        zpe = 0.
+        for energy in self.vib_energies:
+            zpe += 0.5 * energy
+        return zpe
+
+    def _vibrational_energy_contribution(self, temperature):
+        """Calculates the change in internal energy due to vibrations from
+        0K to the specified temperature for a set of vibrations given in
+        inverse centimeters and a temperature given in Kelvin. Returns the
+        energy change in eV."""
+        kT = units.kB * temperature
+        dU = 0.
+        for energy in self.vib_energies:
+            dU += energy / (np.exp(energy/kT) - 1.)
+        return dU
+
+    def _vibrational_entropy_contribution(self, temperature):
+        """Calculates the entropy due to vibrations for a set of vibrations
+        given in inverse centimeters and a temperature given in Kelvin.
+        Returns the entropy in eV/K."""
+        kT = units.kB * temperature
+        S_v = 0.
+        for energy in self.vib_energies:
+            x = energy / kT
+            S_v += x / (np.exp(x)-1.) - np.log(1-np.exp(-x))
+        S_v *= units.kB
+        return S_v
+
+    def _vprint(self, text):
+        """Print output if verbose flag True."""
+        if self.verbose:
+            sys.stdout.write(text + os.linesep)
+
+class HarmonicThermo(ThermoChem):
+    """Class for calculating thermodynamic properties in the approximation
+    that all degrees of freedom are treated harmonically. Often used for
+    adsorbates.
+    
+    Inputs:
+
+    vib_energies : list
+        a list of the harmonic energies of the adsorbate (e.g., from
+        ase.vibrations.Vibrations.get_energies). The number of
+        energies should match the number of degrees of freedom of the
+        adsorbate; i.e., 3*n, where n is the number of atoms. Note that
+        this class does not check that the user has supplied the correct
+        number of energies. Units of energies are eV.
+    electronicenergy : float
+        the electronic energy in eV
+        (If the electronicenergy is unspecified, then the methods of this
+        class can be interpreted as the energy corrections.)
+    """
+
+    def __init__(self, vib_energies, electronicenergy=None):
+        self.vib_energies = vib_energies
+        # Check for imaginary frequencies.
+        if sum(np.iscomplex(self.vib_energies)):
+            raise ValueError('Imaginary vibrational energies are present.')
+        else:
+            self.vib_energies = np.real(self.vib_energies) # clear +0.j 
+
+        if electronicenergy:
+            self.electronicenergy = electronicenergy
+        else:
+            self.electronicenergy = 0.
+
+    def get_internal_energy(self, temperature, verbose=True):
+        """Returns the internal energy, in eV, in the harmonic approximation
+        at a specified temperature (K)."""
+
+        self.verbose = verbose
+        write = self._vprint
+        fmt = '%-15s%13.3f eV'
+        write('Internal energy components at T = %.2f K:' % temperature)
+        write('='*31)
+
+        U = 0.
+
+        write(fmt % ('E_elec', self.electronicenergy))
+        U += self.electronicenergy
+
+        zpe = self.get_ZPE_correction()
+        write(fmt % ('E_ZPE', zpe))
+        U += zpe
+
+        dU_v = self._vibrational_energy_contribution(temperature)
+        write(fmt % ('Cv_harm (0->T)', dU_v))
+        U += dU_v
+
+        write('-'*31)
+        write(fmt % ('U', U))
+        write('='*31)
+        return U
+
+    def get_entropy(self, temperature, verbose=True):
+        """Returns the entropy, in eV/K, in the harmonic approximation
+        at a specified temperature (K)."""
+
+        self.verbose = verbose
+        write = self._vprint
+        fmt = '%-15s%13.7f eV/K%13.3f eV'
+        write('Entropy components at T = %.2f K:' % temperature)
+        write('='*49)
+        write('%15s%13s     %13s'%('', 'S', 'T*S'))
+
+        S = 0.
+
+        S_v = self._vibrational_entropy_contribution(temperature)
+        write(fmt%('S_harm', S_v, S_v*temperature))
+        S += S_v
+
+        write('-'*49)
+        write(fmt%('S', S, S*temperature))
+        write('='*49)
+        return S
+
+    def get_free_energy(self, temperature, verbose=True):
+        """Returns the free energy, in eV, in the harmonic approximation
+        at a specified temperature (K)."""
+
+        self.verbose = True
+        write = self._vprint
+
+        U = self.get_internal_energy(temperature, verbose=verbose)
+        write('')
+        S = self.get_entropy(temperature, verbose=verbose)
+        G = U - temperature * S
+
+        write('')
+        write('Free energy components at T = %.2f K:' % temperature)
+        write('='*23)
+        fmt = '%5s%15.3f eV'
+        write(fmt % ('U', U))
+        write(fmt % ('-T*S', -temperature * S))
+        write('-'*23)
+        write(fmt % ('G', G))
+        write('='*23)
+        return G
+
+class IdealGasThermo(ThermoChem):
+    """Class for calculating thermodynamic properties of a molecule
+    based on statistical mechanical treatments in the ideal gas
+    approximation.
+
+    Inputs for enthalpy calculations:
+
+    vib_energies : list
+        a list of the vibrational energies of the molecule (e.g., from
+        ase.vibrations.Vibrations.get_energies). The number of vibrations
+        used is automatically calculated by the geometry and the number of
+        atoms. If more are specified than are needed, then the lowest 
+        numbered vibrations are neglected. If either atoms or natoms is 
+        unspecified, then uses the entire list. Units are eV.
+    geometry : 'monatomic', 'linear', or 'nonlinear'
+        geometry of the molecule
+    electronicenergy : float
+        the electronic energy in eV
+        (If electronicenergy is unspecified, then the methods of this
+        class can be interpreted as the enthalpy and free energy
+        corrections.)
+    natoms : integer
+        the number of atoms, used along with 'geometry' to determine how
+        many vibrations to use. (Not needed if an atoms object is supplied
+        in 'atoms' or if the user desires the entire list of vibrations
+        to be used.)
+
+    Extra inputs needed for for entropy / free energy calculations:
+
+    atoms : an ASE atoms object
+        used to calculate rotational moments of inertia and molecular mass
+    symmetrynumber : integer
+        symmetry number of the molecule. See, for example, Table 10.1 and
+        Appendix B of C. Cramer "Essentials of Computational Chemistry",
+        2nd Ed.
+    spin : float
+        the total electronic spin. (0 for molecules in which all electrons
+        are paired, 0.5 for a free radical with a single unpaired electron,
+        1.0 for a triplet with two unpaired electrons, such as O_2.)
+    """
+
+    def __init__(self, vib_energies, geometry, electronicenergy=None,
+                 atoms=None, symmetrynumber=None, spin=None, natoms=None):
+        if electronicenergy == None:
+            self.electronicenergy = 0.
+        else:
+            self.electronicenergy = electronicenergy
+        self.geometry = geometry
+        self.atoms = atoms
+        self.sigma = symmetrynumber
+        self.spin = spin
+        if natoms == None:
+            if atoms:
+                natoms = len(atoms)
+        # Cut the vibrations to those needed from the geometry.
+        if natoms:
+            if geometry == 'nonlinear':
+                self.vib_energies = vib_energies[-(3*natoms-6):]
+            elif geometry == 'linear':
+                self.vib_energies = vib_energies[-(3*natoms-5):]
+            elif geometry == 'monatomic':
+                self.vib_energies = []
+        else:
+            self.vib_energies = vib_energies
+        # Make sure no imaginary frequencies remain.
+        if sum(np.iscomplex(self.vib_energies)):
+            raise ValueError('Imaginary frequencies are present.')
+        else:
+            self.vib_energies = np.real(self.vib_energies) # clear +0.j 
+        self.referencepressure = 101325. # Pa
+
+    def get_enthalpy(self, temperature, verbose=True):
+        """Returns the enthalpy, in eV, in the ideal gas approximation
+        at a specified temperature (K)."""
+
+        self.verbose = verbose
+        write = self._vprint
+        fmt = '%-15s%13.3f eV'
+        write('Enthalpy components at T = %.2f K:' % temperature)
+        write('='*31)
+
+        H = 0.
+
+        write(fmt % ('E_elec', self.electronicenergy))
+        H += self.electronicenergy
+
+        zpe = self.get_ZPE_correction()
+        write(fmt % ('E_ZPE', zpe))
+        H += zpe
+
+        Cv_t = 3./2. * units.kB # translational heat capacity (3-d gas)
+        write(fmt % ('Cv_trans (0->T)', Cv_t * temperature))
+        H += Cv_t * temperature
+
+        if self.geometry == 'nonlinear': # rotational heat capacity
+            Cv_r = 3./2.*units.kB
+        elif self.geometry == 'linear':
+            Cv_r = units.kB
+        elif self.geometry == 'monatomic':
+            Cv_r = 0.
+        write(fmt % ('Cv_rot (0->T)', Cv_r * temperature))
+        H += Cv_r*temperature
+
+        dH_v = self._vibrational_energy_contribution(temperature)
+        write(fmt % ('Cv_vib (0->T)', dH_v))
+        H += dH_v
+
+        Cp_corr = units.kB * temperature
+        write(fmt % ('(C_v -> C_p)', Cp_corr))
+        H += Cp_corr
+
+        write('-'*31)
+        write(fmt % ('H', H))
+        write('='*31)
+        return H
+
+    def get_entropy(self, temperature, pressure, verbose=True):
+        """Returns the entropy, in eV/K, in the ideal gas approximation
+        at a specified temperature (K) and pressure (Pa)."""
+
+        if self.atoms == None or self.sigma == None or self.spin == None:
+            raise RuntimeError('atoms, symmetrynumber, and spin must be '
+                               'specified for entropy and free energy '
+                               'calculations.')
+        self.verbose = verbose
+        write = self._vprint
+        fmt = '%-15s%13.7f eV/K%13.3f eV'
+        write('Entropy components at T = %.2f K and P = %.1f Pa:' %
+               (temperature, pressure))
+        write('='*49)
+        write('%15s%13s     %13s' % ('', 'S', 'T*S'))
+
+        S = 0.
+
+        # Translational entropy (term inside the log is in SI units).
+        mass = sum(self.atoms.get_masses()) * units._amu # kg/molecule
+        S_t = (2*np.pi*mass*units._k*temperature/units._hplanck**2)**(3./2)
+        S_t *= units._k * temperature / self.referencepressure 
+        S_t = units.kB * (np.log(S_t) + 5./2.)
+        write(fmt % ('S_trans (1 atm)', S_t, S_t * temperature))
+        S += S_t
+
+        # Rotational entropy (term inside the log is in SI units).
+        if self.geometry == 'monatomic':
+            S_r = 0.
+        elif self.geometry == 'nonlinear':
+            inertias = (rotationalinertia(self.atoms) * units._amu /
+                        (10.**10)**2) # kg m^2
+            S_r = np.sqrt(np.pi*np.product(inertias)) / self.sigma
+            S_r *= (8. * np.pi**2 * units._k * temperature /
+                    units._hplanck**2)**(3./2.)
+            S_r = units.kB * (np.log(S_r) + 3./2.)
+        elif self.geometry == 'linear':
+            inertias = (rotationalinertia(self.atoms) * units._amu /
+                        (10.**10)**2) # kg m^2
+            inertia = max(inertias) # should be two identical and one zero
+            S_r = (8 * np.pi**2 * inertia * units._k * temperature /
+                   self.sigma / units._hplanck**2)
+            S_r = units.kB * ( np.log(S_r) + 1.)
+        write(fmt % ('S_rot', S_r, S_r*temperature))
+        S += S_r
+
+        # Electronic entropy.
+        S_e = units.kB * np.log(2*self.spin+1)
+        write(fmt % ('S_elec', S_e, S_e * temperature))
+        S += S_e
+
+        # Vibrational entropy.
+        S_v = self._vibrational_entropy_contribution(temperature)
+        write(fmt % ('S_vib', S_v, S_v* temperature))
+        S += S_v
+
+        # Pressure correction to translational entropy.
+        S_p = - units.kB * np.log(pressure/self.referencepressure)
+        write(fmt % ('S (1 atm -> P)', S_p, S_p * temperature))
+        S += S_p
+
+        write('-'*49)
+        write(fmt % ('S', S, S * temperature))
+        write('='*49)
+        return S
+
+    def get_free_energy(self, temperature, pressure, verbose=True):
+        """Returns the free energy, in eV, in the ideal gas approximation
+        at a specified temperature (K) and pressure (Pa)."""
+
+        self.verbose = verbose
+        write = self._vprint
+
+        H = self.get_enthalpy(temperature, verbose=verbose)
+        write('')
+        S = self.get_entropy(temperature, pressure, verbose=verbose)
+        G = H - temperature * S
+
+        write('')
+        write('Free energy components at T = %.2f K and P = %.1f Pa:' %
+               (temperature, pressure))
+        write('='*23)
+        fmt = '%5s%15.3f eV'
+        write(fmt % ('H', H))
+        write(fmt % ('-T*S', -temperature * S))
+        write('-'*23)
+        write(fmt % ('G', G))
+        write('='*23)
+        return G
+
Index: ase/dft/__init__.py
===================================================================
--- ase/dft/__init__.py	(revision 0)
+++ ase/dft/__init__.py	(revision 1)
@@ -0,0 +1,27 @@
+import numpy as np
+from ase.dft.stm import STM
+from ase.dft.dos import DOS
+from ase.dft.wannier import Wannier
+from ase.dft.kpoints import monkhorst_pack
+
+def get_distribution_moment(x, y, order=0):
+    """Return the moment of nth order of distribution.
+    
+    1st and 2nd order moments of a band correspond to the band's
+    center and width respectively.
+    
+    For integration, the trapezoid rule is used.
+    """
+
+    x = np.asarray(x)
+    y = np.asarray(y)
+        
+    if order==0:
+        return np.trapz(y, x)
+    elif isinstance(order, int):
+        return np.trapz(x**order * y, x) / np.trapz(y, x)
+    elif hasattr(order, '__iter__'):
+        return [get_distribution_moment(x, y, n) for n in order]
+    else:
+        raise ValueError('Illegal order: %s' % order)
+
Index: ase/dft/stm.py
===================================================================
--- ase/dft/stm.py	(revision 0)
+++ ase/dft/stm.py	(revision 1)
@@ -0,0 +1,167 @@
+from math import exp, sqrt
+
+import numpy as np
+
+from ase.atoms import Atoms
+
+
+class STM:
+    def __init__(self, atoms, symmetries=None):
+        if isinstance(atoms, Atoms):
+            calc = atoms.get_calculator()
+        else:
+            calc = atoms
+            atoms = calc.get_atoms()
+        self.nbands = calc.get_number_of_bands()
+        self.weights = calc.get_k_point_weights()
+        self.nkpts = len(self.weights)
+        self.nspins = calc.get_number_of_spins()
+        self.eigs = np.array([[calc.get_eigenvalues(k, s)
+                               for k in range(self.nkpts)]
+                              for s in range(self.nspins)])
+        self.eigs -= calc.get_fermi_level()
+        self.calc = calc
+        self.cell = atoms.get_cell()
+        assert not self.cell[2, :2].any() and not self.cell[:2, 2].any()
+        self.ldos = None
+        self.symmetries = symmetries or []
+                               
+    def calculate_ldos(self, width=None):
+        if self.ldos is not None and width == self.width:
+            return
+
+        if width is None:
+            width = 0.1
+            
+        ldos = None
+        for s in range(self.nspins):
+            for k in range(self.nkpts):
+                for n in range(self.nbands):
+                    psi = self.calc.get_pseudo_wave_function(n, k, s)
+                    if ldos is None:
+                        ldos = np.zeros_like(psi)
+                    f = (exp(-(self.eigs[s, k, n] / width)**2) *
+                         self.weights[k])
+                    ldos += f * (psi * np.conj(psi)).real
+
+        if 0 in self.symmetries:
+            # (x,y) -> (-x,y)
+            ldos[1:] += ldos[:0:-1].copy()
+            ldos[1:] *= 0.5
+
+        if 1 in self.symmetries:
+            # (x,y) -> (x,-y)
+            ldos[:, 1:] += ldos[:, :0:-1].copy()
+            ldos[:, 1:] *= 0.5
+            
+        if 2 in self.symmetries:
+            # (x,y) -> (y,x)
+            ldos += ldos.transpose((1, 0, 2)).copy()
+            ldos *= 0.5
+            
+        self.ldos = ldos
+        self.width = width
+
+    #def save_ldos(self, filename='ldos.pckl'):
+        
+
+    def get_averaged_current(self, z, width=None):
+        self.calculate_ldos(width)
+        nz = self.ldos.shape[2]
+
+        # Find grid point:
+        n = z / self.cell[2, 2] * nz
+        dn = n - np.floor(n)
+        n = int(n) % nz
+        print n,dn
+
+        # Average and do linear interpolation:
+        return ((1 - dn) * self.ldos[:, :, n].mean() +
+                dn *       self.ldos[:, :, (n + 1) % nz].mean())
+    
+    def scan(self, current, z=None, width=None):
+        self.calculate_ldos(width)
+
+        L = self.cell[2, 2]
+        if z is None:
+            z = L / 2
+
+        nz = self.ldos.shape[2]
+        n = int(round(z / L * nz)) % nz
+        h = L / nz
+
+        ldos = self.ldos.reshape((-1, nz))
+
+        heights = np.empty(ldos.shape[0])
+        for i, a in enumerate(ldos):
+            heights[i], z, n = find_height(a, current, z, n, nz, h)
+
+        heights.shape = self.ldos.shape[:2]
+        return heights
+    
+    def linescan(self, current, p1, p2, npoints=None, z=None, width=None):
+        self.calculate_ldos(width)
+
+        L = self.cell[2, 2]
+        if z is None:
+            z = L / 2
+
+        nz = self.ldos.shape[2]
+        n = int(round(z / L * nz)) % nz
+        h = L / nz
+        ldos = self.ldos.reshape((-1, nz))
+
+        p1 = np.asarray(p1)
+        p2 = np.asarray(p2)
+        d = p2 - p1
+        s = sqrt(np.dot(d, d))
+        
+        if npints == None:
+            npoints = int(3 * s / h + 2)
+
+        cell = self.cell[:2, :2]
+        shape = np.array(self.ldos.shape[:2], float)
+        M = cell.I
+        heights = np.empty(npoints)
+        for i in range(npoints):
+            p = p1 + i * d / (npoints - 1)
+            q = np.dot(M, p) * shape
+            qi = q.astype(int)
+            n0, n1 = qi
+            f = q - qi
+            g = 1 - f
+            a = (g[0] * g[0] * ldos[n0,     n1    ] +
+                 f[0] * g[0] * ldos[n0 + 1, n1    ] +
+                 g[0] * f[0] * ldos[n0,     n1 + 1] +
+                 f[0] * f[0] * ldos[n0 + 1, n1 + 1])
+            heights[i], z, n = find_height(a, current, z, n, nz, h)
+        return np.linspace(0, s, npoints), heights
+
+    def cube(self, filename, atoms=None):
+        pass
+
+
+def find_height(array, current, z, n, nz, h):
+    c1 = array[n]
+    sign = cmp(c1, current)
+    m = 0
+    while m < nz:
+        n = (n + sign) % nz
+        z += sign * h
+        c2 = array[n]
+        if cmp(c2, current) != sign:
+            break
+        c1 = c2
+        m += 1
+
+    if m == nz:
+        print z, n, nz, h, current, array
+        raise RuntimeError('Tip crash!')
+
+    return z - sign * h * (current - c2) / (c1 - c2), z, n
+
+                
+            
+        
+    
+        
Index: ase/dft/wannier.py
===================================================================
--- ase/dft/wannier.py	(revision 0)
+++ ase/dft/wannier.py	(revision 1)
@@ -0,0 +1,754 @@
+""" Maximally localized Wannier Functions
+
+    Find the set of maximally localized Wannier functions
+    using the spread functional of Marzari and Vanderbilt
+    (PRB 56, 1997 page 12847). 
+"""
+import numpy as np
+from time import time
+from math import sqrt, pi
+from pickle import dump, load
+from ase.parallel import paropen
+from ase.calculators.dacapo import Dacapo
+from ase.dft.kpoints import get_monkhorst_shape
+from ase.transport.tools import dagger, normalize
+
+dag = dagger
+
+
+def gram_schmidt(U):
+    """Orthonormalize columns of U according to the Gram-Schmidt procedure."""
+    for i, col in enumerate(U.T):
+        for col2 in U.T[:i]:
+            col -= col2 * np.dot(col2.conj(), col)
+        col /= np.linalg.norm(col)
+
+
+def lowdin(U, S=None):
+    """Orthonormalize columns of U according to the Lowdin procedure.
+    
+    If the overlap matrix is know, it can be specified in S.
+    """
+    if S is None:
+        S = np.dot(dag(U), U)
+    eig, rot = np.linalg.eigh(S)
+    rot = np.dot(rot / np.sqrt(eig), dag(rot))
+    U[:] = np.dot(U, rot)
+
+
+def neighbor_k_search(k_c, G_c, kpt_kc, tol=1e-4):
+    # search for k1 (in kpt_kc) and k0 (in alldir), such that
+    # k1 - k - G + k0 = 0
+    alldir_dc = np.array([[0,0,0],[1,0,0],[0,1,0],[0,0,1],
+                           [1,1,0],[1,0,1],[0,1,1]], int)
+    for k0_c in alldir_dc:
+        for k1, k1_c in enumerate(kpt_kc):
+            if np.linalg.norm(k1_c - k_c - G_c + k0_c) < tol:
+                return k1, k0_c
+
+    print 'Wannier: Did not find matching kpoint for kpt=', k_c
+    print 'Probably non-uniform k-point grid'
+    raise NotImplementedError
+
+
+def calculate_weights(cell_cc):
+    """ Weights are used for non-cubic cells, see PRB **61**, 10040"""
+    alldirs_dc = np.array([[1, 0, 0], [0, 1, 0], [0, 0, 1],
+                           [1, 1, 0], [1, 0, 1], [0, 1, 1]], dtype=int)
+    g = np.dot(cell_cc, cell_cc.T)
+    # NOTE: Only first 3 of following 6 weights are presently used:
+    w = np.zeros(6)              
+    w[0] = g[0, 0] - g[0, 1] - g[0, 2]
+    w[1] = g[1, 1] - g[0, 1] - g[1, 2]
+    w[2] = g[2, 2] - g[0, 2] - g[1, 2]
+    w[3] = g[0, 1]
+    w[4] = g[0, 2]
+    w[5] = g[1, 2]
+    # Make sure that first 3 Gdir vectors are included - 
+    # these are used to calculate Wanniercenters.
+    Gdir_dc = alldirs_dc[:3]
+    weight_d = w[:3]
+    for d in range(3, 6):
+        if abs(w[d]) > 1e-5:
+            Gdir_dc = np.concatenate(Gdir_dc, alldirs_dc[d])
+            weight_d = np.concatenate(weight_d, w[d])
+    weight_d /= max(abs(weight_d))
+    return weight_d, Gdir_dc
+
+
+def random_orthogonal_matrix(dim, seed=None, real=False):
+    """Generate a random orthogonal matrix"""
+    if seed is not None:
+        np.random.seed(seed)
+
+    H = np.random.rand(dim, dim)
+    np.add(dag(H), H, H)
+    np.multiply(.5, H, H)
+
+    if real:
+        gram_schmidt(H)
+        return H
+    else: 
+        val, vec = np.linalg.eig(H)
+        return np.dot(vec * np.exp(1.j * val), dag(vec))
+
+
+def steepest_descent(func, step=.005, tolerance=1e-6, **kwargs):
+    fvalueold = 0.
+    fvalue = fvalueold + 10
+    count=0
+    while abs((fvalue - fvalueold) / fvalue) > tolerance:
+        fvalueold = fvalue
+        dF = func.get_gradients()
+        func.step(dF * step, **kwargs)
+        fvalue = func.get_functional_value()
+        count += 1
+        print 'SteepestDescent: iter=%s, value=%s' % (count, fvalue)
+
+
+def md_min(func, step=.25, tolerance=1e-6, verbose=False, **kwargs):
+    if verbose:
+        print 'Localize with step =', step, 'and tolerance =', tolerance
+        t = -time()
+    fvalueold = 0.
+    fvalue = fvalueold + 10
+    count = 0
+    V = np.zeros(func.get_gradients().shape, dtype=complex)
+    while abs((fvalue - fvalueold) / fvalue) > tolerance:
+        fvalueold = fvalue
+        dF = func.get_gradients()
+        V *= (dF * V.conj()).real > 0
+        V += step * dF
+        func.step(V, **kwargs)
+        fvalue = func.get_functional_value()
+        if fvalue < fvalueold:
+            step *= 0.5
+        count += 1
+        if verbose:
+            print 'MDmin: iter=%s, step=%s, value=%s' % (count, step, fvalue)
+    if verbose:
+        t += time()
+        print '%d iterations in %0.2f seconds (%0.2f ms/iter), endstep = %s' %(
+            count, t, t * 1000. / count, step)
+
+
+def rotation_from_projection(proj_nw, fixed, ortho=True):
+    """Determine rotation and coefficient matrices from projections
+    
+    proj_nw = <psi_n|p_w>
+    psi_n: eigenstates
+    p_w: localized function
+    
+    Nb (n) = Number of bands
+    Nw (w) = Number of wannier functions
+    M  (f) = Number of fixed states
+    L  (l) = Number of extra degrees of freedom
+    U  (u) = Number of non-fixed states
+    """
+
+    Nb, Nw = proj_nw.shape
+    M = fixed
+    L = Nw - M
+
+    U_ww = np.empty((Nw, Nw), dtype=proj_nw.dtype)
+    U_ww[:M] = proj_nw[:M]
+
+    if L > 0:
+        proj_uw = proj_nw[M:]
+        eig_w, C_ww = np.linalg.eigh(np.dot(dag(proj_uw), proj_uw))
+        C_ul = np.dot(proj_uw, C_ww[:, np.argsort(-eig_w.real)[:L]])
+        #eig_u, C_uu = np.linalg.eigh(np.dot(proj_uw, dag(proj_uw)))
+        #C_ul = C_uu[:, np.argsort(-eig_u.real)[:L]]
+
+        U_ww[M:] = np.dot(dag(C_ul), proj_uw)
+    else:
+        C_ul = np.empty((Nb - M, 0))
+
+    normalize(C_ul)
+    if ortho:
+        lowdin(U_ww)
+    else:
+        normalize(U_ww)
+
+    return U_ww, C_ul
+
+
+class Wannier:
+    """Maximally localized Wannier Functions
+
+    Find the set of maximally localized Wannier functions using the
+    spread functional of Marzari and Vanderbilt (PRB 56, 1997 page
+    12847).
+    """
+
+    def __init__(self, nwannier, calc,
+                 file=None,
+                 nbands=None,
+                 fixedenergy=None,
+                 fixedstates=None,
+                 spin=0,
+                 initialwannier='random',
+                 seed=None,
+                 verbose=False):
+        """
+        Required arguments:
+
+          ``nwannier``: The number of Wannier functions you wish to construct.
+            This must be at least half the number of electrons in the system
+            and at most equal to the number of bands in the calculation.
+
+          ``calc``: A converged DFT calculator class.
+            If ``file`` arg. is not provided, the calculator *must* provide the
+            method ``get_wannier_localization_matrix``, and contain the
+            wavefunctions (save files with only the density is not enough).
+            If the localization matrix is read from file, this is not needed,
+            unless ``get_function`` or ``write_cube`` is called.
+          
+        Optional arguments:
+
+          ``nbands``: Bands to include in localization.
+            The number of bands considered by Wannier can be smaller than the
+            number of bands in the calculator. This is useful if the highest
+            bands of the DFT calculation are not well converged.
+
+          ``spin``: The spin channel to be considered.
+            The Wannier code treats each spin channel independently.
+
+          ``fixedenergy`` / ``fixedstates``: Fixed part of Heilbert space.
+            Determine the fixed part of Hilbert space by either a maximal
+            energy *or* a number of bands (possibly a list for multiple
+            k-points).
+            Default is None meaning that the number of fixed states is equated
+            to ``nwannier``.
+
+          ``file``: Read localization and rotation matrices from this file.
+
+          ``initialwannier``: Initial guess for Wannier rotation matrix.
+            Can be 'bloch' to start from the Bloch states, 'random' to be
+            randomized, or a list passed to calc.get_initial_wannier.
+
+          ``seed``: Seed for random ``initialwannier``.
+
+          ``verbose``: True / False level of verbosity.
+          """
+        # Bloch phase sign convention
+        sign = -1
+        classname = calc.__class__.__name__
+        if classname in ['Dacapo', 'Jacapo']:
+            print 'Using ' + classname
+            sign = +1
+            
+        self.nwannier = nwannier
+        self.calc = calc
+        self.spin = spin
+        self.verbose = verbose
+        self.kpt_kc = sign * calc.get_ibz_k_points()
+        assert len(calc.get_bz_k_points()) == len(self.kpt_kc)
+        
+        self.kptgrid = get_monkhorst_shape(self.kpt_kc)
+        self.Nk = len(self.kpt_kc)
+        self.unitcell_cc = calc.get_atoms().get_cell()
+        self.largeunitcell_cc = (self.unitcell_cc.T * self.kptgrid).T
+        self.weight_d, self.Gdir_dc = calculate_weights(self.largeunitcell_cc)
+        self.Ndir = len(self.weight_d) # Number of directions
+
+        if nbands is not None:
+            self.nbands = nbands
+        else:
+            self.nbands = calc.get_number_of_bands()
+        if fixedenergy is None:
+            if fixedstates is None:
+                self.fixedstates_k = np.array([nwannier] * self.Nk, int)
+            else:
+                if type(fixedstates) is int:
+                    fixedstates = [fixedstates] * self.Nk
+                self.fixedstates_k = np.array(fixedstates, int)
+        else:
+            # Setting number of fixed states and EDF from specified energy.
+            # All states below this energy (relative to Fermi level) are fixed.
+            fixedenergy += calc.get_fermi_level()
+            print fixedenergy
+            self.fixedstates_k = np.array(
+                [calc.get_eigenvalues(k, spin).searchsorted(fixedenergy)
+                 for k in range(self.Nk)], int)
+        self.edf_k = self.nwannier - self.fixedstates_k
+        if verbose:
+            print 'Wannier: Fixed states            : %s' % self.fixedstates_k
+            print 'Wannier: Extra degrees of freedom: %s' % self.edf_k
+
+        # Set the list of neighboring k-points k1, and the "wrapping" k0,
+        # such that k1 - k - G + k0 = 0
+        #
+        # Example: kpoints = (-0.375,-0.125,0.125,0.375), dir=0
+        # G = [0.25,0,0]
+        # k=0.375, k1= -0.375 : -0.375-0.375-0.25 => k0=[1,0,0]
+        #
+        # For a gamma point calculation k1 = k = 0,  k0 = [1,0,0] for dir=0
+        if self.Nk == 1:
+            self.kklst_dk = np.zeros((self.Ndir, 1), int)
+            k0_dkc = self.Gdir_dc.reshape(-1, 1, 3)
+        else:
+            self.kklst_dk = np.empty((self.Ndir, self.Nk), int)
+            k0_dkc = np.empty((self.Ndir, self.Nk, 3), int)
+
+            # Distance between kpoints
+            kdist_c = np.empty(3)
+            for c in range(3):
+                # make a sorted list of the kpoint values in this direction
+                slist = np.argsort(self.kpt_kc[:, c], kind='mergesort')
+                skpoints_kc = np.take(self.kpt_kc, slist, axis=0)
+                kdist_c[c] = max([skpoints_kc[n + 1, c] - skpoints_kc[n, c]
+                                  for n in range(self.Nk - 1)])               
+
+            for d, Gdir_c in enumerate(self.Gdir_dc):
+                for k, k_c in enumerate(self.kpt_kc):
+                    # setup dist vector to next kpoint
+                    G_c = np.where(Gdir_c > 0, kdist_c, 0)
+                    if max(G_c) < 1e-4:
+                        self.kklst_dk[d, k] = k
+                        k0_dkc[d, k] = Gdir_c
+                    else:
+                        self.kklst_dk[d, k], k0_dkc[d, k] = \
+                                       neighbor_k_search(k_c, G_c, self.kpt_kc)
+
+        # Set the inverse list of neighboring k-points
+        self.invkklst_dk = np.empty((self.Ndir, self.Nk), int)
+        for d in range(self.Ndir):
+            for k1 in range(self.Nk):
+                self.invkklst_dk[d, k1] = self.kklst_dk[d].tolist().index(k1)
+
+        Nw = self.nwannier
+        Nb = self.nbands
+        self.Z_dkww = np.empty((self.Ndir, self.Nk, Nw, Nw), complex)
+        self.V_knw = np.zeros((self.Nk, Nb, Nw), complex)
+        if file is None:
+            self.Z_dknn = np.empty((self.Ndir, self.Nk, Nb, Nb), complex)
+            for d, dirG in enumerate(self.Gdir_dc):
+                for k in range(self.Nk):
+                    k1 = self.kklst_dk[d, k]
+                    k0_c = k0_dkc[d, k]
+                    self.Z_dknn[d, k] = calc.get_wannier_localization_matrix(
+                        nbands=Nb, dirG=dirG, kpoint=k, nextkpoint=k1,
+                        G_I=k0_c, spin=self.spin)
+        self.initialize(file=file, initialwannier=initialwannier, seed=seed)
+
+    def initialize(self, file=None, initialwannier='random', seed=None):
+        """Re-initialize current rotation matrix.
+
+        Keywords are identical to those of the constructor.
+        """
+        Nw = self.nwannier
+        Nb = self.nbands
+
+        if file is not None:
+            self.Z_dknn, self.U_kww, self.C_kul = load(paropen(file))
+        elif initialwannier == 'bloch':
+            # Set U and C to pick the lowest Bloch states
+            self.U_kww = np.zeros((self.Nk, Nw, Nw), complex)
+            self.C_kul = []
+            for U, M, L in zip(self.U_kww, self.fixedstates_k, self.edf_k):
+                U[:] = np.identity(Nw, complex)
+                if L > 0:
+                    self.C_kul.append(
+                        np.identity(Nb - M, complex)[:, :L])
+                else:
+                    self.C_kul.append([])
+        elif initialwannier == 'random':
+            # Set U and C to random (orthogonal) matrices
+            self.U_kww = np.zeros((self.Nk, Nw, Nw), complex)
+            self.C_kul = []
+            for U, M, L in zip(self.U_kww, self.fixedstates_k, self.edf_k):
+                U[:] = random_orthogonal_matrix(Nw, seed, real=False)
+                if L > 0:
+                    self.C_kul.append(random_orthogonal_matrix(
+                        Nb - M, seed=seed, real=False)[:, :L])
+                else:
+                    self.C_kul.append(np.array([]))        
+        else:
+            # Use initial guess to determine U and C
+            self.C_kul, self.U_kww = self.calc.initial_wannier(
+                initialwannier, self.kptgrid, self.fixedstates_k,
+                self.edf_k, self.spin)
+        self.update()
+
+    def save(self, file):
+        """Save information on localization and rotation matrices to file."""
+        dump((self.Z_dknn, self.U_kww, self.C_kul), paropen(file, 'w'))
+
+    def update(self):
+        # Update large rotation matrix V (from rotation U and coeff C)
+        for k, M in enumerate(self.fixedstates_k):
+            self.V_knw[k, :M] = self.U_kww[k, :M]
+            if M < self.nwannier:
+                self.V_knw[k, M:] = np.dot(self.C_kul[k], self.U_kww[k, M:])
+            # else: self.V_knw[k, M:] = 0.0
+
+        # Calculate the Zk matrix from the large rotation matrix:
+        # Zk = V^d[k] Zbloch V[k1]
+        for d in range(self.Ndir):
+            for k in range(self.Nk):
+                k1 = self.kklst_dk[d, k]
+                self.Z_dkww[d, k] = np.dot(dag(self.V_knw[k]), np.dot(
+                    self.Z_dknn[d, k], self.V_knw[k1]))
+
+        # Update the new Z matrix
+        self.Z_dww = self.Z_dkww.sum(axis=1) / self.Nk
+
+    def get_centers(self, scaled=False):
+        """Calculate the Wannier centers
+
+        ::
+        
+          pos =  L / 2pi * phase(diag(Z))
+        """
+        coord_wc = np.angle(self.Z_dww[:3].diagonal(0, 1, 2)).T / (2 * pi) % 1
+        if not scaled:
+            coord_wc = np.dot(coord_wc, self.largeunitcell_cc)
+        return coord_wc
+
+    def get_radii(self):
+        """Calculate the spread of the Wannier functions.
+
+        ::
+          
+                        --  /  L  \ 2       2
+          radius**2 = - >   | --- |   ln |Z| 
+                        --d \ 2pi /
+        """
+        r2 = -np.dot(self.largeunitcell_cc.diagonal()**2 / (2 * pi)**2,
+                     np.log(abs(self.Z_dww[:3].diagonal(0, 1, 2))**2))
+        return np.sqrt(r2)
+
+    def get_spectral_weight(self, w):
+        return abs(self.V_knw[:, :, w])**2 / self.Nk
+
+    def get_pdos(self, w, energies, width):
+        """Projected density of states (PDOS).
+
+        Returns the (PDOS) for Wannier function ``w``. The calculation
+        is performed over the energy grid specified in energies. The
+        PDOS is produced as a sum of Gaussians centered at the points
+        of the energy grid and with the specified width.
+        """
+        spec_kn = self.get_spectral_weight(w)
+        dos = np.zeros(len(energies))
+        for k, spec_n in enumerate(spec_kn):
+            eig_n = self.calc.get_eigenvalues(k=kpt, s=self.spin)
+            for weight, eig in zip(spec_n, eig):
+                # Add gaussian centered at the eigenvalue
+                x = ((energies - center) / width)**2
+                dos += weight * np.exp(-x.clip(0., 40.)) / (sqrt(pi) * width)
+        return dos
+
+    def max_spread(self, directions=[0, 1, 2]):
+        """Returns the index of the most delocalized Wannier function
+        together with the value of the spread functional"""
+        d = np.zeros(self.nwannier)
+        for dir in directions:
+            d[dir] = np.abs(self.Z_dww[dir].diagonal())**2 *self.weight_d[dir]
+        index = np.argsort(d)[0]
+        print 'Index:', index
+        print 'Spread:', d[index]           
+
+    def translate(self, w, R):
+        """Translate the w'th Wannier function
+
+        The distance vector R = [n1, n2, n3], is in units of the basis
+        vectors of the small cell.
+        """
+        for kpt_c, U_ww in zip(self.kpt_kc, self.U_kww):
+            U_ww[:, w] *= np.exp(2.j * pi * np.dot(np.array(R), kpt_c))
+        self.update()
+
+    def translate_to_cell(self, w, cell):
+        """Translate the w'th Wannier function to specified cell"""
+        scaled_c = np.angle(self.Z_dww[:3, w, w]) * self.kptgrid / (2 * pi)
+        trans = np.array(cell) - np.floor(scaled_c)
+        self.translate(w, trans)
+
+    def translate_all_to_cell(self, cell=[0, 0, 0]):
+        """Translate all Wannier functions to specified cell.
+
+        Move all Wannier orbitals to a specific unit cell.  There
+        exists an arbitrariness in the positions of the Wannier
+        orbitals relative to the unit cell. This method can move all
+        orbitals to the unit cell specified by ``cell``.  For a
+        `\Gamma`-point calculation, this has no effect. For a
+        **k**-point calculation the periodicity of the orbitals are
+        given by the large unit cell defined by repeating the original
+        unitcell by the number of **k**-points in each direction.  In
+        this case it is usefull to move the orbitals away from the
+        boundaries of the large cell before plotting them. For a bulk
+        calculation with, say 10x10x10 **k** points, one could move
+        the orbitals to the cell [2,2,2].  In this way the pbc
+        boundary conditions will not be noticed.
+        """
+        scaled_wc = np.angle(self.Z_dww[:3].diagonal(0, 1, 2)).T  * \
+                    self.kptgrid / (2 * pi)
+        trans_wc =  np.array(cell)[None] - np.floor(scaled_wc)
+        for kpt_c, U_ww in zip(self.kpt_kc, self.U_kww):
+            U_ww *= np.exp(2.j * pi * np.dot(trans_wc, kpt_c))
+        self.update()
+
+    def distances(self, R):
+        Nw = self.nwannier
+        cen = self.get_centers()
+        r1 = cen.repeat(Nw, axis=0).reshape(Nw, Nw, 3)
+        r2 = cen.copy()
+        for i in range(3):
+            r2 += self.unitcell_cc[i] * R[i]
+
+        r2 = np.swapaxes(r2.repeat(Nw, axis=0).reshape(Nw, Nw, 3), 0, 1)
+        return np.sqrt(np.sum((r1 - r2)**2, axis=-1))
+
+    def get_hopping(self, R):
+        """Returns the matrix H(R)_nm=<0,n|H|R,m>.
+
+        ::
+        
+                                1   _   -ik.R 
+          H(R) = <0,n|H|R,m> = --- >_  e      H(k)
+                                Nk  k         
+
+        where R is the cell-distance (in units of the basis vectors of
+        the small cell) and n,m are indices of the Wannier functions.
+        """
+        H_ww = np.zeros([self.nwannier, self.nwannier], complex)
+        for k, kpt_c in enumerate(self.kpt_kc):
+            phase = np.exp(-2.j * pi * np.dot(np.array(R), kpt_c))
+            H_ww += self.get_hamiltonian(k) * phase
+        return H_ww / self.Nk
+
+    def get_hamiltonian(self, k=0):
+        """Get Hamiltonian at existing k-vector of index k
+
+        ::
+        
+                  dag
+          H(k) = V    diag(eps )  V
+                  k           k    k
+        """
+        eps_n = self.calc.get_eigenvalues(kpt=k, spin=self.spin)
+        return np.dot(dag(self.V_knw[k]) * eps_n, self.V_knw[k])
+
+    def get_hamiltonian_kpoint(self, kpt_c):
+        """Get Hamiltonian at some new arbitrary k-vector
+
+        ::
+        
+                  _   ik.R 
+          H(k) = >_  e     H(R)
+                  R         
+
+        Warning: This method moves all Wannier functions to cell (0, 0, 0)
+        """
+        if self.verbose:
+            print 'Translating all Wannier functions to cell (0, 0, 0)'
+        self.translate_all_to_cell()
+        max = (self.kptgrid - 1) / 2
+        max += max > 0
+        N1, N2, N3 = max
+        Hk = np.zeros([self.nwannier, self.nwannier], complex)
+        for n1 in xrange(-N1, N1 + 1):
+            for n2 in xrange(-N2, N2 + 1):
+                for n3 in xrange(-N3, N3 + 1):
+                    R = np.array([n1, n2, n3], float)
+                    hop_ww = self.get_hopping(R)
+                    phase = np.exp(+2.j * pi * np.dot(R, kpt_c))
+                    Hk += hop_ww * phase
+        return Hk
+
+    def get_function(self, index, repeat=None):
+        """Get Wannier function on grid.
+
+        Returns an array with the funcion values of the indicated Wannier
+        function on a grid with the size of the *repeated* unit cell.
+       
+        For a calculation using **k**-points the relevant unit cell for
+        eg. visualization of the Wannier orbitals is not the original unit
+        cell, but rather a larger unit cell defined by repeating the
+        original unit cell by the number of **k**-points in each direction.
+        Note that for a `\Gamma`-point calculation the large unit cell
+        coinsides with the original unit cell.
+        The large unitcell also defines the periodicity of the Wannier
+        orbitals.
+
+        ``index`` can be either a single WF or a coordinate vector in terms
+        of the WFs.
+        """
+
+        # Default size of plotting cell is the one corresponding to k-points.
+        if repeat is None:
+            repeat = self.kptgrid
+        N1, N2, N3 = repeat
+
+        dim = self.calc.get_number_of_grid_points()
+        largedim = dim * [N1, N2, N3]
+        
+        wanniergrid = np.zeros(largedim, dtype=complex)
+        for k, kpt_c in enumerate(self.kpt_kc):
+            # The coordinate vector of wannier functions
+            if type(index) == int:
+                vec_n = self.V_knw[k, :, index]
+            else:   
+                vec_n = np.dot(self.V_knw[k], index)
+
+            wan_G = np.zeros(dim, complex)
+            for n, coeff in enumerate(vec_n):
+                wan_G += coeff * self.calc.get_pseudo_wave_function(
+                    n, k, self.spin, pad=True)
+
+            # Distribute the small wavefunction over large cell:
+            for n1 in xrange(N1):
+                for n2 in xrange(N2):
+                    for n3 in xrange(N3): # sign?
+                        e = np.exp(-2.j * pi * np.dot([n1, n2, n3], kpt_c))
+                        wanniergrid[n1 * dim[0]:(n1 + 1) * dim[0],
+                                    n2 * dim[1]:(n2 + 1) * dim[1],
+                                    n3 * dim[2]:(n3 + 1) * dim[2]] += e * wan_G
+
+        # Normalization
+        wanniergrid /= np.sqrt(self.Nk)
+        return wanniergrid
+
+    def write_cube(self, index, fname, repeat=None, real=True):
+        """Dump specified Wannier function to a cube file"""
+        from ase.io.cube import write_cube
+
+        # Default size of plotting cell is the one corresponding to k-points.
+        if repeat is None:
+            repeat = self.kptgrid
+        atoms = self.calc.get_atoms() * repeat
+        func = self.get_function(index, repeat)
+
+        # Handle separation of complex wave into real parts
+        if real:
+            if self.Nk == 1:
+                func *= np.exp(-1.j * np.angle(func.max()))
+                if 0: assert max(abs(func.imag).flat) < 1e-4
+                func = func.real
+            else:
+                func = abs(func)
+        else:
+            phase_fname = fname.split('.')
+            phase_fname.insert(1, 'phase')
+            phase_fname = '.'.join(phase_fname)
+            write_cube(phase_fname, atoms, data=np.angle(func))
+            func = abs(func)
+
+        write_cube(fname, atoms, data=func)
+
+    def localize(self, step=0.25, tolerance=1e-08,
+                 updaterot=True, updatecoeff=True):
+        """Optimize rotation to give maximal localization"""
+        md_min(self, step, tolerance, verbose=self.verbose,
+               updaterot=updaterot, updatecoeff=updatecoeff)
+
+    def get_functional_value(self): 
+        """Calculate the value of the spread functional.
+
+        ::
+
+          Tr[|ZI|^2]=sum(I)sum(n) w_i|Z_(i)_nn|^2,
+
+        where w_i are weights."""
+        a_d = np.sum(np.abs(self.Z_dww.diagonal(0, 1, 2))**2, axis=1)
+        return np.dot(a_d, self.weight_d).real
+
+    def get_gradients(self):
+        # Determine gradient of the spread functional.
+        # 
+        # The gradient for a rotation A_kij is::
+        # 
+        #    dU = dRho/dA_{k,i,j} = sum(I) sum(k')
+        #            + Z_jj Z_kk',ij^* - Z_ii Z_k'k,ij^*
+        #            - Z_ii^* Z_kk',ji + Z_jj^* Z_k'k,ji
+        # 
+        # The gradient for a change of coefficients is::
+        # 
+        #   dRho/da^*_{k,i,j} = sum(I) [[(Z_0)_{k} V_{k'} diag(Z^*) +
+        #                                (Z_0_{k''})^d V_{k''} diag(Z)] *
+        #                                U_k^d]_{N+i,N+j}
+        # 
+        # where diag(Z) is a square,diagonal matrix with Z_nn in the diagonal, 
+        # k' = k + dk and k = k'' + dk.
+        # 
+        # The extra degrees of freedom chould be kept orthonormal to the fixed
+        # space, thus we introduce lagrange multipliers, and minimize instead::
+        # 
+        #     Rho_L=Rho- sum_{k,n,m} lambda_{k,nm} <c_{kn}|c_{km}>
+        # 
+        # for this reason the coefficient gradients should be multiplied
+        # by (1 - c c^d).
+        
+        Nb = self.nbands
+        Nw = self.nwannier
+        
+        dU = []
+        dC = []
+        for k in xrange(self.Nk):
+            M = self.fixedstates_k[k]
+            L = self.edf_k[k]
+            U_ww = self.U_kww[k]
+            C_ul = self.C_kul[k]
+            Utemp_ww = np.zeros((Nw, Nw), complex)
+            Ctemp_nw = np.zeros((Nb, Nw), complex)
+
+            for d, weight in enumerate(self.weight_d):
+                if abs(weight) < 1.0e-6:
+                    continue
+
+                Z_knn = self.Z_dknn[d]
+                diagZ_w = self.Z_dww[d].diagonal()
+                Zii_ww = np.repeat(diagZ_w, Nw).reshape(Nw, Nw)
+                k1 = self.kklst_dk[d, k]
+                k2 = self.invkklst_dk[d, k]
+                V_knw = self.V_knw
+                Z_kww = self.Z_dkww[d]
+                
+                if L > 0:
+                    Ctemp_nw += weight * np.dot(
+                        np.dot(Z_knn[k], V_knw[k1]) * diagZ_w.conj() +
+                        np.dot(dag(Z_knn[k2]), V_knw[k2]) * diagZ_w,
+                        dag(U_ww))
+
+                temp = Zii_ww.T * Z_kww[k].conj() - Zii_ww * Z_kww[k2].conj()
+                Utemp_ww += weight * (temp - dag(temp))
+            dU.append(Utemp_ww.ravel())
+            if L > 0:
+                # Ctemp now has same dimension as V, the gradient is in the
+                # lower-right (Nb-M) x L block
+                Ctemp_ul = Ctemp_nw[M:, M:]
+                G_ul = Ctemp_ul - np.dot(np.dot(C_ul, dag(C_ul)), Ctemp_ul)
+                dC.append(G_ul.ravel())
+
+        return np.concatenate(dU + dC)
+                        
+    def step(self, dX, updaterot=True, updatecoeff=True):
+        # dX is (A, dC) where U->Uexp(-A) and C->C+dC
+        Nw = self.nwannier
+        Nk = self.Nk
+        M_k = self.fixedstates_k
+        L_k = self.edf_k
+        if updaterot:
+            A_kww = dX[:Nk * Nw**2].reshape(Nk, Nw, Nw)
+            for U, A in zip(self.U_kww, A_kww):
+                H = -1.j * A.conj()
+                epsilon, Z = np.linalg.eigh(H)
+                # Z contains the eigenvectors as COLUMNS.
+                # Since H = iA, dU = exp(-A) = exp(iH) = ZDZ^d
+                dU = np.dot(Z * np.exp(1.j * epsilon), dag(Z))
+                U[:] = np.dot(U, dU)
+
+        if updatecoeff:
+            start = 0
+            for C, unocc, L in zip(self.C_kul, self.nbands - M_k, L_k):
+                if L == 0 or unocc == 0:
+                    continue
+                Ncoeff = L * unocc
+                deltaC = dX[Nk * Nw**2 + start: Nk * Nw**2 + start + Ncoeff]
+                C += deltaC.reshape(unocc, L)
+                gram_schmidt(C)
+                start += Ncoeff
+
+        self.update()
Index: ase/dft/dos.py
===================================================================
--- ase/dft/dos.py	(revision 0)
+++ ase/dft/dos.py	(revision 1)
@@ -0,0 +1,67 @@
+from math import pi, sqrt
+
+import numpy as np
+
+
+class DOS:
+    def __init__(self, calc, width=0.1, window=None, npts=201):
+        """Electronic Density Of States object.
+
+        calc: calculator object
+            Any ASE compliant calculator object.
+        width: float
+            Width of guassian smearing.
+        window: tuple of two float
+            Use ``window=(emin, emax)``.  If not specified, a window
+            big enough to hold all the eigenvalues will be used.
+        npts: int
+            Number of points.
+
+        """
+        
+        self.npts = npts
+        self.width = width
+        self.w_k = calc.get_k_point_weights()
+        self.nspins = calc.get_number_of_spins()
+        self.e_skn = np.array([[calc.get_eigenvalues(kpt=k, spin=s)
+                                for k in range(len(self.w_k))]
+                               for s in range(self.nspins)])
+        self.e_skn -= calc.get_fermi_level()
+
+        if window is None:
+            emin = self.e_skn.min() - 5 * self.width
+            emax = self.e_skn.max() + 5 * self.width
+        else:
+            emin, emax = window
+
+        self.energies = np.linspace(emin, emax, npts)
+
+    def get_energies(self):
+        """Return the array of energies used to sample the DOS."""
+        return self.energies
+
+    def delta(self, energy):
+        """Return a delta-function centered at 'energy'."""
+        x = -((self.energies - energy) / self.width)**2
+        return np.exp(x) / (sqrt(pi) * self.width)
+
+    def get_dos(self, spin=None):
+        """Get array of DOS values.
+
+        The *spin* argument can be 0 or 1 (spin up or down) - if not
+        specified, the total DOS is returned.
+        """
+        
+        if spin is None:
+            if self.nspins == 2:
+                # Spin-polarized calculation, but no spin specified -
+                # return the total DOS:
+                return self.get_dos(spin=0) + self.get_dos(spin=1)
+            else:
+                spin = 0
+        
+        dos = np.zeros(self.npts)
+        for w, e_n in zip(self.w_k, self.e_skn[spin]):
+            for e in e_n:
+                dos += w * self.delta(e)
+        return dos
Index: ase/dft/kpoints.py
===================================================================
--- ase/dft/kpoints.py	(revision 0)
+++ ase/dft/kpoints.py	(revision 1)
@@ -0,0 +1,207 @@
+from __future__ import division
+import numpy as np
+
+
+def monkhorst_pack(size):
+    """Construct a uniform sampling of k-space of given size."""
+    if np.less_equal(size, 0).any():
+        raise ValueError('Illegal size: %s' % list(size))
+    kpts = np.indices(size).transpose((1, 2, 3, 0)).reshape((-1, 3))
+    return (kpts + 0.5) / size - 0.5
+
+
+def get_monkhorst_shape(kpts, tol=1e-5):
+    """Return the number of k-points along each axis of input Monkhorst pack.
+
+    The set of k-points must not have been symmetry reduced.
+    """
+    nkpts = len(kpts)
+    if nkpts == 1:
+        return np.ones(3, int)
+    
+    Nk_c = np.zeros(3, int)
+    for c in range(3):
+        # Determine increment between kpoints along current axis
+        DeltaK = max(np.diff(np.sort(kpts[:, c])))
+
+        # Determine number of kpoints as inverse of distance between kpoints
+        if DeltaK > tol:
+            Nk_c[c] = int(round(1. / DeltaK))
+        else:
+            Nk_c[c] = 1
+    return Nk_c
+
+
+def kpoint_convert(cell_cv, skpts_kc=None, ckpts_kv=None):
+    """Convert k-points between scaled and cartesian coordinates.
+
+    Given the atomic unit cell, and either the scaled or cartesian k-point
+    coordinates, the other is determined.
+
+    The k-point arrays can be either a single point, or a list of points,
+    i.e. the dimension k can be empty or multidimensional.
+    """
+    if ckpts_kv is None:
+        icell_cv = 2 * np.pi * np.linalg.inv(cell_cv).T
+        return np.dot(skpts_kc, icell_cv)
+    elif skpts_kc is None:
+        return np.dot(ckpts_kv, cell_cv.T) / (2 * np.pi)
+    else:
+        raise KeyError('Either scaled or cartesian coordinates must be given.')
+
+
+def get_bandpath(points, cell, npoints=50):
+    """Make a list of kpoints defining the path between the given points.
+
+    points: list
+        List of special IBZ point pairs, e.g. ``points =
+        [W, L, Gamma, X, W, K]``.  These should be given in
+        scaled coordinates.
+    cell: 3x3 ndarray
+        Unit cell of the atoms.
+    npoints: int
+        Length of the output kpts list.
+
+    Return list of k-points, list of x-coordinates and list of
+    x-coordinates of special points."""
+
+    points = np.asarray(points)
+    dists = points[1:] - points[:-1]
+    lengths = [np.linalg.norm(d) for d in kpoint_convert(cell, skpts_kc=dists)]
+    length = sum(lengths)
+    kpts = []
+    x0 = 0
+    x = []
+    X = [0]
+    for P, d, L in zip(points[:-1], dists, lengths):
+        n = int(round(L * (npoints - 1 - len(x)) / (length - x0)))
+        for t in np.linspace(0, 1, n, endpoint=False):
+            kpts.append(P + t * d)
+            x.append(x0 + t * L)
+        x0 += L
+        X.append(x0)
+    kpts.append(points[-1])
+    x.append(x0)
+    return kpts, np.array(x), np.array(X)
+
+
+# The following is a list of the critical points in the 1. Brillouin zone
+# for some typical crystal structures.
+# (In units of the reciprocal basis vectors)
+# See http://en.wikipedia.org/wiki/Brillouin_zone
+ibz_points = {'cubic': {'Gamma': [0,     0,     0    ],
+                        'X':     [0,     0 / 2, 1 / 2],
+                        'R':     [1 / 2, 1 / 2, 1 / 2],
+                        'M':     [0 / 2, 1 / 2, 1 / 2]},
+
+              'fcc':   {'Gamma': [0,     0,     0    ],
+                        'X':     [1 / 2, 0,     1 / 2],
+                        'W':     [1 / 2, 1 / 4, 3 / 4],
+                        'K':     [3 / 8, 3 / 8, 3 / 4],
+                        'U':     [5 / 8, 1 / 4, 5 / 8],
+                        'L':     [1 / 2, 1 / 2, 1 / 2]},
+
+              'bcc':   {'Gamma': [0,      0,     0    ],
+                        'H':     [1 / 2, -1 / 2, 1 / 2],
+                        'N':     [0,      0,     1 / 2],
+                        'P':     [1 / 4,  1 / 4, 1 / 4]},
+
+              }
+
+# ChadiCohen k point grids. The k point grids are given in units of the
+# reciprocal unit cell. The variables are named after the following
+# convention: cc+'<Nkpoints>'+_+'shape'. For example an 18 k point
+# sq(3)xsq(3) is named 'cc18_sq3xsq3'.
+
+cc6_1x1 = np.array([1, 1, 0, 1, 0, 0, 0, -1, 0, -1, -1, 0, -1, 0, 0,
+    0, 1, 0] ).reshape((6, 3)) / 3.0
+
+cc12_2x3 = np.array([3, 4, 0, 3, 10, 0, 6, 8, 0, 3, -2, 0, 6, -4, 0,
+    6, 2, 0, -3, 8, 0, -3, 2, 0, -3, -4, 0, -6, 4, 0, -6, -2, 0, -6,
+    -8, 0] ).reshape((12, 3)) / 18.0
+
+cc18_sq3xsq3 = np.array([2, 2, 0, 4, 4, 0, 8, 2, 0, 4, -2, 0, 8, -4,
+    0, 10, -2, 0, 10, -8, 0, 8, -10, 0, 2, -10, 0, 4, -8, 0, -2, -8,
+    0, 2, -4, 0, -4, -4, 0, -2, -2, 0, -4, 2, 0, -2, 4, 0, -8, 4, 0,
+    -4, 8, 0] ).reshape((18, 3)) / 18.0
+
+cc18_1x1 = np.array([2, 4, 0, 2, 10, 0, 4, 8, 0, 8, 4, 0, 8, 10, 0,
+    10, 8, 0, 2, -2, 0, 4, -4, 0, 4, 2, 0, -2, 8, 0, -2, 2, 0, -2, -4,
+    0, -4, 4, 0, -4, -2, 0, -4, -8, 0, -8, 2, 0, -8, -4, 0, -10, -2,
+    0] ).reshape((18, 3)) / 18.0
+
+cc54_sq3xsq3 = np.array([4, -10, 0, 6, -10, 0, 0, -8, 0, 2, -8, 0, 6,
+    -8, 0, 8, -8, 0, -4, -6, 0, -2, -6, 0, 2, -6, 0, 4, -6, 0, 8, -6,
+    0, 10, -6, 0, -6, -4, 0, -2, -4, 0, 0, -4, 0, 4, -4, 0, 6, -4, 0,
+    10, -4, 0, -6, -2, 0, -4, -2, 0, 0, -2, 0, 2, -2, 0, 6, -2, 0, 8,
+    -2, 0, -8, 0, 0, -4, 0, 0, -2, 0, 0, 2, 0, 0, 4, 0, 0, 8, 0, 0,
+    -8, 2, 0, -6, 2, 0, -2, 2, 0, 0, 2, 0, 4, 2, 0, 6, 2, 0, -10, 4,
+    0, -6, 4, 0, -4, 4, 0, 0, 4, 0, 2, 4, 0, 6, 4, 0, -10, 6, 0, -8,
+    6, 0, -4, 6, 0, -2, 6, 0, 2, 6, 0, 4, 6, 0, -8, 8, 0, -6, 8, 0,
+    -2, 8, 0, 0, 8, 0, -6, 10, 0, -4, 10, 0]).reshape((54, 3)) / 18.0
+
+cc54_1x1 = np.array([2, 2, 0, 4, 4, 0, 8, 8, 0, 6, 8, 0, 4, 6, 0, 6,
+    10, 0, 4, 10, 0, 2, 6, 0, 2, 8, 0, 0, 2, 0, 0, 4, 0, 0, 8, 0, -2,
+    6, 0, -2, 4, 0, -4, 6, 0, -6, 4, 0, -4, 2, 0, -6, 2, 0, -2, 0, 0,
+    -4, 0, 0, -8, 0, 0, -8, -2, 0, -6, -2, 0, -10, -4, 0, -10, -6, 0,
+    -6, -4, 0, -8, -6, 0, -2, -2, 0, -4, -4, 0, -8, -8, 0, 4, -2, 0,
+    6, -2, 0, 6, -4, 0, 2, 0, 0, 4, 0, 0, 6, 2, 0, 6, 4, 0, 8, 6, 0,
+    8, 0, 0, 8, 2, 0, 10, 4, 0, 10, 6, 0, 2, -4, 0, 2, -6, 0, 4, -6,
+    0, 0, -2, 0, 0, -4, 0, -2, -6, 0, -4, -6, 0, -6, -8, 0, 0, -8, 0,
+    -2, -8, 0, -4, -10, 0, -6, -10, 0] ).reshape((54, 3)) / 18.0
+
+cc162_sq3xsq3 = np.array([-8, 16, 0, -10, 14, 0, -7, 14, 0, -4, 14,
+    0, -11, 13, 0, -8, 13, 0, -5, 13, 0, -2, 13, 0, -13, 11, 0, -10,
+    11, 0, -7, 11, 0, -4, 11, 0, -1, 11, 0, 2, 11, 0, -14, 10, 0, -11,
+    10, 0, -8, 10, 0, -5, 10, 0, -2, 10, 0, 1, 10, 0, 4, 10, 0, -16,
+    8, 0, -13, 8, 0, -10, 8, 0, -7, 8, 0, -4, 8, 0, -1, 8, 0, 2, 8, 0,
+    5, 8, 0, 8, 8, 0, -14, 7, 0, -11, 7, 0, -8, 7, 0, -5, 7, 0, -2, 7,
+    0, 1, 7, 0, 4, 7, 0, 7, 7, 0, 10, 7, 0, -13, 5, 0, -10, 5, 0, -7,
+    5, 0, -4, 5, 0, -1, 5, 0, 2, 5, 0, 5, 5, 0, 8, 5, 0, 11, 5, 0,
+    -14, 4, 0, -11, 4, 0, -8, 4, 0, -5, 4, 0, -2, 4, 0, 1, 4, 0, 4, 4,
+    0, 7, 4, 0, 10, 4, 0, -13, 2, 0, -10, 2, 0, -7, 2, 0, -4, 2, 0,
+    -1, 2, 0, 2, 2, 0, 5, 2, 0, 8, 2, 0, 11, 2, 0, -11, 1, 0, -8, 1,
+    0, -5, 1, 0, -2, 1, 0, 1, 1, 0, 4, 1, 0, 7, 1, 0, 10, 1, 0, 13, 1,
+    0, -10, -1, 0, -7, -1, 0, -4, -1, 0, -1, -1, 0, 2, -1, 0, 5, -1,
+    0, 8, -1, 0, 11, -1, 0, 14, -1, 0, -11, -2, 0, -8, -2, 0, -5, -2,
+    0, -2, -2, 0, 1, -2, 0, 4, -2, 0, 7, -2, 0, 10, -2, 0, 13, -2, 0,
+    -10, -4, 0, -7, -4, 0, -4, -4, 0, -1, -4, 0, 2, -4, 0, 5, -4, 0,
+    8, -4, 0, 11, -4, 0, 14, -4, 0, -8, -5, 0, -5, -5, 0, -2, -5, 0,
+    1, -5, 0, 4, -5, 0, 7, -5, 0, 10, -5, 0, 13, -5, 0, 16, -5, 0, -7,
+    -7, 0, -4, -7, 0, -1, -7, 0, 2, -7, 0, 5, -7, 0, 8, -7, 0, 11, -7,
+    0, 14, -7, 0, 17, -7, 0, -8, -8, 0, -5, -8, 0, -2, -8, 0, 1, -8,
+    0, 4, -8, 0, 7, -8, 0, 10, -8, 0, 13, -8, 0, 16, -8, 0, -7, -10,
+    0, -4, -10, 0, -1, -10, 0, 2, -10, 0, 5, -10, 0, 8, -10, 0, 11,
+    -10, 0, 14, -10, 0, 17, -10, 0, -5, -11, 0, -2, -11, 0, 1, -11, 0,
+    4, -11, 0, 7, -11, 0, 10, -11, 0, 13, -11, 0, 16, -11, 0, -1, -13,
+    0, 2, -13, 0, 5, -13, 0, 8, -13, 0, 11, -13, 0, 14, -13, 0, 1,
+    -14, 0, 4, -14, 0, 7, -14, 0, 10, -14, 0, 13, -14, 0, 5, -16, 0,
+    8, -16, 0, 11, -16, 0, 7, -17, 0, 10, -17, 0]).reshape((162, 3)) / 27.0
+
+cc162_1x1 = np.array([-8, -16, 0, -10, -14, 0, -7, -14, 0, -4, -14,
+    0, -11, -13, 0, -8, -13, 0, -5, -13, 0, -2, -13, 0, -13, -11, 0,
+    -10, -11, 0, -7, -11, 0, -4, -11, 0, -1, -11, 0, 2, -11, 0, -14,
+    -10, 0, -11, -10, 0, -8, -10, 0, -5, -10, 0, -2, -10, 0, 1, -10,
+    0, 4, -10, 0, -16, -8, 0, -13, -8, 0, -10, -8, 0, -7, -8, 0, -4,
+    -8, 0, -1, -8, 0, 2, -8, 0, 5, -8, 0, 8, -8, 0, -14, -7, 0, -11,
+    -7, 0, -8, -7, 0, -5, -7, 0, -2, -7, 0, 1, -7, 0, 4, -7, 0, 7, -7,
+    0, 10, -7, 0, -13, -5, 0, -10, -5, 0, -7, -5, 0, -4, -5, 0, -1,
+    -5, 0, 2, -5, 0, 5, -5, 0, 8, -5, 0, 11, -5, 0, -14, -4, 0, -11,
+    -4, 0, -8, -4, 0, -5, -4, 0, -2, -4, 0, 1, -4, 0, 4, -4, 0, 7, -4,
+    0, 10, -4, 0, -13, -2, 0, -10, -2, 0, -7, -2, 0, -4, -2, 0, -1,
+    -2, 0, 2, -2, 0, 5, -2, 0, 8, -2, 0, 11, -2, 0, -11, -1, 0, -8,
+    -1, 0, -5, -1, 0, -2, -1, 0, 1, -1, 0, 4, -1, 0, 7, -1, 0, 10, -1,
+    0, 13, -1, 0, -10, 1, 0, -7, 1, 0, -4, 1, 0, -1, 1, 0, 2, 1, 0, 5,
+    1, 0, 8, 1, 0, 11, 1, 0, 14, 1, 0, -11, 2, 0, -8, 2, 0, -5, 2, 0,
+    -2, 2, 0, 1, 2, 0, 4, 2, 0, 7, 2, 0, 10, 2, 0, 13, 2, 0, -10, 4,
+    0, -7, 4, 0, -4, 4, 0, -1, 4, 0, 2, 4, 0, 5, 4, 0, 8, 4, 0, 11, 4,
+    0, 14, 4, 0, -8, 5, 0, -5, 5, 0, -2, 5, 0, 1, 5, 0, 4, 5, 0, 7, 5,
+    0, 10, 5, 0, 13, 5, 0, 16, 5, 0, -7, 7, 0, -4, 7, 0, -1, 7, 0, 2,
+    7, 0, 5, 7, 0, 8, 7, 0, 11, 7, 0, 14, 7, 0, 17, 7, 0, -8, 8, 0,
+    -5, 8, 0, -2, 8, 0, 1, 8, 0, 4, 8, 0, 7, 8, 0, 10, 8, 0, 13, 8, 0,
+    16, 8, 0, -7, 10, 0, -4, 10, 0, -1, 10, 0, 2, 10, 0, 5, 10, 0, 8,
+    10, 0, 11, 10, 0, 14, 10, 0, 17, 10, 0, -5, 11, 0, -2, 11, 0, 1,
+    11, 0, 4, 11, 0, 7, 11, 0, 10, 11, 0, 13, 11, 0, 16, 11, 0, -1,
+    13, 0, 2, 13, 0, 5, 13, 0, 8, 13, 0, 11, 13, 0, 14, 13, 0, 1, 14,
+    0, 4, 14, 0, 7, 14, 0, 10, 14, 0, 13, 14, 0, 5, 16, 0, 8, 16, 0,
+    11, 16, 0, 7, 17, 0, 10, 17, 0]).reshape((162, 3)) / 27.0
Index: ase/examples/COCu111.py
===================================================================
--- ase/examples/COCu111.py	(revision 0)
+++ ase/examples/COCu111.py	(revision 1)
@@ -0,0 +1,73 @@
+from math import sqrt
+from ase import Atoms, Atom
+from ase.constraints import FixAtoms
+from ase.optimize import QuasiNewton
+from ase.io import PickleTrajectory
+from ase.neb import NEB
+from ase.calculators.emt import EMT
+
+# Distance between Cu atoms on a (111) surface:
+a = 3.6
+d = a / sqrt(2)
+y = d * sqrt(3) / 2
+fcc111 = Atoms('Cu',
+               cell=[(d, 0, 0),
+                     (d / 2, y, 0),
+                     (d / 2, y / 3, -a / sqrt(3))],
+               pbc=True)
+slab = fcc111 * (2, 2, 4)
+slab.set_cell([2 * d, 2 * y, 1])
+slab.set_pbc((1, 1, 0))
+slab.set_calculator(EMT())
+Z = slab.get_positions()[:, 2]
+indices = [i for i, z in enumerate(Z) if z < Z.mean()]
+constraint = FixAtoms(indices=indices)
+slab.set_constraint(constraint)
+dyn = QuasiNewton(slab)
+dyn.run(fmax=0.05)
+Z = slab.get_positions()[:, 2]
+print Z[0] - Z[1]
+print Z[1] - Z[2]
+print Z[2] - Z[3]
+
+b = 1.2
+h = 2.0
+slab += Atom('C', (d, 2 * y / 3, h))
+slab += Atom('O', (3 * d / 2, y / 3, h))
+traj = PickleTrajectory('initial.traj', 'w', slab)
+dyn = QuasiNewton(slab)
+dyn.attach(traj.write)
+dyn.run(fmax=0.05)
+#view(slab)
+# Make band:
+images = [slab.copy() for i in range(6)]
+neb = NEB(images, climb=True)
+
+# Set constraints and calculator:
+for image in images:
+    image.set_calculator(EMT())
+    image.set_constraint(constraint)
+
+# Displace last image:
+images[-1].positions[-1] = (2 * d, 2 * y / 3, h)
+traj = PickleTrajectory('final.traj', 'w', images[-1])
+dyn = QuasiNewton(images[-1])
+dyn.attach(traj.write)
+dyn.run(fmax=0.05)
+
+# Interpolate positions between initial and final states:
+neb.interpolate()
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
+
+traj = PickleTrajectory('mep.traj', 'w')
+
+#dyn = MDMin(neb, dt=0.4)
+#dyn = FIRE(neb, dt=0.4)
+dyn = QuasiNewton(neb)
+dyn.attach(neb.writer(traj))
+dyn.run(fmax=0.05)
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
Index: ase/examples/Al100.py
===================================================================
--- ase/examples/Al100.py	(revision 0)
+++ ase/examples/Al100.py	(revision 1)
@@ -0,0 +1,10 @@
+from ase import Atom, Atoms
+from gpaw import GPAW
+a = 4.0
+b = a / 2**.5
+L = 7.0
+al = Atoms([Atom('Al')], cell=(b, b, L), pbc=True)
+calc = GPAW(kpts=(4, 4, 1))
+al.set_calculator(calc)
+al.get_potential_energy()
+calc.write('Al100.gpw', 'all')
Index: ase/examples/Pt_island.py
===================================================================
--- ase/examples/Pt_island.py	(revision 0)
+++ ase/examples/Pt_island.py	(revision 1)
@@ -0,0 +1,74 @@
+import numpy as np
+from math import sqrt
+from ase import Atom, Atoms
+from ase.optimize import QuasiNewton, FIRE
+from ase.constraints import FixAtoms
+from ase.neb import NEB
+from ase.io import write, PickleTrajectory
+from ase.calculators.emt import ASAP
+
+# Distance between Cu atoms on a (100) surface:
+d = 2.74
+h1 = d * sqrt(3) / 2
+h2 = d * sqrt(2.0 / 3)
+initial = Atoms(symbols='Pt',
+                positions=[(0, 0, 0)],#(1.37,0.79,2.24),(2.74,1.58,4.48),(0,0,6.72),(1.37,0.79,8.96),(2.74,1.58,11.2)],
+                cell=([(d,0,0),(d/2,h1,0),(d/2,h1/3,-h2)]),
+                pbc=(True, True, True))
+initial *= (7, 8, 6)  # 5x5 (100) surface-cell
+cell = initial.get_cell()
+cell[2] = (0, 0, 22)
+initial.set_cell(cell)
+#initial.set_pbc((True,True,False))
+# Approximate height of Ag atom on Cu(100) surfece:
+h0 = 2.2373
+initial += Atom('Pt', (10.96, 11.074, h0))
+initial += Atom('Pt', (13.7, 11.074, h0))
+initial += Atom('Pt', (9.59, 8.701, h0))
+initial += Atom('Pt', (12.33, 8.701, h0))
+initial += Atom('Pt', (15.07, 8.701, h0))
+initial += Atom('Pt', (10.96, 6.328, h0))
+initial += Atom('Pt', (13.7, 6.328, h0))
+
+if 0:
+    view(initial)
+
+# Make band:
+images = [initial.copy() for i in range(7)]
+neb = NEB(images)
+
+# Set constraints and calculator:
+indices = np.compress(initial.positions[:, 2] < -5.0, range(len(initial)))
+constraint = FixAtoms(indices)
+for image in images:
+    image.set_calculator(ASAP())
+    image.constraints.append(constraint)
+
+# Displace last image:
+for i in xrange(1,8,1):
+    images[-1].positions[-i] += (d/2, -h1/3, 0)
+
+write('initial.traj', images[0])
+# Relax height of Ag atom for initial and final states:
+for image in [images[0], images[-1]]:
+    QuasiNewton(image).run(fmax=0.01)
+
+if 0:
+    write('initial.pckl', image[0])
+    write('finial.pckl', image[-1])
+# Interpolate positions between initial and final states:
+neb.interpolate()
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
+
+traj = PickleTrajectory('mep.traj', 'w')
+
+dyn = FIRE(neb, dt=0.1)
+#dyn = MDMin(neb, dt=0.1)
+#dyn = QuasiNewton(neb)
+dyn.attach(neb.writer(traj))
+dyn.run(fmax=0.01,steps=150)
+
+for image in images:
+    print image.positions[-1], image.get_potential_energy()
Index: ase/examples/stm.py
===================================================================
--- ase/examples/stm.py	(revision 0)
+++ ase/examples/stm.py	(revision 1)
@@ -0,0 +1,11 @@
+from ase import Atoms
+from ase.dft import STM
+from gpaw import GPAW
+calc = GPAW('Al100.gpw')
+a0 = calc.get_atoms()
+stm = STM(calc, [0, 1, 2])
+c = stm.get_averaged_current(2.5)
+h = stm.scan(c)
+print h[8]-h[:, 8]
+
+
Index: ase/examples/neb1.py
===================================================================
--- ase/examples/neb1.py	(revision 0)
+++ ase/examples/neb1.py	(revision 1)
@@ -0,0 +1,55 @@
+from ase import *
+from math import sqrt
+
+a = 4.0614
+b = a / sqrt(2)
+h = b / 2
+initial = Atoms([Atom('Al', (0, 0, 0)),
+                 Atom('Al', (a / 2, b / 2, -h))],
+                pbc=(1, 1, 0),
+                cell=(a, b, 2 * h))
+initial *= (2, 2, 2)
+initial.append(Atom('Al', (a / 2, b / 2, 3 * h)))
+
+#view(initial)
+
+#initial.set_cell((2*2,2*b,)
+final = initial.copy()
+final.positions[-1, 1] += b
+
+# Construct a list of images:
+images = [initial]
+for i in range(5):
+    images.append(initial.copy())
+images.append(final)
+
+# Make a mask of zeros and ones that select the dynamic atoms (the
+# three topmost layers):
+mask = initial.positions[:, 2] < 0.5 * h
+constraint = FixAtoms(mask=mask)
+print mask
+print 'Fixed atoms:', constraint.fixed
+
+for image in images:
+    # Let all images use an EMT calculator:
+    image.set_calculator(EMT())
+    image.set_constraint(constraint)
+
+# Relax the initial and final states:
+QuasiNewton(initial).run(fmax=0.05)
+QuasiNewton(final).run(fmax=0.05)
+
+# Create a Nudged Elastic Band:
+neb = NEB(images)
+
+# Mak a starting guess for the minimum energy path (a straight line
+# from the initial to the final state):
+neb.interpolate()
+
+# Use MDMin to relax the path:
+minimizer = QuasiNewton(neb)
+minimizer.run(fmax=0.05)
+
+# Write the path to a trajectory:
+traj = PickleTrajectory('jump1.traj', 'w')
+neb.write(traj)
Index: ase/examples/plot-ase-atoms.py
===================================================================
--- ase/examples/plot-ase-atoms.py	(revision 0)
+++ ase/examples/plot-ase-atoms.py	(revision 1)
@@ -0,0 +1,51 @@
+from ase import Atoms, Atom, view
+from gpaw import GPAW
+
+logo = """\
+ H   HH HHH
+H H H   H
+HHH  H  HH
+H H   H H
+H H HH  HHH"""
+
+d = 0.8
+atoms = Atoms()
+for y, line in enumerate(logo.split('\n')):
+    for x, c in enumerate(line):
+        if c == 'H':
+            atoms.append(Atom('H', [d * x, -d * y, 0]))
+atoms.center(vacuum=2.0)
+view(atoms)
+
+if 0:
+    calc = GPAW(nbands=30)
+    atoms.set_calculator(calc)
+    atoms.get_potential_energy()
+    calc.write('ase-logo.gpw')
+else:
+    calc = GPAW('ase-logo.gpw', txt=None)
+
+density = calc.get_pseudo_density()
+image = density[..., density.shape[2] // 2]
+
+if 1: # scale colors to wiki background / foreground
+    import numpy as np
+    background = np.array([[[19., 63., 82.]]]).T / 255 # 1c4e63 blueish
+    foreground = np.array([[[1., 1., 1.]]]).T  # white
+    image = background + image / image.max() * (foreground - background)
+    image = image.T
+else: # Use a standard color scheme
+    image = pl.cm.hot(image.T)
+
+import pylab as pl
+x, y, z = atoms.cell.diagonal()
+pl.figure(1, figsize=(8, 8 * y / x), dpi=80)
+pl.axes([0, 0, 1, 1])
+pl.imshow(image,
+          origin='lower',
+          extent=[0, x, 0, y],
+          aspect='equal',
+          interpolation='spline16')
+pl.axis('off')
+pl.savefig('ase-logo.png', dpi=80)
+pl.show()
Index: PKG-INFO
===================================================================
--- PKG-INFO	(revision 0)
+++ PKG-INFO	(revision 1)
@@ -0,0 +1,11 @@
+Metadata-Version: 1.0
+Name: python-ase
+Version: 3.4.1.1765
+Summary: Atomic Simulation Environment
+Home-page: https://wiki.fysik.dtu.dk/ase
+Author: CAMd
+Author-email: camd@fysik.dtu.dk
+License: LGPLv2.1+
+Description: ASE is a python package providing an open source Atomic Simulation
+        Environment in the python scripting language.
+Platform: linux
Index: README.txt
===================================================================
--- README.txt	(revision 0)
+++ README.txt	(revision 1)
@@ -0,0 +1,5 @@
+==========
+Python ASE
+==========
+
+Webpage: http://wiki.fysik.dtu.dk/ase

Property changes on: README.txt
___________________________________________________________________
Added: svn:executable
   + 

