# Path: pyinform/error.py
# class InformError(Exception):
#     """
#     InformError signifies an error occurred in a call to inform.
#     """
# 
#     def __init__(self, e=-1, func=None):
#         msg = error_string(e)
# 
#         if func is None:
#             msg = "an inform error occurred - \"{}\"".format(msg)
#         else:
#             msg = "an inform error occurred in `{}` - \"{}\"".format(func, msg)
# 
#         super(InformError, self).__init__(msg)
# 
#         self.error_code = e if isinstance(e, ErrorCode) else ErrorCode(e)
# 
# Path: pyinform/transferentropy.py
# def transfer_entropy(source, target, k, condition=None, local=False):
#     """
#     Compute the local or average transfer entropy from one time series to
#     another with target history length *k*. Optionally, time series can be
#     provided against which to *condition*.
# 
#     :param source: the source time series
#     :type source: sequence or ``numpy.ndarray``
#     :param target: the target time series
#     :type target: sequence or ``numpy.ndarray``
#     :param int k: the history length
#     :param condition: time series of any conditions
#     :type condition: sequence or ``numpy.ndarray``
#     :param bool local: compute the local transfer entropy
#     :returns: the average or local transfer entropy
#     :rtype: float or ``numpy.ndarray``
#     :raises ValueError: if the time series have different shapes
#     :raises ValueError: if either time series has no initial conditions
#     :raises ValueError: if either time series is greater than 2-D
#     :raises InformError: if an error occurs within the ``inform`` C call
#     """
#     ys = np.ascontiguousarray(source, np.int32)
#     xs = np.ascontiguousarray(target, np.int32)
#     cs = np.ascontiguousarray(condition, np.int32) if condition is not None else None
# 
#     if xs.shape != ys.shape:
#         raise ValueError("source and target timeseries are different shapes")
#     elif xs.ndim > 2:
#         raise ValueError("source and target have too great a dimension; must be 2 or less")
# 
#     if cs is None:
#         pass
#     elif cs.ndim == 1 and cs.shape != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim == 2 and xs.ndim == 1 and cs.shape[1:] != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim == 2 and xs.ndim == 2 and cs.shape != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim == 3 and cs.shape[1:] != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim > 3:
#         raise ValueError("condition has too great a dimension; must be 3 or less")
# 
#     ydata = ys.ctypes.data_as(POINTER(c_int))
#     xdata = xs.ctypes.data_as(POINTER(c_int))
#     cdata = cs.ctypes.data_as(POINTER(c_int)) if cs is not None else None
# 
#     if cs is None:
#         b = max(2, max(np.amax(xs), np.amax(ys)) + 1)
#     else:
#         b = max(2, max(np.amax(xs), np.amax(ys), np.amax(cs)) + 1)
# 
#     if cs is None:
#         z = 0
#     elif cs.ndim == 1 or (cs.ndim == 2 and xs.ndim == 2):
#         z = 1
#     elif cs.ndim == 3 or (cs.ndim == 2 and xs.ndim == 1):
#         z = cs.shape[0]
#     else:
#         raise RuntimeError("unexpected state: condition and source are inconsistent shapes")
# 
#     if xs.ndim == 1:
#         n, m = 1, xs.shape[0]
#     else:
#         n, m = xs.shape
# 
#     e = ErrorCode(0)
# 
#     if local is True:
#         q = max(0, m - k)
#         te = np.empty((n, q), dtype=np.float64)
#         out = te.ctypes.data_as(POINTER(c_double))
#         _local_transfer_entropy(ydata, xdata, cdata, c_ulong(z), c_ulong(n), c_ulong(m), c_int(b), c_ulong(k), out, byref(e))
#     else:
#         te = _transfer_entropy(ydata, xdata, cdata, c_ulong(z), c_ulong(n), c_ulong(m), c_int(b), c_ulong(k), byref(e))
# 
#     error_guard(e)
# 
#     return te


import unittest
import numpy as np
from pyinform.error import InformError
from pyinform.transferentropy import transfer_entropy
# Copyright 2016-2019 Douglas G. Moore. All rights reserved.
# Use of this source code is governed by a MIT
# license that can be found in the LICENSE file.


class TestTransferEntropy(unittest.TestCase):
    def test_transfer_entropy_empty(self):
        with self.assertRaises(ValueError):
            transfer_entropy([], [], 1)

        with self.assertRaises(ValueError):
            transfer_entropy([], [1, 1, 1], 1)

        with self.assertRaises(ValueError):
            transfer_entropy([1, 1, 1], [], 1)

    def test_transfer_entropy_dimension_too_great(self):
        with self.assertRaises(ValueError):
            transfer_entropy([[[0, 1, 1, 0]]], [[[1, 1, 1, 1]]], 2)

    def test_transfer_entropy_shape_mismatch(self):
        with self.assertRaises(ValueError):

