Esempio n. 1
0
  def testDestroySegment(self):
    """ Creates a segment, destroys it, and makes sure it got destroyed along
        with all of its synapses.
    """
    connections = Connections(1024)

    connections.createSegment(10)
    segment2 = connections.createSegment(20)
    connections.createSegment(30)
    connections.createSegment(40)

    connections.createSynapse(segment2, 80, 0.85)
    connections.createSynapse(segment2, 81, 0.85)
    connections.createSynapse(segment2, 82, 0.15)

    self.assertEqual(4, connections.numSegments())
    self.assertEqual(3, connections.numSynapses())

    connections.destroySegment(segment2)

    self.assertEqual(3, connections.numSegments())
    self.assertEqual(0, connections.numSynapses())

    args = [segment2]
    self.assertRaises(ValueError, connections.synapsesForSegment, *args)

    active, matching = connections.computeActivity([80, 81, 82], .5, 2, .1, 1)
    self.assertEqual(len(active), 0)
    self.assertEqual(len(matching), 0)
Esempio n. 2
0
    def testDestroySegment(self):
        """ Creates a segment, destroys it, and makes sure it got destroyed along
        with all of its synapses.
    """
        connections = Connections(1024)

        connections.createSegment(10)
        segment2 = connections.createSegment(20)
        connections.createSegment(30)
        connections.createSegment(40)

        connections.createSynapse(segment2, 80, 0.85)
        connections.createSynapse(segment2, 81, 0.85)
        connections.createSynapse(segment2, 82, 0.15)

        self.assertEqual(4, connections.numSegments())
        self.assertEqual(3, connections.numSynapses())

        connections.destroySegment(segment2)

        self.assertEqual(3, connections.numSegments())
        self.assertEqual(0, connections.numSynapses())

        args = [segment2]
        self.assertRaises(ValueError, connections.synapsesForSegment, *args)

        active, matching = connections.computeActivity([80, 81, 82], .5, 2, .1,
                                                       1)
        self.assertEqual(len(active), 0)
        self.assertEqual(len(matching), 0)
Esempio n. 3
0
  def testWriteRead(self):
    c1 = Connections(1024)

    # Add data before serializing
    s1 = c1.createSegment(0)
    c1.createSynapse(s1, 254, 0.1173)

    s2 = c1.createSegment(100)
    c1.createSynapse(s2, 20, 0.3)

    c1.createSynapse(s1, 40, 0.3)

    s3 = c1.createSegment(0)
    c1.createSynapse(s3, 0, 0.5)
    c1.createSynapse(s3, 1, 0.5)

    s4 = c1.createSegment(10)
    c1.createSynapse(s4, 0, 0.5)
    c1.createSynapse(s4, 1, 0.5)
    c1.destroySegment(s4)

    proto1 = ConnectionsProto_capnp.ConnectionsProto.new_message()
    c1.write(proto1)

    # Write the proto to a temp file and read it back into a new proto
    with tempfile.TemporaryFile() as f:
      proto1.write(f)
      f.seek(0)
      proto2 = ConnectionsProto_capnp.ConnectionsProto.read(f)

    # Load the deserialized proto
    c2 = Connections.read(proto2)

    # Check that the two connections objects are functionally equal
    self.assertEqual(c1, c2)
Esempio n. 4
0
    def testComputeActivity(self):
        """ Creates a sample set of connections, and makes sure that computing the
        activity for a collection of cells with no activity returns the right
        activity data.
    """
        connections = Connections(1024)

        # Cell with 1 segment.
        # Segment with:
        # - 1 connected synapse: active
        # - 2 matching synapses
        segment1a = connections.createSegment(10)
        connections.createSynapse(segment1a, 150, .85)
        connections.createSynapse(segment1a, 151, .15)

        # Cell with 1 segment.
        # Segment with:
        # - 2 connected synapse: 2 active
        # - 3 matching synapses: 3 active
        segment2a = connections.createSegment(20)
        connections.createSynapse(segment2a, 80, .85)
        connections.createSynapse(segment2a, 81, .85)
        synapse = connections.createSynapse(segment2a, 82, .85)
        connections.updateSynapsePermanence(synapse, .15)

        inputVec = [50, 52, 53, 80, 81, 82, 150, 151]
        (numActiveConnected,
         numActivePotential) = connections.computeActivity(inputVec, .5)

        self.assertEqual(1, numActiveConnected[segment1a.flatIdx])
        self.assertEqual(2, numActivePotential[segment1a.flatIdx])

        self.assertEqual(2, numActiveConnected[segment2a.flatIdx])
        self.assertEqual(3, numActivePotential[segment2a.flatIdx])
Esempio n. 5
0
    def testDestroySegment(self):
        """ Creates a segment, destroys it, and makes sure it got destroyed along
        with all of its synapses.
    """
        connections = Connections(1024)

        connections.createSegment(10)
        segment2 = connections.createSegment(20)
        connections.createSegment(30)
        connections.createSegment(40)

        connections.createSynapse(segment2, 80, 0.85)
        connections.createSynapse(segment2, 81, 0.85)
        connections.createSynapse(segment2, 82, 0.15)

        self.assertEqual(4, connections.numSegments())
        self.assertEqual(3, connections.numSynapses())

        connections.destroySegment(segment2)

        self.assertEqual(3, connections.numSegments())
        self.assertEqual(0, connections.numSynapses())

        (numActiveConnected,
         numActivePotential) = connections.computeActivity([80, 81, 82], 0.5)

        self.assertEqual(0, numActiveConnected[segment2.flatIdx])
        self.assertEqual(0, numActivePotential[segment2.flatIdx])
Esempio n. 6
0
  def testDestroySegment(self):
    """ Creates a segment, destroys it, and makes sure it got destroyed along
        with all of its synapses.
    """
    connections = Connections(1024)

    connections.createSegment(10)
    segment2 = connections.createSegment(20)
    connections.createSegment(30)
    connections.createSegment(40)

    connections.createSynapse(segment2, 80, 0.85)
    connections.createSynapse(segment2, 81, 0.85)
    connections.createSynapse(segment2, 82, 0.15)

    self.assertEqual(4, connections.numSegments())
    self.assertEqual(3, connections.numSynapses())

    connections.destroySegment(segment2)

    self.assertEqual(3, connections.numSegments())
    self.assertEqual(0, connections.numSynapses())

    (numActiveConnected,
     numActivePotential) = connections.computeActivity([80, 81, 82], 0.5)

    self.assertEqual(0, numActiveConnected[segment2.flatIdx])
    self.assertEqual(0, numActivePotential[segment2.flatIdx])
Esempio n. 7
0
    def testWriteRead(self):
        c1 = Connections(1024)

        # Add data before serializing
        s1 = c1.createSegment(0)
        c1.createSynapse(s1, 254, 0.1173)

        s2 = c1.createSegment(100)
        c1.createSynapse(s2, 20, 0.3)

        c1.createSynapse(s1, 40, 0.3)

        s3 = c1.createSegment(0)
        c1.createSynapse(s3, 0, 0.5)
        c1.createSynapse(s3, 1, 0.5)

        s4 = c1.createSegment(10)
        c1.createSynapse(s4, 0, 0.5)
        c1.createSynapse(s4, 1, 0.5)
        c1.destroySegment(s4)

        proto1 = ConnectionsProto_capnp.ConnectionsProto.new_message()
        c1.write(proto1)

        # Write the proto to a temp file and read it back into a new proto
        with tempfile.TemporaryFile() as f:
            proto1.write(f)
            f.seek(0)
            proto2 = ConnectionsProto_capnp.ConnectionsProto.read(f)

        # Load the deserialized proto
        c2 = Connections.read(proto2)

        # Check that the two connections objects are functionally equal
        self.assertEqual(c1, c2)
Esempio n. 8
0
  def testComputeActivity(self):
    """ Creates a sample set of connections, and makes sure that computing the
        activity for a collection of cells with no activity returns the right
        activity data.
    """
    connections = Connections(1024)

    # Cell with 1 segment.
    # Segment with:
    # - 1 connected synapse: active
    # - 2 matching synapses
    segment1a = connections.createSegment(10)
    connections.createSynapse(segment1a, 150, .85)
    connections.createSynapse(segment1a, 151, .15)

    # Cell with 1 segment.
    # Segment with:
    # - 2 connected synapse: 2 active
    # - 3 matching synapses: 3 active
    segment2a = connections.createSegment(20)
    connections.createSynapse(segment2a, 80, .85)
    connections.createSynapse(segment2a, 81, .85)
    synapse = connections.createSynapse(segment2a, 82, .85)
    connections.updateSynapsePermanence(synapse, .15)

    inputVec = [50, 52, 53, 80, 81, 82, 150, 151]
    (numActiveConnected,
     numActivePotential) = connections.computeActivity(inputVec, .5)

    self.assertEqual(1, numActiveConnected[segment1a.flatIdx])
    self.assertEqual(2, numActivePotential[segment1a.flatIdx])

    self.assertEqual(2, numActiveConnected[segment2a.flatIdx])
    self.assertEqual(3, numActivePotential[segment2a.flatIdx])
Esempio n. 9
0
  def testComputeActivity(self):
    """ Creates a sample set of connections, and makes sure that computing the
        activity for a collection of cells with no activity returns the right
        activity data.
    """
    connections = Connections(1024)

    # Cell with 1 segment.
    # Segment with:
    # - 1 connected synapse: active
    # - 2 matching synapses
    segment1a = connections.createSegment(10)
    connections.createSynapse(segment1a, 150, .85)
    connections.createSynapse(segment1a, 151, .15)

    # Cell with 2 segment.
    # Segment with:
    # - 2 connected synapse: 2 active
    # - 3 matching synapses: 3 active
    segment2a = connections.createSegment(20)
    connections.createSynapse(segment2a, 80, .85)
    connections.createSynapse(segment2a, 81, .85)
    synapse = connections.createSynapse(segment2a, 82, .85)
    connections.updateSynapsePermanence(synapse, .15)


    # Segment with:
    # - 2 connected synapses: 1 active, 1 inactive
    # - 3 matching synapses: 2 active, 1 inactive
    # - 1 non-matching synapse: 1 active
    segment2b = connections.createSegment(20)
    connections.createSynapse(segment2b, 50, .85)
    connections.createSynapse(segment2b, 51, .85)
    connections.createSynapse(segment2b, 52, .15)
    connections.createSynapse(segment2b, 53, .05)

    # Cell with one segment.
    # Segment with:
    # - 1 non-matching synapse: 1 active
    segment3a = connections.createSegment(30)
    connections.createSynapse(segment3a, 53, .05)

    inputVec = [50, 52, 53, 80, 81, 82, 150, 151]
    active, matching = connections.computeActivity(inputVec, .5, 2, .1, 1)

    self.assertEqual(1, len(active))
    self.assertEqual(segment2a, active[0].segment)
    self.assertEqual(2, active[0].overlap)

    self.assertEqual(3, len(matching))
    self.assertEqual(segment1a, matching[0].segment)
    self.assertEqual(2, matching[0].overlap)
    self.assertEqual(segment2a, matching[1].segment)
    self.assertEqual(3, matching[1].overlap)
    self.assertEqual(segment2b, matching[2].segment)
    self.assertEqual(2, matching[2].overlap)
