/*
 * Revision Control Information
 *
 * $Source$
 * $Author$
 * $Revision$
 * $Date$
 *
 */
/*
 *  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();
static pcover compl_merge();
static void compl_d1merge();
static pcover compl_cube();
static void compl_lift();
static void compl_lift_onset();
static void compl_lift_onset_complex();
static bool simp_comp_special_cases();
static bool simplify_special_cases();


/* complement -- compute the complement of T */
pcover complement(T)
pcube *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(T, Tbar)
pcube *T;            /* will be disposed if answer is determined */
pcover *Tbar;            /* 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(T1, L, R, cl, cr, var, lifting)
pcube *T1;            /* Original ON-set */
pcover L, R;            /* Complement from each recursion branch */
register pcube cl, cr;        /* cubes used for cofactoring */
int var;            /* splitting variable */
int lifting;            /* whether to perform lifting or not */
{
    register pcube p, last, pt;
    pcover T, Tbar;
    pcube *L1, *R1;

    if (debug & COMPL) {
    (void) printf("compl_merge: left %d, right %d\n", L->count, R->count);
    (void) printf("%s (cl)\n%s (cr)\nLeft is\n", pc1(cl), pc2(cr));
    cprint(L);
    (void) 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), (int (*)()) d1_order);
    qsort((char *) (R1 = sf_list(R)), R->count, sizeof(pset), (int (*)()) 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;
    default:
        ;
    }
    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) {
    (void) 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(A1, B1, bcube, var)
pcube *A1, *B1, 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(A1, T, bcube, var)
pcube *A1;
pcover T;
pcube 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(A1, T, var)
pcube *A1;            /* array of pointers to new result */
pcover T;            /* original ON-set */
int var;            /* 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(L1, R1)
register pcube *L1, *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);
        default:
        ;
    }
}



/* compl_cube -- return the complement of a single cube (De Morgan's law) */
static pcover compl_cube(p)
register pcube 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(T, Tnew, Tbar)
pcube *T;            /* T will be disposed of */
pcover *Tnew;
pcover *Tbar;
{
    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(T, Tnew, Tbar)
pcube *T;            /* will be disposed if answer is determined */
pcover *Tnew;            /* returned only if answer determined */
pcover *Tbar;            /* 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(T)
pcube *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(T, Tnew)
pcube *T;            /* will be disposed if answer is determined */
pcover *Tnew;            /* 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;
    }
}
