/*
 * This file is Appendix C of the paper:
 *   Populations on Fragmented Landscapes with Spatially Structured
 *   Heterogeneities: Landscape Generation and Local Dispersal
 *     by David Hiebeler
 *     Ecology (2000), vol. XX, pp. YY-ZZ.
 *
 *
 *
 * File: 2x1gen.c
 *   By: Dave Hiebeler
 *       hiebeler@cam.cornell.edu
 *       Original version: September 1996
 *       Revised for publication: December 1998
 *
 * This program generates a heterogeneous landscape on a rectangular lattice,
 * which has a given probability distribution of 2x1 blocks for the
 * two habitat types.
 *
 * By default, a text picture of the landscape is output to the file
 * "2x1gen.out", with the characters `.' and `X' representing habitat
 * types 0 and 1, respectively.
 *
 * If you use the "-pgm" argument, a Portable Graymap image will be output
 * instead.  There is a large collection of free utilities written by
 * Jef Poskanzer which manipulates files of this format and converts them to
 * many other image formats (such as GIF); these utilities come for free with
 * many distributions of Linux, or you can download them from:
 *   http://www.acme.com/software/pbmplus/
 *
 * If you run the program with no command-line arguments, information
 * about other command-line options will be displayed.
 *
 * EXAMPLE:
 * A simple example of how to use the program is: to generate a 100x100
 * landscape with p0=0.5 and q00=0.8 (see the paper for a description of
 * these parameters), and store the output in the default file "2x1gen.out",
 * you would run:
 *   2x1gen 100 100 0.5 0.8
 * To do the same thing, but generate a PGM file called "land.pgm", you
 * would run:
 *   2x1gen -pgm -out land.pgm 100 100 0.5 0.8
 *
 * Unless you use the "-q" option for "quiet mode", the program displays
 * some diagnostics about the parameters you specified, and how close
 * the landscape came to achieving the desired structure.
 *
 * On my Linux system, I compile this program as follows:
 *   gcc -O3 -o 2x1gen 2x1gen.c
 * (The "-O3" flag tells the compiler to optimize the code's performance
 * as much as possible.)
 *
 *
 * Portability concerns:
 * This program was developed on a machine running Linux.  It should
 * be very portable, with two possible exceptions:
 *
 * (1) The main thing that may be a problem is the "my_srandom()"
 *     function, which will probably need to be changed for non-Unix systems.
 *     Simply change it to call whatever the appropriate initialization routine
 *     is for your random number generator.  Note that my function generates
 *     a seed based on the current time, and then calls the system routine
 *     "srandom()" with that seed.  I also call "srandom()" from "main()",
 *     if the user supplies a seed, so you'll need to change that as well.
 *
 * (2) The random number generator itself may be different if you are not
 *     using Unix.  The random number generator I use is called "random()",
 *     which on my computer (running Linux) returns a random 32-bit integer.
 *     I use the "random()" routine in my "randFrac()" function, and also
 *     in the "gen2x1()" function to choose random coordinates of a site
 *     to try flipping.
 */


#include <stdio.h>
#include <math.h>
#include <sys/time.h>  /* for seeding the random number generator */

#define RAND01_PRECISION 100000 /* granularity for generating random numbers */
#define EPSILON 1.0e-5 /* used to test whether or not a number is zero */
#define DEFAULT_OUTFNAME "2x1gen.out" /* default output filename */
#define DEFAULT_MAXITERS 1000000 /* default maximum number of iterations */
#define DEFAULT_MAXERR 0.001 /* default tolerance between desired and measured
			      * probabilities */
#define DEFAULT_CHAR0 '.'
#define DEFAULT_CHAR1 'X'


typedef unsigned char byte;  /* just for convenience */


/*
 * Print an error message and exit
 */
void
quit(char *s)
{
    fprintf(stderr, "%s\n", s);
    exit(1);
}


/*
 * Print a usage message and exit.
 */