Esempio n. 10
0
    def testComputeActivity(self):
        """ Creates a sample set of connections, and makes sure that computing the
        activity for a collection of cells with no activity returns the right
        activity data.
    """
        connections = Connections(1024)

        # Cell with 1 segment.
        # Segment with:
        # - 1 connected synapse: active
        # - 2 matching synapses
        segment1a = connections.createSegment(10)
        connections.createSynapse(segment1a, 150, .85)
        connections.createSynapse(segment1a, 151, .15)

        # Cell with 2 segment.
        # Segment with:
        # - 2 connected synapse: 2 active
        # - 3 matching synapses: 3 active
        segment2a = connections.createSegment(20)
        connections.createSynapse(segment2a, 80, .85)
        connections.createSynapse(segment2a, 81, .85)
        synapse = connections.createSynapse(segment2a, 82, .85)
        connections.updateSynapsePermanence(synapse, .15)

        # Segment with:
        # - 2 connected synapses: 1 active, 1 inactive
        # - 3 matching synapses: 2 active, 1 inactive
        # - 1 non-matching synapse: 1 active
        segment2b = connections.createSegment(20)
        connections.createSynapse(segment2b, 50, .85)
        connections.createSynapse(segment2b, 51, .85)
        connections.createSynapse(segment2b, 52, .15)
        connections.createSynapse(segment2b, 53, .05)

        # Cell with one segment.
        # Segment with:
        # - 1 non-matching synapse: 1 active
        segment3a = connections.createSegment(30)
        connections.createSynapse(segment3a, 53, .05)

        inputVec = [50, 52, 53, 80, 81, 82, 150, 151]
        active, matching = connections.computeActivity(inputVec, .5, 2, .1, 1)

        self.assertEqual(1, len(active))
        self.assertEqual(segment2a, active[0].segment)
        self.assertEqual(2, active[0].overlap)

        self.assertEqual(3, len(matching))
        self.assertEqual(segment1a, matching[0].segment)
        self.assertEqual(2, matching[0].overlap)
        self.assertEqual(segment2a, matching[1].segment)
        self.assertEqual(3, matching[1].overlap)
        self.assertEqual(segment2b, matching[2].segment)
        self.assertEqual(2, matching[2].overlap)
Esempio n. 11
0
  def testCreateSegment(self):
    connections = Connections(1024)

    segment1 = connections.createSegment(10)
    self.assertEqual(segment1.cell, 10)

    segment2 = connections.createSegment(10)
    self.assertEqual(segment2.cell, 10)

    self.assertEqual([segment1, segment2],
                     list(connections.segmentsForCell(10)))
Esempio n. 12
0
    def testCreateSegment(self):
        connections = Connections(1024)

        segment1 = connections.createSegment(10)
        self.assertEqual(segment1.cell, 10)

        segment2 = connections.createSegment(10)
        self.assertEqual(segment2.cell, 10)

        self.assertEqual([segment1, segment2],
                         list(connections.segmentsForCell(10)))
Esempio n. 13
0
    def testCreateSegmentReuse(self):
        connections = Connections(1024, 2)

        segment1 = connections.createSegment(42)
        connections.createSynapse(segment1, 1, .5)
        connections.createSynapse(segment1, 2, .5)

        # Let some time pass.
        connections.startNewIteration()
        connections.startNewIteration()
        connections.startNewIteration()

        # Create a segment with 3 synapse.
        segment2 = connections.createSegment(42)
        connections.createSynapse(segment2, 1, .5)
        connections.createSynapse(segment2, 2, .5)
        connections.createSynapse(segment2, 3, .5)
        connections.startNewIteration()

        # Give the first segment some activity.
        connections.recordSegmentActivity(segment1)

        # Create a new segment with 1 synapse.
        segment3 = connections.createSegment(42)
        connections.createSynapse(segment3, 1, .5)

        segments = connections.segmentsForCell(42)
        self.assertEqual(2, len(segments))

        # Verify first segment is still there with the same synapses.
        self.assertEqual(
            set([1, 2]),
            set(synapse.presynapticCell
                for synapse in connections.synapsesForSegment(segments[0])))

        # Verify second segment has been replaced.
        self.assertEqual(
            set([1]),
            set(synapse.presynapticCell
                for synapse in connections.synapsesForSegment(segments[1])))

        # Verify the flatIdxs were properly reused.
        self.assertLess(segment1.flatIdx, 2)
        self.assertLess(segment3.flatIdx, 2)
        self.assertTrue(
            segment1 is connections.segmentForFlatIdx(segment1.flatIdx))
        self.assertTrue(
            segment3 is connections.segmentForFlatIdx(segment3.flatIdx))
Esempio n. 14
0
  def testPathsNotInvalidatedByOtherDestroys(self):
    """ Creates segments and synapses, then destroys segments and synapses on
        either side of them and verifies that existing Segment and Synapse
        instances still point to the same segment / synapse as before.
    """
    connections = Connections(1024)
    segment1 = connections.createSegment(11)
    connections.createSegment(12)
    segment3 = connections.createSegment(13)
    connections.createSegment(14)
    segment5 = connections.createSegment(15)

    synapse1 = connections.createSynapse(segment3, 201, .85)
    synapse2 = connections.createSynapse(segment3, 202, .85)
    synapse3 = connections.createSynapse(segment3, 203, .85)
    synapse4 = connections.createSynapse(segment3, 204, .85)
    synapse5 = connections.createSynapse(segment3, 205, .85)

    self.assertEqual(203, synapse3.presynapticCell)
    connections.destroySynapse(synapse1)
    self.assertEqual(203, synapse3.presynapticCell)
    connections.destroySynapse(synapse5)
    self.assertEqual(203, synapse3.presynapticCell)

    connections.destroySegment(segment1)
    self.assertEqual(set([synapse2, synapse3, synapse4]),
                     connections.synapsesForSegment(segment3))
    connections.destroySegment(segment5)
    self.assertEqual(set([synapse2, synapse3, synapse4]),
                     connections.synapsesForSegment(segment3))
    self.assertEqual(203, synapse3.presynapticCell)
Esempio n. 15
0
    def testPathsNotInvalidatedByOtherDestroys(self):
        """ Creates segments and synapses, then destroys segments and synapses on
        either side of them and verifies that existing Segment and Synapse
        instances still point to the same segment / synapse as before.
    """
        connections = Connections(1024)
        segment1 = connections.createSegment(11)
        connections.createSegment(12)
        segment3 = connections.createSegment(13)
        connections.createSegment(14)
        segment5 = connections.createSegment(15)

        synapse1 = connections.createSynapse(segment3, 201, .85)
        synapse2 = connections.createSynapse(segment3, 202, .85)
        synapse3 = connections.createSynapse(segment3, 203, .85)
        synapse4 = connections.createSynapse(segment3, 204, .85)
        synapse5 = connections.createSynapse(segment3, 205, .85)

        self.assertEqual(203,
                         connections.dataForSynapse(synapse3).presynapticCell)
        connections.destroySynapse(synapse1)
        self.assertEqual(203,
                         connections.dataForSynapse(synapse3).presynapticCell)
        connections.destroySynapse(synapse5)
        self.assertEqual(203,
                         connections.dataForSynapse(synapse3).presynapticCell)

        connections.destroySegment(segment1)
        self.assertEqual(connections.synapsesForSegment(segment3),
                         [synapse2, synapse3, synapse4])
        connections.destroySegment(segment5)
        self.assertEqual(connections.synapsesForSegment(segment3),
                         [synapse2, synapse3, synapse4])
        self.assertEqual(203,
                         connections.dataForSynapse(synapse3).presynapticCell)
Esempio n. 16
0
  def testCreateSegmentReuse(self):
    connections = Connections(1024, 2)

    segment1 = connections.createSegment(42)
    connections.createSynapse(segment1, 1, .5)
    connections.createSynapse(segment1, 2, .5)

    connections.computeActivity([], .5, 2, .1, 1)
    connections.computeActivity([], .5, 2, .1, 1)
    connections.computeActivity([], .5, 2, .1, 1)

    segment2 = connections.createSegment(42)
    activeSegs, _ = connections.computeActivity([1, 2], .5, 2, .1, 1)
    self.assertEqual(1, len(activeSegs))
    self.assertEqual(segment1, activeSegs[0].segment)

    segment3 = connections.createSegment(42)
    self.assertEqual(segment2.idx, segment3.idx)
Esempio n. 17
0
    def testCreateSegmentReuse(self):
        connections = Connections(1024, 2)

        segment1 = connections.createSegment(42)
        connections.createSynapse(segment1, 1, .5)
        connections.createSynapse(segment1, 2, .5)

        connections.computeActivity([], .5, 2, .1, 1)
        connections.computeActivity([], .5, 2, .1, 1)
        connections.computeActivity([], .5, 2, .1, 1)

        segment2 = connections.createSegment(42)
        activeSegs, _ = connections.computeActivity([1, 2], .5, 2, .1, 1)
        self.assertEqual(1, len(activeSegs))
        self.assertEqual(segment1, activeSegs[0].segment)

        segment3 = connections.createSegment(42)
        self.assertEqual(segment2.idx, segment3.idx)
Esempio n. 18
0
  def testCreateSegmentReuse(self):
    connections = Connections(1024, 2)

    segment1 = connections.createSegment(42)
    connections.createSynapse(segment1, 1, .5)
    connections.createSynapse(segment1, 2, .5)

    # Let some time pass.
    connections.startNewIteration();
    connections.startNewIteration();
    connections.startNewIteration();

    # Create a segment with 3 synapse.
    segment2 = connections.createSegment(42)
    connections.createSynapse(segment2, 1, .5)
    connections.createSynapse(segment2, 2, .5)
    connections.createSynapse(segment2, 3, .5)
    connections.startNewIteration();

    # Give the first segment some activity.
    connections.recordSegmentActivity(segment1)

    # Create a new segment with 1 synapse.
    segment3 = connections.createSegment(42);
    connections.createSynapse(segment3, 1, .5)

    segments = connections.segmentsForCell(42)
    self.assertEqual(2, len(segments))

    # Verify first segment is still there with the same synapses.
    self.assertEqual(set([1, 2]),
                     set(synapse.presynapticCell for synapse in
                         connections.synapsesForSegment(segments[0])))

    # Verify second segment has been replaced.
    self.assertEqual(set([1]),
                     set(synapse.presynapticCell for synapse in
                         connections.synapsesForSegment(segments[1])))

    # Verify the flatIdxs were properly reused.
    self.assertLess(segment1.flatIdx, 2)
    self.assertLess(segment3.flatIdx, 2)
    self.assertTrue(segment1 is connections.segmentForFlatIdx(segment1.flatIdx))
    self.assertTrue(segment3 is connections.segmentForFlatIdx(segment3.flatIdx))
