/*******************************************************************************
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 * 
 * Contact Info:
 * 	Bruce Donald
 * 	Duke University
 * 	Department of Computer Science
 * 	Levine Science Research Center (LSRC)
 * 	Durham
 * 	NC 27708-0129 
 * 	USA
 * 	brd@cs.duke.edu
 * 
 * Copyright (C) 2011 Jeffrey W. Martin and Bruce R. Donald
 * 
 * <signature of Bruce Donald>, April 2011
 * Bruce Donald, Professor of Computer Science
 ******************************************************************************/


package edu.duke.donaldLab.jdshot.test.grid;

import java.util.ArrayList;

import edu.duke.donaldLab.jdshot.TestMain;
import edu.duke.donaldLab.jdshot.grid.Axes;
import edu.duke.donaldLab.jdshot.grid.Symmetry;
import edu.duke.donaldLab.jdshot.grid.dn.DnCellChecker;
import edu.duke.donaldLab.jdshot.grid.dn.DnGridCell;
import edu.duke.donaldLab.jdshot.grid.dn.DnGridPoint;
import edu.duke.donaldLab.jdshot.grid.dn.DnSearchSpace;
import edu.duke.donaldLab.jdshot.search.BoundContext;
import edu.duke.donaldLab.jdshot.search.SubunitType;
import edu.duke.donaldLab.jdshot.test.ExtendedTestCase;
import edu.duke.donaldLab.share.geom.AxisAlignedBox;
import edu.duke.donaldLab.share.geom.Intersect;
import edu.duke.donaldLab.share.geom.OrientedBox;
import edu.duke.donaldLab.share.geom.Vector3;
import edu.duke.donaldLab.share.math.CompareReal;
import edu.duke.donaldLab.share.math.Matrix3;
import edu.duke.donaldLab.share.math.PointIterator;
import edu.duke.donaldLab.share.protein.Atom;

public class TestDnCellChecker extends ExtendedTestCase
{
	private static boolean EnableTestEpsilons = true;
	private static boolean EnableTestOrientedBoxBound = true;
	private static boolean EnableTestAxisAlignedBoxBound = true;
	private static boolean EnableTestBound = true;
	
	private static boolean EnableLongTests = false;
	
	private class DnCellCheckerPublisher extends DnCellChecker
	{
		public DnCellCheckerPublisher( )
		{
			super( TestMain.getSymmetryContext() );
			
			// just in case...
			assert( TestMain.getSymmetryContext().getSymmetry() == Symmetry.Dn );
		}
		
		public void getBoundContext( BoundContext boundContext, DnGridCell cell, Atom atom )
		{
			super.getBoundContext( boundContext, cell, atom );
		}
		
		public void getOrientedBox( OrientedBox orientedBox, BoundContext boundContext, Vector3 q )
		{
			super.getOrientedBox( orientedBox, boundContext, q );
		}
		
		public void getSmallAxisAlignedBox( AxisAlignedBox axisAlignedBox, BoundContext boundContext, Vector3 q )
		{
			super.getSmallAxisAlignedBox( axisAlignedBox, boundContext, q );
		}
		
		public void getBoundingBox( AxisAlignedBox axisAlignedBox, BoundContext boundContext )
		{
			super.getBoundingBox( axisAlignedBox, boundContext );
		}
	}
	
	public void testIntradimerBoundContext( )
	{
		// set up
		DnGridCell cell = new DnGridCell(
			new DnGridPoint( 3.0, 5.0, 7.0, 0.0, 0.0, 0.0 ),
			new DnGridPoint( 4.0, 6.0, 8.0, Math.PI / 3.0, Math.PI / 3.0, Math.PI / 3.0 )
		);
		Vector3 q = new Vector3( 3.0, 4.0, 5.0 );
		Atom atom = new Atom();
		atom.setPosition( q );
		
		// operation
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		BoundContext boundContext = new BoundContext( DnGridCell.NumTCorners );
		boundContext.type = SubunitType.Intradimer;
		cellChecker.getBoundContext( boundContext, cell, atom );
		
		// verify
		assertEquals( q, boundContext.q );
		assertEquals( Math.PI, boundContext.alpha );
		assertEquals( new Vector3( 0.39952, 0.80801, -0.43301 ), boundContext.a );
		assertEquals( new Vector3( 3.0, 5.0, 7.0 ), boundContext.corners[0] );
		assertEquals( new Vector3( 3.0, 5.0, 8.0 ), boundContext.corners[1] );
		assertEquals( new Vector3( 3.0, 6.0, 7.0 ), boundContext.corners[2] );
		assertEquals( new Vector3( 3.0, 6.0, 8.0 ), boundContext.corners[3] );
		assertEquals( new Vector3( 4.0, 5.0, 7.0 ), boundContext.corners[4] );
		assertEquals( new Vector3( 4.0, 5.0, 8.0 ), boundContext.corners[5] );
		assertEquals( new Vector3( 4.0, 6.0, 7.0 ), boundContext.corners[6] );
		assertEquals( new Vector3( 4.0, 6.0, 8.0 ), boundContext.corners[7] );
	}
	
