def learn(self, bottomUpInput):
    """
    Parameters:
    --------------------------------------------
    input:        Current bottom-up input
    retval:       ?
    """

    self.lrnIterationIdx = self.lrnIterationIdx + 1
    self.iterationIdx = self.iterationIdx + 1
    if self.verbosity >= 3:
      print "\n==== Iteration: %d =====" % (self.iterationIdx)
      print "Active cols:", bottomUpInput.nonzero()[0]


    if self.verbosity >= 4:
      print len(self.segmentUpdates), "updates"
      for k,v in self.segmentUpdates.iteritems():
        print 'cell:', k[0] * self.cellsPerColumn + k[1],
        for vv in v:
          print 'seg:', vv[1].segment,
          print 'timeStamp:', vv[0],
          print '/ src cells:', vv[1].activeSynapses

    # Copy t into t-1
    # This time also copy over learnState.
    self.activeState['t-1'][:,:] = self.activeState['t'][:,:]
    self.activeState['t'].fill(0)
    self.predictedState['t-1'][:,:] = self.predictedState['t'][:,:]
    self.predictedState['t'].fill(0)
    self.learnState['t-1'][:,:] = self.learnState['t'][:,:]
    self.learnState['t'][:,:] = 0
    self.confidence['t-1'][:,:] = self.confidence['t'][:,:]
    self.confidence['t'].fill(0.0)

    # Update segment duty cycles if we are crossing a "tier"
    # We determine if it's time to update the segment duty cycles. Since the
    # duty cycle calculation is a moving average based on a tiered alpha, it is
    # important that we update all segments on each tier boundary
    if self.lrnIterationIdx in Segment.dutyCycleTiers:
      for c, i in product(xrange(self.numberOfCols),
                                    xrange(self.cellsPerColumn)):
        for segment in self.cells[c][i]:
          segment.dutyCycle()


    # Phase 1: compute current state for each cell
    # For each column (winning in the SP):
    # - if the bottom up input was predicted by one of the sequence
    #   segments of a cell, this cell bursts, (those cells also become
    #   learning cells),
    # - if the bottom up input was not predicted by any cell, the entire
    #   column bursts, AND:
    #   - if a cell had a sequence segment above minThreshold, it becomes
    #     a learning cell, else
    #   - we create a new sequence segment for that cell, and it also
    #     becomes a learning cell.
    activeColumns = bottomUpInput.nonzero()[0]
    numUnpredictedColumns = 0


    for c in activeColumns:

      # todo: cache this list when building it in iteration at t
      buPredicted = False  # Was this bottom up input predicted?
      predictingCells = numpy.where(self.predictedState['t-1'][c] == 1)[0]

      for i in predictingCells:
        # Convert from Numpy integer structures, for debugging in Komodo
        c,i = int(c), int(i)
        buPredicted = True
        self.activeState['t'][c,i] = 1
        self.learnState['t'][c,i] = 1

      # Turn on the active state of the whole column if the bottom up input was
      # not predicted
      if not buPredicted:
        numUnpredictedColumns += 1

        self.activeState['t'][c,:] = 1

        # We didn't find a cell to learn on (since no cell was predicted), so
        # just pick the best matching cell, or, failing that, the least allocated
        # cell.
        # sum(all synapses) >= minThreshold, "weak" activation
        i,s = self.getBestMatchingCell(c,self.activeState['t-1'])

        if s is not None:
          s.totalActivations += 1      # activationFrequency
          s.lastActiveIteration = self.iterationIdx
        else:
          # if best matching cell does not exist, then get least used cell
          i = self.getLeastUsedCell(c)
          s = None



          # Update a possibly weakly matching segment
          # todo: In here, should we only use learnState to check for
          # a weak match?
        self.learnState['t'][c,i] = 1
        self.activeState['t'][c,i] = 1    # In case we are in PAM mode

        # propose a list of synapse change
        segUpdate = self.getSegmentActiveSynapses(c,i,s,'t-1',
                            newSynapses = True)
        segUpdate.phase1Flag = True
        self.addToSegmentUpdates(c, i, segUpdate)

    # ----------------------------------------------------------------------
    # Phase 2: compute predicted state for each cell
    # - if a segment has enough horizontal connections firing because of
    #   bottomUpInput, it's set to be predicting, and we queue up the segment
    #   for reinforcement,
    self.computePhase2(doLearn=True)

    # ----------------------------------------------------------------------
    # Phase 3: update synapses for currently active cells (due to bottom up input)
    # that have queued up segment updates, or cells that just stopped predicting
    # (those cells that stopped predicting are _negatively_ reinforced).
    # Also clean up queues by removing seg updates that are too old.
    self.processSegmentUpdates()

    # ----------------------------------------------------------------------
    # Phase 4: Apply global decay, and remove synapses and/or segments.
    # Synapses are removed if their permanence value is <= 0.
    # Segments are removed when they don't have synapses anymore.
    # Removal of synapses can trigger removal of whole segments!
    # todo: isolate the synapse/segment retraction logic so that
    # it can be called in adaptSegments, in the case where we
    # do global decay only episodically.
    if self.globalDecay > 0.0 and ((self.iterationIdx % self.maxAge) == 0):
      for c, i in product(xrange(self.numberOfCols), xrange(self.cellsPerColumn)):

        segsToDel = [] # collect and remove outside the loop
        for segment in self.cells[c][i]:
          age = self.iterationIdx - segment[0][self.kSegLastActiveIteration]
          if age <= self.maxAge:
            continue

          #print "Decrementing seg age %d:" % (age), c, i, segment
          synsToDel = [] # collect and remove outside the loop
          for synapse in segment.syns: # skip sequenceSegment flag

            synapse[2] = synapse[2] - self.globalDecay # decrease permanence

            if synapse[2] <= 0:
              synsToDel.append(synapse) # add to list to delete

          if len(synsToDel) == len(segment.syns): # 1 for sequenceSegment flag
            segsToDel.append(segment) # will remove the whole segment
          elif len(synsToDel) > 0:
            for syn in synsToDel: # remove some synapses on segment
              segment.syns.remove(syn)

        for seg in segsToDel: # remove some segments of this cell
          self.cleanUpdatesList(c,i,seg)
          self.cells[c][i].remove(seg)


    # Update the prediction score stats
    # Learning always includes inference
    if self.collectStats:
      self._updateStatsInferEnd(self._internalStats, activeColumns,
                                self.predictedState['t-1'],
                                self.confidence['t-1'])

    # Finally return learning output
    output = self.computeOutput()

    # Print diagnostic information based on the current verbosity level
    self.printComputeEnd(output, learn=True)

    self.resetCalled = False

    return self.computeOutput()
Parameters:
    --------------------------------------------
    input:        Current bottom-up input
    retval:       ?