"""
Tests for the various actions in ParmEd
"""
from copy import copy
from io import StringIO
import numpy as np
import os
import parmed as pmd
import pytest
from parmed import periodic_table, gromacs, load_file, amber
from parmed.amber import AmberParm, ChamberParm, AmoebaParm, AmberFormat, AmberMask
from parmed.charmm import CharmmPsfFile
from parmed.exceptions import AmberWarning, CharmmWarning
from parmed.formats import PDBFile, CIFFile
from parmed.utils import PYPY
import parmed.unit as u
import parmed.tools as PT
from parmed.tools import exceptions as exc
from parmed.tools import parmlist
from parmed.tools.simulations import sanderapi
from parmed.tools.actions import ArgumentList
import re
import saved_outputs as saved
import sys
import unittest
from utils import (HAS_GROMACS, get_fn, get_saved_fn, diff_files,
        FileIOTestCase, TestCaseRelative, detailed_diff, has_openmm,
        create_random_structure, app)
import warnings
try:
    import pandas as pd
except ImportError:
    pd = None
try:
    import sander
except ImportError:
    sander = None

try:
    import rdkit.Chem as Chem
    HAS_RDKIT = True
except ImportError:
    HAS_RDKIT = False

gasparm = AmberParm(get_fn('trx.prmtop'))
solvparm = AmberParm(get_fn('solv2.parm7'))
gascham = ChamberParm(get_fn('ala_ala_ala.parm7'))
solvchamber = ChamberParm(get_fn('ala3_solv.parm7'))
amoebaparm = AmoebaParm(get_fn('nma.parm7'))
phenolparm = AmberParm(get_fn('phenol.prmtop'))

# Make sure default overwrite is False
PT.Action.overwrite = False

# Make some new action subclasses
class NewActionNoUsage(PT.Action):
    needs_parm = False
    def init(self, arg_list):
        raise exc.NoArgument()

class NewActionWithUsage(PT.Action):
    needs_parm = False
    usage = '<some_argument>'
    def init(self, arg_list):
        raise exc.NoArgument()

class TestActionAPI(unittest.TestCase):
    """ Tests the Action API """

    def test_argument_types(self):
        """ Test argument type handling of Action subclass """
        self.assertRaises(TypeError, lambda: PT.actions.Action(10))

    def test_error_handling(self):
        """ Test error handling in Action initializer """
        with self.assertRaises(exc.ParmError):
            PT.changeRadii(None, 'mbondi2')
        self.assertFalse(NewActionNoUsage(None).valid)
        self.assertFalse(NewActionWithUsage(None).valid)
        with self.assertWarns(exc.UnhandledArgumentWarning):
            PT.changeRadii(gasparm, 'mbondi2', 'extra', 'arguments')
        str(NewActionNoUsage(None)) # Make sure it doesn't crash

class TestActionAPI(unittest.TestCase):
    """ Tests the Action API """

    def test_argument_types(self):
        """ Test argument type handling of Action subclass """
        self.assertRaises(TypeError, lambda: PT.actions.Action(10))

    def test_error_handling(self):
        """ Test error handling in Action initializer """
        self.assertRaises(exc.ParmError, lambda:
                PT.changeRadii(None, 'mbondi2'))

class TestNonParmActions(FileIOTestCase):
    """ Tests all actions that do not require a prmtop instance """

    def setUp(self):
        self.parm = gasparm
        super().setUp()

    def test_overwrite(self):
        """ Test setting overwrite capabilities on ParmEd interpeter """
        a = PT.setOverwrite(self.parm, False)
        self.assertFalse(PT.Action.overwrite)
        a.execute()
        self.assertFalse(PT.Action.overwrite)
        self.assertEqual(str(a), 'Files are NOT overwritable')
        a = PT.setOverwrite(self.parm, True)
        a.execute()
        self.assertTrue(PT.Action.overwrite)
        self.assertEqual(str(a), 'Files are overwritable')
        PT.setOverwrite(None, False).execute() # Turn it back off
        with self.assertWarns(exc.SeriousParmWarning):
            PT.setOverwrite(None, 'badarg')

    def test_list_parms(self):
        """ Test listing of the prmtop files in the ParmEd interpreter """
        a = PT.listParms(self.parm)
        a.execute() # Should do nothing
        lines = str(a).split('\n')
        self.assertEqual(lines[0], 'Loaded topology files:')
        self.assertEqual(lines[1], '[0]\t%s (active)' % self.get_fn('trx.prmtop'))

    @unittest.skipUnless(HAS_GROMACS, "Cannot run GROMACS tests without GROMACS")
    def test_chamber(self):
        """ Test the chamber action with a basic protein """
        a = PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                       self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'))
        str(a)
        a.execute()
        parm = a.parm
        self._standard_parm_tests(parm)
        self._extensive_checks(parm)
        self.assertTrue(parm.chamber)
        self.assertTrue(parm.has_cmap)
        self.assertEqual(parm.ptr('ifbox'), 0)
        # Error checking
        self.assertRaises(exc.FileDoesNotExist, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                           '-top', 'foo', '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb')).execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-top', self.get_fn('top_all22_prot.inp'),
                           '-param', self.get_fn('par_all22_prot.inp'), '-crd',
                           self.get_fn('ala_ala_ala.pdb')).execute()
        )
        self.assertRaises(exc.FileDoesNotExist, lambda:
                PT.chamber(self.parm, '-psf', 'foo', '-top', self.get_fn('top_all22_prot.inp'),
                           '-param', self.get_fn('par_all22_prot.inp'), '-crd',
                           self.get_fn('ala_ala_ala.pdb')).execute()
        )
        self.assertRaises(exc.FileDoesNotExist, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                           '-top', self.get_fn('top_all22_prot.inp'),
                           '-param', 'foo', '-crd', self.get_fn('ala_ala_ala.pdb')).execute()
        )
        self.assertRaises(exc.FileDoesNotExist, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                           self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', 'foo').execute()
        )
        self.assertRaises(exc.FileDoesNotExist, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                           self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb'), '-str', 'foo').execute()
        )
        self.assertRaises(exc.FileDoesNotExist, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                           self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb'), '-toppar', 'foo').execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                           '-top', self.get_fn('top_all22_prot.inp'),
                           '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb'),
                           '-toppar', self.get_fn('trx.prmtop')).execute()
        )
        fn = self.get_fn('test.inp', written=True)
        with open(fn, 'w') as f:
            pass
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                           self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb'), '-toppar', fn).execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                           '-top', self.get_fn('top_all22_prot.inp'), '-param',
                           self.get_fn('par_all22_prot.inp'), '-crd', self.get_fn('*.pdb')).execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('*.psf'), '-top', self.get_fn('top_all22_prot.inp'),
                           '-param', self.get_fn('par_all22_prot.inp'), '-crd',
                           self.get_fn('ala_ala_ala.pdb')).execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                           self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb'), '-box', 'a,b,c,d,e,f').execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                           self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb'), '-box', '1,2,3,4').execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                           '-top', self.get_fn('top_all22_prot.inp'),
                           '-crd', self.get_fn('ala_ala_ala.pdb')).execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                           '-top', self.get_fn('top_all22_prot.inp'),
                           '-param', self.get_fn('par_all22_prot.inp'), '-radii', 'foobar').execute()
        )
        self.assertRaises(exc.InputError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                           '-top', self.get_fn('top_all22_prot.inp'),
                           '-param', self.get_fn('par_all22_prot.inp'), '-radii', 'foobar').execute()
        )
        self.assertRaises(exc.ChamberError, lambda:
                PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'), '-top',
                           self.get_fn('top_all22_prot.inp'), '-param', self.get_fn('par_all22_prot.inp'),
                           '-crd', self.get_fn('trx.prmtop')).execute()
        )

    def test_chamber_model(self):
        """ Test the chamber action with a model compound """
        a = PT.chamber(self.parm, '-psf', self.get_fn('propane.psf'), '-top',
                       self.get_fn('top_all36_prot.rtf'), '-param', self.get_fn('par_all36_prot.prm'),
                       '-str', self.get_fn('toppar_all36_prot_model.str'), '-str',
                       self.get_fn('toppar_water_ions.str'), '-crd', self.get_fn('propane.pdb'))
        str(a)
        a.execute()
        parm = a.parm
        self._standard_parm_tests(parm)
#       self._extensive_checks(parm)
        self.assertTrue(parm.chamber)
        self.assertEqual(parm.ptr('ifbox'), 0)

    def test_chamber_globbing(self):
        """ Test globbing in the chamber action """
        a = PT.chamber(self.parm, '-psf', self.get_fn('ala_ala_ala.psf'),
                       '-toppar', self.get_fn('*_all22_prot.inp'),
                       '-crd', self.get_fn('ala_ala_ala.pdb'))
        str(a)
        a.execute()
        parm = a.parm
        self._standard_parm_tests(parm)
        self._extensive_checks(parm)
        self.assertTrue(parm.chamber)
        self.assertTrue(parm.has_cmap)
        self.assertEqual(parm.ptr('ifbox'), 0)

    def test_chamber_nbfix(self):
        """ Test the chamber action with a complex system using NBFIX """
        a = PT.chamber(self.parm, '-psf', self.get_fn('ala3_solv.psf'), '-toppar',
                       self.get_fn('???_all36_prot.???'), '-toppar', self.get_fn('toppar_water_ions.str'),
                       '-crd', self.get_fn('ala3_solv.crd'), '-box', 'bounding')
        a.execute()
        str(a)
        parm = a.parm
        self.assertTrue(parm.has_NBFIX())
        self._standard_parm_tests(parm)
#       self._extensive_checks(parm)

    def test_chamber_bug1(self):
        """ Test chamber BFNA creation (former bug) """
        a = PT.chamber(self.parm, '-top', self.get_fn('top_all36_cgenff.rtf'),
                '-top', self.get_fn('top_bfna_nonbonded_stitched.rtf'), '-param',
                self.get_fn('par_bfna_nonbonded_stitched.prm'), '-param',
                self.get_fn('par_all36_cgenff.prm'), '-box', 'bounding', '-psf',
                self.get_fn('bfna_nonbonded_vmd_autopsf.psf'), 'nocondense', '-crd',
                self.get_fn('bfna_nonbonded_vmd_autopsf.pdb'), '-nocmap'
        )
        str(a)
        a.execute()
        parm = a.parm
        self._standard_parm_tests(parm)
        self._extensive_checks(parm)
        psf = CharmmPsfFile(self.get_fn('bfna_nonbonded_vmd_autopsf.psf'))
        self.assertEqual(len(psf.atoms), len(parm.atoms))
        self.assertEqual(len(psf.residues), len(parm.residues))
        for a1, a2 in zip(psf.atoms, parm.atoms):
            self.assertEqual(a1.name[:4], a2.name[:4])
            self.assertEqual(a1.type[:4], a2.type[:4])
            self.assertAlmostEqual(a1.charge, a2.charge)
            self.assertAlmostEqual(a1.mass, a2.mass)

    def test_chamber_bug2(self):
        """ Test that chamber sets the box angles for triclinics correctly """
        a = PT.chamber(self.parm, '-psf', self.get_fn('ala3_solv.psf'),
                       '-param', self.get_fn('par_all36_prot.prm'),
                       '-str', self.get_fn('toppar_water_ions.str'),
                       '-crd', self.get_fn('ala3_solv.crd'), '-box',
                       '33,33,33,109.475,109.475,109.475')
        a.execute()
        str(a)
        parm = a.parm
        self._standard_parm_tests(parm)
#       self._extensive_checks(parm)
        for x, y in zip(parm.parm_data['BOX_DIMENSIONS'], [109.475] + [33]*3):
            self.assertAlmostEqual(x, y)
        for x, y in zip(parm.box, [33]*3 + [109.475]*3):
            self.assertAlmostEqual(x, y)
        # Now check implied orthorhombic UC
        a = PT.chamber(self.parm, '-psf', self.get_fn('ala3_solv.psf'), '-param',
                       self.get_fn('par_all36_prot.prm'), '-str', self.get_fn('toppar_water_ions.str'),
                       '-crd', self.get_fn('ala3_solv.crd'), '-box', '33,33,33')
        a.execute()
        str(a)
        parm = a.parm
        for x, y in zip(parm.parm_data['BOX_DIMENSIONS'], [90] + [33]*3):
            self.assertEqual(x, y)
        has_wat = False
        for res in parm.residues:
            self.assertNotEqual(res.name, 'TIP3')
            if res.name == 'WAT':
                has_wat = True
                names = [a.name for a in res]
                self.assertNotIn('OH2', names)
                self.assertIn('O', names)
        self.assertTrue(has_wat)

    @unittest.skipUnless(HAS_GROMACS, "Cannot run GROMACS tests without GROMACS")
    def test_gromber(self):
        """ Test the gromber action on a small system (no coords) """
        a = PT.gromber(None, os.path.join(self.get_fn('03.AlaGlu'), 'topol.top'))
        str(a)
        a.execute()
        parm = a.parm
        self._standard_parm_tests(parm)
        self._extensive_checks(parm)
        self.assertIs(parm.box, None)

    @unittest.skipUnless(HAS_GROMACS, "Cannot run GROMACS tests without GROMACS")
    def test_gromber2(self):
        """ Test the gromber action with coordinates """
        a = PT.gromber(None, os.path.join(self.get_fn('03.AlaGlu'), 'topol.top'),
                       os.path.join(self.get_fn('03.AlaGlu'), 'conf.gro'))
        str(a)
        a.execute()
        parm = a.parm
        parm.box = None
        self._standard_parm_tests(parm)
        self._extensive_checks(parm)
        self.assertIs(parm.box, None) # AmberParm deletes the box without solvent
        for atom in parm.atoms:
            self.assertTrue(hasattr(atom, 'xx'))
            self.assertTrue(hasattr(atom, 'xy'))
            self.assertTrue(hasattr(atom, 'xz'))

    @unittest.skipUnless(HAS_GROMACS, "Cannot run GROMACS tests without GROMACS")
    def test_gromber3(self):
        """ Test the gromber action passing various defines """
        a = PT.gromber(None, os.path.join(self.get_fn('03.AlaGlu'), 'topol.top'),
                       os.path.join(self.get_fn('03.AlaGlu'), 'conf.gro'),
                       'define', 'SOMEDEF=this', 'define', 'SOMEDEF2=that')
        stra = str(a)
        self.assertIn('SOMEDEF', stra)
        self.assertIn('SOMEDEF2', stra)
        a.execute()
        parm = a.parm
        parm.box = None
        self._standard_parm_tests(parm)
        self._extensive_checks(parm)
        self.assertIs(parm.box, None)
        for atom in parm.atoms:
            self.assertTrue(hasattr(atom, 'xx'))
            self.assertTrue(hasattr(atom, 'xy'))
            self.assertTrue(hasattr(atom, 'xz'))

    @unittest.skipUnless(HAS_GROMACS, "Cannot run GROMACS tests without GROMACS")
    def test_gromber_box(self):
        """ Test the gromber action when a box should be defined """
        a = PT.gromber(None, self.get_fn('ala3.solv.top'), self.get_fn('ala3.solv.gro'))
        a.execute()
        str(a)
        parm = a.parm
        self._standard_parm_tests(parm)
#       self._extensive_checks(parm)
        for atom in parm.atoms:
            self.assertTrue(hasattr(atom, 'xx'))
            self.assertTrue(hasattr(atom, 'xy'))
            self.assertTrue(hasattr(atom, 'xz'))
        np.testing.assert_allclose(parm.box,
                [31.3585000, 31.3585000, 31.3584443,
                 60.0000468, 60.0000468, 90.0000000])

    # Copied from test_parmed_amber -- tests the prmtop file generated by the
    # "chamber" action
    def _standard_parm_tests(self, parm):
        self.assertEqual(parm.ptr('natom'), len(parm.atoms))
        self.assertEqual(parm.ptr('nres'), len(parm.residues))
        self.assertEqual(parm.ptr('nbonh'), len(list(parm.bonds_inc_h)))
        self.assertEqual(parm.ptr('nbona'), len(list(parm.bonds_without_h)))
        self.assertEqual(parm.ptr('ntheth'), len(list(parm.angles_inc_h)))
        self.assertEqual(parm.ptr('ntheta'), len(list(parm.angles_without_h)))
        self.assertEqual(parm.ptr('nphih'), len(list(parm.dihedrals_inc_h)))
        self.assertEqual(parm.ptr('nphia'), len(list(parm.dihedrals_without_h)))
        self.assertEqual([a.name for a in parm.atoms],
                         parm.parm_data['ATOM_NAME'])
        self.assertEqual([a.type[:4] for a in parm.atoms],
                         parm.parm_data['AMBER_ATOM_TYPE'])

    def _extensive_checks(self, parm):
        # Check the __contains__ methods of the various topologyobjects
        atoms = parm.atoms
        for bond in parm.bonds:
            self.assertEqual(sum([a in bond for a in atoms]), 2)
        for angle in parm.angles:
            self.assertEqual(sum([a in angle for a in atoms]), 3)
            self.assertEqual(sum([b in angle for b in parm.bonds]), 2)
        for dihedral in parm.dihedrals:
            self.assertEqual(sum([a in dihedral for a in atoms]), 4)
            self.assertEqual(sum([b in dihedral for b in parm.bonds]), 3)
        for residue in parm.residues:
            self.assertTrue(all([a in residue for a in residue.atoms]))
            self.assertEqual(sum([a in residue for a in atoms]),
                             len(residue))
        if not parm.chamber: return
        # Chamber tests now
        for ub in parm.urey_bradleys:
            self.assertEqual(sum([a in ub for a in atoms]), 2)
            self.assertEqual(sum([b in ub for b in parm.bonds]), 2)
        for imp in parm.impropers:
            self.assertEqual(sum([a in imp for a in atoms]), 4)
            self.assertEqual(sum([b in imp for b in parm.bonds]), 3)
        if parm.has_cmap:
            for cmap in parm.cmaps:
                self.assertEqual(sum([a in cmap for a in atoms]), 5)
                self.assertEqual(sum([b in cmap for b in parm.bonds]), 4)

