
/* 
 * Argyll Color Management System
 * Multi-dimensional regularized splines
 * optimiser based initialiser.
 *
 * Author: Graeme W. Gill
 * Date:   2001/5/16
 *
 * Copyright 1996 - 2001 Graeme W. Gill
 * All rights reserved.
 *
 * This material is licenced under the GNU AFFERO GENERAL PUBLIC LICENSE Version 3 :-
 * see the License.txt file for licencing details.
 */

/* This file contains an rspl initialiser that */
/* works from an optimisation function callback. */
/* It is intended to support the creation of optimised */
/* color separations, although this usage is not hard coded */
/* here. */

/* TTBD:
 *
 * !!! fix so that this can also be used for smoothed
 *     inversion, ie. PCS -> DevN, as well as
 *     separation PseudoCMY/K -> DevN.
 *
 * Plan:
 *		Have additional callback function used for invert,
 *		called at grid initialisation that initialised
 *		the target values to fixed PCS values.
 *		For separation, these are dynamic, and adjusted by
 *		the usual optimisation callback.
 *		(Or can the usual callback figure out when the
 *		 initial initialisation is needed ?)
 *
 *		Need to return average/extrapolated surround values
 *		on the edge, just like fit, so smoothness can be
 *		evaluated in inversion.
 *		Provide another mechanism for sep to know
 *		it is on the edge of the grid, and should expand
 *		gamut if possible.
 *
 * Get rid of error() calls - return status instead
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <math.h>
#include <time.h>

#include "rspl_imp.h"
#include "numlib.h"
#include "counters.h"	/* Counter macros */

#undef DEBUG

/* Tuning parameters */
#define TOL 1e-6		/* Tollerance of result */
#define GRATIO 1.7		/* Multi-grid ratio */
#define SMOOTH 80.0		/* Set nominal smoothing (1.0) */

#undef NEVER
#define ALWAYS

/* Implemented in rspl.c: */
extern void alloc_grid(rspl *s);

extern int is_mono(rspl *s);

/* Convention is to use:
   i to index grid points u.a
   n to index data points d.a
   e to index position dimension di
   f to index output function dimension fdi
   j misc and cube corners
   k misc
 */

/* ================================================= */
/* Structure to hold temporary data for multi-grid caliculations */
/* Only used in this file. */
struct _omgtp {
	rspl *s;	/* Associated rspl */

	/* Configuration data */
	int tdi;	/* Target guide values dimensionality (must be <= MXDI) */
				/* (Typically the Lab aim values corresponding to this pseudo device value) */
	int adi;	/* Additional grid point data allowance (must be <= 2 * MXDI) */
				/* (Typically black locus range) */

	double (*func)(void *fdata, double *inout, double *surav, int first, double *cw);
					/* Optimisation function */
	void *fdata;	/* Pointer to opaque data needed by callback function */

	struct {
		double cw[MXDI];	/* Curvature weight factor for each dimension */
	} sf;

	/* Grid points data */
	struct {
		int res[MXDI];	/* Single dimension grid resolution for each dimension */
		int bres, brix;	/* Biggest resolution and its index */
		double mres;	/* Geometric mean res[] */
		int no;			/* Total number of points in grid = res ^ di */
		datai l,h,w;	/* Grid low, high, grid cell width */

	
		double *a;		/* Grid point data */
						/* Array is res ^ di entries double[fdi+tdi+adi] */
						/* The output values start at offset 0, the */
						/* target data values start at offset fdi, and */
						/* the additional data starts at offset fdi+tdi. */
		int pss;		/* Grid point structure size = fdi+tdi */

		/* Grid array offset lookups */
		int ci[MXDI];		/* Grid coordinate increments for each dimension */
		int fci[MXDI];		/* Grid coordinate increments for each dimension in doubles */
		int *hi;			/* 2^di Combination offset for sequence through cube. */
		int *fhi;			/* Combination offset for sequence through cube of */
							/* 2^di points, starting at base, in floats */
		int a_hi[DEF2MXDI];	/* Default allocation for *hi */
		int a_fhi[DEF2MXDI];/* Default allocation for *fhi */
	} g;

}; typedef struct _omgtp omgtp;

