/* weighted.c
********************************************************************************
* weighted.c -- Routines for re-weighted statistical series objects
*
* User routines
*	Pro_WeightedStat()	creates a re-weighted statistical series object.
*
* In a re-weighted statistical series, each sample is derived by taking a
* weighted sum of the samples in another statistical series.  The user must
* provide a function that calculates the (possibly un-normalized) weights and
* another function that returns the range of nonzero-weighted samples in the old
* series that comprise each new sample.
*
* Version 1.0: Original version.
*              Mark Showalter, PDS Ring-Moon Systems Node, March 1998.
* Version 1.1: Support for Pro_StatRange() function added.
*              Mark Showalter, January 2000.
*******************************************************************************/
#include <stdio.h>
#include <string.h>
#include "profile.h"

/********************
 * Type definitions *
 ********************/

typedef struct ZPRO_WEIGHTED_STRUCT {
    XPRO_CLASS	class;
    PRO_OBJECT	*oldstat;
    void	(*krange)  RL_PROTO((RL_INT4 knew, RL_INT4 *k1, RL_INT4 *k2,
		                     RL_VOID *params));
    RL_FLT8	(*weights) RL_PROTO((RL_INT4 knew, RL_INT4 k,
		                     RL_VOID *params));
    RL_VOID	*params;
    RL_SIZE	paramsize;
    RL_BOOL	isduped;
    RL_INT4	kmin, kmax;
    void	(*printmore) RL_PROTO((RL_VOID *params));
} ZPRO_WEIGHTED;

/********************
 * Static variables *
 ********************/

static XPRO_CLASS weighted_class = {XPRO_WEIGHTED_CLASS, "weighted", NULL};

/********************************
 * Internal function prototypes *
 ********************************/

static RL_FLT8	ZPro_WeightedValue RL_PROTO((RL_VOID *pointer, RL_INT4 k,
		                             RL_INT4 *flag));
static RL_FLT8	ZPro_WeightedCovar RL_PROTO((RL_VOID *pointer, RL_INT4 k1,
		                             RL_INT4 k2));
static void	ZPro_WeightedRange RL_PROTO((RL_VOID *pointer, RL_INT4 k,
		                             RL_INT4 *k1, RL_INT4 *k2));
static void 	ZPro_FreeWeighted  RL_PROTO((RL_VOID *pointer));
static void	ZPro_PrintWeighted RL_PROTO((RL_VOID *pointer));

/*
********************************************************************************
* EXPORTED USER ROUTINES
********************************************************************************
*$ Component_name:
*	Pro_WeightedStat (weighted.c)
*$ Abstract:
*	This routine creates a new statistical series in which each sample is a
*	weighted sum of samples from another statistical series.
*$ Keywords:
*	PROFILE
*	C, PUBLIC, SUBROUTINE
*$ Declarations:
*	PRO_OBJECT	*Pro_WeightedStat(stat, k1, k2, x1, dx, krange, weights,
*		                 params, paramsize, dupe, printmore)
*	PRO_OBJECT	*stat;
*	RL_INT4		k1, k2;
*	RL_FLT8		x1, dx;
*	void		(*krange) (RL_INT4 knew, RL_INT4 *k1, RL_INT4 *k2,
*			           RL_VOID *params);
*	RL_FLT8		(*weights) (RL_INT4 knew, RL_INT4 k, RL_VOID *params);
*	RL_VOID		*params;
*	RL_SIZE		paramsize;
*	RL_BOOL		dupe;
*	void		(*printmore) (RL_VOID *params);
*$ Inputs:
*	stat		pointer to the statistical series to re-weight.
*	k1, k2		new range of indices.
*	x1, dx		new x coordinate starting value and interval.
*	krange		function that returns the range of indices on the old
*			series corresponding to nonzero weights for a given new
*			index.
*	weights		function that returns the weights on each old sample
*			for each new sample.
*	params		parameter set to pass to krange and weight functions.
*	paramsize	number of bytes in parameter set.
*	dupe		TRUE to duplicate parameters; FALSE just to save
*			pointer.
*	printmore	optional subroutine to print more info about weighted
*			stat series; NULL to ignore.
*$ Outputs:
*	none
*$ Returns:
*	pointer to a new statistical series object; NULL on non-fatal error.
*$ Detailed_description:
*	This routine creates a new statistical series in which each sample is a
*	weighted sum of samples from another statistical series.  The user must
*	provide subroutines that return the set of samples and (possibly
*	unnormalized) weights to apply to each old sample to generate any given
*	new sample.
*
*	This is the "workhorse" routine used by Pro_Filtered() and
*	Pro_Resampled().  Note that there is no FORTRAN-callable equivalent to
*	this routine.
*$ External_references:
*	Profile toolkit
*$ Side_effects:
*	Memory is allocated.  The new object links to the original statistical
*	series.
*$ Examples:
*	Suppose we have a statistical series stat, and we wish to create a new
*	series in which each sample is the average of the nearest three samples
*	in the original series.  (This is actually much easier to do using
*	Pro_FilteredStat).
*
*	We need these two functions:
*
*	void krange(RL_INT4 knew, RL_INT4 *k1, RL_INT4 *k2, RL_VOID *params)
*	{
*		k1 = knew - 1;
*		k2 = knew + 1;
*	}
*
*	RL_FLT8	weights(RL_INT4 knew, RL_INT4 k, RL_VOID *params)
*	{
*		return 1.;
*	}
*
*	Then this snippet of code creates and returns stat3, the new series:
*
*	PRO_OBJECT	*stat, *stat3;
*	RL_INT4		k1, k2;
*	RL_FLT8		x1, dx;

*	Pro_SeriesIndices(stat, &k1, &k2);
*	Pro_SeriesSampling(stat, &x1, NULL, &dx);
*	stat3 = Pro_WeightedStat(stat, k1, k2, x1, dx, krange, weights,
*			NULL, 0, FALSE, NULL);
*$ Error_handling:
*	Profile library error handling is in effect.
*
*	Conditions raised:
*	RL_MEMORY_ERROR		on memory allocation failure.
*	PRO_CLASS_ERROR 	if the object is NULL or is not a stat series.
*$ Limitations:
*	none
*$ Author_and_institution:
*	Mark R. Showalter
*	NASA/Ames Research Center
*$ Version_and_date:
*	1.0: March 1998
*$ Change_history:
*	none
*******************************************************************************/

