def test():
    ## Provided the weights for the stationary distribution and the exchangeable coefficients
    ## we use them to generate the true rate matrix and the sequences to get the averaged
    ## sufficient statistics for the sequences throughout a larger number of replications
    ## Based on the sufficient statistics and we fix the exchangeable coefficients to
    ## its true values, we use HMC to estimate the weights for the stationary distribution
    ## The rate matrix is an un-normalized version
    
    ## The correctness of HMC and ExpectedCompleteReversibleObjective has been tested
    ## The estimated stationary distribution 'stationaryDistEst' is very close to stationaryDist

    nStates = 4
    nRep = 1000
    seedNum = np.arange(0, nRep)
    np.random.seed(123)
    weights = np.random.uniform(0, 1, nStates)
    print(weights)
    exchangeCoef = np.array((1, 2, 3, 4, 5, 6))

    ## get the rate matrix
    testRateMtx = ReversibleRateMtxPiAndExchangeGTR(nStates, weights, exchangeCoef)
    stationaryDist = testRateMtx.getStationaryDist()
    rateMtx = testRateMtx.getRateMtx()
    bt = 5.0
    nSeq = 100

    nInit = np.zeros(nStates)
    holdTimes = np.zeros(nStates)
    nTrans = np.zeros((nStates, nStates))

    for j in range(nRep):
        ## do forward sampling
        seqList = generateFullPathUsingRateMtxAndStationaryDist(nSeq, nStates, seedNum[j], rateMtx, stationaryDist, bt)
        ## summarize the sufficient statistics
        ## extract first state from sequences
        firstStates = getFirstAndLastStateOfListOfSeq(seqList)['firstLastState'][:, 0]
        unique, counts = np.unique(firstStates, return_counts=True)
        nInitCount = np.asarray((unique, counts)).T
        nInit = nInit + nInitCount[:, 1]

        for i in range(nSeq):
            sequences = seqList[i]
            holdTimes = holdTimes + sequences['sojourn']
            nTrans = nTrans + sequences['transitCount']

    avgNTrans = nTrans / nRep
    avgHoldTimes = holdTimes / nRep
    avgNInit = nInit / nRep

    expectedCompleteReversibleObjective = ExpectedCompleteReversibleObjective(holdTimes=avgHoldTimes, nInit=avgNInit, nTrans=avgNTrans, kappa=1, exchangeCoef=exchangeCoef)
    prng = np.random.RandomState(1)
    hmc = HMC(40, 0.02, expectedCompleteReversibleObjective, expectedCompleteReversibleObjective)
    sample = prng.uniform(0, 1, nStates)
    samples = hmc.run(0, 5000, sample)
    avgWeights = np.sum(samples, axis=0) / samples.shape[0]
    stationaryDistEst = np.exp(avgWeights)/np.sum(np.exp(avgWeights))
    print(weights)
    print(avgWeights)
    print(stationaryDist)
    print(stationaryDistEst)
def obtainSufficientStatisticsForChainGraphRateMtx(nStates, nRep=5000, bt=5.0, nSeq=100, SDOfBiWeights=0.5, bivariateDictionary=None):

    ## we generate the sufficient statistics for a large number of replications first
    ## and then we summarize the sufficient statistics for the forward sampler and
    ## then we use this data to run HMC and local BPS algorithms separately to see
    ## if we can obtain reasonable estimates of the exchangeable parameters

    seedNum = np.arange(0, nRep)

    if bivariateDictionary is None:
        bivariateDictionary = getHardCodedDictChainGraph(nStates)

    prng = RandomState(1234567890)
    stationaryWeights = prng.uniform(0, 1, nStates)
    bivariateWeights = prng.normal(0, SDOfBiWeights, int((nStates) * (nStates - 1) / 2))
    testRateMtx = ReversibleRateMtxPiAndBinaryWeightsWithGraphicalStructure(nStates, stationaryWeights,
                                                                            bivariateWeights, bivariateDictionary)
    stationaryDist = testRateMtx.getStationaryDist()
    rateMatrix = testRateMtx.getRateMtx()

    nInit = np.zeros(nStates)
    holdTimes = np.zeros(nStates)
    nTrans = np.zeros((nStates, nStates))

    for i in seedNum:

        seqList = generateFullPathUsingRateMtxAndStationaryDist(RandomState(i), nSeq, nStates,  rateMatrix,
                                                                stationaryDist, bt)
        ## summarize the sufficient statistics
        ## extract first state from sequences
        firstStates = getFirstAndLastStateOfListOfSeq(seqList)['firstLastState'][:, 0]
        unique, counts = np.unique(firstStates, return_counts=True)
        nInitCount = np.asarray((unique, counts)).T
        nInit = nInit + nInitCount[:, 1]

        for j in range(nSeq):
            sequences = seqList[j]
            holdTimes = holdTimes + sequences['sojourn']
            nTrans = nTrans + sequences['transitCount']
        print(i)

    avgNTrans = nTrans / nRep
    avgHoldTimes = holdTimes / nRep
    avgNInit = nInit / nRep

    result = {}
    result['stationaryWeights'] = stationaryWeights
    result['bivariateDictionary'] = bivariateDictionary
    result['bivariateWeights'] = bivariateWeights
    result['rateMatrix'] = rateMatrix
    result['stationaryDist'] = stationaryDist
    result['exchangeableCoef'] = testRateMtx.getExchangeCoef()
    result['transitCount'] = avgNTrans
    result['sojourn'] = avgHoldTimes
    result['nInit'] = avgNInit
    return result