/* ================================================= */
static omgtp *new_omgtp(rspl *s, int tdi, int adi, int mxres,
	double (*func)(void *fdata, double *inout, double *surav, int first, double *cw),
	void *fdata);
static void free_omgtp(omgtp *m);
static void solve_gres(omgtp *m, double tol);
static void init_soln(omgtp  *m1, omgtp  *m2);
static void init_fsoln(omgtp *m, double **vdata);

/* Initialise the regular spline from the optimisation callback function. */
/* The target data is auxiliary data used to "target" the optimisation */
/* callback function. */
/* The callback function arguments are as follows:
 *	void *fdata,
 *	double *inout,	Pointers to fdi+tdi+adi values for the grid point being optimised.
 *	double *surav,	Pointers to fdi+tdi values which are the average of the
 *					neighbors of this grid point. Pointer will NULL if this
 *					is a surface grid point.
 *	int first,		Flag, NZ if this is the first optimisation of this point.
 *	double *cw		the (grid resolution) curvature weighting factor for each dimension 
 *	
 *	Returns value is the "error" for this point.
 */

int
opt_rspl_imp(
	rspl *s,		/* this */
	int flags,		/* Combination of flags */
	int tdi,		/* Dimensionality of target data */
	int adi,		/* Additional per grid point data allocation */
	double **vdata,	/* di^2 array of function, target and additional values to init */
					/* array corners with. Corners are ordered with lowest index */
					/* dimension changing most rapidly. */
	double (*func)(void *fdata, double *inout, double *surav, int first, double *cw),
					/* Optimisation function */
	void *fdata,	/* Opaque data needed by function */
	datai glow,		/* Grid low scale - NULL = default 0.0 */
	datai ghigh,	/* Grid high scale - NULL = default 1.0 */
	int gres[MXDI],	/* Spline grid resolution for each dimension */
	datao vlow,		/* Data value low normalize, NULL = default 0.0 */
	datao vhigh		/* Data value high normalize - NULL = default 1.0 */
) {
//	int di = s->di
	int fdi = s->fdi;
	int i, e, f;
//	int n;

	/* set debug level */
	s->debug = (flags >> 24);

	if (flags & RSPL_VERBOSE)	/* Turn on progress messages to stdout */
		s->verbose = 1;
	if (flags & RSPL_NOVERBOSE)	/* Turn off progress messages to stdout */
		s->verbose = 0;

	s->symdom = (flags & RSPL_SYMDOMAIN) ? 1 : 0;	/* Turn on symetric smoothness with gres */

	if (tdi >= MXDI)
		error("rspl, opt: tdi %d > MXDI %d",tdi,MXDI);

	if (adi >= (2 * MXDI))
		error("rspl, opt: adi %d > 2 * MXDI %d",adi,2 * MXDI);

	/* transfer desired grid range to structure */
	s->g.mres = 1.0;
	s->g.bres = 0;
	for (e = 0; e < s->di; e++) {
		if (gres[e] < 2)
			error("rspl: grid res must be >= 2!");
		s->g.res[e] = gres[e];	/* record the desired resolution of the grid */
		s->g.mres *= gres[e];
		if (gres[e] > s->g.bres) {
			s->g.bres = gres[e];
			s->g.brix = e;
		}

		if (glow == NULL)
			s->g.l[e] = 0.0;
		else
			s->g.l[e] = glow[e];

		if (ghigh == NULL)
			s->g.h[e] = 1.0;
		else
			s->g.h[e] = ghigh[e];
	}
	s->g.mres = pow(s->g.mres, 1.0/e);		/* geometric mean */

	/* compute width of each grid cell */
	for (e = 0; e < s->di; e++) {
		s->g.w[e] = (s->g.h[e] - s->g.l[e])/(double)(gres[e]-1);
	}

	/* record low and width data normalizing factors */
	for (f = 0; f < s->fdi; f++) {
		if (vlow == NULL)
			s->d.vl[f] = 0.0;
		else
			s->d.vl[f] = vlow[f];

		if (vhigh == NULL)
			s->d.vw[f] = 1.0 - s->d.vl[f];
		else
			s->d.vw[f] = vhigh[f] - s->d.vl[f];
	}

	/* Do optimisation of data points */
	{
		int nn, res, sres;
		double fres, gratio = GRATIO;
		float *gp;		/* rspl grid pointer */
		double *mgp;	/* Temp muligrid pointer */
		omgtp *m, *om = NULL;

		sres = 4;		/* Start at initial grid res of 4 */
		if (sres > s->g.bres)
			sres = s->g.bres;	/* Drop to target resolution */

		/* Calculate the resolution scaling ratio */
		if (((double)s->g.bres/(double)sres) <= gratio) {
			gratio = (double)s->g.bres/(double)sres;
			nn = 1;
		} else {	/* More than one needed */
			nn = (int)((log((double)s->g.bres) - log((double)sres))/log(gratio) + 0.5);
			gratio = exp((log((double)s->g.bres) - log((double)sres))/(double)nn);
		}

		/* Do each grid resolution in turn */
		for (fres = (double)sres, res = sres;;) {
			m = new_omgtp(s, tdi, adi, res, func, fdata);

			if (om == NULL) {
				init_fsoln(m, vdata);	/* Set the initial targets & values from corners */
			} else {
				init_soln(m, om);	/* Scale targets & values from from previous resolution */
				free_omgtp(om);		/* Free previous grid res solution */
			}
			solve_gres(m, TOL * s->g.mres/res);	/* Use itterative */

			if (res >= s->g.mres)
				break;	/* Done */

			fres *= gratio;
			res = (int)(fres + 0.5);
			if ((res + 1) >= s->g.mres)	/* If close enough */
				res = (int)s->g.mres;
			om = m;
		}

		/* Allocate the final rspl grid data */
		alloc_grid(s);
	
		/* Transfer result in x[] to appropriate grid point value */
		for (gp = s->g.a, mgp = m->g.a, i = 0; i < s->g.no; gp += s->g.pss, mgp += m->g.pss, i++)
			for (f = 0; f < fdi; f++)
				gp[f] = (float)mgp[f];
		free_omgtp(m);
	}

	/* Return non-mono check */
	return is_mono(s);
}