	public void testInterdimerStandardBoundContext( )
	{
		// set up
		DnGridCell cell = new DnGridCell(
			new DnGridPoint( 3.0, 5.0, 7.0, 0.0, 0.0, 0.0 ),
			new DnGridPoint( 4.0, 6.0, 8.0, Math.PI / 3.0, Math.PI / 3.0, Math.PI / 3.0 )
		);
		Vector3 q = new Vector3( 3.0, 4.0, 5.0 );
		Atom atom = new Atom();
		atom.setPosition( q );
		
		// operation
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		BoundContext boundContext = new BoundContext( DnGridCell.NumTCorners );
		boundContext.type = SubunitType.InterdimerStandard;
		cellChecker.getBoundContext( boundContext, cell, atom );
		
		// verify
		assertEquals( q, boundContext.q );
		assertEquals( Math.PI, boundContext.alpha );
		assertEquals( new Vector3( -0.80801, 0.53349, 0.24999 ), boundContext.a );
		assertEquals( new Vector3( 3.0, 5.0, 7.0 ), boundContext.corners[0] );
		assertEquals( new Vector3( 3.0, 5.0, 8.0 ), boundContext.corners[1] );
		assertEquals( new Vector3( 3.0, 6.0, 7.0 ), boundContext.corners[2] );
		assertEquals( new Vector3( 3.0, 6.0, 8.0 ), boundContext.corners[3] );
		assertEquals( new Vector3( 4.0, 5.0, 7.0 ), boundContext.corners[4] );
		assertEquals( new Vector3( 4.0, 5.0, 8.0 ), boundContext.corners[5] );
		assertEquals( new Vector3( 4.0, 6.0, 7.0 ), boundContext.corners[6] );
		assertEquals( new Vector3( 4.0, 6.0, 8.0 ), boundContext.corners[7] );
	}
	
	public void testInterdimerGoofyBoundContext( )
	{
		// set up
		DnGridCell cell = new DnGridCell(
			new DnGridPoint( 3.0, 5.0, 7.0, 0.0, 0.0, 0.0 ),
			new DnGridPoint( 4.0, 6.0, 8.0, Math.PI / 3.0, Math.PI / 3.0, Math.PI / 3.0 )
		);
		Vector3 q = new Vector3( 3.0, 4.0, 5.0 );
		Atom atom = new Atom();
		atom.setPosition( q );
		
		// operation
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		BoundContext boundContext = new BoundContext( DnGridCell.NumTCorners );
		boundContext.type = SubunitType.InterdimerGoofy;
		cellChecker.getBoundContext( boundContext, cell, atom );
		
		// HACKHACK: I'm lazy, so I'll assume the Axes class is correct here
		Vector3 axis = new Vector3();
		Axes.getRotationVector( axis, Math.PI / 6.0, Math.PI / 6.0 );
		
		// verify
		assertEquals( q, boundContext.q );
		assertEquals( Math.PI, boundContext.alpha );
		assertEquals( axis, boundContext.a );
		assertEquals( new Vector3( 3.0, 5.0, 7.0 ), boundContext.corners[0] );
		assertEquals( new Vector3( 3.0, 5.0, 8.0 ), boundContext.corners[1] );
		assertEquals( new Vector3( 3.0, 6.0, 7.0 ), boundContext.corners[2] );
		assertEquals( new Vector3( 3.0, 6.0, 8.0 ), boundContext.corners[3] );
		assertEquals( new Vector3( 4.0, 5.0, 7.0 ), boundContext.corners[4] );
		assertEquals( new Vector3( 4.0, 5.0, 8.0 ), boundContext.corners[5] );
		assertEquals( new Vector3( 4.0, 6.0, 7.0 ), boundContext.corners[6] );
		assertEquals( new Vector3( 4.0, 6.0, 8.0 ), boundContext.corners[7] );
	}
	
	public void testEpsilons( )
	{
		// handle execution flags
		if( !EnableTestEpsilons || !EnableLongTests )
		{
			return;
		}
		
		// setup
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		double halfBeta = 2.0 * Math.PI / (double)TestMain.getSearchContext().getNumSubunits();
		
		Vector3 f = new Vector3();
		Vector3 c = new Vector3();
		Vector3 r = new Vector3();
		BoundContext intradimerBoundContext = new BoundContext( DnGridCell.NumTCorners );
		intradimerBoundContext.type = SubunitType.Intradimer;
		BoundContext interdimerStandardBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerStandardBoundContext.type = SubunitType.InterdimerStandard;
		BoundContext interdimerGoofyBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerGoofyBoundContext.type = SubunitType.InterdimerStandard;
		
		// for each cell...
		for( DnGridCell cell : getCustomCells() )
		{
			// compute the epsilons
			cellChecker.getBoundContext( intradimerBoundContext, cell, new Atom() );
			double intradimerEpsilonSq = intradimerBoundContext.epsilon * intradimerBoundContext.epsilon;
			
			cellChecker.getBoundContext( interdimerStandardBoundContext, cell, new Atom() );
			double interdimerStandardEpsilonSq = interdimerStandardBoundContext.epsilon * interdimerStandardBoundContext.epsilon;
			
			cellChecker.getBoundContext( interdimerGoofyBoundContext, cell, new Atom() );
			double interdimerGoofyEpsilonSq = interdimerGoofyBoundContext.epsilon * interdimerGoofyBoundContext.epsilon;
			
			// make sure we can't generate a vector more than epsilon away from a
			PointIterator iterPoint = new PointIterator(
				Vector3.Dimension,
				new int[] { 10, 10, 10 },
				new double[] { cell.min.phi, cell.min.theta, cell.min.rho },
				new double[] { cell.max.phi, cell.max.theta, cell.max.rho }
			);
			while( iterPoint.hasNext() )
			{
				Vector3 angles = new Vector3( iterPoint.next() );
				
				Axes.getFlipVector( f, angles.x, angles.y, angles.z );
				assertLte( intradimerEpsilonSq, f.getSquaredDistance( intradimerBoundContext.a ) );
				
				Axes.getCVector( c, angles.x, angles.y, angles.z, halfBeta );
				assertLte( interdimerStandardEpsilonSq, c.getSquaredDistance( interdimerStandardBoundContext.a ) );
				
				Axes.getRotationVector( r, angles.x, angles.y );
				assertLte( interdimerGoofyEpsilonSq, c.getSquaredDistance( interdimerGoofyBoundContext.a ) );
			}
		}
	}
	