class TestAmberParmActions(FileIOTestCase, TestCaseRelative):
    """ Tests actions on Amber prmtop files """

    @unittest.skipIf(PYPY, 'Cannot test with NetCDF on pypy')
    def test_parmout_outparm_load_restrt(self):
        """ Test parmout, outparm, and loadRestrt actions on AmberParm """
        parm = copy(gasparm)
        lract = PT.loadRestrt(parm, self.get_fn('trx.inpcrd'))
        lract.execute()
        for atom in parm.atoms:
            self.assertTrue(hasattr(atom, 'xx'))
            self.assertTrue(hasattr(atom, 'xy'))
            self.assertTrue(hasattr(atom, 'xz'))
        act = PT.parmout(parm, self.get_fn('test.parm7', written=True))
        act.execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 1)
        self.assertTrue(diff_files(self.get_fn('trx.prmtop'), self.get_fn('test.parm7', written=True)))
        self._empty_writes()
        PT.parmout(parm, self.get_fn('test.parm7', written=True), self.get_fn('test.rst7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 2)
        self.assertTrue(diff_files(self.get_fn('trx.prmtop'), self.get_fn('test.parm7', written=True)))
        self.assertTrue(diff_files(self.get_fn('trx.inpcrd'), self.get_fn('test.rst7', written=True),
                                   absolute_error=0.0001))
        self._empty_writes()
        PT.outparm(parm, self.get_fn('test.parm7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 1)
        self.assertTrue(diff_files(self.get_fn('trx.prmtop'), self.get_fn('test.parm7', written=True)))
        self._empty_writes()
        PT.outparm(parm, self.get_fn('test.parm7', written=True), self.get_fn('test.rst7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 2)
        self.assertTrue(diff_files(self.get_fn('trx.prmtop'), self.get_fn('test.parm7', written=True)))
        self.assertTrue(diff_files(self.get_fn('trx.inpcrd'), self.get_fn('test.rst7', written=True),
                                   absolute_error=0.0001))
        self._empty_writes()
        # Now try NetCDF format
        act2 = PT.outparm(parm, self.get_fn('test.parm7', written=True),
                          self.get_fn('test.ncrst', written=True), 'netcdf')
        act2.execute()
        self.assertTrue(
                amber.NetCDFRestart.id_format(self.get_fn('test.ncrst', written=True)))
        # Now make sure the string methods work
        str(act2); str(act); str(lract)
        # Make sure overwriting does not work
        self.assertRaises(exc.FileExists, act2.execute)
        # Delete the prmtop and make sure it still raises
        os.unlink(self.get_fn('test.parm7', written=True))
        self.assertRaises(exc.FileExists, act2.execute)

    def test_write_frcmod(self):
        """ Test writeFrcmod on AmberParm """
        parm = gasparm
        act = PT.writeFrcmod(parm, self.get_fn('test.frcmod', written=True))
        act.execute()
        self.assertTrue(diff_files(self.get_fn('test.frcmod', saved=True), self.get_fn('test.frcmod', written=True)))
        self.assertRaises(exc.FileExists, act.execute)
        # Make sure 10-12 prmtops fail
        parm = load_file(self.get_fn('ff91.parm7'))
        with self.assertWarns(exc.SeriousParmWarning):
            PT.writeFrcmod(load_file(self.get_fn('ff91.parm7')), self.get_fn('test2.frcmod', written=True))
        # Make sure string does not error
        str(act)

    def test_write_off_load_restrt(self):
        """ Test writeOFF on AmberParm """
        parm = copy(gasparm)
        parm.coordinates = None
        self.assertRaises(exc.ParmError, lambda:
                PT.writeOFF(parm, self.get_fn('test.off', written=True)).execute())
        PT.loadRestrt(parm, self.get_fn('trx.inpcrd')).execute()
        act = PT.writeOFF(parm, self.get_fn('test.off', written=True))
        act.execute()
        self.assertTrue(diff_files(self.get_fn('test.off', saved=True),
                                   self.get_fn('test.off', written=True),
                                   absolute_error=0.0001))
        str(act)
        self.assertRaises(exc.FileExists, act.execute)

    def test_change_radii(self):
        """ Test changeRadii on AmberParm """
        parm = copy(gasparm)
        act = PT.changeRadii(parm, 'amber6')
        act.execute()
        str(act) # Make sure it doesn't fail
        self.assertEqual(parm.parm_data['RADIUS_SET'][0],
                         'amber6 modified Bondi radii (amber6)')
        for i, atom in enumerate(parm.atoms):
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            self.assertEqual(parm.parm_data['RADII'][i], radii)
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.bond_partners[0].atomic_number == 6:
                    self.assertEqual(radii, 1.3)
                elif atom.bond_partners[0].atomic_number in (8, 16):
                    self.assertEqual(radii, 0.8)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        # Make sure it adds back all necessary flags
        parm.delete_flag('RADIUS_SET')
        parm.delete_flag('RADII')
        parm.delete_flag('SCREEN')
        PT.changeRadii(parm, 'bondi').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0], 'Bondi radii (bondi)')
        for atom in parm.atoms:
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        PT.changeRadii(parm, 'mbondi').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0],
                         'modified Bondi radii (mbondi)')
        for atom in parm.atoms:
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.bond_partners[0].atomic_number in (6, 7):
                    self.assertEqual(radii, 1.3)
                elif atom.bond_partners[0].atomic_number in (8, 16):
                    self.assertEqual(radii, 0.8)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        PT.changeRadii(parm, 'mbondi2').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0],
                         'H(N)-modified Bondi radii (mbondi2)')
        for atom in parm.atoms:
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.bond_partners[0].atomic_number == 7:
                    self.assertEqual(radii, 1.3)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        PT.changeRadii(parm, 'mbondi3').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0],
                         'ArgH and AspGluO modified Bondi2 radii (mbondi3)')
        for i, atom in enumerate(parm.atoms):
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8:
                if atom.residue.name in ('ASP,GLU') and (
                            atom.name.startswith('OD') or
                            atom.name.startswith('OE')):
                    self.assertEqual(radii, 1.4)
                elif atom.name == 'OXT' or (i < parm.ptr('natom') and
                            parm.atoms[i+1].name == 'OXT'):
                    self.assertEqual(radii, 1.4)
                else:
                    self.assertEqual(radii, 1.5)
            elif atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.residue.name == 'ARG' and \
                            atom.name[:2] in ('HH', 'HE'):
                    self.assertEqual(radii, 1.17)
                elif atom.bond_partners[0].atomic_number == 7:
                    self.assertEqual(radii, 1.3)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)
        # Now test bad input
        self.assertRaises(exc.ChangeRadiiError, lambda:
                          PT.changeRadii(parm, 'mbondi6').execute())
        # Test that it works on non-Amber topologies
        act = PT.changeRadii(pmd.load_file(self.get_fn('ala_ala_ala.psf')), 'mbondi3')
        act.execute()

    def test_change_lj_pair(self):
        """ Test changeLJPair on AmberParm """
        parm = copy(gasparm)
        act = PT.changeLJPair(parm, '@%N', '@%H', 1.0, 1.0)
        act.execute()
        str(act)
        # Figure out what type numbers each atom type belongs to
        ntype = htype = 0
        for atom in parm.atoms:
            if atom.type == 'N':
                ntype = atom.nb_idx
            elif atom.type == 'H':
                htype = atom.nb_idx
        # Make sure the A and B coefficient matrices are what I expect them to
        # be
        indexes = sorted([ntype, htype])
        acoef = parm.parm_data['LENNARD_JONES_ACOEF'][:]
        bcoef = parm.parm_data['LENNARD_JONES_BCOEF'][:]
        refa = gasparm.parm_data['LENNARD_JONES_ACOEF'][:]
        refb = gasparm.parm_data['LENNARD_JONES_BCOEF'][:]
        ntypes = parm.ptr('ntypes')
        for i in range(ntypes):
            for j in range(i, ntypes):
                idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*i+j]
                if [i+1, j+1] == indexes:
                    self.assertEqual(acoef[idx-1], 1.0)
                    self.assertEqual(bcoef[idx-1], 2.0)
                else:
                    self.assertEqual(acoef[idx-1], refa[idx-1])
                    self.assertEqual(bcoef[idx-1], refb[idx-1])
        # Check handling with no atoms
        PT.changeLJPair(parm, '@NONE', '@%H', 1.0, 1.0).execute()
        # Make sure nothing changed
        np.testing.assert_equal(acoef, parm.parm_data['LENNARD_JONES_ACOEF'])
        np.testing.assert_equal(bcoef, parm.parm_data['LENNARD_JONES_BCOEF'])
        # Check error handling
        self.assertRaises(exc.ChangeLJPairError, lambda:
                PT.changeLJPair(parm, ':*', '@%H', 1.0, 1.0).execute())
        self.assertRaises(exc.ChangeLJPairError, lambda:
                PT.changeLJPair(parm, '@%H', ':*', 1.0, 1.0).execute())

    def test_change_lj_14_pair(self):
        """ Check that changeLJ14Pair fails on AmberParm """
        parm = copy(gasparm)
        self.assertRaises(exc.ParmError, lambda:
            PT.changeLJ14Pair(parm, '@%N', '@%H', 1.0, 1.0).execute())

    def test_change(self):
        """ Test change on AmberParm with all properties """
        parm = copy(gasparm)
        act = PT.change(parm, 'CHARGE', ':ALA', 0, 'quiet')
        act.execute()
        self.assertEqual(len(str(act).split('\n')), 1)
        for flag in parm.parm_data:
            if flag != 'CHARGE':
                self.assertEqual(parm.parm_data[flag], gasparm.parm_data[flag])
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['CHARGE'][i], atom.charge)
            if atom.residue.name == 'ALA':
                self.assertEqual(atom.charge, 0)
            else:
                self.assertEqual(atom.charge, gasparm.atoms[i].charge)
        act = PT.change(parm, 'MASS', ':GLY', 10.0)
        self.assertGreater(len(str(act).split('\n')), 1)
        act.execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['MASS'][i], atom.mass)
            if atom.residue.name == 'GLY':
                self.assertEqual(atom.mass, 10.0)
            else:
                self.assertEqual(atom.mass, gasparm.atoms[i].mass)
            if atom.residue.name == 'ALA':
                self.assertEqual(atom.charge, 0.0)
            else:
                self.assertEqual(atom.charge, gasparm.atoms[i].charge)
        PT.change(parm, 'ATOM_NAME', ':ASP@C', 'JMS').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['ATOM_NAME'][i], atom.name)
            if atom.residue.name == 'ASP' and gasparm.atoms[i].name == 'C':
                self.assertEqual(atom.name, 'JMS')
            else:
                self.assertEqual(atom.name, gasparm.atoms[i].name)
        PT.change(parm, 'AMBER_ATOM_TYPE', ':GLU@N', 'RJLS').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['AMBER_ATOM_TYPE'][i], atom.type)
            if atom.residue.name == 'GLU' and gasparm.atoms[i].name == 'N':
                self.assertEqual(atom.type, 'RJLS')
            else:
                self.assertEqual(atom.type, gasparm.atoms[i].type)
        PT.change(parm, 'ATOM_TYPE_INDEX', '@1', 4).execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.nb_idx, parm.parm_data['ATOM_TYPE_INDEX'][i])
            if i == 0:
                self.assertEqual(atom.nb_idx, 4)
            else:
                self.assertEqual(atom.nb_idx, gasparm.atoms[i].nb_idx)
        PT.change(parm, 'RADII', ':1-20', 2.0, 'quiet').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.solvent_radius, parm.parm_data['RADII'][i])
            if atom.residue.idx < 20:
                self.assertEqual(atom.solvent_radius, 2.0)
            else:
                self.assertEqual(atom.solvent_radius,
                                 gasparm.atoms[i].solvent_radius)
        PT.change(parm, 'SCREEN', '*', 0.0).execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.screen, parm.parm_data['SCREEN'][i])
            self.assertEqual(atom.screen, 0.0)
        PT.change(parm, 'TREE_CHAIN_CLASSIFICATION', ':1-2', 'ABC').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.tree,
                             parm.parm_data['TREE_CHAIN_CLASSIFICATION'][i])
            if atom.residue.idx < 2:
                self.assertEqual(atom.tree, 'ABC')
        # Check if we try to add strings that are too long
        with self.assertWarns(exc.ParmWarning):
            PT.change(parm, 'ATOM_NAME', ':*', 'LALALALA').execute()
        # Make sure the strings get truncated
        PT.change(parm, 'ATOM_NAME', ':*', 'LALALALA').execute()
        for atom in parm.atoms:
            self.assertEqual(atom.name, 'LALA')
            self.assertEqual(parm.parm_data['ATOM_NAME'][atom.idx], 'LALA')
        # Check bad input
        self.assertRaises(exc.ParmedChangeError, lambda:
                          PT.change(parm, 'RESIDUE_LABEL', ':*', 'NaN'))

    def test_print_info(self):
        """ Test printInfo for all flags on AmberParm """
        for flag in gasparm.parm_data:
            act = PT.printInfo(gasparm, flag)
            vals = []
            for line in str(act).split('\n'):
                vals += line.split()
            self.assertEqual(len(vals), len(gasparm.parm_data[flag]))
            try:
                datatype = type(gasparm.parm_data[flag][0])
            except IndexError:
                continue
            for i, j in zip(vals, gasparm.parm_data[flag]):
                # printInfo prints to 5 places for floats.
                if datatype is float:
                    self.assertAlmostEqual(datatype(i), j, places=4)
                else:
                    self.assertEqual(datatype(i), j)
        with self.assertWarns(exc.SeriousParmWarning):
            repr(PT.printInfo(gasparm, 'NOTAFLAG'))

    def test_add_change_lj_type(self):
        """ Test addLJType and changeLJSingleType on AmberParm """
        parm = copy(gasparm)
        act = PT.addLJType(parm, '@1')
        act.execute()
        str(act) # Make sure it doesn't crash
        self.assertEqual(parm.ptr('ntypes'), gasparm.ptr('ntypes') + 1)
        self.assertEqual(parm.atoms[0].nb_idx, parm.ptr('ntypes'))
        ntypes = parm.ptr('ntypes')
        ntypes2 = ntypes - 1
        orig_type = gasparm.atoms[0].nb_idx - 1
        for i in range(ntypes):
            idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*i+ntypes-1]
            if i == ntypes - 1:
                idx2 = gasparm.parm_data['NONBONDED_PARM_INDEX'][
                                                ntypes2*orig_type+orig_type]
            else:
                ii, jj = sorted([orig_type, i])
                idx2 = gasparm.parm_data['NONBONDED_PARM_INDEX'][ntypes2*ii+jj]
            self.assertRelativeEqual(
                            parm.parm_data['LENNARD_JONES_ACOEF'][idx-1],
                            gasparm.parm_data['LENNARD_JONES_ACOEF'][idx2-1],
                            places=7)
            self.assertRelativeEqual(
                            parm.parm_data['LENNARD_JONES_BCOEF'][idx-1],
                            gasparm.parm_data['LENNARD_JONES_BCOEF'][idx2-1],
                            places=7)
        # Ensure that the rest of the values are unchanged (exactly equal)
        for i in range(ntypes2):
            for j in range(ntypes2):
                idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*i+j]
                idx2 = gasparm.parm_data['NONBONDED_PARM_INDEX'][ntypes2*i+j]
                self.assertEqual(
                            parm.parm_data['LENNARD_JONES_ACOEF'][idx-1],
                            gasparm.parm_data['LENNARD_JONES_ACOEF'][idx2-1])
                self.assertEqual(
                            parm.parm_data['LENNARD_JONES_BCOEF'][idx-1],
                            gasparm.parm_data['LENNARD_JONES_BCOEF'][idx2-1])
        # Now supply keywords
        parm2 = copy(gasparm)
        PT.addLJType(parm2, '@1', radius=1.0, epsilon=1.0).execute()
        PT.changeLJSingleType(parm, '@1', 1.0, 1.0).execute()
        for x, y in zip(parm.parm_data['LENNARD_JONES_ACOEF'],
                        parm2.parm_data['LENNARD_JONES_ACOEF']):
            self.assertRelativeEqual(x, y)
        for x, y in zip(parm.parm_data['LENNARD_JONES_BCOEF'],
                        parm2.parm_data['LENNARD_JONES_BCOEF']):
            self.assertRelativeEqual(x, y)
        # Now use addLJType to hack a way to turn off LJ interactions
        PT.addLJType(parm, '*', radius=0.0, epsilon=0.0).execute()
        ntypes = parm.ptr('ntypes')
        for atom in parm.atoms:
            self.assertEqual(atom.nb_idx, ntypes)
        idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*(ntypes-1)+ntypes-1]
        self.assertEqual(parm.parm_data['LENNARD_JONES_ACOEF'][idx-1], 0.0)
        self.assertEqual(parm.parm_data['LENNARD_JONES_BCOEF'][idx-1], 0.0)

    def test_print_lj_types(self):
        """ Test printLJTypes on AmberParm """
        # Simple test
        act = PT.printLJTypes(gasparm, '@1')
        for line in str(act).split('\n'):
            if not line.startswith('ATOM'):
                continue
            words = line.split()
            self.assertTrue(words[2].startswith('N'))
            self.assertTrue(words[3].startswith('N'))
            self.assertEqual(words[7], '1')
        # Integer
        act = PT.printLJTypes(gasparm, 1)
        for line in str(act).split('\n'):
            if not line.startswith('ATOM'):
                continue
            words = line.split()
            self.assertTrue(words[2].startswith('N'))
            self.assertTrue(words[3].startswith('N'))
            self.assertEqual(words[7], '1')
        # Integer tuple
        act = PT.printLJTypes(gasparm, '1,2')
        for line in str(act).split('\n'):
            if not line.startswith('ATOM'):
                continue
            words = line.split()
            self.assertIn(words[2][0], 'NH')
            self.assertIn(words[3][0], 'NH')
            self.assertIn(words[7], ('1', '2'))
        # Default prints out every atom
        i = 0
        for line in str(PT.printLJTypes(gasparm)).split('\n'):
            if not line.startswith('ATOM'):
                continue
            i += 1
        self.assertEqual(i, len(gasparm.atoms))
        # Now check ranges and such
        act = PT.printLJTypes(gasparm, '1-3,5')
        it = (a for a in gasparm.atoms if a.nb_idx in (1, 2, 3, 5))
        for line in str(act).split('\n'):
            if not line.startswith('ATOM'):
                continue
            words = line.split()
            a = next(it)
            self.assertEqual(int(words[1]), a.idx+1)
            self.assertEqual(words[2], a.name)
            self.assertEqual(words[3], a.type)
            self.assertEqual(int(words[7]), a.nb_idx)
            i += 1
        # Check that it still works when nothing selected
        str(PT.printLJTypes(gasparm, '@NOATOM'))
        # Check illegal selections
        self.assertRaises(exc.ParmError, lambda:
                PT.printLJTypes(gasparm, 0))
        self.assertRaises(exc.ParmError, lambda:
                PT.printLJTypes(gasparm, '0-0'))

    def test_scee_scnb(self):
        """ Test scee and scnb actions on AmberParm """
        parm = copy(gasparm)
        act1 = PT.scee(parm, 1.0)
        act2 = PT.scnb(parm, 1.0)
        act1.execute()
        act2.execute()
        str(act1), str(act2)
        for dih in parm.dihedrals:
            self.assertEqual(dih.type.scee, 1.0)
            self.assertEqual(dih.type.scnb, 1.0)
        for x, y in zip(parm.parm_data['SCEE_SCALE_FACTOR'],
                        parm.parm_data['SCNB_SCALE_FACTOR']):
            self.assertEqual(x, 1.0)
            self.assertEqual(y, 1.0)

    def test_print_details(self):
        """ Test printDetails on AmberParm """
        act = PT.printDetails(gasparm, '@1')
        self.assertEqual(str(act), saved.PRINT_DETAILS)
        # Check out different ways of selecting parms
        act = PT.printDetails(gasparm, '@1', parm=0)
        self.assertEqual(str(act), saved.PRINT_DETAILS)
        act = PT.printDetails(gasparm, '@1', parm=str(gasparm))
        self.assertEqual(str(act), saved.PRINT_DETAILS)
        with self.assertWarns(exc.SeriousParmWarning):
            PT.printDetails(gasparm, '@1', parm='DNE')
        with self.assertWarns(exc.SeriousParmWarning):
            PT.printDetails(gasparm, '@1', parm=10)

    def test_print_flags(self):
        """ Test printFlags on AmberParm """
        act = PT.printFlags(gasparm)
        printed_flags = set()
        for line in str(act).split('\n'):
            if line.startswith('%FLAG'):
                printed_flags.add(line.split()[1])
        self.assertEqual(printed_flags, set(gasparm.parm_data.keys()))

    def test_print_pointers(self):
        """ Test printPointers on AmberParm """
        act = PT.printPointers(gasparm)
        printed_pointers = set()
        printed_pointers.add('NEXT') # Not in printed list
        for line in str(act).split('\n'):
            try:
                pointer = line.split()[0]
                value = int(line[line.rfind('=')+1:].strip())
            except (IndexError, ValueError):
                continue
            self.assertEqual(gasparm.ptr(pointer), value)
            printed_pointers.add(pointer)
        self.assertEqual(printed_pointers, set(gasparm.pointers.keys()))

    def test_print_bonds(self):
        """ Test printBonds on AmberParm """
        act = PT.printBonds(gasparm, '@1')
        self.assertEqual(str(act), saved.PRINT_BONDS)
        act = PT.printBonds(gasparm, '@1', ':*')
        self.assertEqual(str(act), saved.PRINT_BONDS)
        act = PT.printBonds(gasparm, '*', '@1')
        self.assertEqual(str(act), saved.PRINT_BONDS)
        act = PT.printBonds(gasparm, '@1', '@3')
        self.assertEqual(str(act), saved.PRINT_BONDS_2MASKS)
        act = PT.printBonds(gasparm, '@3', '@1')
        self.assertEqual(str(act), saved.PRINT_BONDS_2MASKS)

    def test_print_bonds_with_measurements(self):
        """ Test printBonds on AmberParm with measurements """
        act = PT.printBonds(AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7')), '*')
        self.assertEqual(str(act), saved.PRINT_BONDS_MEASURE)

    def test_print_angles(self):
        """ Test printAngles on AmberParm """
        act = PT.printAngles(gasparm, '@1')
        self.assertEqual(str(act), saved.PRINT_ANGLES)
        act = PT.printAngles(gasparm, '@1', '*')
        self.assertEqual(str(act), saved.PRINT_ANGLES_2MASKS_1)
        act = PT.printAngles(gasparm, '@1', '*', '*')
        self.assertEqual(str(act), saved.PRINT_ANGLES_2MASKS_1)
        act = PT.printAngles(gasparm, '*', '*', '@1')
        self.assertEqual(str(act), saved.PRINT_ANGLES_2MASKS_1)
        act = PT.printAngles(gasparm, '*', '@1')
        self.assertEqual(str(act), saved.PRINT_ANGLES_2MASKS_2)
        act = PT.printAngles(gasparm, '*', '@1', '*')
        self.assertEqual(str(act), saved.PRINT_ANGLES_2MASKS_2)
        act = PT.printAngles(gasparm, '@1', '@5')
        self.assertEqual(str(act), saved.PRINT_ANGLES_2MASKS_3)
        act = PT.printAngles(gasparm, '*', '@5', '@1')
        self.assertEqual(str(act), saved.PRINT_ANGLES_2MASKS_3)
        act = PT.printAngles(gasparm, *'@1 @5 @7'.split())
        self.assertEqual(str(act), saved.PRINT_ANGLES_3MASKS)
        act = PT.printAngles(gasparm, *'@7 @5 @1'.split())
        self.assertEqual(str(act), saved.PRINT_ANGLES_3MASKS)

    def test_print_angles_with_measurements(self):
        """ Test printBonds on AmberParm with measurements """
        act = PT.printAngles(AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7')), '*')
        self.assertEqual(str(act), saved.PRINT_ANGLES_MEASURE)

    def test_print_dihedrals(self):
        """ Test printDihedrals on AmberParm """
        act = PT.printDihedrals(gasparm, '@1')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS)
        act = PT.printDihedrals(gasparm, '@1', '@5')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_2MASKS)
        act = PT.printDihedrals(gasparm, '*', '*', '@5', '@1')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_2MASKS)
        act = PT.printDihedrals(gasparm, '@4', '@1', '@5')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_3MASKS)
        act = PT.printDihedrals(gasparm, '@4', '@1', '@5', '*')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_3MASKS)
        act = PT.printDihedrals(gasparm, '*', '@5', '@1', '@4')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_3MASKS)
        act = PT.printDihedrals(gasparm, '@7', ':1@CA', '@12', '@14')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_4MASKS)
        act = PT.printDihedrals(gasparm, '@14', '@12', ':1@CA', '@7')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_4MASKS)
        # Now make sure other combos all work
        str(PT.printDihedrals(gasparm, ':*'))
        str(PT.printDihedrals(gasparm, ':1-10', ':2-11'))
        str(PT.printDihedrals(gasparm, ':*', ':1-10', ':1-10', ':*'))
        str(PT.printDihedrals(gasparm, ':*', ':*', ':1-10', ':1-10'))

    def test_print_angles_with_measurements(self):
        """ Test printBonds on AmberParm with measurements """
        act = PT.printDihedrals(AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7')), '*')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALS_MEASURE)

    def test_set_molecules(self):
        """ Test setMolecules on AmberParm """
        parm = AmberParm(self.get_fn('things.parm7'), self.get_fn('things.rst7'))
        atoms = [atom for atom in parm.atoms] # shallow copy!
        self.assertTrue(all([x is y for x,y in zip(parm.atoms,atoms)]))
        self.assertEqual(parm.ptr('IPTRES'), 29)
        self.assertEqual(parm.ptr('NSPM'), 718)
        self.assertEqual(parm.ptr('NSPSOL'), 23)
        # To keep the output clean
        PT.setMolecules(parm).execute()
        self.assertFalse(all([x is y for x,y in zip(parm.atoms,atoms)]))
        # Now check that setMolecules can apply another time.
        PT.setMolecules(parm, solute_ions=False).execute()

        # Now check that solute_ions keyword works as expected
        parm = AmberParm(self.get_fn('ff14ipq.parm7'), self.get_fn('ff14ipq.rst7'))
        self.assertEqual(parm.parm_data['SOLVENT_POINTERS'], [15, 926, 12])
        PT.setMolecules(parm, solute_ions=False).execute()
        self.assertEqual(parm.parm_data['SOLVENT_POINTERS'], [5, 926, 2])
        act = PT.setMolecules(parm, solute_ions=True)
        act.execute()
        str(act)
        self.assertEqual(parm.parm_data['SOLVENT_POINTERS'], [15, 926, 12])
        with self.assertWarns(exc.SeriousParmWarning):
            PT.setMolecules(parm, solute_ions='foo')

        # Make sure we warn if reordering was required
        with self.assertWarns(exc.ParmWarning):
            PT.setMolecules(pmd.load_file(self.get_fn('things.parm7')), solute_ions=True).execute()

    def test_net_charge(self):
        """ Test netCharge on AmberParm """
        act = PT.netCharge(gasparm)
        chg = act.execute() # check this part of the API
        self.assertEqual(str(act), 'The net charge of :* is %.4f' % chg)
        self.assertAlmostEqual(chg, -4.0, places=6)
        chg = PT.netCharge(gasparm, ':ASP').execute()
        self.assertAlmostEqual(chg, -10.0, places=6)

    def test_strip(self):
        """ Test stripping of AmberParm """
        parm = copy(gasparm)
        parm.box = [10, 10, 10, 90, 90, 90]
        act = PT.strip(parm, ':1')
        act.execute()
        self.assertEqual(parm.ptr('natom'), 1641)
        self.assertEqual(len(parm.atoms), 1641)
        self.assertEqual(str(act), "Removing mask ':1' (%d atoms) "
                         "from the topology file." % (len(gasparm.residues[0])))
        np.testing.assert_equal(parm.box, [10, 10, 10, 90, 90, 90])
        # Now test nobox
        act = PT.strip(parm, ':1', 'nobox')
        act.execute()
        str(act)
        self.assertIs(parm.box, None)

    def test_define_solvent(self):
        """ Test defineSolvent on AmberParm """
        import parmed.residue as residue
        PT.defineSolvent(gasparm, 'WAT,HOH,Na+,Cl-').execute()
        self.assertEqual(residue.SOLVENT_NAMES, 'WAT HOH Na+ Cl-'.split())
        act = PT.defineSolvent(gasparm, 'WAT,HOH,')
        act.execute()
        str(act)
        self.assertEqual(residue.SOLVENT_NAMES, 'WAT HOH'.split())

    def test_add_exclusions(self):
        """ Test addExclusions on AmberParm """
        parm = copy(gasparm)
        in_exclusions_before = []
        for atom1 in parm.residues[0].atoms:
            all_exclusions = (atom1.bond_partners + atom1.angle_partners +
                             atom1.dihedral_partners + atom1.exclusion_partners)
            for atom2 in parm.residues[0].atoms:
                if atom1 is atom2: continue
                in_exclusions_before.append(atom2 in all_exclusions)
        self.assertFalse(all(in_exclusions_before))
        act = PT.addExclusions(parm, ':1', ':1')
        act.execute()
        str(act)
        in_exclusions_after = []
        for atom1 in parm.residues[0].atoms:
            all_exclusions = (atom1.bond_partners + atom1.angle_partners +
                                atom1.dihedral_partners + atom1.exclusion_partners)
            for atom2 in parm.residues[0].atoms:
                if atom1 is atom2: continue
                in_exclusions_after.append(atom2 in all_exclusions)
                if not in_exclusions_after[-1]:
                    print('%s %s not excluded' % (atom1, atom2))
        self.assertTrue(all(in_exclusions_after))
        # Make sure the exclusions are correct when reading in a new version
        # with exceptions defined
        parm.remake_parm()
        parm = AmberParm.from_rawdata(parm)
        in_exclusions_before = []
        for atom1 in parm.residues[0].atoms:
            all_exclusions = (atom1.bond_partners + atom1.angle_partners +
                             atom1.dihedral_partners + atom1.exclusion_partners)
            for atom2 in parm.residues[0].atoms:
                if atom1 is atom2: continue
                in_exclusions_before.append(atom2 in all_exclusions)
        self.assertTrue(all(in_exclusions_before))

    def test_add_delete_dihedral(self):
        """ Test addDihedral and deleteDihedral on AmberParm """
        parm = copy(gasparm)
        act = PT.deleteDihedral(parm, *':ALA@N :ALA@CA :ALA@CB :ALA@HB1'.split())
        n = act.execute()
        str(act)
        str(PT.deleteDihedral(parm, '@NONE', '@NONE', '@NONE', '@NONE'))
        parm.remake_parm()
        self.assertEqual(gasparm.ptr('nphih') + gasparm.ptr('nphia'),
                         parm.ptr('nphih') + parm.ptr('nphia') + n)
        NALA = sum([res.name == 'ALA' for res in parm.residues])
        self.assertEqual(n, NALA)
        act = PT.addDihedral(parm, ':ALA@N', ':ALA@CA', ':ALA@CB', ':ALA@HB1',
                       0.1556, 3, 0, 1.2, 2.0)
        act.execute()
        str(act)
        parm.remake_parm()
        self.assertEqual(gasparm.ptr('nphih') + gasparm.ptr('nphia'),
                         parm.ptr('nphih') + parm.ptr('nphia'))
        PT.addDihedral(parm, ':ALA@N', ':ALA@CA', ':ALA@CB', ':ALA@HB1',
                       0.1556, 1, 0, 1.2, 2.0, type='normal').execute()
        parm.remake_parm()
        self.assertEqual(gasparm.ptr('nphih') + gasparm.ptr('nphia'),
                         parm.ptr('nphih') + parm.ptr('nphia') - n)
        num_dihedrals = 0
        num_ignore_ends = 0
        for atom in parm.atoms:
            if atom.residue.name == 'ALA' and atom.name == 'N':
                for dih in atom.dihedrals:
                    if dih.atom1 is atom:
                        if (dih.atom2.name == 'CA' and dih.atom3.name == 'CB'
                            and dih.atom4.name == 'HB1'):
                            num_dihedrals += 1
                            if dih.ignore_end:
                                num_ignore_ends += 1
                    elif dih.atom4 is atom:
                        if (dih.atom2.name == 'CB' and dih.atom3.name == 'CA' and
                            dih.atom1.name == 'HB1'):
                            num_dihedrals += 1
                            if dih.ignore_end:
                                num_ignore_ends += 1
        self.assertEqual(num_dihedrals, 2*NALA)
        self.assertEqual(num_ignore_ends, 1*NALA)
        # Try adding a new improper
        ntyp = len(parm.dihedral_types)
        for d in parm.dihedrals:
            if d.improper:
                dt = d.type
                break
        else:
            assert False, 'No natural impropers found'
        PT.addDihedral(parm, '@1', '@20', '@30', '@40', dt.phi_k,
            dt.per, dt.phase, dt.scee, dt.scnb, type='improper').execute()
        self.assertEqual(len(parm.dihedral_types), ntyp)
        self.assertIs(parm.dihedrals[-1].type, dt)
        # Error checking
        self.assertRaises(exc.SetParamError,
            PT.addDihedral(parm, '@1-2', '@3', '@4', '@5', 1, 1, 10).execute)
        self.assertRaises(exc.InputError, lambda:
            PT.addDihedral(parm, '@1', '@2', '@3', '@4', 1, 1, 10, type='badtype').execute()
        )
        self.assertRaises(exc.SetParamError, lambda:
            PT.addDihedral(parm, '@1,3', '@2,3', '@4-5', '@6-7', 1, 1, 10).execute())
        self.assertRaises(exc.DeleteDihedralError, lambda:
            PT.deleteDihedral(parm, '@1', '@2-3', '@4', '@5').execute())
        with self.assertWarns(exc.SeriousParmWarning):
            PT.deleteDihedral(parm, '@1,3', '@2-3', '@4-5', '@6-7').execute()
        self.assertEqual(PT.deleteDihedral(parm, '@1', '@25', '@35', '@45').execute(), 0)

    def test_set_bond(self):
        """ Test setBond on AmberParm """
        parm = copy(gasparm)
        PT.setBond(parm, ':ALA@CA', ':ALA@CB', 300.0, 1.5).execute()
        act = PT.printBonds(parm, ':ALA@CA')
        self.assertEqual(str(act), saved.SET_BOND)
        nala = sum([1 for res in parm.residues if res.name == 'ALA'])
        nbon = len(parm.bonds)
        act = PT.setBond(parm, ':ALA@CB', ':ALA@HA', 100.0, 1.5)
        act.execute()
        str(act)
        for atom in parm.atoms:
            if atom.residue.name == 'ALA' and atom.name == 'HA':
                self.assertEqual(len(atom.bonds), 2)
                for atom2 in atom.bond_partners:
                    self.assertIn(atom2.name, ('CA','CB'))
        self.assertEqual(nbon + nala, len(parm.bonds))
        # Add a new bond with an existing bond type and make sure a new type is
        # not added
        ntyp = len(parm.bond_types)
        PT.setBond(parm, '@1', '@20', parm.bond_types[0].k,
                   parm.bond_types[0].req).execute()
        self.assertEqual(ntyp, len(parm.bond_types))
        self.assertIs(parm.bonds[-1].type, parm.bond_types[0])
        # Error checking
        self.assertRaises(exc.SetParamError, lambda:
                PT.setBond(parm, '@1', '@2-3', 300.0, 1.5).execute())

    def test_set_angle(self):
        """ Test setAngle on AmberParm """
        parm = copy(gasparm)
        act = PT.setAngle(parm, ':ALA@CA', ':ALA@CB', ':ALA@HB1', 40, 100)
        act.execute()
        str(act)
        act = PT.printAngles(parm, ':ALA@CB')
        self.assertEqual(str(act), saved.SET_ANGLE)
        nala = sum([1 for res in parm.residues if res.name == 'ALA'])
        nang = len(parm.angles)
        PT.setAngle(parm, ':ALA@HA', ':ALA@CB', ':ALA@HB1', 50, 120).execute()
        self.assertEqual(nang + nala, len(parm.angles))
        # Add a new angle with an existing type and make sure it uses that
        angtyp = parm.angle_types[0]
        ntyp = len(parm.angle_types)
        PT.setAngle(parm, '@1', '@20', '@30', angtyp.k, angtyp.theteq).execute()
        self.assertEqual(len(parm.angle_types), ntyp)
        self.assertIs(parm.angles[-1].type, parm.angle_types[0])
        # Error checking
        self.assertRaises(exc.SetParamError,
                PT.setAngle(parm, '@1', '@2', '@3-4', 50, 120).execute)
        self.assertRaises(exc.SetParamError,
                PT.setAngle(parm, '@1', '@2-3', '@4', 50, 120).execute)

    def test_add_atomic_number(self):
        """ Test addAtomicNumber on AmberParm """
        parm = copy(gasparm)
        self.assertFalse('ATOMIC_NUMBER' in parm.parm_data)
        atomic_numbers = [atom.atomic_number for atom in parm.atoms]
        act = PT.addAtomicNumber(parm)
        act.execute()
        str(act)
        self.assertEqual(parm.parm_data['ATOMIC_NUMBER'], atomic_numbers)
        str(PT.addAtomicNumber(parm)) # Already present

    def test_print_lj_matrix(self):
        """ Test printLJMatrix on AmberParm """
        act = PT.printLJMatrix(gasparm, '@1')
        self.assertEqual(str(act), saved.PRINT_LJMATRIX)
        act = PT.printLJMatrix(gasparm, gasparm[0].nb_idx)
        self.assertEqual(str(act), saved.PRINT_LJMATRIX)

    def test_delete_bond(self):
        """ Test deleteBond on AmberParm """
        parm = copy(gasparm)
        # Pick the bond we plan to delete, pick out every angle and dihedral
        # that contains that bond, and then delete it. Then make sure none of
        # the valence terms that contained that bond remain afterwards. We
        # already have a test to make sure that the __contains__ method works
        # for atoms and bonds.
        for bond in parm.atoms[0].bonds:
            if parm.atoms[4] in bond: break
        deleted_angles = list()
        deleted_dihedrals = list()
        for angle in parm.angles:
            if bond in angle: deleted_angles.append(angle)
        for dihedral in parm.dihedrals:
            if bond in dihedral: deleted_dihedrals.append(dihedral)
        act = PT.deleteBond(parm, '@1', '@5', 'verbose')
        str(act)
        act.execute()
        self.assertTrue(bond not in parm.bonds)
        for angle in deleted_angles:
            self.assertTrue(angle not in parm.angles)
        for dihedral in deleted_dihedrals:
            self.assertTrue(dihedral not in parm.dihedrals)
        # Nothing to do, make sure it doesn't fail, and does nothing
        act = PT.deleteBond(parm, '@1', '@20')
        nbnd = len(parm.bonds)
        str(act)
        act.execute()
        self.assertEqual(nbnd, len(parm.bonds))

    def test_summary(self):
        """ Test summary action on AmberParm """
        parm = AmberParm(self.get_fn('things.parm7'), self.get_fn('things.rst7'))
        act = PT.summary(parm)
        self.assertEqual(str(act), saved.SUMMARY)

    def test_scale(self):
        """ Test scale action on AmberParm """
        parm = copy(gasparm)
        PT.scale(parm, 'DIHEDRAL_FORCE_CONSTANT', 2.0).execute()
        self.assertEqual(
                [2*x for x in gasparm.parm_data['DIHEDRAL_FORCE_CONSTANT']],
                parm.parm_data['DIHEDRAL_FORCE_CONSTANT'])
        PT.scale(parm, 'DIHEDRAL_FORCE_CONSTANT', 0.0).execute()
        for val in parm.parm_data['DIHEDRAL_FORCE_CONSTANT']:
            self.assertEqual(val, 0)

    def test_lmod(self):
        """ Test lmod action on AmberParm """
        parm = copy(gasparm)
        self.assertFalse(all(parm.parm_data['LENNARD_JONES_ACOEF']))
        act = PT.lmod(parm)
        act.execute()
        str(act)
        self.assertTrue(all(parm.parm_data['LENNARD_JONES_ACOEF']))

    def test_prot_state_interpolate(self):
        """ Test changeProtState and interpolate actions on AmberParm """
        self._empty_writes()
        parm = AmberParm(self.get_fn('ash.parm7'))
        origparm = copy(parm)
        origparm.name = origparm.name + '_copy1'
        act = PT.changeProtState(parm, ':ASH', 0)
        act.execute()
        str(act)
        self.assertAlmostEqual(sum(parm.parm_data['CHARGE']), -1)
        self.assertAlmostEqual(sum(origparm.parm_data['CHARGE']), 0)
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.charge, parm.parm_data['CHARGE'][i])
        for i, atom in enumerate(origparm.atoms):
            self.assertEqual(atom.charge, origparm.parm_data['CHARGE'][i])
        # Now set up a ParmList so we can interpolate these topology files
        parms = parmlist.ParmList()
        parms.add_parm(parm)
        parms.add_parm(origparm)
        # First do some error checking
        with self.assertRaises(exc.ArgumentError):
            PT.interpolate(
                parms, -1, 'eleconly', prefix=self.get_fn('test.parm7', written=True)
            ).execute()
        sys.stdout = open(os.devnull, 'w')
        act = PT.interpolate(
            parms, 5, 'eleconly', startnum=2, prefix=self.get_fn('test.parm7', written=True)
        )
        str(act)
        act.execute()
        sys.stdout = sys.__stdout__
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 5)
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.2', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.3', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.4', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.5', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.6', written=True)))
        # Now check them all
        ladder = [origparm]
        ladder.append(AmberParm(self.get_fn('test.parm7.2', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.3', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.4', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.5', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.6', written=True)))
        ladder.append(parm)
        natom = parm.ptr('natom')
        for i in range(1, 6):
            before = ladder[i-1].parm_data['CHARGE']
            after = ladder[i+1].parm_data['CHARGE']
            this = ladder[i].parm_data['CHARGE']
            for j in range(natom):
                if before[j] < after[j]:
                    self.assertTrue(before[j] <= this[j] <= after[j])
                else:
                    self.assertTrue(before[j] >= this[j] >= after[j])
        # Now check that if no atoms are selected, it is handled properly
        parm = pmd.load_file(self.get_fn('ash.parm7'))
        act = PT.changeProtState(parm, ':GLH', 1)
        str(act)
        act.execute()
        # Now check error handling if the selected residue is not protonable
        with self.assertRaises(exc.ChangeStateError):
            PT.changeProtState(parm, ':ACE', 1).execute()
        with self.assertRaises(exc.ChangeStateError):
            PT.changeProtState(parm, ':ASH', 10).execute()
        with self.assertRaises(exc.ChangeStateError):
            PT.changeProtState(parm, ':*', 1).execute()
        with self.assertRaises(exc.ChangeStateError):
            PT.changeProtState(parm, ':ASH@CA', 1).execute()
        with self.assertRaises(exc.ChangeStateError):
            PT.changeProtState(parm, '@13-25', 1).execute()
        with self.assertRaises(exc.NonexistentParm):
            PT.interpolate(load_file(self.get_fn('ash.parm7')), 10, 'eleconly').execute()
        # Make a list of 3 parms and make sure ambiguity causes failure
        act = PT.parm(parm, copy=0)
        act.execute()
        PT.changeProtState(act.parm_list, ':ASH', 1).execute()
        PT.parm(act.parm_list, copy=0).execute()
        self.assertEqual(len(act.parm_list), 3)
        with self.assertRaises(exc.AmbiguousParmError):
            PT.interpolate(act.parm_list, 10, 'eleconly').execute()
        act.parm_list.add_parm(self.get_fn('trx.prmtop'))
        with self.assertRaises(exc.IncompatibleParmsError):
            PT.interpolate(act.parm_list, 10, 'eleconly', parm2=0).execute()
        act.parm_list[0].atoms[0].name = 'FOO'
        with self.assertWarns(exc.SeriousParmWarning):
            PT.interpolate(act.parm_list, 10, 'eleconly', parm=0, parm2=1).execute()

    def test_prot_state_interpolate_2(self):
        """ Test interpolate actions on AmberParm with more parm selection """
        self._empty_writes()
        parm = AmberParm(self.get_fn('ash.parm7'))
        origparm = copy(parm)
        origparm.name = origparm.name + '_copy1'
        act = PT.changeProtState(parm, ':ASH', 0)
        act.execute()
        str(act)
        self.assertAlmostEqual(sum(parm.parm_data['CHARGE']), -1)
        self.assertAlmostEqual(sum(origparm.parm_data['CHARGE']), 0)
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.charge, parm.parm_data['CHARGE'][i])
        for i, atom in enumerate(origparm.atoms):
            self.assertEqual(atom.charge, origparm.parm_data['CHARGE'][i])
        # Now set up a ParmList so we can interpolate these topology files
        parms = parmlist.ParmList()
        parms.add_parm(parm)
        parms.add_parm(origparm)
        parms.add_parm(load_file(self.get_fn('trx.prmtop')))
        sys.stdout = open(os.devnull, 'w')
        PT.interpolate(parms, 5, 'eleconly', parm=1, parm2=0, startnum=2,
                       prefix=self.get_fn('test.parm7', written=True)).execute()
        sys.stdout = sys.__stdout__
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 5)
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.2', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.3', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.4', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.5', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.6', written=True)))
        # Now check them all
        ladder = [origparm]
        ladder.append(AmberParm(self.get_fn('test.parm7.2', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.3', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.4', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.5', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.6', written=True)))
        ladder.append(parm)
        natom = parm.ptr('natom')
        for i in range(1, 6):
            before = ladder[i-1].parm_data['CHARGE']
            after = ladder[i+1].parm_data['CHARGE']
            this = ladder[i].parm_data['CHARGE']
            for j in range(natom):
                if before[j] < after[j]:
                    self.assertTrue(before[j] <= this[j] <= after[j])
                else:
                    self.assertTrue(before[j] >= this[j] >= after[j])

    def test_prot_state_interpolate_3(self):
        """ Test interpolate actions on AmberParm with parm name selection """
        self._empty_writes()
        parm = AmberParm(self.get_fn('ash.parm7'))
        origparm = copy(parm)
        origparm.name = origparm.name + '_copy1'
        act = PT.changeProtState(parm, ':ASH', 0)
        act.execute()
        str(act)
        self.assertAlmostEqual(sum(parm.parm_data['CHARGE']), -1)
        self.assertAlmostEqual(sum(origparm.parm_data['CHARGE']), 0)
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.charge, parm.parm_data['CHARGE'][i])
        for i, atom in enumerate(origparm.atoms):
            self.assertEqual(atom.charge, origparm.parm_data['CHARGE'][i])
        # Now set up a ParmList so we can interpolate these topology files
        parms = parmlist.ParmList()
        parms.add_parm(parm)
        parms.add_parm(origparm)
        parms.add_parm(load_file(self.get_fn('trx.prmtop')))
        sys.stdout = open(os.devnull, 'w')
        PT.interpolate(parms, 5, 'eleconly', parm=1, parm2=self.get_fn('ash.parm7'),
                       startnum=2, prefix=self.get_fn('test.parm7', written=True)).execute()
        sys.stdout = sys.__stdout__
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 5)
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.2', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.3', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.4', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.5', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.6', written=True)))
        # Now check them all
        ladder = [origparm]
        ladder.append(AmberParm(self.get_fn('test.parm7.2', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.3', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.4', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.5', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.6', written=True)))
        ladder.append(parm)
        natom = parm.ptr('natom')
        for i in range(1, 6):
            before = ladder[i-1].parm_data['CHARGE']
            after = ladder[i+1].parm_data['CHARGE']
            this = ladder[i].parm_data['CHARGE']
            for j in range(natom):
                if before[j] < after[j]:
                    self.assertTrue(before[j] <= this[j] <= after[j])
                else:
                    self.assertTrue(before[j] >= this[j] >= after[j])

    def test_add_delete_pdb(self):
        """ Test addPDB and deletePDB actions on AmberParm """
        parm = copy(gasparm)
        PT.addPDB(parm, self.get_fn('trx.pdb'), 'elem', 'allicodes').execute()
        self.assertTrue('RESIDUE_ICODE' in parm.flag_list)
        self.assertTrue('ATOM_ELEMENT' in parm.flag_list)
        self.assertTrue('RESIDUE_NUMBER' in parm.flag_list)
        self.assertTrue('RESIDUE_CHAINID' in parm.flag_list)
        self.assertTrue('ATOM_OCCUPANCY' in parm.flag_list)
        self.assertTrue('ATOM_BFACTOR' in parm.flag_list)
        self.assertTrue(len(parm.parm_data['RESIDUE_ICODE']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['ATOM_ELEMENT']), parm.ptr('natom'))
        self.assertTrue(len(parm.parm_data['RESIDUE_NUMBER']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['RESIDUE_CHAINID']),parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['ATOM_OCCUPANCY']),parm.ptr('natom'))
        self.assertTrue(len(parm.parm_data['ATOM_BFACTOR']), parm.ptr('natom'))
        for i in range(parm.ptr('nres')):
            self.assertEqual(parm.parm_data['RESIDUE_NUMBER'][i], i + 21)
            self.assertEqual(parm.parm_data['RESIDUE_ICODE'][i], '')
            if parm.parm_data['RESIDUE_NUMBER'][i] < 41:
                self.assertEqual(parm.parm_data['RESIDUE_CHAINID'][i], 'A')
            else:
                self.assertEqual(parm.parm_data['RESIDUE_CHAINID'][i], 'B')
        for i, atom in enumerate(parm.atoms):
            atnum = atom.atomic_number
            elem = parm.parm_data['ATOM_ELEMENT'][i].strip()
            self.assertEqual(periodic_table.Element[atnum], elem)
            self.assertEqual(atnum, periodic_table.AtomicNum[elem])
        # Test reading Amber topology file with insertion codes and PDB
        # information stored in it
        parm.write_parm(self.get_fn('addpdb.parm7', written=True))
        nparm = AmberParm(self.get_fn('addpdb.parm7', written=True))
        self.assertEqual(len(nparm.parm_data['RESIDUE_ICODE']),
                         len(nparm.residues))
        nparm = AmberFormat()
        nparm.rdparm(self.get_fn('addpdb.parm7', written=True), slow=True)
        self.assertEqual(len(nparm.parm_data['RESIDUE_ICODE']),
                         len(parm.residues))
        # Test deletePDB
        act = PT.deletePDB(parm)
        act.execute()
        str(act)
        self.assertFalse('RESIDUE_ICODE' in parm.flag_list)
        self.assertFalse('ATOM_ELEMENT' in parm.flag_list)
        self.assertFalse('RESIDUE_NUMBER' in parm.flag_list)
        self.assertFalse('RESIDUE_CHAINID' in parm.flag_list)
        self.assertFalse('ATOM_OCCUPANCY' in parm.flag_list)
        self.assertFalse('ATOM_BFACTOR' in parm.flag_list)
        act = PT.deletePDB(parm)
        act.execute()
        str(act)
        # Add PDB information, but set a couple new names that should be OK.
        # Make sure we don't warn
        for res in parm.residues:
            if res.name == 'ASP':
                res.name = 'AS4'
            elif res.name == 'GLU':
                res.name = 'GL4'
            elif res.name == 'HIS':
                res.name = 'HID'
            elif res.name == 'LYS':
                res.name = 'LYN'
            elif res.name == 'CYS':
                res.name = 'CYM'
        parm.remake_parm()
        PT.addPDB(parm, self.get_fn('trx.pdb'), 'elem', 'allicodes').execute()
        # Make sure addPDB works with nucleic acids, too
        parm2 = AmberParm(self.get_fn('gaucu.parm7'))
        PT.addPDB(parm2, self.get_fn('gaucu.pdb'), 'elem', 'allicodes').execute()
        self.assertTrue('RESIDUE_ICODE' in parm2.flag_list)
        self.assertTrue('ATOM_ELEMENT' in parm2.flag_list)
        self.assertTrue('RESIDUE_NUMBER' in parm2.flag_list)
        self.assertTrue('RESIDUE_CHAINID' in parm2.flag_list)
        self.assertTrue('ATOM_OCCUPANCY' in parm2.flag_list)
        self.assertTrue('ATOM_BFACTOR' in parm2.flag_list)
        self.assertTrue(len(parm2.parm_data['RESIDUE_ICODE']), parm2.ptr('nres'))
        self.assertTrue(len(parm2.parm_data['ATOM_ELEMENT']), parm2.ptr('natom'))
        self.assertTrue(len(parm2.parm_data['RESIDUE_NUMBER']), parm2.ptr('nres'))
        self.assertTrue(len(parm2.parm_data['RESIDUE_CHAINID']),parm2.ptr('nres'))
        self.assertTrue(len(parm2.parm_data['ATOM_OCCUPANCY']),parm2.ptr('natom'))
        self.assertTrue(len(parm2.parm_data['ATOM_BFACTOR']), parm2.ptr('natom'))
        PT.deletePDB(parm2).execute()
        # Now tweak some residue name
        parm2.residues[0].name = 'FOO'
        parm2.remake_parm()
        # Test that AddPDBWarning becomes fatal under 'strict'
        with self.assertRaises(exc.AddPDBWarning):
            PT.addPDB(parm2, self.get_fn('gaucu.pdb'), 'elem', 'strict').execute()
        # Now mix-and-match PDB files that do not have the same number of atoms
        parm2.residues[0].name = 'G5'
        parm2.strip(':5')
        with self.assertRaises(exc.AddPDBError):
            PT.addPDB(parm2, self.get_fn('gaucu.pdb'), 'elem').execute()

    def test_add_pdb2(self):
        """ Test addPDB with atypical numbering and extra residues """
        fname = '4lzt_pdb.parm7'
        parm = load_file(self.get_fn('4lzt.parm7'))
        act = PT.addPDB(parm, self.get_fn('4lzt_NoNO3.pdb'), 'strict')
        act.execute()
        str(act)
        parm.write_parm(self.get_fn(fname, written=True))
        self.assertTrue(
            diff_files(
                self.get_fn(fname, saved=True), self.get_fn(fname, written=True), absolute_error=1e-6
            )
        )
        act = PT.addPDB(parm, self.get_fn('4lzt_NoNO3.pdb'))
        str(act)
        act.execute()

    def test_h_mass_repartition(self):
        """ Test HMassRepartition on AmberParm """
        parm = copy(solvparm)
        PT.HMassRepartition(parm, 2.0).execute()
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                if atom.residue.name == 'WAT':
                    self.assertAlmostEqual(atom.mass, 1.008)
                else:
                    self.assertEqual(atom.mass, 2)
        self.assertEqual(parm.parm_data['MASS'], [a.mass for a in parm.atoms])
        self.assertAlmostEqual(sum(solvparm.parm_data['MASS']), sum(parm.parm_data['MASS']))
        PT.HMassRepartition(parm, 3.0, 'dowater').execute()
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                self.assertEqual(atom.mass, 3.0)
        self.assertAlmostEqual(sum(solvparm.parm_data['MASS']), sum(parm.parm_data['MASS']))
        with self.assertRaises(exc.HMassRepartitionError):
            PT.HMassRepartition(parm, 100.0).execute()

    @unittest.skipUnless(HAS_RDKIT, 'Cannot test without RDKit')
    def test_ring_h_mass_repartition(self):
        """
        Test HMassRepartition on AmberParm
        Phenol is a system with all hydrogens on carbons in a ring
        and one hydrogen on an oxygen not in the ring.
        """
        parm = copy(phenolparm)
        PT.HMassRepartition(parm, 2.0, ring_hmass=1.5).execute()
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                assert len(atom.bond_partners) == 1
                if atom.bond_partners[0].atomic_number == 8:
                    self.assertEqual(atom.mass, 2.0)
                else:
                    self.assertEqual(atom.mass, 1.5)
        self.assertEqual(parm.parm_data['MASS'], [a.mass for a in parm.atoms])
        self.assertAlmostEqual(sum(phenolparm.parm_data['MASS']), sum(parm.parm_data['MASS']))

        PT.HMassRepartition(parm, 2.0).execute()
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                self.assertEqual(atom.mass, 2.0)
        self.assertEqual(parm.parm_data['MASS'], [a.mass for a in parm.atoms])
        self.assertAlmostEqual(sum(phenolparm.parm_data['MASS']), sum(parm.parm_data['MASS']))



    @unittest.skipUnless(has_openmm, 'Cannot test without OpenMM')
    def test_openmm_action(self):
        """ Tests the OpenMM action for AmberParm """
        parm = AmberParm(self.get_fn('ash.parm7'))
        mdin = self.get_fn('mdin', written=True)
        with open(mdin, 'w') as f:
            f.write('''\
Basic MD simulation
 &cntrl
    imin=0, nstlim=10, dt=0.001, ntb=0, igb=5,
    ntwr=2, ntwx=2, ntpr=2, ntwv=-1, ioutfm=1,
    cut=500.0, tempi=100,
 /
''')
        traj = self.get_fn('ash.nc', written=True)
        mdout = self.get_fn('ash.mdout', written=True)
        mdinfo = self.get_fn('ash.mdinfo', written=True)
        restart = self.get_fn('ash.restrt', written=True)
        script = self.get_fn('ash.py', written=True)
        self.assertRaises(exc.SimulationError, lambda:
                PT.OpenMM(parm, '-O', '-i', mdin, '-o', mdout, '-inf', mdinfo,
                          '-r', restart, '-x', traj, '-p', self.get_fn('ash.parm7'),
                          script=script).execute()
        )
        act = PT.OpenMM(parm, '-O', '-i', mdin, '-c', self.get_fn('ash.rst7'), '-o',
                        mdout, '-inf', mdinfo, '-r', restart, '-x', traj,
                        '-p', self.get_fn('ash.parm7'), script=script)
        str(act)
        act.execute()
        self.assertTrue(os.path.exists(script))
        self.assertTrue(pmd.amber.AmberAsciiRestart.id_format(restart))
        self.assertTrue(pmd.amber.NetCDFTraj.id_format(traj))
        # Now check that the attributes are as expected
        if pd is not None:
            df = pd.read_csv(mdout)
            self.assertEqual(df.shape, (5, 6))
            np.testing.assert_allclose(df['Time (ps)'],
                    [0.002, 0.004, 0.006, 0.008, 0.010])
        # Now check that the coordinates have moved
        t = pmd.load_file(traj)
        self.assertEqual(t.coordinates.shape, (5, len(parm.atoms), 3))
        diff = pmd.load_file(self.get_fn('ash.rst7')).coordinates - t.coordinates[4]
        self.assertTrue((np.abs(diff) > 1e-3).any())

        # Now run the python script and make sure it does the same thing
        os.system('cd "%s" && "%s" "%s"' % (self._temporary_directory.name, sys.executable, script))
        if pd is not None:
            df = pd.read_csv(mdout)
            self.assertEqual(df.shape, (5, 6))
            np.testing.assert_allclose(df['Time (ps)'],
                    [0.002, 0.004, 0.006, 0.008, 0.010])
        # Now check that the coordinates have moved
        t = pmd.load_file(traj)
        self.assertEqual(t.coordinates.shape, (5, len(parm.atoms), 3))
        diff = pmd.load_file(self.get_fn('ash.rst7')).coordinates - t.coordinates[4]
        self.assertTrue((np.abs(diff) > 1e-3).any())

    @unittest.skipUnless(has_openmm, 'Cannot test energy function without OMM')
    def test_energy_openmm(self):
        """ Tests the energy action with OpenMM """
        parm = AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7'))
        f = StringIO()
        PT.energy.output = f
        act = PT.energy(parm, 'omm', 'decompose', igb=5, saltcon=0.1)
        str(act)
        act.execute()
        f.seek(0)
        info = f.read()
        ene = float(re.findall(r'TOTAL\s+=\s+([-\d\.]+)', info)[0])
        self.assertLess(abs(ene + 23.01), 0.05)

    @unittest.skipIf(sander is None, 'Cannot test energy function without pysander')
    def test_energy_sander_gb(self):
        """ Tests gb energy action with sander """
        parm = AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7'))
        f = StringIO()
        PT.energy.output = f
        PT.energy(parm, igb=5, saltcon=0.1).execute()
        f.seek(0)
        info = f.read()
        ene = float(re.findall(r'TOTAL\s+=\s+([-\d\.]+)', info)[0])
        self.assertLess(abs(ene + 23.01), 0.05)
        self.assertRaises(exc.SimulationError, lambda: PT.energy(parm, igb=100).execute())
        self.assertRaises(exc.SimulationError, lambda: PT.energy(parm, cutoff=-2.0).execute())
        self.assertRaises(exc.SimulationError, lambda: PT.energy(parm, saltcon=-0.2).execute())
        PT.energy(parm, igb=0).execute()
        parm.coordinates = None
        self.assertRaises(exc.SimulationError, lambda: PT.energy(parm).execute())

    @unittest.skipIf(sander is None, 'Cannot test energy function without pysander')
    def test_energy_sander_explicit_water(self):
        """ Tests energy action with sander """
        parm = AmberParm(self.get_fn('solv2.parm7'), self.get_fn('solv2.rst7'))
        f = StringIO()
        PT.energy.output = f
        PT.energy(parm).execute()
        f.seek(0)
        info = f.read()
        ene = float(re.findall(r'TOTAL\s+=\s+([-\d\.]+)', info)[0])
        self.assertLess(abs(ene + 12785.68), 0.05)
        self.assertRaises(exc.SimulationError, lambda: PT.energy(parm, cutoff=-2.0).execute())

    @unittest.skipIf(sander is None, 'Cannot test amber minimization without pysander')
    def test_minimize_from_action_tools(self):
        """ Tests the minimize action with pysander and scipy """
        # just want to make sure those minimizations runnable
        parm7 = self.get_fn('ala_ala_ala.parm7')
        rst7 = self.get_fn('ala_ala_ala.rst7')
        parm = pmd.load_file(parm7, rst7)
        original_coordinates = parm.coordinates

        for igb in (0, 1, 2, 5, 6, 7, 8):
            arg_list = dict(igb=igb, maxcyc=10)
            parm.coordinates = original_coordinates
            pmd.tools.minimize(parm, **arg_list).execute()

        def test_coordinates_is_None():
            parm.coordinates = None
            pmd.tools.minimize(parm, igb=8, maxcyc=10).execute()
        self.assertRaises(exc.SimulationError, test_coordinates_is_None)

    @unittest.skipIf(True, "OpenMM minimization test is currently failing") # Need to figure out why this is segfaulting in parallel
    @unittest.skipUnless(has_openmm, 'Cannot test minimize function without OpenMM')
    def test_minimize_openmm(self):
        """ Tests the minimize action with OpenMM """
        self._check_emin_omm(AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7')), 0)
        self._check_emin_omm(AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7')), 1)
        self._check_emin_omm(AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7')), 2)
        self._check_emin_omm(AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7')), 5)
        parm = AmberParm(self.get_fn('ash.parm7'), self.get_fn('ash.rst7'))
        PT.changeRadii(parm, 'mbondi3').execute()
        self._check_emin_omm(parm, 7)
        self._check_emin_omm(parm, 8)

    def _check_emin_omm(self, parm, igb):
        if igb in (0, 6):
            IS = None
        elif igb == 1:
            IS = app.HCT
        elif igb == 2:
            IS = app.OBC1
        elif igb == 5:
            IS = app.OBC2
        elif igb == 7:
            IS = app.GBn
        elif igb == 8:
            IS = app.GBn2
        else:
            assert False, 'illegal input'
        system = parm.createSystem(implicitSolvent=IS, implicitSolventSaltConc=0.1*u.molar)
        starting_e = 0
        for _, e in pmd.openmm.energy_decomposition_system(parm, system):
            starting_e += e
        script = self.get_fn('minimize.py', written=True)
        act = PT.minimize(parm, 'omm', script=script, maxcyc=10, igb=igb, saltcon=0.1)
        str(act)
        act.execute()
        end_e = 0
        for _, e in pmd.openmm.energy_decomposition_system(parm, system):
            end_e += e
        self.assertLess(end_e, starting_e)

    def test_out_pdb(self):
        """ Test the outPDB action on AmberParm """
        parm = copy(gasparm)
        PT.loadRestrt(parm, self.get_fn('trx.inpcrd')).execute()
        PT.outPDB(parm, self.get_fn('outPDB1.pdb', written=True)).execute()
        f = PDBFile.parse(self.get_fn('outPDB1.pdb', written=True))
        self.assertEqual(len(f.atoms), len(parm.atoms))
        self.assertEqual(len(f.residues), len(parm.residues))
        for a1, a2 in zip(f.atoms, parm.atoms):
            self.assertEqual(a1.name, a2.name)
            self.assertEqual(a1.atomic_number, a2.atomic_number)
            self.assertAlmostEqual(a1.xx, a2.xx, delta=2e-3)
            self.assertAlmostEqual(a1.xy, a2.xy, delta=2e-3)
            self.assertAlmostEqual(a1.xz, a2.xz, delta=2e-3)
            self.assertEqual(a1.residue.name, a2.residue.name)
            self.assertEqual(a1.residue.idx, a2.residue.idx)

    def test_out_cif(self):
        """ Test the outCIF action on AmberParm """
        parm = copy(gasparm)
        PT.loadRestrt(parm, self.get_fn('trx.inpcrd')).execute()
        PT.outCIF(parm, self.get_fn('outPDB1.cif', written=True)).execute()
        f = CIFFile.parse(self.get_fn('outPDB1.cif', written=True))
        self.assertEqual(len(f.atoms), len(parm.atoms))
        self.assertEqual(len(f.residues), len(parm.residues))
        for a1, a2 in zip(f.atoms, parm.atoms):
            self.assertEqual(a1.name, a2.name)
            self.assertEqual(a1.atomic_number, a2.atomic_number)
            self.assertAlmostEqual(a1.xx, a2.xx, delta=2e-3)
            self.assertAlmostEqual(a1.xy, a2.xy, delta=2e-3)
            self.assertAlmostEqual(a1.xz, a2.xz, delta=2e-3)
            self.assertEqual(a1.residue.name, a2.residue.name)
            self.assertEqual(a1.residue.idx, a2.residue.idx)

    def test_ti_merge1(self):
        """ Tests the tiMerge action on AmberParm with gas-phase parm """
        timask1re = re.compile(r'''timask1 *= *["']([\d,@:]+)["']''', re.I)
        timask2re = re.compile(r'''timask2 *= *["']([\d,@:]+)["']''', re.I)
        scmask1re = re.compile(r'''scmask1 *= *["']([\d,@:]+)["']''', re.I)
        scmask2re = re.compile(r'''scmask2 *= *["']([\d,@:]+)["']''', re.I)
        def get_masks(info):
            return dict(timask1=timask1re.findall(info)[0],
                        timask2=timask2re.findall(info)[0],
                        scmask1=scmask1re.findall(info)[0],
                        scmask2=scmask2re.findall(info)[0])

        output = StringIO()
        PT.tiMerge.output = output
        parm = AmberParm(self.get_fn('ava_aaa.parm7'), self.get_fn('ava_aaa.rst7'))
        act = PT.tiMerge(parm, ':1-5', ':6-10', ':3', ':8')
        str(act)
        # Make sure topology objects are the same
        a = parm[0]
        act.execute()
        self.assertIs(a, parm[0])
        output.seek(0)
        info = output.read()
        masks = get_masks(info)
        # Make sure the timasks are what we expect them to be
        timask1 = AmberMask(parm, masks['timask1'])
        timask2 = AmberMask(parm, masks['timask2'])
        scmask1 = AmberMask(parm, masks['scmask1'])
        scmask2 = AmberMask(parm, masks['scmask2'])
        self.assertEqual(timask1.Selection(),
                         [int(a.residue.idx == 2) for a in parm.atoms])
        self.assertEqual(timask2.Selection(),
                         [int(a.residue.idx == 5) for a in parm.atoms])
        self.assertEqual(scmask1.Selection(),
                         [int(a.residue.idx == 2) for a in parm.atoms])
        self.assertEqual(scmask2.Selection(),
                         [int(a.residue.idx == 5) for a in parm.atoms])
        self.assertEqual(len(parm.residues), 6) # Kept first 5 and mutant ALA

        output.truncate() # Reset the buffer for the next test

        # Check some error processing
        self.assertRaises(exc.TiMergeError, lambda:
                PT.tiMerge(load_file(self.get_fn('ava_aaa.parm7'), self.get_fn('ava_aaa.rst7')),
                           ':1-5', ':6-10', ':8', ':8').execute())
        self.assertRaises(exc.TiMergeError, lambda:
                PT.tiMerge(load_file(self.get_fn('ava_aaa.parm7'), self.get_fn('ava_aaa.rst7')),
                           ':1-5', ':6-10', ':3', ':3').execute())
        self.assertRaises(exc.TiMergeError, lambda:
                PT.tiMerge(load_file(self.get_fn('ava_aaa.parm7'), self.get_fn('ava_aaa.rst7')),
                           ':1-5', ':5-10', ':3', ':8').execute())
        self.assertRaises(exc.TiMergeError,
                PT.tiMerge(load_file(self.get_fn('ava_aaa.parm7')), ':1-5', ':6-10',
                    ':3', ':8').execute)
        parm = load_file(self.get_fn('ava_aaa.parm7'), self.get_fn('ava_aaa.rst7'))
        parm.residues[5][0].xx += 1 # Move away so it's not recognized as same
        self.assertRaises(exc.TiMergeError, lambda:
                PT.tiMerge(parm, ':1-5', ':6-10', ':3', ':8').execute())

    def test_ti_merge2(self):
        """ Tests the tiMerge action on AmberParm with solvated parm """
        parm = AmberParm(self.get_fn('ava_aaa.solv.parm7'), self.get_fn('ava_aaa.solv.rst7'))
        timask1re = re.compile(r'''timask1 *= *["']([\d,@:]+)["']''', re.I)
        timask2re = re.compile(r'''timask2 *= *["']([\d,@:]+)["']''', re.I)
        scmask1re = re.compile(r'''scmask1 *= *["']([\d,@:]+)["']''', re.I)
        scmask2re = re.compile(r'''scmask2 *= *["']([\d,@:]+)["']''', re.I)
        def get_masks(info):
            return dict(timask1=timask1re.findall(info)[0],
                        timask2=timask2re.findall(info)[0],
                        scmask1=scmask1re.findall(info)[0],
                        scmask2=scmask2re.findall(info)[0])

        output = StringIO()
        PT.tiMerge.output = output
        act = PT.tiMerge(parm, ':1-5', ':6-10', ':3', ':8', ':11', ':12')
        str(act)
        # Make sure topology objects are the same
        a = parm[0]
        act.execute()
        self.assertIs(a, parm[0])
        output.seek(0)
        info = output.read()
        masks = get_masks(info)
        # Make sure the timasks are what we expect them to be
        timask1 = AmberMask(parm, masks['timask1'])
        timask2 = AmberMask(parm, masks['timask2'])
        scmask1 = AmberMask(parm, masks['scmask1'])
        scmask2 = AmberMask(parm, masks['scmask2'])
        self.assertEqual(timask1.Selection(),
                         [int(a.residue.idx in (2, 6)) for a in parm.atoms])
        self.assertEqual(timask2.Selection(),
                         [int(a.residue.idx in (5, 7)) for a in parm.atoms])
        self.assertEqual(scmask1.Selection(),
                         [int(a.residue.idx in (2, 6)) for a in parm.atoms])
        self.assertEqual(scmask2.Selection(),
                         [int(a.residue.idx in (5, 7)) for a in parm.atoms])

        output.truncate() # Reset the buffer for the next test
        # Error handling checking
        parm = AmberParm(self.get_fn('ava_aaa.solv.parm7'), self.get_fn('ava_aaa.solv.rst7'))
        self.assertRaises(exc.TiMergeError, lambda:
                PT.tiMerge(parm, ':1-5', ':11', ':3', ':11').execute())

    def test_add12_6_4(self):
        """ Test the add12_6_4 action on AmberParm """
        parm = AmberParm(self.get_fn('Mg_ti1_b.parm7'))
        PT.addLJType(parm, '@14').execute()
        PT.changeLJPair(parm, '@14', ':MG', 3.26, 0.061666).execute()
        act = PT.add12_6_4(parm, ':MG', watermodel='TIP4PEW',
                     polfile=self.get_fn('lj_1264_pol.dat'))
        act.execute()
        str(act)
        parm.write_parm(self.get_fn('Mg_ti1_b_1264.parm7', written=True))
        self.assertTrue(diff_files(self.get_fn('Mg_ti1_b_1264.parm7', written=True),
                                   get_saved_fn('Mg_ti1_b_1264.parm7'))
        )
        # Error handling
        self.assertRaises(exc.LJ12_6_4Error, lambda:
                PT.add12_6_4(parm, ':MG', watermodel='FOO',
                     polfile=self.get_fn('lj_1264_pol.dat')).execute()
        )
        self.assertRaises(exc.LJ12_6_4Error, lambda:
                PT.add12_6_4(parm, ':MG', watermodel='FOO',
                    c4file='BAR').execute()
        )

    def test_add12_6_4_c4file(self):
        """ Test add12_6_4 action on AmberParm specifying c4file """
        from parmed.tools.add1264 import DEFAULT_C4_PARAMS
        fn = self.get_fn('c4file', written=True)
        with open(fn, 'w') as f:
            for items in DEFAULT_C4_PARAMS['TIP4PEW'].items():
                f.write('%s %s\n' % items)
        parm = AmberParm(self.get_fn('Mg_ti1_b.parm7'))
        PT.addLJType(parm, '@14').execute()
        PT.changeLJPair(parm, '@14', ':MG', 3.26, 0.061666).execute()
        act = PT.add12_6_4(parm, ':MG', c4file=fn, polfile=self.get_fn('lj_1264_pol.dat'))
        act.execute()
        str(act)
        parm.write_parm(self.get_fn('Mg_ti1_b_1264.parm7', written=True))
        self.assertTrue(
            diff_files(self.get_fn('Mg_ti1_b_1264.parm7', written=True),
                       get_saved_fn('Mg_ti1_b_1264.parm7'))
        )

    def test_add_12_6_4_2metals(self):
        """ Test the add12_6_4 action on AmberParm with 2+ metals """
        parm1 = AmberParm(self.get_fn('mg_na_cl.parm7'))
        parm2 = AmberParm(self.get_fn('na_cl_mg.parm7'))
        PT.add12_6_4(parm1, ':MG,NA,CL', polfile=self.get_fn('lj_1264_pol.dat')).execute()
        PT.add12_6_4(parm2, ':MG,NA,CL', watermodel='TIP3P', polfile=self.get_fn('lj_1264_pol.dat')).execute()
        self.assertEqual(str(PT.printLJMatrix(parm1, ':MG')), saved.PRINTLJMATRIX_MGNACL)
        self.assertEqual(str(PT.printLJMatrix(parm2, ':MG')), saved.PRINTLJMATRIX_NACLMG)

    @unittest.skipIf(PYPY, 'NetCDF support does not work on PYPY yet')
    def test_write_coordinates(self):
        """ Test writeCoordinates method """
        parm = copy(gasparm)
        PT.loadCoordinates(parm, self.get_fn('trx.inpcrd')).execute()
        basefn = self.get_fn('test', written=True)
        # NetCDF trajectory
        act = PT.writeCoordinates(parm, basefn + '.nc')
        act.execute()
        str(act) # Make sure it doesn't fail
        self.assertTrue(pmd.amber.NetCDFTraj.id_format(basefn + '.nc'))
        PT.writeCoordinates(parm, basefn + '_nc', 'netcdftraj').execute()
        self.assertTrue(pmd.amber.NetCDFTraj.id_format(basefn + '_nc'))
        # NetCDF restart
        PT.writeCoordinates(parm, basefn + '.ncrst').execute()
        self.assertTrue(pmd.amber.NetCDFRestart.id_format(basefn + '.ncrst'))
        PT.writeCoordinates(parm, basefn + '_ncrst', 'netcdf').execute()
        self.assertTrue(pmd.amber.NetCDFRestart.id_format(basefn + '_ncrst'))
        # PDB
        PT.writeCoordinates(parm, basefn + '.pdb').execute()
        self.assertTrue(pmd.formats.PDBFile.id_format(basefn + '.pdb'))
        PT.writeCoordinates(parm, basefn + '_pdb', 'pdb').execute()
        self.assertTrue(pmd.formats.PDBFile.id_format(basefn + '_pdb'))
        # CIF
        PT.writeCoordinates(parm, basefn + '.cif').execute()
        self.assertTrue(pmd.formats.CIFFile.id_format(basefn + '.cif'))
        PT.writeCoordinates(parm, basefn + '_cif', 'CIF').execute()
        self.assertTrue(pmd.formats.CIFFile.id_format(basefn + '_cif'))
        # ASCII restart
        PT.writeCoordinates(parm, basefn + '.rst7').execute()
        self.assertTrue(pmd.amber.AmberAsciiRestart.id_format(basefn + '.rst7'))
        PT.writeCoordinates(parm, basefn + '_rst7', 'restart').execute()
        self.assertTrue(pmd.amber.AmberAsciiRestart.id_format(basefn + '_rst7'))
        # ASCII trajectory
        PT.writeCoordinates(parm, basefn + '.mdcrd').execute()
        self.assertTrue(pmd.amber.AmberMdcrd.id_format(basefn + '.mdcrd'))
        PT.writeCoordinates(parm, basefn + '_mdcrd', 'mdcrd').execute()
        self.assertTrue(pmd.amber.AmberMdcrd.id_format(basefn + '_mdcrd'))
        # Mol2 file
        PT.writeCoordinates(parm, basefn + '.mol2').execute()
        self.assertTrue(pmd.formats.Mol2File.id_format(basefn + '.mol2'))
        PT.writeCoordinates(parm, basefn + '_mol2', 'mol2').execute()
        self.assertTrue(pmd.formats.Mol2File.id_format(basefn + '_mol2'))
        # ASCII restart by default
        PT.writeCoordinates(parm, basefn + '.noext').execute()
        self.assertTrue(pmd.amber.AmberAsciiRestart.id_format(basefn + '.noext'))
        self.assertRaises(exc.InputError, lambda:
                PT.writeCoordinates(parm, basefn, 'noformat').execute())
        # Make sure overwriting is correctly handled
        self.assertRaises(exc.FileExists, act.execute)

    def test_check_validity(self):
        """ Tests the checkValidity test """
        act = PT.checkValidity(gasparm)
        str(act)
        act.execute()

    def test_check_validity_solvated_parm(self):
        """ Tests the checkValidity test for a solvated topology """
        act = PT.checkValidity(solvparm)
        str(act)
        act.execute()

class TestChamberParmActions(FileIOTestCase, TestCaseRelative):
    """ Tests actions on Amber prmtop files """

    def test_parmout_outparm_load_restrt(self):
        """ Test parmout, outparm, and loadCoordinates actions for ChamberParm """
        self._empty_writes()
        parm = copy(gascham)
        act = PT.loadCoordinates(parm, self.get_fn('ala_ala_ala.rst7'))
        act.execute()
        str(act) # Make sure it doesn't fail
        for atom in parm.atoms:
            self.assertTrue(hasattr(atom, 'xx'))
            self.assertTrue(hasattr(atom, 'xy'))
            self.assertTrue(hasattr(atom, 'xz'))
        PT.parmout(parm, self.get_fn('test.parm7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 1)
        self.assertTrue(diff_files(get_saved_fn('ala_ala_ala.parm7'),
                                   self.get_fn('test.parm7', written=True),
                                   absolute_error=1e-6))
        self._empty_writes()
        PT.parmout(parm, self.get_fn('test.parm7', written=True),
                         self.get_fn('test.rst7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 2)
        self.assertTrue(diff_files(get_saved_fn('ala_ala_ala.parm7'),
                                   self.get_fn('test.parm7', written=True),
                                   absolute_error=1e-6))
        self.assertTrue(diff_files(self.get_fn('ala_ala_ala.rst7'),
                                   self.get_fn('test.rst7', written=True),
                                   absolute_error=0.0001))
        self._empty_writes()
        PT.outparm(parm, self.get_fn('test.parm7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 1)
        self.assertTrue(diff_files(get_saved_fn('ala_ala_ala.parm7'),
                                   self.get_fn('test.parm7', written=True),
                                   absolute_error=1e-6))
        self._empty_writes()
        PT.outparm(parm, self.get_fn('test.parm7', written=True),
                         self.get_fn('test.rst7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 2)
        self.assertTrue(diff_files(get_saved_fn('ala_ala_ala.parm7'),
                                   self.get_fn('test.parm7', written=True),
                                   absolute_error=1e-6))
        self.assertTrue(diff_files(self.get_fn('ala_ala_ala.rst7'),
                                   self.get_fn('test.rst7', written=True),
                                   absolute_error=0.0001))
        # Check loadCoordinates error handling
        self.assertRaises(exc.ParmError, lambda:
                PT.loadCoordinates(parm, self.get_fn('trx.prmtop')).execute())

    def test_write_frcmod(self):
        """ Check that writeFrcmod fails for ChamberParm """
        parm = gascham
        self.assertRaises(exc.ParmError, lambda:
                PT.writeFrcmod(parm, self.get_fn('x', written=True)).execute())

    def test_write_off_load_restrt(self):
        """ Check that writeOFF fails for ChamberParm """
        parm = copy(gascham)
        PT.loadRestrt(parm, self.get_fn('ala_ala_ala.rst7')).execute()
        self.assertRaises(exc.ParmError, lambda:
                PT.writeOFF(parm, self.get_fn('test.off', written=True)).execute())

    def test_ti_merge(self):
        """ Check that tiMerge joins CHARMM-specific terms in ChamberParm """
        timask1re = re.compile(r'''timask1 *= *["']([\d,@:]+)["']''', re.I)
        timask2re = re.compile(r'''timask2 *= *["']([\d,@:]+)["']''', re.I)
        scmask1re = re.compile(r'''scmask1 *= *["']([\d,@:]+)["']''', re.I)
        scmask2re = re.compile(r'''scmask2 *= *["']([\d,@:]+)["']''', re.I)
        def get_masks(info):
            return dict(timask1=timask1re.findall(info)[0],
                        timask2=timask2re.findall(info)[0],
                        scmask1=scmask1re.findall(info)[0],
                        scmask2=scmask2re.findall(info)[0])
        # First convert from CHARMM-GUI PSF file
        a = PT.chamber(parmlist.ParmList(), '-psf', self.get_fn('ava_aaa.psf'),
                '-top', self.get_fn('top_all36_prot.rtf'), '-param',
                self.get_fn('par_all36_prot.prm'), '-crd', self.get_fn('ava_full.pdb'))
        a.execute()
        PT.tiMerge(a.parm, ':1-3', ':4-6', ':2', ':5').execute()
        self.assertEqual(len(a.parm.residues), 4) # Removed 2 redundant res.
        # Error checking
        parm = copy(gascham)
        PT.loadRestrt(parm, self.get_fn('ala_ala_ala.rst7')).execute()
        self.assertRaises(exc.ParmError, lambda:
                PT.tiMerge(parm, ':1-3', ':4-6', ':2', ':5').execute())

    def test_change_radii(self):
        """ Test changeRadii for ChamberParm """
        parm = copy(gascham)
        PT.changeRadii(parm, 'amber6').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0],
                         'amber6 modified Bondi radii (amber6)')
        for i, atom in enumerate(parm.atoms):
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            self.assertEqual(parm.parm_data['RADII'][i], radii)
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.bond_partners[0].atomic_number == 6:
                    self.assertEqual(radii, 1.3)
                elif atom.bond_partners[0].atomic_number in (8, 16):
                    self.assertEqual(radii, 0.8)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        PT.changeRadii(parm, 'bondi').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0], 'Bondi radii (bondi)')
        for atom in parm.atoms:
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        PT.changeRadii(parm, 'mbondi').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0], 'modified Bondi radii (mbondi)')
        for atom in parm.atoms:
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.bond_partners[0].atomic_number in (6, 7):
                    self.assertEqual(radii, 1.3)
                elif atom.bond_partners[0].atomic_number in (8, 16):
                    self.assertEqual(radii, 0.8)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        PT.changeRadii(parm, 'mbondi2').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0], 'H(N)-modified Bondi radii (mbondi2)')
        for atom in parm.atoms:
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8 or atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.bond_partners[0].atomic_number == 7:
                    self.assertEqual(radii, 1.3)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)

        PT.changeRadii(parm, 'mbondi3').execute()
        self.assertEqual(parm.parm_data['RADIUS_SET'][0], 'ArgH and AspGluO modified Bondi2 radii (mbondi3)')
        for i, atom in enumerate(parm.atoms):
            radii, atomic_number = atom.solvent_radius, atom.atomic_number
            if atomic_number == 6:
                self.assertEqual(radii, 1.7)
            elif atomic_number == 7:
                self.assertEqual(radii, 1.55)
            elif atomic_number == 8:
                if atom.residue.name in ('ASP,GLU') and (atom.name.startswith('OD') or atom.name.startswith('OE')):
                    self.assertEqual(radii, 1.4)
                elif atom.name == 'OXT' or (i < parm.ptr('natom')-1 and parm.atoms[i+1].name == 'OXT'):
                    self.assertEqual(radii, 1.4)
                else:
                    self.assertEqual(radii, 1.5)
            elif atomic_number == 9:
                self.assertEqual(radii, 1.5)
            elif atomic_number == 14:
                self.assertEqual(radii, 2.1)
            elif atomic_number == 15:
                self.assertEqual(radii, 1.85)
            elif atomic_number == 16:
                self.assertEqual(radii, 1.8)
            elif atomic_number == 1:
                if atom.residue.name == 'ARG' and atom.name[:2] in ('HH', 'HE'):
                    self.assertEqual(radii, 1.17)
                elif atom.bond_partners[0].atomic_number == 7:
                    self.assertEqual(radii, 1.3)
                else:
                    self.assertEqual(radii, 1.2)
            else:
                self.assertEqual(radii, 1.5)
        # Now test bad input
        with self.assertRaises(exc.ChangeRadiiError):
            PT.changeRadii(parm, 'mbondi6').execute()

    def test_change_lj_pair(self):
        """ Test changeLJPair for ChamberParm """
        parm = copy(gascham)
        PT.changeLJPair(parm, '@%NH3', '@%HC', 1.0, 1.0).execute()
        # Figure out what type numbers each atom type belongs to
        ntype = htype = 0
        for atom in parm.atoms:
            if atom.type == 'NH3':
                ntype = atom.nb_idx
            elif atom.type == 'HC':
                htype = atom.nb_idx
        # Make sure the A and B coefficient matrices are what I expect them to
        # be
        indexes = sorted([ntype, htype])
        acoef = parm.parm_data['LENNARD_JONES_ACOEF']
        bcoef = parm.parm_data['LENNARD_JONES_BCOEF']
        refa = gascham.parm_data['LENNARD_JONES_ACOEF']
        refb = gascham.parm_data['LENNARD_JONES_BCOEF']
        ntypes = parm.ptr('ntypes')
        for i in range(ntypes):
            for j in range(i, ntypes):
                idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*i+j]
                if [i+1, j+1] == indexes:
                    self.assertEqual(acoef[idx-1], 1.0)
                    self.assertEqual(bcoef[idx-1], 2.0)
                else:
                    self.assertEqual(acoef[idx-1], refa[idx-1])
                    self.assertEqual(bcoef[idx-1], refb[idx-1])

    def test_change_lj_14_pair(self):
        """ Test changeLJ14Pair for ChamberParm """
        parm = copy(gascham)
        act = PT.changeLJ14Pair(parm, '@%NH3', '@%HC', 1.0, 1.0)
        act.execute()
        str(act)
        # Figure out what type numbers each atom type belongs to
        ntype = htype = 0
        for atom in parm.atoms:
            if atom.type == 'NH3':
                ntype = atom.nb_idx
            elif atom.type == 'HC':
                htype = atom.nb_idx
        # Make sure the A and B coefficient matrices are what I expect them to
        # be
        indexes = sorted([ntype, htype])
        acoef = parm.parm_data['LENNARD_JONES_14_ACOEF']
        bcoef = parm.parm_data['LENNARD_JONES_14_BCOEF']
        refa = gascham.parm_data['LENNARD_JONES_14_ACOEF']
        refb = gascham.parm_data['LENNARD_JONES_14_BCOEF']
        ntypes = parm.ptr('ntypes')
        for i in range(ntypes):
            for j in range(i, ntypes):
                idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*i+j]
                if [i+1, j+1] == indexes:
                    self.assertEqual(acoef[idx-1], 1.0)
                    self.assertEqual(bcoef[idx-1], 2.0)
                else:
                    self.assertEqual(acoef[idx-1], refa[idx-1])
                    self.assertEqual(bcoef[idx-1], refb[idx-1])
        # Check handling with no atoms
        PT.changeLJ14Pair(parm, '@NONE', '@%H', 1.0, 1.0).execute()
        # Make sure nothing changed
        np.testing.assert_equal(acoef, parm.parm_data['LENNARD_JONES_14_ACOEF'])
        np.testing.assert_equal(bcoef, parm.parm_data['LENNARD_JONES_14_BCOEF'])
        # Check error handling
        self.assertRaises(exc.ChangeLJPairError, lambda:
                PT.changeLJ14Pair(parm, ':*', '@%H', 1.0, 1.0).execute())
        self.assertRaises(exc.ChangeLJPairError, lambda:
                PT.changeLJ14Pair(parm, '@%H', ':*', 1.0, 1.0).execute())

    def test_change(self):
        """ Test change on ChamberParm with all properties """
        parm = copy(gascham)
        PT.change(parm, 'CHARGE', ':ALA', 0, 'quiet').execute()
        for flag in parm.parm_data:
            if flag != 'CHARGE':
                self.assertEqual(parm.parm_data[flag], gascham.parm_data[flag])
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['CHARGE'][i], atom.charge)
            if atom.residue.name == 'ALA':
                self.assertEqual(atom.charge, 0)
            else:
                self.assertEqual(atom.charge, gascham.parm_data['CHARGE'][i])
        PT.change(parm, 'MASS', ':1', 10.0).execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['MASS'][i], atom.mass)
            if atom.residue.idx == 0:
                self.assertEqual(atom.mass, 10.0)
            else:
                self.assertEqual(atom.mass, gascham.parm_data['MASS'][i])
            if atom.residue.name == 'ALA':
                self.assertEqual(atom.charge, 0.0)
            else:
                self.assertEqual(atom.charge, gascham.parm_data['CHARGE'][i])
        PT.change(parm, 'ATOM_NAME', ':ALA@C', 'JMS').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['ATOM_NAME'][i], atom.name)
            if atom.residue.name == 'ALA' and \
                        gascham.parm_data['ATOM_NAME'][i] == 'C':
                self.assertEqual(atom.name, 'JMS')
            else:
                self.assertEqual(atom.name, gascham.parm_data['ATOM_NAME'][i])
        PT.change(parm, 'AMBER_ATOM_TYPE', ':ALA@%NH3', 'RJLS').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['AMBER_ATOM_TYPE'][i], atom.type)
            if atom.residue.name == 'ALA' and \
                        gascham.parm_data['AMBER_ATOM_TYPE'][i] == 'NH3':
                self.assertEqual(atom.type, 'RJLS')
            else:
                self.assertEqual(atom.type,
                                 gascham.parm_data['AMBER_ATOM_TYPE'][i])
        PT.change(parm, 'ATOM_TYPE_INDEX', '@1', 4).execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.nb_idx, parm.parm_data['ATOM_TYPE_INDEX'][i])
            if i == 0:
                self.assertEqual(atom.nb_idx, 4)
            else:
                self.assertEqual(atom.nb_idx,
                                 gascham.parm_data['ATOM_TYPE_INDEX'][i])
        PT.change(parm, 'RADII', ':1-2', 2.0, 'quiet').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.solvent_radius, parm.parm_data['RADII'][i])
            if atom.residue.idx < 2:
                self.assertEqual(atom.solvent_radius, 2.0)
            else:
                self.assertEqual(atom.solvent_radius, gascham.parm_data['RADII'][i])
        PT.change(parm, 'SCREEN', '*', 0.0).execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.screen, parm.parm_data['SCREEN'][i])
            self.assertEqual(atom.screen, 0.0)
        # Check bad input
        self.assertRaises(exc.ParmedChangeError, lambda:
                          PT.change(parm, 'RESIDUE_LABEL', ':*', 'NaN'))

    def test_print_info(self):
        """ Test printInfo on ChamberParm for all FLAGs """
        for flag in gascham.parm_data:
            if flag == 'FORCE_FIELD_TYPE': continue
            if flag == 'RADIUS_SET': continue
            act = PT.printInfo(gascham, flag)
            vals = []
            for line in str(act).split('\n'):
                vals += line.split()
            self.assertEqual(len(vals), len(gascham.parm_data[flag]))
            try:
                datatype = type(gascham.parm_data[flag][0])
            except IndexError:
                continue
            for i, j in zip(vals, gascham.parm_data[flag]):
                # printInfo prints to 5 places for floats.
                if datatype is float:
                    self.assertAlmostEqual(datatype(i), j, places=4)
                else:
                    self.assertEqual(datatype(i), j)
        # Check this on non-AmberParm Structure classes
        self.assertRaises(exc.ParmError, lambda:
                PT.printInfo(load_file(self.get_fn('2koc.pdb'))))

    def test_add_change_lj_type(self):
        """ Test addLJType and changeLJSingleType on ChamberParm """
        parm = copy(gascham)
        act = PT.addLJType(parm, '@1')
        act.execute()
        str(act)
        self.assertEqual(parm.ptr('ntypes'), gascham.ptr('ntypes') + 1)
        self.assertEqual(parm.atoms[0].nb_idx, parm.ptr('ntypes'))
        ntypes = parm.ptr('ntypes')
        ntypes2 = ntypes - 1
        orig_type = gascham.atoms[0].nb_idx - 1
        for i in range(ntypes):
            idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*i+ntypes-1]
            if i == ntypes - 1:
                idx2 = gascham.parm_data['NONBONDED_PARM_INDEX'][
                                                ntypes2*orig_type+orig_type]
            else:
                ii, jj = sorted([orig_type, i])
                idx2 = gascham.parm_data['NONBONDED_PARM_INDEX'][ntypes2*ii+jj]
            self.assertRelativeEqual(
                            parm.parm_data['LENNARD_JONES_ACOEF'][idx-1],
                            gascham.parm_data['LENNARD_JONES_ACOEF'][idx2-1],
                            places=7)
            self.assertRelativeEqual(
                            parm.parm_data['LENNARD_JONES_BCOEF'][idx-1],
                            gascham.parm_data['LENNARD_JONES_BCOEF'][idx2-1],
                            places=7)
        # Ensure that the rest of the values are unchanged (exactly equal)
        for i in range(ntypes2):
            for j in range(ntypes2):
                idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*i+j]
                idx2 = gascham.parm_data['NONBONDED_PARM_INDEX'][ntypes2*i+j]
                self.assertEqual(
                            parm.parm_data['LENNARD_JONES_ACOEF'][idx-1],
                            gascham.parm_data['LENNARD_JONES_ACOEF'][idx2-1])
                self.assertEqual(
                            parm.parm_data['LENNARD_JONES_BCOEF'][idx-1],
                            gascham.parm_data['LENNARD_JONES_BCOEF'][idx2-1])
        # Now supply keywords
        parm2 = copy(gascham)
        act = PT.addLJType(parm2, '@1', radius=1.0, epsilon=1.0)
        act.execute()
        str(act)
        act = PT.changeLJSingleType(parm, '@1', 1.0, 1.0)
        act.execute()
        str(act)
        for x, y in zip(parm.parm_data['LENNARD_JONES_ACOEF'],
                        parm2.parm_data['LENNARD_JONES_ACOEF']):
            self.assertRelativeEqual(x, y)
        for x, y in zip(parm.parm_data['LENNARD_JONES_BCOEF'],
                        parm2.parm_data['LENNARD_JONES_BCOEF']):
            self.assertRelativeEqual(x, y)
        # Now use addLJType to hack a way to turn off LJ interactions
        PT.addLJType(parm, '*', radius=0.0, epsilon=0.0).execute()
        ntypes = parm.ptr('ntypes')
        for atom in parm.atoms:
            self.assertEqual(atom.nb_idx, ntypes)
        idx = parm.parm_data['NONBONDED_PARM_INDEX'][ntypes*(ntypes-1)+ntypes-1]
        self.assertEqual(parm.parm_data['LENNARD_JONES_ACOEF'][idx-1], 0.0)
        self.assertEqual(parm.parm_data['LENNARD_JONES_BCOEF'][idx-1], 0.0)
        # Make sure an empty selection still works
        act = PT.changeLJSingleType(parm, '@NOATOM', 1, 2)
        act.execute()
        str(act)
        # Turn the first atom into a new type, then make sure changeLJSingleType
        # fails if multiple atom types are selected
        PT.addLJType(parm, '@1', radius=1, epsilon=0.5).execute()
        self.assertRaises(exc.LJ_TypeError, PT.changeLJSingleType(parm, '@1-2',
                                        1, 2).execute)

    def test_print_lj_types(self):
        """ Test printLJTypes for ChamberParm """
        # Simple test
        act = PT.printLJTypes(gascham, '@1')
        for line in str(act).split('\n'):
            if not line.startswith('ATOM'):
                continue
            words = line.split()
            self.assertTrue(words[2].startswith('N'))
            self.assertTrue(words[3].startswith('N'))
            self.assertEqual(words[7], '1')

    def test_scee_scnb(self):
        """ Test scee and scnb for ChamberParm """
        parm = copy(gascham)
        PT.scee(parm, 10).execute()
        PT.scnb(parm, 10).execute()
        for dih in parm.dihedrals:
            self.assertEqual(dih.type.scee, 10)
            self.assertEqual(dih.type.scnb, 10)
        for x, y in zip(parm.parm_data['SCEE_SCALE_FACTOR'],
                        parm.parm_data['SCNB_SCALE_FACTOR']):
            self.assertEqual(x, 10)
            self.assertEqual(y, 10)

    def test_print_details(self):
        """ Test printDetails for ChamberParm """
        act = PT.printDetails(gascham, '@1')
        self.assertEqual(str(act), saved.PRINT_DETAILSC)

    def test_print_flags(self):
        """ Test printFlags for ChamberParm """
        act = PT.printFlags(gascham)
        printed_flags = set()
        for line in str(act).split('\n'):
            if line.startswith('%FLAG'):
                printed_flags.add(line.split()[1])
        self.assertEqual(printed_flags, set(gascham.parm_data.keys()))

    def test_print_pointers(self):
        """ Test printPointers for ChamberParm """
        act = PT.printPointers(gascham)
        printed_pointers = set(['NEXT', 'NIMPRTYPES', 'NUBTYPES', 'CMAP',
                                'CMAP_TYPES', 'NIMPHI', 'NUB'])
        for line in str(act).split('\n'):
            try:
                pointer = line.split()[0]
                value = int(line[line.rfind('=')+1:].strip())
            except (IndexError, ValueError):
                continue
            self.assertEqual(gascham.ptr(pointer), value)
            printed_pointers.add(pointer)
        self.assertEqual(printed_pointers, set(gascham.pointers.keys()))

    def test_print_bonds(self):
        """ Test printBonds for ChamberParm """
        act = PT.printBonds(gascham, '@1')
        self.assertEqual(str(act), saved.PRINT_BONDSC)

    def test_print_angles(self):
        """ Test printAngles for ChamberParm """
        act = PT.printAngles(gascham, '@1')
        self.assertEqual(str(act), saved.PRINT_ANGLESC)

    def test_print_dihedrals(self):
        """ Test printDihedrals for ChamberParm """
        act = PT.printDihedrals(gascham, '@1')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALSC)

    def test_set_molecules(self):
        """ Test setMolecules for ChamberParm """
        parm = copy(solvchamber)
        atoms = [atom for atom in parm.atoms] # shallow copy!
        self.assertTrue(all([x is y for x,y in zip(parm.atoms,atoms)]))
        self.assertEqual(parm.ptr('IPTRES'), 3)
        self.assertEqual(parm.ptr('NSPM'), 942)
        self.assertEqual(parm.ptr('NSPSOL'), 2)
        # To keep the output clean
        PT.setMolecules(parm).execute()
        self.assertTrue(all([x is y for x,y in zip(parm.atoms, atoms)]))
        # Now check that setMolecules can apply another time.
        PT.setMolecules(parm, solute_ions=False).execute()

    def test_net_charge(self):
        """ Test netCharge for ChamberParm """
        act = PT.netCharge(gascham)
        chg = act.execute() # check this part of the API
        self.assertEqual(str(act), 'The net charge of :* is %.4f' % chg)
        self.assertAlmostEqual(chg, 0.0, places=6)
        chg = PT.netCharge(gascham, ':1').execute()
        self.assertAlmostEqual(chg, 1.0, places=6)
        chg = PT.netCharge(gascham, ':3').execute()
        self.assertAlmostEqual(chg, -1.0, places=6)

    def test_strip(self):
        """ Test strip action for ChamberParm """
        parm = copy(gascham)
        act = PT.strip(parm, ':1')
        str(act)
        act.execute()
        self.assertEqual(parm.ptr('natom'), 21)
        self.assertEqual(len(parm.atoms), 21)
        # Good enough for here. The strip action is repeatedly tested in the
        # core Amber test suite as part of the MM/PBSA tests via ante-MMPBSA.py
        # and that part also tests that the energies come out correct as well

    def test_define_solvent(self):
        """ Test defineSolvent for ChamberParm """
        import parmed.residue as residue
        PT.defineSolvent(gascham, 'WAT,HOH,Na+,Cl-').execute()
        self.assertEqual(residue.SOLVENT_NAMES, 'WAT HOH Na+ Cl-'.split())
        PT.defineSolvent(gascham, 'WAT,HOH').execute()
        self.assertEqual(residue.SOLVENT_NAMES, 'WAT HOH'.split())

    def test_add_exclusions(self):
        """ Test addExclusions for ChamberParm """
        parm = copy(gascham)
        in_exclusions_before = []
        for atom1 in parm.residues[0].atoms:
            all_exclusions = (atom1.bond_partners + atom1.angle_partners +
                             atom1.dihedral_partners + atom1.exclusion_partners)
            for atom2 in parm.residues[0].atoms:
                if atom1 is atom2: continue
                in_exclusions_before.append(atom2 in all_exclusions)
        self.assertFalse(all(in_exclusions_before))
        PT.addExclusions(parm, ':1', ':1').execute()
        in_exclusions_after = []
        for atom1 in parm.residues[0].atoms:
            all_exclusions = (atom1.bond_partners + atom1.angle_partners +
                             atom1.dihedral_partners + atom1.exclusion_partners)
            for atom2 in parm.residues[0].atoms:
                if atom1 is atom2: continue
                in_exclusions_after.append(atom2 in all_exclusions)
                if not in_exclusions_after[-1]:
                    print('%s %s not excluded' % (atom1, atom2))
        self.assertTrue(all(in_exclusions_after))

    def test_add_delete_dihedral(self):
        """ Test the addDihedral and deleteDihedral actions for ChamberParm """
        parm = copy(gascham)
        n = PT.deleteDihedral(parm, *':ALA@N :ALA@CA :ALA@CB :ALA@HB1'.split()).execute()
        parm.remake_parm()
        self.assertEqual(gascham.ptr('nphih') + gascham.ptr('nphia'),
                         parm.ptr('nphih') + parm.ptr('nphia') + n)
        NALA = sum([res.name == 'ALA' for res in parm.residues])
        self.assertEqual(n, NALA)
        PT.addDihedral(parm, ':ALA@N', ':ALA@CA', ':ALA@CB', ':ALA@HB1',
                       0.1556, 3, 0, 1.2, 2.0).execute()
        parm.remake_parm()
        self.assertEqual(gascham.ptr('nphih') + gascham.ptr('nphia'),
                         parm.ptr('nphih') + parm.ptr('nphia'))
        PT.addDihedral(parm, ':ALA@N', ':ALA@CA', ':ALA@CB', ':ALA@HB1',
                       0.1556, 1, 0, 1.2, 2.0, type='normal').execute()
        parm.remake_parm()
        self.assertEqual(gascham.ptr('nphih') + gascham.ptr('nphia'),
                         parm.ptr('nphih') + parm.ptr('nphia') - n)
        num_dihedrals = 0
        num_ignore_ends = 0
        for atom in parm.atoms:
            if atom.residue.name == 'ALA' and atom.name == 'N':
                for dih in atom.dihedrals:
                    if dih.atom1 is atom:
                        if (dih.atom2.name == 'CA' and dih.atom3.name == 'CB'
                            and dih.atom4.name == 'HB1'):
                            num_dihedrals += 1
                            if dih.ignore_end:
                                num_ignore_ends += 1
                    elif dih.atom4 is atom:
                        if (dih.atom2.name == 'CB' and dih.atom3.name == 'CA'
                            and dih.atom1.name == 'HB1'):
                            num_dihedrals += 1
                            if dih.ignore_end:
                                num_ignore_ends += 1
        self.assertEqual(num_dihedrals, 2*NALA)
        self.assertEqual(num_ignore_ends, 1*NALA)

    def test_set_bond(self):
        """ Test setBond for ChamberParm """
        parm = copy(gascham)
        PT.setBond(parm, ':ALA@CA', ':ALA@CB', 300.0, 1.5).execute()
        act = PT.printBonds(parm, ':ALA@CA')
        self.assertEqual(str(act), saved.SET_BONDC)

    def test_set_angle(self):
        """ Test setAngle for ChamberParm """
        parm = copy(gascham)
        PT.setAngle(parm, ':ALA@CA', ':ALA@CB', ':ALA@HB1', 40, 100).execute()
        act = PT.printAngles(parm, ':ALA@CB')
        self.assertEqual(str(act), saved.SET_ANGLEC)

    def test_add_atomic_number(self):
        """ Test addAtomicNumber for ChamberParm """
        parm = copy(gascham)
        self.assertFalse('ATOMIC_NUMBER' in parm.parm_data)
        atomic_numbers = [atom.atomic_number for atom in parm.atoms]
        PT.addAtomicNumber(parm).execute()
        self.assertEqual(parm.parm_data['ATOMIC_NUMBER'], atomic_numbers)

    def test_print_lj_matrix(self):
        """ Test printLJMatrix for ChamberParm """
        act = PT.printLJMatrix(gascham, '@1')
        self.assertEqual(str(act), saved.PRINT_LJMATRIXC)

    def test_delete_bond(self):
        """ Test deleteBond for ChamberParm """
        parm = copy(gascham)
        # Pick the bond we plan to delete, pick out every angle and dihedral
        # that contains that bond, and then delete it. Then make sure none of
        # the valence terms that contained that bond remain afterwards. We
        # already have a test to make sure that the __contains__ method works
        # for atoms and bonds.
        for bond in parm.atoms[10].bonds:
            if parm.atoms[12] in bond: break
        deleted_angles = list()
        deleted_dihedrals = list()
        deleted_impropers = list()
        deleted_urey_bradleys = list()
        deleted_cmaps = list()
        for angle in parm.angles:
            if bond in angle: deleted_angles.append(angle)
        for dihedral in parm.dihedrals:
            if bond in dihedral: deleted_dihedrals.append(dihedral)
        for imp in parm.impropers:
            if bond in imp: deleted_impropers.append(imp)
        for ub in parm.urey_bradleys:
            if bond in ub: deleted_urey_bradleys.append(ub)
        for cmap in parm.cmaps:
            if bond in cmap: deleted_cmaps.append(cmap)
        act = PT.deleteBond(parm, '@11', '@13', 'verbose')
        str(act)
        act.execute()
        self.assertTrue(bond not in parm.bonds)
        for angle in deleted_angles:
            self.assertTrue(angle not in parm.angles)
        for dihedral in deleted_dihedrals:
            self.assertTrue(all([dihedral is not d for d in parm.dihedrals]))
        for improper in deleted_impropers:
            self.assertTrue(improper not in parm.impropers)
        for ureybrad in deleted_urey_bradleys:
            self.assertTrue(ureybrad not in parm.urey_bradleys)
        self.assertFalse(parm.has_cmap)

    def test_delete_bond2(self):
        """ Test deleteBond with different parameter types """
        parm = copy(gascham)
        # Pick the bond we plan to delete, pick out every angle and dihedral
        # that contains that bond, and then delete it. Then make sure none of
        # the valence terms that contained that bond remain afterwards. We
        # already have a test to make sure that the __contains__ method works
        # for atoms and bonds.
        bond = parm.atoms[1].bonds[0]
        deleted_angles = list()
        deleted_dihedrals = list()
        deleted_impropers = list()
        deleted_urey_bradleys = list()
        deleted_cmaps = list()
        for angle in parm.angles:
            if bond in angle: deleted_angles.append(angle)
        for dihedral in parm.dihedrals:
            if bond in dihedral: deleted_dihedrals.append(dihedral)
            # Move these to the r-b torsion list
            parm.rb_torsions.append(dihedral)
        # Remove them from the dihedral list
        for i in reversed(range(len(parm.dihedrals))):
            if parm.dihedrals[i] in parm.rb_torsions:
                del parm.dihedrals[i]
        for imp in parm.impropers:
            if bond in imp: deleted_impropers.append(imp)
        for ub in parm.urey_bradleys:
            if bond in ub: deleted_urey_bradleys.append(ub)
        for cmap in parm.cmaps:
            if bond in cmap: deleted_cmaps.append(cmap)
        act = PT.deleteBond(parm, '@1', '@2', 'verbose')
        str(act)
        act.execute()
        self.assertTrue(bond not in parm.bonds)
        for angle in deleted_angles:
            self.assertTrue(angle not in parm.angles)
        for dihedral in deleted_dihedrals:
            self.assertTrue(all([dihedral is not d for d in parm.dihedrals]))
        for improper in deleted_impropers:
            self.assertTrue(improper not in parm.impropers)
        for ureybrad in deleted_urey_bradleys:
            self.assertTrue(ureybrad not in parm.urey_bradleys)

    def test_summary(self):
        """ Test summary action for ChamberParm """
        parm = load_file(self.get_fn('dhfr_cmap_pbc.parm7'))
        parm.load_rst7(self.get_fn('dhfr_cmap_pbc.rst7'))
        act = PT.summary(parm)
        self.assertTrue(detailed_diff(str(act), saved.SUMMARYC1, relative_error=1e-6))
        act = PT.summary(load_file(self.get_fn('2koc.pdb')))
        repr(act)

    def test_scale(self):
        """ Test scale action for ChamberParm """
        parm = copy(gascham)
        act = PT.scale(parm, 'DIHEDRAL_FORCE_CONSTANT', 2.0)
        act.execute()
        str(act)
        self.assertEqual(
                [2*x for x in gascham.parm_data['DIHEDRAL_FORCE_CONSTANT']],
                parm.parm_data['DIHEDRAL_FORCE_CONSTANT'])
        for dt1, dt2 in zip(parm.dihedral_types, gascham.dihedral_types):
            self.assertEqual(dt1.phi_k, dt2.phi_k*2)
        PT.scale(parm, 'DIHEDRAL_FORCE_CONSTANT', 0.0).execute()
        for val in parm.parm_data['DIHEDRAL_FORCE_CONSTANT']:
            self.assertEqual(val, 0)
        for dt in parm.dihedral_types:
            self.assertEqual(dt.phi_k, 0)
        # Error handling
        self.assertRaises(exc.ArgumentError, lambda:
                PT.scale(parm, 'NOTAFLAG', 10.0).execute())
        self.assertRaises(exc.ArgumentError, lambda:
                PT.scale(parm, 'ATOM_NAME', 10.0).execute())

    def test_interpolate(self):
        """ Test interpolate action for ChamberParm """
        self._empty_writes()
        parm = copy(gascham)
        origparm = copy(gascham)
        origparm.name = origparm.name + '_copy1'
        PT.change(parm, 'CHARGE', ':1', 0).execute()
        self.assertAlmostEqual(sum(parm.parm_data['CHARGE']), -1)
        self.assertAlmostEqual(sum(origparm.parm_data['CHARGE']), 0)
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(atom.charge, parm.parm_data['CHARGE'][i])
        for i, atom in enumerate(origparm.atoms):
            self.assertEqual(atom.charge, origparm.parm_data['CHARGE'][i])
        # Now set up a ParmList so we can interpolate these topology files
        parms = parmlist.ParmList()
        parms.add_parm(parm)
        parms.add_parm(origparm)
        sys.stdout = open(os.devnull, 'w')
        PT.interpolate(parms, 5, 'eleconly', startnum=2,
                       prefix=self.get_fn('test.parm7', written=True)).execute()
        sys.stdout = sys.__stdout__
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 5)
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.2', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.3', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.4', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.5', written=True)))
        self.assertTrue(os.path.exists(self.get_fn('test.parm7.6', written=True)))
        # Now check them all
        ladder = [origparm]
        ladder.append(AmberParm(self.get_fn('test.parm7.2', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.3', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.4', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.5', written=True)))
        ladder.append(AmberParm(self.get_fn('test.parm7.6', written=True)))
        ladder.append(parm)
        natom = parm.ptr('natom')
        for i in range(1, 6):
            before = ladder[i-1].parm_data['CHARGE']
            after = ladder[i+1].parm_data['CHARGE']
            this = ladder[i].parm_data['CHARGE']
            for j in range(natom):
                if before[j] < after[j]:
                    self.assertTrue(before[j] <= this[j] <= after[j])
                else:
                    self.assertTrue(before[j] >= this[j] >= after[j])

    def test_change_prot_state(self):
        """ Check that changeProtState fails for ChamberParm """
        parm = copy(solvchamber)
        self.assertRaises(exc.ParmError, lambda:
                PT.changeProtState(parm, ':32', 0).execute())

    def test_lmod(self):
        """ Test lmod action for ChamberParm """
        parm = copy(gascham)
        parm.parm_data['LENNARD_JONES_ACOEF'][3] = 0.0
        self.assertFalse(all(parm.parm_data['LENNARD_JONES_ACOEF']))
        PT.lmod(parm).execute()
        self.assertTrue(all(parm.parm_data['LENNARD_JONES_ACOEF']))

    def test_add_delete_pdb(self):
        """ Test addPDB and deletePDB actions for ChamberParm """
        parm = copy(gascham)
        PT.addPDB(parm, self.get_fn('ala_ala_ala.pdb'), 'elem', 'allicodes').execute()
        self.assertTrue('RESIDUE_ICODE' in parm.flag_list)
        self.assertTrue('ATOM_ELEMENT' in parm.flag_list)
        self.assertTrue('RESIDUE_NUMBER' in parm.flag_list)
        self.assertTrue('RESIDUE_CHAINID' in parm.flag_list)
        self.assertTrue(len(parm.parm_data['RESIDUE_ICODE']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['ATOM_ELEMENT']), parm.ptr('natom'))
        self.assertTrue(len(parm.parm_data['RESIDUE_NUMBER']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['RESIDUE_CHAINID']),parm.ptr('nres'))
        for i in range(parm.ptr('nres')):
            self.assertEqual(parm.parm_data['RESIDUE_NUMBER'][i], i+1)
            self.assertEqual(parm.parm_data['RESIDUE_ICODE'][i], '')
            self.assertEqual(parm.parm_data['RESIDUE_CHAINID'][i], 'A')
        for i, atom in enumerate(parm.atoms):
            atnum = atom.atomic_number
            elem = parm.parm_data['ATOM_ELEMENT'][i].strip()
            self.assertEqual(periodic_table.Element[atnum], elem)
            self.assertEqual(atnum, periodic_table.AtomicNum[elem])
        PT.deletePDB(parm).execute()
        self.assertFalse('RESIDUE_ICODE' in parm.flag_list)
        self.assertFalse('ATOM_ELEMENT' in parm.flag_list)
        self.assertFalse('RESIDUE_NUMBER' in parm.flag_list)
        self.assertFalse('RESIDUE_CHAINID' in parm.flag_list)
        PT.addPDB(parm, self.get_fn('ala_ala_ala.pdb')).execute()
        self.assertFalse('RESIDUE_ICODE' in parm.flag_list)
        self.assertFalse('ATOM_ELEMENT' in parm.flag_list)
        self.assertTrue('RESIDUE_NUMBER' in parm.flag_list)
        self.assertTrue('RESIDUE_CHAINID' in parm.flag_list)

    def test_h_mass_repartition(self):
        """ Test HMassRepartition action for ChamberParm """
        parm = copy(solvchamber)
        PT.defineSolvent(parm, 'TIP3').execute()
        act = PT.HMassRepartition(parm, 2.0)
        act.execute()
        str(act)
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                if atom.residue.name == 'TIP3':
                    self.assertAlmostEqual(atom.mass, 1.008)
                else:
                    self.assertEqual(atom.mass, 2)
        self.assertEqual(parm.parm_data['MASS'],
                         [a.mass for a in parm.atoms])
        self.assertAlmostEqual(sum(solvchamber.parm_data['MASS']),
                               sum(parm.parm_data['MASS']))
        act = PT.HMassRepartition(parm, 3.0, 'dowater')
        act.execute()
        str(act)
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                self.assertEqual(atom.mass, 3.0)
        self.assertAlmostEqual(sum(solvchamber.parm_data['MASS']),
                               sum(parm.parm_data['MASS']), places=6)

    def test_out_pdb(self):
        """ Test the outPDB action on ChamberParm """
        parm = copy(gascham)
        PT.loadRestrt(parm, self.get_fn('ala_ala_ala.rst7')).execute()
        PT.outPDB(parm, self.get_fn('outPDB1.pdb', written=True)).execute()
        f = PDBFile.parse(self.get_fn('outPDB1.pdb', written=True))
        self.assertEqual(len(f.atoms), len(parm.atoms))
        self.assertEqual(len(f.residues), len(parm.residues))
        for a1, a2 in zip(f.atoms, parm.atoms):
            self.assertEqual(a1.name, a2.name)
            self.assertEqual(a1.atomic_number, a2.atomic_number)
            self.assertAlmostEqual(a1.xx, a2.xx, delta=2e-3)
            self.assertAlmostEqual(a1.xy, a2.xy, delta=2e-3)
            self.assertAlmostEqual(a1.xz, a2.xz, delta=2e-3)
            self.assertEqual(a1.residue.name, a2.residue.name)
            self.assertEqual(a1.residue.idx, a2.residue.idx)

    def test_out_cif(self):
        """ Test the outCIF action on ChamberParm """
        parm = copy(gascham)
        PT.loadRestrt(parm, self.get_fn('ala_ala_ala.rst7')).execute()
        PT.outCIF(parm, self.get_fn('outPDB1.cif', written=True)).execute()
        f = CIFFile.parse(self.get_fn('outPDB1.cif', written=True))
        self.assertEqual(len(f.atoms), len(parm.atoms))
        self.assertEqual(len(f.residues), len(parm.residues))
        for a1, a2 in zip(f.atoms, parm.atoms):
            self.assertEqual(a1.name, a2.name)
            self.assertEqual(a1.atomic_number, a2.atomic_number)
            self.assertAlmostEqual(a1.xx, a2.xx, delta=2e-3)
            self.assertAlmostEqual(a1.xy, a2.xy, delta=2e-3)
            self.assertAlmostEqual(a1.xz, a2.xz, delta=2e-3)
            self.assertEqual(a1.residue.name, a2.residue.name)
            self.assertEqual(a1.residue.idx, a2.residue.idx)

    def test_check_validity(self):
        """ Test the checkValidity action on ChamberParm """
        act = PT.checkValidity(gascham)
        str(act)
        act.execute()

    def test_check_validity_solv(self):
        """ Test the checkValidity action on solvated ChamberParm """
        act = PT.checkValidity(solvchamber)
        str(act)
        act.execute()

class TestAmoebaParmActions(FileIOTestCase, TestCaseRelative):
    """ Tests actions on Amber prmtop files """

    def test_parmout_outparm_load_restrt(self):
        """ Test parmout, outparm, and loadRestrt actions on AmoebaParm """
        self._empty_writes()
        parm = copy(amoebaparm)
        PT.loadRestrt(parm, self.get_fn('nma.rst7')).execute()
        for atom in parm.atoms:
            self.assertTrue(hasattr(atom, 'xx'))
            self.assertTrue(hasattr(atom, 'xy'))
            self.assertTrue(hasattr(atom, 'xz'))
        PT.parmout(parm, self.get_fn('test.parm7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 1)
        self.assertTrue(diff_files(self.get_fn('nma.parm7'),
                                   self.get_fn('test.parm7', written=True)))
        self._empty_writes()
        PT.parmout(parm, self.get_fn('test.parm7', written=True),
                         self.get_fn('test.rst7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 2)
        self.assertTrue(diff_files(self.get_fn('nma.parm7'),
                                   self.get_fn('test.parm7', written=True)))
        self.assertTrue(diff_files(self.get_fn('nma.rst7'),
                                   self.get_fn('test.rst7', written=True),
                                   absolute_error=0.0001))
        self._empty_writes()
        PT.outparm(parm, self.get_fn('test.parm7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 1)
        self.assertTrue(diff_files(self.get_fn('nma.parm7'),
                                   self.get_fn('test.parm7', written=True)))
        self._empty_writes()
        PT.outparm(parm, self.get_fn('test.parm7', written=True),
                         self.get_fn('test.rst7', written=True)).execute()
        self.assertEqual(len(os.listdir(self._temporary_directory.name)), 2)
        self.assertTrue(diff_files(self.get_fn('nma.parm7'),
                                   self.get_fn('test.parm7', written=True)))
        self.assertTrue(diff_files(self.get_fn('nma.rst7'),
                                   self.get_fn('test.rst7', written=True),
                                   absolute_error=0.0001))

    def test_write_frcmod(self):
        """ Check that writeFrcmod fails for AmoebaParm """
        with self.assertRaises(exc.ParmError):
            PT.writeFrcmod(amoebaparm, self.get_fn('x', written=True)).execute()

    def test_write_off_load_restrt(self):
        """ Check that writeOFF fails for AmoebaParm """
        parm = copy(amoebaparm)
        PT.loadRestrt(parm, self.get_fn('nma.rst7')).execute()
        self.assertRaises(exc.ParmError, lambda:
                PT.writeOFF(parm, self.get_fn('test.off', written=True)).execute())

    def test_change_radii(self):
        """ Check that changeRadii fails for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
                PT.changeRadii(parm, 'amber6').execute())

    def test_change_lj_pair(self):
        """ Check that changeLJPair fails for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
                PT.changeLJPair(parm, '@%NH3', '@%HC', 1.0, 1.0).execute())

    def test_change_lj_14_pair(self):
        """ Check that changeLJ14Pair fails for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
                PT.changeLJ14Pair(parm, '@%NH3', '@%HC', 1.0, 1.0).execute())

    def test_change(self):
        """ Test the 'change' action for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmedChangeError, lambda:
                PT.change(parm, 'CHARGE', ':ACE', 0, 'quiet').execute())
        PT.change(parm, 'MASS', ':1', 10.0).execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['MASS'][i], atom.mass)
            if atom.residue.idx == 0:
                self.assertEqual(atom.mass, 10.0)
            else:
                self.assertEqual(atom.mass, amoebaparm.parm_data['MASS'][i])
        PT.change(parm, 'ATOM_NAME', ':WAT@O', 'JMS').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['ATOM_NAME'][i], atom.name)
            if atom.residue.name == 'WAT' and \
                        amoebaparm.parm_data['ATOM_NAME'][i] == 'O':
                self.assertEqual(atom.name, 'JMS')
            else:
                self.assertEqual(atom.name,
                                 amoebaparm.parm_data['ATOM_NAME'][i])
        PT.change(parm, 'AMBER_ATOM_TYPE', ':WAT@H*', 'RJLS').execute()
        for i, atom in enumerate(parm.atoms):
            self.assertEqual(parm.parm_data['AMBER_ATOM_TYPE'][i], atom.type)
            if atom.residue.name == 'WAT' and \
                        amoebaparm.parm_data['AMBER_ATOM_TYPE'][i][0] == 'H':
                self.assertEqual(atom.type, 'RJLS')
            else:
                self.assertEqual(atom.type,
                                 amoebaparm.parm_data['AMBER_ATOM_TYPE'][i])
        # Change atomic number
        PT.change(parm, 'ATOMIC_NUMBER', '@1', 10).execute()
        self.assertEqual(parm.atoms[0].atomic_number, 10)
        self.assertEqual(parm.parm_data['AMOEBA_ATOMIC_NUMBER'][0], 10)
        # Now make sure it adds the AMOEBA_ATOMIC_NUMBER section
        parm.delete_flag('AMOEBA_ATOMIC_NUMBER')
        PT.change(parm, 'ATOMIC_NUMBER', '@1', 1).execute()
        self.assertIn('AMOEBA_ATOMIC_NUMBER', parm.parm_data)
        self.assertEqual(parm[0].atomic_number, 1)
        self.assertEqual(parm.parm_data['AMOEBA_ATOMIC_NUMBER'][0], 1)
        # Check some error-handling
        self.assertRaises(exc.ParmedChangeError, lambda:
                PT.change(parm, 'ATOM_TYPE_INDEX', '@1', 4).execute())
        self.assertRaises(exc.ParmedChangeError, lambda:
                PT.change(parm, 'RADII', ':1-2', 2.0, 'quiet').execute())
        self.assertRaises(exc.ParmedChangeError, lambda:
                PT.change(parm, 'SCREEN', '*', 0.0).execute())
        # Check bad input
        self.assertRaises(exc.ParmedChangeError, lambda:
                          PT.change(parm, 'RESIDUE_LABEL', ':*', 'NaN'))
        # Make sure string casting always works
        act = PT.change(parm, 'MASS', '@NOTHING', 0, 'quiet')
        str(act)
        previous_masses = parm.parm_data['MASS'][:]
        act.execute()
        np.testing.assert_equal(previous_masses, parm.parm_data['MASS'])

    def test_print_info(self):
        """ Test printInfo for all flags of AmoebaParm """
        for flag in amoebaparm.parm_data:
            if flag == 'TITLE': continue
            act = PT.printInfo(amoebaparm, flag)
            vals = []
            for line in str(act).split('\n'):
                vals += line.split()
            self.assertEqual(len(vals), len(amoebaparm.parm_data[flag]))
            try:
                datatype = type(amoebaparm.parm_data[flag][0])
            except IndexError:
                continue
            for i, j in zip(vals, amoebaparm.parm_data[flag]):
                # printInfo prints to 5 places for floats.
                if datatype is float:
                    self.assertAlmostEqual(datatype(i), j, places=4)
                else:
                    self.assertEqual(datatype(i), j)

    def test_add_change_lj_type(self):
        """ Check that addLJType and changeLJSingleType fail for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
                PT.addLJType(parm, '@1').execute())
        self.assertRaises(exc.ParmError, lambda:
                PT.changeLJSingleType(parm, '@1', 1.0, 1.0).execute())

    def test_print_lj_types(self):
        """ Check that printLJTypes fails for AmoebaParm """
        self.assertRaises(exc.ParmError, lambda:
                PT.printLJTypes(amoebaparm, '@1'))

    def test_scee_scnb(self):
        """ Check that scee and scnb fail for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda: PT.scee(parm, 10).execute())
        self.assertRaises(exc.ParmError, lambda: PT.scnb(parm, 10).execute())

    def test_print_details(self):
        """ Test printDetails for AmoebaParm """
        act = PT.printDetails(amoebaparm, ':1-2')
        self.assertEqual(str(act), saved.PRINT_DETAILSA)

    def test_print_flags(self):
        """ Test printFlags for AmoebaParm """
        act = PT.printFlags(amoebaparm)
        printed_flags = set()
        for line in str(act).split('\n'):
            if line.startswith('%FLAG'):
                printed_flags.add(line.split()[1])
        self.assertEqual(printed_flags, set(amoebaparm.parm_data.keys()))

    def test_print_pointers(self):
        """ Test printPointers for AmoebaParm """
        act = PT.printPointers(amoebaparm)
        printed_pointers = set(['NEXT'])
        for line in str(act).split('\n'):
            try:
                pointer = line.split()[0]
                value = int(line[line.rfind('=')+1:].strip())
            except (IndexError, ValueError):
                continue
            self.assertEqual(amoebaparm.ptr(pointer), value)
            printed_pointers.add(pointer)
        self.assertEqual(printed_pointers, set(amoebaparm.pointers.keys()))

    def test_print_bonds(self):
        """ Test printBonds for AmoebaParm """
        act = PT.printBonds(amoebaparm, '@1')
        self.assertEqual(str(act), saved.PRINT_BONDSA)

    def test_print_angles(self):
        """ Test printAngles for AmoebaParm """
        act = PT.printAngles(amoebaparm, '@1')
        self.assertEqual(str(act), saved.PRINT_ANGLESA)

    def test_print_dihedrals(self):
        """ Test printDihedrals for AmoebaParm """
        act = PT.printDihedrals(amoebaparm, '@1')
        self.assertEqual(str(act), saved.PRINT_DIHEDRALSA)
        str(PT.printDihedrals(amoebaparm, ':*', '@10-12', ':*', ':*'))
        str(PT.printDihedrals(amoebaparm, ':*', ':1-20', ':*', ':*'))

    def test_set_molecules(self):
        """ Test setMolecules for AmoebaParm """
        parm = copy(amoebaparm)
        atoms = [atom for atom in parm.atoms] # shallow copy!
        self.assertTrue(all([x is y for x,y in zip(parm.atoms,atoms)]))
        self.assertEqual(parm.ptr('IPTRES'), 2)
        self.assertEqual(parm.ptr('NSPM'), 819)
        self.assertEqual(parm.ptr('NSPSOL'), 2)
        PT.setMolecules(parm).execute()
        self.assertEqual(parm.ptr('IPTRES'), 2)
        self.assertEqual(parm.ptr('NSPM'), 819)
        self.assertEqual(parm.ptr('NSPSOL'), 2)
        self.assertTrue(all([x is y for x,y in zip(parm.atoms, atoms)]))
        # Now check that setMolecules can apply another time
        PT.setMolecules(parm).execute()

    def test_net_charge(self):
        """ Test netCharge for AmoebaParm (charge is the monopole) """
        act = PT.netCharge(amoebaparm)
        chg = act.execute() # check the netCharge.execute return value
        self.assertEqual(str(act), 'The net charge of :* is %.4f' % chg)
        self.assertAlmostEqual(chg, 0.0)
        chg = PT.netCharge(amoebaparm, ':WAT').execute()
        self.assertAlmostEqual(chg, 0)

    def test_strip(self):
        """ Test strip action for AmoebaParm """
        parm = copy(amoebaparm)
        natoms = len(parm.atoms)
        lenres = len(parm.residues[0])
        PT.strip(parm, ':1').execute()
        self.assertEqual(parm.ptr('natom'), natoms-lenres)
        self.assertEqual(len(parm.atoms), natoms-lenres)
        # Good enough for here. The strip action is repeatedly tested in the
        # core Amber test suite as part of the MM/PBSA tests via ante-MMPBSA.py
        # and that part also tests that the energies come out correct as well

    def test_define_solvent(self):
        """ Test defineSolvent for AmoebaParm """
        import parmed.residue as residue
        PT.defineSolvent(amoebaparm, 'WAT,HOH,Na+,Cl-').execute()
        self.assertEqual(residue.SOLVENT_NAMES, 'WAT HOH Na+ Cl-'.split())
        PT.defineSolvent(amoebaparm, 'WAT,HOH').execute()
        self.assertEqual(residue.SOLVENT_NAMES, 'WAT HOH'.split())

    def test_add_exclusions(self):
        """ Check that addExclusions fails for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
                PT.addExclusions(parm, ':*', ':*').execute())

    def test_add_delete_dihedral(self):
        """ Check that addDihedral and deleteDihedral fail for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
            PT.deleteDihedral(parm, *'@1 @2 @3 @4'.split()).execute())
        self.assertRaises(exc.ParmError, lambda:
            PT.addDihedral(parm, '@1 @2 @3 @4 0.1556 3 0 1.2 2.0', type='normal').execute()
        )

    def test_set_bond(self):
        """ Check that setBond fails for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
            PT.setBond(parm, ':ALA@CA', ':ALA@CB', 300.0, 1.5).execute())

    def test_set_angle(self):
        """ Check that setAngle fails for AmoebaParm """
        parm = copy(amoebaparm)
        self.assertRaises(exc.ParmError, lambda:
            PT.setAngle(parm, ':ALA@CA :ALA@CB :ALA@HB1 40 100').execute())

    def test_add_atomic_number(self):
        """ Test addAtomicNumber for AmoebaParm """
        parm = copy(amoebaparm)
        atomic_numbers = [atom.atomic_number for atom in parm.atoms]
        PT.addAtomicNumber(parm).execute()
        self.assertEqual(parm.parm_data['AMOEBA_ATOMIC_NUMBER'], atomic_numbers)

    def test_print_lj_matrix(self):
        """ Check that printLJMatrix fails for AmoebaParm """
        self.assertRaises(exc.ParmError, lambda:
                PT.printLJMatrix(amoebaparm, '@1'))

    def test_delete_bond(self):
        """ Test deleteBond for AmoebaParm """
        parm = copy(amoebaparm)
        for bond in parm.atoms[0].bonds:
            if parm.atoms[1] in bond: break
        TrackedList = type(parm.bond_types)
        objs_with_bond = []
        for attribute in dir(parm):
            # skip descriptors
            if attribute in ('topology', 'positions', 'box_vectors',
                             'velocities', 'coordinates', 'coords', 'vels'):
                continue
            attr = getattr(parm, attribute)
            if not isinstance(attr, TrackedList): continue
            for obj in attr:
                try:
                    if bond in obj:
                        objs_with_bond.append(attr)
                        break
                except TypeError:
                    break
        self.assertTrue(len(objs_with_bond) > 0)
        act = PT.deleteBond(parm, '@1', '@2', 'verbose')
        str(act)
        act.execute()
        self.assertTrue(bond not in parm.bonds)
        for attr in objs_with_bond:
            for obj in attr:
                self.assertNotIn(bond, attr)

    @pytest.mark.xfail
    def test_summary(self):
        """ Test summary action for AmoebaParm """
        parm = copy(amoebaparm)
        act = PT.summary(parm)
        self.assertEqual(str(act), saved.SUMMARYA1)
        PT.loadRestrt(parm, self.get_fn('nma.rst7')).execute()
        act = PT.summary(parm)
        self.assertEqual(str(act), saved.SUMMARYA2)

    def test_scale(self):
        """ Test scale action for AmoebaParm """
        parm = copy(amoebaparm)
        flag = 'AMOEBA_STRETCH_BEND_FORCE_CONSTANT'
        PT.scale(parm, flag, 2.0).execute()
        self.assertEqual([2*x for x in amoebaparm.parm_data[flag]], parm.parm_data[flag])
        PT.scale(parm, flag, 0.0).execute()
        for val in parm.parm_data[flag]:
            self.assertEqual(val, 0)

    def test_interpolate(self):
        """ Check that interpolate action fails for AmoebaParm """
        with self.assertRaises(exc.ParmError):
            PT.interpolate(amoebaparm).execute()

    def test_change_prot_state(self):
        """ Check that changeProtState fails for AmoebaParm """
        parm = copy(amoebaparm)
        with self.assertRaises(exc.ParmError):
            PT.changeProtState(parm, ':32', 0).execute()

    def test_lmod(self):
        """ Check that lmod fails for AmoebaParm """
        self.assertRaises(exc.ParmError, lambda: PT.lmod(amoebaparm).execute())

    def test_add_delete_pdb(self):
        """ Test addPDB and deletePDB for AmoebaParm """
        parm = copy(amoebaparm)
        PT.addPDB(parm, self.get_fn('nma.pdb'), 'elem', 'allicodes').execute()
        self.assertTrue('RESIDUE_ICODE' in parm.flag_list)
        self.assertTrue('ATOM_ELEMENT' in parm.flag_list)
        self.assertTrue('RESIDUE_NUMBER' in parm.flag_list)
        self.assertTrue('RESIDUE_CHAINID' in parm.flag_list)
        self.assertTrue(len(parm.parm_data['RESIDUE_ICODE']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['ATOM_ELEMENT']), parm.ptr('natom'))
        self.assertTrue(len(parm.parm_data['RESIDUE_NUMBER']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['RESIDUE_CHAINID']),parm.ptr('nres'))
        for i in range(parm.ptr('nres')):
            self.assertEqual(parm.parm_data['RESIDUE_NUMBER'][i], i+1)
            self.assertEqual(parm.parm_data['RESIDUE_ICODE'][i], '')
            if parm.residues[i].name == 'WAT':
                self.assertEqual(parm.parm_data['RESIDUE_CHAINID'][i], 'B')
            else:
                self.assertEqual(parm.parm_data['RESIDUE_CHAINID'][i], 'A')
        for i, atom in enumerate(parm.atoms):
            atnum = atom.atomic_number
            elem = parm.parm_data['ATOM_ELEMENT'][i].strip()
            self.assertEqual(periodic_table.Element[atnum], elem)
            self.assertEqual(atnum, periodic_table.AtomicNum[elem])
        parm.write_parm(self.get_fn('amoeba_pdb.parm7', written=True))
        parm = AmoebaParm(self.get_fn('amoeba_pdb.parm7', written=True))
        self.assertTrue('RESIDUE_ICODE' in parm.flag_list)
        self.assertTrue('ATOM_ELEMENT' in parm.flag_list)
        self.assertTrue('RESIDUE_NUMBER' in parm.flag_list)
        self.assertTrue('RESIDUE_CHAINID' in parm.flag_list)
        self.assertTrue(len(parm.parm_data['RESIDUE_ICODE']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['ATOM_ELEMENT']), parm.ptr('natom'))
        self.assertTrue(len(parm.parm_data['RESIDUE_NUMBER']), parm.ptr('nres'))
        self.assertTrue(len(parm.parm_data['RESIDUE_CHAINID']),parm.ptr('nres'))
        for i in range(parm.ptr('nres')):
            self.assertEqual(parm.parm_data['RESIDUE_NUMBER'][i], i+1)
            self.assertEqual(parm.parm_data['RESIDUE_ICODE'][i], '')
            if parm.residues[i].name == 'WAT':
                self.assertEqual(parm.parm_data['RESIDUE_CHAINID'][i], 'B')
            else:
                self.assertEqual(parm.parm_data['RESIDUE_CHAINID'][i], 'A')
        for i, atom in enumerate(parm.atoms):
            atnum = atom.atomic_number
            elem = parm.parm_data['ATOM_ELEMENT'][i].strip()
            self.assertEqual(periodic_table.Element[atnum], elem)
            self.assertEqual(atnum, periodic_table.AtomicNum[elem])
        PT.deletePDB(parm).execute()
        self.assertFalse('RESIDUE_ICODE' in parm.flag_list)
        self.assertFalse('ATOM_ELEMENT' in parm.flag_list)
        self.assertFalse('RESIDUE_NUMBER' in parm.flag_list)
        self.assertFalse('RESIDUE_CHAINID' in parm.flag_list)
        PT.addPDB(parm, self.get_fn('nma.pdb')).execute()
        self.assertFalse('RESIDUE_ICODE' in parm.flag_list)
        self.assertFalse('ATOM_ELEMENT' in parm.flag_list)
        self.assertTrue('RESIDUE_NUMBER' in parm.flag_list)
        self.assertTrue('RESIDUE_CHAINID' in parm.flag_list)

    def test_h_mass_repartition(self):
        """ Test HMassRepartition action for AmoebaParm """
        parm = copy(amoebaparm)
        PT.HMassRepartition(parm, 2.0).execute()
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                if atom.residue.name == 'WAT':
                    self.assertAlmostEqual(atom.mass, 1.008)
                else:
                    self.assertEqual(atom.mass, 2)
        self.assertEqual(parm.parm_data['MASS'],
                         [a.mass for a in parm.atoms])
        self.assertAlmostEqual(sum(amoebaparm.parm_data['MASS']),
                               sum(parm.parm_data['MASS']))
        PT.HMassRepartition(parm, 3.0, 'dowater').execute()
        for atom in parm.atoms:
            if atom.atomic_number == 1:
                self.assertEqual(atom.mass, 3.0)
        self.assertAlmostEqual(sum(amoebaparm.parm_data['MASS']),
                               sum(parm.parm_data['MASS']), places=6)

    def test_out_pdb(self):
        """ Test the outPDB action on AmoebaParm """
        parm = copy(amoebaparm)
        PT.loadRestrt(parm, self.get_fn('nma.rst7')).execute()
        PT.outPDB(parm, self.get_fn('outPDB1.pdb', written=True)).execute()
        f = PDBFile.parse(self.get_fn('outPDB1.pdb', written=True))
        self.assertEqual(len(f.atoms), len(parm.atoms))
        self.assertEqual(len(f.residues), len(parm.residues))
        for a1, a2 in zip(f.atoms, parm.atoms):
            self.assertEqual(a1.name, a2.name)
            self.assertEqual(a1.atomic_number, a2.atomic_number)
            self.assertAlmostEqual(a1.xx, a2.xx, delta=2e-3)
            self.assertAlmostEqual(a1.xy, a2.xy, delta=2e-3)
            self.assertAlmostEqual(a1.xz, a2.xz, delta=2e-3)
            self.assertEqual(a1.residue.name, a2.residue.name)
            self.assertEqual(a1.residue.idx, a2.residue.idx)

    def test_out_cif(self):
        """ Test the outCIF action on AmoebaParm """
        parm = copy(amoebaparm)
        PT.loadRestrt(parm, self.get_fn('nma.rst7')).execute()
        PT.outCIF(parm, self.get_fn('outPDB1.cif', written=True)).execute()
        f = CIFFile.parse(self.get_fn('outPDB1.cif', written=True))
        self.assertEqual(len(f.atoms), len(parm.atoms))
        self.assertEqual(len(f.residues), len(parm.residues))
        for a1, a2 in zip(f.atoms, parm.atoms):
            self.assertEqual(a1.name, a2.name)
            self.assertEqual(a1.atomic_number, a2.atomic_number)
            self.assertAlmostEqual(a1.xx, a2.xx, delta=2e-3)
            self.assertAlmostEqual(a1.xy, a2.xy, delta=2e-3)
            self.assertAlmostEqual(a1.xz, a2.xz, delta=2e-3)
            self.assertEqual(a1.residue.name, a2.residue.name)
            self.assertEqual(a1.residue.idx, a2.residue.idx)

    def test_ti_merge(self):
        """ Check that tiMerge fails for AmoebaParm """
        parm = copy(amoebaparm)
        PT.loadRestrt(parm, self.get_fn('nma.rst7')).execute()
        self.assertRaises(exc.ParmError, lambda:
                PT.tiMerge(parm, ':1-3', ':4-6', ':2', ':5').execute())

class TestOtherParm(FileIOTestCase):
    """ Tests the use of other parms as the main parm """

    def test_summary(self):
        """ Tests the use of a PDB file with the summary action """
        parm = load_file(self.get_fn('4lzt.pdb'))
        self.assertEqual(str(PT.summary(parm)), saved.PDB_SUMMARY)

    def test_print_bonds(self):
        """ Tests printBonds on a PSF file with no parameters """
        parm = load_file(self.get_fn('ala_ala_ala.psf'))
        repr(PT.printBonds(parm, ':1-2'))

    def test_print_angles(self):
        """ Tests printAngles on a PSF file with no parameters """
        parm = load_file(self.get_fn('ala_ala_ala.psf'))
        repr(PT.printAngles(parm, ':1'))
        repr(PT.printAngles(parm, ':1', ':1'))
        repr(PT.printAngles(parm, ':1', ':1', ':1'))

    def test_print_dihedrals(self):
        """ Tests printDihedrals on a PSF file with no parameters """
        parm = load_file(self.get_fn('ala_ala_ala.psf'))
        repr(PT.printDihedrals(parm, ':1'))
        repr(PT.printDihedrals(parm, ':*', ':1'))
        repr(PT.printDihedrals(parm, ':*', ':*', ':1'))
        repr(PT.printDihedrals(parm, ':*', ':*', ':*', ':*'))
        parm.dihedrals[0].improper = True
        parm.dihedrals[1].ignore_end = True
        repr(PT.printDihedrals(parm, ':1'))
        repr(PT.printDihedrals(parm, ':*', ':1'))
        repr(PT.printDihedrals(parm, ':*', ':*', ':1'))
        repr(PT.printDihedrals(parm, ':*', ':*', ':*', ':*'))

    @unittest.skipUnless(HAS_GROMACS, 'Cannot test without GROMACS')
    def test_parm(self):
        """ Tests the parm action on a series of topology types and listParms """
        parms = parmlist.ParmList()
        # Make sure listParms works on an empty list
        repr(PT.listParms(parms))
        # First add an AmberParm
        act = PT.parm(parms, self.get_fn('ash.parm7'))
        act.execute()
        str(act)
        self.assertEqual(len(parms), 1)
        self.assertIs(parms.parm, parms[-1])
        # Next add a PDB and make sure it is the active parm
        PT.parm(parms, self.get_fn('2koc.pdb')).execute()
        self.assertEqual(len(parms), 2)
        self.assertIs(parms.parm, parms[-1])
        # Next add a GROMACS topology file
        PT.parm(parms, self.get_fn('ildn.solv.top')).execute()
        self.assertEqual(len(parms), 3)
        self.assertIs(parms.parm, parms[-1])
        # Next copy the amber parm
        act = PT.parm(parms, copy=0)
        act.execute()
        str(act)
        self.assertEqual(len(parms), 4)
        self.assertIs(parms[-1], parms.parm)
        self.assertEqual(len(parms[0].atoms), len(parms[3].atoms))
        # Next copy the GROMACS topology file parm
        act = PT.parm(parms, copy=self.get_fn('ildn.solv.top'))
        act.execute()
        str(act)
        self.assertEqual(len(parms), 5)
        self.assertIs(parms[-1], parms.parm)
        self.assertEqual(len(parms[2].atoms), len(parms[4].atoms))

        # Check setting new active parm by name and index
        act = PT.parm(parms, select=0)
        str(act)
        act.execute()
        self.assertEqual(len(parms), 5)
        self.assertIs(parms.parm, parms[0])
        act = PT.parm(parms, select=self.get_fn('2koc.pdb'))
        str(act)
        act.execute()
        self.assertEqual(len(parms), 5)
        self.assertIs(parms.parm, parms[1])

        # Error handling
        self.assertRaises(exc.ParmError, lambda: PT.parm(parms).execute())
        self.assertRaises(exc.ParmError, lambda:
                PT.parm(parms, copy=0, select=1).execute())
        self.assertRaises(exc.ParmError, lambda:
                PT.parm(parms, 'notafile').execute())
        act = PT.parm(parms, select='notafile')
        str(act)
        self.assertWarns(exc.SeriousParmWarning, act.execute)
        act = PT.parm(parms, select=100)
        str(act)
        self.assertWarns(exc.SeriousParmWarning, act.execute)
        act = PT.parm(parms, copy='notafile')
        str(act)
        self.assertWarns(exc.SeriousParmWarning, act.execute)
        act = PT.parm(parms, copy=100)
        str(act)
        self.assertWarns(exc.SeriousParmWarning, act.execute)

        # Catch permissions error, but only on Linux
        import platform
        if platform.system() == 'Windows': return
        fn = self.get_fn('test.pdb', written=True)
        with open(fn, 'w') as fw, open(self.get_fn('ash.parm7')) as fr:
            fw.write(fr.read())
        # Change the permissions to remove read perms
        os.chmod(fn, int('333', 8))
        act = PT.parm(parms, fn)
        self.assertWarns(exc.SeriousParmWarning, act.execute)

        # Now check that listParms works
        info = repr(PT.listParms(parms))
        self.assertIn('(active)', info)

    def test_h_mass_repartition(self):
        """ Tests HMassRepartition on arbitrary Structure instances """
        from parmed import periodic_table
        S = periodic_table.AtomicNum['S']
        Sm = periodic_table.Mass['S']
        struct = pmd.Structure()
        struct.add_atom(pmd.Atom(name='H1', atomic_number=1, mass=1.001), 'H2', 1)
        struct.add_atom(pmd.Atom(name='H2', atomic_number=1, mass=1.001), 'H2', 1)
        struct.add_atom(pmd.Atom(name='H1', atomic_number=1, mass=1.001), 'HSH', 2)
        struct.add_atom(pmd.Atom(name='H2', atomic_number=1, mass=1.001), 'HSH', 2)
        struct.add_atom(pmd.Atom(name='S', atomic_number=S, mass=Sm), 'HSH', 2)
        struct.bonds.append(pmd.Bond(struct[0], struct[1]))
        struct.bonds.append(pmd.Bond(struct[2], struct[3]))
        struct.bonds.append(pmd.Bond(struct[2], struct[4]))
        struct.bonds.append(pmd.Bond(struct[3], struct[4]))
        total_mass = sum(a.mass for a in struct.atoms)

        with self.assertWarns(exc.ParmWarning):
            PT.HMassRepartition(struct).execute()
        PT.HMassRepartition(struct).execute()
        self.assertEqual(struct[0].mass, 1.001)
        self.assertEqual(struct[1].mass, 1.001)
        self.assertEqual(struct[2].mass, 3.024)
        self.assertEqual(struct[3].mass, 3.024)
        self.assertAlmostEqual(sum(a.mass for a in struct.atoms), total_mass)
        # Now what happens if we make our Hs *too* heavy?
        with self.assertRaises(exc.HMassRepartitionError):
            PT.HMassRepartition(struct, 100).execute()

    def test_delete_bond(self):
        """ Tests deleteBond on arbitrary Structure instances """
        from parmed import periodic_table
        struct = create_random_structure(parametrized=True)
        act = PT.deleteBond(struct, '@%d' % (struct.bonds[0].atom1.idx+1),
                '@%d' % (struct.bonds[0].atom2.idx+1))
        str(act)
        act.execute()