void
printusage(char *s)
{
    fprintf(stderr, "Usage: %s [-byte val0 val1 | -char char0 char1 | -pgm]\n", s);
    fprintf(stderr, "            [-out fname] [-q] [-iter maxiters maxerr] [-s seed]\n");
    fprintf(stderr, "            xsize ysize p0 q00\n", s);
    fprintf(stderr, "   -byte val0 val1: numerically specify the 2 values to output\n");
    fprintf(stderr, "   -char char0 char1: use characters to specify the 2 values to output\n");
    fprintf(stderr, "                      (default: the characters `.' and `X')\n");
    fprintf(stderr, "   -s seed: specify random number seed\n");
    fprintf(stderr, "   -out fname: specify where to store the results (use `-' for stdout)\n");
    fprintf(stderr, "               (default: `2x1gen.out')\n");
    fprintf(stderr, "   -q: quiet mode\n");
    fprintf(stderr, "   -iter maxiters maxerr: specify maximum number of iterations, and max error\n");
    fprintf(stderr, "              (default: 1000000 .001)\n");
    fprintf(stderr, "  xsize and ysize specify the size of the landscape\n");
    fprintf(stderr, "  p0 and q00 are the landscape parameters as described in the paper\n");
    exit(2);
}


/*
 * Seed the random number generator, based on the current time in
 * milliseconds.  This will probably need to be rewritten for non-Unix
 * systems.
 */
void
my_srandom()
{
    struct timeval tp;
    struct timezone *tzp;
    FILE *fp;

    tzp = NULL;
    gettimeofday(&tp, tzp);
    srandom((int) tp.tv_usec);
    if ((fp=fopen(".2x1gen.srandom", "w"))==NULL)
	return;
    fprintf(fp, "%d\n", tp.tv_usec);
    fclose(fp);
}


/*
 * Return 1 a specified fraction of the time, else return 0.
 */
int
randFrac(double frac)
{
    int i;
    double d;

    /* On the system I use, the random() function returns a random
     * 32-bit integer.  If you are using a computer which has a
     * function that returns a random floating-point number between
     * 0 and 1, then you can just use that function to set the value
     * of the variable "d" directly, which is basically what I achieve
     * below.
     */
    i = random() % RAND01_PRECISION;
    d = ((double) i) / (double)RAND01_PRECISION;
    if (d < frac)
	return 1;
    else
	return 0;
}


/*
 * Check to see whether a number is close enough to zero that we really
 * should call it zero.
 */
int
fis_zero(double x)
{
    if (fabs(x) > EPSILON)
	return 0;
    else
	return 1;
}


/*
 * Measure the 2x1 block counts in the lattice.
 */
void
measureBlockCounts(int counts[4],  /* array for returning the counts */
		   byte *lattice,  /* pointer to the landscape array */
		   int xsize, int ysize)  /* size of the landscape array */
{
    int i, x, y, x1, y1;

    /* First initialize the counts to zero */
    for (i=0; i < 4; i++)
	counts[i] = 0;
    
    for (y=0; y < ysize; y++)
	for (x=0; x < xsize; x++) {
	    /* north */
	    x1 = x;
	    y1 = (y-1+ysize)%ysize;
	    counts[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]]++;

	    /* south */
	    x1 = x;
	    y1 = (y+1)%ysize;
	    counts[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]]++;

	    /* east */
	    x1 = (x+1)%xsize;
	    y1 = y;
	    counts[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]]++;

	    /* west */
	    x1 = (x-1+xsize)%xsize;
	    y1 = y;
	    counts[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]]++;
	}
}


/*
 * Compute how the block counts would be affected if we were
 * to flip the state of the cell at position (x,y)
 */