Esempio n. 19
0
    def testCreateSegmentReuse(self):
        connections = Connections(1024, 2)

        segment1 = connections.createSegment(42)
        connections.createSynapse(segment1, 1, .5)
        connections.createSynapse(segment1, 2, .5)

        # Let some time pass.
        connections.startNewIteration()
        connections.startNewIteration()
        connections.startNewIteration()

        segment2 = connections.createSegment(42)
        connections.startNewIteration()

        connections.recordSegmentActivity(segment1)

        segment3 = connections.createSegment(42)

        self.assertEqual(segment2.idx, segment3.idx)
Esempio n. 20
0
  def testCreateSegmentReuse(self):
    connections = Connections(1024, 2)

    segment1 = connections.createSegment(42)
    connections.createSynapse(segment1, 1, .5)
    connections.createSynapse(segment1, 2, .5)

    # Let some time pass.
    connections.startNewIteration();
    connections.startNewIteration();
    connections.startNewIteration();

    segment2 = connections.createSegment(42)
    connections.startNewIteration();

    connections.recordSegmentActivity(segment1)

    segment3 = connections.createSegment(42);

    self.assertEqual(segment2.idx, segment3.idx)
Esempio n. 21
0
  def testUpdateSynapsePermanence(self):
    """ Creates a synapse and updates its permanence, and makes sure that its
        data was correctly updated.
    """
    connections = Connections(1024)
    segment = connections.createSegment(10)
    synapse = connections.createSynapse(segment, 50, .34)

    connections.updateSynapsePermanence(synapse, .21)

    synapseData = connections.dataForSynapse(synapse)
    self.assertAlmostEqual(synapseData.permanence, .21)
Esempio n. 22
0
    def testUpdateSynapsePermanence(self):
        """ Creates a synapse and updates its permanence, and makes sure that its
        data was correctly updated.
    """
        connections = Connections(1024)
        segment = connections.createSegment(10)
        synapse = connections.createSynapse(segment, 50, .34)

        connections.updateSynapsePermanence(synapse, .21)

        synapseData = connections.dataForSynapse(synapse)
        self.assertAlmostEqual(synapseData.permanence, .21)
Esempio n. 23
0
    def testReuseSegmentWithDestroyedSynapses(self):
        """ Destroy a segment that has a destroyed synapse and a non-destroyed
        synapse. Create a new segment in the same place. Make sure its synapse
        count is correct.
    """
        connections = Connections(1024)

        segment = connections.createSegment(11)

        synapse1 = connections.createSynapse(segment, 201, .85)
        connections.createSynapse(segment, 202, .85)

        connections.destroySynapse(synapse1)

        self.assertEqual(1, connections.numSynapses(segment))

        connections.destroySegment(segment)

        reincarnated = connections.createSegment(11)

        self.assertEqual(0, connections.numSynapses(reincarnated))
        self.assertEqual(0, len(connections.synapsesForSegment(reincarnated)))
Esempio n. 24
0
  def testReuseSegmentWithDestroyedSynapses(self):
    """ Destroy a segment that has a destroyed synapse and a non-destroyed
        synapse. Create a new segment in the same place. Make sure its synapse
        count is correct.
    """
    connections = Connections(1024)

    segment = connections.createSegment(11)

    synapse1 = connections.createSynapse(segment, 201, .85)
    connections.createSynapse(segment, 202, .85)

    connections.destroySynapse(synapse1)

    self.assertEqual(1, connections.numSynapses(segment))

    connections.destroySegment(segment)

    reincarnated = connections.createSegment(11)

    self.assertEqual(0, connections.numSynapses(reincarnated))
    self.assertEqual(0, len(connections.synapsesForSegment(reincarnated)))
Esempio n. 25
0
  def testReachSegmentLimitMultipleTimes(self):
    """ Hit the maxSynapsesPerSegment threshold multiple times. Make sure it
        works more than once.
    """
    connections = Connections(1024, 2, 2)

    segment = connections.createSegment(10)
    connections.createSynapse(segment, 201, .85)
    self.assertEqual(1, connections.numSynapses())
    connections.createSynapse(segment, 202, .9)
    self.assertEqual(2, connections.numSynapses())
    connections.createSynapse(segment, 203, .8)
    self.assertEqual(2, connections.numSynapses())
    synapse = connections.createSynapse(segment, 204, .8)
    self.assertEqual(2, connections.numSynapses())
Esempio n. 26
0
    def testReachSegmentLimitMultipleTimes(self):
        """ Hit the maxSynapsesPerSegment threshold multiple times. Make sure it
        works more than once.
    """
        connections = Connections(1024, 2, 2)

        segment = connections.createSegment(10)
        connections.createSynapse(segment, 201, .85)
        self.assertEqual(1, connections.numSynapses())
        connections.createSynapse(segment, 202, .9)
        self.assertEqual(2, connections.numSynapses())
        connections.createSynapse(segment, 203, .8)
        self.assertEqual(2, connections.numSynapses())
        synapse = connections.createSynapse(segment, 204, .8)
        self.assertEqual(2, connections.numSynapses())
Esempio n. 27
0
  def testDestroySynapse(self):
    """ Creates a segment, creates a number of synapses on it, destroys a
        synapse, and makes sure it got destroyed.
    """
    connections = Connections(1024)

    segment = connections.createSegment(20)
    synapse1 = connections.createSynapse(segment, 80, .85)
    synapse2 = connections.createSynapse(segment, 81, .85)
    synapse3 = connections.createSynapse(segment, 82, .15)

    self.assertEqual(3, connections.numSynapses())

    connections.destroySynapse(synapse2)

    self.assertEqual(2, connections.numSynapses())
    self.assertEqual(connections.synapsesForSegment(segment), [synapse1,
                                                               synapse3])
    active, matching = connections.computeActivity([80, 81, 82], .5, 2, 0.0, 1)
    self.assertEqual(0, len(active))
    self.assertEqual(1, len(matching))
    self.assertEqual(2, matching[0].overlap)
Esempio n. 28
0
  def testSynapseReuse(self):
    """ Creates a synapse over the synapses per segment limit, and verifies
        that the lowest permanence synapse is removed to make room for the new
        synapse.
    """
    connections = Connections(1024, 1024, 2)
    segment = connections.createSegment(10)

    synapse1 = connections.createSynapse(segment, 50, .34)
    synapse2 = connections.createSynapse(segment, 51, .34)

    synapses = connections.synapsesForSegment(segment)
    self.assertEqual(synapses, [synapse1, synapse2])

    #Add an additional synapse to force it over the limit of num synapses
    #per segment.
    synapse3 = connections.createSynapse(segment, 52, .52)
    self.assertEqual(0, synapse3.idx)

    #ensure lower permanence synapse was removed
    synapses = connections.synapsesForSegment(segment)
    self.assertEqual(synapses, [synapse3, synapse2])
Esempio n. 29
0
  def testDestroySynapsesThenReachLimit(self):
    """ Destroy some synapses then verify that the maxSynapsesPerSegment is
        still correctly applied.
    """
    connections = Connections(1024, 2, 2)

    segment = connections.createSegment(10)

    synapse1 = connections.createSynapse(segment, 201, .85)
    synapse2 = connections.createSynapse(segment, 202, .85)

    self.assertEqual(2, connections.numSynapses())
    connections.destroySynapse(synapse1)
    connections.destroySynapse(synapse2)
    self.assertEqual(0, connections.numSynapses())

    connections.createSynapse(segment, 201, .85)
    self.assertEqual(1, connections.numSynapses())
    connections.createSynapse(segment, 202, .90)
    self.assertEqual(2, connections.numSynapses())
    synapse3 = connections.createSynapse(segment, 203, .8)
    self.assertEqual(2, connections.numSynapses())
Esempio n. 30
0
    def testDestroySynapsesThenReachLimit(self):
        """ Destroy some synapses then verify that the maxSynapsesPerSegment is
        still correctly applied.
    """
        connections = Connections(1024, 2, 2)

        segment = connections.createSegment(10)

        synapse1 = connections.createSynapse(segment, 201, .85)
        synapse2 = connections.createSynapse(segment, 202, .85)

        self.assertEqual(2, connections.numSynapses())
        connections.destroySynapse(synapse1)
        connections.destroySynapse(synapse2)
        self.assertEqual(0, connections.numSynapses())

        connections.createSynapse(segment, 201, .85)
        self.assertEqual(1, connections.numSynapses())
        connections.createSynapse(segment, 202, .90)
        self.assertEqual(2, connections.numSynapses())
        synapse3 = connections.createSynapse(segment, 203, .8)
        self.assertEqual(2, connections.numSynapses())
Esempio n. 31
0
    def testSynapseReuse(self):
        """ Creates a synapse over the synapses per segment limit, and verifies
        that the lowest permanence synapse is removed to make room for the new
        synapse.
    """
        connections = Connections(1024, 1024, 2)
        segment = connections.createSegment(10)

        synapse1 = connections.createSynapse(segment, 50, .34)
        synapse2 = connections.createSynapse(segment, 51, .34)

        synapses = connections.synapsesForSegment(segment)
        self.assertEqual(synapses, [synapse1, synapse2])

        #Add an additional synapse to force it over the limit of num synapses
        #per segment.
        synapse3 = connections.createSynapse(segment, 52, .52)
        self.assertEqual(0, synapse3.idx)

        #ensure lower permanence synapse was removed
        synapses = connections.synapsesForSegment(segment)
        self.assertEqual(synapses, [synapse3, synapse2])
Esempio n. 32
0
  def testSynapseReuse(self):
    """ Creates a synapse over the synapses per segment limit, and verifies
        that the lowest permanence synapse is removed to make room for the new
        synapse.
    """
    connections = Connections(1024, 1024, 2)
    segment = connections.createSegment(10)

    synapse1 = connections.createSynapse(segment, 50, .34)
    synapse2 = connections.createSynapse(segment, 51, .48)

    synapses = connections.synapsesForSegment(segment)
    self.assertEqual(set([synapse1, synapse2]), synapses)

    # Add an additional synapse to force it over the limit of num synapses
    # per segment.
    connections.createSynapse(segment, 52, .52)

    # Ensure lower permanence synapse was removed.
    self.assertEqual(set([51, 52]),
                     set(synapse.presynapticCell
                         for synapse in
                         connections.synapsesForSegment(segment)))
