simDominanceModifier / simDominanceModifier.py
simDominanceModifier.py
Raw
##########----------Import packages
import os, sys, time, argparse
import numpy as np
import pandas as pd
import random
from multiprocessing import Pool

##########----------calc fractional occupany for haploid
def calcFO_Haploid(conc1, m1, k):
    numer = conc1 / (k ** m1)
    denom = 1 + conc1 / (k ** m1)
    return (numer / denom)

#########----------calc fractional occupancy
def calcFO(conc1, conc2, m1, m2, k):
    numer = conc1 / (k ** m1)
    denom = 1 + conc1 / (k ** m1) + conc2 / (k ** m2)
    return (numer / denom)

##########----------calc expression of B based on allele at A
##########  if A is 0 it reduce expression, if A is 1 it enhances expression
def calcExpB(A, conc1, conc2, m1, m2, k):
    FO = calcFO(conc1, conc2, m1, m2, k)
    if A==0:
        return (-1 * FO)
    else:
        return (FO)

#########----------return gt given two alleles
#########   assume values are 0 or 1 only
def calcGt(allele1, allele2):
        if allele1 == 0 and allele2 == 0:
            return 0
        elif allele1 == 1 and allele2 == 1:
            return 2
        else:
            return 1

##########----------return binding affinity for alpha based on sex
def calcMAlpha(SEX, ALPHA):
    if SEX=='m':
        return ALPHA
    else:
        return round(1-ALPHA, 1)

##########----------return binding affinity for beta based allele of A
def calcMBeta(A, BETA):
    if A==0:
        return BETA
    else:
        return round(1-BETA, 1)

##########----------calc absolute fitness based on sex, standardize concentration of B and selection regime
##########  SR = 0 for sexual antagonism
##########  SR = 1 for sexually concordant selection using 'female' fitness eq
##########  SR = 2 for sexually concordant selection using 'male' fitness eq

def calcAbsFitness(SEX, STCONC, SR, S_F, S_M, GAMMA_F, GAMMA_M):
    if SR == 0:
        if SEX == 'm':
            return (1 - (S_M * STCONC) ** GAMMA_M)
        else:
            return (1 - (S_F * (1 - STCONC) ** GAMMA_F))
    elif SR == 1:
        return (1 - (S_F * (1 - STCONC) ** GAMMA_F))
    elif SR == 2:
        return (1 - (S_M * STCONC) ** GAMMA_M)
'''
def calcAbsFitness(SEX, STCONC, SR, S_F, S_M, GAMMA_F, GAMMA_M):
    if SR == 0:
        if SEX == 'm':
            return 0.9
        else:
            return 0.1
    elif SR == 1:
        return 0.25
    elif SR == 2:
        return 0.75
'''
##########----------mutate binding values (alpha, beta)
##########   increments of 0.1 and value bound between 0 and 1
def mutateBindValue(mu, bValue):
    rBinom = np.random.binomial(1, mu)
    if rBinom == 1:
        newBValue = -1
        while (newBValue < 0 or newBValue > 1):
            epsilon = random.choices([-0.1, 0.1], k=1)
            newBValue = round(bValue + epsilon[0], 1)
    else:
        newBValue = bValue
    return (newBValue)

###########----------generate gamete through selection on parents
def selectionGamete(listIndiv, maxFit, popSize):
    #####-----while loop stop when get enough gametes
    listID, nGam = [], 0
    lGamAlpha, lGamA, lGamBeta = [], [], []
    while nGam < popSize:
        #####-----choose random parent and get relative fitness
        pID = int(random.randint(0, len(listIndiv) - 1))
        pFit = listIndiv[pID].getAbsFitness()
        relFit = pFit/maxFit
        #####-----selection by testing relFit against random uniform variate
        rVar = np.random.uniform(0, 1, 1)
        if relFit >= rVar:
            nGam += 1
            pGam = listIndiv[pID].getGamete()
            lGamAlpha.append(pGam[0])
            lGamA.append(pGam[1])
            lGamBeta.append(pGam[2])
    #####-----return list of gametes
    return ([lGamAlpha, lGamA, lGamBeta])

