/*
 *  module: compl.c
 *  purpose: compute the complement of a multiple-valued function
 *
 *  The "unate recursive paradigm" is used.  After a set of special
 *  cases are examined, the function is split on the "most active
 *  variable".  These two halves are complemented recursively, and then
 *  the results are merged.
 *
 *  Changes (from Version 2.1 to Version 2.2)
 *      1. Minor bug in compl_lifting -- cubes in the left half were
 *      not marked as active, so that when merging a leaf from the left
 *      hand side, the active flags were essentially random.  This led
 *      to minor impredictability problem, but never affected the
 *      accuracy of the results.
 */

#include "espresso.h"

#define USE_COMPL_LIFT			0
#define USE_COMPL_LIFT_ONSET		1
#define USE_COMPL_LIFT_ONSET_COMPLEX	2
#define NO_LIFTING			3

static bool compl_special_cases(pset *T, pset_family *Tbar);
static pcover compl_merge(pset *T1, pset_family L, pset_family R, register pset cl, register pset cr, int var, int lifting);
static void compl_d1merge(register pset *L1, register pset *R1);
static pcover compl_cube(register pset p);
static void compl_lift(pset *A1, pset *B1, pset bcube, int var);
static void compl_lift_onset(pset *A1, pset_family T, pset bcube, int var);
static void compl_lift_onset_complex(pset *A1, pset_family T, int var);
static bool simp_comp_special_cases(pset *T, pset_family *Tnew, pset_family *Tbar);
static bool simplify_special_cases(pset *T, pset_family *Tnew);


/* complement -- compute the complement of T */
pcover complement(pset *T)
         			/* T will be disposed of */
{
    register pcube cl, cr;
    register int best;
    pcover Tbar, Tl, Tr;
    int lifting;
    static int compl_level = 0;

    if (debug & COMPL)
	debug_print(T, "COMPLEMENT", compl_level++);

    if (compl_special_cases(T, &Tbar) == MAYBE) {

	/* Allocate space for the partition cubes */
	cl = new_cube();
	cr = new_cube();
	best = binate_split_select(T, cl, cr, COMPL);

	/* Complement the left and right halves */
	Tl = complement(scofactor(T, cl, best));
	Tr = complement(scofactor(T, cr, best));

	if (Tr->count*Tl->count > (Tr->count+Tl->count)*CUBELISTSIZE(T)) {
	    lifting = USE_COMPL_LIFT_ONSET;
	} else {
	    lifting = USE_COMPL_LIFT;
	}
	Tbar = compl_merge(T, Tl, Tr, cl, cr, best, lifting);

	free_cube(cl);
	free_cube(cr);
	free_cubelist(T);
    }

    if (debug & COMPL)
	debug1_print(Tbar, "exit COMPLEMENT", --compl_level);
    return Tbar;
}

static bool compl_special_cases(pset *T, pset_family *Tbar)
         			/* will be disposed if answer is determined */
             			/* returned only if answer determined */
{
    register pcube *T1, p, ceil, cof=T[0];
    pcover A, ceil_compl;

    /* Check for no cubes in the cover */
    if (T[2] == NULL) {
	*Tbar = sf_addset(new_cover(1), cube.fullset);
	free_cubelist(T);
	return TRUE;
    }

    /* Check for only a single cube in the cover */
    if (T[3] == NULL) {
	*Tbar = compl_cube(set_or(cof, cof, T[2]));
	free_cubelist(T);
	return TRUE;
    }

    /* Check for a row of all 1's (implies complement is null) */
    for(T1 = T+2; (p = *T1++) != NULL; ) {
	if (full_row(p, cof)) {
	    *Tbar = new_cover(0);
	    free_cubelist(T);
	    return TRUE;
	}
    }

    /* Check for a column of all 0's which can be factored out */
    ceil = set_save(cof);
    for(T1 = T+2; (p = *T1++) != NULL; ) {
	INLINEset_or(ceil, ceil, p);
    }
    if (! setp_equal(ceil, cube.fullset)) {
	ceil_compl = compl_cube(ceil);
	(void) set_or(cof, cof, set_diff(ceil, cube.fullset, ceil));
	set_free(ceil);
	*Tbar = sf_append(complement(T), ceil_compl);
	return TRUE;
    }
    set_free(ceil);

    /* Collect column counts, determine unate variables, etc. */
    massive_count(T);

    /* If single active variable not factored out above, then tautology ! */
    if (cdata.vars_active == 1) {
	*Tbar = new_cover(0);
	free_cubelist(T);
	return TRUE;

    /* Check for unate cover */
    } else if (cdata.vars_unate == cdata.vars_active) {
	A = map_cover_to_unate(T);
	free_cubelist(T);
	A = unate_compl(A);
	*Tbar = map_unate_to_cover(A);
	sf_free(A);
	return TRUE;

    /* Not much we can do about it */
    } else {
	return MAYBE;
    }
}