PRO_OBJECT	*Pro_WeightedStat(stat, k1, k2, x1, dx, krange, weights,
		                 params, paramsize, dupe, printmore)
PRO_OBJECT	*stat;
RL_INT4		k1, k2;
RL_FLT8		x1, dx;
void		(*krange)  RL_PROTO((RL_INT4 knew, RL_INT4 *k1, RL_INT4 *k2,
		                     RL_VOID *params));
RL_FLT8		(*weights) RL_PROTO((RL_INT4 knew, RL_INT4 k,
		                     RL_VOID *params));
RL_VOID		*params;
RL_SIZE		paramsize;
RL_BOOL		dupe;
void		(*printmore) RL_PROTO((RL_VOID *params));
{
ZPRO_WEIGHTED	*weighted;
PRO_OBJECT	*new;

    /* Make sure it's a statistical series */
    if (XPro_StatPtr(stat) == NULL) return NULL;

    /* Allocate new weighted statistical series structure */
    weighted = (ZPRO_WEIGHTED *) XRL_Malloc(sizeof(ZPRO_WEIGHTED));
    if (weighted == NULL) return NULL;

    /* Initialize structure */
    weighted->class     = weighted_class;
    weighted->oldstat   = stat;
    weighted->krange    = krange;
    weighted->weights   = weights;
    weighted->printmore = printmore;
    weighted->kmin      = k1;
    weighted->kmax      = k2;

    /* Copy parameters if necessary */
    if (dupe && paramsize > 0 && params != NULL) {
	weighted->params = XRL_Malloc(paramsize);
	if (weighted->params == NULL) {
	    XRL_Free((RL_VOID *) weighted);
	    return NULL;
	}
	memcpy(weighted->params, params, paramsize);
	weighted->paramsize = paramsize;
	weighted->isduped   = TRUE;
    }
    else {
	weighted->params    = params;
	weighted->paramsize = paramsize;
	weighted->isduped   = FALSE;
    }

    /* Create new statistical series object */
    new = XPro_MakeStat(k1, k2, x1, dx,
                        ZPro_WeightedValue,
			ZPro_WeightedCovar, ZPro_WeightedRange,
                        ZPro_FreeWeighted, ZPro_PrintWeighted,
                        (RL_VOID *) weighted);

    /* Enslave old statistical series and check for errors */
    if (new != NULL && XPro_EnslaveObject(new, stat)) {
	Pro_FreeObject(new);
	new = NULL;
    }

    /* Transfer Y-coordinate name to new object */
    Pro_RenameObject(new, 2, Pro_ObjectName(stat,2));

    return new;
}

