// -*- Mode: C++; tab-width: 2; -*-
// vi: set ts=2:
//

#include <BALL/CONCEPT/classTest.h>
#include <BALLTestConfig.h>

///////////////////////////
#include <BALL/MOLMEC/CHARMM/charmm.h>
#include <BALL/MOLMEC/CHARMM/charmmNonBonded.h>
#include <BALL/MOLMEC/CHARMM/charmmBend.h>
#include <BALL/MOLMEC/CHARMM/charmmStretch.h>
#include <BALL/MOLMEC/CHARMM/charmmTorsion.h>
#include <BALL/MOLMEC/CHARMM/charmmImproperTorsion.h>

#include <BALL/FORMAT/PDBFile.h>
#include <BALL/FORMAT/HINFile.h>
#include <BALL/STRUCTURE/defaultProcessors.h>
#include <BALL/STRUCTURE/fragmentDB.h>
///////////////////////////

START_TEST(CharmmFF)

/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////

using namespace BALL;

FragmentDB frag_db("fragments/Fragments.db");
{
	// read the PDB file containing a single GLY
	PDBFile f(BALL_TEST_DATA_PATH(CharmmFF_test_1.pdb));	
	System s;
	f >> s;
	f.close();
		
	// normalize the names and build the bonds
	s.apply(frag_db.normalize_names);
	s.apply(frag_db.build_bonds);

	// setup the force field
	Options options;
	options[CharmmFF::Option::ASSIGN_CHARGES] = "true";
	options[CharmmFF::Option::ASSIGN_TYPENAMES] = "true";
	options[CharmmFF::Option::ASSIGN_TYPES] = "true";
	options[CharmmFF::Option::FILENAME] = "CHARMM/EEF1/param19_eef1.ini";
	CharmmFF eef1(s, options);

	CHECK(energy test 1 (GLY) [EEF1])
		// check whether we got the right number of atoms
		TEST_EQUAL(s.countAtoms(), 8)

		eef1.updateEnergy();
		PRECISION(5e-3)
		TEST_REAL_EQUAL(eef1.getEnergy(), 157.4572251)
		TEST_REAL_EQUAL(eef1.getStretchEnergy(), 30.83411352)
		TEST_REAL_EQUAL(eef1.getBendEnergy(), 307.4544619)
		TEST_REAL_EQUAL(eef1.getProperTorsionEnergy(), 2.11036776)
		TEST_REAL_EQUAL(eef1.getImproperTorsionEnergy(), 5.48237888)
		TEST_REAL_EQUAL(eef1.getTorsionEnergy(), 7.59275)
		TEST_REAL_EQUAL(eef1.getVdWEnergy(), 26.64563664)
		TEST_REAL_EQUAL(eef1.getESEnergy(), -53.3215644)
		TEST_REAL_EQUAL(eef1.getSolvationEnergy(), -161.7481263)
	RESULT

	CHECK(gradient test 1 (GLY) [EEF1])
		eef1.updateForces();
		PRECISION(5e-3)
		TEST_REAL_EQUAL(eef1.getRMSGradient(), 219.2263702)
	RESULT

	CHECK(force test 1 (GLY, stretch only) [EEF1])
		// remove all components except for stretches
		eef1.removeComponent("CHARMM NonBonded");
		eef1.removeComponent("CHARMM Torsion");
		eef1.removeComponent("CHARMM ImproperTorsion");
		eef1.removeComponent("CHARMM Bend");
		eef1.options[CharmmFF::Option::FILENAME] = "CHARMM/EEF1/param19_eef1.ini";

		eef1.setup(s, options);

		eef1.updateForces();
		eef1.updateEnergy();

		TEST_REAL_EQUAL(eef1.getEnergy(), 30.83411352)
		TEST_REAL_EQUAL(eef1.getBendEnergy(), 0.0)
		TEST_REAL_EQUAL(eef1.getProperTorsionEnergy(), 0.0)
		TEST_REAL_EQUAL(eef1.getImproperTorsionEnergy(), 0.0)
		TEST_REAL_EQUAL(eef1.getTorsionEnergy(), 0.0)
		TEST_REAL_EQUAL(eef1.getStretchEnergy(), 30.83411352)
		TEST_REAL_EQUAL(eef1.getESEnergy(), 0.0)
		TEST_REAL_EQUAL(eef1.getVdWEnergy(), 0.0)
		TEST_REAL_EQUAL(eef1.getSolvationEnergy(), 0.0)
		TEST_REAL_EQUAL(eef1.getRMSGradient(), 134.2599576)

		Size i = 0;
		Vector3 force;
		AtomIterator atom_it = s.beginAtom();
		for (; +atom_it; ++atom_it, ++i)
		{
			switch (i)
			{
				// values are taken from CHARMM calculaction
				// (version 27b1 with param19_eef1.inp and toph19_eef1.inp,
				// with modified nonbonded: atom rdiel cutnb 20. cutonnb 11. cutofnb 15.)
				case 0:	force = Vector3( 26.44796,  91.42712, -23.82851); break;
				case 1:	force = Vector3( 14.55307,   1.84307, -19.22059); break;
				case 2:	force = Vector3(-14.56795, -89.42944,  62.77158); break;
				case 3:	force = Vector3(-16.84492, -12.50059,  -5.06838); break;
				case 4:	force = Vector3( -8.71000,   3.45793, -15.72975); break;
				case 5:	force = Vector3( -1.06629, -17.55152,  -0.53075); break;
				case 6:	force = Vector3( 19.60441,  12.80711,  11.19068); break;
				case 7:	force = Vector3(-19.41627,   9.94631,  -9.58428);
			}
			force = force - atom_it->getForce() * Constants::NA * 1e-13 / -4.184;
			TEST_REAL_EQUAL(force.getLength(), 0.0)
		}
	RESULT

}