/* - - - - - - - - - - - - - - - - - - - - - - - -*/
/* omgtp routines */

/* Create a new omgtp. */
/* Grid data will be uninitialised */
static omgtp *new_omgtp(
	rspl *s,		/* associated rspl */
	int tdi,		/* Target dimensions */
	int adi,		/* Additional per grid point data allocation */
	int mxres,		/* maximum resolution to create */
	double (*func)(void *fdata, double *inout, double *surav, int first, double *cw),
					/* Optimisation function */
	void *fdata		/* Opaque data needed by function */
) {
	omgtp *m;
	int di = s->di, fdi = s->fdi;
//	int dno = s->d.no;
	int gno;
	int e, g, i;
//	int f, n, j, k;

	/* Allocate a structure */
	if ((m = (omgtp *) calloc(1, sizeof(omgtp))) == NULL)
		error("rspl: malloc failed - omgtp");

	/* Allocate space for cube offset arrays */
	m->g.hi = m->g.a_hi;
	m->g.fhi = m->g.a_fhi;
	if ((1 << di) > DEF2MXDI) {
		if ((m->g.hi = (int *) malloc(sizeof(int) * (1 << di))) == NULL)
			error("rspl omgtp malloc failed - hi[]");
		if ((m->g.fhi = (int *) malloc(sizeof(int) * (1 << di))) == NULL)
			error("rspl omgtp malloc failed - fhi[]");
	}

	/* General stuff */
	m->s = s;
	m->tdi = tdi;
	m->adi = adi;
	m->func = func;
	m->fdata = fdata;

	/* Grid related */
	m->g.mres = 1.0;
	m->g.bres = 0;
	for (gno = 1, e = 0; e < di; e++) {
		if (mxres >= s->g.res[e])			/* Shoose smaller of gres and target res */
			m->g.res[e] = s->g.res[e];
		else
			m->g.res[e] = mxres;

		m->g.mres *= m->g.res[e];
		if (m->g.res[e] > m->g.bres) {
			m->g.bres = m->g.res[e];
			m->g.brix = e;
		}
		gno *= m->g.res[e];
	}
	m->g.mres = pow(m->g.mres, 1.0/e);		/* geometric mean */
	m->g.no = gno;

	m->g.pss = fdi+tdi+adi;	/* doubles for each output value + target data + additional data */

	/* record high, low limits, and width of each grid cell */
	for (e = 0; e < s->di; e++) {
		m->g.l[e] = s->g.l[e];
		m->g.h[e] = s->g.h[e];
		m->g.w[e] = (s->g.h[e] - s->g.l[e])/(double)(m->g.res[e]-1);
	}

	/* Compute index coordinate increments into linear grid for each dimension */
	/* ie. 1, gres, gres^2, gres^3 */
	for (m->g.ci[0] = 1, e = 1; e < di; e++) {
		m->g.ci[e] = m->g.ci[e-1] * m->g.res[e-1];	/* In grid points */
		m->g.fci[e] = m->g.ci[e] * m->g.pss;		/* In doubles */
	}

	/* Compute index offsets from base of cube to other corners */
	for (m->g.hi[0] = 0, e = 0, g = 1; e < di; g *= 2, e++) {
		for (i = 0; i < g; i++) {
			m->g.hi[g+i] = m->g.hi[i] + m->g.ci[e];		/* In grid points */
			m->g.fhi[g+i] = m->g.hi[g+i] * m->g.pss;	/* In doubles */
		}
	}

	/* Allocate space for grid */
	if ((m->g.a = (double *) malloc(sizeof(double) * gno * m->g.pss)) == NULL)
		error("rspl malloc failed - multi-grid points");

	/* Compute curvature weighting for matching intermediate resolutions. */
	/* cw[] is multiplied by the grid curvature_errors_squared[] to keep */
	/* the same ratio with the sum of data position errors squared. */
	for (e = 0; e < di; e++) {
		double rsm;				/* Resolution smoothness factor */
		if (s->symdom)
			rsm = m->g.res[e]-1.0;	/* Relative final grid size  */
		else
			rsm = m->g.mres-1.0;	/* Relative mean final grid size */
		rsm =    pow(rsm,8.0/di);
		rsm /= pow(200.0,8.0/di)/pow(200.0, 4.0);	/* (Scale factor to adjust power) */

		m->sf.cw[e] = (s->smooth * SMOOTH)/(rsm * (double)di);
	}

	return m;
}