/*
********************************************************************************
* INTERNAL ROUTINES
********************************************************************************
* ZPro_WeightedValue(pointer, k, flag)
*
* This internal routine returns the value of a weighted statistical series.
*
* Inputs:
*	pointer		pointer to the ZPRO_WEIGHTED structure.
*	k		index at which to evaluate series.
*
* Outputs:
*	*flag		0 if value is valid.
*******************************************************************************/

static RL_FLT8	ZPro_WeightedValue(pointer, k, flag)
RL_VOID		*pointer;
RL_INT4		k, *flag;
{
ZPRO_WEIGHTED	*stat;
RL_INT4		i, i1, i2, oldflag, newflag;
RL_FLT8		sum0, sum1, value, weight;

    stat = (ZPRO_WEIGHTED *) pointer;
    (stat->krange) (k, &i1, &i2, stat->params);

    sum0 = 0.;
    sum1 = 0.;
    newflag = PRO_MISSING_FLAG;	/* use only if every value is missing */
    for (i=i1; i<=i2; i++) {
	value = Pro_SeriesValue(stat->oldstat, i, &oldflag);

 	if (oldflag == 0) {
	    weight = (stat->weights)(k, i, stat->params);
	    sum0 = sum0 + weight;
	    sum1 = sum1 + weight * value;
	}
	else if (oldflag == PRO_INVALID_FLAG) {
	    newflag = PRO_INVALID_FLAG; /* change from missing to invalid */
	}
    }

    if (sum0 == 0.) {
	*flag = newflag;
	return 0.;
    }

    *flag = 0;
    return sum1/sum0;
}

/*
********************************************************************************
* ZPro_WeightedCovar(pointer, k1, k2)
*
* This internal routine evaluates the covariance of a weighted statistical
* series.
*
* Inputs:
*	pointer		pointer to the ZPRO_WEIGHTED structure.
*	k1, k2		pair of new indices at which to evaluate covariance.
*
* Returns:		value of covariance between k1 and k2;
*******************************************************************************/

static RL_FLT8	ZPro_WeightedCovar(pointer, k1, k2)
RL_VOID		*pointer;
RL_INT4		k1, k2;
{
ZPRO_WEIGHTED	*stat;
RL_INT4		i, i1, i2, j, j1, j2, jmin, jmax, flag;
RL_FLT8		sum0, sum1, wsumi, wsumj, weight;

    stat = (ZPRO_WEIGHTED *) pointer;

    /* Find ranges of associated samples in old series */
    (stat->krange) (k1, &i1, &i2, stat->params);
    (stat->krange) (k2, &j1, &j2, stat->params);

/*******************************************************************************
** The old code...
**
**  sum0 = 0.;
**  sum1 = 0.;
**  for (i=i1; i<=i2; i++) {
**	(void) Pro_SeriesValue(stat->oldstat, i, &flag1);
**
**  for (j=j1; j<=j2; j++) {
**	(void) Pro_SeriesValue(stat->oldstat, j, &flag2);
**
**	if (flag1 == 0 && flag2 == 0) {
**	    weight = (stat->weights)(k1, i, stat->params) *
**	             (stat->weights)(k2, j, stat->params);
**	    sum0 = sum0 + weight;
**	    sum1 = sum1 + weight * Pro_StatCovar(stat->oldstat, i, j);
**	}
**  }}
**
** The trouble with the code above is that, as the filter width increases, the
** computational effort goes as the width squared.  It fails to take advantage
** of the fact that the covariance of samples i and j in the original series is
** likely to be zero except for i close to j.  The revised code shown below
** takes advantage of this fact and so runs much faster.
*******************************************************************************/

/* Sum the products of weights for each pair of old samples */
    wsumi = 0.;
    for (i=i1; i<=i2; i++) {
        (void) Pro_SeriesValue(stat->oldstat, i, &flag);
	if (flag == 0) wsumi += (stat->weights)(k1, i, stat->params);
    }

    if (k1 == k2) {
	wsumj = wsumi;
    } else {
	wsumj = 0.;
	for (j=j1; j<=j2; j++) {
            (void) Pro_SeriesValue(stat->oldstat, j, &flag);
	    if (flag == 0) wsumj += (stat->weights)(k2, j, stat->params);
	}
    }

    sum0 = wsumi * wsumj;

/* Sum the products of weights times the covariance of each pair.
 * For this sum, we can skip pairs whose covariance is zero.
 */
    sum1 = 0.;
    for (i=i1; i<=i2; i++) {
	(void) Pro_SeriesValue(stat->oldstat, i, &flag);
	if (flag != 0) continue;

	Pro_StatRange(stat->oldstat, i, &jmin, &jmax);
	if (jmax < j1) continue;
	if (jmin > j2) continue;

	if (jmin < j1) jmin = j1;
	if (jmax > j2) jmax = j2;

	weight = (stat->weights)(k1, i, stat->params);
	for (j=jmin; j<=jmax; j++) {
	    (void) Pro_SeriesValue(stat->oldstat, j, &flag);
	    if (flag != 0) continue;

	    weight *= (stat->weights)(k2, j, stat->params);
	    sum1 += weight * Pro_StatCovar(stat->oldstat, i, j);
	}
    }

    if (sum0 == 0.) return 0.;

    return sum1/sum0;
}

