/**CFile****************************************************************

  FileName    [giaIso.c]

  SystemName  [ABC: Logic synthesis and verification system.]

  PackageName [Scalable AIG package.]

  Synopsis    [Graph isomorphism.]

  Author      [Alan Mishchenko]
  
  Affiliation [UC Berkeley]

  Date        [Ver. 1.0. Started - June 20, 2005.]

  Revision    [$Id: giaIso.c,v 1.00 2005/06/20 00:00:00 alanmi Exp $]

***********************************************************************/

#include "gia.h"

ABC_NAMESPACE_IMPL_START
 

#define ISO_MASK 0xFF
static int s_256Primes[ISO_MASK+1] = 
{
    0x984b6ad9,0x18a6eed3,0x950353e2,0x6222f6eb,0xdfbedd47,0xef0f9023,0xac932a26,0x590eaf55,
    0x97d0a034,0xdc36cd2e,0x22736b37,0xdc9066b0,0x2eb2f98b,0x5d9c7baf,0x85747c9e,0x8aca1055,
    0x50d66b74,0x2f01ae9e,0xa1a80123,0x3e1ce2dc,0xebedbc57,0x4e68bc34,0x855ee0cf,0x17275120,
    0x2ae7f2df,0xf71039eb,0x7c283eec,0x70cd1137,0x7cf651f3,0xa87bfa7a,0x14d87f02,0xe82e197d,
    0x8d8a5ebe,0x1e6a15dc,0x197d49db,0x5bab9c89,0x4b55dea7,0x55dede49,0x9a6a8080,0xe5e51035,
    0xe148d658,0x8a17eb3b,0xe22e4b38,0xe5be2a9a,0xbe938cbb,0x3b981069,0x7f9c0c8e,0xf756df10,
    0x8fa783f7,0x252062ce,0x3dc46b4b,0xf70f6432,0x3f378276,0x44b137a1,0x2bf74b77,0x04892ed6,
    0xfd318de1,0xd58c235e,0x94c6d25b,0x7aa5f218,0x35c9e921,0x5732fbbb,0x06026481,0xf584a44f,
    0x946e1b5f,0x8463d5b2,0x4ebca7b2,0x54887b15,0x08d1e804,0x5b22067d,0x794580f6,0xb351ea43,
    0xbce555b9,0x19ae2194,0xd32f1396,0x6fc1a7f1,0x1fd8a867,0x3a89fdb0,0xea49c61c,0x25f8a879,
    0xde1e6437,0x7c74afca,0x8ba63e50,0xb1572074,0xe4655092,0xdb6f8b1c,0xc2955f3c,0x327f85ba,
    0x60a17021,0x95bd261d,0xdea94f28,0x04528b65,0xbe0109cc,0x26dd5688,0x6ab2729d,0xc4f029ce,
    0xacf7a0be,0x4c912f55,0x34c06e65,0x4fbb938e,0x1533fb5f,0x03da06bd,0x48262889,0xc2523d7d,
    0x28a71d57,0x89f9713a,0xf574c551,0x7a99deb5,0x52834d91,0x5a6f4484,0xc67ba946,0x13ae698f,
    0x3e390f34,0x34fc9593,0x894c7932,0x6cf414a3,0xdb7928ab,0x13a3b8a3,0x4b381c1d,0xa10b54cb,
    0x55359d9d,0x35a3422a,0x58d1b551,0x0fd4de20,0x199eb3f4,0x167e09e2,0x3ee6a956,0x5371a7fa,
    0xd424efda,0x74f521c5,0xcb899ff6,0x4a42e4f4,0x747917b6,0x4b08df0b,0x090c7a39,0x11e909e4,
    0x258e2e32,0xd9fad92d,0x48fe5f69,0x0545cde6,0x55937b37,0x9b4ae4e4,0x1332b40e,0xc3792351,
    0xaff982ef,0x4dba132a,0x38b81ef1,0x28e641bf,0x227208c1,0xec4bbe37,0xc4e1821c,0x512c9d09,
    0xdaef1257,0xb63e7784,0x043e04d7,0x9c2cea47,0x45a0e59a,0x281315ca,0x849f0aac,0xa4071ed3,
    0x0ef707b3,0xfe8dac02,0x12173864,0x471f6d46,0x24a53c0a,0x35ab9265,0xbbf77406,0xa2144e79,
    0xb39a884a,0x0baf5b6d,0xcccee3dd,0x12c77584,0x2907325b,0xfd1adcd2,0xd16ee972,0x345ad6c1,
    0x315ebe66,0xc7ad2b8d,0x99e82c8d,0xe52da8c8,0xba50f1d3,0x66689cd8,0x2e8e9138,0x43e15e74,
    0xf1ced14d,0x188ec52a,0xe0ef3cbb,0xa958aedc,0x4107a1bc,0x5a9e7a3e,0x3bde939f,0xb5b28d5a,
    0x596fe848,0xe85ad00c,0x0b6b3aae,0x44503086,0x25b5695c,0xc0c31dcd,0x5ee617f0,0x74d40c3a,
    0xd2cb2b9f,0x1e19f5fa,0x81e24faf,0xa01ed68f,0xcee172fc,0x7fdf2e4d,0x002f4774,0x664f82dd,
    0xc569c39a,0xa2d4dcbe,0xaadea306,0xa4c947bf,0xa413e4e3,0x81fb5486,0x8a404970,0x752c980c,
    0x98d1d881,0x5c932c1e,0xeee65dfb,0x37592cdd,0x0fd4e65b,0xad1d383f,0x62a1452f,0x8872f68d,
    0xb58c919b,0x345c8ee3,0xb583a6d6,0x43d72cb3,0x77aaa0aa,0xeb508242,0xf2db64f8,0x86294328,
    0x82211731,0x1239a9d5,0x673ba5de,0xaf4af007,0x44203b19,0x2399d955,0xa175cd12,0x595928a7,
    0x6918928b,0xde3126bb,0x6c99835c,0x63ba1fa2,0xdebbdff0,0x3d02e541,0xd6f7aac6,0xe80b4cd0,
    0xd0fa29f1,0x804cac5e,0x2c226798,0x462f624c,0xad05b377,0x22924fcd,0xfbea205c,0x1b47586d
};

////////////////////////////////////////////////////////////////////////
///                        DECLARATIONS                              ///
////////////////////////////////////////////////////////////////////////

typedef struct Gia_IsoMan_t_       Gia_IsoMan_t;
struct Gia_IsoMan_t_ 
{
    Gia_Man_t *      pGia;
    int              nObjs;
    int              nUniques;
    int              nSingles;
    int              nEntries;
    // internal data
    int *            pLevels;
    int *            pUniques;
    word *           pStoreW;
    unsigned *       pStoreU;
    // equivalence classes
    Vec_Int_t *      vLevCounts;
    Vec_Int_t *      vClasses;
    Vec_Int_t *      vClasses2;
    // statistics 
    abctime          timeStart;
    abctime          timeSim;
    abctime          timeRefine;
    abctime          timeSort;
    abctime          timeOther;
    abctime          timeTotal;
};

