#!/usr/bin/env python

# test_qhull.py - test of _qhull module
#
# Copyright 2004, 2006 Floris Bruynooghe
#
# This file is part of Delny.
#
# Delny is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Delny 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
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Delny; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA.
#
#
# Author: Floris Bruynooghe (flub)

"""Test the _qhull module from the delaunay package

This module provides the unittests for the _qhull module of the delaunay
package.
"""


import unittest

import Numeric
import RandomArray

import delaunay._qhull

from test import test_support


class TestQhullFunctions(unittest.TestCase):
    """This tests the types and structures returned."""
    def setUp(self):
        # Prepare a set of points and their neighbours.
        self.set = [(0,0),(2,1),(1,2),(4,0),(0,4),(4,4)]
        result = delaunay._qhull.delny(self.set)
        self.neighbours = result[0]
        self.elements = result[1]
        self.elements_by_index = result[2]

    def test_nb_dict(self):
        # delaunay._qhull.delny: neighbours are returned as a dict?
        self.assert_(isinstance(self.neighbours, dict))

    def test_nb_keys(self):
        # delaunay._qhull.delny: neighbours keys are of correct type?
        for i in self.neighbours.keys():
            self.assert_(isinstance(i, tuple))
            self.assertEqual(len(i), len(self.set[0]))
            for j in i:
                self.assert_(isinstance(j, float))

    def test_nb_values(self):
        # delaunay._qhull.delny: neighbours values are of correct type?
        for i in self.neighbours.values():
            self.assert_(isinstance(i, list))
            for j in i:
                self.assert_(isinstance(j, tuple))
                self.assertEqual(len(j), len(self.set[0]))
                for k in j:
                    self.assert_(isinstance(k, float))

    def test_nb_points(self):
        # delaunay._qhull.delny: all points in neighbours are from the set?
        for i in self.neighbours.keys():
            self.assert_(i in self.set)
        for i in self.neighbours.values():
            for j in i:
                self.assert_(j in self.set)
        
    def test_elem_list(self):
        # delaunay._qhull.delny: returned elements are in a list?
        self.assert_(isinstance(self.elements, list))

    def test_elem(self):
        # delaunay._qhull.delny: returned elements have correct type?
        for i in self.elements:
            self.assert_(isinstance(i, list))
            for j in i:
                self.assert_(isinstance(j, tuple))
                self.assertEqual(len(j), len(self.set[0]))
                for k in j:
                    self.assert_(isinstance(k, float))

    def test_elem_points(self):
        # delaunay._qhull.delny: all points from elements are in the set?
        for i in self.elements:
            for j in i:
                self.assert_(j in self.set)

    def test_elem_index(self):
        # Type of facet_list is correct?
        self.assert_(isinstance(self.elements_by_index, list))
        for i in self.elements_by_index:
            self.assert_(isinstance(i, list))
            for j in i:
                self.assert_(isinstance(j, int))


class TestPredictedOutputs2D(unittest.TestCase):
    """Values used in this class come from the qhull binary"""

    def setUp(self):
        self.set = [(0,0),(2,1),(1,2),(4,0),(0,4),(4,4)]
        self.nb, self.el, self.el_i = delaunay._qhull.delny(self.set)
    
    def test_neighbours(self):
        # delaunay._qhull.delny: neighbours are as predicted? (2D)
        nb = {(0,0): [(2,1), (4,0), (1,2), (0,4)],
              (0,4): [(1,2), (0,0), (4,4)],
              (1,2): [(0,4), (0,0), (2,1), (4,4)],
              (2,1): [(4,0), (0,0), (4,4), (1,2)],
              (4,0): [(2,1), (0,0), (4,4)],
              (4,4): [(2,1), (4,0), (1,2), (0,4)]}
        # sort the lists for each key
        for l1, l2 in zip(nb.values(), self.nb.values()):
            l1.sort()
            l2.sort()
        self.assertEqual(self.nb, nb)
        
    def test_elements(self):
        # delaunay._qhull.delny: elements are as predicted? (2D)
        el = [[(2,1), (4,0), (0,0)],
              [(4,4), (2,1), (4,0)],
              [(1,2), (0,4), (0,0)],
              [(1,2), (2,1), (0,0)],
              [(1,2), (4,4), (0,4)],
              [(1,2), (4,4), (2,1)]]
        for l in el:
            l.sort()
        for l in self.el:
            l.sort()
        el.sort()
        self.el.sort()
        self.assertEqual(self.el, el)

    def test_elements_by_index(self):
        # Elements are as predicted?  (2d)
        el_i = [[1, 3, 0], [5, 1, 3], [2, 4, 0],
                [2, 1, 0], [2, 5, 4], [2, 5, 1]]
        for l in el_i:
            l.sort()
        for l in self.el_i:
            l.sort()
        el_i.sort()
        self.el_i.sort()
        self.assertEqual(self.el_i, el_i)

    def test_square(self):
        # delaunay._qhull.delny: 2D square triangulates?
        set = [(0,0),(1,0),(0,1),(1,1)]
        self.assert_(delaunay._qhull.delny(set))

    def test_three_by_three_square(self):
        # 2D 3x3 square triangulates correctly?
        # XXX This fails currently
        vertices = [(-1,-1),(-1,0),(-1,1),
                    ( 0,-1),( 0,0),( 0,1),
                    ( 1,-1),( 1,0),( 1,1)]
        expected = [[1,3,4],[3,1,0],[3,7,4],[7,3,6],
                    [5,1,4],[1,5,2],[7,5,4],[5,7,8]]
        elements_i = delaunay._qhull.delny(vertices)[2]
        for f in expected:
            f.sort()
        expected.sort()
        for f in elements_i:
            f.sort()
        elements_i.sort()
        self.assertEqual(elements_i, expected)