##########----------get highest fitness
def getMaxAbsFit(listIndiv):
    listAbsFit = [x.getAbsFitness() for x in listIndiv]
    maxAbsFit = np.max(listAbsFit)
    return(maxAbsFit)

##########----------get average fitness
def getAvgAbsFit(listIndiv):
    listStat = [x.getAbsFitness() for x in listIndiv]
    avgStat = np.mean(listStat)
    return (avgStat)

##########----------get average fitness by genotype of protein A
def getAvgAbsFit_GtA(listIndiv):
    listFit = [x.getAbsFitness() for x in listIndiv]
    listGt = [x.getGtA() for x in listIndiv]
    n0, n1, n2, fitA0, fitA1, fitA2 = 0, 0, 0, [], [], []
    for i in range(len(listIndiv)):
        gt, fit = listGt[i], listFit[i]
        if gt == 0:
            n0 += 1
            fitA0 = fitA0 + [fit]
        elif gt == 1:
            n1 += 1
            fitA1 = fitA1 + [fit]
        elif gt == 2:
            n2 += 1
            fitA2 = fitA2 + [fit]
    avgFitA0 = np.mean(fitA0) if n0 > 0 else 0
    avgFitA1 = np.mean(fitA1) if n1 > 0 else 0
    avgFitA2 = np.mean(fitA2) if n2 > 0 else 0
    return [avgFitA0, avgFitA1, avgFitA2]

##########----------get mean for total expression of A
def getAvgExpA(listIndiv):
    listStat = [sum(x.getExpA()) for x in listIndiv]
    avgStat = np.mean(listStat)
    return (avgStat)

##########----------get variance for total expression of A
def getVarExpA(listIndiv):
    listStat = [sum(x.getExpA()) for x in listIndiv]
    varStat = np.var(listStat)
    return (varStat)

##########----------get expression of allele A0 and A1 from heterozygotes averaged across individuals
def getAvgAlleleExpA_Het(listIndiv):
    hetConcA0, hetConcA1 = [], []
    for INDIV in listIndiv:
        gt = INDIV.getGtA()
        a1, a2 = INDIV.getA()
        concA1, concA2 = INDIV.getExpA()
        #####-----get conc of allele A0 and A1 in heterozygote
        if gt == 1:
            if a1 == 0:
                hetConcA0 += [concA1]
                hetConcA1 += [concA2]
            else:
                hetConcA0 += [concA2]
                hetConcA1 += [concA1]
    if len(hetConcA0) > 0:
        avgConcC0, avgConcA1 = np.mean(hetConcA0), np.mean(hetConcA1)
        return ([avgConcC0, avgConcA1])
    else:
        return ([-1, -1])

##########----------get average standardized experssion of B
def getAvgStExpB(listIndiv):
    listStat = [x.getStExpB() for x in listIndiv]
    avgStat = np.mean(listStat)
    return (avgStat)

##########----------get variance for standardized experssion of B
def getVarStExpB(listIndiv):
    listStat = [x.getStExpB() for x in listIndiv]
    varStat = np.var(listStat)
    return (varStat)


##########----------get frequency of A=1 allele
def getFreqA(listIndiv):
    listStat = [x.getGtA() for x in listIndiv]
    freqA = np.sum(listStat)/(2*len(listIndiv))
    return (freqA)

##########----------get genotype frequency at coding region of gene A
def getGtFreqA(listIndiv):
    listStat = [x.getGtA() for x in listIndiv]
    gtFreqA = [listStat.count(x)/len(listIndiv) for x in [0,1,2]]
    return (gtFreqA)

##########----------get average value of ALPHA
def getAvgAlpha(listIndiv):
    listStat = []
    [listStat.extend(x.getAlpha()) for x in listIndiv]
    avgStat = np.mean(listStat)
    return (avgStat)

##########----------get average value of BETA
def getAvgBeta(listIndiv):
    listStat = []
    [listStat.extend(x.getBeta()) for x in listIndiv]
    avgStat = np.mean(listStat)
    return (avgStat)