void
computeCountChanges(int changes[4], /* array to return the vector of changes */
		    byte *lattice,  /* landscape array */
		    int xsize, int ysize, /* size of landscape */
		    int x, int y)  /* which site we want to flip */
{
    int x1, y1, i;

    /* Clear out array just to be safe */
    for (i=0; i < 4; i++)
	changes[i] = 0;

    /* north */
    x1 = x;
    y1 = (y-1+ysize)%ysize;
    /* First decrease the appropriate count for the current configuration.
    * Note we always increase or decrease by 2, because every time we make
    * a flip, it will change the 2x1 block as seen from both ends, so to
    * speak, so we need to count the change twice. */
    changes[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]] -= 2;
    /* And then increase the count for the new configuration */
    changes[(1-lattice[y*xsize+x])*2 + lattice[y1*xsize+x1]] += 2;

    /* south */
    x1 = x;
    y1 = (y+1)%ysize;
    changes[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]] -= 2;
    changes[(1-lattice[y*xsize+x])*2 + lattice[y1*xsize+x1]] += 2;

    /* east */
    x1 = (x+1)%xsize;
    y1 = y;
    changes[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]] -= 2;
    changes[(1-lattice[y*xsize+x])*2 + lattice[y1*xsize+x1]] += 2;

    /* west */
    x1 = (x-1+xsize)%xsize;
    y1 = y;
    changes[lattice[y*xsize+x]*2 + lattice[y1*xsize+x1]] -= 2;
    changes[(1-lattice[y*xsize+x])*2 + lattice[y1*xsize+x1]] += 2;

    /* Now handle symmetry, lumping together the changes for [01] and [10]
    * blocks, since they are really the same by symmetry assumptions. */
    i = changes[1] + changes[2];
    if (i%2)
	quit("computeCountChanges(): Hey, 'i' was odd!!! That shouldn't be possible!");

    changes[1] = changes[2] = i/2;
}


/*
 * Given the parameters p0 and q00, compute the 2x1 block
 * probabilities, p00, p01, and p11.
 */
void
condProbsTo2x1Probs(double p0, double q00,  /* input parameters */
		    double *p00, double *p01, double *p11)  /* return parms */
{
    *p00 = p0 * q00;				/* p00 */
    *p01 = p0 - *p00;				/* p01 */
    *p11 = 1.0 - (*p00 + 2.0 * (*p01));		/* p11 */

    /* Sometimes we end up with a parameter which is 1.0e-10, i.e. zero for
     * all practical purposes.  Here we set such values to actually BE zero,
     * for simplicity */
    if (fis_zero(*p00))
	*p00 = 0.0;
    if (fis_zero(*p01))
	*p01 = 0.0;
    if (fis_zero(*p11))
	*p11 = 0.0;
    return;
}


/*
 * This is the heart of the program.  It generates the landscape with
 * spatially structured heterogeneities.
 */