	public void testOrientedBoxBound( )
	{
		// handle execution flags
		if( !EnableTestOrientedBoxBound || !EnableLongTests )
		{
			return;
		}
		
		// setup
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		double halfBeta = 2.0 * Math.PI / (double)TestMain.getSearchContext().getNumSubunits();
		
		// min distances vars
		double[] intradimerMinDistances = new double[Vector3.Dimension * 2];
		double[] interdimerStandardMinDistances = new double[Vector3.Dimension * 2];
		double[] interdimerGoofyMinDistances = new double[Vector3.Dimension * 2];
		
		Vector3 f = new Vector3();
		Vector3 c = new Vector3();
		Vector3 r = new Vector3();
		BoundContext intradimerBoundContext = new BoundContext( DnGridCell.NumTCorners );
		intradimerBoundContext.type = SubunitType.Intradimer;
		BoundContext interdimerStandardBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerStandardBoundContext.type = SubunitType.InterdimerStandard;
		BoundContext interdimerGoofyBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerGoofyBoundContext.type = SubunitType.InterdimerGoofy;
		Matrix3 rot = new Matrix3();
		Vector3 intradimerQp = new Vector3();
		Vector3 interdimerStandardQp = new Vector3();
		Vector3 interdimerGoofyQp = new Vector3();
		OrientedBox intradimerOrientedBox = new OrientedBox();
		OrientedBox interdimerStandardOrientedBox = new OrientedBox();
		OrientedBox interdimerGoofyOrientedBox = new OrientedBox();
		
		// for each cell...
		for( DnGridCell cell : getCustomCells() )
		{
			// get atom positions
			double monomerRadius = TestMain.getSearchContext().getBoundingSphere().radius;
			PointIterator iterAtom = new PointIterator(
				Vector3.Dimension,
				new int[] { 10, 10, 10 },
				new double[] { -monomerRadius, -monomerRadius, -monomerRadius },
				new double[] { monomerRadius, monomerRadius, monomerRadius }
			);
			while( iterAtom.hasNext() )
			{
				Vector3 q = new Vector3( iterAtom.next() );
				
				// get the oriented box bounds
				cellChecker.getBoundContext( intradimerBoundContext, cell, new Atom() );
				cellChecker.getOrientedBox( intradimerOrientedBox, intradimerBoundContext, q );
				cellChecker.getBoundContext( interdimerStandardBoundContext, cell, new Atom() );
				cellChecker.getOrientedBox( interdimerStandardOrientedBox, interdimerStandardBoundContext, q );
				cellChecker.getBoundContext( interdimerGoofyBoundContext, cell, new Atom() );
				cellChecker.getOrientedBox( interdimerGoofyOrientedBox, interdimerGoofyBoundContext, q );
				
				// make sure the direction vectors are orthogonal
				assertEquals( 0.0, intradimerOrientedBox.x.getDot( intradimerOrientedBox.y ), CompareReal.getEpsilon() );
				assertEquals( 0.0, intradimerOrientedBox.x.getDot( intradimerOrientedBox.z ), CompareReal.getEpsilon() );
				assertEquals( 0.0, intradimerOrientedBox.y.getDot( intradimerOrientedBox.z ), CompareReal.getEpsilon() );
				assertEquals( 0.0, interdimerStandardOrientedBox.x.getDot( interdimerStandardOrientedBox.y ), CompareReal.getEpsilon() );
				assertEquals( 0.0, interdimerStandardOrientedBox.x.getDot( interdimerStandardOrientedBox.z ), CompareReal.getEpsilon() );
				assertEquals( 0.0, interdimerStandardOrientedBox.y.getDot( interdimerStandardOrientedBox.z ), CompareReal.getEpsilon() );
				assertEquals( 0.0, interdimerGoofyOrientedBox.x.getDot( interdimerGoofyOrientedBox.y ), CompareReal.getEpsilon() );
				assertEquals( 0.0, interdimerGoofyOrientedBox.x.getDot( interdimerGoofyOrientedBox.z ), CompareReal.getEpsilon() );
				assertEquals( 0.0, interdimerGoofyOrientedBox.y.getDot( interdimerGoofyOrientedBox.z ), CompareReal.getEpsilon() );
				
				// make sure the direction vectors are unit length
				assertEquals( 1.0, intradimerOrientedBox.x.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, intradimerOrientedBox.y.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, intradimerOrientedBox.z.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, interdimerStandardOrientedBox.x.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, interdimerStandardOrientedBox.y.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, interdimerStandardOrientedBox.z.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, interdimerGoofyOrientedBox.x.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, interdimerGoofyOrientedBox.y.getSquaredLength(), CompareReal.getEpsilon() );
				assertEquals( 1.0, interdimerGoofyOrientedBox.z.getSquaredLength(), CompareReal.getEpsilon() );
				
				// reset distances
				for( int i=0; i<Vector3.Dimension*2; i++ )
				{
					intradimerMinDistances[i] = Double.POSITIVE_INFINITY;
					interdimerStandardMinDistances[i] = Double.POSITIVE_INFINITY;
					interdimerGoofyMinDistances[i] = Double.POSITIVE_INFINITY;
				}
				
				// try to rotate q outside of the box
				PointIterator iterAngles = new PointIterator(
					Vector3.Dimension,
					new int[] { 10, 10, 10 },
					new double[] { cell.min.phi, cell.min.theta, cell.min.rho },
					new double[] { cell.max.phi, cell.max.theta, cell.max.rho }
				);
				while( iterAngles.hasNext() )
				{
					Vector3 angles = new Vector3( iterAngles.next() );
					
					// do the rotation
					Axes.getFlipVector( f, angles.x, angles.y, angles.z );
					Matrix3.getRotation( rot, f, intradimerBoundContext.alpha );
					intradimerQp.set( q );
					rot.multiply( intradimerQp );
					assertPointIn( intradimerQp, intradimerOrientedBox );
					
					Axes.getCVector( c, angles.x, angles.y, angles.z, halfBeta );
					Matrix3.getRotation( rot, c, interdimerStandardBoundContext.alpha );
					interdimerStandardQp.set( q );
					rot.multiply( interdimerStandardQp );
					assertPointIn( interdimerStandardQp, interdimerStandardOrientedBox );
					
					Axes.getRotationVector( r, angles.x, angles.y );
					Matrix3.getRotation( rot, r, interdimerGoofyBoundContext.alpha );
					interdimerGoofyQp.set( q );
					rot.multiply( interdimerGoofyQp );
					assertPointIn( interdimerGoofyQp, interdimerGoofyOrientedBox );
					
					// calculate the distances
					double[] intradimerDistances = Intersect.getDistancesFromPointIn( intradimerQp, intradimerOrientedBox );
					double[] interdimerStandardDistances = Intersect.getDistancesFromPointIn( interdimerStandardQp, interdimerStandardOrientedBox );
					double[] interdimerGoofyDistances = Intersect.getDistancesFromPointIn( interdimerGoofyQp, interdimerGoofyOrientedBox );
					for( int n=0; n<Vector3.Dimension*2; n++ )
					{
						if( intradimerDistances[n] < intradimerMinDistances[n] )
						{
							intradimerMinDistances[n] = intradimerDistances[n];
						}
						if( interdimerStandardDistances[n] < interdimerStandardMinDistances[n] )
						{
							interdimerStandardMinDistances[n] = interdimerStandardDistances[n];
						}
						if( interdimerGoofyDistances[n] < interdimerGoofyMinDistances[n] )
						{
							interdimerGoofyMinDistances[n] = interdimerGoofyDistances[n];
						}
					}
				}
				
				/* DEBUG: calculate the "tightness" of the bounds
				System.out.print( "intradimer bound tightness:" );
				for( int n=0; n<Vector.Dimension*2; n++ )
				{
					System.out.print( "\t" + intradimerMinDistances[n] );
				}
				System.out.println( "" );
				System.out.println( intradimerOrientedBox );
				
				System.out.print( "interdimer bound tightness:" );
				for( int n=0; n<Vector.Dimension*2; n++ )
				{
					System.out.print( "\t" + interdimerMinDistances[n] );
				}
				System.out.println( "" );
				System.out.println( interdimerOrientedBox );
				
				// TEMP: just do one
				return;
				*/
			}
		}
	}
	