/* Completely free an omgtp */
static void free_omgtp(omgtp  *m) {

	free((void *)m->g.a);

	/* Free structure */
	if (m->g.hi != m->g.a_hi) {
		free(m->g.hi);
		free(m->g.fhi);
	}
	free((void *)m);
}

/* Set the first targets & values from the corner values. */
static void init_fsoln(
omgtp *m,			/* Destination */
double **vdata		/* di^2 array of function and target values to init array corners with. */
					/* Corners are ordered with lowest index dimension changing most rapidly. */
					/* (Function data at index 0, target data at index fdi) */
) {
	rspl *s = m->s;
	int di  = s->di;
	int fdi = s->fdi;
	int gno = m->g.no;
	int gres_1[MXDI];
	int e, n;
	double *gp;				/* Pointer to dest g.a[] grid cube base */
	ECOUNT(gc, MXDIDO, di, 0, m->g.res, 0);	/* Counter for output points */
	double *gw;				/* weight for each grid cube corner */
	double a_gw[DEF2MXDI];	/* default allocation for gw */

	gw = a_gw;
	if ((1 << di) > DEF2MXDI) {
		if ((gw = (double *) malloc(sizeof(double) * (1 << di))) == NULL)
			error("rspl malloc failed - interp_rspl_nl");
	}

	for (e = 0; e < di; e++)
		gres_1[e] = m->g.res[e]-1;

	/* For all output grid points (could skip non-surface points ?) */
	EC_INIT(gc);
	for (n = 0, gp = m->g.a; n < gno; n++, gp += m->g.pss) {
		double we[MXDI];		/* 1.0 - Weight in each dimension */
		
		/* Figure out the pointer to the grid data and its weighting */
		{
			gp = m->g.a;					/* Base of output array */
			for (e = 0; e < di; e++)
				we[e] = (double)gc[e]/gres_1[e]; /* 1.0 - weight */
		}

		/* Compute corner weights needed for interpolation */
		{
			int i, g;
			gw[0] = 1.0;
			for (e = 0, g = 1; e < di; g *= 2, e++) {
				for (i = 0; i < g; i++) {
					gw[g+i] = gw[i] * we[e];
					gw[i] *= (1.0 - we[e]);
				}
			}
		}

		/* Compute the output values */
		{
			int i, f;
			double w = gw[0];
			double *d = vdata[0];

			for (f = 0; f < m->g.pss; f++)		/* Base of cube */
				gp[f] = w * d[f];

			for (i = 1; i < (1 << di); i++) {	/* For all other corners of cube */
				w = gw[i];						/* Strength reduce */
				d = vdata[i];
				for (f = 0; f < fdi; f++)
					gp[f] += w * d[f];
			}
		
		}

		EC_INC(gc);
	}

	if (gw != a_gw)
		free(gw);
}