/*
 *  compl_merge -- merge the two cofactors around the splitting
 *  variable
 *
 *  The merge operation involves intersecting each cube of the left
 *  cofactor with cl, and intersecting each cube of the right cofactor
 *  with cr.  The union of these two covers is the merged result.
 *
 *  In order to reduce the number of cubes, a distance-1 merge is
 *  performed (note that two cubes can only combine distance-1 in the
 *  splitting variable).  Also, a simple expand is performed in the
 *  splitting variable (simple implies the covering check for the
 *  expansion is not full containment, but single-cube containment).
 */

static pcover compl_merge(pset *T1, pset_family L, pset_family R, register pset cl, register pset cr, int var, int lifting)
          			/* Original ON-set */
            			/* Complement from each recursion branch */
                      		/* cubes used for cofactoring */
        			/* splitting variable */
            			/* whether to perform lifting or not */
{
    register pcube p, last, pt;
    pcover T, Tbar;
    pcube *L1, *R1;

    if (debug & COMPL) {
	printf("compl_merge: left %d, right %d\n", L->count, R->count);
	printf("%s (cl)\n%s (cr)\nLeft is\n", pc1(cl), pc2(cr));
	cprint(L);
	printf("Right is\n");
	cprint(R);
    }

    /* Intersect each cube with the cofactored cube */
    foreach_set(L, last, p) {
	INLINEset_and(p, p, cl);
	SET(p, ACTIVE);
    }
    foreach_set(R, last, p) {
	INLINEset_and(p, p, cr);
	SET(p, ACTIVE);
    }

    /* Sort the arrays for a distance-1 merge */
    (void) set_copy(cube.temp[0], cube.var_mask[var]);
    qsort((char *) (L1 = sf_list(L)), L->count, sizeof(pset), (qsort_compare_func) d1_order);
    qsort((char *) (R1 = sf_list(R)), R->count, sizeof(pset), (qsort_compare_func) d1_order);

    /* Perform distance-1 merge */
    compl_d1merge(L1, R1);

    /* Perform lifting */
    switch(lifting) {
	case USE_COMPL_LIFT_ONSET:
	    T = cubeunlist(T1);
	    compl_lift_onset(L1, T, cr, var);
	    compl_lift_onset(R1, T, cl, var);
	    free_cover(T);
	    break;
	case USE_COMPL_LIFT_ONSET_COMPLEX:
	    T = cubeunlist(T1);
	    compl_lift_onset_complex(L1, T, var);
	    compl_lift_onset_complex(R1, T, var);
	    free_cover(T);
	    break;
	case USE_COMPL_LIFT:
	    compl_lift(L1, R1, cr, var);
	    compl_lift(R1, L1, cl, var);
	    break;
	case NO_LIFTING:
	    break;
    }
    FREE(L1);
    FREE(R1);

    /* Re-create the merged cover */
    Tbar = new_cover(L->count + R->count);
    pt = Tbar->data;
    foreach_set(L, last, p) {
	INLINEset_copy(pt, p);
	Tbar->count++;
	pt += Tbar->wsize;
    }
    foreach_active_set(R, last, p) {
	INLINEset_copy(pt, p);
	Tbar->count++;
	pt += Tbar->wsize;
    }

    if (debug & COMPL) {
	printf("Result %d\n", Tbar->count);
	if (verbose_debug)
	    cprint(Tbar);
    }

    free_cover(L);
    free_cover(R);
    return Tbar;
}

/*
 *  compl_lift_simple -- expand in the splitting variable using single
 *  cube containment against the other recursion branch to check
 *  validity of the expansion, and expanding all (or none) of the
 *  splitting variable.
 */