	public void testAxisAlignedBoxBound( )
	{
		// handle execution flags
		if( !EnableTestAxisAlignedBoxBound || !EnableLongTests )
		{
			return;
		}
		
		// setup
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		double halfBeta = 2.0 * Math.PI / (double)TestMain.getSearchContext().getNumSubunits();
		
		// min distances vars
		double[] intradimerMinDistances = new double[Vector3.Dimension * 2];
		double[] interdimerStandardMinDistances = new double[Vector3.Dimension * 2];
		double[] interdimerGoofyMinDistances = new double[Vector3.Dimension * 2];
		
		Vector3 f = new Vector3();
		Vector3 c = new Vector3();
		Vector3 r = new Vector3();
		BoundContext intradimerBoundContext = new BoundContext( DnGridCell.NumTCorners );
		intradimerBoundContext.type = SubunitType.Intradimer;
		BoundContext interdimerStandardBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerStandardBoundContext.type = SubunitType.InterdimerStandard;
		BoundContext interdimerGoofyBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerGoofyBoundContext.type = SubunitType.InterdimerGoofy;
		Matrix3 rot = new Matrix3();
		Vector3 intradimerQp = new Vector3();
		Vector3 interdimerStandardQp = new Vector3();
		Vector3 interdimerGoofyQp = new Vector3();
		AxisAlignedBox intradimerAxisAlignedBox = new AxisAlignedBox();
		AxisAlignedBox interdimerStandardAxisAlignedBox = new AxisAlignedBox();
		AxisAlignedBox interdimerGoofyAxisAlignedBox = new AxisAlignedBox();
		
		// for each cell...
		for( DnGridCell cell : getCustomCells() )
		{
			// get atom positions
			double monomerRadius = TestMain.getSearchContext().getBoundingSphere().radius;
			PointIterator iterAtom = new PointIterator(
				Vector3.Dimension,
				new int[] { 10, 10, 10 },
				new double[] { -monomerRadius, -monomerRadius, -monomerRadius },
				new double[] { monomerRadius, monomerRadius, monomerRadius }
			);
			while( iterAtom.hasNext() )
			{
				Vector3 q = new Vector3( iterAtom.next() );
				
				// get the axis aligned box bounds
				cellChecker.getBoundContext( intradimerBoundContext, cell, new Atom() );
				cellChecker.getSmallAxisAlignedBox( intradimerAxisAlignedBox, intradimerBoundContext, q );
				cellChecker.getBoundContext( interdimerStandardBoundContext, cell, new Atom() );
				cellChecker.getSmallAxisAlignedBox( interdimerStandardAxisAlignedBox, interdimerStandardBoundContext, q );
				cellChecker.getBoundContext( interdimerGoofyBoundContext, cell, new Atom() );
				cellChecker.getSmallAxisAlignedBox( interdimerGoofyAxisAlignedBox, interdimerGoofyBoundContext, q );
				
				// reset distances
				for( int i=0; i<Vector3.Dimension*2; i++ )
				{
					intradimerMinDistances[i] = Double.POSITIVE_INFINITY;
					interdimerStandardMinDistances[i] = Double.POSITIVE_INFINITY;
					interdimerGoofyMinDistances[i] = Double.POSITIVE_INFINITY;
				}
				
				PointIterator iterAngles = new PointIterator(
					Vector3.Dimension,
					new int[] { 10, 10, 10 },
					new double[] { cell.min.phi, cell.min.theta, cell.min.rho },
					new double[] { cell.max.phi, cell.max.theta, cell.max.rho }
				);
				while( iterAngles.hasNext() )
				{
					Vector3 angles = new Vector3( iterAngles.next() );
					
					// do the rotations
					Axes.getFlipVector( f, angles.x, angles.y, angles.z );
					Matrix3.getRotation( rot, f, intradimerBoundContext.alpha );
					intradimerQp.set( q );
					rot.multiply( intradimerQp );
					assertPointIn( intradimerQp, intradimerAxisAlignedBox );
					
					Axes.getCVector( c, angles.x, angles.y, angles.z, halfBeta );
					Matrix3.getRotation( rot, c, interdimerStandardBoundContext.alpha );
					interdimerStandardQp.set( q );
					rot.multiply( interdimerStandardQp );
					assertPointIn( interdimerStandardQp, interdimerStandardAxisAlignedBox );
					
					Axes.getRotationVector( r, angles.x, angles.y );
					Matrix3.getRotation( rot, r, interdimerGoofyBoundContext.alpha );
					interdimerGoofyQp.set( q );
					rot.multiply( interdimerGoofyQp );
					assertPointIn( interdimerGoofyQp, interdimerGoofyAxisAlignedBox );
					
					// calculate the distances
					double[] intradimerDistances = Intersect.getDistancesFromPointIn( intradimerQp, intradimerAxisAlignedBox );
					double[] interdimerStandardDistances = Intersect.getDistancesFromPointIn( interdimerStandardQp, interdimerStandardAxisAlignedBox );
					double[] interdimerGoofyDistances = Intersect.getDistancesFromPointIn( interdimerGoofyQp, interdimerGoofyAxisAlignedBox );
					for( int n=0; n<Vector3.Dimension*2; n++ )
					{
						if( intradimerDistances[n] < intradimerMinDistances[n] )
						{
							intradimerMinDistances[n] = intradimerDistances[n];
						}
						if( interdimerStandardDistances[n] < interdimerStandardMinDistances[n] )
						{
							interdimerStandardMinDistances[n] = interdimerStandardDistances[n];
						}
						if( interdimerGoofyDistances[n] < interdimerGoofyMinDistances[n] )
						{
							interdimerGoofyMinDistances[n] = interdimerGoofyDistances[n];
						}
					}
				}
				
				/* DEBUG: calculate the "tightness" of the bounds
				System.out.print( "intradimer bound tightness:" );
				for( int n=0; n<Vector.Dimension*2; n++ )
				{
					System.out.print( "\t" + intradimerMinDistances[n] );
				}
				System.out.println( "" );
				System.out.println( intradimerAxisAlignedBox );
				
				System.out.print( "interdimer bound tightness:" );
				for( int n=0; n<Vector.Dimension*2; n++ )
				{
					System.out.print( "\t" + interdimerMinDistances[n] );
				}
				System.out.println( "" );
				System.out.println( interdimerAxisAlignedBox );
				*/
			}
		}
	}
	