CHECK(force test 9 (2 atoms, ES switching function) [EEF1/CDIEL])
	// read the PDB file containing two atoms
	PDBFile f(BALL_TEST_DATA_PATH(CharmmFF_test_2.pdb));	
	System s;
	f >> s;
	f.close();
	// check whether we got the right number of atoms
	TEST_EQUAL(s.countAtoms(), 2)
		
	// normalize the names and build the bonds
	s.apply(frag_db.normalize_names);

	CharmmFF eef1;
	// remove all components except for electrostatics
	eef1.removeComponent("CHARMM Torsion");
	eef1.removeComponent("CHARMM ImproperTorsion");
	eef1.removeComponent("CHARMM Bend");
	eef1.removeComponent("CHARMM Stretch");
	eef1.options[CharmmFF::Option::USE_EEF1] = "false";
	eef1.options[CharmmFF::Option::ASSIGN_CHARGES] = "true";
	eef1.options[CharmmFF::Option::ASSIGN_TYPENAMES] = "true";
	eef1.options[CharmmFF::Option::ASSIGN_TYPES] = "true";
	eef1.options[CharmmFF::Option::FILENAME] = "CHARMM/EEF1/param19_eef1.ini";
	// turn off vdW interactions
	eef1.options[CharmmFF::Option::VDW_CUTOFF] = 0.1;
	eef1.options[CharmmFF::Option::VDW_CUTON] = 0.05;
	eef1.options[CharmmFF::Option::ELECTROSTATIC_CUTON] = 2.0;
	eef1.options[CharmmFF::Option::ELECTROSTATIC_CUTOFF] = 4.0;
	eef1.options[CharmmFF::Option::DISTANCE_DEPENDENT_DIELECTRIC] = "false";
	eef1.setup(s);

	Size i = 0;
	Vector3 force;
	Atom* a1;
	Atom* a2;
	AtomIterator atom_it = s.beginAtom();
	for (; +atom_it; ++atom_it, ++i)
	{
		switch (i)
		{
			case 0:	a1 = &*atom_it; break;
			case 1:	a2 = &*atom_it; break;
		}
	}
	TEST_NOT_EQUAL(a1, 0)
	TEST_NOT_EQUAL(a2, 0)

	// set one atom to the origin
	a1->setPosition(Vector3(0.0));
	
	i = 0;
	for (double d = 1.2; d < 6.0; d += 0.2, i++)
	{
		a2->setPosition(Vector3(d, 0.0, 0.0));
		eef1.updateForces();
		double energy = eef1.updateEnergy();
		double force = a1->getForce().x * Constants::NA / 1e13;
		switch (i)
		{
			case 0:	
				TEST_REAL_EQUAL(force, 1563.02)
				TEST_REAL_EQUAL(energy, -1875.625)
				break;
			case 1:	
				TEST_REAL_EQUAL(force, 1148.34)
				TEST_REAL_EQUAL(energy, -1607.68)
				break;
			case 2:	
				TEST_REAL_EQUAL(force, 879.201)
				TEST_REAL_EQUAL(energy, -1406.72)
				break;
			case 3:	
				TEST_REAL_EQUAL(force, 694.678)
				TEST_REAL_EQUAL(energy, -1250.415)
				break;
			case 4:	
				TEST_REAL_EQUAL(force, 562.689)
				TEST_REAL_EQUAL(energy, -1125.375)
				break;
			case 5:	
				TEST_REAL_EQUAL(force, 605.039)
				TEST_REAL_EQUAL(energy, -1008.73)
				break;
			case 6:	
				TEST_REAL_EQUAL(force, 649.699)
				TEST_REAL_EQUAL(energy, -883.211)
				break;
			case 7:	
				TEST_REAL_EQUAL(force, 686.823)
				TEST_REAL_EQUAL(energy, -749.357)
				break;
			case 8:	
				TEST_REAL_EQUAL(force, 707.471)
				TEST_REAL_EQUAL(energy, -609.581)
				break;
			case 9:	
				TEST_REAL_EQUAL(force, 703.071)
				TEST_REAL_EQUAL(energy, -468.038)
				break;
			case 10:	
				TEST_REAL_EQUAL(force, 665.098)
				TEST_REAL_EQUAL(energy, -330.59)
				break;
			case 11:	
				TEST_REAL_EQUAL(force, 584.89)
				TEST_REAL_EQUAL(energy, -204.814)
				break;
			case 12:	
				TEST_REAL_EQUAL(force, 453.532)
				TEST_REAL_EQUAL(energy, -100.043)
				break;
			case 13:	
				TEST_REAL_EQUAL(force, 261.778)
				TEST_REAL_EQUAL(energy, -27.4272)
				break;
			default:	// above cutoff -> everything equals zero
				TEST_REAL_EQUAL(force, 0.0)
				TEST_REAL_EQUAL(energy, 0.0)
		}
	}