static void compl_lift(pset *A1, pset *B1, pset bcube, int var)
{
    register pcube a, b, *B2, lift=cube.temp[4], liftor=cube.temp[5];
    pcube mask = cube.var_mask[var];

    (void) set_and(liftor, bcube, mask);

    /* for each cube in the first array ... */
    for(; (a = *A1++) != NULL; ) {
	if (TESTP(a, ACTIVE)) {

	    /* create a lift of this cube in the merging coord */
	    (void) set_merge(lift, bcube, a, mask);

	    /* for each cube in the second array */
	    for(B2 = B1; (b = *B2++) != NULL; ) {
		INLINEsetp_implies(lift, b, /* when_false => */ continue);
		/* when_true => fall through to next statement */

		/* cube of A1 was contained by some cube of B1, so raise */
		INLINEset_or(a, a, liftor);
		break;
	    }
	}
    }
}



/*
 *  compl_lift_onset -- expand in the splitting variable using a
 *  distance-1 check against the original on-set; expand all (or
 *  none) of the splitting variable.  Each cube of A1 is expanded
 *  against the original on-set T.
 */
static void compl_lift_onset(pset *A1, pset_family T, pset bcube, int var)
{
    register pcube a, last, p, lift=cube.temp[4], mask=cube.var_mask[var];

    /* for each active cube from one branch of the complement */
    for(; (a = *A1++) != NULL; ) {
	if (TESTP(a, ACTIVE)) {

	    /* create a lift of this cube in the merging coord */
	    INLINEset_and(lift, bcube, mask);	/* isolate parts to raise */
	    INLINEset_or(lift, a, lift);	/* raise these parts in a */

	    /* for each cube in the ON-set, check for intersection */
	    foreach_set(T, last, p) {
		if (cdist0(p, lift)) {
		    goto nolift;
		}
	    }
	    INLINEset_copy(a, lift);		/* save the raising */
	    SET(a, ACTIVE);
nolift : ;
	}
    }
}

/*
 *  compl_lift_complex -- expand in the splitting variable, but expand all
 *  parts which can possibly expand.
 *  T is the original ON-set
 *  A1 is either the left or right cofactor
 */
static void compl_lift_onset_complex(pset *A1, pset_family T, int var)
          			/* array of pointers to new result */
         			/* original ON-set */
        			/* which variable we split on */
{
    register int dist;
    register pcube last, p, a, xlower;

    /* for each cube in the complement */
    xlower = new_cube();
    for(; (a = *A1++) != NULL; ) {

	if (TESTP(a, ACTIVE)) {

	    /* Find which parts of the splitting variable are forced low */
	    INLINEset_clear(xlower, cube.size);
	    foreach_set(T, last, p) {
		if ((dist = cdist01(p, a)) < 2) {
		    if (dist == 0) {
			fatal("compl: ON-set and OFF-set are not orthogonal");
		    } else {
			(void) force_lower(xlower, p, a);
		    }
		}
	    }

	    (void) set_diff(xlower, cube.var_mask[var], xlower);
	    (void) set_or(a, a, xlower);
	    free_cube(xlower);
	}
    }
}



/*
 *  compl_d1merge -- distance-1 merge in the splitting variable
 */
static void compl_d1merge(register pset *L1, register pset *R1)
{
    register pcube pl, pr;

    /* Find equal cubes between the two cofactors */
    for(pl = *L1, pr = *R1; (pl != NULL) && (pr != NULL); )
	switch (d1_order(L1, R1)) {
	    case 1:
		pr = *(++R1); break;            /* advance right pointer */
	    case -1:
		pl = *(++L1); break;            /* advance left pointer */
	    case 0:
		RESET(pr, ACTIVE);
		INLINEset_or(pl, pl, pr);
		pr = *(++R1);
	}
}