# Path: pyinform/error.py
# class InformError(Exception):
#     """
#     InformError signifies an error occurred in a call to inform.
#     """
# 
#     def __init__(self, e=-1, func=None):
#         msg = error_string(e)
# 
#         if func is None:
#             msg = "an inform error occurred - \"{}\"".format(msg)
#         else:
#             msg = "an inform error occurred in `{}` - \"{}\"".format(func, msg)
# 
#         super(InformError, self).__init__(msg)
# 
#         self.error_code = e if isinstance(e, ErrorCode) else ErrorCode(e)
# 
# Path: pyinform/transferentropy.py
# def transfer_entropy(source, target, k, condition=None, local=False):
#     """
#     Compute the local or average transfer entropy from one time series to
#     another with target history length *k*. Optionally, time series can be
#     provided against which to *condition*.
# 
#     :param source: the source time series
#     :type source: sequence or ``numpy.ndarray``
#     :param target: the target time series
#     :type target: sequence or ``numpy.ndarray``
#     :param int k: the history length
#     :param condition: time series of any conditions
#     :type condition: sequence or ``numpy.ndarray``
#     :param bool local: compute the local transfer entropy
#     :returns: the average or local transfer entropy
#     :rtype: float or ``numpy.ndarray``
#     :raises ValueError: if the time series have different shapes
#     :raises ValueError: if either time series has no initial conditions
#     :raises ValueError: if either time series is greater than 2-D
#     :raises InformError: if an error occurs within the ``inform`` C call
#     """
#     ys = np.ascontiguousarray(source, np.int32)
#     xs = np.ascontiguousarray(target, np.int32)
#     cs = np.ascontiguousarray(condition, np.int32) if condition is not None else None
# 
#     if xs.shape != ys.shape:
#         raise ValueError("source and target timeseries are different shapes")
#     elif xs.ndim > 2:
#         raise ValueError("source and target have too great a dimension; must be 2 or less")
# 
#     if cs is None:
#         pass
#     elif cs.ndim == 1 and cs.shape != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim == 2 and xs.ndim == 1 and cs.shape[1:] != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim == 2 and xs.ndim == 2 and cs.shape != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim == 3 and cs.shape[1:] != xs.shape:
#         raise ValueError("condition has a shape that's inconsistent with the source and target")
#     elif cs.ndim > 3:
#         raise ValueError("condition has too great a dimension; must be 3 or less")
# 
#     ydata = ys.ctypes.data_as(POINTER(c_int))
#     xdata = xs.ctypes.data_as(POINTER(c_int))
#     cdata = cs.ctypes.data_as(POINTER(c_int)) if cs is not None else None
# 
#     if cs is None:
#         b = max(2, max(np.amax(xs), np.amax(ys)) + 1)
#     else:
#         b = max(2, max(np.amax(xs), np.amax(ys), np.amax(cs)) + 1)
# 
#     if cs is None:
#         z = 0
#     elif cs.ndim == 1 or (cs.ndim == 2 and xs.ndim == 2):
#         z = 1
#     elif cs.ndim == 3 or (cs.ndim == 2 and xs.ndim == 1):
#         z = cs.shape[0]
#     else:
#         raise RuntimeError("unexpected state: condition and source are inconsistent shapes")
# 
#     if xs.ndim == 1:
#         n, m = 1, xs.shape[0]
#     else:
#         n, m = xs.shape
# 
#     e = ErrorCode(0)
# 
#     if local is True:
#         q = max(0, m - k)
#         te = np.empty((n, q), dtype=np.float64)
#         out = te.ctypes.data_as(POINTER(c_double))
#         _local_transfer_entropy(ydata, xdata, cdata, c_ulong(z), c_ulong(n), c_ulong(m), c_int(b), c_ulong(k), out, byref(e))
#     else:
#         te = _transfer_entropy(ydata, xdata, cdata, c_ulong(z), c_ulong(n), c_ulong(m), c_int(b), c_ulong(k), byref(e))
# 
#     error_guard(e)
# 
#     return te


# Path: test/test_transferentropy.py
import unittest
import numpy as np
from pyinform.error import InformError
from pyinform.transferentropy import transfer_entropy

# Copyright 2016-2019 Douglas G. Moore. All rights reserved.
# Use of this source code is governed by a MIT
# license that can be found in the LICENSE file.


class TestTransferEntropy(unittest.TestCase):
    def test_transfer_entropy_empty(self):
        with self.assertRaises(ValueError):
            transfer_entropy([], [], 1)

        with self.assertRaises(ValueError):
            transfer_entropy([], [1, 1, 1], 1)

        with self.assertRaises(ValueError):
            transfer_entropy([1, 1, 1], [], 1)

    def test_transfer_entropy_dimension_too_great(self):
        with self.assertRaises(ValueError):
            transfer_entropy([[[0, 1, 1, 0]]], [[[1, 1, 1, 1]]], 2)

    def test_transfer_entropy_shape_mismatch(self):
        with self.assertRaises(ValueError):

            transfer_entropy([1, 1, 1], [1, 1, 1, 1], 1)