/*
********************************************************************************
* ZPro_WeightedRange(pointer, k, k1, k2)
*
* This internal routine evaluates the range of samples that might have non-zero
* covariances with a given sample in a weighted statistical series.
*
* Inputs:
*	pointer		pointer to the ZPRO_WEIGHTED structure.
*	k		index at which to evaluate correlation range.
*
* Outputs:
*	k1, k2		range of indices within which samples are correlated.
*
* Returns:		none.
*******************************************************************************/

static void	ZPro_WeightedRange(pointer, k, k1, k2)
RL_VOID		*pointer;
RL_INT4		k, *k1, *k2;
{
ZPRO_WEIGHTED	*stat;
RL_INT4		i1, i2, imin, imax, dummy, ktest;

    stat = (ZPRO_WEIGHTED *) pointer;

    /* Find range of old samples that are used in this sample */
    (stat->krange) (k, &i1, &i2, stat->params);

    /* Find range of old samples that might correlate with this range */
    Pro_StatRange(stat->oldstat, i1, &imin,  &dummy);
    Pro_StatRange(stat->oldstat, i2, &dummy, &imax);

    /* Search down for first uncorrelated new sample */
    for (ktest = k; ktest >= stat->kmin; ktest--) {
	(stat->krange) (ktest, &i1, &i2, stat->params);
	if (i2 < imin || i1 > imax) break;
    }
    *k1 = ktest + 1;

    /* Search up for first uncorrelated sample */
    for (ktest = k; ktest <= stat->kmax; ktest++) {
	(stat->krange) (ktest, &i1, &i2, stat->params);
	if (i2 < imin || i1 > imax) break;
    }
    *k2 = ktest - 1;
}

/*
********************************************************************************
* ZPro_FreeWeighted(pointer)
*
* This internal routine deallocates the memory used by a weighted statistical
* series.
*
* Input:
*	pointer		pointer to the ZPRO_WEIGHTED structure.
*******************************************************************************/

static void	ZPro_FreeWeighted(pointer)
RL_VOID		*pointer;
{
ZPRO_WEIGHTED	*stat;

    stat = (ZPRO_WEIGHTED *) pointer;

    if (stat->isduped) XRL_Free(stat->params);
    XRL_Free(pointer);
}

/*
********************************************************************************
* ZPro_PrintWeighted(pointer)
*
* This internal routine prints information about a weighted statistical series.
*
* Input:
*	pointer		pointer to the ZPRO_FIXEDSTAT structure.
*******************************************************************************/

static void	ZPro_PrintWeighted(pointer)
RL_VOID		*pointer;
{
ZPRO_WEIGHTED	*weighted;

    weighted = (ZPRO_WEIGHTED *) pointer;

    /* Make sure object is not NULL */
    if (weighted == NULL) {
	printf("PRINT ERROR: Weighted statistical series pointer is NULL\n");
	return;
    }

    /* Make sure object is a weighted statistical series */
    if (weighted->class.id != XPRO_WEIGHTED_CLASS) {
	printf("PRINT ERROR: Object is not a weighted statistical series\n");
	return;
    }

    /* Print object info... */
    printf("\nWeighted statistical series parameters...\n");
    printf("stat series = "); XPro_PrintInfo(weighted->oldstat);
    printf("  paramsize = %d\n", weighted->paramsize);
    printf(" duplicated = %s\n", (weighted->isduped ? "true" : "false"));

    /* Print optional additional info... */
    if (weighted->printmore != NULL)
	(*weighted->printmore) (weighted->params);

    return;
}

/*******************************************************************************
* FORTRAN INTERFACE ROUTINES
********************************************************************************
* Not provided, because generating FORTRAN interfaces to the "krange" and
* "weights" functions is just too complicated.
********************************************************************************
*/