Esempio n. 33
0
  def testDestroySynapse(self):
    """ Creates a segment, creates a number of synapses on it, destroys a
        synapse, and makes sure it got destroyed.
    """
    connections = Connections(1024)

    segment = connections.createSegment(20)
    synapse1 = connections.createSynapse(segment, 80, .85)
    synapse2 = connections.createSynapse(segment, 81, .85)
    synapse3 = connections.createSynapse(segment, 82, .15)

    self.assertEqual(3, connections.numSynapses())

    connections.destroySynapse(synapse2)

    self.assertEqual(2, connections.numSynapses())
    self.assertEqual(set([synapse1, synapse3]),
                     connections.synapsesForSegment(segment))
    (numActiveConnected,
     numActivePotential) = connections.computeActivity([80, 81, 82], .5)

    self.assertEqual(1, numActiveConnected[segment.flatIdx])
    self.assertEqual(2, numActivePotential[segment.flatIdx])
Esempio n. 34
0
    def testDestroySynapse(self):
        """ Creates a segment, creates a number of synapses on it, destroys a
        synapse, and makes sure it got destroyed.
    """
        connections = Connections(1024)

        segment = connections.createSegment(20)
        synapse1 = connections.createSynapse(segment, 80, .85)
        synapse2 = connections.createSynapse(segment, 81, .85)
        synapse3 = connections.createSynapse(segment, 82, .15)

        self.assertEqual(3, connections.numSynapses())

        connections.destroySynapse(synapse2)

        self.assertEqual(2, connections.numSynapses())
        self.assertEqual(set([synapse1, synapse3]),
                         connections.synapsesForSegment(segment))
        (numActiveConnected,
         numActivePotential) = connections.computeActivity([80, 81, 82], .5)

        self.assertEqual(1, numActiveConnected[segment.flatIdx])
        self.assertEqual(2, numActivePotential[segment.flatIdx])
Esempio n. 35
0
    def testDestroySynapse(self):
        """ Creates a segment, creates a number of synapses on it, destroys a
        synapse, and makes sure it got destroyed.
    """
        connections = Connections(1024)

        segment = connections.createSegment(20)
        synapse1 = connections.createSynapse(segment, 80, .85)
        synapse2 = connections.createSynapse(segment, 81, .85)
        synapse3 = connections.createSynapse(segment, 82, .15)

        self.assertEqual(3, connections.numSynapses())

        connections.destroySynapse(synapse2)

        self.assertEqual(2, connections.numSynapses())
        self.assertEqual(connections.synapsesForSegment(segment),
                         [synapse1, synapse3])
        active, matching = connections.computeActivity([80, 81, 82], .5, 2,
                                                       0.0, 1)
        self.assertEqual(0, len(active))
        self.assertEqual(1, len(matching))
        self.assertEqual(2, matching[0].overlap)
Esempio n. 36
0
    def testSynapseReuse(self):
        """ Creates a synapse over the synapses per segment limit, and verifies
        that the lowest permanence synapse is removed to make room for the new
        synapse.
    """
        connections = Connections(1024, 1024, 2)
        segment = connections.createSegment(10)

        synapse1 = connections.createSynapse(segment, 50, .34)
        synapse2 = connections.createSynapse(segment, 51, .48)

        synapses = connections.synapsesForSegment(segment)
        self.assertEqual(set([synapse1, synapse2]), synapses)

        # Add an additional synapse to force it over the limit of num synapses
        # per segment.
        connections.createSynapse(segment, 52, .52)

        # Ensure lower permanence synapse was removed.
        self.assertEqual(
            set([51, 52]),
            set(synapse.presynapticCell
                for synapse in connections.synapsesForSegment(segment)))
Esempio n. 37
0
  def testDestroySegmentWithDestroyedSynapses(self):
    """ Destroy a segment that has a destroyed synapse and a non-destroyed
        synapse. Make sure nothing gets double-destroyed.
    """
    connections = Connections(1024)

    segment1 = connections.createSegment(11)
    segment2 = connections.createSegment(12)

    connections.createSynapse(segment1, 101, .85)
    synapse2a = connections.createSynapse(segment2, 201, .85)
    connections.createSynapse(segment2, 202, .85)

    self.assertEqual(3, connections.numSynapses())

    connections.destroySynapse(synapse2a)

    self.assertEqual(2, connections.numSegments())
    self.assertEqual(2, connections.numSynapses())

    connections.destroySegment(segment2)

    self.assertEqual(1, connections.numSegments())
    self.assertEqual(1, connections.numSynapses())
Esempio n. 38
0
    def testDestroySegmentWithDestroyedSynapses(self):
        """ Destroy a segment that has a destroyed synapse and a non-destroyed
        synapse. Make sure nothing gets double-destroyed.
    """
        connections = Connections(1024)

        segment1 = connections.createSegment(11)
        segment2 = connections.createSegment(12)

        connections.createSynapse(segment1, 101, .85)
        synapse2a = connections.createSynapse(segment2, 201, .85)
        connections.createSynapse(segment2, 202, .85)

        self.assertEqual(3, connections.numSynapses())

        connections.destroySynapse(synapse2a)

        self.assertEqual(2, connections.numSegments())
        self.assertEqual(2, connections.numSynapses())

        connections.destroySegment(segment2)

        self.assertEqual(1, connections.numSegments())
        self.assertEqual(1, connections.numSynapses())
Esempio n. 39
0
  def testDestroySegmentsThenReachLimit(self):
    """ Destroy some segments then verify that the maxSegmentsPerCell is still
        correctly applied.
    """
    connections = Connections(1024, 2, 2)

    segment1 = connections.createSegment(11)
    segment2 = connections.createSegment(11)

    self.assertEqual(2, connections.numSegments())
    connections.destroySegment(segment1)
    connections.destroySegment(segment2)
    self.assertEqual(0, connections.numSegments())

    connections.createSegment(11)
    self.assertEqual(1, connections.numSegments())
    connections.createSegment(11)
    self.assertEqual(2, connections.numSegments())
    segment3 = connections.createSegment(11)
    self.assertEqual(2, connections.numSegments(11))
    self.assertEqual(2, connections.numSegments())
Esempio n. 40
0
    def testDestroySegmentsThenReachLimit(self):
        """ Destroy some segments then verify that the maxSegmentsPerCell is still
        correctly applied.
    """
        connections = Connections(1024, 2, 2)

        segment1 = connections.createSegment(11)
        segment2 = connections.createSegment(11)

        self.assertEqual(2, connections.numSegments())
        connections.destroySegment(segment1)
        connections.destroySegment(segment2)
        self.assertEqual(0, connections.numSegments())

        connections.createSegment(11)
        self.assertEqual(1, connections.numSegments())
        connections.createSegment(11)
        self.assertEqual(2, connections.numSegments())
        segment3 = connections.createSegment(11)
        self.assertLess(segment3.idx, 2)
        self.assertEqual(2, connections.numSegments())