RESULT

CHECK(force test 10 (2 atoms, vdW switching function) [EEF1])
	// read the PDB file containing two atoms
	PDBFile f(BALL_TEST_DATA_PATH(CharmmFF_test_2.pdb));	
	System s;
	f >> s;
	f.close();
	// check whether we got the right number of atoms
	TEST_EQUAL(s.countAtoms(), 2)
		
	// normalize the names and build the bonds
	s.apply(frag_db.normalize_names);

	CharmmFF eef1;
	// remove all components except for vdW
	eef1.removeComponent("CHARMM Torsion");
	eef1.removeComponent("CHARMM ImproperTorsion");
	eef1.removeComponent("CHARMM Bend");
	eef1.removeComponent("CHARMM Stretch");
	eef1.options[CharmmFF::Option::USE_EEF1] = "false";
	eef1.options[CharmmFF::Option::ASSIGN_CHARGES] = "true";
	eef1.options[CharmmFF::Option::ASSIGN_TYPENAMES] = "true";
	eef1.options[CharmmFF::Option::ASSIGN_TYPES] = "true";
	eef1.options[CharmmFF::Option::FILENAME] = "CHARMM/EEF1/param19_eef1.ini";
	// turn off ES interactions
	eef1.options[CharmmFF::Option::VDW_CUTOFF] = 6.0;
	eef1.options[CharmmFF::Option::VDW_CUTON] = 4.0;
	eef1.options[CharmmFF::Option::ELECTROSTATIC_CUTON] = 0.05;
	eef1.options[CharmmFF::Option::ELECTROSTATIC_CUTOFF] = 0.1;
	eef1.setup(s);

	Size i = 0;
	Vector3 force;
	Atom* a1;
	Atom* a2;
	AtomIterator atom_it = s.beginAtom();
	for (; +atom_it; ++atom_it, ++i)
	{
		switch (i)
		{
			case 0:	a1 = &*atom_it; break;
			case 1:	a2 = &*atom_it; break;
		}
	}
	TEST_NOT_EQUAL(a1, 0)
	TEST_NOT_EQUAL(a2, 0)

	// set one atom to the origin
	a1->setPosition(Vector3(0.0));
	
	i = 0;
	for (double d = 3.2; d < 8.0; d += 0.2, i++)
	{
		a2->setPosition(Vector3(d, 0.0, 0.0));
		eef1.updateForces();
		double energy = eef1.updateEnergy();
		double force = a1->getForce().x * Constants::NA / 1e13;
		switch (i)
		{
			case 0:	
				TEST_REAL_EQUAL(force, -8.81132)
				TEST_REAL_EQUAL(energy, 0.658678)
				break;
			case 1:	
				TEST_REAL_EQUAL(force, -2.74153)
				TEST_REAL_EQUAL(energy, -0.398596)
				break;
			case 2:	
				TEST_REAL_EQUAL(force, -0.496799)
				TEST_REAL_EQUAL(energy, -0.685084)
				break;
			case 3:	
				TEST_REAL_EQUAL(force, 0.281582)
				TEST_REAL_EQUAL(energy, -0.692204)
				break;
			case 4:	
				TEST_REAL_EQUAL(force, 0.496839)
				TEST_REAL_EQUAL(energy, -0.608901)
				break;
			case 5:	
				TEST_REAL_EQUAL(force, 0.589907)
				TEST_REAL_EQUAL(energy, -0.497289)
				break;
			case 6:	
				TEST_REAL_EQUAL(force, 0.559986)
				TEST_REAL_EQUAL(energy, -0.380997)
				break;
			case 7:	
				TEST_REAL_EQUAL(force, 0.479373)
				TEST_REAL_EQUAL(energy, -0.276597)
				break;
			case 8:	
				TEST_REAL_EQUAL(force, 0.384436)
				TEST_REAL_EQUAL(energy, -0.190162)
				break;
			case 9:	
				TEST_REAL_EQUAL(force, 0.292368)
				TEST_REAL_EQUAL(energy, -0.12261)
				break;
			case 10:	
				TEST_REAL_EQUAL(force, 0.210405)
				TEST_REAL_EQUAL(energy, -0.0725306)
				break;
			case 11:	
				TEST_REAL_EQUAL(force, 0.14078)
				TEST_REAL_EQUAL(energy, -0.0376211)
				break;
			case 12:	
				TEST_REAL_EQUAL(force, 0.0833467)
				TEST_REAL_EQUAL(energy, -0.0154035)
				break;
			case 13:	
				TEST_REAL_EQUAL(force, 0.0369267)
				TEST_REAL_EQUAL(energy, -0.00354749)
				break;
			default:	// above cutoff -> everything equals zero
				TEST_REAL_EQUAL(force, 0.0)
				TEST_REAL_EQUAL(energy, 0.0)
		}
	}