#########----------count occurence of each binding value
def countBindValue(listStat):
    lValues = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
    lCounts = [listStat.count(x) for x in lValues]
    return (lCounts)

#########----------count occurence of values for ALPHA
def getDistAlpha(listIndiv):
    listStat = []
    [listStat.extend(x.getAlpha()) for x in listIndiv]
    listCounts = countBindValue(listStat)
    return (listCounts)

#########----------count occurence of values for BETA
def getDistBeta(listIndiv):
    listStat = []
    [listStat.extend(x.getBeta()) for x in listIndiv]
    listCounts = countBindValue(listStat)
    return (listCounts)


##########----------count num individuals with exp B in discrete bins
##########  bins are MIN <= x < MAX
##########      last bin uses 0.9 <= x < 1.1 because maximum expression of B is 1
def countBinnedExpB(listStat):
    lMin = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]
    lMax = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.1]
    lCounts = []
    for MIN, MAX in zip(lMin, lMax):
        lCounts += [sum([1 for x in listStat if MIN <= x < MAX])]
    return (lCounts)

def getDistStExpB(listIndiv):
    listStat = [x.getStExpB() for x in listIndiv]
    listCounts = countBinnedExpB(listStat)
    return (listCounts)

##########----------calculate correlation between loci
def getCorrel(listIndiv):
    #####get lists of value from each indiv
    N = len(listIndiv)
    alpha1, alpha2, a1, a2, beta1, beta2 = [-1]*N, [-1]*N, [-1]*N, [-1]*N, [-1]*N, [-1]*N
    for i, x in enumerate(listIndiv):
        alpha1[i], alpha2[i] = x.getAlpha()
        a1[i], a2[i] = x.getA()
        beta1[i], beta2[i] = x.getBeta()
    #####combine list between homologous Cm for haplotype correlations
    alphaAll, aAll, betaAll = alpha1 + alpha2, a1 + a2, beta1 + beta2
    #####haplotype correlations (across loci)
    cAlphaA, cAlphaBeta, cABeta = float("nan"), float("nan"), float("nan")
    if np.var(alphaAll) > 0 and np.var(aAll) > 0:
        cAlphaA = np.corrcoef(alphaAll, aAll)[0, 1]
    if np.var(alphaAll) > 0 and np.var(betaAll) > 0:
        cAlphaBeta = np.corrcoef(alphaAll, betaAll)[0, 1]
    if np.var(aAll) > 0 and np.var(betaAll) > 0:
        cABeta = np.corrcoef(aAll, betaAll)[0, 1]
    #####return
    return [cAlphaA, cAlphaBeta, cABeta]



