From d3502c6c36f68c41c2337e8ab9b1158cb63b769c Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 3 May 2019 17:44:48 +0200 Subject: [PATCH 1/6] Revert "Revert "TM: activeCells_ is now a SDR"" This reverts commit 5515f19ba90e03728ade49f59bd0369cb09f03b8. --- API_CHANGELOG.md | 2 +- .../bindings/algorithms/py_TemporalMemory.cpp | 2 +- src/nupic/algorithms/TemporalMemory.cpp | 61 +++++++++---------- src/nupic/algorithms/TemporalMemory.hpp | 9 +-- src/nupic/regions/TMRegion.cpp | 11 ++-- .../unit/algorithms/TemporalMemoryTest.cpp | 14 ++--- 6 files changed, 46 insertions(+), 53 deletions(-) diff --git a/API_CHANGELOG.md b/API_CHANGELOG.md index 253435f87f..f611b14d67 100644 --- a/API_CHANGELOG.md +++ b/API_CHANGELOG.md @@ -76,6 +76,6 @@ longer accept a synapse permanence threshold argument. PR #305 * SDRClassifier class is replaced by `Classifier` and `Predictor` classes. -* TemporalMemory::getPredictiveCells() now returns a SDR. This ensures more convenient API and that the SDR object has correct +* TemporalMemory::getPredictiveCells(), and getActiveCells() now return a SDR. This ensures more convenient API and that the SDR object has correct dimensions matching TM. use TM.getPredictiveCells().getSparse() to obtain the sparse vector as before. diff --git a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp index f3f2634e04..a261cc4b03 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp @@ -139,7 +139,7 @@ using namespace nupic::algorithms::connections; py_HTM.def("getActiveCells", [](const HTM_t& self) { - auto activeCells = self.getActiveCells(); + auto activeCells = self.getActiveCells().getSparse(); return py::array_t(activeCells.size(), activeCells.data()); }); diff --git a/src/nupic/algorithms/TemporalMemory.cpp b/src/nupic/algorithms/TemporalMemory.cpp index 5f1907388a..d644a8525e 100644 --- a/src/nupic/algorithms/TemporalMemory.cpp +++ b/src/nupic/algorithms/TemporalMemory.cpp @@ -141,6 +141,10 @@ void TemporalMemory::initialize( connections = Connections(static_cast(numberOfColumns() * cellsPerColumn_), connectedPermanence_); rng_ = Random(seed); + auto cellsDims = getColumnDimensions(); //nD column dimensions (eg 10x100) + cellsDims.push_back(getCellsPerColumn()); //add n+1-th dimension for cellsPerColumn (eg. 10x100x8) + activeCells_.initialize(cellsDims); + maxSegmentsPerCell_ = maxSegmentsPerCell; maxSynapsesPerSegment_ = maxSynapsesPerSegment; iteration_ = 0; @@ -298,7 +302,7 @@ static void growSynapses(Connections &connections, } static void activatePredictedColumn( - vector &activeCells, + SDR &activeCellsSDR, vector &winnerCells, Connections &connections, Random &rng, @@ -314,6 +318,7 @@ static void activatePredictedColumn( const SynapseIdx maxSynapsesPerSegment, const bool learn) { auto activeSegment = columnActiveSegmentsBegin; + auto& activeCells = activeCellsSDR.getSparse(); do { const CellIdx cell = connections.cellForSegment(*activeSegment); activeCells.push_back(cell); @@ -337,6 +342,8 @@ static void activatePredictedColumn( } while (++activeSegment != columnActiveSegmentsEnd && connections.cellForSegment(*activeSegment) == cell); } while (activeSegment != columnActiveSegmentsEnd); + + activeCellsSDR.setSparse(activeCells); //update SDR } static Segment createSegment(Connections &connections, //TODO remove, use TM::createSegment @@ -365,7 +372,7 @@ static Segment createSegment(Connections &connections, //TODO remove, use TM::c } static void -burstColumn(vector &activeCells, +burstColumn(SDR &activeCellsSDR, vector &winnerCells, Connections &connections, Random &rng, @@ -385,12 +392,17 @@ burstColumn(vector &activeCells, const SegmentIdx maxSegmentsPerCell, const SynapseIdx maxSynapsesPerSegment, const bool learn) { + + { + auto& activeCells = activeCellsSDR.getSparse(); // Calculate the active cells. const CellIdx start = column * cellsPerColumn; const CellIdx end = start + cellsPerColumn; for (CellIdx cell = start; cell < end; cell++) { activeCells.push_back(cell); } + activeCellsSDR.setSparse(activeCells); + } const auto bestMatchingSegment = std::max_element(columnMatchingSegmentsBegin, columnMatchingSegmentsEnd, @@ -474,10 +486,10 @@ void TemporalMemory::activateCells(const size_t activeColumnsSize, } vector prevActiveCellsDense(numberOfCells() + extra_, false); - for (CellIdx cell : activeCells_) { + for (CellIdx cell : activeCells_.getSparse()) { prevActiveCellsDense[cell] = true; } - activeCells_.clear(); + activeCells_.zero(); const vector prevWinnerCells = std::move(winnerCells_); @@ -567,10 +579,15 @@ void TemporalMemory::activateDendrites(bool learn, NTA_CHECK( extraWinners.size() != 1 || extraWinners[0] != SENTINEL ) << "TM.ActivateDendrites() missing argument extraWinners!"; + //add extra active + auto& activeVec = activeCells_.getSparse(); for(const auto &active : extraActive) { NTA_ASSERT( active < extra_ ); - activeCells_.push_back( static_cast(active + numberOfCells()) ); + activeVec.push_back( static_cast(active + numberOfCells()) ); } + activeCells_.setSparse(activeVec); + + //add extra winners for(const auto &winner : extraWinners) { NTA_ASSERT( winner < extra_ ); winnerCells_.push_back( static_cast(winner + numberOfCells()) ); @@ -589,7 +606,7 @@ void TemporalMemory::activateDendrites(bool learn, numActivePotentialSynapsesForSegment_.assign(length, 0); connections.computeActivity(numActiveConnectedSynapsesForSegment_, numActivePotentialSynapsesForSegment_, - activeCells_); + activeCells_.getSparse()); // Active segments, connected synapses. activeSegments_.clear(); @@ -650,7 +667,7 @@ void TemporalMemory::compute(const SDR &activeColumns, bool learn) { } void TemporalMemory::reset(void) { - activeCells_.clear(); + activeCells_.zero(); winnerCells_.clear(); activeSegments_.clear(); matchingSegments_.clear(); @@ -674,10 +691,7 @@ UInt TemporalMemory::columnForCell(const CellIdx cell) const { SDR TemporalMemory::cellsToColumns(const SDR& cells) const { - auto correctDims = getColumnDimensions(); //nD column dimensions (eg 10x100) - correctDims.push_back(getCellsPerColumn()); //add n+1-th dimension for cellsPerColumn (eg. 10x100x8) - - NTA_CHECK(cells.dimensions == correctDims) + NTA_CHECK(cells.dimensions == activeCells_.dimensions) << "cells.dimensions must match TM's (column dims x cellsPerColumn) "; SDR cols(getColumnDimensions()); @@ -706,13 +720,8 @@ vector TemporalMemory::cellsForColumn(CellIdx column) { return cellsInColumn; } -vector TemporalMemory::getActiveCells() const { return activeCells_; } -void TemporalMemory::getActiveCells(SDR &activeCells) const -{ - NTA_CHECK( activeCells.size == numberOfCells() ); - activeCells.setSparse( getActiveCells() ); -} +SDR TemporalMemory::getActiveCells() const { return activeCells_; } SDR TemporalMemory::getPredictiveCells() const { @@ -720,10 +729,7 @@ SDR TemporalMemory::getPredictiveCells() const { NTA_CHECK( segmentsValid_ ) << "Call TM.activateDendrites() before TM.getPredictiveCells()!"; - auto correctDims = getColumnDimensions(); - correctDims.push_back(getCellsPerColumn()); - SDR predictive(correctDims); - + SDR predictive(activeCells_.dimensions); //match TM's dimensions, same as active cells auto& predictiveCells = predictive.getSparse(); for (auto segment = activeSegments_.cbegin(); segment != activeSegments_.cend(); @@ -881,10 +887,7 @@ void TemporalMemory::save(ostream &outStream) const { } outStream << endl; - outStream << activeCells_.size() << " "; - for (CellIdx cell : activeCells_) { - outStream << cell << " "; - } + activeCells_.save(outStream); outStream << endl; outStream << winnerCells_.size() << " "; @@ -962,13 +965,7 @@ void TemporalMemory::load(istream &inStream) { inStream >> columnDimensions_[i]; } - UInt numActiveCells; - inStream >> numActiveCells; - for (UInt i = 0; i < numActiveCells; i++) { - CellIdx cell; - inStream >> cell; - activeCells_.push_back(cell); - } + activeCells_.load(inStream); if (version < 2) { UInt numPredictiveCells; diff --git a/src/nupic/algorithms/TemporalMemory.hpp b/src/nupic/algorithms/TemporalMemory.hpp index 3193c8a30e..3dbdd7a2d1 100644 --- a/src/nupic/algorithms/TemporalMemory.hpp +++ b/src/nupic/algorithms/TemporalMemory.hpp @@ -303,12 +303,9 @@ using namespace nupic::algorithms::connections; size_t numberOfCells(void) const { return connections.numCells(); } /** - * Returns the indices of the active cells. - * - * @returns (std::vector) Vector of indices of active cells. + * @return SDR with indices of active cells. */ - vector getActiveCells() const; //TODO remove - void getActiveCells(sdr::SDR &activeCells) const; + sdr::SDR getActiveCells() const; /** * @return SDR with indices of the predictive cells. @@ -613,7 +610,7 @@ using namespace nupic::algorithms::connections; Permanence predictedSegmentDecrement_; UInt extra_; - vector activeCells_; + sdr::SDR activeCells_; vector winnerCells_; bool segmentsValid_; vector activeSegments_; diff --git a/src/nupic/regions/TMRegion.cpp b/src/nupic/regions/TMRegion.cpp index 4e608eb2ae..30af11361b 100644 --- a/src/nupic/regions/TMRegion.cpp +++ b/src/nupic/regions/TMRegion.cpp @@ -248,18 +248,17 @@ void TMRegion::compute() { out = getOutput("bottomUpOut"); if (out && (out->hasOutgoingLinks() || LogItem::isDebug())) { SDR& sdr = out->getData().getSDR(); - if (args_.orColumnOutputs) { //aggregate to columns - tm_->getActiveCells(sdr); + sdr = tm_->getActiveCells(); + if (args_.orColumnOutputs) { //output as columns SDR cols = tm_->cellsToColumns(sdr); - sdr.setSparse(cols.getSparse()); - } else { //output as cells - tm_->getActiveCells(sdr); + sdr.setSDR(cols); } NTA_DEBUG << "compute " << *out << std::endl; } out = getOutput("activeCells"); if (out && (out->hasOutgoingLinks() || LogItem::isDebug())) { - tm_->getActiveCells(out->getData().getSDR()); + SDR& sdr = out->getData().getSDR(); + sdr = tm_->getActiveCells(); NTA_DEBUG << "compute " << *out << std::endl; } out = getOutput("predictedActiveCells"); diff --git a/src/test/unit/algorithms/TemporalMemoryTest.cpp b/src/test/unit/algorithms/TemporalMemoryTest.cpp index 8f2703d28c..1a636a01e8 100644 --- a/src/test/unit/algorithms/TemporalMemoryTest.cpp +++ b/src/test/unit/algorithms/TemporalMemoryTest.cpp @@ -115,7 +115,7 @@ TEST(TemporalMemoryTest, ActivateCorrectlyPredictiveCells) { ASSERT_EQ(expectedActiveCells, tm.getPredictiveCells().getSparse()); tm.compute(numActiveColumns, activeColumns, true); - EXPECT_EQ(expectedActiveCells, tm.getActiveCells()); + EXPECT_EQ(expectedActiveCells, tm.getActiveCells().getSparse()); } /** @@ -141,7 +141,7 @@ TEST(TemporalMemoryTest, BurstUnpredictedColumns) { tm.compute(1, activeColumns, true); - EXPECT_EQ(burstingCells, tm.getActiveCells()); + EXPECT_EQ(burstingCells, tm.getActiveCells().getSparse()); } /** @@ -175,13 +175,13 @@ TEST(TemporalMemoryTest, ZeroActiveColumns) { tm.connections.createSynapse(segment, previousActiveCells[2], 0.5f); tm.connections.createSynapse(segment, previousActiveCells[3], 0.5f); tm.compute(1, previousActiveColumns, true); - ASSERT_FALSE(tm.getActiveCells().empty()); + ASSERT_FALSE(tm.getActiveCells().getSum() == 0); ASSERT_FALSE(tm.getWinnerCells().empty()); tm.activateDendrites(); ASSERT_FALSE(tm.getPredictiveCells().getSum() == 0); EXPECT_NO_THROW(tm.compute(0, nullptr, true)) << "failed with empty compute"; - EXPECT_TRUE(tm.getActiveCells().empty()); + EXPECT_TRUE(tm.getActiveCells().getSum() == 0); EXPECT_TRUE(tm.getWinnerCells().empty()); tm.activateDendrites(); EXPECT_TRUE(tm.getPredictiveCells().getSum() == 0); @@ -1053,7 +1053,7 @@ TEST(TemporalMemoryTest, AddSegmentToCellWithFewestSegments) { tm.compute(4, previousActiveColumns, true); tm.compute(1, activeColumns, true); - ASSERT_EQ(activeCells, tm.getActiveCells()); + ASSERT_EQ(activeCells, tm.getActiveCells().getSparse()); EXPECT_EQ(3ul, tm.connections.numSegments()); EXPECT_EQ(1ul, tm.connections.segmentsForCell(0).size()); @@ -1443,7 +1443,7 @@ void serializationTestVerify(TemporalMemory &tm) { // Verify the correct cells were activated. EXPECT_EQ((vector{4, 8, 9, 10, 11, 12, 13, 14, 15}), - tm.getActiveCells()); + tm.getActiveCells().getSparse()); const vector winnerCells = tm.getWinnerCells(); ASSERT_EQ(3ul, winnerCells.size()); EXPECT_EQ(4ul, winnerCells[0]); @@ -1615,7 +1615,7 @@ TEST(TemporalMemoryTest, testExtraActive) { SDR predictedColumns = tm.cellsToColumns(tm.getPredictiveCells()); // Calculate TM output tm.compute(x, true); - extraActive = tm.getActiveCells(); + extraActive = tm.getActiveCells().getSparse(); extraWinners = tm.getWinnerCells(); // Calculate Anomaly of current input based on prior predictions. From cd480990fe2ee03481f8d8cbd8c3c225bc2ad514 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 3 May 2019 17:45:04 +0200 Subject: [PATCH 2/6] Revert "Revert "TMRegion: fix testLinking fail"" This reverts commit 38a58775c62fd9ab6319196ced4cd93387de5781. --- src/nupic/regions/TMRegion.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/nupic/regions/TMRegion.cpp b/src/nupic/regions/TMRegion.cpp index 30af11361b..861c812827 100644 --- a/src/nupic/regions/TMRegion.cpp +++ b/src/nupic/regions/TMRegion.cpp @@ -248,17 +248,21 @@ void TMRegion::compute() { out = getOutput("bottomUpOut"); if (out && (out->hasOutgoingLinks() || LogItem::isDebug())) { SDR& sdr = out->getData().getSDR(); - sdr = tm_->getActiveCells(); - if (args_.orColumnOutputs) { //output as columns - SDR cols = tm_->cellsToColumns(sdr); - sdr.setSDR(cols); + if (args_.orColumnOutputs) { //aggregate to columns + SDR cols = tm_->cellsToColumns(tm_->getActiveCells()); + sdr.setSparse(cols.getSparse()); + } else { //output as cells + SDR cells = tm_->getActiveCells(); + sdr.setSparse(cells.getSparse()); } NTA_DEBUG << "compute " << *out << std::endl; } out = getOutput("activeCells"); if (out && (out->hasOutgoingLinks() || LogItem::isDebug())) { SDR& sdr = out->getData().getSDR(); - sdr = tm_->getActiveCells(); + SDR cells = tm_->getActiveCells(); + sdr.setSparse(cells.getSparse()); + NTA_DEBUG << "compute " << *out << std::endl; } out = getOutput("predictedActiveCells"); From 2911cd2b7459628690c1b163d2824e5dfd66ef9d Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Fri, 3 May 2019 17:46:40 +0200 Subject: [PATCH 3/6] TM:getActiveCells() python return SDR --- bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp index a261cc4b03..73dc43b4c0 100644 --- a/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp +++ b/bindings/py/cpp_src/bindings/algorithms/py_TemporalMemory.cpp @@ -139,9 +139,7 @@ using namespace nupic::algorithms::connections; py_HTM.def("getActiveCells", [](const HTM_t& self) { - auto activeCells = self.getActiveCells().getSparse(); - - return py::array_t(activeCells.size(), activeCells.data()); + return self.getActiveCells(); }); py_HTM.def("activateDendrites", [](HTM_t &self, bool learn) { From e7200077d1f33982ccfcd88c18d9eb5a4a062e73 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Sun, 5 May 2019 23:11:33 +0200 Subject: [PATCH 4/6] fix Debug code --- src/nupic/algorithms/TemporalMemory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nupic/algorithms/TemporalMemory.cpp b/src/nupic/algorithms/TemporalMemory.cpp index b4358d87ae..1d946ef441 100644 --- a/src/nupic/algorithms/TemporalMemory.cpp +++ b/src/nupic/algorithms/TemporalMemory.cpp @@ -533,7 +533,7 @@ void TemporalMemory::activateDendrites(const bool learn, NTA_CHECK( extraWinners.size == extra_ ); NTA_CHECK( extraActive.dimensions == extraWinners.dimensions); #ifdef NTA_ASSERTIONS_ON - SDR both(extraActive.dimensions); + SDR both(extraActive.dimensions, {}); both.intersection(extraActive, extraWinners); NTA_ASSERT(both == extraWinners) << "ExtraWinners must be a subset of ExtraActive"; #endif From b318d8af912ae8b3df243246b8dd440bfce0cb07 Mon Sep 17 00:00:00 2001 From: Marek Otahal Date: Mon, 6 May 2019 00:27:21 +0200 Subject: [PATCH 5/6] Revert "fix Debug code" This reverts commit e7200077d1f33982ccfcd88c18d9eb5a4a062e73. --- src/nupic/algorithms/TemporalMemory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nupic/algorithms/TemporalMemory.cpp b/src/nupic/algorithms/TemporalMemory.cpp index 81f9dad4e1..48caaf21d7 100644 --- a/src/nupic/algorithms/TemporalMemory.cpp +++ b/src/nupic/algorithms/TemporalMemory.cpp @@ -545,7 +545,7 @@ void TemporalMemory::activateDendrites(const bool learn, NTA_CHECK( extraWinners.size == extra_ ); NTA_CHECK( extraActive.dimensions == extraWinners.dimensions); #ifdef NTA_ASSERTIONS_ON - SDR both(extraActive.dimensions, {}); + SDR both(extraActive.dimensions); both.intersection(extraActive, extraWinners); NTA_ASSERT(both == extraWinners) << "ExtraWinners must be a subset of ExtraActive"; #endif From 5230991adab7a8a626c3007f562e5d481655d9ec Mon Sep 17 00:00:00 2001 From: ctrl-z-9000-times Date: Sun, 5 May 2019 19:15:12 -0400 Subject: [PATCH 6/6] TM.activeCells as SDR: Fix for external predictive inputs. --- src/nupic/algorithms/TemporalMemory.cpp | 24 ++++++++++-------------- src/nupic/types/Sdr.cpp | 3 +++ src/nupic/types/Sdr.hpp | 4 ++++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/nupic/algorithms/TemporalMemory.cpp b/src/nupic/algorithms/TemporalMemory.cpp index 48caaf21d7..bd704c21d1 100644 --- a/src/nupic/algorithms/TemporalMemory.cpp +++ b/src/nupic/algorithms/TemporalMemory.cpp @@ -298,7 +298,7 @@ static void growSynapses(Connections &connections, } static void activatePredictedColumn( - SDR &activeCellsSDR, + SDR &activeCellsSDR, vector &winnerCells, Connections &connections, Random &rng, @@ -543,12 +543,12 @@ void TemporalMemory::activateDendrites(const bool learn, { NTA_CHECK( extraActive.size == extra_ ); NTA_CHECK( extraWinners.size == extra_ ); - NTA_CHECK( extraActive.dimensions == extraWinners.dimensions); -#ifdef NTA_ASSERTIONS_ON - SDR both(extraActive.dimensions); - both.intersection(extraActive, extraWinners); - NTA_ASSERT(both == extraWinners) << "ExtraWinners must be a subset of ExtraActive"; -#endif + NTA_CHECK( extraActive.dimensions == extraWinners.dimensions); + #ifdef NTA_ASSERTIONS_ON + SDR both(extraActive.dimensions); + both.intersection(extraActive, extraWinners); + NTA_ASSERT(both == extraWinners) << "ExtraWinners must be a subset of ExtraActive"; + #endif } else { @@ -560,12 +560,8 @@ void TemporalMemory::activateDendrites(const bool learn, if( segmentsValid_ ) return; -#ifdef NTA_ASSERTIONS_ON - for(const auto &active : extraActive.getSparse()) { - NTA_ASSERT( active < extra_ ); - } -#endif - activeCells_.concatenate(activeCells_, extraActive); + SDR dendriteInputs({ activeCells_.size + extraActive.size }); + dendriteInputs.concatenate(activeCells_.flatten(), extraActive.flatten()); for(const auto &winner : extraWinners.getSparse()) { NTA_ASSERT( winner < extra_ ); @@ -578,7 +574,7 @@ void TemporalMemory::activateDendrites(const bool learn, numActivePotentialSynapsesForSegment_.assign(length, 0); connections.computeActivity(numActiveConnectedSynapsesForSegment_, numActivePotentialSynapsesForSegment_, - activeCells_.getSparse()); + dendriteInputs.getSparse()); // Active segments, connected synapses. activeSegments_.clear(); diff --git a/src/nupic/types/Sdr.cpp b/src/nupic/types/Sdr.cpp index 0809548616..5ba53c4fb4 100644 --- a/src/nupic/types/Sdr.cpp +++ b/src/nupic/types/Sdr.cpp @@ -426,6 +426,9 @@ namespace sdr { SDR::setDenseInplace(); } + Reshape SparseDistributedRepresentation::flatten() const + { return Reshape(*this, {size} ); } + bool SparseDistributedRepresentation::operator==(const SparseDistributedRepresentation &sdr) const { // Check attributes if( sdr.size != size or dimensions.size() != sdr.dimensions.size() ) diff --git a/src/nupic/types/Sdr.hpp b/src/nupic/types/Sdr.hpp index d6f3457553..91adcbfb9c 100644 --- a/src/nupic/types/Sdr.hpp +++ b/src/nupic/types/Sdr.hpp @@ -42,6 +42,8 @@ using SDR_sparse_t = std::vector; using SDR_coordinate_t = std::vector>; using SDR_callback_t = std::function; +class Reshape; // Forward Declaration. + /** * SparseDistributedRepresentation class * Also known as "SDR" class @@ -519,6 +521,8 @@ class SparseDistributedRepresentation : public Serializable void concatenate(std::vector inputs, UInt axis = 0u); + Reshape flatten() const; + /** * Print a human readable version of the SDR. */