static inline unsigned  Gia_IsoGetValue( Gia_IsoMan_t * p, int i )             { return (unsigned)(p->pStoreW[i]);       }
static inline unsigned  Gia_IsoGetItem( Gia_IsoMan_t * p, int i )              { return (unsigned)(p->pStoreW[i] >> 32); }

static inline void      Gia_IsoSetValue( Gia_IsoMan_t * p, int i, unsigned v ) { ((unsigned *)(p->pStoreW + i))[0] = v;  }
static inline void      Gia_IsoSetItem( Gia_IsoMan_t * p, int i, unsigned v )  { ((unsigned *)(p->pStoreW + i))[1] = v;  }

////////////////////////////////////////////////////////////////////////
///                     FUNCTION DEFINITIONS                         ///
////////////////////////////////////////////////////////////////////////

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_IsoMan_t * Gia_IsoManStart( Gia_Man_t * pGia )
{
    Gia_IsoMan_t * p;
    p = ABC_CALLOC( Gia_IsoMan_t, 1 );
    p->pGia      = pGia;
    p->nObjs     = Gia_ManObjNum( pGia );
    p->nUniques  = 1;
    p->nEntries  = p->nObjs;
    // internal data
    p->pLevels   = ABC_CALLOC( int, p->nObjs );
    p->pUniques  = ABC_CALLOC( int, p->nObjs );
    p->pStoreW   = ABC_CALLOC( word, p->nObjs );
    // class representation
    p->vClasses  = Vec_IntAlloc( p->nObjs/4 );
    p->vClasses2 = Vec_IntAlloc( p->nObjs/4 );
    return p;
}
void Gia_IsoManStop( Gia_IsoMan_t * p )
{
    // class representation
    Vec_IntFree( p->vClasses );
    Vec_IntFree( p->vClasses2 );
    // internal data
    ABC_FREE( p->pLevels );
    ABC_FREE( p->pUniques );
    ABC_FREE( p->pStoreW );
    ABC_FREE( p );
}
void Gia_IsoManTransferUnique( Gia_IsoMan_t * p )
{
    Gia_Obj_t * pObj;
    int i;
    // copy unique numbers into the nodes
    Gia_ManForEachObj( p->pGia, pObj, i )
        pObj->Value = p->pUniques[i];
}