RESULT

CHECK(force test 11 (2 atoms, solvation switching function) [EEF1])
	// read the PDB file containing two atoms
	PDBFile f(BALL_TEST_DATA_PATH(CharmmFF_test_2.pdb));	
	System s;
	f >> s;
	f.close();
	// check whether we got the right number of atoms
	TEST_EQUAL(s.countAtoms(), 2)
		
	// normalize the names and build the bonds
	s.apply(frag_db.normalize_names);

	CharmmFF eef1;
	// remove all components except for vdW
	eef1.removeComponent("CHARMM Torsion");
	eef1.removeComponent("CHARMM ImproperTorsion");
	eef1.removeComponent("CHARMM Bend");
	eef1.removeComponent("CHARMM Stretch");
	eef1.options[CharmmFF::Option::USE_EEF1] = "true";
	eef1.options[CharmmFF::Option::ASSIGN_CHARGES] = "true";
	eef1.options[CharmmFF::Option::ASSIGN_TYPENAMES] = "true";
	eef1.options[CharmmFF::Option::ASSIGN_TYPES] = "true";
	eef1.options[CharmmFF::Option::FILENAME] = "CHARMM/EEF1/param19_eef1.ini";
	// turn off vdW and ES interactions
	eef1.options[CharmmFF::Option::VDW_CUTON] = 0.05;
	eef1.options[CharmmFF::Option::VDW_CUTOFF] = 0.1;
	eef1.options[CharmmFF::Option::ELECTROSTATIC_CUTON] = 0.05;
	eef1.options[CharmmFF::Option::ELECTROSTATIC_CUTOFF] = 0.1;
	eef1.options[CharmmFF::Option::SOLVATION_CUTON] = 2.0;
	eef1.options[CharmmFF::Option::SOLVATION_CUTOFF] = 4.0;
	eef1.setup(s);

	Size i = 0;
	Vector3 force;
	Atom* a1;
	Atom* a2;
	AtomIterator atom_it = s.beginAtom();
	for (; +atom_it; ++atom_it, ++i)
	{
		switch (i)
		{
			case 0:	a1 = &*atom_it; break;
			case 1:	a2 = &*atom_it; break;
		}
	}
	TEST_NOT_EQUAL(a1, 0)
	TEST_NOT_EQUAL(a2, 0)

	// set one atom to the origin
	a1->setPosition(Vector3(0.0));
	
	i = 0;
	for (double d = 1.2; d < 6.0; d += 0.2, i++)
	{
		a2->setPosition(Vector3(d, 0.0, 0.0));
		eef1.updateForces();
		double energy = eef1.updateEnergy();
		double force = a1->getForce().x * Constants::NA / 1e13;
		switch (i)
		{
			case 0:	
				TEST_REAL_EQUAL(force, -20.9295)
				TEST_REAL_EQUAL(energy, -70.9525)
				break;
			case 1:	
				TEST_REAL_EQUAL(force, -13.2985)
				TEST_REAL_EQUAL(energy, -74.298)
				break;
			case 2:	
				TEST_REAL_EQUAL(force, -8.98882)
				TEST_REAL_EQUAL(energy, -76.4889)
				break;
			case 3:	
				TEST_REAL_EQUAL(force, -6.36919)
				TEST_REAL_EQUAL(energy, -78.0044)
				break;
			case 4:	
				TEST_REAL_EQUAL(force, -4.68368)
				TEST_REAL_EQUAL(energy, -79.0981)
				break;
			case 5:	
				TEST_REAL_EQUAL(force, -4.03846)
				TEST_REAL_EQUAL(energy, -79.967)
				break;
			case 6:	
				TEST_REAL_EQUAL(force, -3.53859)
				TEST_REAL_EQUAL(energy, -80.723)
				break;
			case 7:	
				TEST_REAL_EQUAL(force, -3.11062)
				TEST_REAL_EQUAL(energy, -81.3872)
				break;
			case 8:	
				TEST_REAL_EQUAL(force, -2.71063)
				TEST_REAL_EQUAL(energy, -81.9691)
				break;
			case 9:	
				TEST_REAL_EQUAL(force, -2.31205)
				TEST_REAL_EQUAL(energy, -82.4715)
				break;
			case 10:	
				TEST_REAL_EQUAL(force, -1.89916)
				TEST_REAL_EQUAL(energy, -82.893)
				break;
			case 11:	
				TEST_REAL_EQUAL(force, -1.46339)
				TEST_REAL_EQUAL(energy, -83.2297)
				break;
			case 12:	
				TEST_REAL_EQUAL(force, -1.00115)
				TEST_REAL_EQUAL(energy, -83.4766)
				break;
			case 13:	
				TEST_REAL_EQUAL(force, -0.512456)
				TEST_REAL_EQUAL(energy, -83.6283)
				break;
			default:	
				// above cutoff -> everything equals zero
				// energy equals to the sum of dG_ref
				TEST_REAL_EQUAL(force, 0.0)
				TEST_REAL_EQUAL(energy, -83.68)
		}
	}