/* compl_cube -- return the complement of a single cube (De Morgan's law) */
static pcover compl_cube(register pset p)
{
    register pcube diff=cube.temp[7], pdest, mask, full=cube.fullset;
    int var;
    pcover R;

    /* Allocate worst-case size cover (to avoid checking overflow) */
    R = new_cover(cube.num_vars);

    /* Compute bit-wise complement of the cube */
    INLINEset_diff(diff, full, p);

    for(var = 0; var < cube.num_vars; var++) {
	mask = cube.var_mask[var];
	/* If the bit-wise complement is not empty in var ... */
	if (! setp_disjoint(diff, mask)) {
	    pdest = GETSET(R, R->count++);
	    INLINEset_merge(pdest, diff, full, mask);
	}
    }
    return R;
}

/* simp_comp -- quick simplification of T */
void simp_comp(pset *T, pset_family *Tnew, pset_family *Tbar)
         			/* T will be disposed of */
             
             
{
    register pcube cl, cr;
    register int best;
    pcover Tl, Tr, Tlbar, Trbar;
    int lifting;
    static int simplify_level = 0;

    if (debug & COMPL)
	debug_print(T, "SIMPCOMP", simplify_level++);

    if (simp_comp_special_cases(T, Tnew, Tbar) == MAYBE) {

	/* Allocate space for the partition cubes */
	cl = new_cube();
	cr = new_cube();
	best = binate_split_select(T, cl, cr, COMPL);

	/* Complement the left and right halves */
	simp_comp(scofactor(T, cl, best), &Tl, &Tlbar);
	simp_comp(scofactor(T, cr, best), &Tr, &Trbar);

	lifting = USE_COMPL_LIFT;
	*Tnew = compl_merge(T, Tl, Tr, cl, cr, best, lifting);

	lifting = USE_COMPL_LIFT;
	*Tbar = compl_merge(T, Tlbar, Trbar, cl, cr, best, lifting);

	/* All of this work for nothing ? Let's hope not ... */
	if ((*Tnew)->count > CUBELISTSIZE(T)) {
	    sf_free(*Tnew);
	    *Tnew = cubeunlist(T);
	}

	free_cube(cl);
	free_cube(cr);
	free_cubelist(T);
    }

    if (debug & COMPL) {
	debug1_print(*Tnew, "exit SIMPCOMP (new)", simplify_level);
	debug1_print(*Tbar, "exit SIMPCOMP (compl)", simplify_level);
	simplify_level--;
    }
}

static bool simp_comp_special_cases(pset *T, pset_family *Tnew, pset_family *Tbar)
         			/* will be disposed if answer is determined */
             			/* returned only if answer determined */
             			/* returned only if answer determined */
{
    register pcube *T1, p, ceil, cof=T[0];
    pcube last;
    pcover A;

    /* Check for no cubes in the cover (function is empty) */
    if (T[2] == NULL) {
	*Tnew = new_cover(1);
	*Tbar = sf_addset(new_cover(1), cube.fullset);
	free_cubelist(T);
	return TRUE;
    }

    /* Check for only a single cube in the cover */
    if (T[3] == NULL) {
	(void) set_or(cof, cof, T[2]);
	*Tnew = sf_addset(new_cover(1), cof);
	*Tbar = compl_cube(cof);
	free_cubelist(T);
	return TRUE;
    }

    /* Check for a row of all 1's (function is a tautology) */
    for(T1 = T+2; (p = *T1++) != NULL; ) {
	if (full_row(p, cof)) {
	    *Tnew = sf_addset(new_cover(1), cube.fullset);
	    *Tbar = new_cover(1);
	    free_cubelist(T);
	    return TRUE;
	}
    }

    /* Check for a column of all 0's which can be factored out */
    ceil = set_save(cof);
    for(T1 = T+2; (p = *T1++) != NULL; ) {
	INLINEset_or(ceil, ceil, p);
    }
    if (! setp_equal(ceil, cube.fullset)) {
	p = new_cube();
	(void) set_diff(p, cube.fullset, ceil);
	(void) set_or(cof, cof, p);
	set_free(p);
	simp_comp(T, Tnew, Tbar);

	/* Adjust the ON-set */
	A = *Tnew;
	foreach_set(A, last, p) {
	    INLINEset_and(p, p, ceil);
	}

	/* Compute the new complement */
	*Tbar = sf_append(*Tbar, compl_cube(ceil));
	set_free(ceil);
	return TRUE;
    }
    set_free(ceil);

    /* Collect column counts, determine unate variables, etc. */
    massive_count(T);

    /* If single active variable not factored out above, then tautology ! */
    if (cdata.vars_active == 1) {
	*Tnew = sf_addset(new_cover(1), cube.fullset);
	*Tbar = new_cover(1);
	free_cubelist(T);
	return TRUE;

    /* Check for unate cover */
    } else if (cdata.vars_unate == cdata.vars_active) {
	/* Make the cover minimum by single-cube containment */
	A = cubeunlist(T);
	*Tnew = sf_contain(A);

	/* Now form a minimum representation of the complement */
	A = map_cover_to_unate(T);
	A = unate_compl(A);
	*Tbar = map_unate_to_cover(A);
	sf_free(A);
	free_cubelist(T);
	return TRUE;

    /* Not much we can do about it */
    } else {
	return MAYBE;
    }
}