	public void testBound( )
	{
		// handle execution flags
		if( !EnableTestBound || !EnableLongTests )
		{
			return;
		}
		
		// setup
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		double halfBeta = 2.0 * Math.PI / (double)TestMain.getSearchContext().getNumSubunits();
		
		// min distances vars
		double[] intradimerMinDistances = new double[Vector3.Dimension * 2];
		double[] interdimerStandardMinDistances = new double[Vector3.Dimension * 2];
		double[] interdimerGoofyMinDistances = new double[Vector3.Dimension * 2];
		
		Vector3 f = new Vector3();
		Vector3 c = new Vector3();
		Vector3 r = new Vector3();
		BoundContext intradimerBoundContext = new BoundContext( DnGridCell.NumTCorners );
		intradimerBoundContext.type = SubunitType.Intradimer;
		BoundContext interdimerStandardBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerStandardBoundContext.type = SubunitType.InterdimerStandard;
		BoundContext interdimerGoofyBoundContext = new BoundContext( DnGridCell.NumTCorners );
		interdimerGoofyBoundContext.type = SubunitType.InterdimerGoofy;
		Matrix3 rot = new Matrix3();
		Vector3 intradimerQp = new Vector3();
		Vector3 interdimerStandardQp = new Vector3();
		Vector3 interdimerGoofyQp = new Vector3();
		AxisAlignedBox intradimerBound = new AxisAlignedBox();
		AxisAlignedBox interdimerStandardBound = new AxisAlignedBox();
		AxisAlignedBox interdimerGoofyBound = new AxisAlignedBox();
		
		// for each cell...
		for( DnGridCell cell : getCustomCells() )
		{
			// TEMP: print cell deltas
			//System.out.println( cell );
			//System.out.println( "Cell Deltas: " + cell.getDeltas() );
			
			// get atom positions
			double monomerRadius = TestMain.getSearchContext().getBoundingSphere().radius;
			PointIterator iterAtom = new PointIterator(
				Vector3.Dimension,
				new int[] { 4, 4, 4 },
				new double[] { -monomerRadius, -monomerRadius, -monomerRadius },
				new double[] { monomerRadius, monomerRadius, monomerRadius }
			);
			while( iterAtom.hasNext() )
			{
				Atom atom = new Atom();
				atom.setPosition( new Vector3( iterAtom.next() ) );
				
				// get the bounds
				cellChecker.getBoundContext( intradimerBoundContext, cell, atom );
				cellChecker.getBoundingBox( intradimerBound, intradimerBoundContext );
				cellChecker.getBoundContext( interdimerStandardBoundContext, cell, atom );
				cellChecker.getBoundingBox( interdimerStandardBound, interdimerStandardBoundContext );
				cellChecker.getBoundContext( interdimerGoofyBoundContext, cell, atom );
				cellChecker.getBoundingBox( interdimerGoofyBound, interdimerGoofyBoundContext );
				
				// reset distances
				for( int i=0; i<Vector3.Dimension*2; i++ )
				{
					intradimerMinDistances[i] = Double.POSITIVE_INFINITY;
					interdimerStandardMinDistances[i] = Double.POSITIVE_INFINITY;
					interdimerGoofyMinDistances[i] = Double.POSITIVE_INFINITY;
				}
				
				// pick a t
				PointIterator iterPos = new PointIterator(
					Vector3.Dimension,
					new int[] { 6, 6, 6 },
					new double[] { cell.min.x, cell.min.y, cell.min.z },
					new double[] { cell.max.x, cell.max.y, cell.max.z }
				);
				while( iterPos.hasNext() )
				{
					Vector3 t = new Vector3( iterPos.next() );
					Vector3 q = new Vector3( atom.getPosition() );
					q.subtract( t );
									
					PointIterator iterAngles = new PointIterator(
						Vector3.Dimension,
						new int[] { 6, 6, 6 },
						new double[] { cell.min.phi, cell.min.theta, cell.min.rho },
						new double[] { cell.max.phi, cell.max.theta, cell.max.rho }
					);
					while( iterAngles.hasNext() )
					{
						Vector3 angles = new Vector3( iterAngles.next() );
						
						// do the rotations
						Axes.getFlipVector( f, angles.x, angles.y, angles.z );
						Matrix3.getRotation( rot, f, intradimerBoundContext.alpha );
						intradimerQp.set( q );
						rot.multiply( intradimerQp );
						intradimerQp.add( t );
						assertPointIn( intradimerQp, intradimerBound );
						
						Axes.getCVector( c, angles.x, angles.y, angles.z, halfBeta );
						Matrix3.getRotation( rot, c, interdimerStandardBoundContext.alpha );
						interdimerStandardQp.set( q );
						rot.multiply( interdimerStandardQp );
						interdimerStandardQp.add( t );
						assertPointIn( interdimerStandardQp, interdimerStandardBound );
						
						Axes.getRotationVector( r, angles.x, angles.y );
						Matrix3.getRotation( rot, r, interdimerGoofyBoundContext.alpha );
						interdimerGoofyQp.set( q );
						rot.multiply( interdimerGoofyQp );
						interdimerGoofyQp.add( t );
						assertPointIn( interdimerGoofyQp, interdimerGoofyBound );
						
						// calculate the distances
						double[] intradimerDistances = Intersect.getDistancesFromPointIn( intradimerQp, intradimerBound );
						double[] interdimerStandardDistances = Intersect.getDistancesFromPointIn( interdimerStandardQp, interdimerStandardBound );
						double[] interdimerGoofyDistances = Intersect.getDistancesFromPointIn( interdimerGoofyQp, interdimerGoofyBound );
						
						// TEMP
						assertDistancesPositive( intradimerDistances, intradimerQp, intradimerBound );
						assertDistancesPositive( interdimerStandardDistances, interdimerStandardQp, interdimerStandardBound );
						assertDistancesPositive( interdimerGoofyDistances, interdimerGoofyQp, interdimerGoofyBound );
						
						for( int n=0; n<Vector3.Dimension*2; n++ )
						{
							if( intradimerDistances[n] < intradimerMinDistances[n] )
							{
								intradimerMinDistances[n] = intradimerDistances[n];
							}
							if( interdimerStandardDistances[n] < interdimerStandardMinDistances[n] )
							{
								interdimerStandardMinDistances[n] = interdimerStandardDistances[n];
							}
							if( interdimerGoofyDistances[n] < interdimerGoofyMinDistances[n] )
							{
								interdimerGoofyMinDistances[n] = interdimerGoofyDistances[n];
							}
						}
					}
				}
				
				/* DEBUG: calculate the "tightness" of the bounds
				System.out.print( "intradimer bound tightness:" );
				for( int n=0; n<Vector.Dimension*2; n++ )
				{
					System.out.print( "\t" + intradimerMinDistances[n] );
				}
				System.out.println( "" );
				
				System.out.print( "interdimer bound tightness:" );
				for( int n=0; n<Vector.Dimension*2; n++ )
				{
					System.out.print( "\t" + interdimerMinDistances[n] );
				}
				System.out.println( "" );
				
				// TEMP
				//return;
				*/
			}
		}
		// Jeff: 5/28/2008 - hahaha, these loops are ridiculous!
	}
	