/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_IsoPrintClasses( Gia_IsoMan_t * p )
{
    int fVerbose = 0;
    int i, k, iBegin, nSize;
    printf( "The total of %d classes:\n", Vec_IntSize(p->vClasses)/2 );
    Vec_IntForEachEntryDouble( p->vClasses, iBegin, nSize, i )
    {
        printf( "%5d : (%3d,%3d)  ", i/2, iBegin, nSize );
        if ( fVerbose )
        {
            printf( "{" );
            for ( k = 0; k < nSize; k++ )
                printf( " %3d,%08x", Gia_IsoGetItem(p, iBegin+k), Gia_IsoGetValue(p, iBegin+k) );
            printf( " }" );
        }
        printf( "\n" );
    }
}
void Gia_IsoPrint( Gia_IsoMan_t * p, int Iter, abctime Time )
{
    printf( "Iter %4d :  ", Iter );
    printf( "Entries =%8d.  ", p->nEntries );
//    printf( "Classes =%8d.  ", Vec_IntSize(p->vClasses)/2 );
    printf( "Uniques =%8d.  ", p->nUniques );
    printf( "Singles =%8d.  ", p->nSingles );
    printf( "%9.2f sec", (float)(Time)/(float)(CLOCKS_PER_SEC) );
    printf( "\n" );
    fflush( stdout );
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_IsoPrepare( Gia_IsoMan_t * p )
{
    Gia_Obj_t * pObj;
    int * pLevBegins, * pLevSizes;
    int i, iObj, MaxLev = 0;
    // assign levels
    p->pLevels[0] = 0;
    Gia_ManForEachCi( p->pGia, pObj, i )
        p->pLevels[Gia_ObjId(p->pGia, pObj)] = 0;
    Gia_ManForEachAnd( p->pGia, pObj, i )
        p->pLevels[i] = 1 + Abc_MaxInt( p->pLevels[Gia_ObjFaninId0(pObj, i)], p->pLevels[Gia_ObjFaninId1(pObj, i)] );
    Gia_ManForEachCo( p->pGia, pObj, i )
    {
        iObj = Gia_ObjId(p->pGia, pObj);
        p->pLevels[iObj] = 1 + p->pLevels[Gia_ObjFaninId0(pObj, iObj)]; // "1 +" is different!
        MaxLev = Abc_MaxInt( MaxLev, p->pLevels[Gia_ObjId(p->pGia, pObj)] );
    }

    // count nodes on each level
    pLevSizes = ABC_CALLOC( int, MaxLev+1 );
    for ( i = 1; i < p->nObjs; i++ )
        pLevSizes[p->pLevels[i]]++;
    // start classes
    Vec_IntClear( p->vClasses );
    Vec_IntPush( p->vClasses, 0 );
    Vec_IntPush( p->vClasses, 1 );
    // find beginning of each level
    pLevBegins = ABC_CALLOC( int, MaxLev+2 );
    pLevBegins[0] = 1;
    for ( i = 0; i <= MaxLev; i++ )
    {
        assert( pLevSizes[i] > 0 ); // we do not allow AIG with a const node and no PIs
        Vec_IntPush( p->vClasses, pLevBegins[i] );
        Vec_IntPush( p->vClasses, pLevSizes[i] );
        pLevBegins[i+1] = pLevBegins[i] + pLevSizes[i];
    }
    assert( pLevBegins[MaxLev+1] == p->nObjs );
    // put them into the structure
    for ( i = 1; i < p->nObjs; i++ )
        Gia_IsoSetItem( p, pLevBegins[p->pLevels[i]]++, i );
    ABC_FREE( pLevBegins );
    ABC_FREE( pLevSizes );
/*
    // print the results
    for ( i = 0; i < p->nObjs; i++ )
        printf( "%3d : (%d,%d)\n", i, Gia_IsoGetItem(p, i), Gia_IsoGetValue(p, i) );
    printf( "\n" );
*/
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_IsoAssignUnique( Gia_IsoMan_t * p )
{
    int i, iBegin, nSize;
    p->nSingles = 0;
    Vec_IntClear( p->vClasses2 );
    Vec_IntForEachEntryDouble( p->vClasses, iBegin, nSize, i )
    {
        if ( nSize == 1 )
        {
            assert( p->pUniques[Gia_IsoGetItem(p, iBegin)] == 0 );
            p->pUniques[Gia_IsoGetItem(p, iBegin)] = p->nUniques++;
            p->nSingles++;
        }
        else
        {
            Vec_IntPush( p->vClasses2, iBegin );
            Vec_IntPush( p->vClasses2, nSize );
        }
    }
    ABC_SWAP( Vec_Int_t *, p->vClasses, p->vClasses2 );
    p->nEntries -= p->nSingles;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Gia_IsoSort( Gia_IsoMan_t * p )
{
    Gia_Obj_t * pObj, * pObj0;
    int i, k, fSameValue, iBegin, iBeginOld, nSize, nSizeNew;
    int fRefined = 0;
    abctime clk;

    // go through the equiv classes
    p->nSingles = 0;
    Vec_IntClear( p->vClasses2 );
    Vec_IntForEachEntryDouble( p->vClasses, iBegin, nSize, i )
    {
        assert( nSize > 1 );
        fSameValue = 1;
        pObj0 = Gia_ManObj( p->pGia, Gia_IsoGetItem(p,iBegin) );
        for ( k = 0; k < nSize; k++ )
        {
            pObj = Gia_ManObj( p->pGia, Gia_IsoGetItem(p,iBegin+k) );
            Gia_IsoSetValue( p, iBegin+k, pObj->Value );
            if ( pObj->Value != pObj0->Value )
                fSameValue = 0;
        }
        if ( fSameValue )
        {
            Vec_IntPush( p->vClasses2, iBegin );
            Vec_IntPush( p->vClasses2, nSize );
            continue;
        }
        fRefined = 1;
        // sort objects
        clk = Abc_Clock();
        Abc_QuickSort3( p->pStoreW + iBegin, nSize, 0 );
        p->timeSort += Abc_Clock() - clk;
        // divide into new classes
        iBeginOld = iBegin;
        pObj0 = Gia_ManObj( p->pGia, Gia_IsoGetItem(p,iBegin) );
        for ( k = 1; k < nSize; k++ )
        {
            pObj = Gia_ManObj( p->pGia, Gia_IsoGetItem(p,iBegin+k) );
            if ( pObj0->Value == pObj->Value )
                continue;
            nSizeNew = iBegin + k - iBeginOld;
            if ( nSizeNew == 1 )
            {
                assert( p->pUniques[Gia_IsoGetItem(p, iBeginOld)] == 0 );
                p->pUniques[Gia_IsoGetItem(p, iBeginOld)] = p->nUniques++;
                p->nSingles++;
            }
            else
            {
                Vec_IntPush( p->vClasses2, iBeginOld );
                Vec_IntPush( p->vClasses2, nSizeNew );
            }
            iBeginOld = iBegin + k;
            pObj0 = pObj;
        }
        // add the last one
        nSizeNew = iBegin + k - iBeginOld;
        if ( nSizeNew == 1 )
        {
            assert( p->pUniques[Gia_IsoGetItem(p, iBeginOld)] == 0 );
            p->pUniques[Gia_IsoGetItem(p, iBeginOld)] = p->nUniques++;
            p->nSingles++;
        }
        else
        {
            Vec_IntPush( p->vClasses2, iBeginOld );
            Vec_IntPush( p->vClasses2, nSizeNew );
        }
    }

    ABC_SWAP( Vec_Int_t *, p->vClasses, p->vClasses2 );
    p->nEntries -= p->nSingles;
    return fRefined;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Vec_Ptr_t * Gia_IsoCollectCosClasses( Gia_IsoMan_t * p, int fVerbose )
{
    Vec_Ptr_t * vGroups;
    Vec_Int_t * vLevel;
    Gia_Obj_t * pObj;
    int i, k, iBegin, nSize;
    // add singletons
    vGroups = Vec_PtrAlloc( 1000 );
    Gia_ManForEachPo( p->pGia, pObj, i )
        if ( p->pUniques[Gia_ObjId(p->pGia, pObj)] > 0 )
        {
            vLevel = Vec_IntAlloc( 1 );
            Vec_IntPush( vLevel, i );
            Vec_PtrPush( vGroups, vLevel );
        }

    // add groups
    Vec_IntForEachEntryDouble( p->vClasses, iBegin, nSize, i )
    {
        for ( k = 0; k < nSize; k++ )
        {
            pObj = Gia_ManObj( p->pGia, Gia_IsoGetItem(p,iBegin+k) );
            if ( Gia_ObjIsPo(p->pGia, pObj) )
                break;
        }
        if ( k == nSize )
            continue;
        vLevel = Vec_IntAlloc( 8 );
        for ( k = 0; k < nSize; k++ )
        {
            pObj = Gia_ManObj( p->pGia, Gia_IsoGetItem(p,iBegin+k) );
            if ( Gia_ObjIsPo(p->pGia, pObj) )
                Vec_IntPush( vLevel, Gia_ObjCioId(pObj) );
        }
        Vec_PtrPush( vGroups, vLevel );
    }
    // canonicize order
    Vec_PtrForEachEntry( Vec_Int_t *, vGroups, vLevel, i )
        Vec_IntSort( vLevel, 0 );
    Vec_VecSortByFirstInt( (Vec_Vec_t *)vGroups, 0 );
//    Vec_VecFree( (Vec_Vec_t *)vGroups );
//    return NULL;
    return vGroups;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
static inline unsigned Gia_IsoUpdateValue( int Value, int fCompl )
{
    return (Value+1) * s_256Primes[Abc_Var2Lit(Value, fCompl) & ISO_MASK];
}
static inline unsigned Gia_IsoUpdate( Gia_IsoMan_t * p, int Iter, int iObj, int fCompl )
{
    if ( Iter == 0 )              return Gia_IsoUpdateValue( p->pLevels[iObj], fCompl );
    if ( p->pUniques[iObj] > 0 )  return Gia_IsoUpdateValue( p->pUniques[iObj], fCompl );
//    if ( p->pUniques[iObj] > 0 )  return Gia_IsoUpdateValue( 11, fCompl );
    return 0;
}
void Gia_IsoSimulate( Gia_IsoMan_t * p, int Iter )
{
    Gia_Obj_t * pObj, * pObjF;
    int i, iObj;
    // initialize constant, inputs, and flops in the first frame
    Gia_ManConst0(p->pGia)->Value += s_256Primes[ISO_MASK];
    Gia_ManForEachPi( p->pGia, pObj, i )
        pObj->Value += s_256Primes[ISO_MASK-1];
    if ( Iter == 0 )
        Gia_ManForEachRo( p->pGia, pObj, i )
            pObj->Value += s_256Primes[ISO_MASK-2];
    // simulate nodes
    Gia_ManForEachAnd( p->pGia, pObj, i )
    {
        pObj->Value += Gia_ObjFanin0(pObj)->Value + Gia_IsoUpdate(p, Iter, Gia_ObjFaninId0(pObj, i), Gia_ObjFaninC0(pObj));
        pObj->Value += Gia_ObjFanin1(pObj)->Value + Gia_IsoUpdate(p, Iter, Gia_ObjFaninId1(pObj, i), Gia_ObjFaninC1(pObj));
    }
    // simulate COs
    Gia_ManForEachCo( p->pGia, pObj, i )
    {
        iObj = Gia_ObjId(p->pGia, pObj);
        pObj->Value += Gia_ObjFanin0(pObj)->Value + Gia_IsoUpdate(p, Iter, Gia_ObjFaninId0(pObj, iObj), Gia_ObjFaninC0(pObj));
    }
    // transfer flop values
    Gia_ManForEachRiRo( p->pGia, pObjF, pObj, i )
        pObj->Value += pObjF->Value;
}
void Gia_IsoSimulateBack( Gia_IsoMan_t * p, int Iter )
{
    Gia_Obj_t * pObj, * pObjF;
    int i, iObj;
    // simulate COs
    Gia_ManForEachCo( p->pGia, pObj, i )
    {
        iObj = Gia_ObjId(p->pGia, pObj);
        Gia_ObjFanin0(pObj)->Value += pObj->Value + Gia_IsoUpdate(p, Iter, iObj, Gia_ObjFaninC0(pObj));
    }
    // simulate objects
    Gia_ManForEachAndReverse( p->pGia, pObj, i )
    {
        Gia_ObjFanin0(pObj)->Value += pObj->Value + Gia_IsoUpdate(p, Iter, i, Gia_ObjFaninC0(pObj));
        Gia_ObjFanin1(pObj)->Value += pObj->Value + Gia_IsoUpdate(p, Iter, i, Gia_ObjFaninC1(pObj));
    }
    // transfer flop values
    Gia_ManForEachRiRo( p->pGia, pObjF, pObj, i )
        pObjF->Value += pObj->Value;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_IsoAssignOneClass2( Gia_IsoMan_t * p )
{
    int i, iBegin = -1, nSize = -1;
    // find two variable class
    assert( Vec_IntSize(p->vClasses) > 0 );
    Vec_IntForEachEntryDouble( p->vClasses, iBegin, nSize, i )
    {
        if ( nSize == 2 )
            break;
    }
    assert( nSize > 1 );

    if ( nSize == 2 )
    {
        assert( p->pUniques[Gia_IsoGetItem(p, iBegin)] == 0 );
        p->pUniques[Gia_IsoGetItem(p, iBegin)] = p->nUniques++;
        p->nSingles++;
        p->nEntries--;

        assert( p->pUniques[Gia_IsoGetItem(p, iBegin+1)] == 0 );
        p->pUniques[Gia_IsoGetItem(p, iBegin+1)] = p->nUniques++;
        p->nSingles++;
        p->nEntries--;
    }
    else
    {
        assert( p->pUniques[Gia_IsoGetItem(p, iBegin)] == 0 );
        p->pUniques[Gia_IsoGetItem(p, iBegin)] = p->nUniques++;
        p->nSingles++;
        p->nEntries--;
    }

    for ( ; i < Vec_IntSize(p->vClasses) - 2; i += 2 )
    {
        p->vClasses->pArray[i+0] = p->vClasses->pArray[i+2];
        p->vClasses->pArray[i+1] = p->vClasses->pArray[i+3];
    }
    Vec_IntShrink( p->vClasses, Vec_IntSize(p->vClasses) - 2 );

    printf( "Broke ties in class %d of size %d at level %d.\n", i/2, nSize, p->pLevels[Gia_IsoGetItem(p, iBegin)] );
}

void Gia_IsoAssignOneClass3( Gia_IsoMan_t * p )
{
    int iBegin, nSize;
    // find the last class
    assert( Vec_IntSize(p->vClasses) > 0 );
    iBegin = Vec_IntEntry( p->vClasses, Vec_IntSize(p->vClasses) - 2 );
    nSize  = Vec_IntEntry( p->vClasses, Vec_IntSize(p->vClasses) - 1 );
    Vec_IntShrink( p->vClasses, Vec_IntSize(p->vClasses) - 2 );

    // assign the class
    assert( nSize > 1 );
    if ( nSize == 2 )
    {
        assert( p->pUniques[Gia_IsoGetItem(p, iBegin)] == 0 );
        p->pUniques[Gia_IsoGetItem(p, iBegin)] = p->nUniques++;
        p->nSingles++;
        p->nEntries--;

        assert( p->pUniques[Gia_IsoGetItem(p, iBegin+1)] == 0 );
        p->pUniques[Gia_IsoGetItem(p, iBegin+1)] = p->nUniques++;
        p->nSingles++;
        p->nEntries--;
    }
    else
    {
        assert( p->pUniques[Gia_IsoGetItem(p, iBegin)] == 0 );
        p->pUniques[Gia_IsoGetItem(p, iBegin)] = p->nUniques++;
        p->nSingles++;
        p->nEntries--;
    }
    printf( "Broke ties in last class of size %d at level %d.\n", nSize, p->pLevels[Gia_IsoGetItem(p, iBegin)] );
}

void Gia_IsoAssignOneClass( Gia_IsoMan_t * p, int fVerbose )
{
    int i, k, iBegin0, iBegin, nSize, Shrink;
    // find the classes with the highest level
    assert( Vec_IntSize(p->vClasses) > 0 );
    iBegin0 = Vec_IntEntry( p->vClasses, Vec_IntSize(p->vClasses) - 2 );
    for ( i = Vec_IntSize(p->vClasses) - 2; i >= 0; i -= 2 )
    {
        iBegin = Vec_IntEntry( p->vClasses, i );
        if ( p->pLevels[Gia_IsoGetItem(p, iBegin)] != p->pLevels[Gia_IsoGetItem(p, iBegin0)] )
            break;
    }
    i += 2;
    assert( i >= 0 );
    // assign all classes starting with this one
    for ( Shrink = i; i < Vec_IntSize(p->vClasses); i += 2 )
    {
        iBegin = Vec_IntEntry( p->vClasses, i );
        nSize  = Vec_IntEntry( p->vClasses, i + 1 );
        for ( k = 0; k < nSize; k++ )
        {
            assert( p->pUniques[Gia_IsoGetItem(p, iBegin+k)] == 0 );
            p->pUniques[Gia_IsoGetItem(p, iBegin+k)] = p->nUniques++;
//            Gia_ManObj(p->pGia, Gia_IsoGetItem(p, iBegin+k))->Value += s_256Primes[0]; ///  new addition!!!
            p->nSingles++;
            p->nEntries--;
        }
        if ( fVerbose )
            printf( "Broke ties in class of size %d at level %d.\n", nSize, p->pLevels[Gia_IsoGetItem(p, iBegin)] );
    }
    Vec_IntShrink( p->vClasses, Shrink );
}

/**Function*************************************************************

  Synopsis    [Report topmost equiv nodes.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_IsoReportTopmost( Gia_IsoMan_t * p )
{
    Gia_Obj_t * pObj;
    int i, k, iBegin, nSize, Counter = 0;
    // go through equivalence classes
    Gia_ManIncrementTravId( p->pGia );
    Vec_IntForEachEntryDouble( p->vClasses, iBegin, nSize, i )
    {
//        printf( "%d(%d) ", nSize, p->pLevels[Gia_IsoGetItem(p, iBegin)] );
        for ( k = 0; k < nSize; k++ )
        {
            pObj = Gia_ManObj( p->pGia, Gia_IsoGetItem(p, iBegin+k) );
            if ( Gia_ObjIsAnd(pObj) )
            {
                Gia_ObjSetTravIdCurrent( p->pGia, Gia_ObjFanin0(pObj) );
                Gia_ObjSetTravIdCurrent( p->pGia, Gia_ObjFanin1(pObj) );
            }
            else if ( Gia_ObjIsRo(p->pGia, pObj) )
                Gia_ObjSetTravIdCurrent( p->pGia, Gia_ObjFanin0(Gia_ObjRoToRi(p->pGia, pObj)) );
        }
    }
//    printf( "\n" );

    // report non-labeled nodes
    Vec_IntForEachEntryDouble( p->vClasses, iBegin, nSize, i )
    {
        for ( k = 0; k < nSize; k++ )
        {
            pObj = Gia_ManObj( p->pGia, Gia_IsoGetItem(p, iBegin+k) );
            if ( !Gia_ObjIsTravIdCurrent(p->pGia, pObj) )
            {
                printf( "%5d : ", ++Counter );
                printf( "Obj %6d : Level = %4d.  iBegin = %4d.  Size = %4d.\n", 
                    Gia_ObjId(p->pGia, pObj), p->pLevels[Gia_ObjId(p->pGia, pObj)], iBegin, nSize );
                break;
            }
        }
    }
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_IsoRecognizeMuxes( Gia_Man_t * pGia )
{
    Gia_Obj_t * pObj, * pObjC, * pObj1, * pObj0;
    int i;
    Gia_ManForEachAnd( pGia, pObj, i )
    {
        if ( !Gia_ObjIsMuxType(pObj) )
            continue;
        pObjC = Gia_ObjRecognizeMux( pObj, &pObj1, &pObj0 );
        if ( Gia_Regular(pObj0) == Gia_Regular(pObj1) )
        {
            // this is XOR
            Gia_Regular(pObj)->Value += s_256Primes[233];
            Gia_Regular(pObjC)->Value += s_256Primes[234];
            Gia_Regular(pObj0)->Value += s_256Primes[234];
        }
        else
        {
            // this is MUX
            Gia_Regular(pObj)->Value += s_256Primes[235];
            Gia_Regular(pObjC)->Value += s_256Primes[236];
            Gia_Regular(pObj0)->Value += s_256Primes[237];
            Gia_Regular(pObj1)->Value += s_256Primes[237];
        }
    }
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Vec_Ptr_t * Gia_IsoDeriveEquivPos( Gia_Man_t * pGia, int fForward, int fVerbose )
{
    int nIterMax = 10000;
    int nFixedPoint = 1;
    Gia_IsoMan_t * p;
    Vec_Ptr_t * vEquivs = NULL;
    int fRefined, fRefinedAll;
    int i, c;
    abctime clk = Abc_Clock(), clkTotal = Abc_Clock();
    assert( Gia_ManCiNum(pGia) > 0 );
    assert( Gia_ManPoNum(pGia) > 0 );

    Gia_ManCleanValue( pGia );
    p = Gia_IsoManStart( pGia );
    Gia_IsoPrepare( p );
    Gia_IsoAssignUnique( p );
    p->timeStart = Abc_Clock() - clk;
    if ( fVerbose )
        Gia_IsoPrint( p, 0, Abc_Clock() - clkTotal );

//    Gia_IsoRecognizeMuxes( pGia );

    i = 0;
    if ( fForward )
    {
        for ( c = 0; i < nIterMax && c < nFixedPoint+1; i++, c = fRefined ? 0 : c+1 )
        {
            clk = Abc_Clock();   Gia_IsoSimulate( p, i );      p->timeSim += Abc_Clock() - clk;
            clk = Abc_Clock();   fRefined = Gia_IsoSort( p );  p->timeRefine += Abc_Clock() - clk;
            if ( fVerbose )
                Gia_IsoPrint( p, i+1, Abc_Clock() - clkTotal );
        }
    }
    else
    {
        while ( Vec_IntSize(p->vClasses) > 0 )
        {
            for ( fRefinedAll = 1; i < nIterMax && fRefinedAll; )
            {
                fRefinedAll = 0;
                for ( c = 0; i < nIterMax && c < nFixedPoint+1; i++, c = fRefined ? 0 : c+1 )
                {
                    clk = Abc_Clock();   Gia_IsoSimulate( p, i );      p->timeSim += Abc_Clock() - clk;
                    clk = Abc_Clock();   fRefined = Gia_IsoSort( p );  p->timeRefine += Abc_Clock() - clk;
                    if ( fVerbose )
                        Gia_IsoPrint( p, i+1, Abc_Clock() - clkTotal );
                    fRefinedAll |= fRefined;
                }
                for ( c = 0; i < nIterMax && c < nFixedPoint+1; i++, c = fRefined ? 0 : c+1 )
                {
                    clk = Abc_Clock();   Gia_IsoSimulateBack( p, i );  p->timeSim += Abc_Clock() - clk;
                    clk = Abc_Clock();   fRefined = Gia_IsoSort( p );  p->timeRefine += Abc_Clock() - clk;
                    if ( fVerbose )
                        Gia_IsoPrint( p, i+1, Abc_Clock() - clkTotal );
                    fRefinedAll |= fRefined;
                }
            }
            if ( !fRefinedAll )
                break;
        }

//        Gia_IsoReportTopmost( p );

        while ( Vec_IntSize(p->vClasses) > 0 )
        {
            Gia_IsoAssignOneClass( p, fVerbose );
            for ( fRefinedAll = 1; i < nIterMax && fRefinedAll; )
            {
                fRefinedAll = 0;
                for ( c = 0; i < nIterMax && c < nFixedPoint; i++, c = fRefined ? 0 : c+1 )
                {
                    clk = Abc_Clock();   Gia_IsoSimulateBack( p, i );  p->timeSim += Abc_Clock() - clk;
                    clk = Abc_Clock();   fRefined = Gia_IsoSort( p );  p->timeRefine += Abc_Clock() - clk;
                    if ( fVerbose )
                        Gia_IsoPrint( p, i+1, Abc_Clock() - clkTotal );
                    fRefinedAll |= fRefined;
                }
                for ( c = 0; i < nIterMax && c < nFixedPoint; i++, c = fRefined ? 0 : c+1 )
                {
                    clk = Abc_Clock();   Gia_IsoSimulate( p, i );      p->timeSim += Abc_Clock() - clk;
                    clk = Abc_Clock();   fRefined = Gia_IsoSort( p );  p->timeRefine += Abc_Clock() - clk;
                    if ( fVerbose )
                        Gia_IsoPrint( p, i+1, Abc_Clock() - clkTotal );
                    fRefinedAll |= fRefined;
//                    if ( fRefined )
//                        printf( "Refinedment happened.\n" );
                }
            }
        }

        if ( fVerbose )
            Gia_IsoPrint( p, i+2, Abc_Clock() - clkTotal );
    }
//    Gia_IsoPrintClasses( p );
    if ( fVerbose )
    {
        p->timeTotal = Abc_Clock() - clkTotal;
        p->timeOther = p->timeTotal - p->timeStart - p->timeSim - p->timeRefine;
        ABC_PRTP( "Start    ", p->timeStart,              p->timeTotal );
        ABC_PRTP( "Simulate ", p->timeSim,                p->timeTotal );
        ABC_PRTP( "Refine   ", p->timeRefine-p->timeSort, p->timeTotal );
        ABC_PRTP( "Sort     ", p->timeSort,               p->timeTotal );
        ABC_PRTP( "Other    ", p->timeOther,              p->timeTotal );
        ABC_PRTP( "TOTAL    ", p->timeTotal,              p->timeTotal );
    }

    if ( Gia_ManPoNum(p->pGia) > 1 )
        vEquivs = Gia_IsoCollectCosClasses( p, fVerbose );
    Gia_IsoManTransferUnique( p );
    Gia_IsoManStop( p );

    return vEquivs;
}




/**Function*************************************************************

  Synopsis    [Finds canonical ordering of CIs/COs/nodes.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Gia_ObjCompareByValue( Gia_Obj_t ** pp1, Gia_Obj_t ** pp2 )
{
    Gia_Obj_t * pObj1 = *pp1;
    Gia_Obj_t * pObj2 = *pp2;
//    assert( pObj1->Value != pObj2->Value );
    return (int)pObj1->Value - (int)pObj2->Value;
}
void Gia_ManFindCaninicalOrder_rec( Gia_Man_t * p, Gia_Obj_t * pObj, Vec_Int_t * vAnds )
{
    if ( Gia_ObjIsTravIdCurrent(p, pObj) )
        return;
    Gia_ObjSetTravIdCurrent(p, pObj);
    assert( Gia_ObjIsAnd(pObj) );
    if ( !Gia_ObjIsAnd(Gia_ObjFanin0(pObj)) || !Gia_ObjIsAnd(Gia_ObjFanin1(pObj)) )
    {
        Gia_ManFindCaninicalOrder_rec( p, Gia_ObjFanin0(pObj), vAnds );
        Gia_ManFindCaninicalOrder_rec( p, Gia_ObjFanin1(pObj), vAnds );
    }
    else
    {
        assert( Gia_ObjFanin0(pObj)->Value != Gia_ObjFanin1(pObj)->Value );
        if ( Gia_ObjFanin0(pObj)->Value < Gia_ObjFanin1(pObj)->Value )
        {
            Gia_ManFindCaninicalOrder_rec( p, Gia_ObjFanin0(pObj), vAnds );
            Gia_ManFindCaninicalOrder_rec( p, Gia_ObjFanin1(pObj), vAnds );
        }
        else
        {
            Gia_ManFindCaninicalOrder_rec( p, Gia_ObjFanin1(pObj), vAnds );
            Gia_ManFindCaninicalOrder_rec( p, Gia_ObjFanin0(pObj), vAnds );
        }
    }
    Vec_IntPush( vAnds, Gia_ObjId(p, pObj) );
}
void Gia_ManFindCaninicalOrder( Gia_Man_t * p, Vec_Int_t * vCis, Vec_Int_t * vAnds, Vec_Int_t * vCos, Vec_Int_t ** pvPiPerm )
{
    Vec_Ptr_t * vTemp;
    Gia_Obj_t * pObj;
    int i;

    vTemp = Vec_PtrAlloc( 1000 );
    Vec_IntClear( vCis );
    Vec_IntClear( vAnds );
    Vec_IntClear( vCos );

    // assign unique IDs to PIs
    Vec_PtrClear( vTemp );
    Gia_ManForEachPi( p, pObj, i )
        Vec_PtrPush( vTemp, pObj );
    Vec_PtrSort( vTemp, (int (*)(void))Gia_ObjCompareByValue );
    // create the result
    Vec_PtrForEachEntry( Gia_Obj_t *, vTemp, pObj, i )
        Vec_IntPush( vCis, Gia_ObjId(p, pObj) );
    // remember PI permutation
    if ( pvPiPerm ) 
    {
        *pvPiPerm = Vec_IntAlloc( Gia_ManPiNum(p) );
        Vec_PtrForEachEntry( Gia_Obj_t *, vTemp, pObj, i )
            Vec_IntPush( *pvPiPerm, Gia_ObjCioId(pObj) );
    }

    // assign unique IDs to POs
    if ( Gia_ManPoNum(p) == 1 )
        Vec_IntPush( vCos, Gia_ObjId(p, Gia_ManPo(p, 0)) );
    else
    {
        Vec_PtrClear( vTemp );
        Gia_ManForEachPo( p, pObj, i )
        {
            pObj->Value = Abc_Var2Lit( Gia_ObjFanin0(pObj)->Value, Gia_ObjFaninC0(pObj) );
            Vec_PtrPush( vTemp, pObj );
        }
        Vec_PtrSort( vTemp, (int (*)(void))Gia_ObjCompareByValue );
        Vec_PtrForEachEntry( Gia_Obj_t *, vTemp, pObj, i )
            Vec_IntPush( vCos, Gia_ObjId(p, pObj) );
    }

    // assign unique IDs to ROs
    Vec_PtrClear( vTemp );
    Gia_ManForEachRo( p, pObj, i )
        Vec_PtrPush( vTemp, pObj );
    Vec_PtrSort( vTemp, (int (*)(void))Gia_ObjCompareByValue );
    // create the result
    Vec_PtrForEachEntry( Gia_Obj_t *, vTemp, pObj, i )
    {
        Vec_IntPush( vCis, Gia_ObjId(p, pObj) );
        Vec_IntPush( vCos, Gia_ObjId(p, Gia_ObjRoToRi(p, pObj)) );
    }
    Vec_PtrFree( vTemp );

    // assign unique IDs to internal nodes
    Gia_ManIncrementTravId( p );
    Gia_ObjSetTravIdCurrent( p, Gia_ManConst0(p) );
    Gia_ManForEachObjVec( vCis, p, pObj, i )
        Gia_ObjSetTravIdCurrent( p, pObj );
    Gia_ManForEachObjVec( vCos, p, pObj, i )
        Gia_ManFindCaninicalOrder_rec( p, Gia_ObjFanin0(pObj), vAnds );
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_Man_t * Gia_ManIsoCanonicize( Gia_Man_t * p, int fVerbose )
{
    Gia_Man_t * pRes = NULL;
    Vec_Int_t * vCis, * vAnds, * vCos;
    Vec_Ptr_t * vEquiv;
    if ( Gia_ManCiNum(p) == 0 ) // const AIG
    {
        assert( Gia_ManPoNum(p) == 1 );
        assert( Gia_ManObjNum(p) == 2 );
        return Gia_ManDup(p);
    }
    // derive canonical values
    vEquiv = Gia_IsoDeriveEquivPos( p, 0, fVerbose );
    Vec_VecFreeP( (Vec_Vec_t **)&vEquiv );
    // find canonical order of CIs/COs/nodes
    // find canonical order
    vCis  = Vec_IntAlloc( Gia_ManCiNum(p) );
    vAnds = Vec_IntAlloc( Gia_ManAndNum(p) );
    vCos  = Vec_IntAlloc( Gia_ManCoNum(p) );
    Gia_ManFindCaninicalOrder( p, vCis, vAnds, vCos, NULL );
    // derive the new AIG
    pRes = Gia_ManDupFromVecs( p, vCis, vAnds, vCos, Gia_ManRegNum(p) );
    // cleanup
    Vec_IntFree( vCis );
    Vec_IntFree( vAnds );
    Vec_IntFree( vCos );
    return pRes;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Vec_Str_t * Gia_ManIsoFindString( Gia_Man_t * p, int iPo, int fVerbose, Vec_Int_t ** pvPiPerm )
{
    Gia_Man_t * pPart;
    Vec_Ptr_t * vEquiv;
    Vec_Int_t * vCis, * vAnds, * vCos;
    Vec_Str_t * vStr;
    // duplicate
    pPart = Gia_ManDupCones( p, &iPo, 1, 1 );
//Gia_ManPrint( pPart );
    assert( Gia_ManPoNum(pPart) == 1 );
    if ( Gia_ManCiNum(pPart) == 0 ) // const AIG
    {
        assert( Gia_ManPoNum(pPart) == 1 );
        assert( Gia_ManObjNum(pPart) == 2 );
        vStr = Gia_AigerWriteIntoMemoryStr( pPart );
        Gia_ManStop( pPart );
        if ( pvPiPerm )
            *pvPiPerm = Vec_IntAlloc( 0 );
        return vStr;
    }
    // derive canonical values
    vEquiv = Gia_IsoDeriveEquivPos( pPart, 0, fVerbose );
    Vec_VecFreeP( (Vec_Vec_t **)&vEquiv );
    // find canonical order
    vCis  = Vec_IntAlloc( Gia_ManCiNum(pPart) );
    vAnds = Vec_IntAlloc( Gia_ManAndNum(pPart) );
    vCos  = Vec_IntAlloc( Gia_ManCoNum(pPart) );
    Gia_ManFindCaninicalOrder( pPart, vCis, vAnds, vCos, pvPiPerm );
//printf( "Internal: " );
//Vec_IntPrint( vCis );
    // derive the AIGER string
    vStr = Gia_AigerWriteIntoMemoryStrPart( pPart, vCis, vAnds, vCos, Gia_ManRegNum(pPart) );
    // cleanup
    Vec_IntFree( vCis );
    Vec_IntFree( vAnds );
    Vec_IntFree( vCos );
    Gia_ManStop( pPart );
    return vStr;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
int Vec_IntCountNonTrivial( Vec_Ptr_t * vEquivs, int * pnUsed )
{
    Vec_Int_t * vClass;
    int i, nClasses = 0;
    *pnUsed = 0;
    Vec_PtrForEachEntry( Vec_Int_t *, vEquivs, vClass, i )
    {
        if ( Vec_IntSize(vClass) < 2 )
            continue;
        nClasses++;
        (*pnUsed) += Vec_IntSize(vClass);
    }
    return nClasses;
}

/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Gia_Man_t * Gia_ManIsoReduce( Gia_Man_t * pInit, Vec_Ptr_t ** pvPosEquivs, Vec_Ptr_t ** pvPiPerms, int fEstimate, int fDualOut, int fVerbose, int fVeryVerbose )
{ 
    Gia_Man_t * p, * pPart;
    Vec_Ptr_t * vEquivs, * vEquivs2, * vStrings;
    Vec_Int_t * vRemain, * vLevel, * vLevel2;
    Vec_Str_t * vStr, * vStr2;
    int i, k, s, sStart, iPo, Counter;
    int nClasses, nUsedPos;
    abctime clk = Abc_Clock();
    if ( pvPosEquivs )
        *pvPosEquivs = NULL;
    if ( pvPiPerms )
        *pvPiPerms = Vec_PtrStart( Gia_ManPoNum(pInit) );

    if ( fDualOut )
    {
        assert( (Gia_ManPoNum(pInit) & 1) == 0 );
        if ( Gia_ManPoNum(pInit) == 2 )
            return Gia_ManDup(pInit);
        p = Gia_ManTransformMiter( pInit );
        p = Gia_ManSeqStructSweep( pPart = p, 1, 1, 0 );
        Gia_ManStop( pPart );
    }
    else
    {
        if ( Gia_ManPoNum(pInit) == 1 )
            return Gia_ManDup(pInit);
        p = pInit;
    }

    // create preliminary equivalences
    vEquivs = Gia_IsoDeriveEquivPos( p, 1, fVeryVerbose );
    if ( vEquivs == NULL )
    {
        if ( fDualOut )
            Gia_ManStop( p );
        return NULL;
    }
    nClasses = Vec_IntCountNonTrivial( vEquivs, &nUsedPos );
    printf( "Reduced %d outputs to %d candidate   classes (%d outputs are in %d non-trivial classes).  ", 
        Gia_ManPoNum(p), Vec_PtrSize(vEquivs), nUsedPos, nClasses );
    Abc_PrintTime( 1, "Time", Abc_Clock() - clk );
    if ( fEstimate )
    {
        Vec_VecFree( (Vec_Vec_t *)vEquivs );
        return Gia_ManDup(pInit);
    }

    // perform refinement of equivalence classes
    Counter = 0;
    vEquivs2 = Vec_PtrAlloc( 100 );
    Vec_PtrForEachEntry( Vec_Int_t *, vEquivs, vLevel, i )
    {
        if ( Vec_IntSize(vLevel) < 2 )
        {
            Vec_PtrPush( vEquivs2, Vec_IntDup(vLevel) );
            for ( k = 0; k < Vec_IntSize(vLevel); k++ )
                if ( ++Counter % 100 == 0 )
                    printf( "%6d finished...\r", Counter );
            continue;
        }

        if ( fVerbose )
        {
            iPo = Vec_IntEntry(vLevel, 0);
            printf( "%6d %6d %6d : ", i, Vec_IntSize(vLevel), iPo );
            pPart = Gia_ManDupCones( p, &iPo, 1, 1 );
            Gia_ManPrintStats(pPart, NULL);
            Gia_ManStop( pPart );
        }

        sStart = Vec_PtrSize( vEquivs2 ); 
        vStrings = Vec_PtrAlloc( 100 );
        Vec_IntForEachEntry( vLevel, iPo, k )
        {
            if ( ++Counter % 100 == 0 )
                printf( "%6d finished...\r", Counter );
            assert( pvPiPerms == NULL || Vec_PtrArray(*pvPiPerms)[iPo] == NULL );
            vStr = Gia_ManIsoFindString( p, iPo, 0, pvPiPerms ? (Vec_Int_t **)Vec_PtrArray(*pvPiPerms) + iPo : NULL );

//            printf( "Output %2d : ", iPo );
//            Vec_IntPrint( Vec_PtrArray(*pvPiPerms)[iPo] );

            // check if this string already exists
            Vec_PtrForEachEntry( Vec_Str_t *, vStrings, vStr2, s )
                if ( Vec_StrCompareVec(vStr, vStr2) == 0 )
                    break;
            if ( s == Vec_PtrSize(vStrings) )
            {
                Vec_PtrPush( vStrings, vStr );
                Vec_PtrPush( vEquivs2, Vec_IntAlloc(8) );
            }
            else
                Vec_StrFree( vStr );
            // add this entry to the corresponding level
            vLevel2 = (Vec_Int_t *)Vec_PtrEntry( vEquivs2, sStart + s );
            Vec_IntPush( vLevel2, iPo );
        }
//        if ( Vec_PtrSize(vEquivs2) - sStart > 1 )
//            printf( "Refined class %d into %d classes.\n", i, Vec_PtrSize(vEquivs2) - sStart );
        Vec_VecFree( (Vec_Vec_t *)vStrings );
    }
    assert( Counter == Gia_ManPoNum(p) );
    Vec_VecSortByFirstInt( (Vec_Vec_t *)vEquivs2, 0 );
    Vec_VecFree( (Vec_Vec_t *)vEquivs );
    vEquivs = vEquivs2;

    // collect the first ones
    vRemain = Vec_IntAlloc( 100 );
    Vec_PtrForEachEntry( Vec_Int_t *, vEquivs, vLevel, i )
        Vec_IntPush( vRemain, Vec_IntEntry(vLevel, 0) );

    if ( fDualOut )
    {
        Vec_Int_t * vTemp = Vec_IntAlloc( Vec_IntSize(vRemain) );
        int i, Entry;
        Vec_IntForEachEntry( vRemain, Entry, i )
        {
//            printf( "%d ", Entry );
            Vec_IntPush( vTemp, 2*Entry );
            Vec_IntPush( vTemp, 2*Entry+1 );
        }
//        printf( "\n" );
        Vec_IntFree( vRemain );
        vRemain = vTemp;
        Gia_ManStop( p );
        p = pInit;
    }


    // derive the resulting AIG
    pPart = Gia_ManDupCones( p, Vec_IntArray(vRemain), Vec_IntSize(vRemain), 0 );
    Vec_IntFree( vRemain );
    // report the results
    nClasses = Vec_IntCountNonTrivial( vEquivs, &nUsedPos );
    if ( !fDualOut )
        printf( "Reduced %d outputs to %d equivalence classes (%d outputs are in %d non-trivial classes).  ", 
            Gia_ManPoNum(p), Vec_PtrSize(vEquivs), nUsedPos, nClasses );
    else
        printf( "Reduced %d dual outputs to %d dual outputs.  ", Gia_ManPoNum(p)/2, Gia_ManPoNum(pPart)/2 );
    Abc_PrintTime( 1, "Time", Abc_Clock() - clk );
    if ( fVerbose )
    {
        printf( "Nontrivial classes:\n" );
        Vec_VecPrintInt( (Vec_Vec_t *)vEquivs, 1 );
    }
    if ( pvPosEquivs )
        *pvPosEquivs = vEquivs;
    else
        Vec_VecFree( (Vec_Vec_t *)vEquivs );
//    Gia_ManStopP( &pPart );
    return pPart;
}


/**Function*************************************************************

  Synopsis    []

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
void Gia_IsoTestOld( Gia_Man_t * p, int fVerbose )
{
    Vec_Ptr_t * vEquivs;
    abctime clk = Abc_Clock(); 
    vEquivs = Gia_IsoDeriveEquivPos( p, 0, fVerbose );
    printf( "Reduced %d outputs to %d.  ", Gia_ManPoNum(p), vEquivs ? Vec_PtrSize(vEquivs) : 1 );
    Abc_PrintTime( 1, "Time", Abc_Clock() - clk );
    if ( fVerbose && vEquivs && Gia_ManPoNum(p) != Vec_PtrSize(vEquivs) )
    {
        printf( "Nontrivial classes:\n" );
//        Vec_VecPrintInt( (Vec_Vec_t *)vEquivs, 1 );
    }
    Vec_VecFreeP( (Vec_Vec_t **)&vEquivs );
}

/**Function*************************************************************

  Synopsis    [Test remapping of CEXes for isomorphic POs.]

  Description []
               
  SideEffects []

  SeeAlso     []

***********************************************************************/
Vec_Int_t * Gia_IsoTestGenPerm( int nPis )
{
    Vec_Int_t * vPerm;
    int i, * pArray;
    vPerm = Vec_IntStartNatural( nPis );
    pArray = Vec_IntArray( vPerm );
    for ( i = 0; i < nPis; i++ )
    {
        int iNew = rand() % nPis;
        ABC_SWAP( int, pArray[i], pArray[iNew] );
    }
    return vPerm;
}
void Gia_IsoTest( Gia_Man_t * p, Abc_Cex_t * pCex, int fVerbose )
{
    Abc_Cex_t * pCexNew;
    Vec_Int_t * vPiPerm;
    Vec_Ptr_t * vPosEquivs, * vPisPerm;
    Vec_Int_t * vPerm0, * vPerm1;
    Gia_Man_t * pPerm, * pDouble, * pAig;
    assert( Gia_ManPoNum(p) == 1 );
    assert( Gia_ManRegNum(p) > 0 );
    // generate random permutation of PIs
    vPiPerm = Gia_IsoTestGenPerm( Gia_ManPiNum(p) );
    printf( "Considering random permutation of the primary inputs of the AIG:\n" );
    Vec_IntPrint( vPiPerm );
    // create AIG with two primary outputs (original and permuted)
    pPerm = Gia_ManDupPerm( p, vPiPerm );
    pDouble = Gia_ManDupAppendNew( p, pPerm );
//Gia_AigerWrite( pDouble, "test.aig", 0, 0 );

    // analyze the two-output miter
    pAig = Gia_ManIsoReduce( pDouble, &vPosEquivs, &vPisPerm, 0, 0, 0, 0 );
    Vec_VecFree( (Vec_Vec_t *)vPosEquivs );

    // given CEX for output 0, derive CEX for output 1
    vPerm0 = (Vec_Int_t *)Vec_PtrEntry( vPisPerm, 0 );
    vPerm1 = (Vec_Int_t *)Vec_PtrEntry( vPisPerm, 1 );
    pCexNew = Abc_CexPermuteTwo( pCex, vPerm0, vPerm1 );
    Vec_VecFree( (Vec_Vec_t *)vPisPerm );

    // check that original CEX and the resulting CEX is valid
    if ( Gia_ManVerifyCex(p, pCex, 0) )
        printf( "CEX for the init AIG is valid.\n" );
    else
        printf( "CEX for the init AIG is not valid.\n" );
    if ( Gia_ManVerifyCex(pPerm, pCexNew, 0) )
        printf( "CEX for the perm AIG is valid.\n" );
    else
        printf( "CEX for the perm AIG is not valid.\n" );
    // delete
    Gia_ManStop( pAig );
    Gia_ManStop( pDouble );
    Gia_ManStop( pPerm );
    Vec_IntFree( vPiPerm );
    Abc_CexFree( pCexNew );
}

////////////////////////////////////////////////////////////////////////
///                       END OF FILE                                ///
////////////////////////////////////////////////////////////////////////


ABC_NAMESPACE_IMPL_END