Esempio n. 41
0
class TemporalMemory(object):
    """
  Class implementing the Temporal Memory algorithm.
  """
    def __init__(self,
                 columnDimensions=(2048, ),
                 cellsPerColumn=32,
                 activationThreshold=13,
                 initialPermanence=0.21,
                 connectedPermanence=0.50,
                 minThreshold=10,
                 maxNewSynapseCount=20,
                 permanenceIncrement=0.10,
                 permanenceDecrement=0.10,
                 predictedSegmentDecrement=0.0,
                 seed=42):
        """
    @param columnDimensions          (list)  Dimensions of the column space
    @param cellsPerColumn            (int)   Number of cells per column
    @param activationThreshold       (int)   If the number of active connected synapses on a segment is at least this threshold, the segment is said to be active.
    @param initialPermanence         (float) Initial permanence of a new synapse.
    @param connectedPermanence       (float) If the permanence value for a synapse is greater than this value, it is said to be connected.
    @param minThreshold              (int)   If the number of synapses active on a segment is at least this threshold, it is selected as the best matching cell in a bursting column.
    @param maxNewSynapseCount        (int)   The maximum number of synapses added to a segment during learning.
    @param permanenceIncrement       (float) Amount by which permanences of synapses are incremented during learning.
    @param permanenceDecrement       (float) Amount by which permanences of synapses are decremented during learning.
    @param predictedSegmentDecrement (float) Amount by which active permanences of synapses of previously predicted but inactive segments are decremented.
    @param seed                      (int)   Seed for the random number generator.

    Notes:

    predictedSegmentDecrement: A good value is just a bit larger than
    (the column-level sparsity * permanenceIncrement). So, if column-level
    sparsity is 2% and permanenceIncrement is 0.01, this parameter should be
    something like 4% * 0.01 = 0.0004).
    """
        # Error checking
        if not len(columnDimensions):
            raise ValueError(
                "Number of column dimensions must be greater than 0")

        if not cellsPerColumn > 0:
            raise ValueError(
                "Number of cells per column must be greater than 0")

        # TODO: Validate all parameters (and add validation tests)

        # Save member variables
        self.columnDimensions = columnDimensions
        self.cellsPerColumn = cellsPerColumn
        self.activationThreshold = activationThreshold
        self.initialPermanence = initialPermanence
        self.connectedPermanence = connectedPermanence
        self.minThreshold = minThreshold
        self.maxNewSynapseCount = maxNewSynapseCount
        self.permanenceIncrement = permanenceIncrement
        self.permanenceDecrement = permanenceDecrement
        self.predictedSegmentDecrement = predictedSegmentDecrement
        # Initialize member variables
        self.connections = Connections(self.numberOfCells())
        self._random = Random(seed)

        self.activeCells = set()
        self.predictiveCells = set()
        self.activeSegments = set()
        self.winnerCells = set()
        self.matchingSegments = set()
        self.matchingCells = set()

    # ==============================
    # Main functions
    # ==============================

    def compute(self, activeColumns, learn=True):
        """
    Feeds input record through TM, performing inference and learning.

    @param activeColumns (set)  Indices of active columns
    @param learn         (bool) Whether or not learning is enabled

    Updates member variables:
      - `activeCells`     (set)
      - `winnerCells`     (set)
      - `activeSegments`  (set)
      - `predictiveCells` (set)
      - `matchingSegments`(set)
      - `matchingCells`   (set)
      - `connections`     (Connections)
    """
        prevPredictiveCells = self.predictiveCells
        prevActiveSegments = self.activeSegments
        prevActiveCells = self.activeCells
        prevWinnerCells = self.winnerCells
        prevMatchingSegments = self.matchingSegments
        prevMatchingCells = self.matchingCells

        activeCells = set()
        winnerCells = set()

        (_activeCells, _winnerCells, predictedActiveColumns,
         predictedInactiveCells) = self.activateCorrectlyPredictiveCells(
             prevPredictiveCells, prevMatchingCells, activeColumns)

        activeCells.update(_activeCells)
        winnerCells.update(_winnerCells)

        (_activeCells, _winnerCells, learningSegments) = self.burstColumns(
            activeColumns, predictedActiveColumns, prevActiveCells,
            prevWinnerCells)

        activeCells.update(_activeCells)
        winnerCells.update(_winnerCells)

        if learn:
            self.learnOnSegments(prevActiveSegments, learningSegments,
                                 prevActiveCells, winnerCells, prevWinnerCells,
                                 predictedInactiveCells, prevMatchingSegments)

        (activeSegments, predictiveCells, matchingSegments,
         matchingCells) = self.computePredictiveCells(activeCells)

        self.activeCells = activeCells
        self.winnerCells = winnerCells
        self.activeSegments = activeSegments
        self.predictiveCells = predictiveCells
        self.matchingSegments = matchingSegments
        self.matchingCells = matchingCells

    def reset(self):
        """
    Indicates the start of a new sequence. Resets sequence state of the TM.
    """
        self.activeCells = set()
        self.predictiveCells = set()
        self.activeSegments = set()
        self.winnerCells = set()

    # ==============================
    # Phases
    # ==============================

    def activateCorrectlyPredictiveCells(self, prevPredictiveCells,
                                         prevMatchingCells, activeColumns):
        """
    Phase 1: Activate the correctly predictive cells.

    Pseudocode:

      - for each prev predictive cell
        - if in active column
          - mark it as active
          - mark it as winner cell
          - mark column as predicted => active
        - if not in active column
          - mark it as an predicted but inactive cell

    @param prevPredictiveCells (set) Indices of predictive cells in `t-1`
    @param activeColumns       (set) Indices of active columns in `t`

    @return (tuple) Contains:
                      `activeCells`               (set),
                      `winnerCells`               (set),
                      `predictedActiveColumns`    (set),
                      `predictedInactiveCells`    (set)
    """
        activeCells = set()
        winnerCells = set()
        predictedActiveColumns = set()
        predictedInactiveCells = set()

        for cell in prevPredictiveCells:
            column = self.columnForCell(cell)

            if column in activeColumns:
                activeCells.add(cell)
                winnerCells.add(cell)
                predictedActiveColumns.add(column)

        if self.predictedSegmentDecrement > 0:
            for cell in prevMatchingCells:
                column = self.columnForCell(cell)

                if column not in activeColumns:
                    predictedInactiveCells.add(cell)

        return (activeCells, winnerCells, predictedActiveColumns,
                predictedInactiveCells)

    def burstColumns(self, activeColumns, predictedActiveColumns,
                     prevActiveCells, prevWinnerCells):
        """
    Phase 2: Burst unpredicted columns.

    Pseudocode:

      - for each unpredicted active column
        - mark all cells as active
        - mark the best matching cell as winner cell
          - (learning)
            - if it has no matching segment
              - (optimization) if there are prev winner cells
                - add a segment to it
            - mark the segment as learning

    @param activeColumns                   (set)         Indices of active columns in `t`
    @param predictedActiveColumns          (set)         Indices of predicted => active columns in `t`
    @param prevActiveCells                 (set)         Indices of active cells in `t-1`
    @param prevWinnerCells                 (set)         Indices of winner cells in `t-1`

    @return (tuple) Contains:
                      `activeCells`      (set),
                      `winnerCells`      (set),
                      `learningSegments` (set)
    """
        activeCells = set()
        winnerCells = set()
        learningSegments = set()

        unpredictedActiveColumns = activeColumns - predictedActiveColumns

        for column in unpredictedActiveColumns:
            cells = self.cellsForColumn(column)
            activeCells.update(cells)

            (bestCell,
             bestSegment) = self.bestMatchingCell(cells, prevActiveCells)
            winnerCells.add(bestCell)

            if bestSegment is None and len(prevWinnerCells):
                bestSegment = self.connections.createSegment(bestCell)

            if bestSegment is not None:
                learningSegments.add(bestSegment)

        return activeCells, winnerCells, learningSegments

    def learnOnSegments(self, prevActiveSegments, learningSegments,
                        prevActiveCells, winnerCells, prevWinnerCells,
                        predictedInactiveCells, prevMatchingSegments):
        """
    Phase 3: Perform learning by adapting segments.

    Pseudocode:

      - (learning) for each prev active or learning segment
        - if learning segment or from winner cell
          - strengthen active synapses
          - weaken inactive synapses
        - if learning segment
          - add some synapses to the segment
            - subsample from prev winner cells

      - if predictedSegmentDecrement > 0
        - for each previously matching segment
          - if cell is a predicted inactive cell
            - weaken active synapses but don't touch inactive synapses

    @param prevActiveSegments           (set)         Indices of active segments in `t-1`
    @param learningSegments             (set)         Indices of learning segments in `t`
    @param prevActiveCells              (set)         Indices of active cells in `t-1`
    @param winnerCells                  (set)         Indices of winner cells in `t`
    @param prevWinnerCells              (set)         Indices of winner cells in `t-1`
    @param predictedInactiveCells       (set)         Indices of predicted inactive cells
    @param prevMatchingSegments         (set)         Indices of segments with
    """
        for segment in prevActiveSegments | learningSegments:
            isLearningSegment = segment in learningSegments
            isFromWinnerCell = self.connections.cellForSegment(
                segment) in winnerCells

            activeSynapses = self.activeSynapsesForSegment(
                segment, prevActiveCells)

            if isLearningSegment or isFromWinnerCell:
                self.adaptSegment(segment, activeSynapses,
                                  self.permanenceIncrement,
                                  self.permanenceDecrement)

            if isLearningSegment:
                n = self.maxNewSynapseCount - len(activeSynapses)

                for presynapticCell in self.pickCellsToLearnOn(
                        n, segment, prevWinnerCells):
                    self.connections.createSynapse(segment, presynapticCell,
                                                   self.initialPermanence)

        if self.predictedSegmentDecrement > 0:
            for segment in prevMatchingSegments:
                isPredictedInactiveCell = (
                    self.connections.cellForSegment(segment)
                    in predictedInactiveCells)
                activeSynapses = self.activeSynapsesForSegment(
                    segment, prevActiveCells)

                if isPredictedInactiveCell:
                    self.adaptSegment(segment, activeSynapses,
                                      -self.predictedSegmentDecrement, 0.0)

    def computePredictiveCells(self, activeCells):
        """
    Phase 4: Compute predictive cells due to lateral input
    on distal dendrites.

    Pseudocode:

      - for each distal dendrite segment with activity >= activationThreshold
        - mark the segment as active
        - mark the cell as predictive

      - if predictedSegmentDecrement > 0
        - for each distal dendrite segment with unconnected
          activity >=  minThreshold
          - mark the segment as matching
          - mark the cell as matching

    Forward propagates activity from active cells to the synapses that touch
    them, to determine which synapses are active.

    @param activeCells (set)         Indices of active cells in `t`

    @return (tuple) Contains:
                      `activeSegments`  (set),
                      `predictiveCells` (set),
                      `matchingSegments` (set),
                      `matchingCells`    (set)
    """
        numActiveConnectedSynapsesForSegment = defaultdict(int)
        numActiveSynapsesForSegment = defaultdict(int)
        activeSegments = set()
        predictiveCells = set()

        matchingSegments = set()
        matchingCells = set()

        for cell in activeCells:
            synapses = self.connections.synapsesForPresynapticCell(cell)

            for synapseData in synapses.values():
                segment = synapseData.segment
                permanence = synapseData.permanence

                if permanence >= self.connectedPermanence:
                    numActiveConnectedSynapsesForSegment[segment] += 1

                    if (numActiveConnectedSynapsesForSegment[segment] >=
                            self.activationThreshold):
                        activeSegments.add(segment)
                        predictiveCells.add(
                            self.connections.cellForSegment(segment))

                if permanence > 0 and self.predictedSegmentDecrement > 0:
                    numActiveSynapsesForSegment[segment] += 1

                    if numActiveSynapsesForSegment[
                            segment] >= self.minThreshold:
                        matchingSegments.add(segment)
                        matchingCells.add(
                            self.connections.cellForSegment(segment))

        return activeSegments, predictiveCells, matchingSegments, matchingCells

    # ==============================
    # Helper functions
    # ==============================

    def bestMatchingCell(self, cells, activeCells):
        """
    Gets the cell with the best matching segment
    (see `TM.bestMatchingSegment`) that has the largest number of active
    synapses of all best matching segments.

    If none were found, pick the least used cell (see `TM.leastUsedCell`).

    @param cells       (set) Indices of cells
    @param activeCells (set) Indices of active cells

    @return (tuple) Contains:
                      `cell`        (int),
                      `bestSegment` (int)
    """
        maxSynapses = 0
        bestCell = None
        bestSegment = None

        for cell in cells:
            segment, numActiveSynapses = self.bestMatchingSegment(
                cell, activeCells)

            if segment is not None and numActiveSynapses > maxSynapses:
                maxSynapses = numActiveSynapses
                bestCell = cell
                bestSegment = segment

        if bestCell is None:
            bestCell = self.leastUsedCell(cells)

        return bestCell, bestSegment

    def bestMatchingSegment(self, cell, activeCells):
        """
    Gets the segment on a cell with the largest number of activate synapses,
    including all synapses with non-zero permanences.

    @param cell        (int) Cell index
    @param activeCells (set) Indices of active cells

    @return (tuple) Contains:
                      `segment`                 (int),
                      `connectedActiveSynapses` (set)
    """
        maxSynapses = self.minThreshold
        bestSegment = None
        bestNumActiveSynapses = None

        for segment in self.connections.segmentsForCell(cell):
            numActiveSynapses = 0

            for synapse in self.connections.synapsesForSegment(segment):
                synapseData = self.connections.dataForSynapse(synapse)
                if ((synapseData.presynapticCell in activeCells)
                        and synapseData.permanence > 0):
                    numActiveSynapses += 1

            if numActiveSynapses >= maxSynapses:
                maxSynapses = numActiveSynapses
                bestSegment = segment
                bestNumActiveSynapses = numActiveSynapses

        return bestSegment, bestNumActiveSynapses

    def leastUsedCell(self, cells):
        """
    Gets the cell with the smallest number of segments.
    Break ties randomly.

    @param cells (set) Indices of cells

    @return (int) Cell index
    """
        leastUsedCells = set()
        minNumSegments = float("inf")

        for cell in cells:
            numSegments = len(self.connections.segmentsForCell(cell))

            if numSegments < minNumSegments:
                minNumSegments = numSegments
                leastUsedCells = set()

            if numSegments == minNumSegments:
                leastUsedCells.add(cell)

        i = self._random.getUInt32(len(leastUsedCells))
        return sorted(leastUsedCells)[i]

    def activeSynapsesForSegment(self, segment, activeCells):
        """
    Returns the synapses on a segment that are active due to lateral input
    from active cells.

    @param segment     (int)         Segment index
    @param activeCells (set)         Indices of active cells

    @return (set) Indices of active synapses on segment
    """
        synapses = set()

        for synapse in self.connections.synapsesForSegment(segment):
            synapseData = self.connections.dataForSynapse(synapse)

            if synapseData.presynapticCell in activeCells:
                synapses.add(synapse)

        return synapses

    def adaptSegment(self, segment, activeSynapses, permanenceIncrement,
                     permanenceDecrement):
        """
    Updates synapses on segment.
    Strengthens active synapses; weakens inactive synapses.

    @param segment              (int)    Segment index
    @param activeSynapses       (set)    Indices of active synapses
    @param permanenceIncrement  (float)  Amount to increment active synapses
    @param permanenceDecrement  (float)  Amount to decrement inactive synapses
    """
        # Need to copy synapses for segment set below because it will be modified
        # during iteration by `destroySynapse`
        for synapse in set(self.connections.synapsesForSegment(segment)):
            synapseData = self.connections.dataForSynapse(synapse)
            permanence = synapseData.permanence

            if synapse in activeSynapses:
                permanence += permanenceIncrement
            else:
                permanence -= permanenceDecrement

            # Keep permanence within min/max bounds
            permanence = max(0.0, min(1.0, permanence))

            if (abs(permanence) < EPSILON):
                self.connections.destroySynapse(synapse)
            else:
                self.connections.updateSynapsePermanence(synapse, permanence)

    def pickCellsToLearnOn(self, n, segment, winnerCells):
        """
    Pick cells to form distal connections to.

    TODO: Respect topology and learningRadius

    @param n           (int) Number of cells to pick
    @param segment     (int) Segment index
    @param winnerCells (set) Indices of winner cells in `t`

    @return (set) Indices of cells picked
    """
        candidates = set(winnerCells)

        # Remove cells that are already synapsed on by this segment
        for synapse in self.connections.synapsesForSegment(segment):
            synapseData = self.connections.dataForSynapse(synapse)
            presynapticCell = synapseData.presynapticCell

            if presynapticCell in candidates:
                candidates.remove(presynapticCell)

        n = min(n, len(candidates))
        candidates = sorted(candidates)
        cells = set()

        # Pick n cells randomly
        for _ in range(n):
            i = self._random.getUInt32(len(candidates))
            cells.add(candidates[i])
            del candidates[i]

        return cells

    def columnForCell(self, cell):
        """
    Returns the index of the column that a cell belongs to.

    @param cell (int) Cell index

    @return (int) Column index
    """
        self._validateCell(cell)

        return int(cell / self.cellsPerColumn)

    def cellsForColumn(self, column):
        """
    Returns the indices of cells that belong to a column.

    @param column (int) Column index

    @return (set) Cell indices
    """
        self._validateColumn(column)

        start = self.cellsPerColumn * self.getCellIndex(column)
        end = start + self.cellsPerColumn
        return set(xrange(start, end))

    def numberOfColumns(self):
        """
    Returns the number of columns in this layer.

    @return (int) Number of columns
    """
        return reduce(mul, self.columnDimensions, 1)

    def numberOfCells(self):
        """
    Returns the number of cells in this layer.

    @return (int) Number of cells
    """
        return self.numberOfColumns() * self.cellsPerColumn

    def mapCellsToColumns(self, cells):
        """
    Maps cells to the columns they belong to

    @param cells (set) Cells

    @return (dict) Mapping from columns to their cells in `cells`
    """
        cellsForColumns = defaultdict(set)

        for cell in cells:
            column = self.columnForCell(cell)
            cellsForColumns[column].add(cell)

        return cellsForColumns

    def write(self, proto):
        """
    Writes serialized data to proto object

    @param proto (DynamicStructBuilder) Proto object
    """
        proto.columnDimensions = self.columnDimensions
        proto.cellsPerColumn = self.cellsPerColumn
        proto.activationThreshold = self.activationThreshold
        proto.initialPermanence = self.initialPermanence
        proto.connectedPermanence = self.connectedPermanence
        proto.minThreshold = self.minThreshold
        proto.maxNewSynapseCount = self.maxNewSynapseCount
        proto.permanenceIncrement = self.permanenceIncrement
        proto.permanenceDecrement = self.permanenceDecrement
        proto.predictedSegmentDecrement = self.predictedSegmentDecrement

        self.connections.write(proto.connections)
        self._random.write(proto.random)

        proto.activeCells = list(self.activeCells)
        proto.predictiveCells = list(self.predictiveCells)
        proto.activeSegments = list(self.activeSegments)
        proto.winnerCells = list(self.winnerCells)
        proto.matchingSegments = list(self.matchingSegments)
        proto.matchingCells = list(self.matchingCells)

    @classmethod
    def read(cls, proto):
        """
    Reads deserialized data from proto object

    @param proto (DynamicStructBuilder) Proto object

    @return (TemporalMemory) TemporalMemory instance
    """
        tm = object.__new__(cls)

        tm.columnDimensions = list(proto.columnDimensions)
        tm.cellsPerColumn = int(proto.cellsPerColumn)
        tm.activationThreshold = int(proto.activationThreshold)
        tm.initialPermanence = proto.initialPermanence
        tm.connectedPermanence = proto.connectedPermanence
        tm.minThreshold = int(proto.minThreshold)
        tm.maxNewSynapseCount = int(proto.maxNewSynapseCount)
        tm.permanenceIncrement = proto.permanenceIncrement
        tm.permanenceDecrement = proto.permanenceDecrement
        tm.predictedSegmentDecrement = proto.predictedSegmentDecrement

        tm.connections = Connections.read(proto.connections)
        tm._random = Random()
        tm._random.read(proto.random)

        tm.activeCells = set([int(x) for x in proto.activeCells])
        tm.predictiveCells = set([int(x) for x in proto.predictiveCells])
        tm.activeSegments = set([int(x) for x in proto.activeSegments])
        tm.winnerCells = set([int(x) for x in proto.winnerCells])
        tm.matchingSegments = set([int(x) for x in proto.matchingSegments])
        tm.matchingCells = set([int(x) for x in proto.matchingCells])

        return tm

    def __eq__(self, other):
        """
    Equality operator for TemporalMemory instances.
    Checks if two instances are functionally identical
    (might have different internal state).

    @param other (TemporalMemory) TemporalMemory instance to compare to
    """
        if self.columnDimensions != other.columnDimensions: return False
        if self.cellsPerColumn != other.cellsPerColumn: return False
        if self.activationThreshold != other.activationThreshold: return False
        if abs(self.initialPermanence - other.initialPermanence) > EPSILON:
            return False
        if abs(self.connectedPermanence - other.connectedPermanence) > EPSILON:
            return False
        if self.minThreshold != other.minThreshold: return False
        if self.maxNewSynapseCount != other.maxNewSynapseCount: return False
        if abs(self.permanenceIncrement - other.permanenceIncrement) > EPSILON:
            return False
        if abs(self.permanenceDecrement - other.permanenceDecrement) > EPSILON:
            return False
        if abs(self.predictedSegmentDecrement -
               other.predictedSegmentDecrement) > EPSILON:
            return False

        if self.connections != other.connections: return False

        if self.activeCells != other.activeCells: return False
        if self.predictiveCells != other.predictiveCells: return False
        if self.winnerCells != other.winnerCells: return False
        if self.matchingSegments != other.matchingSegments: return False
        if self.matchingCells != other.matchingCells: return False

        return True

    def __ne__(self, other):
        """
    Non-equality operator for TemporalMemory instances.
    Checks if two instances are not functionally identical
    (might have different internal state).

    @param other (TemporalMemory) TemporalMemory instance to compare to
    """
        return not self.__eq__(other)

    def _validateColumn(self, column):
        """
    Raises an error if column index is invalid.

    @param column (int) Column index
    """
        if column >= self.numberOfColumns() or column < 0:
            raise IndexError("Invalid column")

    def _validateCell(self, cell):
        """
    Raises an error if cell index is invalid.

    @param cell (int) Cell index
    """
        if cell >= self.numberOfCells() or cell < 0:
            raise IndexError("Invalid cell")

    @classmethod
    def getCellIndices(cls, cells):
        return [cls.getCellIndex(c) for c in cells]

    @staticmethod
    def getCellIndex(cell):
        return cell