####################--------------------Object to represent individual
class indiv():
    #####-----Initizlize individual
    def __init__(self, SEX, CONC_D, GA, ALPHA1, ALPHA2, A1, A2, BETA1, BETA2, KA, KB, ASAT, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB):
        
        #####-----assign sex and allele
        #####   'alpha' is binding site allele for gene A (real number between 0 and 1)
        #####   'a' is protein coding allele for gene A (binary 0 or 1)
        #####   'beta' is binding site allele for gene B (real number between 0 and 1)
        self.sex = SEX
        self.uAlpha, self.uBeta = UA, UB
        self.alpha1, self.alpha2 = ALPHA1, ALPHA2
        self.a1, self.a2 = A1, A2
        self.beta1, self.beta2 = BETA1, BETA2
        self.gtA = calcGt(self.a1, self.a2)
        self.absFitness = 1

        #####-----assign binding affinity between sex TF and gene A binding site
        self.mSexAlpha1 = calcMAlpha(self.sex, self.alpha1)
        self.mSexAlpha2 = calcMAlpha(self.sex, self.alpha2)

        #####-----assign binding affinity between gene A product and gene B binding site
        #####   depends on value of 'a' (0 or 1) and 'beta'
        self.mA1Beta1 = calcMBeta(self.a1, self.beta1)
        self.mA1Beta2 = calcMBeta(self.a1, self.beta2)
        self.mA2Beta1 = calcMBeta(self.a2, self.beta1)
        self.mA2Beta2 = calcMBeta(self.a2, self.beta2)

        #####-----calc concentration of product from gene A (TF for gene B)
        self.concA1 = calcFO_Haploid(CONC_D, self.mSexAlpha1, KA)*GA
        self.concA2 = calcFO_Haploid(CONC_D, self.mSexAlpha2, KA)*GA

        #####-----calc expression of gene B
        #####   depends on concentration of gene A products (concA1, concA2) ,their identity (a1, a2) and binding sites affinity (mABeta)
        self.concB1 = 0.5 * (calcExpB(self.a1, self.concA1, self.concA2, self.mA1Beta1, self.mA2Beta1, KB) + calcExpB(self.a2, self.concA2, self.concA1, self.mA2Beta1, self.mA1Beta1, KB))
        self.concB2 = 0.5 * (calcExpB(self.a1, self.concA1, self.concA2, self.mA1Beta2, self.mA2Beta2, KB) + calcExpB(self.a2, self.concA2, self.concA1, self.mA2Beta2, self.mA1Beta2, KB))
        self.concB = self.concB1 + self.concB2

        #####-----calc max and min expression of B using saturating conc of A
        self.concMinB = -1 * calcFO_Haploid(ASAT, 0, KB)
        self.concMaxB = calcFO_Haploid(ASAT, 0, KB)

        #####-----standardize expression of B
        self.stConcB = (self.concB-self.concMinB)/( self.concMaxB-self.concMinB)

        #####-----calc absolute fitness using stConcB and selection parameters
        #self.absFitness = calcAbsFitness(SEX, self.stConcB, S_F, S_M, GAMMA_F, GAMMA_M)

    #####-----Propoerties of individual
    def getSex(self):
        return self.sex
    def getGtA(self):
        return self.gtA
    def getA(self):
        return [self.a1, self.a2]
    def getAlpha(self):
        return [self.alpha1, self.alpha2]
    def getBeta(self):
        return [self.beta1, self.beta2]
    def getMSAlpha(self):
        return [self.mSexAlpha1, self.mSexAlpha2]
    def getMABeta(self):
        return [self.mA1Beta1, self.mA1Beta2, self.mA2Beta1, self.mA2Beta2]
    def getExpA(self):
        return [self.concA1, self.concA2]
    def getExpB(self):
        return [self.concB1, self.concB2, self.concB]
    def getSatExpB(self):
        return [self.concMinB, self.concMaxB]
    def getStExpB(self):
        return self.stConcB
    def getAbsFitness(self):
        return self.absFitness
    def getSmy(self):
        SMY = [self.sex, self.gtA, self.a1, self.a2, self.alpha1, self.alpha2, self.beta1, self.beta2, self.concA1, self.concA2, self.stConcB, self.absFitness]
        return SMY

    #####-----randomly pass on one allele as gamete
    #####   assume complete linkage for gene A and free recomb between A and B
    def getGamete(self):
        #####-----get random 0 or 1
        rBinom1 = np.random.binomial(1, 0.5)
        rBinom2 = np.random.binomial(1, 0.5)
        #####-----rBinom1, rBinom2 controls inheritance of gene A and B, respectively
        if rBinom1 == 0:
            if rBinom2 == 0:
                return [mutateBindValue(self.uAlpha, self.alpha1), self.a1, mutateBindValue(self.uBeta, self.beta1)]
            else:
                return [mutateBindValue(self.uAlpha, self.alpha1), self.a1, mutateBindValue(self.uBeta, self.beta2)]
        else:
            if rBinom2 == 0:
                return [mutateBindValue(self.uAlpha, self.alpha2), self.a2, mutateBindValue(self.uBeta, self.beta1)]
            else:
                return [mutateBindValue(self.uAlpha, self.alpha2), self.a2, mutateBindValue(self.uBeta, self.beta2)]