void
gen2x1(byte *lattice,  /* landscape array */
       int xsize, int ysize,  /* size of landscape */
       double p0, double q00,  /* the main parameter values */
       int quiet,   /* whether or not to work quietly */
       int iterates, /*maximum number of times to loop through the algorithm*/
       double maxErr)  /* the maximum difference between measured and desired
			* probabilities that we will accept when stopping */
{
    int
	x, y, x1, y1, i, j,
	blockCounts[4],  /* used to count the numbers of occurances of the
			  * various possible 2x1 blocks on the landscape */
	tmpCounts[4],  /* a temporary array of counts */
	targetCounts[4],  /* the counts we would see if we had the landscape
			   * we are trying to get */
	countChanges[4],  /* for computing changes to counts */
	oldErr, newErr,  /* differences between desired and achieved COUNTS */
	timesFlipped,  /* for counting how many times we flipped sites */
	stop;  /* flag for stopping the algorithm */
    double p00, p01, p11,  /* the 2x1 block probabilities */
	p1,
	probErr, tmpProbErr; /* differences between desired and
			      * achieved PROBABILITIES */

    timesFlipped = 0;

    /* Compute the 2x1 block probabilities */
    condProbsTo2x1Probs(p0, q00, &p00, &p01, &p11);
    p1 = 1.0 - p0;

    /* First fill the lattice randomly, using the correct patch
     * occupancy probability */
    bzero(lattice, xsize*ysize);  /* Clear out the lattice */
    for (y=0; y < ysize; y++)
	for (x=0; x < xsize; x++)
	    if (randFrac(p1))
		lattice[y*xsize + x] = 1;

    /* Now measure the block counts from the lattice */
    measureBlockCounts(blockCounts, lattice, xsize, ysize);

    /* Compute target block counts from the block probabilities.
     * The reason for the factor of 4 is because when we count blocks,
     * we check 4 neighbors of each site.
     */
    targetCounts[0] = (int)(p00 * xsize*ysize*4);
    targetCounts[1] = targetCounts[2] = (int)(p01 * xsize*ysize*4);
    targetCounts[3] = (int)(p11 * xsize*ysize*4);

    if (quiet == 0) {  /* Print out some diagnostic info */
	printf("Target counts are ");
	for (j=0; j < 4; j++)
	    printf("%d  ", targetCounts[j]);
	printf("  (sum = %d)", targetCounts[0]+targetCounts[1]+
	       targetCounts[2]+targetCounts[3]);
	printf("\n");

	printf("Starting counts are ");
	for (j=0; j < 4; j++)
	    printf("%d  ", blockCounts[j]);
	printf("  (sum = %d)", blockCounts[0]+blockCounts[1]+
	       blockCounts[2]+blockCounts[3]);
	printf("\n");
    }


    oldErr = 0;
    /* Initialize the discrepancy between desired and achieved counts */
    for (i=0; i < 4; i++)
	oldErr += abs(targetCounts[i] - blockCounts[i]);

    /* Now comes the main loop.  We repeatedly pick a site at random, and
     * see if flipping its value (0 to 1, or 1 to 0) would move the block
     * counts (or equivalently, the 2x1 block probabilities) closer to the
     * desired values.  If so, flip the site.  Otherwise, try again. */
    stop = 0;
    for (i=0; (i < iterates) && (stop == 0); i++) {
	for (j=0; j < 4; j++)  /* copy the current counts into a temp. array */
	    tmpCounts[j] = blockCounts[j];

	/* Pick a random site.
	 * If your random number generator returns a number between 0 and 1,
	 * you will need to do something like this [say your random number
	 * generator is called rand01() ]:
	 *   x = (int)(rand01() * xsize);
	 *   y = (int)(rand01() * ysize);
	 * That assumes rand01() returns a number greater than or equal
	 * to zero, but strictly less than 1.
	 */
	x = random()%xsize;
	y = random()%ysize;

	/* now see how the counts would be affected if we were to
	 * change the state of this cell */
	computeCountChanges(countChanges, lattice, xsize, ysize, x, y);

	/* Merge the changes back into the count array */
	for (j=0; j < 4; j++)
	    tmpCounts[j] += countChanges[j];

	/* And compute what the new discrepancy between desired and achieved
	 * counts would be if we make this flip */
	newErr = 0;
	for (j=0; j < 4; j++)
	    newErr += abs(targetCounts[j] - tmpCounts[j]);

	/* if the new error would be smaller, flip the site's value */
	if (newErr <= oldErr) {
	    lattice[y*xsize+x] = 1-lattice[y*xsize+x];
	    oldErr = newErr;
	    /* Move the temporary copy of the counts into the regular array */
	    for (j=0; j < 4; j++)
		blockCounts[j] = tmpCounts[j];
	    timesFlipped++;  /* keep track of how many flips we make */
	}

	/* Convert the discrepancy in number of blocks into discrepancy
	 * in probabilities, by normalizing */
	probErr = ((double)oldErr)/(double)(xsize*ysize*4.0);

	/* And if the error is small enough, stop. */
	if (probErr < maxErr)
	    stop = 1;
    }

    if (quiet == 0) {
	printf("Achieved counts are ");
	for (j=0; j < 4; j++)
	    printf("%d  ", blockCounts[j]);
	printf("  (sum = %d)", blockCounts[0]+blockCounts[1]+
	       blockCounts[2]+blockCounts[3]);
	printf("\n");

	printf("Changed site's value a fraction %lg of the time, after %d iterations\n",
	       ((double)timesFlipped)/(double)i, i);
    }
}