class TestPredictedOutputs3D(unittest.TestCase):
    """Values used in this class come from the qhull binary"""

    def setUp(self):
        self.set = [(0,0,1), (4,0,1), (0,4,1), (4,4,0), (2,1,2), (1,2,3)]
        self.nb, self.el, self.el_i = delaunay._qhull.delny(self.set)
    
    def test_neigbours(self):
        # delaunay._qhull.delny: neighbours are as predicted? (3D)
        nb = {(0,0,1): [(4,0,1), (0,4,1), (4,4,0), (2,1,2), (1,2,3)],
              (4,0,1): [(0,0,1), (4,4,0), (2,1,2), (1,2,3)],
              (0,4,1): [(0,0,1), (4,4,0), (2,1,2), (1,2,3)],
              (4,4,0): [(0,0,1), (4,0,1), (0,4,1), (2,1,2), (1,2,3)],
              (2,1,2): [(0,0,1), (4,0,1), (0,4,1), (4,4,0), (1,2,3)],
              (1,2,3): [(0,0,1), (4,0,1), (0,4,1), (4,4,0), (2,1,2)]}
        for l in nb.values():
            l.sort()
        for l in self.nb.values():
            l.sort()
        self.assertEqual(self.nb, nb)

    def test_elements(self):
        # delaunay._qhull.delny: elements are as predicted? (3D)
        el = [[(2,1,2), (4,4,0), (4,0,1), (0,0,1)],
              [(2,1,2), (4,4,0), (1,2,3), (4,0,1)],
              [(2,1,2), (1,2,3), (0,4,1), (0,0,1)],
              [(4,4,0), (2,1,2), (0,4,1), (0,0,1)],
              [(4,4,0), (2,1,2), (1,2,3), (0,4,1)]]
        for l in el:
            l.sort()
        for l in self.el:
            l.sort()
        el.sort()
        self.el.sort()
        self.assertEqual(self.el, el)

    def test_elements_by_index(self):
        # Elements by index are as predicted?
        el_i = [[4, 3, 1, 0], [4, 3, 5, 1],
                [4, 5, 2, 0], [3, 4, 2, 0], [3, 4, 5, 2]]
        for l in el_i:
            l.sort()
        for l in self.el_i:
            l.sort()
        el_i.sort()
        self.el_i.sort()
        self.assertEqual(self.el_i, el_i)


class EatingOfNodes(unittest.TestCase):
    def setUp(self):
        self.number = 50
        X = RandomArray.random(self.number)
        Y = RandomArray.random(self.number)
        Z = RandomArray.random(self.number)
        co = Numeric.array([X, Y, Z])
        self.points = []
        for i in range(len(co[0])):
            self.points.append(tuple(co[:,i].tolist()))

    def test_no_nodes(self):
        #_qhull.delny(set) does not eat nodes?
        # Do this many times as it does not always happen
        for i in range(self.number):
            neighbours, elements, elements_by_index \
                        = delaunay._qhull.delny(self.points)
            self.assertEqual(self.number, len(neighbours))


def test_main():
    test_support.run_unittest(TestQhullFunctions,
                              TestPredictedOutputs2D,
                              TestPredictedOutputs3D,
                              EatingOfNodes)


if __name__ == '__main__':
    test_main()