RESULT


CharmmNonBonded* cnb_ptr = 0;
CHECK(CharmmNonBonded::CharmmNonBonded())
	cnb_ptr = new CharmmNonBonded;
	TEST_NOT_EQUAL(cnb_ptr, 0)
RESULT

CHECK(CharmmNonBonded::CharmmNonBonded())
	delete cnb_ptr;
RESULT

CHECK(CharmmNonBonded::CharmmNonBonded(const CharmmNonBonded& cnb))
	CharmmNonBonded cnb;
	CharmmNonBonded cnb2(cnb);
RESULT

CharmmBend* cb_ptr = 0;
CHECK(CharmmBend::CharmmBend())
	cb_ptr = new CharmmBend;
	TEST_NOT_EQUAL(cb_ptr, 0)
RESULT

CHECK(CharmmBend::CharmmBend())
	delete cb_ptr;
RESULT

CHECK(CharmmBend::CharmmBend(const CharmmBend& cnb))
	CharmmBend cb;
	CharmmBend cb2(cb);
RESULT


CharmmImproperTorsion* cit_ptr = 0;
CHECK(CharmmImproperTorsion::CharmmImproperTorsion())
	cit_ptr = new CharmmImproperTorsion;
	TEST_NOT_EQUAL(cit_ptr, 0)
