#! /usr/bin/env python
# coding=utf-8
"""A module containing routines for setting up simple friction simulations.
"""
# get Pysic - this is the package that handles atomic interactions
# see http://thynnine.github.io/pysic/
import pysic
# get some components of ASE - this is the package that handles dynamics
# see https://wiki.fysik.dtu.dk/ase/
from ase.lattice.compounds import Rocksalt
#from ase.structure import bulk
from ase.lattice import bulk
from ase.md.velocitydistribution import MaxwellBoltzmannDistribution
from ase.md.verlet import VelocityVerlet
from ase.md.npt import NPT
from ase.md.langevin import Langevin
from ase.md.nvtberendsen import NVTBerendsen
from ase.optimize import BFGSLineSearch
from ase.io.trajectory import PickleTrajectory
from ase.constraints import FixAtoms
from ase.lattice.hexagonal import Graphite
import ase.io
from ase import Atoms
from ase import units
# get numeric tools from NumPy - see http://scipy.org
import numpy as np
import numpy.random as nr
# get Python tools - these are part of the Python default library
import math
import time
import copy
# get plotting tools
try:
    import matplotlib.pyplot as plt
except:
    print "MatPlotLib not found, plotting will not work"
[docs]def set_random_number_seed(seed=5801):
    """Initialize random number generator (RNG).
    By default, the RNG is always given the same seed so that simulations are reproducible.
    However, if you would like to rerun simulations with different random sequences,
    give a new random seed here.
    This affects :meth:`set_initial_temperature` and :meth:`create_dynamics` / :math:`run_simulation`
    if Langevin dynamics are chosen.
    Parameters:
    seed: integer
        a new RNG seed
    """
    nr.seed(seed) 