####################--------------------Simulation type 1
####################  random assigment of ALPHA, A and BETA values
def runSim01(CONC_D, GA, KA, KB, ASAT, SR, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB, N, PROPF, NUMGEN):
    ##########----------initialize dfs to collect data
    SIMTYPE = 1
    COLNAME = [
        'SIMTYPE','CONC_D','GA','KA','KB','ASAT','SR','S_F','S_M','GAMMA_F','GAMMA_M','UA','UB','N','PROPF','NUMGEN','GEN',
        'maxFit_F','maxFit_M','maxFit','avgFit_F','avgFit_M','avgFit',
        'avgFit_F00','avgFit_F01','avgFit_F11','avgFit_M00','avgFit_M01','avgFit_M11','avgFit_00','avgFit_01','avgFit_11',
        'avgExpA_F', 'avgExpA_M', 'avgExpA', 'varExpA_F', 'varExpA_M', 'varExpA',
        'avgStExpB_F', 'avgStExpB_M', 'avgStExpB', 'varStExpB_F', 'varStExpB_M', 'varStExpB',
        'avgHetExpA0_F', 'avgHetExpA1_F', 'avgHetExpA0_M', 'avgHetExpA1_M',
        'freqA_F','freqA_M','freqA','gtA00_F','gtA01_F','gtA11_F','gtA00_M','gtA01_M','gtA11_M','gtA00','gtA01','gtA11',
        'avgAlpha_F','avgAlpha_M','avgAlpha','avgBeta_F','avgBeta_M','avgBeta',
        'cAlphaA_F', 'cAlphaBeta_F', 'cABeta_F', 'cAlphaA_M', 'cAlphaBeta_M', 'cABeta_M'
    ]
    dfGeneral = pd.DataFrame(columns=COLNAME)

    COLNAME = [
        'SIMTYPE', 'CONC_D', 'GA', 'KA', 'KB', 'ASAT', 'SR', 'S_F', 'S_M', 'GAMMA_F', 'GAMMA_M', 'UA', 'UB', 'N', 'PROPF', 'NUMGEN', 'GEN',
        'alF0', 'alF1', 'alF2', 'alF3', 'alF4', 'alF5', 'alF6', 'alF7', 'alF8', 'alF9', 'alF10',
        'alM0', 'alM1', 'alM2', 'alM3', 'alM4', 'alM5', 'alM6', 'alM7', 'alM8', 'alM9', 'alM10',
        'beF0', 'beF1', 'beF2', 'beF3', 'beF4', 'beF5', 'beF6', 'beF7', 'beF8', 'beF9', 'beF10',
        'beM0', 'beM1', 'beM2', 'beM3', 'beM4', 'beM5', 'beM6', 'beM7', 'beM8', 'beM9', 'beM10',
        'al0', 'al1', 'al2', 'al3', 'al4', 'al5', 'al6', 'al7', 'al8', 'al9', 'al10',
        'be0', 'be1', 'be2', 'be3', 'be4', 'be5', 'be6', 'be7', 'be8', 'be9', 'be10',
        'BF0', 'BF1', 'BF2', 'BF3', 'BF4', 'BF5', 'BF6', 'BF7', 'BF8', 'BF9',
        'BM0', 'BM1', 'BM2', 'BM3', 'BM4', 'BM5', 'BM6', 'BM7', 'BM8', 'BM9',
    ]
    dfDist = pd.DataFrame(columns=COLNAME)

    ##########----------calculate num of male and females
    N_F= int(N*(1-PROPF))
    N_M = N - N_F

    ##########----------Initialize alleles
    ##########  equal prob for each allele of each locus
    listBind = [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]
    #listBind = [1.0]
    lAlpha1 = random.choices(listBind, k=N)
    lAlpha2 = random.choices(listBind, k=N)
    lA1 = np.random.binomial(1, 0.5, N)
    lA2 = np.random.binomial(1, 0.5, N)
    lBeta1 = random.choices(listBind, k=N)
    lBeta2 = random.choices(listBind, k=N)
    random.shuffle(lAlpha1)
    random.shuffle(lAlpha2)
    random.shuffle(lA1)
    random.shuffle(lA2)
    random.shuffle(lBeta1)
    random.shuffle(lBeta2)

    ##########----------Initialize sexes based on N_F, N_M
    popSex = ['f'] * int(N_F) + ['m'] * int(N_M)
    random.shuffle(popSex)

    ##########----------Initilize N individuals in population
    POPUL_F, POPUL_M = [], []
    for i in range(N):
        SEX = popSex[i]
        if SEX == 'f':
            POPUL_F.append(
                indiv('f', CONC_D, GA, lAlpha1[i], lAlpha2[i], lA1[i], lA2[i], lBeta1[i], lBeta2[i], KA, KB, ASAT, S_F,
                      S_M, GAMMA_F, GAMMA_M, UA, UB))
        else:
            POPUL_M.append(
                indiv('m', CONC_D, GA, lAlpha1[i], lAlpha2[i], lA1[i], lA2[i], lBeta1[i], lBeta2[i], KA, KB, ASAT, S_F,
                      S_M, GAMMA_F, GAMMA_M, UA, UB))
    ##########----------Initial calculation of absolute fitness
    for INDIV in POPUL_F + POPUL_M:
        INDIV.absFitness = calcAbsFitness(INDIV.getSex(), INDIV.getStExpB(), SR, S_F, S_M, GAMMA_F, GAMMA_M)

    ##########----------Run for NUMGEN generations
    ##########  if 'A' gets fixed (freq = 0 or 1), then sim stops after MaxExtraGEN generations
    extraGen, MaxExtraGen = 0, 500
    for gen in range(NUMGEN):
        #####-----determine highest fitness
        maxAbsFit_F = getMaxAbsFit(POPUL_F)
        maxAbsFit_M = getMaxAbsFit(POPUL_M)
        #####-----get gametes using relative fitness
        listFemGam = selectionGamete(POPUL_F, maxAbsFit_F, N)
        listMalGam = selectionGamete(POPUL_M, maxAbsFit_M, N)
        lAlpha1, lA1, lBeta1 = listFemGam[0], listFemGam[1], listFemGam[2]
        lAlpha2, lA2, lBeta2 = listMalGam[0], listMalGam[1], listMalGam[2]
        #####-----create offspring from gametes
        offspringSex = ['f'] * int(N_F) + ['m'] * int(N_M)
        random.shuffle(offspringSex)
        OFFSPRING_F, OFFSPRING_M = [], []
        for i in range(N):
            SEX = offspringSex[i]
            if SEX == 'f':
                tempInd = indiv('f', CONC_D, GA, lAlpha1[i], lAlpha2[i], lA1[i], lA2[i], lBeta1[i], lBeta2[i], KA, KB,
                                ASAT, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB)
                OFFSPRING_F.append(tempInd)
            else:
                tempInd = indiv('m', CONC_D, GA, lAlpha1[i], lAlpha2[i], lA1[i], lA2[i], lBeta1[i], lBeta2[i], KA, KB,
                                ASAT, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB)
                OFFSPRING_M.append(tempInd)
        #####-----new population created from offspring
        POPUL_F = OFFSPRING_F.copy()
        POPUL_M = OFFSPRING_M.copy()

        #####-----Calculate fitness
        for INDIV in POPUL_F + POPUL_M:
            INDIV.absFitness = calcAbsFitness(INDIV.getSex(), INDIV.getStExpB(), SR, S_F, S_M, GAMMA_F, GAMMA_M)

        #####-----Collect data every 10 generations
        if (gen + 1) % 10 == 0:
            vParam = [SIMTYPE, CONC_D, GA, KA, KB, ASAT, SR, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB, N, PROPF, NUMGEN, gen+1]
            vMaxFit = [getMaxAbsFit(POPUL_F), getMaxAbsFit(POPUL_M), getMaxAbsFit(POPUL_F+POPUL_M)]
            vAvgFit = [getAvgAbsFit(POPUL_F), getAvgAbsFit(POPUL_M), getAvgAbsFit(POPUL_F+POPUL_M)]
            vAvgFit_GtA = getAvgAbsFit_GtA(POPUL_F) + getAvgAbsFit_GtA(POPUL_M) + getAvgAbsFit_GtA(POPUL_F+POPUL_M)
            vAvgStExpA = [getAvgExpA(POPUL_F), getAvgExpA(POPUL_M), getAvgExpA(POPUL_F + POPUL_M)]
            vVarStExpA = [getVarExpA(POPUL_F), getVarExpA(POPUL_M), getVarExpA(POPUL_F + POPUL_M)]
            vAvgStExpB = [getAvgStExpB(POPUL_F), getAvgStExpB(POPUL_M), getAvgStExpB(POPUL_F+POPUL_M)]
            vVarStExpB = [getVarStExpB(POPUL_F), getVarStExpB(POPUL_M), getVarStExpB(POPUL_F+POPUL_M)]
            vAvgAlleleExpA_het = getAvgAlleleExpA_Het(POPUL_F) + getAvgAlleleExpA_Het(POPUL_M)
            vFreqA = [getFreqA(POPUL_F), getFreqA(POPUL_M), getFreqA(POPUL_F+POPUL_M)]
            vGtA = getGtFreqA(POPUL_F) + getGtFreqA(POPUL_M) + getGtFreqA(POPUL_F+POPUL_M)
            vAvgAlpha = [getAvgAlpha(POPUL_F), getAvgAlpha(POPUL_M), getAvgAlpha(POPUL_F+POPUL_M)]
            vAvgBeta = [getAvgBeta(POPUL_F), getAvgBeta(POPUL_M), getAvgBeta(POPUL_F+POPUL_M)]
            vCorrel_F, vCorrel_M = getCorrel(POPUL_F), getCorrel(POPUL_M)

            vDistAlpha_F, vDistAlpha_M = getDistAlpha(POPUL_F), getDistAlpha(POPUL_M)
            vDistBeta_F, vDistBeta_M = getDistBeta(POPUL_F), getDistBeta(POPUL_M)
            vDistAlpha, vDistBeta = getDistAlpha(POPUL_F+POPUL_M), getDistBeta(POPUL_M+POPUL_M)
            vDistStExpB_F, vDistStExpB_M = getDistStExpB(POPUL_F), getDistStExpB(POPUL_M)

            vAllData = vParam + vMaxFit + vAvgFit + vAvgFit_GtA + \
                       vAvgStExpA + vVarStExpA + vAvgStExpB + vVarStExpB + vAvgAlleleExpA_het + \
                       vFreqA + vGtA + vAvgAlpha + vAvgBeta + vCorrel_F + vCorrel_M
            dfGeneral.loc[len(dfGeneral)] = vAllData

            vDistData = vParam + vDistAlpha_F + vDistAlpha_M + vDistBeta_F + vDistBeta_M + vDistAlpha + vDistBeta + vDistStExpB_F + vDistStExpB_M
            dfDist.loc[len(dfDist)] = vDistData

        '''
        #####-----Check freq of A, if fixed then stops after MaxExtraGen generations
        freqA = getFreqA(POPUL_F+POPUL_M)
        if freqA == 0 or freqA == 1:
            extraGen += 1
        if extraGen == MaxExtraGen:
            break
        '''
        if gen == NUMGEN-1:
            COLNAME = ['ID', 'SEX', 'GTA', 'A1', 'A2', 'ALPHA1', 'ALPHA2', 'BETA1', 'BETA2', 'concA1', 'concA2', 'stconB', 'absFitness']
            dfIndiv = pd.DataFrame(columns=COLNAME)
            #[self.sex, self.gtA, self.a1, self.a2, self.alpha1, self.alpha2, self.beta1, self.beta2, self.concA1, self.concA2, self.stConB, self.absFitness]
            for i, INDIV in enumerate(POPUL_F+POPUL_M):
                SMY = INDIV.getSmy()
                dfIndiv.loc[len(dfIndiv)] = [i]+SMY

    #####-----return data
    return dfGeneral, dfDist, dfIndiv