/*
 * Main() routine.
 */
main(int argc, char **argv)
{
    int xsize, ysize,  /* size of landscape */
	iterates=DEFAULT_MAXITERS,  /* maximum number of iterations */
	quiet,  /* flag for controlling output */
	x, y, i, tmpInt,
	asciiFlag=1,  /* whether or not we should output the landscape in
		      * a human-readable format */
	pgmFlag=0,
	blockCounts[4],
	rseed, gotSeed=0;
    double p00, p01, p11,  /* the 2x1 block probabilities */
	p0, q00,  /* marginal and conditional probability parameters */
	dsum, actualProbs[4], actualp0, actualq00,
	maxErr=DEFAULT_MAXERR;  /* maximum discrepancy in probabilities that
				 * we will accept */
    char outFname[256],  /* name of output file */
	char0=DEFAULT_CHAR0, char1=DEFAULT_CHAR1;  /* output characters */
    FILE *fp;
    byte *lattice;  /* the main landscape array */

    quiet=0;
    sprintf(outFname, DEFAULT_OUTFNAME);


    /*
     * Parse command-line arguments
     */
    i = 1;
    while ((i < argc) && (argv[i][0] == '-')) {
	if (!strcmp(argv[i], "-byte")) {
	    if (i >= argc-2)
		printusage(argv[0]);
	    if (sscanf(argv[i+1], "%d", &tmpInt) != 1)
		printusage(argv[0]);
	    char0 = (char)tmpInt;
	    if (sscanf(argv[i+2], "%d", &tmpInt) != 1)
		printusage(argv[0]);
	    char1 = (char)tmpInt;
	    asciiFlag = 0;
	    i += 3;
	}
	else if (!strcmp(argv[i], "-char")) {
	    if (i >= argc-2)
		printusage(argv[0]);
	    if (strlen(argv[i+1]) != 1)
		printusage(argv[0]);
	    char0 = argv[i+1][0];
	    if (strlen(argv[i+2]) != 1)
		printusage(argv[0]);
	    char1 = argv[i+2][0];
	    i += 3;
	}
	else if (!strcmp(argv[i], "-pgm")) {
	    pgmFlag = 1;
	    asciiFlag = 0;
	    char0 = 0;
	    char1 = 1;
	    i++;
	}
	else if (!strcmp(argv[i], "-s")) {
	    gotSeed = 1;
	    if (i >= argc-1)
		printusage(argv[0]);
	    if (sscanf(argv[i+1], "%d", &rseed) != 1)
		printusage(argv[0]);
	    srandom(rseed);
	    i += 2;
	}
	else if (!strcmp(argv[i], "-out")) {
	    if (i >= argc-1)
		printusage(argv[0]);
	    strncpy(outFname, argv[i+1], 255);
	    i += 2;
	}
	else if (!strcmp(argv[i], "-q")) {
	    quiet = 1;
	    i++;
	}
	else if ((!strcmp(argv[i], "-iter")) || (!strcmp(argv[i], "-iters"))) {
	    if (i >= argc-2)
		printusage(argv[0]);
	    if (sscanf(argv[i+1], "%d", &iterates) != 1)
		printusage(argv[0]);
	    if (sscanf(argv[i+2], "%lg", &maxErr) != 1)
		printusage(argv[0]);
	    i += 3;
	}
	else
	    printusage(argv[0]);
    }
    if (gotSeed == 0)
	my_srandom();
    if (argc != i+4)
	printusage(argv[0]);
    if (sscanf(argv[i++], "%d", &xsize) != 1)
	printusage(argv[0]);
    if (sscanf(argv[i++], "%d", &ysize) != 1)
	printusage(argv[0]);
    if (sscanf(argv[i++], "%lg", &p0) != 1)
	printusage(argv[0]);
    if (sscanf(argv[i++], "%lg", &q00) != 1)
	printusage(argv[0]);

    /* Compute 2x1 block probabilities */
    condProbsTo2x1Probs(p0, q00, &p00, &p01, &p11);

    if (quiet == 0) {
	printf("p0 = %lg, q00 = %lg\n", p0, q00);
	printf("p00 = %lg, p01 = %lg, p11 = %lg\n", p00, p01, p11);
    }

    /* Check to make sure there aren't any problems with the parameters
     * entered (even problems that may come up due to round-off). */
    if ((p00 < 0.0) || (p01 < 0.0) || (p11 < 0.0) ||
	(p00 > 1.0) || (p01 > 1.0) || (p11 > 1.0))
	quit("Invalid values for p0 and q00");
    if ((p00 + 2.0*p01 - 1.0 > EPSILON) || (p11 + 2.0*p01 - 1.0 > EPSILON))
	quit("Probabilities were too large!");

    /* Allocate memory for the landscape array */
    if ((lattice=(byte *)malloc(xsize*ysize))==NULL)
	quit("Couldn't malloc lattice");

    /* Open output file */
    if (!strcmp(outFname, "-"))
	fp=stdout;
    else
	if ((fp=fopen(outFname, "w"))==NULL)
	    quit("Couldn't open 2x1gen.out");


    /* Now generate the landscape. */
    gen2x1(lattice, xsize, ysize, p0, q00, quiet, iterates, maxErr);

    /* And output it. */
    /* First I output a standard header, which my simulation program
     * will recognize, to make sure I use the right kind of landscape files
     * with the simulations... */
    if (asciiFlag) {
	fprintf(fp, "ascii landscape v0.2\n");
	fprintf(fp, "xsize=%d ysize=%d 0char=%c 1char=%c\n", xsize, ysize,
		char0, char1);
    }
    else if (pgmFlag) {
	fprintf(fp, "P5\n");  /* header for PGM file */
	fprintf(fp, "%d %d\n", xsize, ysize);  /* image size */
	fprintf(fp, "1\n");   /* maximum gray value in the image */
    }
    else {
	fprintf(fp, "landscape v0.2\n");
	fprintf(fp, "xsize=%d ysize=%d 0byte=%d 1byte=%d\n", xsize, ysize,
		(int)char0, (int)char1);
    }
    for (y=0; y < ysize; y++) {
	for (x=0; x < xsize; x++)
	    if (lattice[y*xsize+x] == 0)
		putc(char0, fp);
	    else
		putc(char1, fp);
	if (asciiFlag)
	    putc('\n', fp);
    }
    fclose(fp);

    /* Now compute some diagnostics */
    measureBlockCounts(blockCounts, lattice, xsize, ysize);

    /* Compute the 2x1 block probabilities in the landscape we generated */
    for (i=0; i < 4; i++)
	actualProbs[i] = ((double)blockCounts[i])/(xsize*ysize*4.0);
    actualp0 = actualProbs[0] + actualProbs[1];
    if (actualp0 == 0.0)
	actualq00 = 0.0;
    else
	actualq00 = actualProbs[0]/actualp0;  /*  q00 = p[00]/p0  */

    if (quiet == 0) {
	printf("=====Desired 2x1 block probabilities=====\n");
	printf("p[00]=%lg  p[01]=%lg  p[11]=%lg\n", p00, p01, p11);
	printf("=====Actual 2x1 block probabilities=====\n");
	printf("p[00]=%lg  p[01]=%lg  p[11]=%lg\n", actualProbs[0],
	       actualProbs[1], actualProbs[3]);
	printf("\n");
    }

    if (quiet == 0) {
	printf("====Desired marginal and conditional probabilities====\n");
	printf("p0 = %lg, q00 = %lg\n", p0, q00);
	printf("====Actual marginal and conditional probabilities====\n");
	printf("p0 = %lg, q00 = %lg\n", actualp0, actualq00);
    }

    dsum = fabs(actualProbs[0] - p00) + fabs(actualProbs[1] - p01) +
	fabs(actualProbs[2] - p01) + fabs(actualProbs[3] - p11);

    if (quiet == 0)
	printf("1-norm of difference in 2x1 block probs is %lg\n", dsum);
    exit(0);
}