	// not a real test - disabled
	/*
	public void XtestBallRadius( )
	{
		// setup
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		
		// pick our cell
		GridCell cell = new GridCell(
			new GridPoint( 1.0, 1.0, 1.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0 ),
			new GridPoint( 2.0, 2.0, 2.0, 5.5 * Math.PI / 8.0, 5.5 * Math.PI / 8.0, 5.5 * Math.PI / 8.0 )
		);
		
		// get the bound context
		BoundContext intradimerBoundContext = cellChecker.getBoundContext( cell, new Atom(), BoundContext.Type.Intradimer );
		
		// TEMP: mess with epsilon
		intradimerBoundContext.epsilon = 0.01;
		
		// print cell deltas
		System.out.println( cell );
		System.out.println( "Cell Deltas: " + cell.getDeltas() );
		System.out.println( "Epsilon: " + intradimerBoundContext.epsilon );
		
		// vars for offby states
		double minOffBy = Double.POSITIVE_INFINITY;
		double maxOffBy = Double.NEGATIVE_INFINITY;
		double sumOffBy = 0.0;
		int countOffBy = 0;
		
		// get atom positions
		//double monomerRadius = TestMain.getSearchContext().getBoundingSphere().radius;
		PointIterator iterAtom = new PointIterator(
			3,
			new int[] { 10, 10, 10 },
			new double[] { -monomerRadius, -monomerRadius, -monomerRadius },
			new double[] { monomerRadius, monomerRadius, monomerRadius }
		);//
		PointIterator iterAtom = new PointIterator(
			3,
			new int[] { 10, 10, 10 },
			new double[] { -100, -100, -100 },
			new double[] { 100, 100, 100 }
		);
		while( iterAtom.hasNext() )
		{
			Vector k = new Vector( iterAtom.next() );
			
			// print out k
			//System.out.println( "k=" + k );
			
			double rs = k.getLength();
			double rb = 2.0 * intradimerBoundContext.epsilon * ( Math.abs( k.getDot( intradimerBoundContext.a ) ) + rs );
			
			// approximate the actual rb
			Vector u = Matrix3.getRotation( intradimerBoundContext.a, intradimerBoundContext.alpha ).multiply( k );
			double approxRb = 0.0;
			PointIterator iterAngles = new PointIterator(
				Vector.Dimension,
				new int[] { 12, 12, 12 },
				new double[] { cell.min.phi, cell.min.theta, cell.min.rho },
				new double[] { cell.max.phi, cell.max.theta, cell.max.rho }
			);
			while( iterAngles.hasNext() )
			{
				Vector angles = new Vector( iterAngles.next() );
				Vector f = Axes.getFlipVector( angles.x, angles.y, angles.z );
				Vector kp = Matrix3.getRotation( f, intradimerBoundContext.alpha ).multiply( k );
				
				double radius = kp.getDistance( u );
				if( radius > approxRb )
				{
					approxRb = radius;
				}
			}
			
			// output the results for this cell
			double offBy = ( rb + approxRb ) / approxRb - 1.0;
			//System.out.println( String.format(
				"\trs=%1$6.2f\trb=%2$6.2f\tapproxRb=%3$6.2f\toffBy=%4$6.2f",
				rs, rb, approxRb, offBy
			) );
			//
			
			// update offby stats
			if( offBy < minOffBy )
			{
				minOffBy = offBy;
			}
			if( offBy > maxOffBy )
			{
				maxOffBy = offBy;
			}
			sumOffBy += offBy;
			countOffBy++;
		}
		
		// output offby stats
		System.out.println( "Offby: min=" + minOffBy + "\tmax=" + maxOffBy + "\tavg=" + ( sumOffBy / (double)countOffBy ) );
	}
	*/
	