RESULT

CHECK(CharmmImproperTorsion::CharmmImproperTorsion())
	delete cit_ptr;
RESULT

CHECK(CharmmImproperTorsion::CharmmImproperTorsion(const CharmmImproperTorsion& cit))
	CharmmImproperTorsion cit;
	CharmmImproperTorsion cit2(cit);
RESULT

CharmmStretch* cs_ptr = 0;
CHECK(CharmmStretch::CharmmStretch())
	cs_ptr = new CharmmStretch;
	TEST_NOT_EQUAL(cs_ptr, 0)
RESULT

CHECK(CharmmStretch::CharmmStretch())
	delete cs_ptr;
RESULT

CHECK(CharmmStretch::CharmmStretch(const CharmmStretch& cs))
	CharmmStretch cs;
	CharmmStretch cs2(cs);
RESULT

CharmmTorsion* ct_ptr = 0;
CHECK(CharmmTorsion::CharmmTorsion())
	ct_ptr = new CharmmTorsion;
	TEST_NOT_EQUAL(ct_ptr, 0)
RESULT

CHECK(CharmmTorsion::CharmmTorsion())
	delete ct_ptr;
RESULT

CHECK(CharmmTorsion::CharmmTorsion(const CharmmTorsion& ct))
	CharmmTorsion ct;
	CharmmTorsion ct2(ct);
RESULT

CHECK(void* CharmmNonBonded::create())
	CharmmNonBonded cnb;
	STATUS("initialized")
	CharmmNonBonded* clone = (CharmmNonBonded*)cnb.create();
	STATUS("cloned")
	delete clone;
	STATUS("deleted")
	ForceFieldComponent* ffc = &cnb;
	ForceFieldComponent* ffc_clone = (ForceFieldComponent*)ffc->create();
	STATUS("cloned after cast")
	delete ffc_clone;
	STATUS("deleted")
	CharmmFF cff;
	TEST_NOT_EQUAL(cff.getComponent("CHARMM NonBonded"), 0)
RESULT

