def doExperiment(numColumns, l2Overrides, objectDescriptions, noiseMu,
                 noiseSigma, numInitialTraversals, noiseEverywhere):
  """
  Touch every point on an object 'numInitialTraversals' times, then evaluate
  whether it has inferred the object by touching every point once more and
  checking the number of correctly active and incorrectly active cells.

  @param numColumns (int)
  The number of sensors to use

  @param l2Overrides (dict)
  Parameters for the ColumnPooler

  @param objectDescriptions (dict)
  A mapping of object names to their feature-locations.
  See 'createRandomObjectDescriptions'.

  @param noiseMu (float)
  The average amount of noise in a feedforward input. The noise level for each
  column's input is determined once per touch. It is a gaussian distribution
  with mean 'noiseMu' and sigma 'noiseSigma'.

  @param noiseSigma (float)
  The sigma for the gaussian distribution of noise levels. If the noiseSigma is
  0, then the noise level will always be 'noiseMu'.

  @param numInitialTraversals (int)
  The number of times to traverse the object before testing whether the object
  has been inferred.

  @param noiseEverywhere (bool)
  If true, add noise to every column's input, and record accuracy of every
  column. If false, add noise to one column's input, and only record accuracy
  of that column.
  """

  # For each column, keep a mapping from feature-location names to their SDRs
  layer4sdr = lambda : np.array(sorted(random.sample(xrange(L4_CELL_COUNT),
                                                     40)), dtype="uint32")
  featureLocationSDRs = [defaultdict(layer4sdr) for _ in xrange(numColumns)]

  params = {"inputWidth": L4_CELL_COUNT,
            "lateralInputWidths": [4096]*(numColumns-1),
            "seed": random.randint(0, 1024)}
  params.update(l2Overrides)

  l2Columns = [ColumnPooler(**params)
               for _ in xrange(numColumns)]

  # Learn the objects
  objectL2Representations = {}
  for objectName, featureLocations in  objectDescriptions.iteritems():
    for featureLocationName in featureLocations:
      # Touch it enough times for the distal synapses to reach the
      # connected permanence, and then once more.
      for _ in xrange(4):
        allLateralInputs = [l2.getActiveCells() for l2 in l2Columns]
        for columnNumber, l2 in enumerate(l2Columns):
          feedforwardInput = featureLocationSDRs[columnNumber][featureLocationName]
          lateralInputs = [lateralInput
                           for i, lateralInput in enumerate(allLateralInputs)
                           if i != columnNumber]
          l2.compute(feedforwardInput, lateralInputs, learn=True)
    objectL2Representations[objectName] = [set(l2.getActiveCells())
                                           for l2 in l2Columns]
    for l2 in l2Columns:
      l2.reset()

  results = []

  # Try to infer the objects
  for objectName, featureLocations in objectDescriptions.iteritems():
    for l2 in l2Columns:
      l2.reset()

    sensorPositionsIterator = greedySensorPositions(numColumns, len(featureLocations))

    # Touch each location at least numInitialTouches times, and then touch it
    # once more, testing it. For each traversal, touch each point on the object
    # ~once. Not once per sensor -- just once. So we translate the "number of
    # traversals" into a "number of touches" according to the number of sensors.
    numTouchesPerTraversal = len(featureLocations) / float(numColumns)
    numInitialTouches = int(math.ceil(numInitialTraversals * numTouchesPerTraversal))

    if noiseEverywhere:
      numTestTouches = int(math.ceil(1 * numTouchesPerTraversal))
    else:
      numTestTouches = len(featureLocations)

    for touch in xrange(numInitialTouches + numTestTouches):
      sensorPositions = next(sensorPositionsIterator)

      # Give the system a few timesteps to settle, allowing lateral connections
      # to cause cells to be inhibited.
      for _ in xrange(3):
        allLateralInputs = [l2.getActiveCells() for l2 in l2Columns]
        for columnNumber, l2 in enumerate(l2Columns):
          position = sensorPositions[columnNumber]
          featureLocationName = featureLocations[position]
          feedforwardInput = featureLocationSDRs[columnNumber][featureLocationName]

          if noiseEverywhere or columnNumber == 0:
            noiseLevel = random.gauss(noiseMu, noiseSigma)
            noiseLevel = max(0.0, min(1.0, noiseLevel))
            feedforwardInput = noisy(feedforwardInput, noiseLevel, L4_CELL_COUNT)

          lateralInputs = [lateralInput
                           for i, lateralInput in enumerate(allLateralInputs)
                           if i != columnNumber]

          l2.compute(feedforwardInput, lateralInputs, learn=False)

      if touch >= numInitialTouches:
        if noiseEverywhere:
          for columnNumber, l2 in enumerate(l2Columns):
            activeCells = set(l2.getActiveCells())
            correctCells = objectL2Representations[objectName][columnNumber]
            results.append((len(activeCells & correctCells),
                            len(activeCells - correctCells)))
        else:
          activeCells = set(l2Columns[0].getActiveCells())
          correctCells = objectL2Representations[objectName][0]
          results.append((len(activeCells & correctCells),
                          len(activeCells - correctCells)))

  return results
Touch every point on an object 'numInitialTraversals' times, then evaluate
  whether it has inferred the object by touching every point once more and
  checking the number of correctly active and incorrectly active cells.

  @param numColumns (int)
  The number of sensors to use

  @param l2Overrides (dict)
  Parameters for the ColumnPooler

  @param objectDescriptions (dict)
  A mapping of object names to their feature-locations.
  See 'createRandomObjectDescriptions'.

  @param noiseMu (float)
  The average amount of noise in a feedforward input. The noise level for each
  column's input is determined once per touch. It is a gaussian distribution
  with mean 'noiseMu' and sigma 'noiseSigma'.

  @param noiseSigma (float)
  The sigma for the gaussian distribution of noise levels. If the noiseSigma is
  0, then the noise level will always be 'noiseMu'.

  @param numInitialTraversals (int)
  The number of times to traverse the object before testing whether the object
  has been inferred.

  @param noiseEverywhere (bool)
  If true, add noise to every column's input, and record accuracy of every
  column. If false, add noise to one column's input, and only record accuracy
  of that column.