	/* not a real test - disabled
	public void XtestIterationsToEpsilon( )
	{
		// setup
		DnCellCheckerPublisher cellChecker = new DnCellCheckerPublisher();
		
		// get a single starting cell
		CellIterator iterCell = (new CellGateway()).getStartingCells();
		GridCell cell = iterCell.next();
		
		// see how many divisions it takes to get an epsilon that's within 0.1
		int numDivisions = 0;
		GridCell leftCell = new GridCell();
		GridCell rightCell = new GridCell();
		while( true )
		{
			// calculate epsilon
			BoundContext boundContext = cellChecker.getBoundContext( cell, new Atom(), BoundContext.Type.Intradimer );
			
			// output epsilon
			System.out.println( "divisions=" + numDivisions + "\tepsilon=" + boundContext.epsilon );
			
			if( boundContext.epsilon <= 0.01 )
			{
				break;
			}
			else
			{
				// divide
				numDivisions++;
				cell.split( leftCell, rightCell, GridCell.Subspace.Rotations );
				cell = leftCell;
			}
		}
	}
	*/
	
	@SuppressWarnings( "unused" )
	private ArrayList<DnGridCell> getStartingCells( )
	{
		return ((DnSearchSpace)TestMain.getSearchContext().getSearchSpace()).getInitialCells();
	}
	