/* Transfer a device and target values solution from one omgtp to another. */
/* (We assume that they are for the same problem) */
static void init_soln(
	omgtp  *m1,		/* Destination */
	omgtp  *m2		/* Source */
) {
	rspl *s = m1->s;
	int di  = s->di;
	int gno = m1->g.no;
	int gres1_1[MXDI];
	int gres2_1[MXDI];
	int e, n;
	double *a;				/* Pointer to dest g.a[] grid cube base */
	ECOUNT(gc, MXDIDO, di, 0, m1->g.res, 0);	/* Counter for output points */
	double *gw;				/* weight for each grid cube corner */
	double a_gw[DEF2MXDI];	/* default allocation for gw */

	gw = a_gw;
	if ((1 << di) > DEF2MXDI) {
		if ((gw = (double *) malloc(sizeof(double) * (1 << di))) == NULL)
			error("rspl malloc failed - interp_rspl_nl");
	}

	for (e = 0; e < di; e++) {
		gres1_1[e] = m1->g.res[e]-1;
		gres2_1[e] = m2->g.res[e]-1;
	}

	/* For all output grid points */
	EC_INIT(gc);
	for (n = 0, a = m1->g.a; n < gno; n++, a += m1->g.pss) {
		double we[MXDI];		/* 1.0 - Weight in each dimension */
		double *gp;				/* Pointer to source g.a[] grid cube base */
		
		/* Figure out which grid cell the point falls into */
		{
			double t;
			int mi;
			gp = m2->g.a;					/* Base of solution array */
			for (e = 0; e < di; e++) {
				t = (double)gc[e] * gres2_1[e]/gres1_1[e];
				mi = (int)floor(t);			/* Grid coordinate */
				if (mi < 0)					/* Limit to valid cube base index range */
					mi = 0;
				else if (mi >= gres2_1[e])
					mi = gres2_1[e]-1;
				gp += mi * m2->g.fci[e];	/* Add Index offset for grid cube base in dimen */
				we[e] = t - (double)mi;		/* 1.0 - weight */
			}
		}

		/* Compute corner weights needed for interpolation */
		{
			int i, g;
			gw[0] = 1.0;
			for (e = 0, g = 1; e < di; g *= 2, e++) {
				for (i = 0; i < g; i++) {
					gw[g+i] = gw[i] * we[e];
					gw[i] *= (1.0 - we[e]);
				}
			}
		}

		/* Compute the output values */
		{
			int i, f;
			double w = gw[0];
			double *d = gp + m2->g.fhi[0];

			for (f = 0; f < m1->g.pss; f++)			/* Base of cube */
				a[f] = w * d[f];

			for (i = 1; i < (1 << di); i++) {	/* For all other corners of cube */
				w = gw[i];						/* Strength reduce */
				d = gp + m2->g.fhi[i];
				for (f = 0; f < m1->g.pss; f++)
					a[f] += w * d[f];
			}
		
		}
		EC_INC(gc);
	}

	if (gw != a_gw)
		free(gw);
}

