def test_computeMassIncrease(self):
		sequences = np.random.randint(4, size = (20, 10)).astype(np.int8, copy = False)
		sequenceElongations = np.random.randint(10, size = (20,)).astype(np.int64, copy = False)
		monomerWeights = np.array([10., 4., 7., 1.], dtype = np.float64)

		massIncrease = computeMassIncrease(
			sequences,
			sequenceElongations,
			monomerWeights
			)

		comparison_massIncrease = np.empty(sequences.shape[0], dtype = np.float64)
		for i in range(sequences.shape[0]):
			comparison_massIncrease[i] = np.sum(monomerWeights[sequences[i][:sequenceElongations[i]]])

		assert_equal(massIncrease, comparison_massIncrease)
def initializeReplication(bulkMolCntr, uniqueMolCntr, sim_data):
    """
	Purpose: Initialize replication by creating an appropriate number of
	replication forks given the cell growth rate.
	"""

    # Determine the number and location of replication forks at the start of
    # the cell cycle
    # Get growth rate constants
    C = sim_data.growthRateParameters.c_period.asUnit(units.min)
    D = sim_data.growthRateParameters.d_period.asUnit(units.min)
    tau = sim_data.conditionToDoublingTime[sim_data.condition].asUnit(
        units.min)

    # Calculate replication length
    genome_length = sim_data.process.replication.genome_length
    replication_length = np.ceil(0.5 * genome_length) * units.nt

    # Generate arrays specifying appropriate initial replication conditions
    n_oric, chromosomeIndexOriC = determineOriCState(C, D, tau)
    sequenceIdx, sequenceLength, replicationRound, chromosomeIndexPolymerase = determineChromosomeState(
        C, D, tau, replication_length)
    n_dnap = sequenceIdx.size

    oriC = uniqueMolCntr.objectsNew('originOfReplication', n_oric)
    oriC.attrIs(chromosomeIndex=chromosomeIndexOriC)

    if n_dnap != 0:
        # Update mass to account for DNA strands that have already been elongated
        # Determine the sequences of already-replicated DNA
        sequences = sim_data.process.replication.replication_sequences
        sequenceElongations = sequenceLength.astype(np.int64)
        massIncreaseDna = computeMassIncrease(
            np.tile(sequences, (n_dnap // 4, 1)), sequenceElongations,
            sim_data.process.replication.replicationMonomerWeights.asNumber(
                units.fg))

        # Add replicating DNA polymerases as unique molecules and set attributes
        dnaPoly = uniqueMolCntr.objectsNew('dnaPolymerase', n_dnap)
        dnaPoly.attrIs(
            sequenceIdx=sequenceIdx,
            sequenceLength=sequenceLength,
            replicationRound=replicationRound,
            chromosomeIndex=chromosomeIndexPolymerase,
            massDiff_DNA=massIncreaseDna,
        )
    def evolveState(self):

        ## Module 1: Replication initiation
        # Get number of active DNA polymerases and oriCs
        activeDnaPoly = self.activeDnaPoly.molecules()
        activePolymerasePresent = (len(activeDnaPoly) > 0)
        oriCs = self.oriCs.molecules()
        n_oric = len(oriCs)
        n_chromosomes = self.full_chromosome.total()[0]

        # If there are no chromosomes and oriC's, return immediately
        if n_oric == 0 and n_chromosomes == 0:
            return

        # Get cell mass
        cellMass = (self.readFromListener("Mass", "cellMass") * units.fg)

        # Get critical initiation mass for simulation medium environment
        current_nutrients = self._external_states['Environment'].nutrients
        self.criticalInitiationMass = self.getDnaCriticalMass(
            self.nutrientToDoublingTime[current_nutrients])

        # Calculate mass per origin of replication, and compare to critical
        # initiation mass. This is a rearrangement of the equation:
        # 	(Cell Mass)/(Number of origins) > Critical mass
        # If the above inequality holds true, initiate a round of chromosome
        # replication for every origin of replication
        massFactor = cellMass / self.criticalInitiationMass
        massPerOrigin = massFactor / n_oric
        replication_initiated = False

        # If conditions are true, initiate a round of replication on every
        # origin of replication
        if massPerOrigin >= 1.0 and self.partialChromosomes.counts().sum(
        ) == 0:
            replication_initiated = True

            # Get replication round indexes of active DNA polymerases
            if activePolymerasePresent:
                replicationRound = activeDnaPoly.attr('replicationRound')
            else:
                # Set to -1 to set values for new polymerases to 0
                replicationRound = np.array([-1])

            # Get chromosome indexes of current oriCs
            chromosomeIndexOriC = oriCs.attr('chromosomeIndex')

            # Calculate number of new DNA polymerases required (4 per origin)
            n_new_polymerase = 4 * n_oric

            # Add new polymerases and oriC's
            activeDnaPolyNew = self.activeDnaPoly.moleculesNew(
                "dnaPolymerase", n_new_polymerase)
            oriCsNew = self.oriCs.moleculesNew("originOfReplication", n_oric)

            # Calculate and set attributes of newly created polymerases
            sequenceIdx = np.tile(np.array([0, 1, 2, 3]), n_oric)
            sequenceLength = np.zeros(n_new_polymerase, dtype=np.int)
            replicationRound = np.ones(
                n_new_polymerase, dtype=np.int) * (replicationRound.max() + 1)

            # Polymerases inherit index of the OriC's they were initiated from
            chromosomeIndexPolymerase = np.repeat(chromosomeIndexOriC, 4)

            activeDnaPolyNew.attrIs(
                sequenceIdx=sequenceIdx,
                sequenceLength=sequenceLength,
                replicationRound=replicationRound,
                chromosomeIndex=chromosomeIndexPolymerase,
            )

            # Calculate and set attributes of newly created oriCs
            # New OriC's share the index of the old OriC's they were
            # replicated from
            oriCsNew.attrIs(chromosomeIndex=chromosomeIndexOriC)

        # Write data from this module to a listener
        self.writeToListener("ReplicationData", "criticalMassPerOriC",
                             massPerOrigin)
        self.writeToListener("ReplicationData", "criticalInitiationMass",
                             self.criticalInitiationMass.asNumber(units.fg))

        ## Module 2: replication elongation
        # If no active polymerases are present, return immediately
        # Note: the new DNA polymerases activated in the previous module are
        # not elongated until the next timestep.
        if len(activeDnaPoly) == 0:
            return

        # Build sequences to polymerize
        dNtpCounts = self.dntps.counts()
        sequenceIdx, sequenceLengths, massDiffDna = activeDnaPoly.attrs(
            'sequenceIdx', 'sequenceLength', 'massDiff_DNA')

        sequences = buildSequences(self.sequences, sequenceIdx,
                                   sequenceLengths,
                                   self._dnaPolymeraseElongationRate())

        # Use polymerize algorithm to quickly calculate the number of
        # elongations each "polymerase" catalyzes
        reactionLimit = dNtpCounts.sum()

        result = polymerize(sequences, dNtpCounts, reactionLimit,
                            self.randomState)

        sequenceElongations = result.sequenceElongation
        dNtpsUsed = result.monomerUsages

        # Compute mass increase for each polymerizing chromosome
        massIncreaseDna = computeMassIncrease(
            sequences, sequenceElongations,
            self.polymerized_dntp_weights.asNumber(units.fg))

        updatedMass = massDiffDna + massIncreaseDna

        # Update positions of each "polymerase"
        updatedLengths = sequenceLengths + sequenceElongations

        activeDnaPoly.attrIs(sequenceLength=updatedLengths,
                             massDiff_DNA=updatedMass)

        # Update counts of polymerized metabolites
        self.dntps.countsDec(dNtpsUsed)
        self.ppi.countInc(dNtpsUsed.sum())

        ## Module 3: replication termination
        # Determine if any polymerases reached the end of their sequences. If
        # so, terminate replication and update the attributes of the remaining
        # polymerases and OriC's to reflect the new chromosome structure.
        terminalLengths = self.sequenceLengths[sequenceIdx]
        didTerminate = (updatedLengths == terminalLengths)

        terminatedChromosomes = np.bincount(sequenceIdx[didTerminate],
                                            minlength=self.sequences.shape[0])

        # If any of the polymerases were terminated, check if all polymerases
        # initiated the same round as the terminated polymerases has already
        # been removed - if they have, update attributes of the remaining
        # polymerases and oriC's, and remove the polymerases. This is not done
        # when some polymerases were initiated in the same timestep, as the
        # attributes for these new polymerases cannot be updated in the same
        # timestep.
        if didTerminate.sum() > 0 and replication_initiated == False:
            # Get attributes from active DNA polymerases and oriC's
            sequenceIdx, chromosomeIndexPolymerase, replicationRound = activeDnaPoly.attrs(
                'sequenceIdx', 'chromosomeIndex', 'replicationRound')
            chromosomeIndexOriC = oriCs.attr('chromosomeIndex')

            # Check that all terminated polymerases were initiated in the same
            # replication round
            assert np.unique(replicationRound[didTerminate]).size == 1

            # Get chromosome indexes of the terminated polymerases
            chromosomeIndexesTerminated = np.unique(
                chromosomeIndexPolymerase[didTerminate])
            newChromosomeIndex = chromosomeIndexPolymerase.max() + 1

            # Get replication round index of the terminated polymerases
            terminatedRound = replicationRound[didTerminate][0]

            for chromosomeIndexTerminated in chromosomeIndexesTerminated:
                # Get all remaining active polymerases initiated in the same
                # replication round and in the given chromosome
                replicationRoundMatch = (replicationRound == terminatedRound)
                chromosomeMatch = (
                    chromosomeIndexPolymerase == chromosomeIndexTerminated)
                remainingPolymerases = np.logical_and(replicationRoundMatch,
                                                      chromosomeMatch)

                # Get all terminated polymerases in the given chromosome
                terminatedPolymerases = np.logical_and(didTerminate,
                                                       chromosomeMatch)

                # If all active polymerases are terminated polymerases, we are
                # ready to split the chromosome and update the attributes.
                if remainingPolymerases.sum() == terminatedPolymerases.sum():

                    # For each set of polymerases initiated in the same
                    # replication round, update the chromosome indexes to a new
                    # index for half of the polymerases.
                    for roundIdx in np.arange(terminatedRound + 1,
                                              replicationRound.max() + 1):
                        replicationRoundMatch = (replicationRound == roundIdx)
                        polymerasesToSplit = np.logical_and(
                            replicationRoundMatch, chromosomeMatch)

                        n_matches = polymerasesToSplit.sum()

                        # Number of polymerases initiated in a single round
                        # must be a multiple of eight.
                        assert n_matches % 8 == 0

                        # Update the chromosome indexes for half of the polymerases
                        secondHalfIdx = np.where(polymerasesToSplit)[0][(
                            n_matches // 2):]
                        chromosomeIndexPolymerase[
                            secondHalfIdx] = newChromosomeIndex

                    # Reset chromosomeIndex for active DNA polymerases
                    activeDnaPoly.attrIs(
                        chromosomeIndex=chromosomeIndexPolymerase)

                    # Get oriC's in the chromosome getting divided
                    chromosomeMatchOriC = (
                        chromosomeIndexOriC == chromosomeIndexTerminated)
                    n_matches = chromosomeMatchOriC.sum()

                    # Number of OriC's in a dividing chromosome should be even
                    assert n_matches % 2 == 0

                    # Update the chromosome indexes for half of the OriC's
                    secondHalfIdx = np.where(chromosomeMatchOriC)[0][(
                        n_matches // 2):]
                    chromosomeIndexOriC[secondHalfIdx] = newChromosomeIndex

                    # Reset chromosomeIndex for oriC's
                    oriCs.attrIs(chromosomeIndex=chromosomeIndexOriC)

                    # Increment the new chromosome index in case another
                    # chromosome needs to be split
                    newChromosomeIndex += 1

            # Delete terminated polymerases
            activeDnaPoly.delByIndexes(np.where(didTerminate)[0])

            # Update counts of newly created chromosome halves. These will be
            # "stitched" together in the ChromosomeFormation process
            self.partialChromosomes.countsInc(terminatedChromosomes)
def initializeRibosomes(bulkMolCntr, uniqueMolCntr, sim_data, randomState):
    """
	Purpose: Activates ribosomes as unique molecules, and distributes them along length of RNA,
	decreases counts of unactivated ribosomal subunits (ribosome30S and ribosome50S).

	Normalizes ribosomes placement per length of protein
	"""

    # Load parameters
    nAvogadro = sim_data.constants.nAvogadro
    currentNutrients = sim_data.conditions[sim_data.condition]['nutrients']
    fracActiveRibosome = sim_data.process.translation.ribosomeFractionActiveDict[
        currentNutrients]
    mrnaIds = sim_data.process.translation.monomerData['rnaId']
    proteinLengths = sim_data.process.translation.monomerData[
        'length'].asNumber()
    proteinSequences = sim_data.process.translation.translationSequences
    translationEfficiencies = normalize(
        sim_data.process.translation.translationEfficienciesByMonomer)
    mRnas = bulkMolCntr.countsView(mrnaIds)
    aaWeightsIncorporated = sim_data.process.translation.translationMonomerWeights
    endWeight = sim_data.process.translation.translationEndWeight

    #find number of ribosomes to activate
    ribosome30S = bulkMolCntr.countsView(
        [sim_data.moleculeIds.s30_fullComplex]).counts()[0]
    ribosome50S = bulkMolCntr.countsView(
        [sim_data.moleculeIds.s50_fullComplex]).counts()[0]
    inactiveRibosomeCount = np.minimum(ribosome30S, ribosome50S)
    ribosomeToActivate = np.int64(fracActiveRibosome * inactiveRibosomeCount)

    # protein synthesis probabilities
    proteinInitProb = normalize(mRnas.counts() * translationEfficiencies)

    # normalize to protein length
    probLengthAdjusted = proteinInitProb * proteinLengths
    probNormalized = probLengthAdjusted / probLengthAdjusted.sum()

    # Sample a multinomial distribution of synthesis probabilities to determine what RNA are initialized
    nNewProteins = randomState.multinomial(ribosomeToActivate, probNormalized)

    # protein Indices
    proteinIndices = np.empty(ribosomeToActivate, np.int64)
    startIndex = 0
    nonzeroCount = (nNewProteins > 0)
    for proteinIndex, counts in izip(
            np.arange(nNewProteins.size)[nonzeroCount],
            nNewProteins[nonzeroCount]):
        proteinIndices[startIndex:startIndex + counts] = proteinIndex
        startIndex += counts

    # TODO (Eran) -- make sure there aren't any peptides at same location on same rna
    updatedLengths = np.array(randomState.rand(ribosomeToActivate) *
                              proteinLengths[proteinIndices],
                              dtype=np.int)

    # update mass
    sequences = proteinSequences[proteinIndices]
    massIncreaseProtein = computeMassIncrease(sequences, updatedLengths,
                                              aaWeightsIncorporated)
    massIncreaseProtein[
        updatedLengths != 0] += endWeight  # add endWeight to all new Rna

    # Create active 70S ribosomes and assign their protein Indices calculated above
    activeRibosomes = uniqueMolCntr.objectsNew('activeRibosome',
                                               ribosomeToActivate)
    activeRibosomes.attrIs(
        proteinIndex=proteinIndices,
        peptideLength=updatedLengths,
        massDiff_protein=massIncreaseProtein,
    )

    # decrease free 30S and 50S ribosomal subunit counts
    bulkMolCntr.countsIs(ribosome30S - ribosomeToActivate,
                         [sim_data.moleculeIds.s30_fullComplex])
    bulkMolCntr.countsIs(ribosome50S - ribosomeToActivate,
                         [sim_data.moleculeIds.s50_fullComplex])
def initializeRNApolymerase(bulkMolCntr, uniqueMolCntr, sim_data, randomState):
    """
	Purpose: Activates RNA polymerases as unique molecules, and distributes them along length of genes,
	decreases counts of unactivated RNA polymerases (APORNAP-CPLX[c]).

	Normalizes RNA poly placement per length of completed RNA, with synthesis probability based on each environmental condition
	"""

    # Load parameters
    nAvogadro = sim_data.constants.nAvogadro
    rnaLengths = sim_data.process.transcription.rnaData['length'].asNumber()
    currentNutrients = sim_data.conditions[sim_data.condition]['nutrients']
    fracActiveRnap = sim_data.process.transcription.rnapFractionActiveDict[
        currentNutrients]
    inactiveRnaPolyCounts = bulkMolCntr.countsView(['APORNAP-CPLX[c]'
                                                    ]).counts()[0]
    rnaSequences = sim_data.process.transcription.transcriptionSequences
    ntWeights = sim_data.process.transcription.transcriptionMonomerWeights
    endWeight = sim_data.process.transcription.transcriptionEndWeight

    # Number of rnaPoly to activate
    rnaPolyToActivate = np.int64(fracActiveRnap * inactiveRnaPolyCounts)

    # Parameters for rnaSynthProb
    recruitmentColNames = sim_data.process.transcription_regulation.recruitmentColNames
    recruitmentView = bulkMolCntr.counts(recruitmentColNames)
    recruitmentData = sim_data.process.transcription_regulation.recruitmentData
    recruitmentMatrix = scipy.sparse.csr_matrix(
        (recruitmentData['hV'],
         (recruitmentData['hI'], recruitmentData['hJ'])),
        shape=recruitmentData['shape'])

    # Synthesis probabilities for different categories of genes
    rnaSynthProbFractions = sim_data.process.transcription.rnaSynthProbFraction
    rnaSynthProbRProtein = sim_data.process.transcription.rnaSynthProbRProtein
    rnaSynthProbRnaPolymerase = sim_data.process.transcription.rnaSynthProbRnaPolymerase

    # Determine changes from genetic perturbations
    genetic_perturbations = {}
    perturbations = getattr(sim_data, 'genetic_perturbations', {})
    if len(perturbations) > 0:
        rnaIdxs, synthProbs = zip(*[(int(
            np.where(
                sim_data.process.transcription.rnaData['id'] == rnaId)[0]),
                                     synthProb)
                                    for rnaId, synthProb in sim_data.
                                    genetic_perturbations.iteritems()])
        fixedSynthProbs = [
            synthProb
            for (rnaIdx, synthProb) in sorted(zip(rnaIdxs, synthProbs),
                                              key=lambda pair: pair[0])
        ]
        fixedRnaIdxs = [
            rnaIdx for (rnaIdx, synthProb) in sorted(zip(rnaIdxs, synthProbs),
                                                     key=lambda pair: pair[0])
        ]
        genetic_perturbations = {
            'fixedRnaIdxs': fixedRnaIdxs,
            'fixedSynthProbs': fixedSynthProbs
        }

    # If initiationShuffleIdxs does not exist, set value to None
    shuffleIdxs = getattr(sim_data.process.transcription,
                          'initiationShuffleIdxs', None)

    # ID Groups
    isRRna = sim_data.process.transcription.rnaData['isRRna']
    isMRna = sim_data.process.transcription.rnaData['isMRna']
    isTRna = sim_data.process.transcription.rnaData['isTRna']
    isRProtein = sim_data.process.transcription.rnaData['isRProtein']
    isRnap = sim_data.process.transcription.rnaData['isRnap']
    isRegulated = np.array([
        1 if x[:-3] in sim_data.process.transcription_regulation.targetTf
        or x in perturbations else 0
        for x in sim_data.process.transcription.rnaData["id"]
    ],
                           dtype=np.bool)
    setIdxs = isRRna | isTRna | isRProtein | isRnap | isRegulated

    # Calculate synthesis probabilities based on transcription regulation
    rnaSynthProb = recruitmentMatrix.dot(recruitmentView)
    if len(genetic_perturbations) > 0:
        rnaSynthProb[genetic_perturbations[
            'fixedRnaIdxs']] = genetic_perturbations['fixedSynthProbs']
    regProbs = rnaSynthProb[isRegulated]

    # Adjust probabilities to not be negative
    rnaSynthProb[rnaSynthProb < 0] = 0.0
    rnaSynthProb /= rnaSynthProb.sum()
    if np.any(rnaSynthProb < 0):
        raise Exception("Have negative RNA synthesis probabilities")

    # Adjust synthesis probabilities depending on environment
    synthProbFractions = rnaSynthProbFractions[currentNutrients]
    rnaSynthProb[
        isMRna] *= synthProbFractions['mRna'] / rnaSynthProb[isMRna].sum()
    rnaSynthProb[
        isTRna] *= synthProbFractions['tRna'] / rnaSynthProb[isTRna].sum()
    rnaSynthProb[
        isRRna] *= synthProbFractions['rRna'] / rnaSynthProb[isRRna].sum()
    rnaSynthProb[isRegulated] = regProbs
    rnaSynthProb[isRProtein] = rnaSynthProbRProtein[currentNutrients]
    rnaSynthProb[isRnap] = rnaSynthProbRnaPolymerase[currentNutrients]
    rnaSynthProb[rnaSynthProb < 0] = 0  # to avoid precision issue
    scaleTheRestBy = (
        1. - rnaSynthProb[setIdxs].sum()) / rnaSynthProb[~setIdxs].sum()
    rnaSynthProb[~setIdxs] *= scaleTheRestBy

    # Shuffle initiation rates if we're running the variant that calls this
    if shuffleIdxs is not None:
        rnaSynthProb = rnaSynthProb[shuffleIdxs]

    # normalize to length of rna
    synthProbLengthAdjusted = rnaSynthProb * rnaLengths
    synthProbNormalized = synthProbLengthAdjusted / synthProbLengthAdjusted.sum(
    )

    # Sample a multinomial distribution of synthesis probabilities to determine what RNA are initialized
    nNewRnas = randomState.multinomial(rnaPolyToActivate, synthProbNormalized)

    # RNA Indices
    rnaIndices = np.empty(rnaPolyToActivate, np.int64)
    startIndex = 0
    nonzeroCount = (nNewRnas > 0)
    for rnaIndex, counts in izip(
            np.arange(nNewRnas.size)[nonzeroCount], nNewRnas[nonzeroCount]):
        rnaIndices[startIndex:startIndex + counts] = rnaIndex
        startIndex += counts

    # TODO (Eran) -- make sure there aren't any rnapolys at same location on same gene
    updatedLengths = np.array(randomState.rand(rnaPolyToActivate) *
                              rnaLengths[rnaIndices],
                              dtype=np.int)

    # update mass
    sequences = rnaSequences[rnaIndices]
    massIncreaseRna = computeMassIncrease(sequences, updatedLengths, ntWeights)
    massIncreaseRna[
        updatedLengths != 0] += endWeight  # add endWeight to all new Rna

    #update molecules. Attributes include which rnas are being transcribed, and the position (length)
    activeRnaPolys = uniqueMolCntr.objectsNew('activeRnaPoly',
                                              rnaPolyToActivate)
    activeRnaPolys.attrIs(
        rnaIndex=rnaIndices,
        transcriptLength=updatedLengths,
        massDiff_mRNA=massIncreaseRna,
    )
    bulkMolCntr.countsIs(inactiveRnaPolyCounts - rnaPolyToActivate,
                         ['APORNAP-CPLX[c]'])
Exemple #6
0
	def evolveState(self):
		ntpCounts = self.ntps.counts()
		self.writeToListener("GrowthLimits", "ntpAllocated", ntpCounts)

		activeRnaPolys = self.activeRnaPolys.molecules()
		if len(activeRnaPolys) == 0:
			return

		# Determine sequences that can be elongated
		rnaIndexes, transcriptLengths, massDiffRna = activeRnaPolys.attrs('rnaIndex', 'transcriptLength', 'massDiff_mRNA')
		sequences = buildSequences(self.rnaSequences, rnaIndexes, transcriptLengths, self.rnapElngRate)
		ntpCountInSequence = np.bincount(sequences[sequences != polymerize.PAD_VALUE], minlength = 4)

		# Polymerize transcripts based on sequences and available nucleotides
		reactionLimit = ntpCounts.sum()
		result = polymerize(sequences, ntpCounts, reactionLimit, self.randomState)
		sequenceElongations = result.sequenceElongation
		ntpsUsed = result.monomerUsages
		nElongations = result.nReactions

		# Calculate changes in mass associated with polymerization and update active polymerases
		massIncreaseRna = computeMassIncrease(sequences, sequenceElongations, self.ntWeights)
		updatedMass = massDiffRna + massIncreaseRna
		didInitialize = (transcriptLengths == 0) & (sequenceElongations > 0)
		updatedLengths = transcriptLengths + sequenceElongations
		updatedMass[didInitialize] += self.endWeight
		activeRnaPolys.attrIs(transcriptLength = updatedLengths, massDiff_mRNA = updatedMass)

		# Determine if transcript has reached the end of the sequence
		terminalLengths = self.rnaLengths[rnaIndexes]
		didTerminate = (updatedLengths == terminalLengths)
		terminatedRnas = np.bincount(rnaIndexes[didTerminate], minlength = self.rnaSequences.shape[0])

		# Remove polymerases that have finished transcription from unique molecules
		activeRnaPolys.delByIndexes(np.where(didTerminate)[0])

		nTerminated = didTerminate.sum()
		nInitialized = didInitialize.sum()
		nElongations = ntpsUsed.sum()

		# Update bulk molecule counts
		self.ntps.countsDec(ntpsUsed)
		self.bulkRnas.countsIs(terminatedRnas)
		self.inactiveRnaPolys.countInc(nTerminated)
		self.ppi.countInc(nElongations - nInitialized)

		# Calculate stalls
		expectedElongations = np.fmin(self.rnapElngRate, terminalLengths - transcriptLengths)
		rnapStalls = expectedElongations - sequenceElongations

		# Write outputs to listeners
		self.writeToListener("TranscriptElongationListener", "countRnaSynthesized", terminatedRnas)
		self.writeToListener("TranscriptElongationListener", "countNTPsUSed", nElongations)
		self.writeToListener("GrowthLimits", "ntpUsed", ntpsUsed)
		self.writeToListener("RnapData", "rnapStalls", rnapStalls)
		self.writeToListener("RnapData", "ntpCountInSequence", ntpCountInSequence)
		self.writeToListener("RnapData", "ntpCounts", ntpCounts)
		self.writeToListener("RnapData", "expectedElongations", expectedElongations.sum())
		self.writeToListener("RnapData", "actualElongations", sequenceElongations.sum())
		self.writeToListener("RnapData", "didTerminate", didTerminate.sum())
		self.writeToListener("RnapData", "terminationLoss", (terminalLengths - transcriptLengths)[didTerminate].sum())
    def evolveState(self):
        # Write allocation data to listener
        self.writeToListener("GrowthLimits", "gtpAllocated", self.gtp.count())
        self.writeToListener("GrowthLimits", "aaAllocated", self.aas.counts())

        # Get number of active ribosomes
        activeRibosomes = self.activeRibosomes.molecules()

        self.writeToListener("GrowthLimits", "activeRibosomeAllocated",
                             len(activeRibosomes))

        if len(activeRibosomes) == 0:
            return

        # Build amino acids sequences for each ribosome to polymerize
        proteinIndexes, peptideLengths, massDiffProtein = activeRibosomes.attrs(
            'proteinIndex', 'peptideLength', 'massDiff_protein')

        sequences = buildSequences(self.proteinSequences, proteinIndexes,
                                   peptideLengths, self.ribosomeElongationRate)

        if sequences.size == 0:
            return

        # Calculate elongation resource capacity
        aaCountInSequence = np.bincount(
            sequences[(sequences != polymerize.PAD_VALUE)])
        aaCounts = self.aas.counts()

        # Using polymerization algorithm elongate each ribosome up to the limits
        # of amino acids, sequence, and GTP
        result = polymerize(
            sequences,
            aaCounts,
            10000000,  # Set to a large number, the limit is now taken care of in metabolism
            self.randomState)

        sequenceElongations = result.sequenceElongation
        aasUsed = result.monomerUsages
        nElongations = result.nReactions

        # Update masses of ribosomes attached to polymerizing polypeptides
        massIncreaseProtein = computeMassIncrease(sequences,
                                                  sequenceElongations,
                                                  self.aaWeightsIncorporated)

        updatedLengths = peptideLengths + sequenceElongations

        didInitialize = ((sequenceElongations > 0) & (peptideLengths == 0))

        updatedMass = massDiffProtein + massIncreaseProtein

        updatedMass[didInitialize] += self.endWeight

        # Write current average elongation to listener
        currElongRate = (sequenceElongations.sum() /
                         len(activeRibosomes)) / self.timeStepSec()
        self.writeToListener("RibosomeData", "effectiveElongationRate",
                             currElongRate)

        # Update active ribosomes, terminating if neccessary
        activeRibosomes.attrIs(peptideLength=updatedLengths,
                               massDiff_protein=updatedMass)

        # Ribosomes that reach the end of their sequences are terminated and
        # dissociated into 30S and 50S subunits. The polypeptide that they are polymerizing
        # is converted into a protein in BulkMolecules
        terminalLengths = self.proteinLengths[proteinIndexes]

        didTerminate = (updatedLengths == terminalLengths)

        terminatedProteins = np.bincount(
            proteinIndexes[didTerminate],
            minlength=self.proteinSequences.shape[0])

        activeRibosomes.delByIndexes(np.where(didTerminate)[0])
        self.bulkMonomers.countsInc(terminatedProteins)

        nTerminated = didTerminate.sum()
        nInitialized = didInitialize.sum()

        self.ribosome30S.countInc(nTerminated)
        self.ribosome50S.countInc(nTerminated)

        # Update counts of amino acids and water to reflect polymerization reactions
        self.aas.countsDec(aasUsed)
        self.h2o.countInc(nElongations - nInitialized)

        # Report stalling information
        expectedElongations = np.fmin(self.ribosomeElongationRate,
                                      terminalLengths - peptideLengths)

        ribosomeStalls = expectedElongations - sequenceElongations

        # Write data to listeners
        self.writeToListener("GrowthLimits", "aasUsed", aasUsed)
        self.writeToListener("GrowthLimits", "gtpUsed", self.gtpUsed)

        self.writeToListener("RibosomeData", "ribosomeStalls", ribosomeStalls)
        self.writeToListener("RibosomeData", "aaCountInSequence",
                             aaCountInSequence)
        self.writeToListener("RibosomeData", "aaCounts", aaCounts)

        self.writeToListener("RibosomeData", "expectedElongations",
                             expectedElongations.sum())
        self.writeToListener("RibosomeData", "actualElongations",
                             sequenceElongations.sum())
        self.writeToListener(
            "RibosomeData", "actualElongationHist",
            np.histogram(sequenceElongations, bins=np.arange(0, 23))[0])
        self.writeToListener(
            "RibosomeData", "elongationsNonTerminatingHist",
            np.histogram(sequenceElongations[~didTerminate],
                         bins=np.arange(0, 23))[0])

        self.writeToListener("RibosomeData", "didTerminate",
                             didTerminate.sum())
        self.writeToListener("RibosomeData", "terminationLoss",
                             (terminalLengths -
                              peptideLengths)[didTerminate].sum())
        self.writeToListener("RibosomeData", "numTrpATerminated",
                             terminatedProteins[self.trpAIndex])

        self.writeToListener("RibosomeData", "processElongationRate",
                             self.ribosomeElongationRate / self.timeStepSec())