Esempio n. 42
0
class TemporalMemory(object):
  """
  Class implementing the Temporal Memory algorithm.
  """

  def __init__(self,
               columnDimensions=(2048,),
               cellsPerColumn=32,
               activationThreshold=13,
               initialPermanence=0.21,
               connectedPermanence=0.50,
               minThreshold=10,
               maxNewSynapseCount=20,
               permanenceIncrement=0.10,
               permanenceDecrement=0.10,
               predictedSegmentDecrement=0.0,
               seed=42):
    """
    @param columnDimensions          (list)  Dimensions of the column space
    @param cellsPerColumn            (int)   Number of cells per column
    @param activationThreshold       (int)   If the number of active connected synapses on a segment is at least this threshold, the segment is said to be active.
    @param initialPermanence         (float) Initial permanence of a new synapse.
    @param connectedPermanence       (float) If the permanence value for a synapse is greater than this value, it is said to be connected.
    @param minThreshold              (int)   If the number of synapses active on a segment is at least this threshold, it is selected as the best matching cell in a bursting column.
    @param maxNewSynapseCount        (int)   The maximum number of synapses added to a segment during learning.
    @param permanenceIncrement       (float) Amount by which permanences of synapses are incremented during learning.
    @param permanenceDecrement       (float) Amount by which permanences of synapses are decremented during learning.
    @param predictedSegmentDecrement (float) Amount by which active permanences of synapses of previously predicted but inactive segments are decremented.
    @param seed                      (int)   Seed for the random number generator.

    Notes:

    predictedSegmentDecrement: A good value is just a bit larger than
    (the column-level sparsity * permanenceIncrement). So, if column-level
    sparsity is 2% and permanenceIncrement is 0.01, this parameter should be
    something like 4% * 0.01 = 0.0004).
    """
    # Error checking
    if not len(columnDimensions):
      raise ValueError("Number of column dimensions must be greater than 0")

    if not cellsPerColumn > 0:
      raise ValueError("Number of cells per column must be greater than 0")

    # TODO: Validate all parameters (and add validation tests)

    # Save member variables
    self.columnDimensions = columnDimensions
    self.cellsPerColumn = cellsPerColumn
    self.activationThreshold = activationThreshold
    self.initialPermanence = initialPermanence
    self.connectedPermanence = connectedPermanence
    self.minThreshold = minThreshold
    self.maxNewSynapseCount = maxNewSynapseCount
    self.permanenceIncrement = permanenceIncrement
    self.permanenceDecrement = permanenceDecrement
    self.predictedSegmentDecrement = predictedSegmentDecrement
    # Initialize member variables
    self.connections = Connections(self.numberOfCells())
    self._random = Random(seed)

    self.activeCells = set()
    self.predictiveCells = set()
    self.activeSegments = set()
    self.winnerCells = set()
    self.matchingSegments = set()
    self.matchingCells = set()

  # ==============================
  # Main functions
  # ==============================

  def compute(self, activeColumns, learn=True):
    """
    Feeds input record through TM, performing inference and learning.

    @param activeColumns (set)  Indices of active columns
    @param learn         (bool) Whether or not learning is enabled

    Updates member variables:
      - `activeCells`     (set)
      - `winnerCells`     (set)
      - `activeSegments`  (set)
      - `predictiveCells` (set)
      - `matchingSegments`(set)
      - `matchingCells`   (set)
      - `connections`     (Connections)
    """
    prevPredictiveCells = self.predictiveCells
    prevActiveSegments = self.activeSegments
    prevActiveCells = self.activeCells
    prevWinnerCells = self.winnerCells
    prevMatchingSegments = self.matchingSegments
    prevMatchingCells = self.matchingCells

    activeCells = set()
    winnerCells = set()

    (_activeCells,
     _winnerCells,
     predictedActiveColumns,
     predictedInactiveCells) = self.activateCorrectlyPredictiveCells(
       prevPredictiveCells,
       prevMatchingCells,
       activeColumns)

    activeCells.update(_activeCells)
    winnerCells.update(_winnerCells)

    (_activeCells,
     _winnerCells,
     learningSegments) = self.burstColumns(activeColumns,
                                           predictedActiveColumns,
                                           prevActiveCells,
                                           prevWinnerCells)

    activeCells.update(_activeCells)
    winnerCells.update(_winnerCells)

    if learn:
      self.learnOnSegments(prevActiveSegments,
                           learningSegments,
                           prevActiveCells,
                           winnerCells,
                           prevWinnerCells,
                           predictedInactiveCells,
                           prevMatchingSegments)

    (activeSegments,
     predictiveCells,
     matchingSegments,
     matchingCells) = self.computePredictiveCells(activeCells)

    self.activeCells = activeCells
    self.winnerCells = winnerCells
    self.activeSegments = activeSegments
    self.predictiveCells = predictiveCells
    self.matchingSegments = matchingSegments
    self.matchingCells = matchingCells


  def reset(self):
    """
    Indicates the start of a new sequence. Resets sequence state of the TM.
    """
    self.activeCells = set()
    self.predictiveCells = set()
    self.activeSegments = set()
    self.winnerCells = set()


  # ==============================
  # Phases
  # ==============================

  def activateCorrectlyPredictiveCells(self,
                                       prevPredictiveCells,
                                       prevMatchingCells,
                                       activeColumns):
    """
    Phase 1: Activate the correctly predictive cells.

    Pseudocode:

      - for each prev predictive cell
        - if in active column
          - mark it as active
          - mark it as winner cell
          - mark column as predicted => active
        - if not in active column
          - mark it as an predicted but inactive cell

    @param prevPredictiveCells (set) Indices of predictive cells in `t-1`
    @param activeColumns       (set) Indices of active columns in `t`

    @return (tuple) Contains:
                      `activeCells`               (set),
                      `winnerCells`               (set),
                      `predictedActiveColumns`    (set),
                      `predictedInactiveCells`    (set)
    """
    activeCells = set()
    winnerCells = set()
    predictedActiveColumns = set()
    predictedInactiveCells = set()

    for cell in prevPredictiveCells:
      column = self.columnForCell(cell)

      if column in activeColumns:
        activeCells.add(cell)
        winnerCells.add(cell)
        predictedActiveColumns.add(column)

    if self.predictedSegmentDecrement > 0:
      for cell in prevMatchingCells:
        column = self.columnForCell(cell)

        if column not in activeColumns:
          predictedInactiveCells.add(cell)

    return (activeCells,
            winnerCells,
            predictedActiveColumns,
            predictedInactiveCells)


  def burstColumns(self,
                   activeColumns,
                   predictedActiveColumns,
                   prevActiveCells,
                   prevWinnerCells):
    """
    Phase 2: Burst unpredicted columns.

    Pseudocode:

      - for each unpredicted active column
        - mark all cells as active
        - mark the best matching cell as winner cell
          - (learning)
            - if it has no matching segment
              - (optimization) if there are prev winner cells
                - add a segment to it
            - mark the segment as learning

    @param activeColumns                   (set)         Indices of active columns in `t`
    @param predictedActiveColumns          (set)         Indices of predicted => active columns in `t`
    @param prevActiveCells                 (set)         Indices of active cells in `t-1`
    @param prevWinnerCells                 (set)         Indices of winner cells in `t-1`

    @return (tuple) Contains:
                      `activeCells`      (set),
                      `winnerCells`      (set),
                      `learningSegments` (set)
    """
    activeCells = set()
    winnerCells = set()
    learningSegments = set()

    unpredictedActiveColumns = activeColumns - predictedActiveColumns

    for column in unpredictedActiveColumns:
      cells = self.cellsForColumn(column)
      activeCells.update(cells)

      (bestCell,
       bestSegment) = self.bestMatchingCell(cells, prevActiveCells)
      winnerCells.add(bestCell)

      if bestSegment is None and len(prevWinnerCells):
        bestSegment = self.connections.createSegment(bestCell)

      if bestSegment is not None:
        learningSegments.add(bestSegment)

    return activeCells, winnerCells, learningSegments


  def learnOnSegments(self,
                      prevActiveSegments,
                      learningSegments,
                      prevActiveCells,
                      winnerCells,
                      prevWinnerCells,
                      predictedInactiveCells,
                      prevMatchingSegments):
    """
    Phase 3: Perform learning by adapting segments.

    Pseudocode:

      - (learning) for each prev active or learning segment
        - if learning segment or from winner cell
          - strengthen active synapses
          - weaken inactive synapses
        - if learning segment
          - add some synapses to the segment
            - subsample from prev winner cells

      - if predictedSegmentDecrement > 0
        - for each previously matching segment
          - if cell is a predicted inactive cell
            - weaken active synapses but don't touch inactive synapses

    @param prevActiveSegments           (set)         Indices of active segments in `t-1`
    @param learningSegments             (set)         Indices of learning segments in `t`
    @param prevActiveCells              (set)         Indices of active cells in `t-1`
    @param winnerCells                  (set)         Indices of winner cells in `t`
    @param prevWinnerCells              (set)         Indices of winner cells in `t-1`
    @param predictedInactiveCells       (set)         Indices of predicted inactive cells
    @param prevMatchingSegments         (set)         Indices of segments with
    """
    for segment in prevActiveSegments | learningSegments:
      isLearningSegment = segment in learningSegments
      isFromWinnerCell = self.connections.cellForSegment(segment) in winnerCells

      activeSynapses = self.activeSynapsesForSegment(segment, prevActiveCells)

      if isLearningSegment or isFromWinnerCell:
        self.adaptSegment(segment,
                          activeSynapses,
                          self.permanenceIncrement,
                          self.permanenceDecrement)

      if isLearningSegment:
        n = self.maxNewSynapseCount - len(activeSynapses)

        for presynapticCell in self.pickCellsToLearnOn(n,
                                                       segment,
                                                       prevWinnerCells):
          self.connections.createSynapse(segment,
                                         presynapticCell,
                                         self.initialPermanence)

    if self.predictedSegmentDecrement > 0:
      for segment in prevMatchingSegments:
        isPredictedInactiveCell = (self.connections.cellForSegment(segment) in
                                   predictedInactiveCells)
        activeSynapses = self.activeSynapsesForSegment(segment, prevActiveCells)

        if isPredictedInactiveCell:
          self.adaptSegment(segment,
                            activeSynapses,
                            -self.predictedSegmentDecrement,
                            0.0)



  def computePredictiveCells(self, activeCells):
    """
    Phase 4: Compute predictive cells due to lateral input
    on distal dendrites.

    Pseudocode:

      - for each distal dendrite segment with activity >= activationThreshold
        - mark the segment as active
        - mark the cell as predictive

      - if predictedSegmentDecrement > 0
        - for each distal dendrite segment with unconnected
          activity >=  minThreshold
          - mark the segment as matching
          - mark the cell as matching

    Forward propagates activity from active cells to the synapses that touch
    them, to determine which synapses are active.

    @param activeCells (set)         Indices of active cells in `t`

    @return (tuple) Contains:
                      `activeSegments`  (set),
                      `predictiveCells` (set),
                      `matchingSegments` (set),
                      `matchingCells`    (set)
    """
    numActiveConnectedSynapsesForSegment = defaultdict(int)
    numActiveSynapsesForSegment = defaultdict(int)
    activeSegments = set()
    predictiveCells = set()

    matchingSegments = set()
    matchingCells = set()

    for cell in activeCells:
      synapses = self.connections.synapsesForPresynapticCell(cell)

      for synapseData in synapses.values():
        segment = synapseData.segment
        permanence = synapseData.permanence

        if permanence >= self.connectedPermanence:
          numActiveConnectedSynapsesForSegment[segment] += 1

          if (numActiveConnectedSynapsesForSegment[segment] >=
              self.activationThreshold):
            activeSegments.add(segment)
            predictiveCells.add(self.connections.cellForSegment(segment))

        if permanence > 0 and self.predictedSegmentDecrement > 0:
          numActiveSynapsesForSegment[segment] += 1

          if numActiveSynapsesForSegment[segment] >= self.minThreshold:
            matchingSegments.add(segment)
            matchingCells.add(self.connections.cellForSegment(segment))

    return activeSegments, predictiveCells, matchingSegments, matchingCells


  # ==============================
  # Helper functions
  # ==============================

  def bestMatchingCell(self, cells, activeCells):
    """
    Gets the cell with the best matching segment
    (see `TM.bestMatchingSegment`) that has the largest number of active
    synapses of all best matching segments.

    If none were found, pick the least used cell (see `TM.leastUsedCell`).

    @param cells       (set) Indices of cells
    @param activeCells (set) Indices of active cells

    @return (tuple) Contains:
                      `cell`        (int),
                      `bestSegment` (int)
    """
    maxSynapses = 0
    bestCell = None
    bestSegment = None

    for cell in cells:
      segment, numActiveSynapses = self.bestMatchingSegment(cell, activeCells)

      if segment is not None and numActiveSynapses > maxSynapses:
        maxSynapses = numActiveSynapses
        bestCell = cell
        bestSegment = segment

    if bestCell is None:
      bestCell = self.leastUsedCell(cells)

    return bestCell, bestSegment


  def bestMatchingSegment(self, cell, activeCells):
    """
    Gets the segment on a cell with the largest number of activate synapses,
    including all synapses with non-zero permanences.

    @param cell        (int) Cell index
    @param activeCells (set) Indices of active cells

    @return (tuple) Contains:
                      `segment`                 (int),
                      `connectedActiveSynapses` (set)
    """
    maxSynapses = self.minThreshold
    bestSegment = None
    bestNumActiveSynapses = None

    for segment in self.connections.segmentsForCell(cell):
      numActiveSynapses = 0

      for synapse in self.connections.synapsesForSegment(segment):
        synapseData = self.connections.dataForSynapse(synapse)
        if ( (synapseData.presynapticCell in activeCells) and
            synapseData.permanence > 0):
          numActiveSynapses += 1

      if numActiveSynapses >= maxSynapses:
        maxSynapses = numActiveSynapses
        bestSegment = segment
        bestNumActiveSynapses = numActiveSynapses

    return bestSegment, bestNumActiveSynapses


  def leastUsedCell(self, cells):
    """
    Gets the cell with the smallest number of segments.
    Break ties randomly.

    @param cells (set) Indices of cells

    @return (int) Cell index
    """
    leastUsedCells = set()
    minNumSegments = float("inf")

    for cell in cells:
      numSegments = len(self.connections.segmentsForCell(cell))

      if numSegments < minNumSegments:
        minNumSegments = numSegments
        leastUsedCells = set()

      if numSegments == minNumSegments:
        leastUsedCells.add(cell)

    i = self._random.getUInt32(len(leastUsedCells))
    return sorted(leastUsedCells)[i]


  def activeSynapsesForSegment(self, segment, activeCells):
    """
    Returns the synapses on a segment that are active due to lateral input
    from active cells.

    @param segment     (int)         Segment index
    @param activeCells (set)         Indices of active cells

    @return (set) Indices of active synapses on segment
    """
    synapses = set()

    for synapse in self.connections.synapsesForSegment(segment):
      synapseData = self.connections.dataForSynapse(synapse)

      if synapseData.presynapticCell in activeCells:
        synapses.add(synapse)

    return synapses


  def adaptSegment(self, segment, activeSynapses,
                   permanenceIncrement, permanenceDecrement):
    """
    Updates synapses on segment.
    Strengthens active synapses; weakens inactive synapses.

    @param segment              (int)    Segment index
    @param activeSynapses       (set)    Indices of active synapses
    @param permanenceIncrement  (float)  Amount to increment active synapses
    @param permanenceDecrement  (float)  Amount to decrement inactive synapses
    """
    # Need to copy synapses for segment set below because it will be modified
    # during iteration by `destroySynapse`
    for synapse in set(self.connections.synapsesForSegment(segment)):
      synapseData = self.connections.dataForSynapse(synapse)
      permanence = synapseData.permanence

      if synapse in activeSynapses:
        permanence += permanenceIncrement
      else:
        permanence -= permanenceDecrement

      # Keep permanence within min/max bounds
      permanence = max(0.0, min(1.0, permanence))

      if (abs(permanence) < EPSILON):
        self.connections.destroySynapse(synapse)
      else:
        self.connections.updateSynapsePermanence(synapse, permanence)


  def pickCellsToLearnOn(self, n, segment, winnerCells):
    """
    Pick cells to form distal connections to.

    TODO: Respect topology and learningRadius

    @param n           (int) Number of cells to pick
    @param segment     (int) Segment index
    @param winnerCells (set) Indices of winner cells in `t`

    @return (set) Indices of cells picked
    """
    candidates = set(winnerCells)

    # Remove cells that are already synapsed on by this segment
    for synapse in self.connections.synapsesForSegment(segment):
      synapseData = self.connections.dataForSynapse(synapse)
      presynapticCell = synapseData.presynapticCell

      if presynapticCell in candidates:
        candidates.remove(presynapticCell)

    n = min(n, len(candidates))
    candidates = sorted(candidates)
    cells = set()

    # Pick n cells randomly
    for _ in range(n):
      i = self._random.getUInt32(len(candidates))
      cells.add(candidates[i])
      del candidates[i]

    return cells


  def columnForCell(self, cell):
    """
    Returns the index of the column that a cell belongs to.

    @param cell (int) Cell index

    @return (int) Column index
    """
    self._validateCell(cell)

    return int(cell / self.cellsPerColumn)


  def cellsForColumn(self, column):
    """
    Returns the indices of cells that belong to a column.

    @param column (int) Column index

    @return (set) Cell indices
    """
    self._validateColumn(column)

    start = self.cellsPerColumn * self.getCellIndex(column)
    end = start + self.cellsPerColumn
    return set(xrange(start, end))


  def numberOfColumns(self):
    """
    Returns the number of columns in this layer.

    @return (int) Number of columns
    """
    return reduce(mul, self.columnDimensions, 1)


  def numberOfCells(self):
    """
    Returns the number of cells in this layer.

    @return (int) Number of cells
    """
    return self.numberOfColumns() * self.cellsPerColumn


  def mapCellsToColumns(self, cells):
    """
    Maps cells to the columns they belong to

    @param cells (set) Cells

    @return (dict) Mapping from columns to their cells in `cells`
    """
    cellsForColumns = defaultdict(set)

    for cell in cells:
      column = self.columnForCell(cell)
      cellsForColumns[column].add(cell)

    return cellsForColumns


  def write(self, proto):
    """
    Writes serialized data to proto object

    @param proto (DynamicStructBuilder) Proto object
    """
    proto.columnDimensions = self.columnDimensions
    proto.cellsPerColumn = self.cellsPerColumn
    proto.activationThreshold = self.activationThreshold
    proto.initialPermanence = self.initialPermanence
    proto.connectedPermanence = self.connectedPermanence
    proto.minThreshold = self.minThreshold
    proto.maxNewSynapseCount = self.maxNewSynapseCount
    proto.permanenceIncrement = self.permanenceIncrement
    proto.permanenceDecrement = self.permanenceDecrement
    proto.predictedSegmentDecrement = self.predictedSegmentDecrement

    self.connections.write(proto.connections)
    self._random.write(proto.random)

    proto.activeCells = list(self.activeCells)
    proto.predictiveCells = list(self.predictiveCells)
    proto.activeSegments = list(self.activeSegments)
    proto.winnerCells = list(self.winnerCells)
    proto.matchingSegments = list(self.matchingSegments)
    proto.matchingCells = list(self.matchingCells)


  @classmethod
  def read(cls, proto):
    """
    Reads deserialized data from proto object

    @param proto (DynamicStructBuilder) Proto object

    @return (TemporalMemory) TemporalMemory instance
    """
    tm = object.__new__(cls)

    tm.columnDimensions = list(proto.columnDimensions)
    tm.cellsPerColumn = int(proto.cellsPerColumn)
    tm.activationThreshold = int(proto.activationThreshold)
    tm.initialPermanence = proto.initialPermanence
    tm.connectedPermanence = proto.connectedPermanence
    tm.minThreshold = int(proto.minThreshold)
    tm.maxNewSynapseCount = int(proto.maxNewSynapseCount)
    tm.permanenceIncrement = proto.permanenceIncrement
    tm.permanenceDecrement = proto.permanenceDecrement
    tm.predictedSegmentDecrement = proto.predictedSegmentDecrement

    tm.connections = Connections.read(proto.connections)
    tm._random = Random()
    tm._random.read(proto.random)

    tm.activeCells = set([int(x) for x in proto.activeCells])
    tm.predictiveCells = set([int(x) for x in proto.predictiveCells])
    tm.activeSegments = set([int(x) for x in proto.activeSegments])
    tm.winnerCells = set([int(x) for x in proto.winnerCells])
    tm.matchingSegments = set([int(x) for x in proto.matchingSegments])
    tm.matchingCells = set([int(x) for x in proto.matchingCells])

    return tm


  def __eq__(self, other):
    """
    Equality operator for TemporalMemory instances.
    Checks if two instances are functionally identical
    (might have different internal state).

    @param other (TemporalMemory) TemporalMemory instance to compare to
    """
    if self.columnDimensions != other.columnDimensions: return False
    if self.cellsPerColumn != other.cellsPerColumn: return False
    if self.activationThreshold != other.activationThreshold: return False
    if abs(self.initialPermanence - other.initialPermanence) > EPSILON:
      return False
    if abs(self.connectedPermanence - other.connectedPermanence) > EPSILON:
      return False
    if self.minThreshold != other.minThreshold: return False
    if self.maxNewSynapseCount != other.maxNewSynapseCount: return False
    if abs(self.permanenceIncrement - other.permanenceIncrement) > EPSILON:
      return False
    if abs(self.permanenceDecrement - other.permanenceDecrement) > EPSILON:
      return False
    if abs(self.predictedSegmentDecrement - other.predictedSegmentDecrement) > EPSILON:
      return False

    if self.connections != other.connections: return False

    if self.activeCells != other.activeCells: return False
    if self.predictiveCells != other.predictiveCells: return False
    if self.winnerCells != other.winnerCells: return False
    if self.matchingSegments != other.matchingSegments: return False
    if self.matchingCells != other.matchingCells: return False

    return True


  def __ne__(self, other):
    """
    Non-equality operator for TemporalMemory instances.
    Checks if two instances are not functionally identical
    (might have different internal state).

    @param other (TemporalMemory) TemporalMemory instance to compare to
    """
    return not self.__eq__(other)


  def _validateColumn(self, column):
    """
    Raises an error if column index is invalid.

    @param column (int) Column index
    """
    if column >= self.numberOfColumns() or column < 0:
      raise IndexError("Invalid column")


  def _validateCell(self, cell):
    """
    Raises an error if cell index is invalid.

    @param cell (int) Cell index
    """
    if cell >= self.numberOfCells() or cell < 0:
      raise IndexError("Invalid cell")


  @classmethod
  def getCellIndices(cls, cells):
    return [cls.getCellIndex(c) for c in cells]


  @staticmethod
  def getCellIndex(cell):
    return cell