CHECK([EXTRA] Additivity of energies w/ selection)
	HINFile f(BALL_TEST_DATA_PATH(G4.hin));
	System S;
	f.read(S);

	ABORT_IF(S.countAtoms() != 31)
	CharmmFF ff;
	ff.options[CharmmFF::Option::OVERWRITE_TYPENAMES] = "true";
	ff.options[CharmmFF::Option::ASSIGN_TYPENAMES] = "true";
	ff.options[CharmmFF::Option::ASSIGN_CHARGES] = "true";
	ff.options[CharmmFF::Option::OVERWRITE_CHARGES] = "true";

	ff.setup(S);
	double total_energy = ff.updateEnergy();
	STATUS("total  : " << total_energy)

	ResidueIterator ri(S.beginResidue());
	Residue& r1 = *ri++;
	Residue& r2 = *ri++;
	Residue& r3 = *ri++;
	Residue& r4 = *ri;

	r1.select();
	r2.select();
	r3.select();
	ff.setup(S);
	S.deselect();
	r1.select();
	double r1_tpl = ff.updateEnergy();
	STATUS("  ES : " << ff.getESEnergy())
	STATUS("  VDW: " << ff.getVdWEnergy())
	STATUS("  STR: " << ff.getStretchEnergy())
	STATUS("  BEN: " << ff.getBendEnergy())
	STATUS("  TOR: " << ff.getTorsionEnergy())
	STATUS("  TTL: " << ff.getEnergy())
	STATUS("r1 - tpl: " << r1_tpl)
	
	S.select();
	r1.deselect();
	ff.setup(S);
	S.deselect();
	r4.select();
	double r4_tpl = ff.updateEnergy();
	STATUS("  ES : " << ff.getESEnergy())
	STATUS("  VDW: " << ff.getVdWEnergy())
	STATUS("  STR: " << ff.getStretchEnergy())
	STATUS("  BEN: " << ff.getBendEnergy())
	STATUS("  TOR: " << ff.getTorsionEnergy())
	STATUS("  TTL: " << ff.getEnergy())
	STATUS("r4 - tpl: " << r4_tpl)
	
	S.deselect();
	r1.select();
	r4.select();
	ff.setup(S);
	r4.deselect();
	double r1_r4 = ff.updateEnergy();
	STATUS("  ES : " << ff.getESEnergy())
	STATUS("  VDW: " << ff.getVdWEnergy())
	STATUS("  STR: " << ff.getStretchEnergy())
	STATUS("  BEN: " << ff.getBendEnergy())
	STATUS("  TOR: " << ff.getTorsionEnergy())
	STATUS("  TTL: " << ff.getEnergy())
	STATUS("r1 - r4 : " << r1_r4)

	r4.select();
	r1.deselect();
	double r4_r1 = ff.updateEnergy();
	STATUS("  ES : " << ff.getESEnergy())
	STATUS("  VDW: " << ff.getVdWEnergy())
	STATUS("  STR: " << ff.getStretchEnergy())
	STATUS("  BEN: " << ff.getBendEnergy())
	STATUS("  TOR: " << ff.getTorsionEnergy())
	STATUS("  TTL: " << ff.getEnergy())
	STATUS("r4 - r1 : " << r4_r1)

	S.deselect();
	r1.select();
	ff.setup(S);
	double r1_i = ff.updateEnergy();
	STATUS("  ES : " << ff.getESEnergy())
	STATUS("  VDW: " << ff.getVdWEnergy())
	STATUS("  STR: " << ff.getStretchEnergy())
	STATUS("  BEN: " << ff.getBendEnergy())
	STATUS("  TOR: " << ff.getTorsionEnergy())
	STATUS("  TTL: " << ff.getEnergy())
	STATUS("r1_i    : " << r1_i)

	S.deselect();
	r4.select();
	ff.setup(S);
	double r4_i = ff.updateEnergy();
	STATUS("  ES : " << ff.getESEnergy())
	STATUS("  VDW: " << ff.getVdWEnergy())
	STATUS("  STR: " << ff.getStretchEnergy())
	STATUS("  BEN: " << ff.getBendEnergy())
	STATUS("  TOR: " << ff.getTorsionEnergy())
	STATUS("  TTL: " << ff.getEnergy())
	STATUS("r4_i    : " << r4_i)

	S.deselect();
	r2.select();
	r3.select();
	ff.setup(S);
	double tpl_i = ff.updateEnergy();
	STATUS("  ES : " << ff.getESEnergy())
	STATUS("  VDW: " << ff.getVdWEnergy())
	STATUS("  STR: " << ff.getStretchEnergy())
	STATUS("  BEN: " << ff.getBendEnergy())
	STATUS("  TOR: " << ff.getTorsionEnergy())
	STATUS("  TTL: " << ff.getEnergy())
	STATUS("tpl_i   : " << tpl_i)

	TEST_REAL_EQUAL(r4_r1 - r4_i, r1_r4 - r1_i)
	TEST_REAL_EQUAL(r4_r1 - r4_i + r4_tpl + r1_tpl + tpl_i, total_energy)
	TEST_REAL_EQUAL(r1_r4 - r1_i + r1_tpl + r4_tpl + tpl_i, total_energy)	
RESULT
/////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////
END_TEST

