From c672f49fb36e404551f109626ac9ace28b1ab993 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 1 Aug 2019 15:29:08 +0200 Subject: [PATCH 01/34] Connections::adaptSegment fix segment pruning new segmentThreshold arg introduced, as connectedThreshold_ has different, incompatible meaning. --- src/htm/algorithms/Connections.cpp | 8 ++++++-- src/htm/algorithms/Connections.hpp | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index e38ccee3c2..1098434716 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -433,7 +433,8 @@ void Connections::adaptSegment(const Segment segment, const SDR &inputs, const Permanence increment, const Permanence decrement, - const bool pruneZeroSynapses) + const bool pruneZeroSynapses, + const UInt segmentThreshold) { const auto &inputArray = inputs.getDense(); @@ -476,7 +477,10 @@ void Connections::adaptSegment(const Segment segment, } //destroy segment if it has too few synapses left -> will never be able to connect again - if(pruneZeroSynapses and synapses.size() < connectedThreshold_) { //FIXME this is incorrect! connectedThreshold_ is if > then syn = connected. We need stimulusThreshold_ from TM. + if(!pruneZeroSynapses) { + NTA_ASSERT(segmentThreshold == 0) << "Setting segmentThreshold only makes sense when pruneZeroSynapses is allowed."; + } + if(pruneZeroSynapses and synapses.size() < segmentThreshold) { destroySegment(segment); prunedSegs_++; //statistics } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 745bf93286..dbbca588d3 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -459,14 +459,18 @@ class Connections : public Serializable * @param increment Change in permanence for synapses with active presynapses. * @param decrement Change in permanence for synapses with inactive presynapses. * @param pruneZeroSynapses (default false) If set, synapses that reach minPermanence(aka. "zero") - * are removed. This is used in TemporalMemory. If the segment becomes empty due to these - * removed synapses, we remove the segment (see @ref `destroySegment`). + * are removed. This is used in TemporalMemory. + * @param segmentThreshold (optional) (default 0) Minimum number of connected synapses for a segment + * to be considered active. @see raisePermenencesToThreshold(). Equivalent to `SP.stimulusThreshold`. + * If `pruneZeroSynapses` is used and synapses are removed, if the amount of synapses drops below + * `segmentThreshold`, we'll remove the segment as it can never become active again. See `destroySegment`. */ void adaptSegment(const Segment segment, const SDR &inputs, const Permanence increment, const Permanence decrement, - const bool pruneZeroSynapses = false); + const bool pruneZeroSynapses = false, + const UInt segmentThreshold = 0); /** * Ensures a minimum number of connected synapses. This raises permance From 3f4999ca3db0b3db95bc83d52a572d82ee07f8be Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 1 Aug 2019 16:02:40 +0200 Subject: [PATCH 02/34] SP & TM use synapse & segment pruning from Connections in adaptSegment --- src/htm/algorithms/SpatialPooler.cpp | 2 +- src/htm/algorithms/TemporalMemory.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index d529ba2d3f..046358b7d9 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -703,7 +703,7 @@ Real SpatialPooler::avgConnectedSpanForColumnND_(UInt column) const { void SpatialPooler::adaptSynapses_(const SDR &input, const SDR &active) { for(const auto &column : active.getSparse()) { - connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_); + connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_, true, stimulusThreshold_); connections_.raisePermanencesToThreshold( column, stimulusThreshold_ ); } } diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 83308fe65e..4c045be139 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -251,7 +251,7 @@ static void activatePredictedColumn( do { if (learn) { connections.adaptSegment(*activeSegment, prevActiveCells, - permanenceIncrement, permanenceDecrement, true); + permanenceIncrement, permanenceDecrement, true, minThreshold_); const Int32 nGrowDesired = static_cast(maxNewSynapseCount) - @@ -313,7 +313,7 @@ burstColumn(vector &activeCells, if (bestMatchingSegment != columnMatchingSegmentsEnd) { // Learn on the best matching segment. connections.adaptSegment(*bestMatchingSegment, prevActiveCells, - permanenceIncrement, permanenceDecrement, true); + permanenceIncrement, permanenceDecrement, true, minThreshold_); //TODO consolidate SP.stimulusThreshold_ & TM.minThreshold_ into Conn.stimulusThreshold ? (replacing segmentThreshold arg used in some methods in Conn) const Int32 nGrowDesired = maxNewSynapseCount - @@ -351,7 +351,7 @@ static void punishPredictedColumn( for (auto matchingSegment = columnMatchingSegmentsBegin; matchingSegment != columnMatchingSegmentsEnd; matchingSegment++) { connections.adaptSegment(*matchingSegment, prevActiveCells, - -predictedSegmentDecrement, 0.0, true); + -predictedSegmentDecrement, 0.0, true, minThreshold_); } } } From 1c3a68435eae706379326c551a78df74cd88e05d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Thu, 1 Aug 2019 16:10:16 +0200 Subject: [PATCH 03/34] TM.punishPredictedColumn_() is a private method of TM moved from being static --- src/htm/algorithms/TemporalMemory.cpp | 14 +++++--------- src/htm/algorithms/TemporalMemory.hpp | 4 ++++ 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 4c045be139..5f368d5835 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -341,17 +341,15 @@ burstColumn(vector &activeCells, } } -static void punishPredictedColumn( - Connections &connections, +void TemporalMemory::punishPredictedColumn_( vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, - const SDR &prevActiveCells, - Permanence predictedSegmentDecrement) { - if (predictedSegmentDecrement > 0.0) { + const SDR &prevActiveCells) { + if (predictedSegmentDecrement_ > 0.0) { for (auto matchingSegment = columnMatchingSegmentsBegin; matchingSegment != columnMatchingSegmentsEnd; matchingSegment++) { connections.adaptSegment(*matchingSegment, prevActiveCells, - -predictedSegmentDecrement, 0.0, true, minThreshold_); + -predictedSegmentDecrement_, 0.0, true, minThreshold_); } } } @@ -434,9 +432,7 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { } else { // predicted but not active column -> unlearn if (learn) { - punishPredictedColumn(connections, columnMatchingSegmentsBegin, - columnMatchingSegmentsEnd, prevActiveCells, - predictedSegmentDecrement_); + punishPredictedColumn_(columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, prevActiveCells); } } //else: not predicted & not active -> no activity -> does not show up at all } diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index 8533b5330c..f6bcb0d1f6 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -597,6 +597,10 @@ class TemporalMemory : public Serializable * */ SDR cellsToColumns(const SDR& cells) const; +private: + void punishPredictedColumn_(vector::const_iterator columnMatchingSegmentsBegin, + vector::const_iterator columnMatchingSegmentsEnd, + const SDR& prevActiveCells); protected: //all these could be const From c47b7a47ea28ced1e27b30cd159fabd3d3f57921 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 2 Aug 2019 13:21:02 +0200 Subject: [PATCH 04/34] TM:activatePredictedColumns() moved from static to private method --- src/htm/algorithms/TemporalMemory.cpp | 37 +++++++++------------------ src/htm/algorithms/TemporalMemory.hpp | 6 +++++ 2 files changed, 18 insertions(+), 25 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 5f368d5835..4de0bc9563 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -225,41 +225,32 @@ static void growSynapses(Connections &connections, } } -static void activatePredictedColumn( - vector &activeCells, - vector &winnerCells, - Connections &connections, - Random &rng, +void TemporalMemory::activatePredictedColumn_( vector::const_iterator columnActiveSegmentsBegin, vector::const_iterator columnActiveSegmentsEnd, const SDR &prevActiveCells, const vector &prevWinnerCells, - const vector &numActivePotentialSynapsesForSegment, - const UInt maxNewSynapseCount, - const Permanence initialPermanence, - const Permanence permanenceIncrement, - const Permanence permanenceDecrement, - const SynapseIdx maxSynapsesPerSegment, const bool learn) { + auto activeSegment = columnActiveSegmentsBegin; do { const CellIdx cell = connections.cellForSegment(*activeSegment); - activeCells.push_back(cell); - winnerCells.push_back(cell); + activeCells_.push_back(cell); + winnerCells_.push_back(cell); // This cell might have multiple active segments. do { if (learn) { connections.adaptSegment(*activeSegment, prevActiveCells, - permanenceIncrement, permanenceDecrement, true, minThreshold_); + permanenceIncrement_, permanenceDecrement_, true, minThreshold_); const Int32 nGrowDesired = - static_cast(maxNewSynapseCount) - - numActivePotentialSynapsesForSegment[*activeSegment]; + static_cast(maxNewSynapseCount_) - + numActivePotentialSynapsesForSegment_[*activeSegment]; if (nGrowDesired > 0) { - growSynapses(connections, rng, *activeSegment, nGrowDesired, - prevWinnerCells, initialPermanence, - maxSynapsesPerSegment); + growSynapses(connections, rng_, *activeSegment, nGrowDesired, + prevWinnerCells, initialPermanence_, + maxSynapsesPerSegment_); } } } while (++activeSegment != columnActiveSegmentsEnd && @@ -411,13 +402,9 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { if (isActiveColumn) { //current active column... if (columnActiveSegmentsBegin != columnActiveSegmentsEnd) { //...was also predicted -> learn :o) - activatePredictedColumn( - activeCells_, winnerCells_, connections, rng_, + activatePredictedColumn_( columnActiveSegmentsBegin, columnActiveSegmentsEnd, - prevActiveCells, prevWinnerCells, - numActivePotentialSynapsesForSegment_, maxNewSynapseCount_, - initialPermanence_, permanenceIncrement_, permanenceDecrement_, - maxSynapsesPerSegment_, learn); + prevActiveCells, prevWinnerCells, learn); } else { //...has not been predicted -> burstColumn(activeCells_, winnerCells_, connections, rng_, diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index f6bcb0d1f6..b7d4897b9f 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -602,6 +602,12 @@ class TemporalMemory : public Serializable vector::const_iterator columnMatchingSegmentsEnd, const SDR& prevActiveCells); + void activatePredictedColumn_(vector::const_iterator columnActiveSegmentsBegin, + vector::const_iterator columnActiveSegmentsEnd, + const SDR &prevActiveCells, + const vector &prevWinnerCells, + const bool learn); + protected: //all these could be const CellIdx numColumns_; From 9ce19f985410f4f7c64a1adc57d5dc3bee96344f Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 2 Aug 2019 13:33:53 +0200 Subject: [PATCH 05/34] TM::burstColumn() made private from static --- src/htm/algorithms/TemporalMemory.cpp | 61 ++++++++++----------------- src/htm/algorithms/TemporalMemory.hpp | 7 +++ 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index 4de0bc9563..e2fc314bf5 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -259,59 +259,45 @@ void TemporalMemory::activatePredictedColumn_( } -static void -burstColumn(vector &activeCells, - vector &winnerCells, - Connections &connections, - Random &rng, - UInt column, +void TemporalMemory::burstColumn_( + const UInt column, vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, const SDR &prevActiveCells, const vector &prevWinnerCells, - const vector &numActivePotentialSynapsesForSegment, - CellIdx cellsPerColumn, - UInt maxNewSynapseCount, - const Permanence initialPermanence, - const Permanence permanenceIncrement, - const Permanence permanenceDecrement, - const SegmentIdx maxSegmentsPerCell, - const SynapseIdx maxSynapsesPerSegment, const bool learn) { // Calculate the active cells. - const CellIdx start = column * cellsPerColumn; - const CellIdx end = start + cellsPerColumn; + const CellIdx start = column * cellsPerColumn_; + const CellIdx end = start + cellsPerColumn_; for (CellIdx cell = start; cell < end; cell++) { - activeCells.push_back(cell); + activeCells_.push_back(cell); } const auto bestMatchingSegment = std::max_element(columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, [&](Segment a, Segment b) { - return (numActivePotentialSynapsesForSegment[a] < - numActivePotentialSynapsesForSegment[b]); + return (numActivePotentialSynapsesForSegment_[a] < + numActivePotentialSynapsesForSegment_[b]); }); const CellIdx winnerCell = (bestMatchingSegment != columnMatchingSegmentsEnd) ? connections.cellForSegment(*bestMatchingSegment) - : getLeastUsedCell(rng, column, connections, cellsPerColumn); //TODO replace (with random?) this is extremely costly, removing makes TM 6x faster! + : getLeastUsedCell(rng_, column, connections, cellsPerColumn_); //TODO replace (with random?) this is extremely costly, removing makes TM 6x faster! - winnerCells.push_back(winnerCell); + winnerCells_.push_back(winnerCell); // Learn. if (learn) { if (bestMatchingSegment != columnMatchingSegmentsEnd) { // Learn on the best matching segment. connections.adaptSegment(*bestMatchingSegment, prevActiveCells, - permanenceIncrement, permanenceDecrement, true, minThreshold_); //TODO consolidate SP.stimulusThreshold_ & TM.minThreshold_ into Conn.stimulusThreshold ? (replacing segmentThreshold arg used in some methods in Conn) + permanenceIncrement_, permanenceDecrement_, true, minThreshold_); //TODO consolidate SP.stimulusThreshold_ & TM.minThreshold_ into Conn.stimulusThreshold ? (replacing segmentThreshold arg used in some methods in Conn) - const Int32 nGrowDesired = - maxNewSynapseCount - - numActivePotentialSynapsesForSegment[*bestMatchingSegment]; + const Int32 nGrowDesired = maxNewSynapseCount_ - numActivePotentialSynapsesForSegment_[*bestMatchingSegment]; if (nGrowDesired > 0) { - growSynapses(connections, rng, *bestMatchingSegment, nGrowDesired, - prevWinnerCells, initialPermanence, maxSynapsesPerSegment); + growSynapses(connections, rng_, *bestMatchingSegment, nGrowDesired, + prevWinnerCells, initialPermanence_, maxSynapsesPerSegment_); } } else { // No matching segments. @@ -319,19 +305,20 @@ burstColumn(vector &activeCells, // Don't grow a segment that will never match. const UInt32 nGrowExact = - std::min(maxNewSynapseCount, (UInt32)prevWinnerCells.size()); + std::min(static_cast(maxNewSynapseCount_), static_cast(prevWinnerCells.size())); if (nGrowExact > 0) { const Segment segment = - connections.createSegment(winnerCell, maxSegmentsPerCell); + connections.createSegment(winnerCell, maxSegmentsPerCell_); - growSynapses(connections, rng, segment, nGrowExact, prevWinnerCells, - initialPermanence, maxSynapsesPerSegment); + growSynapses(connections, rng_, segment, nGrowExact, prevWinnerCells, + initialPermanence_, maxSynapsesPerSegment_); NTA_ASSERT(connections.numSynapses(segment) == nGrowExact); } } } } + void TemporalMemory::punishPredictedColumn_( vector::const_iterator columnMatchingSegmentsBegin, vector::const_iterator columnMatchingSegmentsEnd, @@ -407,14 +394,10 @@ void TemporalMemory::activateCells(const SDR &activeColumns, const bool learn) { prevActiveCells, prevWinnerCells, learn); } else { //...has not been predicted -> - burstColumn(activeCells_, winnerCells_, connections, rng_, - column, - columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, - prevActiveCells, prevWinnerCells, - numActivePotentialSynapsesForSegment_, - cellsPerColumn_, maxNewSynapseCount_, initialPermanence_, - permanenceIncrement_, permanenceDecrement_, - maxSegmentsPerCell_, maxSynapsesPerSegment_, learn); + burstColumn_(column, + columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, + prevActiveCells, prevWinnerCells, + learn); } } else { // predicted but not active column -> unlearn diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index b7d4897b9f..1211ca999d 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -608,6 +608,13 @@ class TemporalMemory : public Serializable const vector &prevWinnerCells, const bool learn); + void burstColumn_(const UInt column, + vector::const_iterator columnMatchingSegmentsBegin, + vector::const_iterator columnMatchingSegmentsEnd, + const SDR &prevActiveCells, + const vector &prevWinnerCells, + const bool learn); + protected: //all these could be const CellIdx numColumns_; From d3ba477b6cd23b1d9511306fbe9938ce82e5b8c2 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 2 Aug 2019 13:44:45 +0200 Subject: [PATCH 06/34] TM:growSynapses() made private from static --- src/htm/algorithms/TemporalMemory.cpp | 26 ++++++++++---------------- src/htm/algorithms/TemporalMemory.hpp | 4 ++++ 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/htm/algorithms/TemporalMemory.cpp b/src/htm/algorithms/TemporalMemory.cpp index e2fc314bf5..b8ad79df4e 100644 --- a/src/htm/algorithms/TemporalMemory.cpp +++ b/src/htm/algorithms/TemporalMemory.cpp @@ -175,13 +175,10 @@ static CellIdx getLeastUsedCell(Random &rng, } //*/ -static void growSynapses(Connections &connections, - Random &rng, +void TemporalMemory::growSynapses_( const Segment& segment, const SynapseIdx nDesiredNewSynapses, - const vector &prevWinnerCells, - const Permanence initialPermanence, - const SynapseIdx maxSynapsesPerSegment) { + const vector &prevWinnerCells) { // It's possible to optimize this, swapping candidates to the end as // they're used. But this is awkward to mimic in other // implementations, especially because it requires iterating over @@ -211,20 +208,21 @@ static void growSynapses(Connections &connections, const size_t nActual = std::min(static_cast(nDesiredNewSynapses), candidates.size()); // Check if we're going to surpass the maximum number of synapses. - Int overrun = static_cast(connections.numSynapses(segment) + nActual - maxSynapsesPerSegment); + Int overrun = static_cast(connections.numSynapses(segment) + nActual - maxSynapsesPerSegment_); if (overrun > 0) { connections.destroyMinPermanenceSynapses(segment, static_cast(overrun), prevWinnerCells); } // Recalculate in case we weren't able to destroy as many synapses as needed. - const size_t nActualWithMax = std::min(nActual, static_cast(maxSynapsesPerSegment) - connections.numSynapses(segment)); + const size_t nActualWithMax = std::min(nActual, static_cast(maxSynapsesPerSegment_) - connections.numSynapses(segment)); // Pick nActual cells randomly. - for (const auto syn : rng.sample(candidates, static_cast(nActualWithMax))) { - connections.createSynapse(segment, syn, initialPermanence); //TODO createSynapse consider creating a vector of new synapses at once? + for (const auto syn : rng_.sample(candidates, static_cast(nActualWithMax))) { + connections.createSynapse(segment, syn, initialPermanence_); //TODO createSynapse consider creating a vector of new synapses at once? } } + void TemporalMemory::activatePredictedColumn_( vector::const_iterator columnActiveSegmentsBegin, vector::const_iterator columnActiveSegmentsEnd, @@ -248,9 +246,7 @@ void TemporalMemory::activatePredictedColumn_( static_cast(maxNewSynapseCount_) - numActivePotentialSynapsesForSegment_[*activeSegment]; if (nGrowDesired > 0) { - growSynapses(connections, rng_, *activeSegment, nGrowDesired, - prevWinnerCells, initialPermanence_, - maxSynapsesPerSegment_); + growSynapses_(*activeSegment, nGrowDesired, prevWinnerCells); } } } while (++activeSegment != columnActiveSegmentsEnd && @@ -296,8 +292,7 @@ void TemporalMemory::burstColumn_( const Int32 nGrowDesired = maxNewSynapseCount_ - numActivePotentialSynapsesForSegment_[*bestMatchingSegment]; if (nGrowDesired > 0) { - growSynapses(connections, rng_, *bestMatchingSegment, nGrowDesired, - prevWinnerCells, initialPermanence_, maxSynapsesPerSegment_); + growSynapses_(*bestMatchingSegment, nGrowDesired, prevWinnerCells); } } else { // No matching segments. @@ -310,8 +305,7 @@ void TemporalMemory::burstColumn_( const Segment segment = connections.createSegment(winnerCell, maxSegmentsPerCell_); - growSynapses(connections, rng_, segment, nGrowExact, prevWinnerCells, - initialPermanence_, maxSynapsesPerSegment_); + growSynapses_(segment, nGrowExact, prevWinnerCells); NTA_ASSERT(connections.numSynapses(segment) == nGrowExact); } } diff --git a/src/htm/algorithms/TemporalMemory.hpp b/src/htm/algorithms/TemporalMemory.hpp index 1211ca999d..06861f0425 100644 --- a/src/htm/algorithms/TemporalMemory.hpp +++ b/src/htm/algorithms/TemporalMemory.hpp @@ -615,6 +615,10 @@ class TemporalMemory : public Serializable const vector &prevWinnerCells, const bool learn); + void growSynapses_(const Segment& segment, + const SynapseIdx nDesiredNewSynapses, + const vector &prevWinnerCells); + protected: //all these could be const CellIdx numColumns_; From 4be092be79ebee2cad7565fe34645ba85f03e7f5 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 24 Jul 2019 01:05:55 +0200 Subject: [PATCH 07/34] Connections:destroySynapse fixup, use find instead of lower_bound, caused err in Debug. Seems faster --- src/htm/algorithms/Connections.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 1098434716..0cc422106b 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -250,18 +250,14 @@ void Connections::destroySynapse(const Synapse synapse) { } } - const auto synapseOnSegment = - std::lower_bound(segmentData.synapses.cbegin(), segmentData.synapses.cend(), - synapse, - [&](const Synapse a, const Synapse b) -> bool { - return dataForSynapse(a).id < dataForSynapse(b).id; - }); - - NTA_ASSERT(synapseOnSegment != segmentData.synapses.end()); + const auto synapseOnSegment = std::find(segmentData.synapses.cbegin(), + segmentData.synapses.cend(), + synapse); + + NTA_ASSERT(synapseOnSegment != segmentData.synapses.cend()); NTA_ASSERT(*synapseOnSegment == synapse); segmentData.synapses.erase(synapseOnSegment); - destroyedSynapses_.push_back(synapse); } @@ -348,7 +344,7 @@ bool Connections::compareSegments(const Segment a, const Segment b) const { // default sort by cell if (aData.cell == bData.cell) //fallback to ordinals: - return aData.id < bData.id; + return aData.id < bData.id; //TODO is segment's id/ordinals needed? else return aData.cell < bData.cell; } From 6f535c4f5b62ca8707c4559c78ea984ca8089e8d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 2 Aug 2019 16:55:33 +0200 Subject: [PATCH 08/34] fix deterministic test results --- src/examples/hotgym/HelloSPTP.cpp | 10 +++++----- src/test/unit/algorithms/SpatialPoolerTest.cpp | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/examples/hotgym/HelloSPTP.cpp b/src/examples/hotgym/HelloSPTP.cpp index 7edea90645..5c29b969a5 100644 --- a/src/examples/hotgym/HelloSPTP.cpp +++ b/src/examples/hotgym/HelloSPTP.cpp @@ -200,24 +200,24 @@ EPOCHS = 2; // make test faster in Debug SDR goldSP({COLS}); const SDR_sparse_t deterministicSP{ - 62, 72, 73, 82, 85, 102, 263, 277, 287, 303, 306, 308, 309, 322, 337, 339, 340, 352, 370, 493, 1094, 1095, 1114, 1115, 1120, 1463, 1512, 1518, 1647, 1651, 1691, 1694, 1729, 1745, 1746, 1760, 1770, 1774, 1775, 1781, 1797, 1798, 1803, 1804, 1805, 1812, 1827, 1828, 1831, 1832, 1858, 1859, 1860, 1861, 1862, 1875, 1878, 1880, 1881, 1898, 1918, 1923, 1929, 1931,1936, 1950, 1953, 1956, 1958, 1961, 1964, 1965, 1967, 1971, 1973, 1975, 1976, 1979, 1980, 1981, 1982, 1984, 1985, 1986, 1988, 1991, 1994, 1996, 1997, 1998, 1999, 2002, 2006, 2008, 2011, 2012, 2013, 2017, 2019, 2022, 2027, 2030 + 17, 62, 71, 72, 73, 78, 82, 83, 85, 93, 102, 131, 261, 263, 268, 269, 277, 282, 287, 301, 306, 308, 309, 317, 323, 331, 336, 337, 338, 339, 340, 352, 359, 366, 432, 443, 493, 502, 523, 811, 928, 955, 1089, 1095, 1114, 1115, 1120, 1133, 1134, 1428, 1508, 1512, 1651, 1677, 1745, 1760, 1774, 1804, 1805, 1858, 1861, 1925, 1927, 1929, 1936, 1937, 1940, 1950, 1951, 1953,1956, 1961, 1967, 1969, 1971, 1975, 1978, 1979, 1980, 1981, 1982, 1984, 1985, 1987, 1988, 1990, 1994, 1996, 1997, 1998, 1999, 2000, 2002, 2006, 2008, 2011, 2012, 2013, 2016, 2022, 2027, 2034 }; goldSP.setSparse(deterministicSP); SDR goldSPlocal({COLS}); const SDR_sparse_t deterministicSPlocal{ - 12, 13, 71, 72, 75, 78, 82, 85, 131, 171, 182, 186, 189, 194, 201, 263, 277, 287, 308, 319, 323, 337, 339, 365, 407, 423, 429, 432, 434, 445, 493, 494, 502, 508, 523, 542, 554, 559, 585, 586, 610, 611, 612, 644, 645, 647, 691, 698, 699, 701, 702, 707, 777, 809, 810, 811, 833, 839, 841, 920, 923, 928, 929, 935, 955, 1003, 1005, 1073, 1076, 1094, 1095, 1114,1115, 1133, 1134, 1184, 1203, 1214, 1232, 1233, 1244, 1253, 1268, 1278, 1291, 1294, 1306, 1309, 1331, 1402, 1410, 1427, 1434, 1442, 1463, 1508, 1512, 1514, 1515, 1518, 1561, 1564, 1590, 1623, 1626, 1630, 1647, 1651, 1691, 1694, 1729, 1745, 1746, 1760, 1797, 1804, 1805, 1812, 1827, 1858, 1860, 1861, 1862, 1918, 1956, 1961, 1965, 1971, 1975, 1994, 2012 + 13, 62, 71, 72, 73, 78, 80, 134, 140, 167, 169, 179, 189, 194, 261, 263, 268, 269, 308, 323, 328, 337, 339, 365, 407, 423, 425, 432, 434, 443, 493, 494, 508, 512, 514, 520, 585, 598, 601, 610, 630, 644, 645, 673, 675, 691, 701, 707, 748, 749, 777, 809, 811, 833, 838, 841, 853, 889, 906, 921, 926, 928, 952, 958, 967, 989, 1005, 1076, 1089, 1095, 1114, 1115,1120, 1133, 1146, 1181, 1184, 1196, 1203, 1217, 1249, 1252, 1253, 1263, 1282, 1291, 1306, 1309, 1331, 1337, 1401, 1402, 1410, 1434, 1462, 1469, 1487, 1494, 1508, 1512, 1518, 1547, 1563, 1564, 1623, 1624, 1626, 1651, 1672, 1677, 1693, 1694, 1745, 1746, 1750, 1760, 1768, 1802, 1805, 1831, 1858, 1861, 1869, 1880, 1889, 1929, 1950, 1956, 1961, 1994, 2002, 2011, 2012, 2027 }; goldSPlocal.setSparse(deterministicSPlocal); SDR goldTM({COLS}); const SDR_sparse_t deterministicTM{ - 62, 77, 85, 322, 340, 432, 952, 1120, 1488, 1502, 1512, 1518, 1547, 1627, 1633, 1668, 1727, 1729, 1797, 1803, 1805, 1812, 1858, 1859, 1896, 1918, 1923, 1925, 1929, 1931, 1939, 1941, 1942, 1944, 1950, 1953, 1955, 1956, 1965, 1966, 1967, 1968, 1974, 1980, 1987, 1996, 2006, 2008, 2011, 2027, 2030, 2042, 2046 + 17, 37, 62, 72, 73, 93, 134, 261, 268, 269, 301, 309, 323, 331, 337, 338, 432, 493, 502, 523, 532, 542, 645, 754, 833, 921, 952, 1102, 1114, 1507, 1508, 1512, 1627, 1693, 1858, 1861, 1870, 1917, 1925, 1927, 1929, 1937, 1938, 1941, 1943, 1944, 1945, 1946, 1947, 1950, 1952, 1955, 1956, 1959, 1960, 1964, 1966, 1969, 1971, 1974, 1975, 1978, 1979, 1987, 1990, 1991,1995, 1996, 1998, 1999, 2002, 2012, 2027, 2040, 2042 }; goldTM.setSparse(deterministicTM); - const float goldAn = 0.627451f; - const float goldAnAvg = 0.407265f; + const float goldAn = 0.5f; + const float goldAnAvg = 0.386638f; #ifdef _ARCH_DETERMINISTIC if(EPOCHS == 5000) { diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 667a40dd3b..04015c94e1 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -2059,7 +2059,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Silver is an SDR that is loaded by direct initalization from a vector. SDR silver_sdr({ 200 }); SDR_sparse_t data = { - 4, 64, 74, 78, 85, 113, 125, 126, 127, 153 + 11, 17, 31, 63, 78, 125, 126, 153, 173, 193 }; silver_sdr.setSparse(data); @@ -2067,7 +2067,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Gold tests initalizing an SDR from a manually created string in JSON format. // hint: you can generate this string using // silver_sdr.save(std::cout, JSON); - string gold = "{\"dimensions\": [200],\"sparse\": [4, 64, 74, 78, 85, 113, 125, 126, 127, 153]}"; + string gold = "{\"dimensions\": [200],\"sparse\": [11, 17, 31, 63, 78, 125, 126, 153, 173, 193]}"; std::stringstream gold_stream( gold ); SDR gold_sdr; gold_sdr.load( gold_stream, JSON ); From b6d04bb2e4732ae514fe9b213ab74c438a7442f1 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 3 Aug 2019 00:06:09 +0200 Subject: [PATCH 09/34] reintroduce lower_bound w deterministic ordering --- src/htm/algorithms/Connections.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 0cc422106b..5e5e8b269f 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -250,9 +250,12 @@ void Connections::destroySynapse(const Synapse synapse) { } } - const auto synapseOnSegment = std::find(segmentData.synapses.cbegin(), + const auto synapseOnSegment = std::lower_bound(segmentData.synapses.cbegin(), segmentData.synapses.cend(), - synapse); + synapse, + [&](const Synapse a, const Synapse b) -> bool { + return dataForSynapse(a).id < dataForSynapse(b).id; + }); NTA_ASSERT(synapseOnSegment != segmentData.synapses.cend()); NTA_ASSERT(*synapseOnSegment == synapse); From 36608e581ae35bf1788ac9e4f0b32572439dd25b Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 10 Aug 2019 15:14:05 +0200 Subject: [PATCH 10/34] cleanup createSegment --- src/htm/algorithms/Connections.cpp | 40 +++++++++++++++++++++--------- src/htm/algorithms/Connections.hpp | 5 ++++ 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 5e5e8b269f..31baff14da 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -70,23 +70,39 @@ void Connections::unsubscribe(UInt32 token) { eventHandlers_.erase(token); } + +void Connections::pruneLRUSegment_(const CellIdx& cell) { + const auto& destroyCandidates = segmentsForCell(cell); +#ifdef NTA_ASSERTIONS_ON + const auto numBefore = destroyCandidates.size(); +#endif + const auto compareSegmentsByLRU = [&](const Segment a, const Segment b) { + if(dataForSegment(a).lastUsed == dataForSegment(b).lastUsed) { + return a < b; //needed for deterministic sort + } + else return dataForSegment(a).lastUsed < dataForSegment(b).lastUsed; //sort segments by access time + }; + + const auto leastRecentlyUsedSegment = std::min_element(destroyCandidates.cbegin(), + destroyCandidates.cend(), + compareSegmentsByLRU); + destroySegment(*leastRecentlyUsedSegment); + NTA_ASSERT(destroyCandidates.size() < numBefore) << "A segment should have been pruned, but wasn't!"; +#ifdef NTA_ASSERTIONS_ON + if(destroyCandidates.size() > 0) { + // the removed seg should be the "oldest", least recently used. So any other is more recent. We don't check all, but randomly ([0]) + NTA_ASSERT(*leastRecentlyUsedSegment.lastUsed <= destroyCandidates[0].lastUsed) << "Should remove the least recently used segment,but did not."; + } +#endif +} + Segment Connections::createSegment(const CellIdx cell, const SegmentIdx maxSegmentsPerCell) { //limit number of segmets per cell. If exceeded, remove the least recently used ones. NTA_ASSERT(maxSegmentsPerCell > 0); while (numSegments(cell) >= maxSegmentsPerCell) { - const auto& destroyCandidates = segmentsForCell(cell); - const auto compareSegmentsByLRU = [&](const Segment a, const Segment b) { - if(dataForSegment(a).lastUsed == dataForSegment(b).lastUsed) { - return a < b; //needed for deterministic sort - } - else return dataForSegment(a).lastUsed < dataForSegment(b).lastUsed; //sort segments by access time - }; - const auto leastRecentlyUsedSegment = std::min_element(destroyCandidates.cbegin(), - destroyCandidates.cend(), compareSegmentsByLRU); - - destroySegment(*leastRecentlyUsedSegment); + pruneLRUSegment_(cell); } //proceed to create a new segment @@ -95,7 +111,7 @@ Segment Connections::createSegment(const CellIdx cell, segment = destroyedSegments_.back(); destroyedSegments_.pop_back(); } else { //create a new segment - NTA_CHECK(segments_.size() < std::numeric_limits::max()) << "Add segment failed: Range of Segment (data-type) insufficinet size." + NTA_CHECK(segments_.size() < std::numeric_limits::max()) << "Add segment failed: Range of Segment (data-type) insufficient size." << (size_t)segments_.size() << " < " << (size_t)std::numeric_limits::max(); segment = static_cast(segments_.size()); const SegmentData& segmentData = SegmentData(cell, iteration_, nextSegmentOrdinal_++); diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index dbbca588d3..cd5aa4db17 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -710,6 +710,11 @@ class Connections : public Serializable std::vector &synapsesForPresynapticCell, std::vector &segmentsForPresynapticCell); + /** + * Remove least recently used Segment from cell. + */ + void pruneLRUSegment_(const CellIdx& cell); + private: std::vector cells_; std::vector segments_; From f35daf72cd244d545f2665093f947a39af0f7631 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 10 Aug 2019 15:22:43 +0200 Subject: [PATCH 11/34] assert --- src/htm/algorithms/Connections.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 31baff14da..80c264d846 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -223,7 +223,7 @@ void Connections::destroySegment(const Segment segment) { CellData &cellData = cells_[segmentData.cell]; const auto segmentOnCell = std::find(cellData.segments.cbegin(), cellData.segments.cend(), segment); - NTA_ASSERT(segmentOnCell != cellData.segments.cend()) << "Segment to be destroyed not found on the cell!"; + NTA_CHECK(segmentOnCell != cellData.segments.cend()) << "Segment to be destroyed not found on the cell!"; NTA_ASSERT(*segmentOnCell == segment); cellData.segments.erase(segmentOnCell); From 97ec6bfbe4b5d4d036d3cadb0af2fc08baae7f4f Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 10 Aug 2019 16:03:40 +0200 Subject: [PATCH 12/34] Connections: remove vector destroySegments_ and only keep count of segments that were destroyed. Pros: - simpler code - fixed deterministm for (serialized connections & using pruning) - maybe better cache utilization, as vector is continuous now Cons: - larger memory utilization, as segments_ and segmentData_ vectors now have holes in it --- src/htm/algorithms/Connections.cpp | 24 +++++++++--------------- src/htm/algorithms/Connections.hpp | 12 +++++++++--- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 80c264d846..6d3021cc9f 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -40,7 +40,6 @@ Connections::Connections(const CellIdx numCells, void Connections::initialize(CellIdx numCells, Permanence connectedThreshold, bool timeseries) { cells_ = vector(numCells); segments_.clear(); - destroyedSegments_.clear(); synapses_.clear(); destroyedSynapses_.clear(); potentialSynapsesForPresynapticCell_.clear(); @@ -91,7 +90,8 @@ void Connections::pruneLRUSegment_(const CellIdx& cell) { #ifdef NTA_ASSERTIONS_ON if(destroyCandidates.size() > 0) { // the removed seg should be the "oldest", least recently used. So any other is more recent. We don't check all, but randomly ([0]) - NTA_ASSERT(*leastRecentlyUsedSegment.lastUsed <= destroyCandidates[0].lastUsed) << "Should remove the least recently used segment,but did not."; + NTA_ASSERT(dataForSegment(*leastRecentlyUsedSegment).lastUsed <= dataForSegment(destroyCandidates[0]).lastUsed) + << "Should remove the least recently used segment,but older exists."; } #endif } @@ -106,17 +106,11 @@ Segment Connections::createSegment(const CellIdx cell, } //proceed to create a new segment - Segment segment; - if (!destroyedSegments_.empty() ) { //reuse old, destroyed segs - segment = destroyedSegments_.back(); - destroyedSegments_.pop_back(); - } else { //create a new segment - NTA_CHECK(segments_.size() < std::numeric_limits::max()) << "Add segment failed: Range of Segment (data-type) insufficient size." + NTA_CHECK(segments_.size() < std::numeric_limits::max()) << "Add segment failed: Range of Segment (data-type) insufficient size." << (size_t)segments_.size() << " < " << (size_t)std::numeric_limits::max(); - segment = static_cast(segments_.size()); - const SegmentData& segmentData = SegmentData(cell, iteration_, nextSegmentOrdinal_++); - segments_.push_back(segmentData); - } + const Segment segment = static_cast(segments_.size()); + const SegmentData& segmentData = SegmentData(cell, iteration_, nextSegmentOrdinal_++); + segments_.push_back(segmentData); CellData &cellData = cells_[cell]; cellData.segments.push_back(segment); //assign the new segment to its mother-cell @@ -227,7 +221,7 @@ void Connections::destroySegment(const Segment segment) { NTA_ASSERT(*segmentOnCell == segment); cellData.segments.erase(segmentOnCell); - destroyedSegments_.push_back(segment); + destroyedSegments_++; } @@ -722,8 +716,8 @@ std::ostream& operator<< (std::ostream& stream, const Connections& self) << "%) Saturated (" << (Real) synapsesSaturated / self.numSynapses() << "%)" << std::endl; stream << " Synapses pruned (" << (Real) self.prunedSyns_ / self.numSynapses() << "%) Segments pruned (" << (Real) self.prunedSegs_ / self.numSegments() << "%)" << std::endl; - stream << " Buffer for destroyed synapses: " << self.destroyedSynapses_.size() << " \t buffer for destr. segments: " - << self.destroyedSegments_.size() << std::endl; + stream << " Buffer for destroyed synapses: " << self.destroyedSynapses_.size() + << " Buffer for destroyed segments: " << self.destroyedSegments_ << std::endl; return stream; } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index cd5aa4db17..1c9149bc0d 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -566,6 +566,10 @@ class Connections : public Serializable } } ar(CEREAL_NVP(connectedThreshold_)); + //the following member must not be serialized (so is set to =0). + //That is because of we serialize only active segments & synapses, + //excluding the "destroyed", so those fields start empty. +//! ar(CEREAL_NVP(destroyedSegments_)); ar(CEREAL_NVP(sizes)); ar(CEREAL_NVP(syndata)); ar(CEREAL_NVP(iteration_)); @@ -611,8 +615,10 @@ class Connections : public Serializable * @retval Number of segments. */ size_t numSegments() const { - NTA_ASSERT(segments_.size() >= destroyedSegments_.size()); - return segments_.size() - destroyedSegments_.size(); } + const long num = static_cast(segments_.size()) - destroyedSegments_; + NTA_ASSERT(num >= 0) << "Number of segments must be greater than zero!"; + return static_cast(num); + } /** * Gets the number of segments on a cell. @@ -718,7 +724,7 @@ class Connections : public Serializable private: std::vector cells_; std::vector segments_; - std::vector destroyedSegments_; + size_t destroyedSegments_ = 0; std::vector synapses_; std::vector destroyedSynapses_; Permanence connectedThreshold_; //TODO make const From 24b39be5d677354c12ae30959e8fdcced3a6ac11 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 10 Aug 2019 16:11:20 +0200 Subject: [PATCH 13/34] Connections: fix crash in destroySynapse lower_bound failed on "does not parition", find() is fine, and simpler code. --- src/htm/algorithms/Connections.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 6d3021cc9f..1440869f7c 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -259,13 +259,10 @@ void Connections::destroySynapse(const Synapse synapse) { potentialSegmentsForPresynapticCell_.erase( presynCell ); } } - - const auto synapseOnSegment = std::lower_bound(segmentData.synapses.cbegin(), + + const auto synapseOnSegment = std::find(segmentData.synapses.cbegin(), segmentData.synapses.cend(), - synapse, - [&](const Synapse a, const Synapse b) -> bool { - return dataForSynapse(a).id < dataForSynapse(b).id; - }); + synapse); NTA_ASSERT(synapseOnSegment != segmentData.synapses.cend()); NTA_ASSERT(*synapseOnSegment == synapse); From 0b53cf77fbb15ce28aeacdd89d01678c283afffd Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 10 Aug 2019 16:42:58 +0200 Subject: [PATCH 14/34] Connections: removed vector destroyedSynapses_ as a premature optimization, keep only as counter of num destroyed. - cleaner code - faster - but slightly wastes memory (but we only destroy 0.3% synapses on MNIST) --- src/htm/algorithms/Connections.cpp | 17 +++++------------ src/htm/algorithms/Connections.hpp | 6 +++--- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 1440869f7c..9d066a57ae 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -41,7 +41,6 @@ void Connections::initialize(CellIdx numCells, Permanence connectedThreshold, bo cells_ = vector(numCells); segments_.clear(); synapses_.clear(); - destroyedSynapses_.clear(); potentialSynapsesForPresynapticCell_.clear(); connectedSynapsesForPresynapticCell_.clear(); potentialSegmentsForPresynapticCell_.clear(); @@ -127,16 +126,10 @@ Synapse Connections::createSynapse(Segment segment, CellIdx presynapticCell, Permanence permanence) { // Get an index into the synapses_ list, for the new synapse to reside at. - Synapse synapse; - if (!destroyedSynapses_.empty() ) { - synapse = destroyedSynapses_.back(); - destroyedSynapses_.pop_back(); - } else { - NTA_CHECK(synapses_.size() < std::numeric_limits::max()) << "Add synapse failed: Range of Synapse (data-type) insufficient size." + NTA_CHECK(synapses_.size() < std::numeric_limits::max()) << "Add synapse failed: Range of Synapse (data-type) insufficient size." << synapses_.size() << " < " << (size_t)std::numeric_limits::max(); - synapse = static_cast(synapses_.size()); - synapses_.push_back(SynapseData()); - } + const Synapse synapse = static_cast(synapses_.size()); + synapses_.push_back(SynapseData()); // Fill in the new synapse's data SynapseData &synapseData = synapses_[synapse]; @@ -268,7 +261,7 @@ void Connections::destroySynapse(const Synapse synapse) { NTA_ASSERT(*synapseOnSegment == synapse); segmentData.synapses.erase(synapseOnSegment); - destroyedSynapses_.push_back(synapse); + destroyedSynapses_++; } @@ -713,7 +706,7 @@ std::ostream& operator<< (std::ostream& stream, const Connections& self) << "%) Saturated (" << (Real) synapsesSaturated / self.numSynapses() << "%)" << std::endl; stream << " Synapses pruned (" << (Real) self.prunedSyns_ / self.numSynapses() << "%) Segments pruned (" << (Real) self.prunedSegs_ / self.numSegments() << "%)" << std::endl; - stream << " Buffer for destroyed synapses: " << self.destroyedSynapses_.size() + stream << " Buffer for destroyed synapses: " << self.destroyedSynapses_ << " Buffer for destroyed segments: " << self.destroyedSegments_ << std::endl; return stream; diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 1c9149bc0d..6de75e9053 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -635,8 +635,8 @@ class Connections : public Serializable * @retval Number of synapses. */ size_t numSynapses() const { - NTA_ASSERT(synapses_.size() >= destroyedSynapses_.size()); - return synapses_.size() - destroyedSynapses_.size(); + NTA_ASSERT(synapses_.size() >= destroyedSynapses_); + return synapses_.size() - destroyedSynapses_; } /** @@ -726,7 +726,7 @@ class Connections : public Serializable std::vector segments_; size_t destroyedSegments_ = 0; std::vector synapses_; - std::vector destroyedSynapses_; + size_t destroyedSynapses_ = 0; Permanence connectedThreshold_; //TODO make const UInt32 iteration_ = 0; From 2d1bd5a564dc0675f5ab4d7e1fb75ed821c5e845 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sat, 10 Aug 2019 18:26:08 +0200 Subject: [PATCH 15/34] fix exact output results from CI --- src/test/unit/algorithms/SpatialPoolerTest.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 04015c94e1..b3f12e3030 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -2059,7 +2059,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Silver is an SDR that is loaded by direct initalization from a vector. SDR silver_sdr({ 200 }); SDR_sparse_t data = { - 11, 17, 31, 63, 78, 125, 126, 153, 173, 193 + 30, 31, 34, 113, 125, 126, 173, 183, 188, 193 }; silver_sdr.setSparse(data); @@ -2067,7 +2067,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Gold tests initalizing an SDR from a manually created string in JSON format. // hint: you can generate this string using // silver_sdr.save(std::cout, JSON); - string gold = "{\"dimensions\": [200],\"sparse\": [11, 17, 31, 63, 78, 125, 126, 153, 173, 193]}"; + string gold = "{\"dimensions\": [200],\"sparse\": [30, 31, 34, 113, 125, 126, 173, 183, 188, 193]}"; std::stringstream gold_stream( gold ); SDR gold_sdr; gold_sdr.load( gold_stream, JSON ); From a400325355b8b1f4c4ddd36e829e53a3274f6f6d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sun, 11 Aug 2019 01:12:14 +0200 Subject: [PATCH 16/34] SP cannot call adaptSegment with pruning, as pruning seems not platform-independent. --- src/examples/hotgym/HelloSPTP.cpp | 10 +++++----- src/htm/algorithms/SpatialPooler.cpp | 2 +- src/test/unit/algorithms/SpatialPoolerTest.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/examples/hotgym/HelloSPTP.cpp b/src/examples/hotgym/HelloSPTP.cpp index 5c29b969a5..7edea90645 100644 --- a/src/examples/hotgym/HelloSPTP.cpp +++ b/src/examples/hotgym/HelloSPTP.cpp @@ -200,24 +200,24 @@ EPOCHS = 2; // make test faster in Debug SDR goldSP({COLS}); const SDR_sparse_t deterministicSP{ - 17, 62, 71, 72, 73, 78, 82, 83, 85, 93, 102, 131, 261, 263, 268, 269, 277, 282, 287, 301, 306, 308, 309, 317, 323, 331, 336, 337, 338, 339, 340, 352, 359, 366, 432, 443, 493, 502, 523, 811, 928, 955, 1089, 1095, 1114, 1115, 1120, 1133, 1134, 1428, 1508, 1512, 1651, 1677, 1745, 1760, 1774, 1804, 1805, 1858, 1861, 1925, 1927, 1929, 1936, 1937, 1940, 1950, 1951, 1953,1956, 1961, 1967, 1969, 1971, 1975, 1978, 1979, 1980, 1981, 1982, 1984, 1985, 1987, 1988, 1990, 1994, 1996, 1997, 1998, 1999, 2000, 2002, 2006, 2008, 2011, 2012, 2013, 2016, 2022, 2027, 2034 + 62, 72, 73, 82, 85, 102, 263, 277, 287, 303, 306, 308, 309, 322, 337, 339, 340, 352, 370, 493, 1094, 1095, 1114, 1115, 1120, 1463, 1512, 1518, 1647, 1651, 1691, 1694, 1729, 1745, 1746, 1760, 1770, 1774, 1775, 1781, 1797, 1798, 1803, 1804, 1805, 1812, 1827, 1828, 1831, 1832, 1858, 1859, 1860, 1861, 1862, 1875, 1878, 1880, 1881, 1898, 1918, 1923, 1929, 1931,1936, 1950, 1953, 1956, 1958, 1961, 1964, 1965, 1967, 1971, 1973, 1975, 1976, 1979, 1980, 1981, 1982, 1984, 1985, 1986, 1988, 1991, 1994, 1996, 1997, 1998, 1999, 2002, 2006, 2008, 2011, 2012, 2013, 2017, 2019, 2022, 2027, 2030 }; goldSP.setSparse(deterministicSP); SDR goldSPlocal({COLS}); const SDR_sparse_t deterministicSPlocal{ - 13, 62, 71, 72, 73, 78, 80, 134, 140, 167, 169, 179, 189, 194, 261, 263, 268, 269, 308, 323, 328, 337, 339, 365, 407, 423, 425, 432, 434, 443, 493, 494, 508, 512, 514, 520, 585, 598, 601, 610, 630, 644, 645, 673, 675, 691, 701, 707, 748, 749, 777, 809, 811, 833, 838, 841, 853, 889, 906, 921, 926, 928, 952, 958, 967, 989, 1005, 1076, 1089, 1095, 1114, 1115,1120, 1133, 1146, 1181, 1184, 1196, 1203, 1217, 1249, 1252, 1253, 1263, 1282, 1291, 1306, 1309, 1331, 1337, 1401, 1402, 1410, 1434, 1462, 1469, 1487, 1494, 1508, 1512, 1518, 1547, 1563, 1564, 1623, 1624, 1626, 1651, 1672, 1677, 1693, 1694, 1745, 1746, 1750, 1760, 1768, 1802, 1805, 1831, 1858, 1861, 1869, 1880, 1889, 1929, 1950, 1956, 1961, 1994, 2002, 2011, 2012, 2027 + 12, 13, 71, 72, 75, 78, 82, 85, 131, 171, 182, 186, 189, 194, 201, 263, 277, 287, 308, 319, 323, 337, 339, 365, 407, 423, 429, 432, 434, 445, 493, 494, 502, 508, 523, 542, 554, 559, 585, 586, 610, 611, 612, 644, 645, 647, 691, 698, 699, 701, 702, 707, 777, 809, 810, 811, 833, 839, 841, 920, 923, 928, 929, 935, 955, 1003, 1005, 1073, 1076, 1094, 1095, 1114,1115, 1133, 1134, 1184, 1203, 1214, 1232, 1233, 1244, 1253, 1268, 1278, 1291, 1294, 1306, 1309, 1331, 1402, 1410, 1427, 1434, 1442, 1463, 1508, 1512, 1514, 1515, 1518, 1561, 1564, 1590, 1623, 1626, 1630, 1647, 1651, 1691, 1694, 1729, 1745, 1746, 1760, 1797, 1804, 1805, 1812, 1827, 1858, 1860, 1861, 1862, 1918, 1956, 1961, 1965, 1971, 1975, 1994, 2012 }; goldSPlocal.setSparse(deterministicSPlocal); SDR goldTM({COLS}); const SDR_sparse_t deterministicTM{ - 17, 37, 62, 72, 73, 93, 134, 261, 268, 269, 301, 309, 323, 331, 337, 338, 432, 493, 502, 523, 532, 542, 645, 754, 833, 921, 952, 1102, 1114, 1507, 1508, 1512, 1627, 1693, 1858, 1861, 1870, 1917, 1925, 1927, 1929, 1937, 1938, 1941, 1943, 1944, 1945, 1946, 1947, 1950, 1952, 1955, 1956, 1959, 1960, 1964, 1966, 1969, 1971, 1974, 1975, 1978, 1979, 1987, 1990, 1991,1995, 1996, 1998, 1999, 2002, 2012, 2027, 2040, 2042 + 62, 77, 85, 322, 340, 432, 952, 1120, 1488, 1502, 1512, 1518, 1547, 1627, 1633, 1668, 1727, 1729, 1797, 1803, 1805, 1812, 1858, 1859, 1896, 1918, 1923, 1925, 1929, 1931, 1939, 1941, 1942, 1944, 1950, 1953, 1955, 1956, 1965, 1966, 1967, 1968, 1974, 1980, 1987, 1996, 2006, 2008, 2011, 2027, 2030, 2042, 2046 }; goldTM.setSparse(deterministicTM); - const float goldAn = 0.5f; - const float goldAnAvg = 0.386638f; + const float goldAn = 0.627451f; + const float goldAnAvg = 0.407265f; #ifdef _ARCH_DETERMINISTIC if(EPOCHS == 5000) { diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 046358b7d9..3b8cec17d5 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -703,7 +703,7 @@ Real SpatialPooler::avgConnectedSpanForColumnND_(UInt column) const { void SpatialPooler::adaptSynapses_(const SDR &input, const SDR &active) { for(const auto &column : active.getSparse()) { - connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_, true, stimulusThreshold_); + connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_); //! , true, stimulusThreshold_); //FIXME pruning not platform-independent! connections_.raisePermanencesToThreshold( column, stimulusThreshold_ ); } } diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index b3f12e3030..667a40dd3b 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -2059,7 +2059,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Silver is an SDR that is loaded by direct initalization from a vector. SDR silver_sdr({ 200 }); SDR_sparse_t data = { - 30, 31, 34, 113, 125, 126, 173, 183, 188, 193 + 4, 64, 74, 78, 85, 113, 125, 126, 127, 153 }; silver_sdr.setSparse(data); @@ -2067,7 +2067,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Gold tests initalizing an SDR from a manually created string in JSON format. // hint: you can generate this string using // silver_sdr.save(std::cout, JSON); - string gold = "{\"dimensions\": [200],\"sparse\": [30, 31, 34, 113, 125, 126, 173, 183, 188, 193]}"; + string gold = "{\"dimensions\": [200],\"sparse\": [4, 64, 74, 78, 85, 113, 125, 126, 127, 153]}"; std::stringstream gold_stream( gold ); SDR gold_sdr; gold_sdr.load( gold_stream, JSON ); From c3e0cbb9aadcb1007b9141701802cdf752a8ae5a Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Tue, 17 Sep 2019 14:36:12 +0200 Subject: [PATCH 17/34] SP: WIP enable segment pruning may break platform-independent builds! --- src/htm/algorithms/SpatialPooler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 3b8cec17d5..f70508989b 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -703,7 +703,7 @@ Real SpatialPooler::avgConnectedSpanForColumnND_(UInt column) const { void SpatialPooler::adaptSynapses_(const SDR &input, const SDR &active) { for(const auto &column : active.getSparse()) { - connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_); //! , true, stimulusThreshold_); //FIXME pruning not platform-independent! + connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_, true, 0); //! stimulusThreshold_); //FIXME segment pruning not platform-independent! connections_.raisePermanencesToThreshold( column, stimulusThreshold_ ); } } From 9691459cb6860de025184462511306037d5886b2 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 11:51:47 +0200 Subject: [PATCH 18/34] Connections: cosmetic cleanup synapsesForSegment() --- src/htm/algorithms/Connections.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 5cc1e65f7c..ebbb21da20 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -435,9 +435,8 @@ void Connections::adaptSegment(const Segment segment, currentUpdates_.resize( synapses_.size(), minPermanence ); } - const auto& synapses = synapsesForSegment(segment); vector destroyLater; - for(const auto synapse: synapses) { + for(const auto synapse: synapsesForSegment(segment)) { const SynapseData &synapseData = dataForSynapse(synapse); Permanence update; @@ -472,7 +471,7 @@ void Connections::adaptSegment(const Segment segment, } //destroy segment if it has too few synapses left -> will never be able to connect again - if(pruneZeroSynapses and synapses.size() < connectedThreshold_) { //FIXME this is incorrect! connectedThreshold_ is if > then syn = connected. We need stimulusThreshold_ from TM. + if(pruneZeroSynapses and synapsesForSegment(segment).size() < connectedThreshold_) { //FIXME this is incorrect! connectedThreshold_ is if > then syn = connected. We need stimulusThreshold_ from TM. destroySegment(segment); prunedSegs_++; //statistics } @@ -589,9 +588,8 @@ void Connections::synapseCompetition( void Connections::bumpSegment(const Segment segment, const Permanence delta) { - const vector &synapses = synapsesForSegment(segment); // TODO: vectorize? - for( const auto syn : synapses ) { + for( const auto syn : synapsesForSegment(segment) ) { updateSynapsePermanence(syn, synapses_[syn].permanence + delta); } } From b91144db1e5bc217d28bf4a1f703b0280ff841e8 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 13:10:01 +0200 Subject: [PATCH 19/34] fix bug in Conn.pruneLRUSegment_() the debug code was called after! the segment has been already removed --- src/htm/algorithms/Connections.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 883b9b4dd5..e35864f7dd 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -84,8 +84,6 @@ void Connections::pruneLRUSegment_(const CellIdx& cell) { const auto leastRecentlyUsedSegment = std::min_element(destroyCandidates.cbegin(), destroyCandidates.cend(), compareSegmentsByLRU); - destroySegment(*leastRecentlyUsedSegment); - NTA_ASSERT(destroyCandidates.size() < numBefore) << "A segment should have been pruned, but wasn't!"; #ifdef NTA_ASSERTIONS_ON if(destroyCandidates.size() > 0) { // the removed seg should be the "oldest", least recently used. So any other is more recent. We don't check all, but randomly ([0]) @@ -93,6 +91,8 @@ void Connections::pruneLRUSegment_(const CellIdx& cell) { << "Should remove the least recently used segment,but older exists."; } #endif + destroySegment(*leastRecentlyUsedSegment); + NTA_ASSERT(destroyCandidates.size() < numBefore) << "A segment should have been pruned, but wasn't!"; } Segment Connections::createSegment(const CellIdx cell, From 46e4ca5be3c3bc7f0b3bcf836b78d4119a3dd71f Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 13:20:55 +0200 Subject: [PATCH 20/34] SP: enable segment pruning for Conn.adaptSegment by setting segmentThreshold param. This should be ON, but there are some issues with platform independent reproducible results --- src/examples/hello/HelloSPTP.cpp | 10 +++++----- src/htm/algorithms/SpatialPooler.cpp | 2 +- src/test/unit/algorithms/SpatialPoolerTest.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index 018b60479a..dfaf80681a 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -201,24 +201,24 @@ EPOCHS = 2; // make test faster in Debug SDR goldSP({COLS}); const SDR_sparse_t deterministicSP{ - 62, 72, 73, 82, 85, 102, 263, 277, 287, 303, 306, 308, 309, 322, 337, 339, 340, 352, 370, 493, 1094, 1095, 1114, 1115, 1120, 1463, 1512, 1518, 1647, 1651, 1691, 1694, 1729, 1745, 1746, 1760, 1770, 1774, 1775, 1781, 1797, 1798, 1803, 1804, 1805, 1812, 1827, 1828, 1831, 1832, 1858, 1859, 1860, 1861, 1862, 1875, 1878, 1880, 1881, 1898, 1918, 1923, 1929, 1931,1936, 1950, 1953, 1956, 1958, 1961, 1964, 1965, 1967, 1971, 1973, 1975, 1976, 1979, 1980, 1981, 1982, 1984, 1985, 1986, 1988, 1991, 1994, 1996, 1997, 1998, 1999, 2002, 2006, 2008, 2011, 2012, 2013, 2017, 2019, 2022, 2027, 2030 + 17, 62, 71, 72, 73, 78, 82, 83, 85, 93, 102, 131, 261, 263, 268, 269, 277, 282, 287, 301, 306, 308, 309, 317, 323, 331, 336, 337, 338, 339, 340, 352, 359, 366, 432, 443, 493, 502, 523, 811, 928, 955, 1089, 1095, 1114, 1115, 1120, 1133, 1134, 1428, 1508, 1512, 1651, 1677, 1745, 1760, 1774, 1804, 1805, 1858, 1861, 1925, 1927, 1929, 1936, 1937, 1940, 1950, 1951, 1953, 1956, 1961, 1967, 1969, 1971, 1975, 1978, 1979, 1980, 1981, 1982, 1984, 1985, 1987, 1988, 1990, 1994, 1996, 1997, 1998, 1999, 2000, 2002, 2006, 2008, 2011, 2012, 2013, 2016, 2022, 2027, 2034 }; goldSP.setSparse(deterministicSP); SDR goldSPlocal({COLS}); const SDR_sparse_t deterministicSPlocal{ - 12, 13, 71, 72, 75, 78, 82, 85, 131, 171, 182, 186, 189, 194, 201, 263, 277, 287, 308, 319, 323, 337, 339, 365, 407, 429, 432, 434, 443, 445, 493, 494, 502, 508, 523, 542, 554, 559, 585, 586, 610, 611, 612, 644, 645, 647, 691, 698, 699, 701, 702, 707, 777, 809, 810, 811, 833, 839, 841, 920, 923, 928, 929, 935, 955, 1003, 1005, 1073, 1076, 1094, 1095, 1114, 1115, 1133, 1134, 1184, 1203, 1232, 1233, 1244, 1253, 1268, 1278, 1291, 1294, 1306, 1309, 1331, 1402, 1410, 1427, 1434, 1442, 1463, 1508, 1512, 1514, 1515, 1518, 1561, 1564, 1623, 1626, 1630, 1640, 1647, 1691, 1694, 1729, 1745, 1746, 1760, 1797, 1804, 1805, 1812, 1827, 1831, 1858, 1861, 1862, 1918, 1956, 1961, 1965, 1971, 1975, 1994, 2012 + 13, 62, 71, 72, 73, 78, 80, 134, 140, 167, 169, 179, 189, 194, 261, 263, 268, 269, 308, 323, 328, 337, 339, 365, 407, 423, 425, 432, 434, 443, 493, 494, 508, 512, 514, 520, 585,598, 601, 610, 630, 644, 645, 673, 675, 691, 701, 707, 748, 749, 777, 809, 811, 833, 838, 841, 853, 889, 906, 921, 926, 928, 952, 958, 967, 989, 1005, 1076, 1089, 1095, 1114, 1115, 1120, 1133, 1146, 1181, 1184, 1196, 1203, 1217, 1249, 1252, 1253, 1263, 1282, 1291, 1306, 1309, 1331, 1337, 1401, 1402, 1410, 1434, 1462, 1469, 1487, 1494, 1508, 1512, 1518, 1547, 1563, 1564, 1623, 1624, 1626, 1651, 1672, 1677, 1693, 1694, 1745, 1746, 1750, 1760, 1768, 1802, 1805, 1831, 1858, 1861, 1869, 1880, 1889, 1929, 1950, 1956, 1961, 1994, 2002, 2011, 2012, 2027 }; goldSPlocal.setSparse(deterministicSPlocal); SDR goldTM({COLS}); const SDR_sparse_t deterministicTM{ - 87, 93, 102, 282, 303, 308, 337, 340, 502, 542, 952, 1115, 1502, 1518, 1626, 1691, 1694, 1711, 1727, 1760, 1775, 1781, 1804, 1805, 1827, 1831, 1832, 1844, 1851, 1858, 1859, 1918, 1929, 1931, 1941, 1943, 1945, 1952, 1953, 1955, 1956, 1958, 1960, 1961, 1965, 1973, 1975, 1976, 1979, 1980, 1984, 1985, 1986, 1987, 1994, 1996, 1998, 2002, 2006, 2013, 2042 + 82, 83, 85, 102, 131, 147, 268, 269, 282, 286, 301, 323, 337, 339, 340, 352, 493, 502, 523, 645, 754, 811, 833, 1214, 1263, 1544, 1627, 1738, 1808, 1854, 1858, 1867, 1925, 1927, 1931, 1933, 1943, 1947, 1953, 1955, 1956, 1958, 1959, 1961, 1964, 1965, 1966, 1967, 1969, 1970, 1971, 1975, 1976, 1978, 1980, 1981, 1984, 1987, 1990, 1997, 1998, 2008, 2011, 2013, 2025, 2027, 2034, 2040, 2042, 2044 }; goldTM.setSparse(deterministicTM); - const float goldAn = 0.715686f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms - const float goldAnAvg = 0.412719f; // ...the averaged value, on the other hand, should improve/decrease. + const float goldAn = 0.558824f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms + const float goldAnAvg = 0.389571f; // ...the averaged value, on the other hand, should improve/decrease. #ifdef _ARCH_DETERMINISTIC if(e+1 == 5000) { diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index acd93b7eae..9398cdbc68 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -710,7 +710,7 @@ Real SpatialPooler::avgConnectedSpanForColumnND_(const UInt column) const { void SpatialPooler::adaptSynapses_(const SDR &input, const SDR &active) { for(const auto &column : active.getSparse()) { - connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_, true, 0); //! stimulusThreshold_); //FIXME segment pruning not platform-independent! + connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_, true, stimulusThreshold_); connections_.raisePermanencesToThreshold( column, stimulusThreshold_ ); } } diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 76b2cf0bbd..6b428106be 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -2056,7 +2056,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Silver is an SDR that is loaded by direct initalization from a vector. SDR silver_sdr({ 200 }); SDR_sparse_t data = { - 4, 64, 74, 78, 85, 113, 125, 126, 127, 153 + 11, 17, 31, 63, 78, 125, 126, 153, 173, 193 }; silver_sdr.setSparse(data); @@ -2064,7 +2064,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Gold tests initalizing an SDR from a manually created string in JSON format. // hint: you can generate this string using // silver_sdr.save(std::cout, JSON); - string gold = "{\"dimensions\": [200],\"sparse\": [4, 64, 74, 78, 85, 113, 125, 126, 127, 153]}"; + string gold = "{\"dimensions\": [200],\"sparse\": [11, 17, 31, 63, 78, 125, 126, 153, 173, 193]}"; std::stringstream gold_stream( gold ); SDR gold_sdr; gold_sdr.load( gold_stream, JSON ); From b58258cb400abd7449b3b53ba070755d48662f39 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 14:23:16 +0200 Subject: [PATCH 21/34] COnn revert to lower_bound with explicit ordering for platform-independent build? --- src/htm/algorithms/Connections.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index e35864f7dd..39b8d77e07 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -274,9 +274,11 @@ void Connections::destroySynapse(const Synapse synapse) { } } - const auto synapseOnSegment = std::find(segmentData.synapses.cbegin(), + const auto synapseOnSegment = std::lower_bound(segmentData.synapses.cbegin(), segmentData.synapses.cend(), - synapse); + synapse, + [&](const Synapse a, const Synapse b) -> bool { return dataForSynapse(a).id < dataForSynapse(b).id;} + ); NTA_ASSERT(synapseOnSegment != segmentData.synapses.cend()); NTA_ASSERT(*synapseOnSegment == synapse); From 6bc3dc75d3cf4130ed45f869a8aa51340e93e168 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 15:47:52 +0200 Subject: [PATCH 22/34] Connections: add test for destroySynapse --- .../py/tests/algorithms/connections_test.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bindings/py/tests/algorithms/connections_test.py b/bindings/py/tests/algorithms/connections_test.py index 1103b750b9..78bcaebc5a 100644 --- a/bindings/py/tests/algorithms/connections_test.py +++ b/bindings/py/tests/algorithms/connections_test.py @@ -163,6 +163,34 @@ def testAdaptShouldDecrementSynapses(self): self.assertEqual(presynamptic_cells, presynaptic_input_set, "Missing synapses") + def testDestroySynapse(self): + # empty connections + co = Connections(NUM_CELLS, 0.51) + self.assertEqual(co.numSynapses(), 0) + self.assertEqual(co.numSegments(), 0) + + # 1st, create a segment + seg = co.createSegment(NUM_CELLS-1,1) + self.assertEqual(co.numSegments(), 1) + + # create a synapse on that segment + syn1 = co.createSynapse(seg, NUM_CELLS-1, 0.52) + perm1 = co.permanenceForSynapse(syn1) + self.assertEqual(co.numSynapses(), 1) + + #FIXME creating a duplicit synapse should not crash! syn2 = co.createSynapse(seg, NUM_CELLS-1, 0.52) + #assert syn1 ==syn2 + + co.destroySynapse(syn1) + self.assertEqual(co.numSynapses(), 0) + permRemoved = co.permanenceForSynapse(syn1) #FIXME removed syn1 should not have (valid) data + assert permRemoved == perm1 + + with pytest.raises(IndexError): + co.destroySynapse(syn1) + + + def testNumSynapses(self): """ Test that connections are generated on predefined segments. From bcefa8c655c7592219a85230443b1e24f5cc5462 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 15:58:33 +0200 Subject: [PATCH 23/34] COnnections: add test for createSynapse --- .../py/tests/algorithms/connections_test.py | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/bindings/py/tests/algorithms/connections_test.py b/bindings/py/tests/algorithms/connections_test.py index 78bcaebc5a..be30639f32 100644 --- a/bindings/py/tests/algorithms/connections_test.py +++ b/bindings/py/tests/algorithms/connections_test.py @@ -163,14 +163,15 @@ def testAdaptShouldDecrementSynapses(self): self.assertEqual(presynamptic_cells, presynaptic_input_set, "Missing synapses") - def testDestroySynapse(self): + + def testCreateSynapse(self): # empty connections co = Connections(NUM_CELLS, 0.51) self.assertEqual(co.numSynapses(), 0) self.assertEqual(co.numSegments(), 0) # 1st, create a segment - seg = co.createSegment(NUM_CELLS-1,1) + seg = co.createSegment(NUM_CELLS-1, 1) self.assertEqual(co.numSegments(), 1) # create a synapse on that segment @@ -178,15 +179,33 @@ def testDestroySynapse(self): perm1 = co.permanenceForSynapse(syn1) self.assertEqual(co.numSynapses(), 1) - #FIXME creating a duplicit synapse should not crash! syn2 = co.createSynapse(seg, NUM_CELLS-1, 0.52) + #FIXME creating a duplicit synapse should not crash! i + #syn2 = co.createSynapse(seg, NUM_CELLS-1, 0.52) #assert syn1 ==syn2 + def testDestroySynapse(self): + # empty connections + co = Connections(NUM_CELLS, 0.51) + self.assertEqual(co.numSynapses(), 0) + self.assertEqual(co.numSegments(), 0) + + # 1st, create a segment + seg = co.createSegment(NUM_CELLS-1, 1) + self.assertEqual(co.numSegments(), 1) + + # create a synapse on that segment + syn1 = co.createSynapse(seg, NUM_CELLS-1, 0.52) + perm1 = co.permanenceForSynapse(syn1) + self.assertEqual(co.numSynapses(), 1) + + # destroy the synapse co.destroySynapse(syn1) self.assertEqual(co.numSynapses(), 0) + permRemoved = co.permanenceForSynapse(syn1) #FIXME removed syn1 should not have (valid) data assert permRemoved == perm1 - with pytest.raises(IndexError): + with pytest.raises(IndexError): # double remove should fail co.destroySynapse(syn1) From 56b6a1f521606cc01674ddbd63fc969e2196e7f5 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 16:04:16 +0200 Subject: [PATCH 24/34] Connections adaptSegment: improve py binding add arguments with defaults --- .../py/cpp_src/bindings/algorithms/py_Connections.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp index 111e484cd6..979371ff42 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_Connections.cpp @@ -129,7 +129,14 @@ R"(Returns pair of: numActiveConnectedSynapsesForSegment numActivePotentialSynapsesForSegment)"); - py_Connections.def("adaptSegment", &Connections::adaptSegment); + py_Connections.def("adaptSegment", &Connections::adaptSegment, + py::arg("segment"), + py::arg("inputs"), + py::arg("increment"), + py::arg("decrement"), + py::arg("pruneZeroSynapses") = false, + py::arg("segmentThreshold") = 0 + ); py_Connections.def("raisePermanencesToThreshold", &Connections::raisePermanencesToThreshold); From 21cd185dcdee4413d5dadc25572593061b673e6b Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 17:17:45 +0200 Subject: [PATCH 25/34] COnnections: fix dataForSynapse() after destroyed after synapse is destroyed by destroySynapse(syn), its data were still accessible! Which could lead to false operations. Add check to dataForSynapse() that fails if synapse has been removed. Detail: synapseExists_() is available, improve its performance to be widely usable (by dataForSynapse()). Tests for create/destroySynapse --- .../py/tests/algorithms/connections_test.py | 24 +++++++---------- src/htm/algorithms/Connections.cpp | 25 ++++++++++++++---- src/htm/algorithms/Connections.hpp | 26 +++++++++++++------ 3 files changed, 47 insertions(+), 28 deletions(-) diff --git a/bindings/py/tests/algorithms/connections_test.py b/bindings/py/tests/algorithms/connections_test.py index be30639f32..fa8df8faf6 100644 --- a/bindings/py/tests/algorithms/connections_test.py +++ b/bindings/py/tests/algorithms/connections_test.py @@ -84,7 +84,8 @@ def testAdaptShouldRemoveSegments(self): segments = connections.segmentsForCell(cell) self.assertEqual(len(segments), 1, "Segments were prematurely destroyed.") segment = segments[0] - connections.adaptSegment(segment, inputSDR, 0.1, 0.001, True) + numSynapsesOnSegment = len(segments) + connections.adaptSegment(segment, inputSDR, 0.1, 0.001, pruneZeroSynapses=True, segmentThreshold= numSynapsesOnSegment+1) #set to +1 so that segments get always deleted in this test segments = connections.segmentsForCell(cell) self.assertEqual(len(segments), 0, "Segments were not destroyed.") @@ -165,7 +166,7 @@ def testAdaptShouldDecrementSynapses(self): def testCreateSynapse(self): - # empty connections + # empty connections, create segment and a synapse co = Connections(NUM_CELLS, 0.51) self.assertEqual(co.numSynapses(), 0) self.assertEqual(co.numSegments(), 0) @@ -183,29 +184,22 @@ def testCreateSynapse(self): #syn2 = co.createSynapse(seg, NUM_CELLS-1, 0.52) #assert syn1 ==syn2 + def testDestroySynapse(self): - # empty connections + # empty connections, create segment seg and a synapse syn co = Connections(NUM_CELLS, 0.51) - self.assertEqual(co.numSynapses(), 0) - self.assertEqual(co.numSegments(), 0) - - # 1st, create a segment seg = co.createSegment(NUM_CELLS-1, 1) - self.assertEqual(co.numSegments(), 1) - - # create a synapse on that segment syn1 = co.createSynapse(seg, NUM_CELLS-1, 0.52) - perm1 = co.permanenceForSynapse(syn1) - self.assertEqual(co.numSynapses(), 1) # destroy the synapse co.destroySynapse(syn1) self.assertEqual(co.numSynapses(), 0) - permRemoved = co.permanenceForSynapse(syn1) #FIXME removed syn1 should not have (valid) data - assert permRemoved == perm1 + with pytest.raises(RuntimeError): # NTA_CHECK, data for removed synapse must not be accessible! + permRemoved = co.permanenceForSynapse(syn1) + assert permRemoved == perm1 - with pytest.raises(IndexError): # double remove should fail + with pytest.raises(RuntimeError): # NTA_CHECK, double remove should fail co.destroySynapse(syn1) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 39b8d77e07..b15adcd80a 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -184,12 +184,23 @@ bool Connections::segmentExists_(const Segment segment) const { segmentsOnCell.cend()); } -bool Connections::synapseExists_(const Synapse synapse) const { +bool Connections::synapseExists_(const Synapse synapse, bool fast) const { + if(synapse >= synapses_.size()) return false; //out of bounds. Can happen after serialization, where only existing synapses are stored. + +#ifdef NTA_ASSERTIONS_ON + fast = false; //in Debug, do the proper, slow check always +#endif + if(!fast) { + //proper but slow method to check for valid, existing synapse const SynapseData &synapseData = synapses_[synapse]; const vector &synapsesOnSegment = segments_[synapseData.segment].synapses; return (std::find(synapsesOnSegment.begin(), synapsesOnSegment.end(), synapse) != synapsesOnSegment.end()); + } else { + //quick method. Relies on hack in destroySynapse() where we set synapseData.permanence == -1 + return synapses_[synapse].permanence != -1; + } } /** @@ -240,14 +251,15 @@ void Connections::destroySegment(const Segment segment) { void Connections::destroySynapse(const Synapse synapse) { - NTA_ASSERT(synapseExists_(synapse)); + NTA_CHECK(synapseExists_(synapse, true)); + for (auto h : eventHandlers_) { h.second->onDestroySynapse(synapse); } - const SynapseData &synapseData = synapses_[synapse]; - SegmentData &segmentData = segments_[synapseData.segment]; - const auto presynCell = synapseData.presynapticCell; + SynapseData& synapseData = synapses_[synapse]; //like dataForSynapse() but here we need writeable access + SegmentData &segmentData = segments_[synapseData.segment]; + const auto presynCell = synapseData.presynapticCell; if( synapseData.permanence >= connectedThreshold_ ) { segmentData.numConnected--; @@ -284,6 +296,9 @@ void Connections::destroySynapse(const Synapse synapse) { NTA_ASSERT(*synapseOnSegment == synapse); segmentData.synapses.erase(synapseOnSegment); + //Note: dataForSynapse(synapse) are not deleted, unfortunately. And are still accessible. + //To mark them as "removed", we set SynapseData.permanence = -1, this can be used for a quick check later + synapseData.permanence = -1; //marking as "removed" destroyedSynapses_++; } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index adb0f8e318..346b2b27e0 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -360,10 +360,10 @@ class Connections : public Serializable * * @retval Segment data. */ - const SegmentData &dataForSegment(const Segment segment) const { + inline const SegmentData &dataForSegment(const Segment segment) const { return segments_[segment]; } - SegmentData& dataForSegment(const Segment segment) { //editable access, needed by SP + inline SegmentData& dataForSegment(const Segment segment) { //editable access, needed by SP return segments_[segment]; } @@ -374,7 +374,8 @@ class Connections : public Serializable * * @retval Synapse data. */ - const SynapseData &dataForSynapse(const Synapse synapse) const { + inline const SynapseData& dataForSynapse(const Synapse synapse) const { + NTA_CHECK(synapseExists_(synapse, true)); return synapses_[synapse]; } @@ -386,7 +387,7 @@ class Connections : public Serializable * * @retval Segment */ - Segment getSegment(const CellIdx cell, const SegmentIdx idx) const { + inline Segment getSegment(const CellIdx cell, const SegmentIdx idx) const { return cells_[cell].segments[idx]; } @@ -395,7 +396,7 @@ class Connections : public Serializable * * @retval A vector length */ - size_t segmentFlatListLength() const { return segments_.size(); }; + inline size_t segmentFlatListLength() const { return segments_.size(); }; /** * Compare two segments. Returns true if a < b. @@ -694,13 +695,22 @@ class Connections : public Serializable bool segmentExists_(const Segment segment) const; /** - * Check whether this synapse still exists on its segment. + * Check whether this synapse still exists "in Connections" ( on its segment). + * After calling `synapseCreate()` this should be True, after `synapseDestroy()` + * its False. + * + * Note: * * @param Synapse + * @param fast - bool, default false. If false, run the slow, proper check that is always correct. + * If true, we use a "hack" for speed, where destroySynapse sets synapseData.permanence=-1, + * so we can check and compare alter, if ==-1 then synapse is "removed". + * The problem is that synapseData are never truly removed. + * #TODO instead of vector synapses_, try map, that way, we can properly remove (and check). * - * @retval True if it's still in its segment's synapse list. + * @retval True if synapse is valid (not removed, it's still in its segment's synapse list) */ - bool synapseExists_(const Synapse synapse) const; + bool synapseExists_(const Synapse synapse, bool fast = false) const; /** * Remove a synapse from presynaptic maps. From 7463e5f9cd2275b052c32df154e0be3514067992 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 17:43:56 +0200 Subject: [PATCH 26/34] Connections: test for createSynapse --- .../py/tests/algorithms/connections_test.py | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/bindings/py/tests/algorithms/connections_test.py b/bindings/py/tests/algorithms/connections_test.py index fa8df8faf6..949f3dd341 100644 --- a/bindings/py/tests/algorithms/connections_test.py +++ b/bindings/py/tests/algorithms/connections_test.py @@ -175,14 +175,29 @@ def testCreateSynapse(self): seg = co.createSegment(NUM_CELLS-1, 1) self.assertEqual(co.numSegments(), 1) - # create a synapse on that segment + #1. create a synapse on that segment syn1 = co.createSynapse(seg, NUM_CELLS-1, 0.52) - perm1 = co.permanenceForSynapse(syn1) + self.assertEqual(pytest.approx(co.permanenceForSynapse(syn1)), 0.52) self.assertEqual(co.numSynapses(), 1) - #FIXME creating a duplicit synapse should not crash! i - #syn2 = co.createSynapse(seg, NUM_CELLS-1, 0.52) - #assert syn1 ==syn2 + #2. creating a duplicit synapse should not crash! + syn2 = co.createSynapse(seg, NUM_CELLS-1, 0.52) + self.assertEqual( syn1, syn2, "creating duplicate synapses should return the same") + self.assertEqual(co.numSynapses(), 1, "Duplicate synapse, number should not increase") + + #3. create a different synapse + syn3 = co.createSynapse(seg, 1, 0.52) + self.assertNotEqual( syn1, syn3, "creating a different synapse must create a new one") + self.assertEqual(co.numSynapses(), 2, "New synapse should increase the number") + + #4. create existing synapse with a new value -> should update permanence + #4.a lower permanence -> keep max() + syn4 = co.createSynapse(seg, NUM_CELLS-1, 0.11) #all the same just permanence is a lower val + self.assertEqual( syn1, syn4, "just updating existing syn") + self.assertEqual(co.numSynapses(), 2, "Duplicate synapse, number should not increase") + self.assertEqual(pytest.approx(co.permanenceForSynapse(syn1)), 0.52, "update keeps the larger value") + + def testDestroySynapse(self): From 1d9518b2fe318bdca1f6c0a9a93263c6778ae148 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 17:51:34 +0200 Subject: [PATCH 27/34] COnnections.createSynapse : update exiting if higher permanence if the new permanence is higher that the permanence of the old (same) synapse, update to the higher value. --- bindings/py/tests/algorithms/connections_test.py | 5 +++++ src/htm/algorithms/Connections.cpp | 12 +++++++++++- src/htm/algorithms/Connections.hpp | 6 ++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/bindings/py/tests/algorithms/connections_test.py b/bindings/py/tests/algorithms/connections_test.py index 949f3dd341..f0bad8294f 100644 --- a/bindings/py/tests/algorithms/connections_test.py +++ b/bindings/py/tests/algorithms/connections_test.py @@ -197,6 +197,11 @@ def testCreateSynapse(self): self.assertEqual(co.numSynapses(), 2, "Duplicate synapse, number should not increase") self.assertEqual(pytest.approx(co.permanenceForSynapse(syn1)), 0.52, "update keeps the larger value") + #4.b higher permanence -> update + syn5 = co.createSynapse(seg, NUM_CELLS-1, 0.99) #all the same just permanence is a higher val + self.assertEqual( syn1, syn5, "just updating existing syn") + self.assertEqual(co.numSynapses(), 2, "Duplicate synapse, number should not increase") + self.assertEqual(pytest.approx(co.permanenceForSynapse(syn1)), 0.99, "updated to the larger permanence value") diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index b15adcd80a..e0fa529e63 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -139,7 +139,17 @@ Synapse Connections::createSynapse(Segment segment, for (const Synapse& syn : synapsesForSegment(segment)) { const CellIdx existingPresynapticCell = dataForSynapse(syn).presynapticCell; //TODO 1; add way to get all presynaptic cells for segment (fast) if (presynapticCell == existingPresynapticCell) { - return syn; //synapse (connecting to this presyn cell) already exists on the segment; don't create a new one, exit early and return the existing + //synapse (connecting to this presyn cell) already exists on the segment; don't create a new one, exit early and return the existing + NTA_ASSERT(synapseExists_(syn)); + //TODO what is the strategy on creating a new synapse, while the same already exists (on the same segment, presynapticCell) ?? + //1. just keep the older (former default) + //2. throw an error (ideally, user should not createSynapse() but rather updateSynapsePermanence()) + //3. create a duplicit new synapse -- NO. This is the only choice that is incorrect! HTM works on binary synapses, duplicates would break that. + //4. update to the max of the permanences (default) + + auto& synData = synapses_[syn]; + if(permanence > synData.permanence) updateSynapsePermanence(syn, permanence); + return syn; } } //else: the new synapse is not duplicit, so keep creating it. diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 346b2b27e0..ba53ec5081 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -267,9 +267,10 @@ class Connections : public Serializable * * @param segment Segment to create synapse on. * @param presynapticCell Cell to synapse on. - * @param permanence Initial permanence of new synapse. + * @param permanence Initial permanence of new synapse. If calling "create" on an existing synapse (same segment, presynapticCell), + * then we either keep the old one, or update the old one to have higher permanence (from the new call). * - * @return Created synapse. //TODO consider changing to void, or explain what's returned + * @return Created synapse - index to the newly created synapse. Use `dataForSynapse(returnedValue)` to work with it. */ Synapse createSynapse(const Segment segment, const CellIdx presynapticCell, @@ -286,6 +287,7 @@ class Connections : public Serializable * Destroys synapse. * * @param synapse Synapse to destroy. + * @throws if synapse does not exist (ie already removed) */ void destroySynapse(const Synapse synapse); From b0d1338c8d2bd40e09847c62bb809f1ee7b390e3 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 18:01:49 +0200 Subject: [PATCH 28/34] revert changes to SP and deterministic results --- src/examples/hello/HelloSPTP.cpp | 10 +++++----- src/htm/algorithms/SpatialPooler.cpp | 2 +- src/test/unit/algorithms/SpatialPoolerTest.cpp | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index dfaf80681a..018b60479a 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -201,24 +201,24 @@ EPOCHS = 2; // make test faster in Debug SDR goldSP({COLS}); const SDR_sparse_t deterministicSP{ - 17, 62, 71, 72, 73, 78, 82, 83, 85, 93, 102, 131, 261, 263, 268, 269, 277, 282, 287, 301, 306, 308, 309, 317, 323, 331, 336, 337, 338, 339, 340, 352, 359, 366, 432, 443, 493, 502, 523, 811, 928, 955, 1089, 1095, 1114, 1115, 1120, 1133, 1134, 1428, 1508, 1512, 1651, 1677, 1745, 1760, 1774, 1804, 1805, 1858, 1861, 1925, 1927, 1929, 1936, 1937, 1940, 1950, 1951, 1953, 1956, 1961, 1967, 1969, 1971, 1975, 1978, 1979, 1980, 1981, 1982, 1984, 1985, 1987, 1988, 1990, 1994, 1996, 1997, 1998, 1999, 2000, 2002, 2006, 2008, 2011, 2012, 2013, 2016, 2022, 2027, 2034 + 62, 72, 73, 82, 85, 102, 263, 277, 287, 303, 306, 308, 309, 322, 337, 339, 340, 352, 370, 493, 1094, 1095, 1114, 1115, 1120, 1463, 1512, 1518, 1647, 1651, 1691, 1694, 1729, 1745, 1746, 1760, 1770, 1774, 1775, 1781, 1797, 1798, 1803, 1804, 1805, 1812, 1827, 1828, 1831, 1832, 1858, 1859, 1860, 1861, 1862, 1875, 1878, 1880, 1881, 1898, 1918, 1923, 1929, 1931,1936, 1950, 1953, 1956, 1958, 1961, 1964, 1965, 1967, 1971, 1973, 1975, 1976, 1979, 1980, 1981, 1982, 1984, 1985, 1986, 1988, 1991, 1994, 1996, 1997, 1998, 1999, 2002, 2006, 2008, 2011, 2012, 2013, 2017, 2019, 2022, 2027, 2030 }; goldSP.setSparse(deterministicSP); SDR goldSPlocal({COLS}); const SDR_sparse_t deterministicSPlocal{ - 13, 62, 71, 72, 73, 78, 80, 134, 140, 167, 169, 179, 189, 194, 261, 263, 268, 269, 308, 323, 328, 337, 339, 365, 407, 423, 425, 432, 434, 443, 493, 494, 508, 512, 514, 520, 585,598, 601, 610, 630, 644, 645, 673, 675, 691, 701, 707, 748, 749, 777, 809, 811, 833, 838, 841, 853, 889, 906, 921, 926, 928, 952, 958, 967, 989, 1005, 1076, 1089, 1095, 1114, 1115, 1120, 1133, 1146, 1181, 1184, 1196, 1203, 1217, 1249, 1252, 1253, 1263, 1282, 1291, 1306, 1309, 1331, 1337, 1401, 1402, 1410, 1434, 1462, 1469, 1487, 1494, 1508, 1512, 1518, 1547, 1563, 1564, 1623, 1624, 1626, 1651, 1672, 1677, 1693, 1694, 1745, 1746, 1750, 1760, 1768, 1802, 1805, 1831, 1858, 1861, 1869, 1880, 1889, 1929, 1950, 1956, 1961, 1994, 2002, 2011, 2012, 2027 + 12, 13, 71, 72, 75, 78, 82, 85, 131, 171, 182, 186, 189, 194, 201, 263, 277, 287, 308, 319, 323, 337, 339, 365, 407, 429, 432, 434, 443, 445, 493, 494, 502, 508, 523, 542, 554, 559, 585, 586, 610, 611, 612, 644, 645, 647, 691, 698, 699, 701, 702, 707, 777, 809, 810, 811, 833, 839, 841, 920, 923, 928, 929, 935, 955, 1003, 1005, 1073, 1076, 1094, 1095, 1114, 1115, 1133, 1134, 1184, 1203, 1232, 1233, 1244, 1253, 1268, 1278, 1291, 1294, 1306, 1309, 1331, 1402, 1410, 1427, 1434, 1442, 1463, 1508, 1512, 1514, 1515, 1518, 1561, 1564, 1623, 1626, 1630, 1640, 1647, 1691, 1694, 1729, 1745, 1746, 1760, 1797, 1804, 1805, 1812, 1827, 1831, 1858, 1861, 1862, 1918, 1956, 1961, 1965, 1971, 1975, 1994, 2012 }; goldSPlocal.setSparse(deterministicSPlocal); SDR goldTM({COLS}); const SDR_sparse_t deterministicTM{ - 82, 83, 85, 102, 131, 147, 268, 269, 282, 286, 301, 323, 337, 339, 340, 352, 493, 502, 523, 645, 754, 811, 833, 1214, 1263, 1544, 1627, 1738, 1808, 1854, 1858, 1867, 1925, 1927, 1931, 1933, 1943, 1947, 1953, 1955, 1956, 1958, 1959, 1961, 1964, 1965, 1966, 1967, 1969, 1970, 1971, 1975, 1976, 1978, 1980, 1981, 1984, 1987, 1990, 1997, 1998, 2008, 2011, 2013, 2025, 2027, 2034, 2040, 2042, 2044 + 87, 93, 102, 282, 303, 308, 337, 340, 502, 542, 952, 1115, 1502, 1518, 1626, 1691, 1694, 1711, 1727, 1760, 1775, 1781, 1804, 1805, 1827, 1831, 1832, 1844, 1851, 1858, 1859, 1918, 1929, 1931, 1941, 1943, 1945, 1952, 1953, 1955, 1956, 1958, 1960, 1961, 1965, 1973, 1975, 1976, 1979, 1980, 1984, 1985, 1986, 1987, 1994, 1996, 1998, 2002, 2006, 2013, 2042 }; goldTM.setSparse(deterministicTM); - const float goldAn = 0.558824f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms - const float goldAnAvg = 0.389571f; // ...the averaged value, on the other hand, should improve/decrease. + const float goldAn = 0.715686f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms + const float goldAnAvg = 0.412719f; // ...the averaged value, on the other hand, should improve/decrease. #ifdef _ARCH_DETERMINISTIC if(e+1 == 5000) { diff --git a/src/htm/algorithms/SpatialPooler.cpp b/src/htm/algorithms/SpatialPooler.cpp index 9398cdbc68..ce799cb7e2 100644 --- a/src/htm/algorithms/SpatialPooler.cpp +++ b/src/htm/algorithms/SpatialPooler.cpp @@ -710,7 +710,7 @@ Real SpatialPooler::avgConnectedSpanForColumnND_(const UInt column) const { void SpatialPooler::adaptSynapses_(const SDR &input, const SDR &active) { for(const auto &column : active.getSparse()) { - connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_, true, stimulusThreshold_); + connections_.adaptSegment(column, input, synPermActiveInc_, synPermInactiveDec_); connections_.raisePermanencesToThreshold( column, stimulusThreshold_ ); } } diff --git a/src/test/unit/algorithms/SpatialPoolerTest.cpp b/src/test/unit/algorithms/SpatialPoolerTest.cpp index 6b428106be..76b2cf0bbd 100644 --- a/src/test/unit/algorithms/SpatialPoolerTest.cpp +++ b/src/test/unit/algorithms/SpatialPoolerTest.cpp @@ -2056,7 +2056,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Silver is an SDR that is loaded by direct initalization from a vector. SDR silver_sdr({ 200 }); SDR_sparse_t data = { - 11, 17, 31, 63, 78, 125, 126, 153, 173, 193 + 4, 64, 74, 78, 85, 113, 125, 126, 127, 153 }; silver_sdr.setSparse(data); @@ -2064,7 +2064,7 @@ TEST(SpatialPoolerTest, ExactOutput) { // Gold tests initalizing an SDR from a manually created string in JSON format. // hint: you can generate this string using // silver_sdr.save(std::cout, JSON); - string gold = "{\"dimensions\": [200],\"sparse\": [11, 17, 31, 63, 78, 125, 126, 153, 173, 193]}"; + string gold = "{\"dimensions\": [200],\"sparse\": [4, 64, 74, 78, 85, 113, 125, 126, 127, 153]}"; std::stringstream gold_stream( gold ); SDR gold_sdr; gold_sdr.load( gold_stream, JSON ); From 1de0631bbb7aace969f0cf568e767ef9aa11d6ff Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 18:13:58 +0200 Subject: [PATCH 29/34] bump test values for new changes --- src/examples/hello/HelloSPTP.cpp | 6 +++--- src/test/unit/algorithms/ConnectionsPerformanceTest.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/examples/hello/HelloSPTP.cpp b/src/examples/hello/HelloSPTP.cpp index 018b60479a..a0900ba701 100644 --- a/src/examples/hello/HelloSPTP.cpp +++ b/src/examples/hello/HelloSPTP.cpp @@ -213,12 +213,12 @@ EPOCHS = 2; // make test faster in Debug SDR goldTM({COLS}); const SDR_sparse_t deterministicTM{ - 87, 93, 102, 282, 303, 308, 337, 340, 502, 542, 952, 1115, 1502, 1518, 1626, 1691, 1694, 1711, 1727, 1760, 1775, 1781, 1804, 1805, 1827, 1831, 1832, 1844, 1851, 1858, 1859, 1918, 1929, 1931, 1941, 1943, 1945, 1952, 1953, 1955, 1956, 1958, 1960, 1961, 1965, 1973, 1975, 1976, 1979, 1980, 1984, 1985, 1986, 1987, 1994, 1996, 1998, 2002, 2006, 2013, 2042 + 72, 85, 102, 114, 122, 126, 287, 308, 337, 339, 542, 920, 939, 952, 1268, 1507, 1508, 1518, 1546, 1547, 1626, 1627, 1633, 1668, 1727, 1804, 1805, 1827, 1832, 1844, 1859, 1862, 1918, 1920, 1924, 1931, 1933, 1945, 1961, 1965, 1966, 1968, 1970, 1973, 1975, 1976, 1977, 1979, 1986, 1987, 1991, 1992, 1996, 1998, 2002, 2006, 2008, 2012, 2042, 2045 }; goldTM.setSparse(deterministicTM); - const float goldAn = 0.715686f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms - const float goldAnAvg = 0.412719f; // ...the averaged value, on the other hand, should improve/decrease. + const float goldAn = 0.637255f; //Note: this value is for a (randomly picked) datapoint, it does not have to improve (decrease) with better algorithms + const float goldAnAvg = 0.40804f; // ...the averaged value, on the other hand, should improve/decrease. #ifdef _ARCH_DETERMINISTIC if(e+1 == 5000) { diff --git a/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp b/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp index e4ffef21be..f67ece52d1 100644 --- a/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp +++ b/src/test/unit/algorithms/ConnectionsPerformanceTest.cpp @@ -201,7 +201,7 @@ TEST(ConnectionsPerformanceTest, testSP) { /* label */ "spatial pooler"); #ifdef NDEBUG - ASSERT_LE(tim, 4.0f * Timer::getSpeed()); + ASSERT_LE(tim, 8.0f * Timer::getSpeed()); #endif UNUSED(tim); } From 038a29244f86fdc505c32cfe9266508f553d933b Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 19:35:10 +0200 Subject: [PATCH 30/34] COnnections: add couple of noexcept --- src/htm/algorithms/Connections.cpp | 20 +++++++++++--------- src/htm/algorithms/Connections.hpp | 12 ++++++------ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index e0fa529e63..4f037d72bf 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -99,10 +99,11 @@ Segment Connections::createSegment(const CellIdx cell, const SegmentIdx maxSegmentsPerCell) { //limit number of segmets per cell. If exceeded, remove the least recently used ones. - NTA_ASSERT(maxSegmentsPerCell > 0); + NTA_CHECK(maxSegmentsPerCell > 0); while (numSegments(cell) >= maxSegmentsPerCell) { pruneLRUSegment_(cell); } + NTA_ASSERT(numSegments(cell) <= maxSegmentsPerCell); //proceed to create a new segment NTA_CHECK(segments_.size() < std::numeric_limits::max()) << "Add segment failed: Range of Segment (data-type) insufficinet size." @@ -111,6 +112,7 @@ Segment Connections::createSegment(const CellIdx cell, const SegmentData& segmentData = SegmentData(cell, iteration_, nextSegmentOrdinal_++); segments_.push_back(segmentData); + NTA_CHECK(cell < numCells()) << "cell out of bounds!"; CellData &cellData = cells_[cell]; cellData.segments.push_back(segment); //assign the new segment to its mother-cell @@ -186,15 +188,15 @@ Synapse Connections::createSynapse(Segment segment, return synapse; } -bool Connections::segmentExists_(const Segment segment) const { - NTA_CHECK(segment < segments_.size()); +bool Connections::segmentExists_(const Segment segment) const noexcept { + if(segment >= segments_.size()) return false; //OOB segment + const SegmentData &segmentData = segments_[segment]; const vector &segmentsOnCell = cells_[segmentData.cell].segments; - return (std::find(segmentsOnCell.cbegin(), segmentsOnCell.cend(), segment) != - segmentsOnCell.cend()); + return (std::find(segmentsOnCell.cbegin(), segmentsOnCell.cend(), segment) != segmentsOnCell.cend()); //TODO if too slow, also create "fast" variant, as synapseExists_() } -bool Connections::synapseExists_(const Synapse synapse, bool fast) const { +bool Connections::synapseExists_(const Synapse synapse, bool fast) const noexcept { if(synapse >= synapses_.size()) return false; //out of bounds. Can happen after serialization, where only existing synapses are stored. #ifdef NTA_ASSERTIONS_ON @@ -205,8 +207,8 @@ bool Connections::synapseExists_(const Synapse synapse, bool fast) const { const SynapseData &synapseData = synapses_[synapse]; const vector &synapsesOnSegment = segments_[synapseData.segment].synapses; - return (std::find(synapsesOnSegment.begin(), synapsesOnSegment.end(), - synapse) != synapsesOnSegment.end()); + return (std::find(synapsesOnSegment.begin(), synapsesOnSegment.end(), synapse) != synapsesOnSegment.end()); + } else { //quick method. Relies on hack in destroySynapse() where we set synapseData.permanence == -1 return synapses_[synapse].permanence != -1; @@ -404,7 +406,7 @@ vector Connections::synapsesForPresynapticCell(const CellIdx presynapti } -void Connections::reset() +void Connections::reset() noexcept { if( not timeseries_ ) { NTA_WARN << "Connections::reset() called with timeseries=false."; diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index ba53ec5081..1975a3f81e 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -214,7 +214,7 @@ class Connections : public Serializable const Permanence connectedThreshold = 0.5f, const bool timeseries = false); - virtual ~Connections() {} + virtual ~Connections() {} /** * Initialize connections. @@ -398,7 +398,7 @@ class Connections : public Serializable * * @retval A vector length */ - inline size_t segmentFlatListLength() const { return segments_.size(); }; + inline size_t segmentFlatListLength() const noexcept { return segments_.size(); }; /** * Compare two segments. Returns true if a < b. @@ -424,7 +424,7 @@ class Connections : public Serializable /** * For use with time-series datasets. */ - void reset(); + void reset() noexcept; /** * Compute the segment excitations for a vector of active presynaptic @@ -500,7 +500,7 @@ class Connections : public Serializable * SP & TM. */ //! const UInt32& iteration = iteration_; //FIXME cannot construct iteration like this? - UInt32 iteration() const { return iteration_; } + UInt32 iteration() const noexcept { return iteration_; } /** @@ -694,7 +694,7 @@ class Connections : public Serializable * * @retval True if it's still in its cell's segment list. */ - bool segmentExists_(const Segment segment) const; + bool segmentExists_(const Segment segment) const noexcept; /** * Check whether this synapse still exists "in Connections" ( on its segment). @@ -712,7 +712,7 @@ class Connections : public Serializable * * @retval True if synapse is valid (not removed, it's still in its segment's synapse list) */ - bool synapseExists_(const Synapse synapse, bool fast = false) const; + bool synapseExists_(const Synapse synapse, bool fast = false) const noexcept; /** * Remove a synapse from presynaptic maps. From 540f3c40d5b7c138e51d7019e54d8fe8dc89efa1 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 19:55:42 +0200 Subject: [PATCH 31/34] Connections tests for createSegment() --- .../py/tests/algorithms/connections_test.py | 26 +++++++++++++++++++ src/htm/algorithms/Connections.cpp | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/bindings/py/tests/algorithms/connections_test.py b/bindings/py/tests/algorithms/connections_test.py index f0bad8294f..ed7fa34029 100644 --- a/bindings/py/tests/algorithms/connections_test.py +++ b/bindings/py/tests/algorithms/connections_test.py @@ -407,6 +407,32 @@ def testConnectedThreshold(self): conn = Connections(1024, 0.123, False) self.assertAlmostEqual(conn.connectedThreshold, 0.123, places=4) + def testCreateSegment(self): + co = Connections(NUM_CELLS, 0.51) + self.assertEqual(co.numSegments(), 0, "there are zero segments yet") + + # create segment + co.createSegment(NUM_CELLS-1, 20) + self.assertEqual(co.numSegments(), 1, "created 1 new segment") + + # wrong param + with pytest.raises(RuntimeError): + co.createSegment(1, 0) #wrong param maxSegmentsPerCell "0" + + # wrong param - OOB cell + with pytest.raises(RuntimeError): + co.createSegment(NUM_CELLS+22, 1) + + # another segment + co.createSegment(NUM_CELLS-1, 20) + self.assertEqual(co.numSegments(), 2) + + # segment pruning -> reduce to 1 seg per cell + co.createSegment(NUM_CELLS-1, 1) + self.assertEqual(co.numSegments(), 1) + + + if __name__ == "__main__": unittest.main() diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 4f037d72bf..033e6d406c 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -100,6 +100,7 @@ Segment Connections::createSegment(const CellIdx cell, //limit number of segmets per cell. If exceeded, remove the least recently used ones. NTA_CHECK(maxSegmentsPerCell > 0); + NTA_CHECK(cell < numCells()); while (numSegments(cell) >= maxSegmentsPerCell) { pruneLRUSegment_(cell); } @@ -112,7 +113,6 @@ Segment Connections::createSegment(const CellIdx cell, const SegmentData& segmentData = SegmentData(cell, iteration_, nextSegmentOrdinal_++); segments_.push_back(segmentData); - NTA_CHECK(cell < numCells()) << "cell out of bounds!"; CellData &cellData = cells_[cell]; cellData.segments.push_back(segment); //assign the new segment to its mother-cell From bf7ee110418d580aa100b8d8fc8dfc7c67784525 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 22:00:45 +0200 Subject: [PATCH 32/34] COnnections: test for destroySegment() --- .../py/tests/algorithms/connections_test.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/bindings/py/tests/algorithms/connections_test.py b/bindings/py/tests/algorithms/connections_test.py index ed7fa34029..d18dc1fdf6 100644 --- a/bindings/py/tests/algorithms/connections_test.py +++ b/bindings/py/tests/algorithms/connections_test.py @@ -78,16 +78,17 @@ def testAdaptShouldRemoveSegments(self): connections = Connections(NUM_CELLS, 0.51) for i in range(NUM_CELLS): - seg = connections.createSegment(i, 1) + seg = connections.createSegment(i, 2) + seg = connections.createSegment(i, 2) #create 2 segments on each cell for cell in active_cells: segments = connections.segmentsForCell(cell) - self.assertEqual(len(segments), 1, "Segments were prematurely destroyed.") + self.assertEqual(len(segments), 2, "Segments were prematurely destroyed.") segment = segments[0] numSynapsesOnSegment = len(segments) - connections.adaptSegment(segment, inputSDR, 0.1, 0.001, pruneZeroSynapses=True, segmentThreshold= numSynapsesOnSegment+1) #set to +1 so that segments get always deleted in this test + connections.adaptSegment(segment, inputSDR, 0.1, 0.001, pruneZeroSynapses=True, segmentThreshold=1) #set to =1 so that segments get always deleted in this test segments = connections.segmentsForCell(cell) - self.assertEqual(len(segments), 0, "Segments were not destroyed.") + self.assertEqual(len(segments), 1, "Segments were not destroyed.") def testAdaptShouldIncrementSynapses(self): """ @@ -219,8 +220,8 @@ def testDestroySynapse(self): permRemoved = co.permanenceForSynapse(syn1) assert permRemoved == perm1 - with pytest.raises(RuntimeError): # NTA_CHECK, double remove should fail - co.destroySynapse(syn1) + # double remove should be just ignored + co.destroySynapse(syn1) @@ -430,6 +431,23 @@ def testCreateSegment(self): # segment pruning -> reduce to 1 seg per cell co.createSegment(NUM_CELLS-1, 1) self.assertEqual(co.numSegments(), 1) + + + def testDestroySegment(self): + co = Connections(NUM_CELLS, 0.51) + self.assertEqual(co.numSegments(), 0, "there are zero segments yet") + + # removing while no segments exist + co.destroySegment(1) + + # successfully remove + seg = co.createSegment(1, 20) + self.assertEqual(co.numSegments(), 1) + n = co.numConnectedSynapses(seg) #uses dataForSegment() + co.destroySegment(seg) + self.assertEqual(co.numSegments(), 0, "segment should have been removed") + with pytest.raises(RuntimeError): + n2 = co.numConnectedSynapses(seg) From 3c67eb52be7528e4cf94dddb106d27a24b850b63 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 22:20:49 +0200 Subject: [PATCH 33/34] tests advanced_connections.py drop duplicit tests that only test the functionality of "main" Connections, which are already tested. Done due to changes in the main tests, which would have to be rewritten here as well. The "advanced" tests should cover all the special functionality not part of the main. --- .../algorithms/advanced_connections_test.py | 201 ------------------ 1 file changed, 201 deletions(-) diff --git a/py/tests/advanced/algorithms/advanced_connections_test.py b/py/tests/advanced/algorithms/advanced_connections_test.py index 13ceff84e4..27939c550e 100644 --- a/py/tests/advanced/algorithms/advanced_connections_test.py +++ b/py/tests/advanced/algorithms/advanced_connections_test.py @@ -38,207 +38,6 @@ def _getPresynapticCells(self, connections, segment, threshold): return set([connections.presynapticCellForSynapse(synapse) for synapse in connections.synapsesForSegment(segment) if connections.permanenceForSynapse(synapse) >= threshold]) - def testAdaptShouldNotRemoveSegments(self): - """ - Test that connections are generated on predefined segments. - """ - random = Random(1981) - active_cells = np.array(random.sample(np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") - active_cells.sort() - - presynaptic_input = list(range(0, 10)) - inputSDR = SDR(1024) - inputSDR.sparse = presynaptic_input - - connections = Connections(NUM_CELLS, 0.51) - for i in range(NUM_CELLS): - connections.createSegment(i, 1) - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - self.assertEqual(len(segments), 1, "Segments were destroyed.") - segment = segments[0] - connections.adaptSegment(segment, inputSDR, 0.1, 0.001, False) - - segments = connections.segmentsForCell(cell) - self.assertEqual(len(segments), 1, "Segments were destroyed.") - segment = segments[0] - - def testAdaptShouldRemoveSegments(self): - """ - Test that connections are generated on predefined segments. - """ - random = Random(1981) - active_cells = np.array(random.sample(np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") - active_cells.sort() - - presynaptic_input = list(range(0, 10)) - inputSDR = SDR(1024) - inputSDR.sparse = presynaptic_input - - connections = Connections(NUM_CELLS, 0.51) - for i in range(NUM_CELLS): - connections.createSegment(i, 1) - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - self.assertEqual(len(segments), 1, "Segments were prematurely destroyed.") - segment = segments[0] - connections.adaptSegment(segment, inputSDR, 0.1, 0.001, True) - segments = connections.segmentsForCell(cell) - self.assertEqual(len(segments), 0, "Segments were not destroyed.") - - def testAdaptShouldIncrementSynapses(self): - """ - Test that connections are generated on predefined segments. - """ - random = Random(1981) - active_cells = np.array(random.sample(np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") - active_cells.sort() - - presynaptic_input = list(range(0, 10)) - presynaptic_input_set = set(presynaptic_input) - inputSDR = SDR(1024) - inputSDR.sparse = presynaptic_input - - connections = Connections(NUM_CELLS, 0.51) - for i in range(NUM_CELLS): - connections.createSegment(i, 1) - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - segment = segments[0] - for c in presynaptic_input: - connections.createSynapse(segment, c, 0.1) - connections.adaptSegment(segment, inputSDR, 0.1, 0.001, True) - - presynamptic_cells = self._getPresynapticCells(connections, segment, 0.2) - self.assertEqual(presynamptic_cells, presynaptic_input_set, "Missing synapses") - - presynamptic_cells = self._getPresynapticCells(connections, segment, 0.3) - self.assertEqual(presynamptic_cells, set(), "Too many synapses") - - def testAdaptShouldDecrementSynapses(self): - """ - Test that connections are generated on predefined segments. - """ - random = Random(1981) - active_cells = np.array(random.sample(np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") - active_cells.sort() - - presynaptic_input = list(range(0, 10)) - presynaptic_input_set = set(presynaptic_input) - inputSDR = SDR(1024) - inputSDR.sparse = presynaptic_input - - connections = Connections(NUM_CELLS, 0.51) - for i in range(NUM_CELLS): - connections.createSegment(i, 1) - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - segment = segments[0] - for c in presynaptic_input: - connections.createSynapse(segment, c, 0.1) - - connections.adaptSegment(segment, inputSDR, 0.1, 0.0, False) - - presynamptic_cells = self._getPresynapticCells(connections, segment, 0.2) - self.assertEqual(presynamptic_cells, presynaptic_input_set, "Missing synapses") - - presynaptic_input1 = list(range(0, 5)) - presynaptic_input_set1 = set(presynaptic_input1) - inputSDR.sparse = presynaptic_input1 - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - segment = segments[0] - connections.adaptSegment(segment, inputSDR, 0.0, 0.1, False) - - - presynamptic_cells = self._getPresynapticCells(connections, segment, 0.2) - self.assertEqual(presynamptic_cells, presynaptic_input_set1, "Too many synapses") - - presynamptic_cells = self._getPresynapticCells(connections, segment, 0.1) - self.assertEqual(presynamptic_cells, presynaptic_input_set, "Missing synapses") - - - def testNumSynapses(self): - """ - Test that connections are generated on predefined segments. - """ - random = Random(1981) - active_cells = np.array(random.sample(np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") - active_cells.sort() - - presynaptic_input = list(range(0, 10)) - inputSDR = SDR(1024) - inputSDR.sparse = presynaptic_input - - connections = Connections(NUM_CELLS, 0.3) - for i in range(NUM_CELLS): - connections.createSegment(i, 1) - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - segment = segments[0] - for c in presynaptic_input: - connections.createSynapse(segment, c, 0.1) - - connections.adaptSegment(segment, inputSDR, 0.1, 0.0, False) - - num_synapses = connections.numSynapses(segment) - self.assertEqual(num_synapses, len(presynaptic_input), "Missing synapses") - - self.assertEqual(connections.numSynapses(), len(presynaptic_input) * 40, "Missing synapses") - - - def testNumConnectedSynapses(self): - """ - Test that connections are generated on predefined segments. - """ - random = Random(1981) - active_cells = np.array(random.sample(np.arange(0, NUM_CELLS, 1, dtype="uint32"), 40), dtype="uint32") - active_cells.sort() - - presynaptic_input = list(range(0, 10)) - inputSDR = SDR(1024) - inputSDR.sparse = presynaptic_input - - connections = Connections(NUM_CELLS, 0.2) - for i in range(NUM_CELLS): - connections.createSegment(i, 1) - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - segment = segments[0] - for c in presynaptic_input: - connections.createSynapse(segment, c, 0.1) - - connections.adaptSegment(segment, inputSDR, 0.1, 0.0, False) - - connected_synapses = connections.numConnectedSynapses(segment) - self.assertEqual(connected_synapses, len(presynaptic_input), "Missing synapses") - - presynaptic_input1 = list(range(0, 5)) - inputSDR.sparse = presynaptic_input1 - - total_connected = 0 - - for cell in active_cells: - segments = connections.segmentsForCell(cell) - segment = segments[0] - connections.adaptSegment(segment, inputSDR, 0.0, 0.1, False) - - connected_synapses = connections.numConnectedSynapses(segment) - self.assertEqual(connected_synapses, len(presynaptic_input1), "Missing synapses") - - total_connected += connected_synapses - - connected_synapses = connections.numSynapses(segment) - self.assertEqual(connected_synapses, len(presynaptic_input), "Missing synapses") - - self.assertEqual(total_connected, len(presynaptic_input1) * 40, "Missing synapses") def testComputeActivity(self): """ From ffb8ee8acc51c128d73430c592c1fb5aa6e40a5c Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Wed, 3 Jun 2020 22:22:48 +0200 Subject: [PATCH 34/34] Connections dataForSegment throws if segment removed --- src/htm/algorithms/Connections.cpp | 22 ++++++++++++++++------ src/htm/algorithms/Connections.hpp | 10 ++++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/htm/algorithms/Connections.cpp b/src/htm/algorithms/Connections.cpp index 033e6d406c..fe5c53d8f9 100644 --- a/src/htm/algorithms/Connections.cpp +++ b/src/htm/algorithms/Connections.cpp @@ -188,7 +188,7 @@ Synapse Connections::createSynapse(Segment segment, return synapse; } -bool Connections::segmentExists_(const Segment segment) const noexcept { +bool Connections::segmentExists_(const Segment segment) const { if(segment >= segments_.size()) return false; //OOB segment const SegmentData &segmentData = segments_[segment]; @@ -196,7 +196,7 @@ bool Connections::segmentExists_(const Segment segment) const noexcept { return (std::find(segmentsOnCell.cbegin(), segmentsOnCell.cend(), segment) != segmentsOnCell.cend()); //TODO if too slow, also create "fast" variant, as synapseExists_() } -bool Connections::synapseExists_(const Synapse synapse, bool fast) const noexcept { +bool Connections::synapseExists_(const Synapse synapse, bool fast) const { if(synapse >= synapses_.size()) return false; //out of bounds. Can happen after serialization, where only existing synapses are stored. #ifdef NTA_ASSERTIONS_ON @@ -207,7 +207,13 @@ bool Connections::synapseExists_(const Synapse synapse, bool fast) const noexcep const SynapseData &synapseData = synapses_[synapse]; const vector &synapsesOnSegment = segments_[synapseData.segment].synapses; - return (std::find(synapsesOnSegment.begin(), synapsesOnSegment.end(), synapse) != synapsesOnSegment.end()); + const bool found = (std::find(synapsesOnSegment.begin(), synapsesOnSegment.end(), synapse) != synapsesOnSegment.end()); + //validate the fast & slow methods for same result: +#ifdef NTA_ASSERTIONS_ON + const bool removed = synapses_[synapse].permanence == -1; + NTA_ASSERT( (removed and not found) or (not removed and found) ); +#endif + return found; } else { //quick method. Relies on hack in destroySynapse() where we set synapseData.permanence == -1 @@ -239,7 +245,8 @@ void Connections::removeSynapseFromPresynapticMap_( void Connections::destroySegment(const Segment segment) { - NTA_ASSERT(segmentExists_(segment)); + if(not segmentExists_(segment)) return; + for (auto h : eventHandlers_) { h.second->onDestroySegment(segment); } @@ -254,16 +261,18 @@ void Connections::destroySegment(const Segment segment) { CellData &cellData = cells_[segmentData.cell]; const auto segmentOnCell = std::find(cellData.segments.cbegin(), cellData.segments.cend(), segment); - NTA_CHECK(segmentOnCell != cellData.segments.cend()) << "Segment to be destroyed not found on the cell!"; + NTA_ASSERT(segmentOnCell != cellData.segments.cend()) << "Segment to be destroyed not found on the cell!"; NTA_ASSERT(*segmentOnCell == segment); cellData.segments.erase(segmentOnCell); destroyedSegments_++; + + NTA_ASSERT(not segmentExists_(segment)); } void Connections::destroySynapse(const Synapse synapse) { - NTA_CHECK(synapseExists_(synapse, true)); + if(not synapseExists_(synapse, true)) return; for (auto h : eventHandlers_) { h.second->onDestroySynapse(synapse); @@ -312,6 +321,7 @@ void Connections::destroySynapse(const Synapse synapse) { //To mark them as "removed", we set SynapseData.permanence = -1, this can be used for a quick check later synapseData.permanence = -1; //marking as "removed" destroyedSynapses_++; + NTA_ASSERT(not synapseExists_(synapse)); } diff --git a/src/htm/algorithms/Connections.hpp b/src/htm/algorithms/Connections.hpp index 1975a3f81e..f1455d6056 100644 --- a/src/htm/algorithms/Connections.hpp +++ b/src/htm/algorithms/Connections.hpp @@ -362,10 +362,12 @@ class Connections : public Serializable * * @retval Segment data. */ - inline const SegmentData &dataForSegment(const Segment segment) const { + const SegmentData &dataForSegment(const Segment segment) const { + NTA_CHECK(segmentExists_(segment)); return segments_[segment]; } - inline SegmentData& dataForSegment(const Segment segment) { //editable access, needed by SP + SegmentData& dataForSegment(const Segment segment) { //editable access, needed by SP + NTA_CHECK(segmentExists_(segment)); return segments_[segment]; } @@ -694,7 +696,7 @@ class Connections : public Serializable * * @retval True if it's still in its cell's segment list. */ - bool segmentExists_(const Segment segment) const noexcept; + bool segmentExists_(const Segment segment) const; /** * Check whether this synapse still exists "in Connections" ( on its segment). @@ -712,7 +714,7 @@ class Connections : public Serializable * * @retval True if synapse is valid (not removed, it's still in its segment's synapse list) */ - bool synapseExists_(const Synapse synapse, bool fast = false) const noexcept; + bool synapseExists_(const Synapse synapse, bool fast = false) const; /** * Remove a synapse from presynaptic maps.