	private ArrayList<DnGridCell> getCustomCells( )
	{
		ArrayList<DnGridCell> cells = new ArrayList<DnGridCell>();
		/*
		cells.add( new GridCell(
			new GridPoint( 1.0, 1.0, 1.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0 ),
			new GridPoint( 1.01, 1.01, 1.01, 5.01 * Math.PI / 8.0, 5.01 * Math.PI / 8.0, 5.01 * Math.PI / 8.0 )
		)
		cells.add( new GridCell(
			new GridPoint( 1.0, 1.0, 1.0, 1.0 * Math.PI / 2.0, 1.0 * Math.PI / 2.0, 1.0 * Math.PI / 1.0 ),
			new GridPoint( 2.0, 2.0, 2.0, 2.0 * Math.PI / 2.0, 2.0 * Math.PI / 2.0, 2.0 * Math.PI / 2.0 )
		)
		cells.add( new GridCell(
			new GridPoint( -20.0, -20.0, -20.0, 1.0 * Math.PI / 2.0, 1.0 * Math.PI / 2.0, 1.0 * Math.PI / 2.0 ),
			new GridPoint( 20.0, 20.0, 20.0, 2.0 * Math.PI / 2.0, 2.0 * Math.PI / 2.0, 2.0 * Math.PI / 2.0 )
		)
		cells.add( new GridCell(
			new GridPoint( -20.0, -20.0, -20.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0 ),
			new GridPoint( 20.0, 20.0, 20.0, 6.0 * Math.PI / 8.0, 6.0 * Math.PI / 8.0, 6.0 * Math.PI / 8.0 )
		)
		cells.add( new GridCell(
			new GridPoint( 1.0, 1.0, 1.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0, 5.0 * Math.PI / 8.0 ),
			new GridPoint( 2.0, 2.0, 2.0, 6.0 * Math.PI / 8.0, 6.0 * Math.PI / 8.0, 6.0 * Math.PI / 8.0 )
		),
		cells.add( new GridCell(
			new GridPoint( -9.573380608387593, -9.573380608387593, -4.7866903041937965, 1.521708941582556, 0.09817477042468103, 1.5339807878856413 ),
			new GridPoint( -7.180035456290694, -7.180035456290694, -2.3933451520968982, 1.5339807878856413, 0.11044661672776616, 1.5462526341887264 )
		),
		cells.add( new GridCell(
			new GridPoint( -9.573380608387593, -9.573380608387593, -2.3933451520968982, 1.521708941582556, 0.09817477042468103, 1.5339807878856413 ),
			new GridPoint( -7.180035456290694, -7.180035456290694, 0.0, 1.5339807878856413, 0.11044661672776616, 1.5462526341887264 )
		),
		cells.add( new GridCell(
			new GridPoint( -9.573380608387593, -9.573380608387593, -4.7866903041937965, 1.4971652489763856, 0.1227184630308513, 1.5585244804918115 ),
			new GridPoint( -7.180035456290694, -7.180035456290694, -2.3933451520968982, 1.5094370952794707, 0.1349903093339364, 1.5707963267948966 )
		),
		cells.add( new GridCell(
			new GridPoint( -9.573380608387593, -4.7866903041937965, -7.180035456290694, 0.6135923151542565, 2.9329712664373457, 0.8467573949128739 ),
			new GridPoint( -7.180035456290694, -2.3933451520968982, -4.7866903041937965, 0.6197282383057992, 2.945243112740431, 0.859029241215959 )
		),
		cells.add( new GridCell(
			new GridPoint( -9.573380608387593, -4.7866903041937965, -7.180035456290694, 0.6197282383057992, 2.9329712664373457, 0.8467573949128739 ),
			new GridPoint( -7.180035456290694, -2.3933451520968982, -4.7866903041937965, 0.6258641614573417, 2.945243112740431, 0.859029241215959 )
		),
		cells.add( new GridCell(
			new GridPoint( -9.573380608387593, -4.7866903041937965, -7.180035456290694, 0.6258641614573417, 2.9329712664373457, 0.859029241215959 ),
			new GridPoint( -7.180035456290694, -2.3933451520968982, -4.7866903041937965, 0.6320000846088842, 2.945243112740431, 0.8713010875190441 )
		)
		*/
		// these cells are from iteration 45 of a search so they are very small.
		cells.add( new DnGridCell(
			new DnGridPoint( 5.158960041078667, 3.9684308008297435, -3.703868747441094, 2.086213871524472, 0.5399612373357456, 1.368310862793992 ),
			new DnGridPoint( 5.291241067772992, 4.232992854218393, -3.439306694052444, 2.098485717827557, 0.5522330836388307, 1.3744467859455345 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.762116960995693, 3.9684308008297435, -3.703868747441094, 2.1353012567368124, 0.5399612373357456, 1.3621749396424492 ),
			new DnGridPoint( 4.894397987690017, 4.232992854218393, -3.439306694052444, 2.1475731030398975, 0.5522330836388307, 1.368310862793992 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.762116960995693, 3.9684308008297435, -3.703868747441094, 2.110757564130642, 0.5522330836388307, 1.380582709097077 ),
			new DnGridPoint( 4.894397987690017, 4.232992854218393, -3.439306694052444, 2.1230294104337273, 0.5645049299419159, 1.3867186322486198 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 5.026679014384342, 3.9684308008297435, -3.703868747441094, 2.239611950313036, 0.5399612373357456, 1.2762720155208536 ),
			new DnGridPoint( 5.158960041078667, 4.232992854218393, -3.439306694052444, 2.2457478734645786, 0.5522330836388307, 1.2885438618239387 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 5.026679014384342, 3.9684308008297435, -3.703868747441094, 2.1475731030398975, 0.5399612373357456, 4.479223900626072 ),
			new DnGridPoint( 5.158960041078667, 4.232992854218393, -3.439306694052444, 2.1598449493429825, 0.5522330836388307, 4.485359823777614 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.762116960995693, 3.703868747441094, -3.9684308008297435, 2.1475731030398975, 0.5890486225480862, 4.509903516383785 ),
			new DnGridPoint( 4.894397987690017, 3.9684308008297435, -3.703868747441094, 2.1598449493429825, 0.6013204688511713, 4.516039439535327 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.762116960995693, 3.9684308008297435, -3.703868747441094, 2.221204180858408, 0.5522330836388307, 4.448544284868358 ),
			new DnGridPoint( 4.894397987690017, 4.232992854218393, -3.439306694052444, 2.233476027161493, 0.5645049299419159, 4.4546802080199015 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.497554907607043, 3.9684308008297435, -3.703868747441094, 2.196660488252238, 0.5399612373357456, 4.485359823777614 ),
			new DnGridPoint( 4.629835934301368, 4.232992854218393, -3.439306694052444, 2.208932334555323, 0.5522330836388307, 4.491495746929157 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.762116960995693, 3.9684308008297435, -3.703868747441094, 2.1843886419491527, 0.5399612373357456, 4.466952054322986 ),
			new DnGridPoint( 4.894397987690017, 4.232992854218393, -3.439306694052444, 2.196660488252238, 0.5522330836388307, 4.473087977474529 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.629835934301368, 3.9684308008297435, -3.703868747441094, 2.208932334555323, 0.5399612373357456, 4.466952054322986 ),
			new DnGridPoint( 4.762116960995693, 4.232992854218393, -3.439306694052444, 2.221204180858408, 0.5522330836388307, 4.473087977474529 )
		) );
		cells.add( new DnGridCell(
			new DnGridPoint( 4.629835934301368, 3.9684308008297435, -3.703868747441094, 2.1353012567368124, 0.5522330836388307, 4.516039439535327 ),
			new DnGridPoint( 4.762116960995693, 4.232992854218393, -3.439306694052444, 2.1475731030398975, 0.5645049299419159, 4.52217536268687 )
		) );
		return cells;
	}
	
	private void assertPointIn( Vector3 point, AxisAlignedBox box )
	{
		if( !Intersect.isPointIn( point, box ) )
		{
			fail( "point lies outside axis aligned box!\n\t" + point + "\n\t" + box );
		}
	}
	
	private void assertPointIn( Vector3 point, OrientedBox box )
	{
		if( !Intersect.isPointIn( point, box ) )
		{
			fail( "point lies outside oriented box!\n\t" + point + "\n\t" + box );
		}
	}
	
	private void assertDistancesPositive( double[] distances, Vector3 qp, AxisAlignedBox bound )
	{
		for( int i=0; i<Vector3.Dimension*2; i++ )
		{
			if( !CompareReal.gte( distances[i], 0.0 ) )
			{
				fail( "point lies outside axis aligned box!!\n\t" + qp + "\n\t" + bound );
				return;
			}
		}
	}
}