set_random_number_seed()
# mod the Langevin dynamics class from ASE
from ase.parallel import world
from ase.md.md import MolecularDynamics
import sys
class LangevinMod(Langevin):
    def __init__(self, atoms, timestep, temperature, friction, fixcm=False,
                   trajectory=None, logfile=None, loginterval=1, communicator=world,
                   applyArray=None):
          """A copy of the Langevin dynamics class from ASE in case modifications need to be made.
          In Langevin dynamics, a friction force is applied to moving atoms to prevent
          them from accumulating too much kinetic energy. This will drive the temperature
          to zero over time, and so it is balanced by a randomly fluctuating force that
          affects each atom independently. The strength of this random force will determine
          the temperature of the system in equilibrium.
          """
          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()
          self.apply = applyArray
    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.reshape(-1) * (2.0/3.0 - 0.5 * lt) * lt)
          sdpos.shape = (-1, 1)
          sdmom = np.sqrt(self.temp * masses.reshape(-1) * 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.reshape(-1)
          c1.shape = (-1, 1)
          c2 = act2 * dt * dt / masses.reshape(-1)
          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
          self.natoms = self.atoms.get_number_of_atoms()
    def step(self, f):
          atoms = self.atoms
          p = self.atoms.get_momenta()
          random1 = nr.standard_normal(size=(len(atoms), 3))
          random2 = nr.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)
              rrnd *= np.sqrt(self.natoms / (self.natoms - 1.0))
              prnd *= np.sqrt(self.natoms / (self.natoms - 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(atoms.get_momenta() + self.c4 * f)
          return f
[docs]class FixPositions:
    """A constraint class for fixing atomic positions
    """
    def __init__(self, indices, fixed):
        self.indices = indices
        self.fixed = fixed
    def adjust_positions(self, oldpositions, newpositions):
        for index in self.indices:
            for i in range(3):
                if self.fixed[i]:
                    newpositions[index,i] = oldpositions[index,i]
    def adjust_forces(self, positions, forces):
        for index in self.indices:
            for i in range(3):
                if self.fixed[i]:
                    forces[index,i] = 0.0
    def copy(self):
        return copy.deepcopy(self) 
[docs]class FixVelocities:
    """A constraint class for fixing atomic velocities
    """
    def __init__(self, indices, velocity, dt, fixed):
        self.indices = indices
        self.fixed = fixed
        self.step = np.array(velocity, dtype=float)*dt
        self.velocity = np.array(velocity)
    def adjust_positions(self, oldpositions, newpositions):
        for index in self.indices:
            for i in range(3):
                if self.fixed[i]:
                    newpositions[index,i] = oldpositions[index,i] + self.step[i]
    def adjust_forces(self, positions, forces):
        for index in self.indices:
            for i in range(3):
                if self.fixed[i]:
                    forces[index,i] = 0.0
    def copy(self):
        return copy.deepcopy(self) 
[docs]class FixPlane:
    """A constraint class for fixing atomic positions in a plane
    """
    def __init__(self, indices, normal):
        self.indices = indices
        self.normal = np.array(normal) / math.sqrt(np.dot(normal,normal))
    def adjust_positions(self, oldpositions, newpositions):
        for index in self.indices:
            newpositions[index] -= self.normal * \
                
np.dot(newpositions[index] - oldpositions[index], self.normal)
    def adjust_forces(self, positions, forces):
        for index in self.indices:
            forces[index] -= self.normal * np.dot(forces[index], self.normal)
    def copy(self):
        return copy.deepcopy(self) 
[docs]class FixLine:
    """A constraint class for fixing atomic positions on a line
    """
    def __init__(self, indices, direction):
        self.indices = indices
        self.direction = np.array(direction) / math.sqrt(np.dot(direction,direction))
    def adjust_positions(self, oldpositions, newpositions):
        for index in self.indices:
            tangent = np.dot(newpositions[index] - oldpositions[index], self.direction)
            newpositions[index] = oldpositions[index] + tangent * self.direction
    def adjust_forces(self, positions, forces):
        for index in self.indices:
            tangent = np.dot(forces[index], self.direction)
            forces[index] = tangent * self.direction
    def copy(self):
        return copy.deepcopy(self) 
[docs]class FrictionSimulation:
    """A class defining a simulation environment.
    The class contains convenient methods for setting up a simulation.
    """
    # Default values
    timestep = 2.0
    cell_width = 10.0
    def __init__(self):
        self.dynamics = None
        self.system = None
        self.trajectory = None
        self.calc = None
        self.old_positions = None
        self.prev_positions = None
        self.prev_forces = None
        self.work = np.zeros(0,dtype=float)
        self.timestep = FrictionSimulation.timestep
        self.cell_width = FrictionSimulation.cell_width
        self.system = ase.Atoms()
        self.calc = pysic.Pysic()
        self.system.set_calculator(self.calc)
        pysic.utility.error.set_warning_level(1)
    def __del__(self):
        if not self.trajectory is None:
            self.trajectory.close()
[docs]    def create_graphite(self):
        """Creates a graphite structure.
        The slab is 2D infinite in xy-plane, its surfaces exposed in the z-direction.
        The structure is FCC, see e.g. http://en.wikipedia.org/wiki/Face_Centered_Cubic
        Parameters:
        element: string
            the chemical symbol for the atoms of the slab, e.g., 'Au'
        bottom_z: real number
            the z-coordinate of the lower surface of the slab
        top_z: real number
            the z-coordinate of the top surface of the slab
        xy_cells: integer
            the number of lattice cells to be created in x and y directions - as the
            width of the simulation box is determined by the :class:`FrictionSimulation`
            ('cell_width'), this will determine the lattice constant
        z_cells: integer
            the number of lattice cells to be created in z direction
        """
        graphite = Graphite(
            symbol='C',
            latticeconstant={'a': 2.46, 'c': 4*math.sqrt(3)/3*2.46},
            size=(5, 5, 1)
        )
        # from ase.visualize import view
        # view(graphite)
        self.system.set_cell(graphite.get_cell())
        self.system.set_pbc([True, True, False])
        self.system += graphite 
[docs]    def create_graphite2(self):
        """Creates a graphite structure.
        The slab is 2D infinite in xy-plane, its surfaces exposed in the z-direction.
        The structure is FCC, see e.g. http://en.wikipedia.org/wiki/Face_Centered_Cubic
        Parameters:
        element: string
            the chemical symbol for the atoms of the slab, e.g., 'Au'
        bottom_z: real number
            the z-coordinate of the lower surface of the slab
        top_z: real number
            the z-coordinate of the top surface of the slab
        xy_cells: integer
            the number of lattice cells to be created in x and y directions - as the
            width of the simulation box is determined by the :class:`FrictionSimulation`
            ('cell_width'), this will determine the lattice constant
        z_cells: integer
            the number of lattice cells to be created in z direction
        """
        # Create top part
        a = 2.46
        d = math.sqrt(3)/3*a
        positions = np.array([
            [0.0, 0.0, 0.0],
            [1.0/2.0*d, math.sqrt(3)/2.0*d, 0.0],
            [3.0/2.0*d, math.sqrt(3)/2.0*d, 0.0],
            [2.0*d, 0.0, 0.0]
        ])
        cell = np.array(
            [3*d, math.sqrt(3)*d, 3]
        )
        top = ase.Atoms(symbols="CCCC", positions=positions, cell=cell)
        top *= [3, 3, 1]
        # Create bottom part
        bottom = ase.Atoms(symbols="NNNN", positions=positions, cell=cell)
        bottom *= [3, 3, 1]
        bottom.translate([d, 0, 3])
        self.system += top
        self.system += bottom
        self.system.set_cell(top.get_cell())
        self.system.set_pbc([True, True, False]) 
        # print(self.system.get_cell())
        # from ase.visualize import view
        # view(self.system)
[docs]    def create_slab(self, element, bottom_z=0.0, top_z=None, z_cells=3, xy_cells=4):
        """Creates a slab of atoms in the FCC (face-centered cubic) lattice structure.
        The slab is 2D infinite in xy-plane, its surfaces exposed in the z-direction.
        The structure is FCC, see e.g. http://en.wikipedia.org/wiki/Face_Centered_Cubic
        Parameters:
        element: string
            the chemical symbol for the atoms of the slab, e.g., 'Au'
        bottom_z: real number
            the z-coordinate of the lower surface of the slab
        top_z: real number
            the z-coordinate of the top surface of the slab
        xy_cells: integer
            the number of lattice cells to be created in x and y directions - as the
            width of the simulation box is determined by the :class:`FrictionSimulation`
            ('cell_width'), this will determine the lattice constant
        z_cells: integer
            the number of lattice cells to be created in z direction
        """
        lattice_constant = self.cell_width / xy_cells
        print "Lattice constant of the created slab: {}".format(lattice_constant)
        slab = bulk(element, 'fcc', a=lattice_constant, cubic=True)*(xy_cells,xy_cells,z_cells)
        if top_z is None:
            slab.translate([0.0, 0.0, bottom_z])
        elif bottom_z == 0.0:
            slab.translate([0.0, 0.0, top_z-lattice_constant*(z_cells-0.5)])
        else:
            print "Only give either the top or the bottom of the slab as an argument."
            print "Setting the z-position accirding to bottom_z = {bz}".format(bz=str(bottom_z))
            slab.translate([0.0, 0.0, bottom_z])
        self.system += slab
        self.system.set_cell([[lattice_constant*xy_cells,0.0,0.0],
                        [0.0,lattice_constant*xy_cells,0.0],
                        [0.0,0.0,100.0]])
        self.system.set_pbc([True, True, False]) 
[docs]    def create_random_atoms(self,number,element,bottom_z,top_z, minimum_distance=2.0):
        """Adds the given number of atoms randomly in a given volume.
        The atoms are added randomly and for each added atom it is checked that
        it is not too close to previously added atoms. If you try to insert too many
        atoms, there will not be enough room and the algorithm stops after failing
        for long enough.
        Parameters:
        number: integer
            the number of atoms to add
        element: string
            the chemical symbol of the atoms, e.g., 'C'
        bottom_z: real number
            the minimum z coordinate the atoms may get
        top_z: real number
            the maximum z coordinate the atoms may get
        minimum_distance: real number
            the minimum allowed separation between the added atoms
        """
        if top_z < bottom_z:
            print "Maximum z must be greater than minimum z. Swapping the values."
            (top_z, bottom_z) = (bottom_z, top_z)
        imps = 0
        tries = 0
        positions = []
        minimum_squared = minimum_distance*minimum_distance
        while imps < number:
            if tries >= 1000:
                print "Cannot find space for new atoms, terminating."
                break
            rx = nr.random() * self.cell_width
            ry = nr.random() * self.cell_width
            rz = nr.random() * (top_z-bottom_z) + bottom_z
            new_position = [rx, ry, rz]
            if imps == 0:
                # add the first atom
                positions = np.array([new_position])
                imps += 1
            else:
                # check that the new position is not too close to previous ones
                for old_pos in positions:
                    separation = np.array([0.0, 0.0, 0.0])
                    # take periodic boundary conditions in to account
                    for i in range(3):
                        separation[i] = new_position[i]-old_pos[i]
                        if separation[i] > self.cell_width/2.0:
                            separation[i] - self.cell_width
                        elif separation[i] < self.cell_width/2.0:
                            separation[i] + self.cell_width
                    distance_squared = np.dot(separation, separation)
                    if distance_squared < minimum_squared:
                        tries += 1
                        break
                else:
                    # if all the previous positions were far away
                    positions = np.append(positions, [new_position], axis=0)
                    imps += 1
                    tries = 0
        print "Creating {ni} atoms.".format(ni=str(imps))
        atoms = Atoms(element+str(imps), positions)
        self.system += atoms
        self.system.set_cell([ [self.cell_width, 0.0, 0.0],
                         [0.0, self.cell_width, 0.0],
                         [0.0, 0.0, 100.0] ])
        self.system.set_pbc([True, True, False]) 
[docs]    def create_atoms(self,element,positions):
        """Creates atoms of the given element in the given positions.
        The atoms are stored in :attr:`tools.FrictionSimulation.pieces`.
        Once you've added all the pieces you need, finalize the system with
        :meth:`tools.FrictionSimulation.finalize_system`.
        Parameters:
        element: string
            the chemical symbol of the atoms, e.g., 'C'
        positions: array of real numbers
            the xyz coordinates of the atoms, in an array, e.g., ``[[0, 0, 0], [1, 1, 1]]``
        """
        atoms = Atoms(element+str(len(positions)), np.array(positions))
        self.system += atoms 
[docs]    def create_interaction(self, element_pair, strength, equilibrium_distance, type='LJ'):
        """Creates an interaction between the two given types of atoms.
        The interactions are described by the simple Lennard-Jones potential
        (http://en.wikipedia.org/wiki/Lennard-Jones_potential)
        .. math::
           V(r) = \\varepsilon \\left[ \\left( \\frac{\\sigma}{r} \\right)^12 - \\left( \\frac{\\sigma}{r} \\right)^6 \\right]
        This is not a realistic interaction except for some very special cases
        such as noble gases. However, it is simple and will work fine as a placeholder.
        The method creates an interaction between two types of atoms as defined by
        ``element_pair``. The types may be the same, so for instance if you have ``'Au'``
        atoms in your system, you can make them interact with
        ``create_interaction( ['Au', 'Au'], ...)``
        Parameters:
        element_pair: string list
            the types that will interact, e.g, ``['Au', 'Au']`` or ``['B', 'C']``
        strength: real number
            the depth of the potential energy minimum (:math:`-\\min_r V(r)`) for the pair interaction, in eV
        equilibrium_distance: real number
            the atomic distance, :math:`r`, at which :math:`V(r)` has its minimum
        """
        epsilon = 4*strength
        sigma = equilibrium_distance * 0.890898718
        potential = pysic.Potential('LJ', symbols=element_pair,
                                    cutoff=1.9*sigma, cutoff_margin=0.8*sigma,
                                    parameters=[epsilon, sigma])
        self.calc.add_potential(potential) 
[docs]    def attach_with_springs(self, index_list1, index_list2, strength, cutoff=5.0):
        """Attaches the atoms from one list with the atoms in another list with springs.
        The two lists of atoms need to have an equal number of atoms, :math:`N`, and the
        method will create harmonic interactions between the listed atoms such that the
        atoms are connected in order: for ``index_list1 = [0, 1, 2]``,
        ``index_list2 = [3, 4, 5]`` (:math:`N=3`),
        the interactions will be created for pairs 0-3, 1-4, and 2-5.
        The potential energy of the interaction is
        .. math::
          V(r) = \\frac{1}{2} k r^2,
        where :math:`r` is the distance between the atoms. That means the equilibrium
        distance is 0. You can use the method, for instance, to create harmonic potential
        wells for atoms by attaching them to constrained ghost atoms.
        Parameters:
        index_list1: integer list
            indices of atoms to be connected
        index_list2: integer list
            indices of atoms list1 is being connected to
        strength: real number
            strength of the attachment, spring constant :math:`k = \\text{strength}/N`
        cutoff: real number
            cutoff for the interaction - if the atoms are farther away from each other than this, the potential is ignored.
            cutoff is restricted to cutoff < cell_width (default 10.0) because otherwise springs would be attached to periodic replicas as well.
        """
        n = len(index_list1)
        if(len(index_list2) != n):
            raise Error("The index lists need to have the same number of atoms.")
        k = strength/n
        if cutoff >= self.cell_width:
            raise Error("cutoff must be smaller than cell_width")
        for atom1, atom2 in zip(index_list1, index_list2):
            newpot = pysic.Potential('spring',indices=[atom1,atom2],
                                     cutoff=cutoff,parameters=[strength, 0.0])
            self.calc.add_potential(newpot) 
[docs]    def continue_from_trajectory(self, filename='simulation.traj', frame=-1):
        """Read the system geometry from a previously saved trajectory.
        Parameters:
        filename: string
            name of the trajectory file to be read
        frame: integer
            the number of the configuration to be read. by default this is -1, meaning
            the *last* configuration of the trajectory is read.
        """
        traj = PickleTrajectory(filename)
        self.system = traj[frame]
        self.system.set_calculator(self.calc)
        self.remove_constraints() 
[docs]    def list_atoms(self):
        """Lists the elements, indices, and positions of all atoms in the system.
        """
        i = 0
        for at in self.system:
                print "index = ",i,", element = ",at.symbol,", position = ",at.position
                i += 1 
[docs]    def duplicate_atoms(self, element, indices):
        """Creates a set of atoms with the same positions as the atoms with the given indices.
        The new atoms will be of the given element. This function can be used to create
        ghost atoms to be used with :meth:`~friction_tools.FrictionSimulation.attach_with_springs`
        or duplicate parts of the system to be moved in the correct place with
        :meth:`~friction_tools.FrictionSimulation.move_atoms`
        Parameters:
        element: string
            the chemical symbol of the atoms, e.g., 'C'
        indices: integer list
            indices of the atoms to be copied
        """
        i = 0
        newpos = []
        for at in self.system:
                if i in indices:
                    newpos.append(at.position)
                i += 1
        if len(newpos) > 0:
            self.create_atoms(element=element,positions=np.array(newpos))
        else:
            print "no atoms to duplicate" 
[docs]    def remove_atoms(self, indices):
        """Removes atoms with the given indices.
        Parameters:
        indices: integer list
            indices of the atoms to be removed
        """
        for i in range(len(self.system),-1,-1):
            if i in indices:
                del self.system[i] 
[docs]    def set_velocities(self, velocity, indices):
        """Gives the atoms initial velocities.
        You must give the indices of the atoms whose velocities you are defining as a list.
        If all the atoms should receive the same velocity, ``velocity`` can be a single vector.
        You can also, instead, give a list of velocities to define the velocities one by one.
        In that case, the length of the velocity and index lists must match.
        Parameters:
        velocity: real vector or a list of vectors
            the velocity or velocities of th atoms
        indices: integer list
            the indices of the atoms affected
        """
        if len(velocity) == 3:
            velocity = np.array(len(indices)*[velocity])
        if len(velocity) != len(indices):
            print "the velocity and index lists have different lengths in 'set_velocities'"
        index = 0
        for i in indices:
            self.system[i].momentum = np.array(velocity[index])*self.system[i].mass
            index += 1 
[docs]    def set_temperature(self, temperature=50):
        """Gives the atoms initial velocities according to the `Maxwell-Boltzmann distribution <http://en.wikipedia.org/wiki/Maxwell–Boltzmann_distribution>`_.
       The default temperature is 50 K. (Room temperature is about 300 K.)
        Parameters:
        temperature: float
            the initial temperature in K - 50 K by default
        """
        if self.system is None:
            print "You need to 'finalize_system' before setting the temperature."
            return
        MaxwellBoltzmannDistribution(self.system, temperature*units.kB)
        print ("Set initial velocities according to %6.1f K." % temperature) 
[docs]    def create_dynamics(self, dt=timestep,
                        temperature=None, coupled_indices=None, strength=0.01):
        """Defines the type of molecular dynamics to be run.
        By default, energy conserving dynamics are run, i.e,
        the microcanonical ensemble is used. That is, the normal
        `Velocity Verlet <http://en.wikipedia.org/wiki/Velocity_Verlet#Velocity_Verlet>`_
        algorithm is applied.
        NVT dynamics are run if the argument ``temperature`` is given.
        (NVT: number of particles, volume, and temperature are kept constant - temperature only approximately.)
        This is done using `Langevin dynamics <http://en.wikipedia.org/wiki/Langevin_dynamics>`_.
        This Langevin thermostat adds a fictious friction force and a random
        noise force on all atoms. This may lead to computational artifacts if there is collective
        motion. It's especially important not to apply the thermostat on atoms which are being
        dragged or move at a fixed velocity, as the thermostat will add artificial friction
        on them. In order to select the atoms which are being affected by the thermostat,
        you can specify their indices as a list with the ``coupled_indices`` argument.
        - **Note** The atomic structure cannot be altered after this method is called (if temperature is set). Create all the atoms before calling this.
        Parameters:
        dt: real number
            timestep :math:`\\Delta t` used in integrating the equations of motion, in fs - 2 fs by default.
        temperature: real number
            temperature in K, if you wish to apply a thermostat
        coupled_indices: integer list
            indices of the atoms the thermostat affects
        strength: real number
            strength of the thermostat, larger number meaning a stronger deviation from Newtonian mechanics - 0.01 by default
        """
        if not temperature is None:
            temp = temperature
            if coupled_indices is None:
                lan = strength
            else:
                lan = []
                for at in range(len(self.system)):
                    if at in coupled_indices:
                        lan.append(strength)
                    else:
                        lan.append(0.0)
                lan = np.array(lan)
            self.dynamics = LangevinMod(self.system, dt*units.fs, temp*units.kB, lan, fixcm=False)
            if pysic.get_cpu_id() == 0:
                  print ("Set up Langevin dynamics")
        else:
                # Create constant energy dynamics
                self.dynamics = VelocityVerlet(self.system, dt*units.fs)
                if pysic.get_cpu_id() == 0:
                    print ("Set up Verlet dynamics")
        self.timestep = dt 
[docs]    def calculate_potential_energy(self):
        """Returns the potential energy of the system.
        """
        return self.system.get_potential_energy() 
[docs]    def run_simulation(self, time=10, steps=None):
        """Runs molecular dynamics (MD).
        This method executes the actual simulation according to the parameters defined
        earlier by other methods.
        The simulation may take a long time to run, so it is a very good idea to first run a short
        test run to get an idea on the execution time. You can time the simulation with::
          >>> from tools import *
          >>> s = FrictionSimulation()
          >>> # define the system...
          >>> import time
          >>> t0 = time.time()
          >>> s.run_simulation(steps=5)
          >>> t1 = time.time()
          >>> print "The simulation took "+str(t1-t0)+" s."
        The simulation runs for a given length of time, which can be defined as either simulated time or
        number of molecular dynamics steps. If the argument ``steps`` is given, the simulation runs for this
        many steps. If ``time`` is given, the simulation is run for this long (in fs). Even if time :math:`t` is
        specified, the simulation length is still determined in steps :math:`n_\\mathrm{steps} = t/\\Delta t`,
        where :math:`\\Delta t` is the timestep.
        Parameters:
        time: float
            simulation time in fs, 10 fs by default
        steps: integer
            simulation time in MD steps
        """
        if isinstance(self.dynamics,BFGSLineSearch):
            self.dynamics.run(fmax=0.05)
            return
        if steps is None:
            run_steps = int(time/self.timestep)
        else:
            run_steps = steps
        print "Starting a dynamic simulation."
        self.dynamics.run(run_steps) 
[docs]    def remove_constraints(self):
        """Removes any constraints previously applied on the system.
        """
        self.system.constraints = [] 
[docs]    def fix_positions(self, indices, xyz=[True,True,True]):
        """Constrains the positions of particles.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method freezes coordinates of the atoms whose indices are given.
        By default, the atoms are not allowed to move at all, but the optional argument ``xyz``
        allows one to freeze only some coordinates. For example::
          >>> from friction_tools import *
          >>> s = FrictionSimulation()
          >>> # define the system...
          >>> s.fix_positions(indices=[...], xyz=[True,True,False])
        freezes the x and y coordinates of the specified atoms (``xyz = [True,True,False]``).
        Parameters:
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0
        xyz: list of three booleans
            logical tags specifying if the xyz coordinates are fixed (``True`` means the coordinate is fixed)
        """
        c = FixPositions(indices, xyz)
        self.system.constraints.append(c) 
[docs]    def fix_velocities(self, indices, velocity, xyz=[True,True,True]):
        """Constrains the velocities of particles.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method freezes velocities of the atoms whose indices are given.
        By default, the atoms move with constant velocity, but the optional argument ``xyz``
        allows one to fix only a given component of force. For example::
          >>> from tools import *
          >>> s = FrictionSimulation()
          >>> # define the system...
          >>> s.fix_velocities(indices=[...], velocity=[0.01,0.0,0.0], xyz=[True,False,True])
        fixes the velocities in x and z directions to :math:`v_x = 0.01, v_z = 0.0` Ã…/fs
        (``xyz = [True,False,True]``) but allows the velocity to change freely in the y direction.
        Parameters:
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0
        velocity: list of three floats
            the velocity given to the particles
        xyz: list of three booleans
            logical tags specifying if the xyz coordinates are fixed (``True`` means the coordinate is fixed)
        """
        c = FixVelocities(indices, velocity, self.timestep, xyz)
        self.system.constraints.append(c) 
[docs]    def fix_positions_on_line(self, indices, direction):
        """Constrains the positions of particles on a line.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method constrains coordinates of the atoms whose indices are given on a line.
        The line is defined by the given direction :math:`\\mathbf{u}` and the initial position
        of the particle :math:`\\mathbf{r}_0`.
        .. math::
          \\mathbf{r} - \\mathbf{r}_0 \\parallel \\mathbf{u}
        Parameters:
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0
        direction: list of three floats
            the direction ``[ux, uy, uz]`` in which the particles are allowed to move
        """
        c = FixLine(indices, direction)
        self.system.constraints.append(c) 
[docs]    def fix_positions_on_plane(self, indices, normal):
        """Constrains the positions of particles on a plane.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method constrains coordinates of the atoms whose indices are given on a plane.
        The plane is defined by the given normal :math:`\\mathbf{n}` and the initial position
        of the particle :math:`\\mathbf{r}_0`.
        .. math::
          \\mathbf{r} - \\mathbf{r}_0 \\perp \\mathbf{n}
        Parameters:
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0
        normal: list of three floats
            the normal vector ``[nx, ny, nz]`` to the plane in which the particles are allowed to move
        """
        c = FixPlane(indices, normal)
        self.system.constraints.append(c) 
[docs]    def add_constant_force(self, indices, force):
        """Adds a constant external force on atoms.
        Adds an external force on the specified atoms
        .. math::
          \\mathbf{F} = f_x \\mathbf{i} + f_y \\mathbf{j} + f_z \\mathbf{k}.
        Parameters:
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0
        force: list of three floats
            the force ``[fx, fy, fz]`` to be applied on the given atoms
        """
        index_list = []
        for index in indices:
            index_list.append([index])
        pot = pysic.Potential('force', indices=index_list, parameters=force)
        self.calc.add_potential(pot) 
[docs]    def print_stats(self, time_it=False):
        """Prints the energy and temperature information in human readable format.
        Parameters:
        time_it: boolean
            if ``True``, the calculation is timed and the execution time is printed
        """
        t0 = time.time()
        epot = self.system.get_potential_energy() / len(self.system)
        t1 = time.time()
        ekin = self.system.get_kinetic_energy() / len(self.system)
        t2 = time.time()
        stress = self.system.get_stress()
        t3 = time.time()
        step = self._get_step()
        if pysic.get_cpu_id() == 0:
            if step > 0:
                print ("Timestep: %7i" % step)
            print ("Energy per atom: Epot = %.3f eV,  Ekin = %.3f eV,  Etot = %.3f eV" %
                   (epot, ekin, epot+ekin))
            print ("Temperature = %.3f K\n" %
                   (ekin/(1.5*units.kB)))
            if time_it:
                print ("Energy calculation took %.3f s, force and stress calculation took %.3f s\n" %
                       (t1-t0, t3-t2)) 
[docs]    def write_positions_to_file(self, filename='positions.txt', indices=None, addStepNumber=False):
        """Writes the coordinates of the atoms in a file.
        The format is::
           (atom 0 x) (atom 0 y) (atom 0 z)
           (atom 1 x) (atom 1 y) (atom 1 z)
           (atom 2 x) (atom 2 y) (atom 2 z)
           ...
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        addStepNumber: boolean
           meant for internal use - if ``True``, adds the MD step number in the filename to prevent overwriting
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        for index in indices:
            [x,y,z] = self.system[index].get_position()
            output_lines += (" %20.5f  %20.5f  %20.5f \n" % (x,y,z) )
        filing = add_number_to_filename(filename, self._get_step(), addStepNumber)
        write_file(filing, output_lines) 
[docs]    def write_velocities_to_file(self, filename='velocities.txt', indices=None, addStepNumber=False):
        """Writes the instantaneous velocities of the atoms in a file.
        - **Note** Due to a bug/feature in ASE, if you fix the velocities of atoms with :meth:`tools.FrictionSimulation.fix_velocities`, the velocities for these atoms are reported to be 0.0. The method :meth:`tools.FrictionSimulation.gather_average_velocities_during_simulation` does report the correct average velocities for all atoms though.
        The format is::
           (atom 0 v_x) (atom 0 v_y) (atom 0 v_z)
           (atom 1 v_x) (atom 1 v_y) (atom 1 v_z)
           (atom 2 v_x) (atom 2 v_y) (atom 2 v_z)
           ...
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        addStepNumber: boolean
           meant for internal use - if ``True``, adds the MD step number in the filename to prevent overwriting
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        for index in indices:
            try:
                [x,y,z] = self.system[index].get_momentum()/self.system[index].get_mass()
            except:
                [x,y,z] = [0.0,0.0,0.0]
            output_lines += (" %20.5f  %20.5f  %20.5f \n" % (x,y,z) )
        filing = add_number_to_filename(filename, self._get_step(), addStepNumber)
        write_file(filing, output_lines) 
[docs]    def write_forces_to_file(self, filename='forces.txt', indices=None, addStepNumber=False):
        """Writes the instantaneous forces of the atoms in a file.
        The format is::
           (atom 0 f_x) (atom 0 f_y) (atom 0 f_z)
           (atom 1 f_x) (atom 1 f_y) (atom 1 f_z)
           (atom 2 f_x) (atom 2 f_y) (atom 2 f_z)
           ...
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        addStepNumber: boolean
           meant for internal use - if ``True``, adds the MD step number in the filename to prevent overwriting
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        forces = self.system.get_forces(apply_constraint=False)
        for index in indices:
            [x,y,z] = forces[index]
            output_lines += (" %20.5f  %20.5f  %20.5f \n" % (x,y,z) )
        filing = add_number_to_filename(filename, self._get_step(), addStepNumber)
        write_file(filing, output_lines) 
[docs]    def write_average_force_to_file(self, filename='force_sum.txt', indices=None):
        """Writes the average of instantaneous forces of the atoms in a file.
        The format is::
           (f_x) (f_y) (f_z)
           ...
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        forces = self.system.get_forces(apply_constraint=False)
        force_sum = np.array([0.0,0.0,0.0])
        for index in indices:
            force_sum += forces[index]
        [x,y,z] = force_sum / float(len(indices))
        output_line = (" %20.5f  %20.5f  %20.5f \n" % (x,y,z) )
        append_file(filename, output_line) 
[docs]    def write_average_position_to_file(self, filename='avr_position.txt', indices=None):
        """Writes the mean of the coordinates of the atoms in a file.
        The format is::
           (x) (y) (z)
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        pos = self.system.get_positions()
        pos_sum = np.array([0.0,0.0,0.0])
        for index in indices:
            pos_sum += pos[index]
        [x,y,z] = pos_sum / float(len(indices))
        output_line = (" %20.5f  %20.5f  %20.5f \n" % (x,y,z) )
        append_file(filename, output_line) 
[docs]    def write_energy_and_temperature_to_file(self, filename='energies.txt'):
        """Appends the instantaneous energy and temperature information in a file.
        The format is::
           (potential energy) (kinetic energy) (total energy) (temperature)
        Since this method does not write atom-by-atom information, it does not create a new file but
        appends its output at the end of the specified file.
        Parameters:
        filename: string
            name of the file where the information is written
        """
        e_pot = self.system.get_potential_energy()
        e_kin = self.system.get_kinetic_energy()
        t = e_kin/(len(self.system)*(1.5*units.kB))
        output_lines = (" %20.5f  %20.5f  %20.5f  %20.5f \n" % (e_pot, e_kin, (e_pot+e_kin), t) )
        append_file(filename, output_lines) 
    def _record_positions(self):
        """For internal use. Saves the positions of the atoms."""
        self.old_positions = self.system.get_positions()
    def _record_work(self):
        """For internal use. Integrates work done on all atoms."""
        forces = self.system.get_forces(apply_constraint=False)
        move = self.system.get_positions()
        move -= self.prev_positions
        for index in range(len(self.system)):
            self.work[index] += np.dot((forces[index]+self.prev_forces[index])*0.5, move[index])
        self.prev_positions = self.system.get_positions()
        self.prev_forces = forces
    def _clear_work(self):
        """For internal use. Clears the recorded work integrals."""
        self.work = np.zeros(len(self.system),dtype=float)
        self.prev_positions = self.system.get_positions()
        self.prev_forces = self.system.get_forces(apply_constraint=False)
[docs]    def write_work_to_file(self, filename='work.txt', indices=None, addStepNumber=False):
        """For internal use. Writes the work done on the atoms in a file.
        The format is::
           (atom 0 work)
           (atom 1 work)
           (atom 2 work)
           ...
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        addStepNumber: boolean
           meant for internal use - if ``True``, adds the MD step number in the filename to prevent overwriting
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        for index in indices:
            output_lines += (" %20.5f \n" % (self.work[index]) )
        #self._clear_work()
        filing = add_number_to_filename(filename, self._get_step(), addStepNumber)
        write_file(filing, output_lines) 
[docs]    def write_total_work_to_file(self,filename='total_work.txt', indices=None):
        """For internal use. Writes the work done on the atoms in a file.
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        work_sum = 0.0
        for index in indices:
            work_sum += self.work[index]
        output_line = (" %20.5f \n" % (work_sum) )
        append_file(filename, output_line) 
[docs]    def write_average_velocities_to_file(self, dt, filename='avr_velocities.txt',
                                         indices=None, addStepNumber=False):
        """For internal use. Writes the average velocities of the atoms in a file.
        The format is::
           (atom 0 v_x) (atom 0 v_y) (atom 0 v_z)
           (atom 1 v_x) (atom 1 v_y) (atom 1 v_z)
           (atom 2 v_x) (atom 2 v_y) (atom 2 v_z)
           ...
        Parameters:
        filename: string
            name of the file where the information is written
        indices: list of integers
           the indices of the atoms whose info is to be written - note that Python indexing starts from 0. By default all atoms are included
        addStepNumber: boolean
           meant for internal use - if ``True``, adds the MD step number in the filename to prevent overwriting
        """
        if indices is None:
            indices = range(len(self.system))
        output_lines = ""
        for index in indices:
            pos = np.array(self.system[index].get_position())
            pos -= self.old_positions[index]
            pos /= dt*units.fs
            [x,y,z] = pos
            output_lines += (" %20.5f  %20.5f  %20.5f \n" % (x,y,z) )
        self._record_positions()
        filing = add_number_to_filename(filename, self._get_step(), addStepNumber)
        append_file(filing, output_lines) 
[docs]    def print_stats_during_simulation(self, interval=10):
        """Makes the simulator print energies and temperature during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.print_stats` periodically
        during the molecular dynamics run, every ``interval`` steps. This is a convenient way to
        monitor the progress of your simulation.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        """
        self.dynamics.attach(self.print_stats, interval) 
[docs]    def gather_positions_during_simulation(self, interval=10, filename='positions.txt',
                                           indices=None):
        """Makes the simulator write coordinates of atoms during simulation.
        The format is::
           (atom 0 x) (atom 0 y) (atom 0 z)
           (atom 1 x) (atom 1 y) (atom 1 z)
           (atom 2 x) (atom 2 y) (atom 2 z)
           ...
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_positions_to_file` periodically
        during the molecular dynamics run, every ``interval`` steps.
        The given filename is automatically modified to include the current MD step number.
        For instance, if the filename is given as ``data.txt``, the data from, say,
        step 20 will be written to file ``data00020.txt``.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        self.dynamics.attach(self.write_positions_to_file, interval, filename, indices, True) 
[docs]    def gather_velocities_during_simulation(self, interval=10, filename='velocities.txt',
                                            indices=None):
        """Makes the simulator write instantaneous velocities of atoms during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        - **Note** Due to a bug/feature in ASE, if you fix the velocities of atoms with :meth:`tools.FrictionSimulation.fix_velocities`, the velocities for these atoms are reported to be 0.0. The method :meth:`tools.FrictionSimulation.gather_average_velocities_during_simulation` does report the correct average velocities for all atoms though.
        The format is::
           (atom 0 v_x) (atom 0 v_y) (atom 0 v_z)
           (atom 1 v_x) (atom 1 v_y) (atom 1 v_z)
           (atom 2 v_x) (atom 2 v_y) (atom 2 v_z)
           ...
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_velocities_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        The given filename is automatically modified to include the current MD step number.
        For instance, if the filename is given as ``data.txt``, the data from, say,
        step 20 will be written to file ``data00020.txt``.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        self.dynamics.attach(self.write_velocities_to_file, interval, filename, indices, True) 
[docs]    def gather_average_velocities_during_simulation(self, interval=10, filename='avr_velocities.txt',
                                                    indices=None):
        """Makes the simulator write average velocities of atoms during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        The format is::
           (atom 0 v_x) (atom 0 v_y) (atom 0 v_z)
           (atom 1 v_x) (atom 1 v_y) (atom 1 v_z)
           (atom 2 v_x) (atom 2 v_y) (atom 2 v_z)
           ...
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_average_velocities_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        The average velocities
        recorded are the averages since the previous recording.
        So, when writing at intervals of :math:`n_\\mathrm{avr}`,
        the average velocity of atom :math:`i` at time :math:`t` will be the atomic displacement
        :math:`\\Delta \\mathbf{r}_i(t) = \\mathbf{r}_i(t) - \\mathbf{r}_{i}(t-\\Delta t)`
        divided by the elapsed time :math:`\\Delta t = n_\\mathrm{avr} \\Delta t'`, with :math:`\\Delta t'` being
        the MD timestep:
        .. math::
              \\mathbf{v}_{i,\\mathrm{avr}}(t-\\Delta t, t) = \\frac{\\Delta \\mathrm{r}_i}{\\Delta t_i}.
        The given filename is automatically modified to include the current MD step number.
        For instance, if the filename is given as ``data.txt``, the data from, say,
        step 20 will be written to file ``data00020.txt``.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        self._record_positions()
        self.dynamics.attach(self.write_average_velocities_to_file, interval,
                             interval*self.timestep, filename, indices, True) 
[docs]    def gather_forces_during_simulation(self, interval=100, filename='forces.txt',
                                        indices=None):
        """Makes the simulator write instantaneous forces of atoms during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        The format is::
           (atom 0 f_x) (atom 0 f_y) (atom 0 f_z)
           (atom 1 f_x) (atom 1 f_y) (atom 1 f_z)
           (atom 2 f_x) (atom 2 f_y) (atom 2 f_z)
           ...
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_forces_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        The given filename is automatically modified to include the current MD step number.
        For instance, if the filename is given as ``data.txt``, the data from, say,
        step 20 will be written to file ``data00020.txt``.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        self.dynamics.attach(self.write_forces_to_file, interval, filename, indices, True) 
[docs]    def gather_average_position_during_simulation(self, interval=100, filename='avr_position.txt',
                                            indices=None):
        """Makes the simulator write averages of positions of atoms during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        The format is::
           (x) (y) (z)
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_average_position_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        The data is written in a single file where each line represents a point in the time series.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        write_file(filename, "") # clear the file
        self.dynamics.attach(self.write_average_position_to_file, interval, filename, indices) 
[docs]    def gather_average_force_during_simulation(self, interval=100, filename='avr_force.txt',
                                            indices=None):
        """Makes the simulator write averages of instantaneous forces of atoms during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        The format is::
           (f_x) (f_y) (f_z)
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_average_force_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        The data is written in a single file where each line represents a point in the time series.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        write_file(filename, "") # clear the file
        self.dynamics.attach(self.write_average_force_to_file, interval, filename, indices) 
[docs]    def gather_total_work_during_simulation(self, interval=100, filename='total_work.txt',
                                            indices=None):
        """Makes the simulator write the total work done on atoms during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_total_work_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        The data is written in a single file where each line represents a point in the time series.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        write_file(filename, "") # clear the file
        self._clear_work()
        self.dynamics.attach(self._record_work, interval=1)
        self.dynamics.attach(self.write_total_work_to_file, interval, filename, indices) 
[docs]    def gather_energy_and_temperature_during_simulation(self, interval=100, filename='energy.txt'):
        """Makes the simulator write energy and temperature during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        The format is::
          (potential energy)  (kinetic energy)  (total energy)  (temperature)
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_energy_and_temperature_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        The data is written in a single file where each line represents a point in the time series.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        """
        write_file(filename, "") # clear the file
        self.dynamics.attach(self.write_energy_and_temperature_to_file, interval, filename) 
[docs]    def gather_work_during_simulation(self, interval=100, filename='work.txt', indices=None):
        """Makes the simulator write work done on atoms during simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        The format is::
          (atom 0 work)
          (atom 1 work)
          (atom 2 work)
          ...
        This method instructs the simulator to call :meth:`tools.FrictionSimulation.write_work_to_file`
        periodically during the molecular dynamics run, every ``interval`` steps.
        Work is defined as the line integral of the force. The recorded values are the work done
        on each individual atom since the previous recording.
        So, when writing at intervals of :math:`n_\\mathrm{avr}`, the work on atom :math:`i`
        at time :math:`t` is
        .. math::
           W_i(t-\\Delta t, t) = \\int_{t-\\Delta t}^t \\mathbf{F}_i \\cdot \\mathrm{d}\\mathbf{r}_i(t)
        with :math:`\\Delta t = n_\\mathrm{avr} \\Delta t'`, :math:`\\Delta t'` being
        the MD timestep.
        The given filename is automatically modified to include the current MD step number.
        For instance, if the filename is given as ``data.txt``, the data from, say,
        step 20 will be written to file ``data00020.txt``.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        indices: list of integers
            the indices of the atoms to be constrained - note that Python indexing starts from 0. By default, all atoms are included.
        """
        self._clear_work()
        self.dynamics.attach(self._record_work, interval=1)
        self.dynamics.attach(self.write_work_to_file, interval, filename, indices, True) 
[docs]    def save_trajectory_during_simulation(self, interval=100, filename='simulation.traj'):
        """Saves the simulation trajectory in binary format.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method saves the simulation as a trajectory file, from which the positions of
        particles can be extracted at the different points of time of the simulation.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        """
        if not self.trajectory is None:
            #self.trajectory.close()
            mode = 'a'
        else:
            mode = 'w'
        self.trajectory = PickleTrajectory(filename, mode, self.system, master=( pysic.get_cpu_id() == 0 ))
        self.dynamics.attach(self.trajectory.write, interval) 
[docs]    def take_snapshots_during_simulation(self, interval=100, filename='snapshot.png'):
        """Takes png-format snapshots of the system during the simulation.
        - **Note** call this method before running the simulation with :meth:`tools.FrictionSimulation.run_simulation`
        This method generates images of the system in png format.
        Parameters:
        interval: integer
            the number of MD steps between consecutive information writing
        filename: string
            name of the file where the data is written
        """
        self.dynamics.attach(self.take_snapshot, interval, filename, True) 
[docs]    def load_state_from_trajectory(self, step=-1, filename='simulation.traj'):
        """Loads the geometry from a saved trajectory.
        If you have a saved trajectory from :meth:`tools.FrictionSimulation.save_trajectory_during_simulation`,
        this method can be used for reading in a state from the trajectory.
        Parameters:
        step: integer
            the timestep which is restored - by default the last recorded state is chosen
        filename: string
            name of the trajectory file
        """
        traj_in = PickleTrajectory(filename)
        self.system = traj_in[step] 
    def _get_step(self):
        """Returns the number of steps the dynamics has run.
        """
        try:
            return self.dynamics.get_number_of_steps()
        except:
            return 0
[docs]    def take_snapshot(self, filename='snapshot.png', addStepNumber=False):
        """Makes a png format snapshot of the system.
        Parameters:
        filename: string
            name of the trajectory file
        addStepNumber: boolean
           meant for internal use - if ``True``, adds the MD step number in the filename to prevent overwriting
        """
        filing = add_number_to_filename(filename, self._get_step(), addStepNumber)
        ase.io.write(filing, self.system, format='png', rotation='-80x') 
[docs]    def write_xyzfile(self, filename='system.xyz'):
        """Makes an xyz format geometry file of the system.
        XYZ is a commonly used, very simple geometry file format which includes
        information on the types of atoms and their coordinates. This method writes
        such a file based on the current simulation geometry.
        XYZ files can be read with almost any atomic visualization tool.
        Parameters:
        filename: string
            name of the trajectory file
        """
        ase.io.write(filename, self.system, format='xyz') 
[docs]    def get_indices_z_less_than(self, z_limit):
        """Returns the indices of the atoms whose z-coordinate is less than the given value, as a list.
        This can be used for easily finding, e.g., the bottom of a slab.
        Parameters:
        z_limit: float
            the limiting z value
        """
        return [atom.index for atom in self.system if atom.z < z_limit] 
[docs]    def get_indices_z_more_than(self, z_limit):
        """Returns the indices of the atoms whose z-coordinate is more than the given value, as a list.
        This can be used for easily finding, e.g., the top of a slab.
        Parameters:
        z_limit: float
            the limiting z value
        """
        return [atom.index for atom in self.system if atom.z > z_limit] 
[docs]    def get_indices_by_element(self, element):
        """Returns the indices of atoms of given element.
        Parameters:
        element: string
            the chemical symbol of the atoms, e.g., 'C'
        """
        return [atom.index for atom in self.system if atom.symbol == element]  
[docs]def read_arrays_from_files(filename, starting_index, ending_index, index_step):
    """Reads numeric arrays from files.
    If you have run a simulation and produced files containing numeric data through the
    methods ``gather_xxx_during_simulation``, this function can be used for reading the
    data in Python.
    You should have files with names such as ``positions00100.txt`` etc. To read them in,
    give the base name ``positions.txt`` as the ``filename``, and the range of integers through the
    other arguments: ``starting_index`` and ``ending_index`` are the first and last index to be read,
    respectively, while ``index_step`` is the increment.
    Example::
      >>> read_arrays_from_files('data.txt',10,100,10)
    will read the files::
      data00010.txt
      data00020.txt
      ...
      data00100.txt
    The function returns a list containing all the arrays extracted from the files.
    Parameters:
    filename: string
        the base filename to be read
    starting_index: integer
        the first index to be read
    ending_index: integer
        the last index to be read
    index_step: integer
        the increment in indices
    """
    array_collection = []
    for index in range(starting_index, ending_index+index_step, index_step):
        filing = add_number_to_filename(filename, index, True)
        new_array = read_array_from_file(filing)
        array_collection.append(new_array)
    return array_collection 
[docs]def read_array_from_file(filename):
    """Parses a file and returns a numeric array of the data found.
    Parameters:
    filename: string
        name of the file to be read
    """
    array = np.fromfile(filename, dtype=float, count=-1, sep=' ')
    f=file(filename)
    lines = f.readlines()
    f.close()
    array.shape = (len(lines), array.size/len(lines))
    return array 
[docs]def write_array_to_file(filename, array):
    """Writes an array to a file.
    **Note** The method calls the Numpy routine::
      array.tofile(filename, sep=" ", format="%20.5f")
    which results in all the values stored in the array being
    written in one line. If you read it back in with
    :meth:`tools.read_array_from_file`, you get a 1D array
    containing the values. To fix the shape, specify it explicitly::
     >>> arry = [[1, 2], [3, 4]]
     array([[1, 2],
            [3, 4]])
     >>> write_array_to_file(filename='array.txt', arry)
     >>> new_arry = read_array_from_file('array.txt')
     >>> new_arry
     array([[1, 2, 3, 4]])
     >>> new_arry.shape = (2,2)
     >>> new_arry
     array([[1, 2],
            [3, 4]])
    Parameters:
    filename: string
        name of the file to be written
    array: numeric array
        the array to be written
    """
    array.tofile(filename, sep=" ", format="%20.5f") 
[docs]def add_arrays_by_component(array1, array2):
    """Adds two arrays component by component.
    Equal to ``np.array(array1) + np.array(array2)``
    Parameters:
    array1: numeric array of floats
         an array
    array2: numeric array of floats
         an array
    """
    return np.array(array1) + np.array(array2) 
[docs]def subtract_arrays_by_component(array1, array2):
    """Subtracts two arrays component by component.
    Equal to ``np.array(array1) - np.array(array2)``
    Parameters:
    array1: numeric array of floats
         an array
    array2: numeric array of floats
         an array
    """
    return np.array(array1) - np.array(array2) 
[docs]def multiply_arrays_by_component(array1, array2):
    """Multiplies two arrays component by component.
    Equal to ``np.array(array1) * np.array(array2)``
    Parameters:
    array1: numeric array of floats
         an array
    array2: numeric array of floats
         an array
    """
    return np.array(array1) * np.array(array2) 
[docs]def divide_arrays_by_component(array1, array2):
    """Divides two arrays component by component.
    Equal to ``np.array(array1) / np.array(array2)``
    Parameters:
    array1: numeric array of floats
         an array
    array2: numeric array of floats
         an array
    """
    return np.array(array1) / np.array(array2) 
[docs]def add_to_array(array, real):
    """Adds a real number to an array component by component.
    Equal to ``np.array(array) + real``
    Parameters:
    array: numeric array of floats
         an array
    real: float
         a number
    """
    return np.array(array) + real 
[docs]def multiply_array(array, real):
    """Multiplies an array with a real number component by component.
    Equal to ``np.array(array) * real``
    Parameters:
    array: numeric array of floats
         an array
    real: float
         a number
    """
    return np.array(array1) * real 
[docs]def create_array(dimensions, value=0):
    """Creates an array of the given size.
    Parameters:
    dimensions: list of integers
        dimensions of the array
    value: float
        the initial value for the elements of the array
    """
    return np.zeros(dimensions) + value 
[docs]def concatenate_arrays_by_rows(array1, array2):
    """Joins two arrays together by appending row by row.
    Parameters:
    array1: numeric array of floats
         an array
    array2: numeric array of floats
         an array
    """
    cols = array1.shape[1]
    rows1 = array1.shape[0]
    rows2 = array2.shape[0]
    newarray = np.zeros((rows1+rows2,cols))
    for i in range(rows1):
        newarray[i,:] = array1[i,:]
    for i in range(rows2):
        newarray[i+rows1,:] = array2[i,:]
    return newarray 
[docs]def concatenate_arrays_by_columns(array1, array2):
    """Joins two arrays together by appending column by column.
    Parameters:
    array1: numeric array of floats
         an array
    array2: numeric array of floats
         an array
    """
    cols1 = array1.shape[1]
    rows = array1.shape[0]
    cols2 = array2.shape[1]
    newarray = np.zeros((rows,cols1+cols2))
    for i in range(cols1):
        newarray[:,i] = array1[:,i]
    for i in range(cols2):
        newarray[:,i+cols1] = array2[:,i]
    return newarray 
[docs]def shift_array_by_rows(array, steps=1, wrap=False):
    """Moves the elements in an array by rows.
    Parameters:
    array: numeric array of floats
         an array
    steps: integer
         number of rows the elements are moved
    wrap: boolean
         if ``True``, elements that go over the bounds of the array appear on the other side
    """
    newarray = np.zeros_like(array)
    newarray[steps:] = np.array(array)[:-steps]
    if wrap:
        newarray[:steps] = np.array(array)[-steps:]
    return newarray 
[docs]def shift_array_by_columns(array, steps=1, wrap=False):
    """Moves the elements in an array in by columns.
    Parameters:
    array: numeric array of floats
         an array
    steps: integer
         number of rows the elements are moved
    wrap: boolean
         if ``True``, elements that go over the bounds of the array appear on the other side
    """
    newarray = np.zeros_like(array)
    newarray[:,steps:] = np.array(array)[:,:-steps]
    if wrap:
        newarray[:,:steps] = np.array(array)[:,-steps:]
    return newarray 
[docs]def crop_array(top, left, bottom, right, array):
    """Crops an array (i.e., returns a subarray).
    The indices for cropping should be given as arguments.
    The order is ``top, left, bottom, right``.
    For ``top`` and ``left``, they are the first indices to be included,
    while ``bottom`` and ``right`` are the first indices to be dropped.
    For instance, if you have the array::
      arry = [[1, 2, 3],
              [4, 5, 6],
              [7, 8, 9]]
    The top left :math:`2 \\times 2` array is obtained with::
      >>> crop_array(0, 0, 2, 2, arry)
      [[1, 2],
       [4, 5]]
    The command is equivalent to::
      >>> array.copy()[top:bottom,left:right]
    Parameters:
    top: integer
        first index from the top to be included
    left: integer
        first integer from the left to be included
    bottom: integer
        first index from the top to be left out
    rigth: integer
        first index from the left to be left out
    """
    return array.copy()[top:bottom,left:right] 
[docs]def calculate_row_sums(array):
    """Sums all the rows of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) < 3:
        height = array.shape[0]
        result = np.zeros(height)
        for i in range(height):
            row = array[i,:]
            row_sum = np.sum(row)
            result[i] = row_sum
    else:
        raise Exception("Row sum is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def calculate_row_norms(array):
    """Calculates euclidian norms for all the rows of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) < 3:
        height = array.shape[0]
        result = np.zeros(height)
        for i in range(height):
            row = array[i,:]
            row_sum = sqrt(np.dot(row,row))
            result[i] = row_sum
    else:
        raise Exception("Row norm is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def calculate_row_maxima(array):
    """Finds the maxima for all the rows of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) < 3:
        height = array.shape[0]
        result = np.zeros(height)
        for i in range(height):
            row = array[i,:]
            row_sum = np.max(row)
            result[i] = row_sum
    else:
        raise Exception("Row max is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def calculate_row_minima(array):
    """Finds the minima for all the rows of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) < 3:
        height = array.shape[0]
        result = np.zeros(height)
        for i in range(height):
            row = array[i,:]
            row_sum = np.min(row)
            result[i] = row_sum
    else:
        raise Exception("Row min is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def calculate_column_sums(array):
    """Sums all the columns of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) == 2:
        width = array.shape[1]
        result = np.zeros(width)
        for i in range(width):
            column = array[:,i]
            column_sum = np.sum(column)
            result[i] = column_sum
    elif len(array.shape) == 1:
        result = array
    else:
        raise Exception("Column sum is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def calculate_column_norms(array):
    """Calculates euclidian norms for all the columns of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) == 2:
        width = array.shape[1]
        result = np.zeros(width)
        for i in range(width):
            column = array[:,i]
            column_sum = sqrt(np.dot(column,column))
            result[i] = column_sum
    elif len(array.shape) == 1:
        result = array
    else:
        raise Exception("Column norm is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def calculate_column_maxima(array):
    """Finds the maxima for all the columns of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) == 2:
        width = array.shape[1]
        result = np.zeros(width)
        for i in range(width):
            column = array[:,i]
            column_sum = np.max(column)
            result[i] = column_sum
    elif len(array.shape) == 1:
        result = array
    else:
        raise Exception("Column max is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def calculate_column_minima(array):
    """Finds the minima for all the columns of a 2D array and returns a row 1D array containing the results.
    Parameters:
    array: numeric array
        the array to be manipulated
    """
    if len(array.shape) == 2:
        width = array.shape[1]
        result = np.zeros(width)
        for i in range(width):
            column = array[:,i]
            column_sum = np.min(column)
            result[i] = column_sum
    elif len(array.shape) == 1:
        result = array
    else:
        raise Exception("Column min is only defined for 1- or 2-dimensional arrays")
    return result 
[docs]def plot_array(x_values, y_values=None, y_error=None, x_range=None, y_range=None):
    """Plots data.
    If only one column of data is given, it is plotted against a running index.
    If two columns of data are given, the first is treated as x and the second as y.
    **Note** You should also use :func:`tools.hold_plots`.
    Parameters:
    x_values: list of floats
        If it's the only input given, it is treated as the y-variable. If ``y_values`` is given, this is the x-variable.
    y_values: list of floats
        Values of the y-variable.
    y_error: list of floats
        Values for errorbars of y.
    x_range: list of two floats
        Plotting range for x
    y_range: list of two floats
        Plotting range for y
    """
    if y_values == None:
        xs = np.array(range(len(x_values)))
        ys = np.array(x_values)
    else:
        xs = np.array(x_values)
        ys = np.array(y_values)
    if not x_range is None:
        plt.ylim(x_range[0],x_range[1])
    if not y_range is None:
        plt.ylim(y_range[0],y_range[1])
    if y_error == None:
        plt.plot(xs,ys)
    else:
        es = np.array(y_error)
        errorbar(xs, ys, yerr=es, fmt='ro') 
[docs]def plot_zero_line():
    """Adds a horizontal line at y=0 to the plot.
    """
    plt.axhline() 
[docs]def hold_plots():
    """Hold the plot visible.
    By default, your plots will vanish from the screen after plotting. This
    function prevents it. The point is, you can call :func:`tools.plot_array` several
    times and the generated plots will be added together and shown when you call
    this function.
    """
    plt.show() 
[docs]def trajectory_to_xyz(filename_in='simulation.traj',filename_out='simulation.xyz'):
    """Converts a trajectory file to an XYZ file.
    Parameters:
    filaname_in: string
        name of the trajectory file
    filaname_out: string
        name of the XYZ file
    """
    # Write the trajectory to an xyz file
    if pysic.get_cpu_id() == 0:
        traj = PickleTrajectory(filename_in)
        movie = []
        for snapshot in traj:
            movie.append(snapshot)
        ase.io.write(filename_out,movie,format='xyz') 
def add_number_to_filename(filename, number, act=True):
    if act:
        name = filename.split('.')
        try:
            filing = ( (name[0]+"%5.5i."+name[1]) % number )
        except:
            filing = ( (filename+"%5.5i") % number )
    else:
        filing = filename
    return filing
[docs]def write_file(filename, output_lines):
    """Writes the given string in a file.
    Note that you should provide only a single string. If you want to split the output
    to several lines, use the newline character ``\\n``.
    If the file exists, it is overwritten.
    Parameters:
    filename: string
        name of the file to be written
    output_lines: string
        the string to be written
    """
    if pysic.get_cpu_id() == 0: # if we run MPI, only write with the master CPU
        f = file(filename,'w')
        f.write(output_lines)
        f.close() 
[docs]def append_file(filename, output_lines):
    """Writes the given string in a file.
    Note that you should provide only a single string. If you want to split the output
    to several lines, use the newline character '\\n'.
    This function does not overwrite a file, but appends the output at the end.
    Parameters:
    filename: string
        name of the file to be written
    output_lines: string
        the string to be written
    """
    if pysic.get_cpu_id() == 0: # if we run MPI, only write with the master CPU
        f = file(filename,'a')
        f.write(output_lines)
        f.close()