/* simplify -- quick simplification of T */
pcover simplify(pset *T)
         			/* T will be disposed of */
{
    register pcube cl, cr;
    register int best;
    pcover Tbar, Tl, Tr;
    int lifting;
    static int simplify_level = 0;

    if (debug & COMPL) {
	debug_print(T, "SIMPLIFY", simplify_level++);
    }

    if (simplify_special_cases(T, &Tbar) == MAYBE) {

	/* Allocate space for the partition cubes */
	cl = new_cube();
	cr = new_cube();

	best = binate_split_select(T, cl, cr, COMPL);

	/* Complement the left and right halves */
	Tl = simplify(scofactor(T, cl, best));
	Tr = simplify(scofactor(T, cr, best));

	lifting = USE_COMPL_LIFT;
	Tbar = compl_merge(T, Tl, Tr, cl, cr, best, lifting);

	/* All of this work for nothing ? Let's hope not ... */
	if (Tbar->count > CUBELISTSIZE(T)) {
	    sf_free(Tbar);
	    Tbar = cubeunlist(T);
	}

	free_cube(cl);
	free_cube(cr);
	free_cubelist(T);
    }

    if (debug & COMPL) {
	debug1_print(Tbar, "exit SIMPLIFY", --simplify_level);
    }
    return Tbar;
}

static bool simplify_special_cases(pset *T, pset_family *Tnew)
         			/* will be disposed if answer is determined */
             			/* returned only if answer determined */
{
    register pcube *T1, p, ceil, cof=T[0];
    pcube last;
    pcover A;

    /* Check for no cubes in the cover */
    if (T[2] == NULL) {
	*Tnew = new_cover(0);
	free_cubelist(T);
	return TRUE;
    }

    /* Check for only a single cube in the cover */
    if (T[3] == NULL) {
	*Tnew = sf_addset(new_cover(1), set_or(cof, cof, T[2]));
	free_cubelist(T);
	return TRUE;
    }

    /* Check for a row of all 1's (implies function is a tautology) */
    for(T1 = T+2; (p = *T1++) != NULL; ) {
	if (full_row(p, cof)) {
	    *Tnew = sf_addset(new_cover(1), cube.fullset);
	    free_cubelist(T);
	    return TRUE;
	}
    }

    /* Check for a column of all 0's which can be factored out */
    ceil = set_save(cof);
    for(T1 = T+2; (p = *T1++) != NULL; ) {
	INLINEset_or(ceil, ceil, p);
    }
    if (! setp_equal(ceil, cube.fullset)) {
	p = new_cube();
	(void) set_diff(p, cube.fullset, ceil);
	(void) set_or(cof, cof, p);
	free_cube(p);

	A = simplify(T);
	foreach_set(A, last, p) {
	    INLINEset_and(p, p, ceil);
	}
	*Tnew = A;
	set_free(ceil);
	return TRUE;
    }
    set_free(ceil);

    /* Collect column counts, determine unate variables, etc. */
    massive_count(T);

    /* If single active variable not factored out above, then tautology ! */
    if (cdata.vars_active == 1) {
	*Tnew = sf_addset(new_cover(1), cube.fullset);
	free_cubelist(T);
	return TRUE;

    /* Check for unate cover */
    } else if (cdata.vars_unate == cdata.vars_active) {
	A = cubeunlist(T);
	*Tnew = sf_contain(A);
	free_cubelist(T);
	return TRUE;

    /* Not much we can do about it */
    } else {
	return MAYBE;
    }
}