print("The true rate matrix is ")
print(rateMtx)
#[[-0.78028091  0.01019415  0.63967359  0.13041317]
# [ 0.06563807 -0.27952517  0.03614709  0.17774   ]
# [ 1.02131772  0.00896336 -2.46060442  1.43032334]
# [ 0.11767434  0.0249081   0.80833633 -0.95091876]]


print("The true exchangeable parameters are ")
trueExchangeCoef = testRateMtx.getExchangeCoef()
print(trueExchangeCoef)

## generate data sequences of a CTMC with an un-normalized rate matrix
bt = 5.0
nSeq = 5000
seqList = generateFullPathUsingRateMtxAndStationaryDist(nSeq, nStates, seed, rateMtx, stationaryDist, bt)

## initial guess of the parameters
newSeed = 456
np.random.seed(456)
initialWeights = stationaryWeights
print(initialWeights)

## this is the weight at the 0th iteration
#initialBinaryWeights = np.array((0.586, 0.876, -0.1884, 0.8487, 0.5998, -1.35819,
#                                 -1.521, -0.6138, -0.58865, 0.19012))
#print("The initial binary feature weights at 0th iteration are: ")


# This is the weight after 250th iteration
# initialBinaryWeights = np.array((-0.26843204,  0.36688318, -0.97301064, 0.91227563,  1.20215414, -1.69677469,-0.95141154, -0.59620609, 0.8609998,  0.54728876))
def testCalculate():

    ## test the correctness of the code using numeric gradient check
    nStates = 6
    nRep = 10000
    seedNum = np.arange(0, nRep)
    np.random.seed(123)
    weights = np.random.uniform(0, 1, 18)
    print(weights)
    # delta = 0.000001
    bivariateFeatDictionary = getHardCodedDict()
    weightsForPi = weights[0:nStates]
    weightsForBivariate = weights[nStates:len(weights)]

    ## get the rate matrix
    testRateMtx = ReversibleRateMtxPiAndBinaryWeightsWithGraphicalStructure(nStates, weightsForPi, weightsForBivariate,
                                                                  bivariateFeatDictionary)
    stationaryDist = testRateMtx.getStationaryDist()
    rateMtx = testRateMtx.getRateMtx()

    bt = 3.0
    nSeq = 500
    ## simulate the observation data first
    seqList = generateFullPathUsingRateMtxAndStationaryDist(RandomState(seedNum[0]), nSeq, nStates, rateMtx,
                                                            stationaryDist,
                                                            bt)
    ## get observed sequences at a finite number of time points
    dataGenerationRegime = DataGenerationRegime(nStates=nStates,
                                                bivariateFeatIndexDictionary=bivariateFeatDictionary,
                                                btLength=bt, nSeq=nSeq, stationaryWeights=weightsForPi,
                                                bivariateWeights=weightsForBivariate, interLength=0.5)
    ## summarize the sufficient statistics
    obsData = dataGenerationRegime.generatingSeqGivenRateMtxAndBtInterval(seqList)
    marginalResult = dataGenerationRegime.summaryFirstLastStatesArrayIntoMatrix(obsData, nStates)
    obsNInit0 = marginalResult['nInit']
    ## get the marginal count from observations
    marginalCount = marginalResult['count']

    ## extract first state from sequences
    ## Below are our actual observation sequences
    firstStates = getFirstAndLastStateOfListOfSeq(seqList)['firstLastState'][:, 0]
    unique, counts = np.unique(firstStates, return_counts=True)
    nInitCount = np.asarray((unique, counts)).T
    obsNInit = np.zeros(nStates)
    obsNInit = obsNInit + nInitCount[:, 1]
    print(obsNInit)  ## it should be equal to obsNinit0

    rateMtxExpectations = RateMtxExpectations(rateMtx, 0.5)
    marginalExpectations = rateMtxExpectations.expectationsWithMarginalCount(marginalCount)
    ## get expected holding time
    expectedHoldingTime = np.diag(marginalExpectations)
    ## get expected transition count
    expectedTransCount = deepcopy(marginalExpectations)
    np.fill_diagonal(expectedTransCount, 0)

    expectedCompleteObjectiveFromExptStat = ExpectedCompleteReversibleObjective(expectedHoldingTime, obsNInit,
                                                                                expectedTransCount,
                                                                                nBivariateFeatWeightsDictionary=bivariateFeatDictionary)
    expectedForwardResult = expectedCompleteObjectiveFromExptStat.calculate(weights, bivariateFeatDictionary)
    exptFuncValue = expectedForwardResult['value']
    exptGradient = expectedForwardResult['gradient']
    print(exptFuncValue)
    print(exptGradient)

    # nInit = np.zeros(nStates)
    # holdTimes = np.zeros(nStates)
    # nTrans = np.zeros((nStates, nStates))
    #
    # for j in range(nRep):
    # # ## do forward sampling
    #     seqList = generateFullPathUsingRateMtxAndStationaryDist(RandomState(seedNum[0]), nSeq, nStates, rateMtx, stationaryDist, bt)
    # #         ## summarize the sufficient statistics
    # #         ## extract first state from sequences
    #     firstStates = getFirstAndLastStateOfListOfSeq(seqList)['firstLastState'][:, 0]
    #     unique, counts = np.unique(firstStates, return_counts=True)
    #     nInitCount = np.asarray((unique, counts)).T
    #     nInit = nInit + nInitCount[:, 1]
    #
    #     for i in range(nSeq):
    #         sequences = seqList[i]
    #         holdTimes = holdTimes + sequences['sojourn']
    #         nTrans = nTrans + sequences['transitCount']
    #
    # avgNTrans = nTrans/nRep
    # avgHoldTimes = holdTimes/nRep
    # avgNInit = nInit/nRep


    T = 0.5
    postSampler = EndPointSampler(rateMtx, T)
    pathStat2 = PathStatistics(nStates)
    nSegment = dataGenerationRegime.nPairSeq

    counter =0
    for j in range(nRep):
        ## do posterior path sampling
        ## for each segment of the observed path for the time series,
        ## loop over each segment of the sequence
        for i in range(nSegment):
            ## loop over each sequences
            for k in range(len(seqList)):
                p2 = Path()
                startState = obsData[i][int(k)][0]
                endState = obsData[i][k][1]
                postSampler.sample(np.random.RandomState(counter), int(startState), int(endState), T,
                                   pathStat2, p2)
                counter = counter + 1
        if j%10 == 0:
            print(j)

    m2 = pathStat2.getCountsAsSimpleMatrix() / nRep
    avgNInit = obsNInit
    avgNTrans = deepcopy(m2)
    np.fill_diagonal(avgNTrans, 0)
    avgHoldTimes = m2.diagonal()

    originalExpectedCompleteObjective = ExpectedCompleteReversibleObjective(avgHoldTimes, avgNInit, avgNTrans, nBivariateFeatWeightsDictionary=bivariateFeatDictionary)
    forwardResult = originalExpectedCompleteObjective.calculate(weights, bivariateFeatDictionary)
    funcValue = forwardResult['value']
    gradient = forwardResult['gradient']
    print(funcValue)
    print(gradient)