# -*- coding: utf-8 -*-
"""Nonlinear Transformation classes


Created on Sat Apr 16 16:06:11 2011

Author: Josef Perktold
License : BSD
"""

import numpy as np


class TransformFunction(object):
    
    def __call__(self, x):
        self.func(x)



## Hump and U-shaped functions


class SquareFunc(TransformFunction):
    '''class to hold quadratic function with inverse function and derivative
 
    using instance methods instead of class methods, if we want extension
    to parameterized function
    '''

    def func(self, x):
        return np.power(x, 2.) 

    def inverseplus(self, x):
        return np.sqrt(x)

    def inverseminus(self, x):
        return 0.0 - np.sqrt(x)

    def derivplus(self, x):
        return 0.5/np.sqrt(x)

    def derivminus(self, x):
        return 0.0 - 0.5/np.sqrt(x)




class NegSquareFunc(TransformFunction):
    '''negative quadratic function
    
    '''
    def func(self, x):
        return -np.power(x,2)

    def inverseplus(self, x):
        return np.sqrt(-x)
    
    def inverseminus(self, x):
        return 0.0 - np.sqrt(-x)
    
    def derivplus(self, x):
        return 0.0 - 0.5/np.sqrt(-x)
    
    def derivminus(self, x):
        return 0.5/np.sqrt(-x)


class AbsFunc(TransformFunction):
    '''class for absolute value transformation
    '''

    def func(self, x):
        return np.abs(x)     
 
    def inverseplus(self, x):
        return x
    
    def inverseminus(self, x):
        return 0.0 - x
    
    def derivplus(self, x):
        return 1.0
    
    def derivminus(self, x):
        return 0.0 - 1.0


## monotonic functions
# more monotone functions in families.links, some for restricted domains


class LogFunc(TransformFunction):
    
    def func(self, x):
        return np.log(x)
        
    def inverse(self, y):
        return np.exp(y)
        
    def deriv(self, x):
        return 1./x
        
class ExpFunc(TransformFunction):

    
    def func(self, x):
        return np.exp(x)
        
    def inverse(self, y):
        return np.log(y)
        
    def deriv(self, x):
        return np.exp(x)


class BoxCoxNonzeroFunc(TransformFunction):
    
    def __init__(self, lamda):
        self.lamda = lamda
    
    def func(self, x):
        return (np.power(x, self.lamda) - 1)/self.lamda
        
    def inverse(self, y):
        return (self.lamda * y + 1)/self.lamda
        
    def deriv(self, x):
        return np.power(x, self.lamda - 1)
        

class AffineFunc(TransformFunction):
    
    def __init__(self, constant, slope):
        self.constant = constant
        self.slope = slope
        
    def func(self, x):
        return self.constant + self.slope * x
        
    def inverse(self, y):
        return (y - self.constant) / self.slope
        
    def deriv(self, x):
        return self.slope
        

class ChainFunc(TransformFunction):
    
    def __init__(self, finn, fout):
        self.finn = finn
        self.fout = fout
        
    def func(self, x):
        return self.fout.func(self.finn.func(x))
        
    def inverse(self, y):
        return self.f1.inverse(self.fout.inverse(y))
        
    def deriv(self, x):
        z = self.finn.func(x)
        return self.fout.deriv(z) * self.finn.deriv(x)


#def inverse(x):
#    return np.divide(1.0,x)
#
#mux, stdx = 0.05, 0.1
#mux, stdx = 9.0, 1.0
#def inversew(x):
#    return 1.0/(1+mux+x*stdx)
#def inversew_inv(x):
#    return (1.0/x - 1.0 - mux)/stdx #.np.divide(1.0,x)-10
#
#def identit(x):
#    return x
    

if __name__ == '__main__':
    absf = AbsFunc()
    absf.func(5) == 5
    absf.func(-5) == 5
    absf.inverseplus(5) == 5
    absf.inverseminus(5) == -5
    
    chainf = ChainFunc(AffineFunc(1,2), BoxCoxNonzeroFunc(2))
    print chainf.func(3.)
    chainf2 = ChainFunc(BoxCoxNonzeroFunc(2), AffineFunc(1,2))
    print chainf.func(3.)

     