####################--------------------Function that runs multiple replicates of simulation
####################    each replicate output file has a different name (increment by an integer)
def runReps(DIR, NAME, REPS, CONC_D, GA, KA, KB, ASAT, SR, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB, N, PROPF, NUMGEN):
    ##########----------Run simulations for REPS number of replicates
    for r in range(REPS):
        print ('Running [{}] rep [{}]'.format(NAME, r + 1))
        ##########----------run simulation
        dfGeneral, dfDist, dfIndiv = runSim01(CONC_D, GA, KA, KB, ASAT, SR, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB, N, PROPF, NUMGEN)
        ##########----------output data
        outGeneral = DIR + NAME + '.' + str(r + 1) + '.general.csv'
        outDist = DIR + NAME + '.' + str(r + 1) + '.dist.csv'
        outIndiv = DIR + NAME + '.' + str(r + 1) + '.indiv.csv'
        dfGeneral.to_csv(path_or_buf=outGeneral, index=False)
        dfDist.to_csv(path_or_buf=outDist, index=False)
        dfIndiv.to_csv(path_or_buf=outIndiv, index=False)


####################--------------------Function that reads parameters from csv file and runs simulations
def runParams(NPROC, PARFILE):
    ##########----------read parameter file
    try:
        PARAM = pd.read_csv(PARFILE)
    except OSError:
        print('ERROR: could not open [{}]'.format(PARFILE))
        sys.exit()
    ##########----------Check that all NAMES for outputing files are unique
    nRow = len(PARAM)
    nUniqName = len(pd.unique(PARAM['NAME']))
    if nUniqName != nRow:
        print('ERROR: one or more NAME are repeated: {}'.format(PARAM['NAME'].tolist()))
        sys.exit()
    ##########----------Run simulation for each set of parameters
    pool = Pool(processes=NPROC)
    for i in range(len(PARAM)):
        #####-----get params for row
        parRow = PARAM.loc[i]
        DIR, NAME, REPS, N, PROPF, NUMGEN = str(parRow['DIR']), str(parRow['NAME']), int(parRow['REPS']), int(
            parRow['N']), float(parRow['PROPF']), int(parRow['NUMGEN'])
        CONC_D, GA, KA, KB, ASAT = float(parRow['CONC_D']), float(parRow['GA']), float(parRow['KA']), float(
            parRow['KB']), float(parRow['ASAT'])
        SR, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB = int(parRow['SR']), float(parRow['S_F']), float(parRow['S_M']), float(
            parRow['GAMMA_F']), float(parRow['GAMMA_M']), float(parRow['UA']), float(parRow['UB'])
        #print(DIR, NAME, REPS, CONC_D, GA, KA, KB, ASAT, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB, N, PROPF, NUMGEN)
        #####-----run simlation if directory exists
        if os.path.isdir(DIR):
            print('Running simulations for [{}]'.format(NAME))
            pool.apply_async(runReps, args=(DIR, NAME, REPS, CONC_D, GA, KA, KB, ASAT, SR, S_F, S_M, GAMMA_F, GAMMA_M, UA, UB, N, PROPF, NUMGEN))
        else:
            print('DIR [{}] does not exist. Simulations for [{}] were not performed.'.format(DIR, NAME))
    #####-----makes sure all processes finish before continung script
    pool.close()
    pool.join()

####################--------------------Read arguments for MAIN
def parseArg():
    ##########----------Initialize parser
    parser = argparse.ArgumentParser()
    ##########----------Read arguments
    parser.add_argument("-n", "--NPROC", help="Number of processes to run simultaneously", type=int, required=True)
    parser.add_argument("-p", "--PARFILE", help="CSV file containing parameters for simulations", type=str, required=True)
    args = parser.parse_args()
    ##########----------Return arguments
    return args.NPROC, args.PARFILE

####################--------------------MAIN
def main():
    ##########----------get arguments from command line
    NPROC, PARFILE = parseArg()
    print("Num of processes to run: [{}]".format(NPROC))
    print("Reading parameters from: [{}]\n".format(PARFILE))
    ##########----------start program
    starttime = time.time()
    #PARFILE = 'D:/Users/eddie/DomSimData/TestMulti/PARAM.csv'
    runParams(NPROC, PARFILE)
    print('That took {} seconds'.format(time.time() - starttime))

if __name__ == "__main__":
    main()