/* - - - - - - - - - - - - - - - - - - - -*/
static double one_itter(omgtp *m, int first);

/* Itterate the optimisation functions until we are happy things have settled */
static void
solve_gres(
omgtp *m,
double tol
) {
	int i;
	double dtol = tol * 0.1;	/* Delta tol limit */
	double ltt, tt;

	ltt = 1.0;
	tt = tol * 10.0;

	for (i = 0; i < 500; i++) {
		if (i == 0)
			tt = one_itter(m, 1);

		ltt = tt;
		tt = one_itter(m, 0);

		if (tt < tol || (ltt - tt) < dtol)	/* Get within 0.1 % */
			break;
	}
}

/* Optimise the points values and (optionally) targets */
/* Use Red/Black order, return total error after this itteration. */
/* Return the total optimisation error */
static double
one_itter(
omgtp *m,
int first		/* Flag, NZ if this is the first pass at this resolution */
) {
	int di = m->s->di, fdi = m->s->fdi;
	int tdi = m->tdi;
	int i, e, f;
	int gc[MXDI];
	int *gres = m->g.res;
	int gres_1[MXDI];
	DCOUNT(cc, MXDIDO, di, -1, -1, 2);	/* Surrounding cube counter */
	double *gpp;				/* Current grid point pointer */
	double ssum[MXDO+MXDI+2*MXDI];	/* Pointer to surrounding average values */
	double *surav;				/* Surrounding average values */
	double awt;					/* Average weight */
	double terr = 0.0;			/* Total error */
	int surf;					/* This point is on the surface */

	for (e = 0; e < di; e++) {
		gc[e] = 0;	/* init coords */
		gres_1[e] = gres[e] - 1;
	}

	/* Until done */
	for (;;) {

		/* See if we are on the surface */
		surf = 0;
		gpp = m->g.a;
		for (e = 0; e < di; e++) {
			gpp += m->g.fci[e] * gc[e];	/* Compute pointer to current point */

			if (gc[e] == 0 || gc[e] == gres_1[e])
				surf = 1;
		}

		surav = NULL;
		if (!surf) {

			for (f = 0; f < (fdi + tdi); f++)
				ssum[f] = 0.0;
			awt = 0.0;
 
			/* Average the 3x3 surrounders */
			DC_INIT(cc)
			for (i = 0; !DC_DONE(cc); i++ ) {
				double *gp = m->g.a;

				for (e = 0; e < di; e++) {
					int j;
					j = gc[e] + cc[e];
					if (j < 0 && j > gres_1[e]) {	/* outside */
						break;
					}
					gp += m->g.fci[e] * j;	/* Compute pointer to surrounder */
				}
				if (e >= di) {	/* We have a valid point */
					for (f = 0; f < (fdi + tdi); f++)
						ssum[f] += gp[f];
					awt += 1.0;
				}
				DC_INC(cc);
			}
			if (awt > 0.0) {	/* Compute the average */
				for (f = 0; f < (fdi + tdi); f++)
					ssum[f] /= awt;
				surav = ssum;
			}
		}
		
		/* Call optimisation function */
		terr += m->func(m->fdata, gpp, surav, first, m->sf.cw);

		/* Increment index in red/black order */
		for (e = 0; e < di; e++) {
			if (e == 0) {
				gc[0] += 2;	/* Inc coordinate by 2 */
			} else {
				gc[e] += 1;		/* Inc coordinate */
			}
			if (gc[e] < gres[e])
				break;	/* No carry */
			gc[e] -= gres[e];			/* Reset coord */

			if ((gres[e] & 1) == 0) {	/* Compensate for odd grid */
				gc[0] ^= 1; 			/* XOR lsb */
			}
		}
		/* Stop on reaching 0 */
		for(e = 0; e < di; e++)
			if (gc[e] != 0)
				break;
		if (e == di)
			break;		/* Finished */
	}

	return terr;
}













