# -*- coding: iso-8859-1 -*-
#
# quakepy/pmc/PMCFunctions.pyx
# $Id: PMCFunctions.pyx 231 2009-10-05 16:35:27Z fab $
#
# The QuakePy package
# http://www.quakepy.org
#

############################################################################
#    Copyright (C) 2007-2009 by Fabian Euchner and Danijel Schorlemmer     #
#    fabian@fabian-euchner.de                                              #
#                                                                          #
#    This program 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.                                   #
#                                                                          #
#    This program 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 this program; if not, write to the                         #
#    Free Software Foundation, Inc.,                                       #
#    59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             #
############################################################################

"""
The QuakePy package
http://www.quakepy.org
"""

__version__  = '$Id: PMCFunctions.pyx 231 2009-10-05 16:35:27Z fab $'
__revision__ = '$Revision: 231 $'
__author__   = "Danijel Schorlemmer <ds@usc.edu>, Fabian Euchner <fabian@fabian-euchner.de>"
__license__  = "GPL"

import numpy as np

## Cython stuff

cimport  numpy as np
ctypedef np.float_t DTYPE_t


def computeCombinedProbabilities( np.ndarray probabilities not None, combinations, int stationsRequiredForTriggering ):
        """
        compute combined probability for a vector of station probabilities
        input parameter probabilities has to be numpy array

        replaces method in PMC class
        """

        cdef int numUsedStations = probabilities.shape[0]
        #print " numUsedStations = %s" % numUsedStations

        if numUsedStations < stationsRequiredForTriggering:
            return 0.0

        # cdefs of local variables
        cdef int idx
        cdef int num
        
        cdef DTYPE_t prod_1  = 0.0
        cdef DTYPE_t prod_0  = probabilities[:,1].prod()
        
        cdef np.ndarray p    = np.zeros( numUsedStations, dtype=np.float )
        cdef np.ndarray prod = np.zeros( stationsRequiredForTriggering, dtype=np.float )

        # compute prod_1
        for idx in range( 0, numUsedStations ):
            p = probabilities[:,1].copy()
            p[idx] = probabilities[idx, 0]
            prod_1 = prod_1 + p.prod()
            
        for num in range( 2, stationsRequiredForTriggering, 1 ):

            # get combinations list for num (m-tuple of combinations) and numUsedStations
            # get starting index of full list in combinations we have to use:
            # combinations.index[num][numUsedStations]
            # slice ends at end of full list
            
            # use generator method getCombinations
            # combinations have to be pre-computed up to self.stationsRequiredForTriggering-2
            # i.e. for Southern California: no of triggering stations is 4, need pre-computed combinations up to 2
            # i.e. for Northern California: no of triggering stations is 5, need pre-computed combinations up to 3
            for combination in combinations.getCombinations( num, numUsedStations ):

                #print " dim of single combination: %s, %s" % ( len( combination ), combination )
                
                # make copy of "anti"-probabilities (1-P)
                p = probabilities[:,1].copy()

                # dimension of combination is num
                # replace 1-P with P in array p at indices given in combination
                for idx in combination:
                    p[idx] = probabilities[idx, 0]

                prod[num] = prod[num] + p.prod()

        # return overall probability
        return 1 - prod_0 - prod_1 - prod.sum()


def calcRawProbability( np.ndarray pickInfo not None, float magnitude, float distance, calcMagnitudeFromDistance ):
    """
    calc raw probability for given magnitude and distance

    was intended to replace method in PMCInventory class, but not used because it is SLOWER
    """

    cdef int     numSample    = 0

    cdef DTYPE_t probability  = 0.0
    cdef DTYPE_t logDistance  = 0.0

    # if pickInfo has no rows, return probability 0.0
    if pickInfo.shape[0] == 0:
        return ( probability, numSample )

    # create numpy arrays with 4 columns and rows as in pickInfo
    cdef np.ndarray values          = np.zeros( ( pickInfo.shape[0], 4 ), dtype=np.float )
    cdef np.ndarray sample          = np.zeros( ( pickInfo.shape[0], 4 ), dtype=np.float )
    cdef np.ndarray nonSample       = np.zeros( ( pickInfo.shape[0], 4 ), dtype=np.float )
    cdef np.ndarray newSample       = np.zeros( ( pickInfo.shape[0], 4 ), dtype=np.float )
    cdef np.ndarray selection       = np.zeros( ( pickInfo.shape[0], 4 ), dtype=np.float )
    cdef np.ndarray sortedSelection = np.zeros( ( pickInfo.shape[0], 4 ), dtype=np.float )

    cdef np.ndarray sel             = np.zeros( pickInfo.shape[0], dtype=np.int )
    cdef np.ndarray indices         = np.zeros( pickInfo.shape[0], dtype=np.int )

    # 1st column: picked or not picked?
    values[:,0] = pickInfo[:,2]

    # 2nd column: magnitude differences
    values[:,1] = pickInfo[:,1] - magnitude

    # 3rd column: translate difference in distances into a magnitude difference
    logDistance = calcMagnitudeFromDistance( distance )
    values[:,2] = pickInfo[:,4] - logDistance

    # 4rd column: calculate total difference in "magnitude units"
    values[:,3] = np.sqrt( values[:,1]**2 + values[:,2]**2 )

    # select only picks with a "magnitude difference" of less than 0.1
    sel = ( values[:,3] < 0.1 )             # selector: TODO use magnitude threshold

    # check if selector contains at least one 'True'
    if sel.sum() != 0.0:
        sample    = values[sel.T,:]
        numSample = sample.shape[0]
    else:
        numSample   = 0

    if ( numSample < 10 ):                                    # not enough samples available
        nonSample = values[np.logical_not(sel),:]             # invert selector

        sel       = np.logical_and( ( nonSample[:,1] <= 0 ),
                                    ( nonSample[:,2] >= 0 ) ) # select points w/ larger distance, smaller magnitude

        selection = nonSample[sel.T,:]
        indices   = selection[:,3].argsort( axis=0 )          # sort for distance

        if indices.shape[0] < (10 - numSample):               # enough points to fill array up to 10?

            probability = 0.0                                 # no, give up (return zero)
        else:
            sortedSelection = selection[indices]

            if numSample > 0:
                newSample = np.concatenate( ( sample,
                                              sortedSelection[0:(10-numSample),:] ),
                                            axis=0 )
            else:
                newSample = sortedSelection[0:10,:]

            probability = newSample[:,0].sum() / 10.0
            numSample = 10
    else:
        probability = sample[:,0].sum() / numSample

    return ( probability, numSample )
