diff --git a/.gitignore b/.gitignore index 03d7f89..da7b0c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +.DS_STORE /wekiLib/ # Created by https://www.gitignore.io/api/emacs diff --git a/CMakeLists.txt b/CMakeLists.txt index a967538..70e67de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,11 @@ else() add_compile_options(-std=c++20 -O2) endif() +option(RAPIDLIB_DISABLE_JSONCPP "Disable jsoncpp integration." OFF) +if(EMSCRIPTEN) + set(RAPIDLIB_DISABLE_JSONCPP ON) +endif() + # Threads set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) @@ -28,16 +33,21 @@ file(GLOB JSON_SRC "${PROJECT_SOURCE_DIR}/dependencies/jsoncpp.cpp") file(GLOB BAYES_SRC "${PROJECT_SOURCE_DIR}/dependencies/bayesfilter/src/*.cpp") # Set the source for the main library, using the groups defined above -set(RAPIDLIB_FULL_SRC +set(RAPIDLIB_FULL_SRC ${RAPIDLIB_SRC} ${RAPIDLIB_DEP} - ${JSON_SRC} ${BAYES_SRC} - ) + ) add_library(${PROJECT_NAME} SHARED ${RAPIDLIB_FULL_SRC}) add_executable(rapidLibTest test/rapidLibTest.cpp) +if(RAPIDLIB_DISABLE_JSONCPP) + target_compile_definitions(${PROJECT_NAME} PUBLIC RAPIDLIB_DISABLE_JSONCPP) +else() + target_sources(${PROJECT_NAME} PRIVATE ${JSON_SRC}) +endif() + #include(GenerateExportHeader) #generate_export_header(${PROJECT_NAME}) @@ -46,3 +56,4 @@ target_link_libraries(rapidLibTest Threads::Threads) enable_testing() add_test(rapidLibTest rapidLibTest) + diff --git a/dependencies/jsoncpp.cpp b/dependencies/jsoncpp.cpp index 4c1b04c..1956e02 100644 --- a/dependencies/jsoncpp.cpp +++ b/dependencies/jsoncpp.cpp @@ -1767,8 +1767,8 @@ bool OurReader::decodeNumber(Token& token, Value& decoded) { ++current; // TODO: Help the compiler do the div and mod at compile time or get rid of them. Value::LargestUInt maxIntegerValue = - isNegative ? Value::LargestUInt(-Value::minLargestInt) - : Value::maxLargestUInt; + isNegative ? Value::LargestUInt(Value::maxLargestInt) + 1 + : Value::maxLargestUInt; Value::LargestUInt threshold = maxIntegerValue / 10; Value::LargestUInt value = 0; while (current < token.end_) { diff --git a/src/baseModel.h b/src/baseModel.h index 2abcfeb..7c3e566 100644 --- a/src/baseModel.h +++ b/src/baseModel.h @@ -13,7 +13,7 @@ #include #include "trainingExample.h" -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP #include "../dependencies/json/json.h" #endif @@ -49,7 +49,7 @@ class baseModel virtual size_t getNumInputs() const = 0; virtual std::vector getWhichInputs() const = 0; -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP virtual void getJSONDescription(Json::Value& currentModel) = 0; protected: diff --git a/src/classification.cpp b/src/classification.cpp index cf8c1db..0b475d0 100644 --- a/src/classification.cpp +++ b/src/classification.cpp @@ -6,9 +6,10 @@ // Copyright © 2016 Goldsmiths. All rights reserved. // +#include "classification.h" +#include #include -#include "classification.h" #ifdef EMSCRIPTEN #include "emscripten/classificationEmbindings.h" #endif @@ -69,36 +70,39 @@ bool classificationTemplate::train(const std::vector 0) { //create model(s) here - modelSet::numInputs = int(training_set[0].input.size()); - modelSet::numOutputs = int(training_set[0].output.size()); - - for (int i = 0; i < modelSet::numInputs; ++i) + modelSet::numInputs = static_cast(training_set[0].input.size()); + modelSet::numOutputs = static_cast(training_set[0].output.size()); + + for (int i {}; i < modelSet::numInputs; ++i) { modelSet::inputNames.push_back("inputs-" + std::to_string(i + 1)); } - modelSet::numOutputs = int(training_set[0].output.size()); - - for ( auto example : training_set) + + modelSet::numOutputs = static_cast(training_set[0].output.size()); + + for (const auto& example : training_set) { if (example.input.size() != modelSet::numInputs) { throw std::length_error("unequal feature vectors in input."); return false; } + if (example.output.size() != modelSet::numOutputs) { throw std::length_error("unequal output vectors."); return false; } } - std::vector whichInputs; - - for (int j = 0; j < modelSet::numInputs; ++j) + + std::vector whichInputs {}; + + for (int inputNum {}; inputNum < modelSet::numInputs; ++inputNum) { - whichInputs.push_back(j); + whichInputs.push_back(inputNum); } - for (int i = 0; i < modelSet::numOutputs; ++i) + for (int outputNum {}; outputNum < modelSet::numOutputs; ++outputNum) { if (classificationType == svm) { @@ -118,12 +122,13 @@ bool classificationTemplate::train(const std::vector std::vector classificationTemplate::getK() { - std::vector kVector; - + std::vector kVector {}; + for (const baseModel* model : modelSet::myModelSet) { kVector.push_back(dynamic_cast*>(model)->getK()); //FIXME: I really dislike this design } + return kVector; } diff --git a/src/dtw.cpp b/src/dtw.cpp index c86ca45..99d319e 100644 --- a/src/dtw.cpp +++ b/src/dtw.cpp @@ -22,26 +22,28 @@ dtw::~dtw() {}; template inline T dtw::distanceFunction(const std::vector &x, const std::vector &y) { - double euclidianDistance = 0; + double euclidianDistance {}; + if (x.size() != y.size()) { throw std::length_error("comparing different length series"); } else { - for (std::size_t i = 0; i < x.size(); ++i) + for (std::size_t i {}; i < x.size(); ++i) { - euclidianDistance = euclidianDistance + pow((x[i] - y[i]), 2); + euclidianDistance += std::pow((x[i] - y[i]), 2); } euclidianDistance = sqrt(euclidianDistance); } - return (T)euclidianDistance; + + return static_cast(euclidianDistance); }; /* Just returns the cost, doesn't calculate the path */ template -T dtw::getCost(const std::vector > &seriesX, const std::vector > &seriesY) +T dtw::getCost(const std::vector>& seriesX, const std::vector>& seriesY) { if (seriesX.size() < seriesY.size()) return getCost(seriesY, seriesX); @@ -53,22 +55,23 @@ T dtw::getCost(const std::vector > &seriesX, const std::vector //Calculate values for the first column costMatrix[0][0] = distanceFunction(seriesX[0], seriesY[0]); - for (int j = 1; j <= maxY; ++j) + for (int y { 1 }; y <= maxY; ++y) { - costMatrix[0][j] = costMatrix[0][j - 1] + distanceFunction(seriesX[0], seriesY[j]); + costMatrix[0][y] = costMatrix[0][y - 1] + distanceFunction(seriesX[0], seriesY[y]); } - for (std::size_t i = 1; i <= maxX; ++i) + for (std::size_t x { 1 }; x <= maxX; ++x) { //Bottom row of current column - costMatrix[i][0] = costMatrix[i - 1][0] + distanceFunction(seriesX[i], seriesY[0]); - - for (std::size_t j = 1; j <= maxY; ++j) + costMatrix[x][0] = costMatrix[x - 1][0] + distanceFunction(seriesX[x], seriesY[0]); + + for (std::size_t y { 1 }; y <= maxY; ++y) { - T minGlobalCost = fmin(costMatrix[i-1][j-1], costMatrix[i][j-1]); - costMatrix[i][j] = minGlobalCost + distanceFunction(seriesX[i], seriesY[j]); + T minGlobalCost { (std::min(costMatrix[x - 1][y - 1], costMatrix[x][y - 1])) }; + costMatrix[x][y] = minGlobalCost + distanceFunction(seriesX[x], seriesY[y]); } } + return costMatrix[maxX][maxY]; }; @@ -76,38 +79,38 @@ template warpPath dtw::calculatePath(std::size_t seriesXsize, std::size_t seriesYsize) const { warpPath warpPath; - std::size_t i { seriesXsize - 1 }; - std::size_t j { seriesYsize - 1 }; - warpPath.add(i, j); - - while ((i > 0) || (j > 0)) + std::size_t x { seriesXsize - 1 }; + std::size_t y { seriesYsize - 1 }; + warpPath.add(x, y); + + while ((x > 0) || (y > 0)) { - T diagonalCost = ((i > 0) && (j > 0)) ? costMatrix[i - 1][j - 1] : std::numeric_limits::infinity(); - T leftCost = (i > 0) ? costMatrix[i - 1][j] : std::numeric_limits::infinity(); - T downCost = (j > 0) ? costMatrix[i][j - 1] : std::numeric_limits::infinity(); - + T diagonalCost { ((x > 0) && (y > 0)) ? costMatrix[x - 1][y - 1] : std::numeric_limits::infinity() }; + T leftCost { (x > 0) ? costMatrix[x - 1][y] : std::numeric_limits::infinity() }; + T downCost { (y > 0) ? costMatrix[x][y - 1] : std::numeric_limits::infinity() }; + if ((diagonalCost <= leftCost) && (diagonalCost <= downCost)) { - if (i > 0) --i; - if (j > 0) --j; + if (x > 0) --x; + if (y > 0) --y; } else if ((leftCost < diagonalCost) && (leftCost < downCost)) { - --i; + --x; } else if ((downCost < diagonalCost) && (downCost < leftCost)) { - --j; + --y; } - else if (i <= j) + else if (x <= y) { - --j; + --y; } else { - --i; + --x; } - warpPath.add(i, j); + warpPath.add(x, y); } return warpPath; @@ -115,9 +118,10 @@ warpPath dtw::calculatePath(std::size_t seriesXsize, std::size_t seriesYsize) /* calculates both the cost and the warp path*/ template -warpInfo dtw::dynamicTimeWarp(const std::vector > &seriesX, const std::vector > &seriesY) +warpInfo dtw::dynamicTimeWarp(const std::vector>& seriesX, const std::vector>& seriesY) { - warpInfo info; + warpInfo info {}; + //calculate cost matrix info.cost = getCost(seriesX, seriesY); info.path = calculatePath(seriesX.size(), seriesY.size()); @@ -132,26 +136,34 @@ warpInfo dtw::constrainedDTW(const std::vector > &seriesX, costMatrix.clear(); std::vector tempVector(seriesY.size(), std::numeric_limits::max()); costMatrix.assign(seriesX.size(), tempVector); //TODO: this could be smaller, since most cells are unused - std::size_t maxX = seriesX.size() - 1; - std::size_t maxY = seriesY.size() - 1; - + std::size_t maxX { seriesX.size() - 1 }; + std::size_t maxY { seriesY.size() - 1 }; + //fill cost matrix cells based on window - for (std::size_t currentX = 0; currentX < window.minMaxValues.size(); ++currentX) + for (std::size_t currentX {}; currentX < window.minMaxValues.size(); ++currentX) { - for (std::size_t currentY = window.minMaxValues[currentX].first; currentY <= window.minMaxValues[currentX].second; ++currentY) //FIXME: should be <= ? + for (std::size_t currentY { window.minMaxValues[currentX].first }; currentY <= window.minMaxValues[currentX].second; ++currentY) //FIXME: should be <= ? { - if (currentX == 0 && currentY == 0) { //bottom left cell + if (currentX == 0 && currentY == 0) //bottom left cell + { costMatrix[0][0] = distanceFunction(seriesX[0], seriesY[0]); - } else if (currentX == 0) { //first column + } + else if (currentX == 0) //first column + { costMatrix[0][currentY] = distanceFunction(seriesX[0], seriesY[currentY]) + costMatrix[0][currentY - 1]; - } else if (currentY == 0) { //first row + } + else if (currentY == 0) //first row + { costMatrix[currentX][0] = distanceFunction(seriesX[currentX], seriesY[0]) + costMatrix[currentX - 1][0]; - } else { - T minGlobalCost = fmin(costMatrix[currentX - 1][currentY], fmin(costMatrix[currentX-1][currentY-1], costMatrix[currentX][currentY-1])); + } + else + { + T minGlobalCost { std::min(costMatrix[currentX - 1][currentY], std::min(costMatrix[currentX-1][currentY-1], costMatrix[currentX][currentY-1])) }; costMatrix[currentX][currentY] = distanceFunction(seriesX[currentX], seriesY[currentY]) + minGlobalCost; } } } + warpInfo info; info.cost = costMatrix[maxX][maxY]; info.path = calculatePath(seriesX.size(), seriesY.size()); diff --git a/src/fastDTW.cpp b/src/fastDTW.cpp index 49d92a0..ef110ac 100644 --- a/src/fastDTW.cpp +++ b/src/fastDTW.cpp @@ -17,64 +17,73 @@ template fastDTW::~fastDTW() {}; template -warpInfo fastDTW::fullFastDTW(const std::vector> &seriesX, const std::vector > &seriesY, int searchRadius) +warpInfo fastDTW::fullFastDTW(const std::vector>& seriesX, const std::vector>& seriesY, int searchRadius) { - -#ifndef EMSCRIPTEN - if (seriesY.size() > seriesX.size()) - { - return fullFastDTW(seriesY, seriesX, searchRadius); //TODO: I'm not sure why I need this. Also, not sure why it fails with Emscripten. - } + +#ifndef RAPIDLIB_DISABLE_JSONCPP + if (seriesY.size() > seriesX.size()) + { + return fullFastDTW(seriesY, seriesX, searchRadius); //TODO: I'm not sure why I need this. Also, not sure why it fails with Emscripten. + } #endif - - dtw dtw; - searchRadius = (searchRadius < 0) ? 0 : searchRadius; - int minSeries = searchRadius + 2; - if (seriesX.size() <= minSeries || seriesY.size() <= minSeries) - { - return dtw.dynamicTimeWarp(seriesX, seriesY); - } - - T resolution = 2.0;//TODO: Just hardcode this? - std::vector> shrunkenX = downsample(seriesX, resolution); - std::vector> shrunkenY = downsample(seriesY, resolution); - - //some nice recursion here - searchWindow window(int(seriesX.size()), int(seriesY.size()), getWarpPath(shrunkenX, shrunkenY, searchRadius), searchRadius); - return dtw.constrainedDTW(seriesX, seriesY, window); + + dtw dtw; + searchRadius = std::max(0, searchRadius); + const int minSeries { searchRadius + 2 }; + + if (seriesX.size() <= minSeries || seriesY.size() <= minSeries) + { + return dtw.dynamicTimeWarp(seriesX, seriesY); + } + + T resolution { 2.0 };//TODO: Just hardcode this? + std::vector> shrunkenX { downsample(seriesX, resolution) }; + std::vector> shrunkenY { downsample(seriesY, resolution) }; + + //some nice recursion here + searchWindow window(int(seriesX.size()), int(seriesY.size()), getWarpPath(shrunkenX, shrunkenY, searchRadius), searchRadius); + + return dtw.constrainedDTW(seriesX, seriesY, window); }; template -T fastDTW::getCost(const std::vector> &seriesX, const std::vector > &seriesY, int searchRadius) +T fastDTW::getCost(const std::vector>& seriesX, const std::vector>& seriesY, int searchRadius) { - warpInfo info = fullFastDTW(seriesX, seriesY, searchRadius); - return info.cost; + warpInfo info { fullFastDTW(seriesX, seriesY, searchRadius) }; + return info.cost; }; template -warpPath fastDTW::getWarpPath(const std::vector> &seriesX, const std::vector > &seriesY, int searchRadius) +warpPath fastDTW::getWarpPath(const std::vector>& seriesX, const std::vector>& seriesY, int searchRadius) { - warpInfo info = fullFastDTW(seriesX, seriesY, searchRadius); - return info.path; + warpInfo info { fullFastDTW(seriesX, seriesY, searchRadius) }; + return info.path; }; template -inline std::vector > fastDTW::downsample(const std::vector> &series, T resolution) +inline std::vector > fastDTW::downsample(const std::vector>& series, T resolution) { - std::vector > shrunkenSeries; - - for (std::size_t i = 0; i < series.size(); ++i) { - if (i % 2 == 0) { - shrunkenSeries.push_back(series[i]); - } else { - int shrunkIndex = int(i * 0.5); - for (std::size_t j = 0; j < series[i].size(); ++j) { - shrunkenSeries[shrunkIndex][j] = (shrunkenSeries[shrunkIndex][j] + series[i][j]) * (T)0.5; - } - } + std::vector > shrunkenSeries {}; + + for (std::size_t i {}; i < series.size(); ++i) + { + if (i % 2 == 0) + { + shrunkenSeries.push_back(series[i]); + } + else + { + const int shrunkIndex { static_cast(i * 0.5) }; + + for (std::size_t j {}; j < series[i].size(); ++j) + { + shrunkenSeries[shrunkIndex][j] = (shrunkenSeries[shrunkIndex][j] + series[i][j]) * (T)0.5; + } } - //TODO: implement downsampling by resolution - return shrunkenSeries; + } + + //TODO: implement downsampling by resolution + return shrunkenSeries; } //explicit instantiation diff --git a/src/knnClassification.cpp b/src/knnClassification.cpp index 13a57b1..6ea5760 100644 --- a/src/knnClassification.cpp +++ b/src/knnClassification.cpp @@ -6,202 +6,198 @@ // Copyright © 2016 Goldsmiths. All rights reserved. // -#include -#include +#include "knnClassification.h" + +#include + +#include #include +#include #include -#include -#include "knnClassification.h" #ifdef EMSCRIPTEN #include "emscripten/knnEmbindings.h" #endif -template -knnClassification::knnClassification(const int &num_inputs, - const std::vector &which_inputs, - const std::vector > &_neighbours, - const int k) : - numInputs(num_inputs), - whichInputs(which_inputs), - whichOutput(0), - neighbours(_neighbours), - desiredK(k), - currentK(k) -{ -} - -template -knnClassification::~knnClassification() {} - -template -void knnClassification::reset() +template +knnClassification::knnClassification( + const int &num_inputs, + const std::vector &which_inputs, + const std::vector> &_neighbours, + const int k) + : numInputs(num_inputs), + whichInputs(which_inputs), + whichOutput(0), + neighbours(_neighbours), + desiredK(k), + currentK(k) +{} + +template knnClassification::~knnClassification() {} + +template void knnClassification::reset() { - //TODO: implement this + // TODO: implement this } -template -size_t knnClassification::getNumInputs() const +template size_t knnClassification::getNumInputs() const { - return numInputs; + return numInputs; } -template -std::vector knnClassification::getWhichInputs() const +template +std::vector knnClassification::getWhichInputs() const { - return whichInputs; + return whichInputs; } -template -int knnClassification::getK() const +template int knnClassification::getK() const { - return currentK; + return currentK; } -template -inline void knnClassification::updateK() +template inline void knnClassification::updateK() { - if (currentK != desiredK) currentK = std::min(desiredK, (int) neighbours.size()); + if (currentK != desiredK) currentK = std::min(desiredK, (int)neighbours.size()); } -template -void knnClassification::setK(int newK) +template void knnClassification::setK(int newK) { - desiredK = newK; - updateK(); + desiredK = newK; + updateK(); } -template -void knnClassification::addNeighbour(const int &classNum, const std::vector &features) +template +void knnClassification::addNeighbour(const int classNum, const std::vector& features) { - std::vector classVec; - classVec.push_back(T(classNum)); - trainingExampleTemplate newNeighbour = {features, classVec}; - neighbours.push_back(newNeighbour); - updateK(); + std::vector classVec; + classVec.push_back(T(classNum)); + trainingExampleTemplate newNeighbour = {features, classVec}; + neighbours.push_back(newNeighbour); + updateK(); }; -template -void knnClassification::train(const std::vector >& trainingSet) +template +void knnClassification::train(const std::vector>& trainingSet) { - train(trainingSet, 0); + train(trainingSet, 0); } -// FIXME: Not paying attention to whichOutput. -template -void knnClassification::train(const std::vector > &trainingSet, const std::size_t which_output) //FIXME: Does numInputs need to be reset here? -MZ -{ - neighbours.clear(); - neighbours = trainingSet; - updateK(); - whichOutput = which_output; +// FIXME: Not paying attention to whichOutput. +template +void knnClassification::train(const std::vector>& trainingSet, const std::size_t which_output) // FIXME: Does numInputs need to be reset here? -MZ +{ + neighbours.clear(); + neighbours = trainingSet; + updateK(); + whichOutput = which_output; }; -template -T knnClassification::run(const std::vector &inputVector) +template +T knnClassification::run(const std::vector &inputVector) { - std::vector> nearestNeighbours; //These are our k nearest neighbours - - for (size_t i { 0 }; i < currentK; ++i) + std::vector> nearestNeighbours { static_cast(currentK), std::make_pair(0, 0.0) }; // These are our k nearest neighbours + std::pair farthestNN{ 0, 0.0 }; // This one will be replaced if there's a closer one + std::vector pattern {}; // This is what we're trying to match + + for (const auto input : whichInputs) + { + pattern.push_back(inputVector[input]); + } + + // Find k nearest neighbours + for (size_t index {}; const auto &neighbour : neighbours) + { + // find Euclidian distance for this neighbor + T euclidianDistance {}; + + for (size_t j {}; j < numInputs; ++j) { - nearestNeighbours.push_back( std::make_pair(0, 0.) ); - }; - std::pair farthestNN {0, 0.}; //This one will be replaced if there's a closer one - - std::vector pattern; //This is what we're trying to match - for (size_t h { 0 }; h < numInputs; ++h) - { - pattern.push_back(inputVector[whichInputs[h]]); + euclidianDistance += (T)std::pow((pattern[j] - neighbour.input[j]), 2); } - - //Find k nearest neighbours - size_t index { 0 }; - for (auto it = neighbours.cbegin(); it != neighbours.cend(); ++it) - { - //find Euclidian distance for this neighbor - T euclidianDistance { 0 }; - for (size_t j = 0; j < numInputs ; ++j) - { - euclidianDistance += (T)pow((pattern[j] - it->input[j]), 2); - } - euclidianDistance = sqrt(euclidianDistance); - if (index < currentK) - { - //save the first k neighbours - nearestNeighbours[index] = {index, euclidianDistance}; - if (euclidianDistance > farthestNN.second) farthestNN = {index, euclidianDistance}; - } - else if (euclidianDistance < farthestNN.second) - { - //replace farthest, if new neighbour is closer - nearestNeighbours[farthestNN.first] = {index, euclidianDistance}; - size_t currentFarthest { 0 }; - T currentFarthestDistance { 0.0 }; - - for (size_t n { 0 }; n < currentK; ++n) - { - if (nearestNeighbours[n].second > currentFarthestDistance) - { - currentFarthest = n; - currentFarthestDistance = nearestNeighbours[n].second; - } - } - farthestNN = { currentFarthest, currentFarthestDistance} ; - } - ++index; + euclidianDistance = std::sqrt(euclidianDistance); + + if (index < currentK) + { + // save the first k neighbours + nearestNeighbours[index] = {index, euclidianDistance}; + if (euclidianDistance > farthestNN.second) farthestNN = { index, euclidianDistance }; } - - //majority vote on nearest neighbours - std::map classVoteMap; - using classVotePair = std::pair; - for (size_t i = 0; i < currentK; ++i) + else if (euclidianDistance < farthestNN.second) { - T classNum { (T)round(neighbours[nearestNeighbours[i].first].output[whichOutput]) }; - if ( classVoteMap.find(classNum) == classVoteMap.end() ) + // replace farthest, if new neighbour is closer + nearestNeighbours[farthestNN.first] = {index, euclidianDistance}; + size_t currentFarthest{0}; + T currentFarthestDistance{0.0}; + + for (size_t n {}; n < currentK; ++n) + { + if (nearestNeighbours[n].second > currentFarthestDistance) { - classVoteMap.insert(classVotePair(classNum, 1)); - } - else - { - ++classVoteMap[classNum]; + currentFarthest = n; + currentFarthestDistance = nearestNeighbours[n].second; } + } + farthestNN = { currentFarthest, currentFarthestDistance }; } + ++index; + } + + // majority vote on nearest neighbours + std::map classVoteMap; + using classVotePair = std::pair; - T foundClass = 0; - int mostVotes = 0; - for (auto p = classVoteMap.cbegin(); p != classVoteMap.cend(); ++p) + for (size_t i {}; i < currentK; ++i) + { + T classNum{ (T)round(neighbours[nearestNeighbours[i].first].output[whichOutput]) }; + + if (classVoteMap.find(classNum) == classVoteMap.end()) { - if (p->second > mostVotes) - { - mostVotes = p->second; - foundClass = p->first; - } + classVoteMap.insert(classVotePair(classNum, 1)); } - return foundClass; -} + else + { + ++classVoteMap[classNum]; + } + } -#ifndef EMSCRIPTEN -template -void knnClassification::getJSONDescription(Json::Value &jsonModelDescription) -{ - jsonModelDescription["modelType"] = "kNN Classificiation"; - jsonModelDescription["numInputs"] = numInputs; - jsonModelDescription["whichInputs"] = this->vector2json(whichInputs); - jsonModelDescription["k"] = desiredK; - Json::Value examples; + T foundClass {}; + int mostVotes {}; - for (auto it = neighbours.cbegin(); it != neighbours.cend(); ++it) + for (const auto& classVote : classVoteMap) + { + if (classVote.second > mostVotes) { - Json::Value oneExample; - oneExample["class"] = it->output[whichOutput]; - oneExample["features"] = this->vector2json(it->input); - examples.append(oneExample); + mostVotes = classVote.second; + foundClass = classVote.first; } + } + + return foundClass; +} - jsonModelDescription["examples"] = examples; +#ifndef RAPIDLIB_DISABLE_JSONCPP +template +void knnClassification::getJSONDescription(Json::Value &jsonModelDescription) +{ + jsonModelDescription["modelType"] = "kNN Classificiation"; + jsonModelDescription["numInputs"] = numInputs; + jsonModelDescription["whichInputs"] = this->vector2json(whichInputs); + jsonModelDescription["k"] = desiredK; + Json::Value examples; + + for (const auto& neighbour : neighbours) + { + Json::Value oneExample; + oneExample["class"] = neighbour.output[whichOutput]; + oneExample["features"] = this->vector2json(neighbour.input); + examples.append(oneExample); + } + + jsonModelDescription["examples"] = examples; } #endif -//explicit instantiation +// explicit instantiation template class knnClassification; template class knnClassification; diff --git a/src/knnClassification.h b/src/knnClassification.h index bc27d31..64a8881 100644 --- a/src/knnClassification.h +++ b/src/knnClassification.h @@ -12,88 +12,90 @@ #include #include "baseModel.h" -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP #include "../dependencies/json/json.h" #endif /** Class for implementing a knn classifier */ template -class knnClassification final : public baseModel -{ -public: - /** Constructor that takes training examples in - * @param int Number of inputs expected in the training and input vectors - * @param vector of input numbers to be fed into the classifer. - * @param vector of training examples - * @param int how many near neighbours to evaluate - */ - knnClassification(const int &num_inputs, - const std::vector &which_inputs, - const std::vector > &trainingSet, - const int k); - ~knnClassification(); - - /** add another example to the existing training set - * @param class number of example - * @param feature vector of example - */ - void addNeighbour(const int &classNum, const std::vector &features); - - /** Generate an output value from a single input vector. - * @param A standard vector of type T to be evaluated. - * @return A single value of type T: the nearest class as determined by k-nearest neighbor. - */ - T run(const std::vector &inputVector) override; - - /** Fill the model with a vector of examples. - * - * @param The training set is a vector of training examples that contain both a vector of input values and a value specifying desired output class. - * - */ - void train(const std::vector > &trainingSet) override; - - /** Fill the model with a vector of examples. Use this when the model is part of a modelSet. - * - * @param The training set is a vector of training examples that contain both a vector of input values and a value specifying desired output class. - * - */ - void train(const std::vector >& trainingSet, const std::size_t whichOutput) override; - - /** Reset the model to its empty state. */ - void reset() override; - - /** Find out how many inputs the model expects - * @return Integer number of intpus - */ - size_t getNumInputs() const override; - - /** Find out which inputs in a vector will be used - * @return Vector of ints, specifying input indices. - */ - std::vector getWhichInputs() const override; - - /** Get the number of nearest neighbours used by the kNN algorithm. */ - int getK() const; - /** Change the number of nearest neighbours used by the kNN algorithm. - * @param new value for k - */ - void setK(int newK); - -#ifndef EMSCRIPTEN - /** Populate a JSON value with a description of the current model - * @param A JSON value to be populated - */ - void getJSONDescription(Json::Value ¤tModel) override; +class knnClassification final : public baseModel +{ + public: + /** Constructor that takes training examples in + * @param int Number of inputs expected in the training and input vectors + * @param vector of input numbers to be fed into the classifer. + * @param vector of training examples + * @param int how many near neighbours to evaluate + */ + knnClassification(const int &num_inputs, + const std::vector &which_inputs, + const std::vector > &trainingSet, + const int k); + + ~knnClassification(); + + /** add another example to the existing training set + * @param class number of example + * @param feature vector of example + */ + void addNeighbour(const int classNum, const std::vector& features); + + /** Generate an output value from a single input vector. + * @param A standard vector of type T to be evaluated. + * @return A single value of type T: the nearest class as determined by k-nearest neighbor. + */ + T run(const std::vector& inputVector) override; + + /** Fill the model with a vector of examples. + * + * @param The training set is a vector of training examples that contain both a vector of input values and a value specifying desired output class. + * + */ + void train(const std::vector >& trainingSet) override; + + /** Fill the model with a vector of examples. Use this when the model is part of a modelSet. + * + * @param The training set is a vector of training examples that contain both a vector of input values and a value specifying desired output class. + * + */ + void train(const std::vector>& trainingSet, const std::size_t whichOutput) override; + + /** Reset the model to its empty state. */ + void reset() override; + + /** Find out how many inputs the model expects + * @return Integer number of intpus + */ + size_t getNumInputs() const override; + + /** Find out which inputs in a vector will be used + * @return Vector of ints, specifying input indices. + */ + std::vector getWhichInputs() const override; + + /** Get the number of nearest neighbours used by the kNN algorithm. */ + int getK() const; + + /** Change the number of nearest neighbours used by the kNN algorithm. + * @param new value for k + */ + void setK(int newK); + +#ifndef RAPIDLIB_DISABLE_JSONCPP + /** Populate a JSON value with a description of the current model + * @param A JSON value to be populated + */ + void getJSONDescription(Json::Value& currentModel) override; #endif - -private: - int numInputs; - std::vector whichInputs; - std::size_t whichOutput; - std::vector> neighbours; - int desiredK; //K that user asked for might be limited but number of examples - int currentK; //K minimum of desiredK or neighbours.size() - inline void updateK(); + + private: + int numInputs {}; + std::vector whichInputs {}; + std::size_t whichOutput {}; + std::vector> neighbours {}; + int desiredK {}; //K that user asked for might be limited but number of examples + int currentK {}; //K minimum of desiredK or neighbours.size() + inline void updateK(); }; #endif diff --git a/src/modelSet.cpp b/src/modelSet.cpp index 64968a8..8d99a78 100644 --- a/src/modelSet.cpp +++ b/src/modelSet.cpp @@ -15,9 +15,11 @@ #include #include "modelSet.h" -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP #include "../dependencies/json/json.h" -#else +#endif + +#if defined(EMSCRIPTEN) #include "emscripten/modelSetEmbindings.h" #endif @@ -129,7 +131,7 @@ std::vector modelSet::run(const std::vector &inputVector) -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP //In emscripten, we do the JSON parsing with native JavaScript template std::vector json2vector(Json::Value json) diff --git a/src/modelSet.h b/src/modelSet.h index 8a73a2b..7ceacdc 100644 --- a/src/modelSet.h +++ b/src/modelSet.h @@ -15,7 +15,7 @@ #include "neuralNetwork.h" #include "knnClassification.h" #include "svmClassification.h" -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP #include "../dependencies/json/json.h" #endif @@ -48,7 +48,7 @@ class modelSet { bool isTrained; void threadTrain(std::size_t i, const std::vector >& training_set); -#ifndef EMSCRIPTEN //The javascript code will do its own JSON parsing +#ifndef RAPIDLIB_DISABLE_JSONCPP //The javascript code will do its own JSON parsing public: /** Get a JSON representation of the model diff --git a/src/neuralNetwork.cpp b/src/neuralNetwork.cpp index b6aed03..643a819 100644 --- a/src/neuralNetwork.cpp +++ b/src/neuralNetwork.cpp @@ -328,7 +328,7 @@ T neuralNetwork::getOutBase() const return outBase; } -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP template void neuralNetwork::getJSONDescription(Json::Value& jsonModelDescription) { diff --git a/src/neuralNetwork.h b/src/neuralNetwork.h index 5307096..0c06e15 100644 --- a/src/neuralNetwork.h +++ b/src/neuralNetwork.h @@ -12,7 +12,7 @@ #include #include "baseModel.h" -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP #include "../dependencies/json/json.h" #endif @@ -81,7 +81,7 @@ class neuralNetwork final : public baseModel T getOutRange() const; T getOutBase() const; -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP void getJSONDescription(Json::Value& currentModel) override; #endif diff --git a/src/rapidStream.cpp b/src/rapidStream.cpp index 07a5e7d..6cf126e 100644 --- a/src/rapidStream.cpp +++ b/src/rapidStream.cpp @@ -19,32 +19,28 @@ namespace rapidLib { template -rapidStream::rapidStream(std::size_t window_size) : -windowSize(window_size), -windowIndex(0) +rapidStream::rapidStream(std::size_t window_size) : windowSize(window_size), windowIndex(0) { circularWindow.resize(windowSize); std::fill(circularWindow.begin(), circularWindow.end(), 0); - + //Baysian Filter setup - bayesFilt.diffusion = powf(10., -2); - bayesFilt.jump_rate = powf(10., -10); - bayesFilt.mvc[0] = 1.; + bayesFilt.diffusion = powf(10.0f, -2); + bayesFilt.jump_rate = powf(10.0f, -10); + bayesFilt.mvc[0] = 1.0f; bayesFilt.init(); } template -rapidStream::rapidStream() : -windowSize(3), -windowIndex(0) +rapidStream::rapidStream() : windowSize(3), windowIndex(0) { circularWindow.resize(windowSize); std::fill(circularWindow.begin(), circularWindow.end(), 0); - + //Baysian Filter setup - bayesFilt.diffusion = powf(10., -2); - bayesFilt.jump_rate = powf(10., -10); - bayesFilt.mvc[0] = 1.; + bayesFilt.diffusion = powf(10.0f, -2); + bayesFilt.jump_rate = powf(10.0f, -10); + bayesFilt.mvc[0] = 1.0; bayesFilt.init(); } @@ -103,9 +99,10 @@ template uint32_t rapidStream::numZeroCrossings() const { uint32_t zeroCrossings {}; - + //Is the begininng positive, negative, or 0? int previous { 1 }; + if (circularWindow[windowIndex] < 0) { previous = -1; @@ -115,11 +112,11 @@ uint32_t rapidStream::numZeroCrossings() const ++zeroCrossings; previous = 0; } - + for (std::size_t i { 1 }; i < windowSize; ++i) { const std::size_t index { (windowIndex + i) % windowSize}; - + if (circularWindow[index] < 0 && previous >= 0) //Transition to negative { ++zeroCrossings; @@ -135,14 +132,14 @@ uint32_t rapidStream::numZeroCrossings() const previous = 0; } } + return zeroCrossings; } - template T rapidStream::sum() const { - return std::accumulate(circularWindow.begin(), circularWindow.end(), 0); + return std::reduce(circularWindow.begin(), circularWindow.end()); } template @@ -156,26 +153,28 @@ T rapidStream::standardDeviation() const { const T newMean { mean() }; T standardDeviation {}; - - for (auto value : circularWindow) + + for (const auto value : circularWindow) { - standardDeviation += static_cast(pow(value - newMean, 2)); + standardDeviation += static_cast(std::pow(value - newMean, 2)); } - return sqrt(standardDeviation / windowSize); + + return std::sqrt(standardDeviation / windowSize); } template T rapidStream::rms() const { T rms {}; - + for (auto value:circularWindow) { rms += value * value; } - + rms = rms / windowSize; - return sqrt(rms); + + return std::sqrt(rms); } template @@ -211,13 +210,13 @@ template T rapidStream::minVelocity() const { T minVel { std::numeric_limits::infinity() }; - + for (std::size_t i {}; i < windowSize; ++i) { const T currentVel { calcCurrentVel(i) }; if (currentVel < minVel) minVel = currentVel; } - + return minVel; } @@ -225,13 +224,13 @@ template T rapidStream::maxVelocity() const { T maxVel { std::numeric_limits::lowest() }; - + for (std::size_t i {}; i < windowSize; ++i) { const T currentVel { calcCurrentVel(i) }; if (currentVel > maxVel) maxVel = currentVel; } - + return maxVel; } @@ -240,7 +239,7 @@ T rapidStream::minAcceleration() const { T minAccel { std::numeric_limits::infinity() }; T lastVel { calcCurrentVel(1) }; - + for (std::size_t i { 2 }; i < windowSize; ++i) { const T currentVel { calcCurrentVel(i) }; @@ -248,7 +247,7 @@ T rapidStream::minAcceleration() const lastVel = currentVel; if (currentAccel < minAccel) minAccel = currentAccel; } - + return minAccel; } @@ -257,7 +256,7 @@ T rapidStream::maxAcceleration() const { T maxAccel { std::numeric_limits::lowest() }; T lastVel { calcCurrentVel(1) }; - + for (std::size_t i { 2 }; i < windowSize; ++i) { const T currentVel { calcCurrentVel(i) }; @@ -265,7 +264,7 @@ T rapidStream::maxAcceleration() const lastVel = currentVel; if (currentAccel > maxAccel) maxAccel = currentAccel; } - + return maxAccel; } diff --git a/src/searchWindow.cpp b/src/searchWindow.cpp index 0fd6a47..85c58c9 100644 --- a/src/searchWindow.cpp +++ b/src/searchWindow.cpp @@ -11,18 +11,19 @@ #include "searchWindow.h" template -searchWindow::searchWindow(const std::size_t seriesXSize, const std::size_t seriesYSize, const warpPath &shrunkenWarpPath, const int searchRadius) : minMaxValues(seriesXSize, std::make_pair(-1, 0)), maxY(seriesYSize - 1) +searchWindow::searchWindow(const std::size_t seriesXSize, const std::size_t seriesYSize, const warpPath& shrunkenWarpPath, const int searchRadius) : minMaxValues(seriesXSize, std::make_pair(-1, 0)), + maxY(static_cast(seriesYSize - 1)) { //Current location of higher resolution path - std::pair currentIndex = shrunkenWarpPath.indices[0]; - + std::pair currentIndex { shrunkenWarpPath.indices[0] }; + //Last evaluated part of low resolution path - std::pair lastWarped = std::make_pair(std::numeric_limits::max(), std::numeric_limits::max()); - - int blockSize = 2; //TODO: something other than 2? Different for x and y? - + std::pair lastWarped { std::make_pair(std::numeric_limits::max(), std::numeric_limits::max()) }; + + const int blockSize { 2 }; //TODO: something other than 2? Different for x and y? + //project each part of the low-res path to high res cells - for ( auto &xyIndex : shrunkenWarpPath.indices) + for (const auto &xyIndex : shrunkenWarpPath.indices) { if (xyIndex.first > lastWarped.first) currentIndex.first += blockSize; if (xyIndex.second > lastWarped.second) currentIndex.second += blockSize; @@ -32,7 +33,7 @@ searchWindow::searchWindow(const std::size_t seriesXSize, const std::size_t s markVisited(currentIndex.first, currentIndex.second - 1); } - for (int j = 0; j < blockSize; ++j) + for (int j {}; j < blockSize; ++j) { markVisited(currentIndex.first + j, currentIndex.second); markVisited(currentIndex.first + j, currentIndex.second + blockSize - 1); //TODO: These are redundant? @@ -57,9 +58,13 @@ inline void searchWindow::markVisited(std::size_t col, std::size_t row) { minMaxValues[col].first = row; minMaxValues[col].second = row; - } else if (minMaxValues[col].first > row) { + } + else if (minMaxValues[col].first > row) + { minMaxValues[col].first = row; - } else if (minMaxValues[col].second < row) { + } + else if (minMaxValues[col].second < row) + { minMaxValues[col].second = row; } } @@ -71,133 +76,141 @@ void searchWindow::expandWindow(int radius) if (radius > 0) { //Add all cells in the current window to a vector. - std::vector> windowCells; + std::vector> windowCells {}; + for (int currentX = 0; currentX < minMaxValues.size(); ++currentX) { - for (std::size_t currentY = minMaxValues[currentX].first; currentY <= minMaxValues[currentX].second; ++currentY) + for (std::size_t currentY { minMaxValues[currentX].first}; currentY <= minMaxValues[currentX].second; ++currentY) { windowCells.push_back(std::make_pair(currentX, currentY)); } } - int maxX = int(minMaxValues.size() - 1); - - for (auto ¤tCell : windowCells) + const int maxX { static_cast(minMaxValues.size() - 1) }; + + for (const auto ¤tCell : windowCells) { if (currentCell.first != 0 && currentCell.second != maxY) //move to upper left if possible { //expand until edges are met - int targetX = currentCell.first - radius; - int targetY = currentCell.second + radius; - + const int targetX { currentCell.first - radius }; + const int targetY { currentCell.second + radius }; + if (targetX >= 0 && targetY <= maxY) { markVisited(targetX, targetY); } else { - int cellsPastEdge = std::max(0 - targetX, targetY - int(maxY)); //FIXME: ints? size_t? + const int cellsPastEdge { std::max(0 - targetX, targetY - maxY) }; markVisited(targetX + cellsPastEdge, targetY + cellsPastEdge); } } if (currentCell.second != maxY) //move up if possible { - int targetX = currentCell.first; - int targetY = currentCell.second + radius; + const int targetX { currentCell.first }; + const int targetY { currentCell.second + radius }; if (targetY <= maxY) { markVisited(targetX, targetY); } else { - int cellsPastEdge = targetY - int(maxY); + const int cellsPastEdge { targetY - int(maxY) }; markVisited(targetX, targetY - cellsPastEdge); } } if (currentCell.first != maxX && currentCell.second != maxY) //move upper right if possible { - std::size_t targetX = currentCell.first + radius; - std::size_t targetY = currentCell.second + radius; + const int targetX { currentCell.first + radius }; + const int targetY { currentCell.second + radius }; if (targetX <= maxX && targetY <= maxY) { markVisited(targetX, targetY); } else { - std::size_t cellsPastEdge = std::max(targetX - maxX, targetY - maxY); + const int cellsPastEdge { std::max(targetX - maxX, targetY - maxY) }; markVisited(targetX - cellsPastEdge, targetY - cellsPastEdge); } } if (currentCell.first != 0) //move left if possible { - int targetX = currentCell.first - radius; - std::size_t targetY = currentCell.second; + const int targetX { currentCell.first - radius }; + const int targetY { currentCell.second }; + if (targetX != 0) { markVisited(targetX, targetY); } else { - int cellsPastEdge = (0 - targetX); + const int cellsPastEdge { 0 - targetX }; markVisited(targetX + cellsPastEdge, targetY); } } - if (currentCell.first != maxX) { //move right if possible - int targetX = currentCell.first + radius; - int targetY = currentCell.second; + if (currentCell.first != maxX) //move right if possible + { + const int targetX { currentCell.first + radius}; + const int targetY { currentCell.second }; if (targetX <= maxX) { markVisited(targetX, targetY); } else { - int cellsPastEdge = (targetX - maxX); + const int cellsPastEdge { targetX - maxX }; markVisited(targetX - cellsPastEdge, targetY); } } if (currentCell.first != 0 && currentCell.second != 0) { //move to lower left if possible - int targetX = currentCell.first - radius; - int targetY = currentCell.second - radius; - + const int targetX { currentCell.first - radius }; + const int targetY { currentCell.second - radius }; + if (targetX >= 0 && targetY >= 0) { markVisited(targetX, targetY); } else { - int cellsPastEdge = std::max(0 - targetX, 0 - targetY); + const int cellsPastEdge { std::max(0 - targetX, 0 - targetY) }; markVisited(targetX + cellsPastEdge, targetY + cellsPastEdge); } } if (currentCell.second != 0) //move down if possible { - int targetX = currentCell.first; - int targetY = currentCell.second - radius; + const int targetX { currentCell.first }; + const int targetY { currentCell.second - radius }; + if (targetY >= 0) { markVisited(targetX, targetY); } else { - int cellsPastEdge = 0 - targetY; + const int cellsPastEdge { 0 - targetY }; markVisited(targetX, targetY + cellsPastEdge); } } if (currentCell.first != maxX && currentCell.second != 0) //move lower right if possible { - int targetX = currentCell.first + radius; - int targetY = currentCell.second - radius; - if (targetX <= maxX && targetY >= 0) { + const int targetX { currentCell.first + radius }; + const int targetY { currentCell.second - radius }; + + if (targetX <= maxX && targetY >= 0) + { markVisited(targetX, targetY); - } else { - int cellsPastEdge = std::max(targetX - maxX, 0 - targetY); + } + else + { + const int cellsPastEdge { std::max(targetX - maxX, 0 - targetY) }; markVisited(targetX - cellsPastEdge, targetY + cellsPastEdge); } } diff --git a/src/searchWindow.h b/src/searchWindow.h index a3a219b..ce43215 100644 --- a/src/searchWindow.h +++ b/src/searchWindow.h @@ -19,17 +19,18 @@ template class searchWindow { public: - searchWindow(const std::size_t seriesXSize, - const std::size_t seriesYSize, - const warpPath &shrunkenWarpPath, - const int searchRadius); - - std::vector< std::pair > minMaxValues; - + searchWindow(const std::size_t seriesXSize, + const std::size_t seriesYSize, + const warpPath &shrunkenWarpPath, + const int searchRadius); + + std::vector> minMaxValues {}; + private: - std::size_t maxY; - inline void markVisited(std::size_t col, std::size_t row); - void expandWindow(int searchRadius); + inline void markVisited(std::size_t col, std::size_t row); + void expandWindow(int searchRadius); + + int maxY {}; }; -#endif \ No newline at end of file +#endif diff --git a/src/svmClassification.cpp b/src/svmClassification.cpp index 3e42b47..fa745be 100644 --- a/src/svmClassification.cpp +++ b/src/svmClassification.cpp @@ -261,7 +261,7 @@ std::vector svmClassification::getWhichInputs() const { return returnVec; }; -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP template void svmClassification::getJSONDescription(Json::Value ¤tModel){ diff --git a/src/svmClassification.h b/src/svmClassification.h index 55c5e39..168b93f 100644 --- a/src/svmClassification.h +++ b/src/svmClassification.h @@ -113,7 +113,7 @@ class svmClassification final : public baseModel size_t getNumInputs() const override; std::vector getWhichInputs() const override; -#ifndef EMSCRIPTEN +#ifndef RAPIDLIB_DISABLE_JSONCPP void getJSONDescription(Json::Value ¤tModel) override; #endif diff --git a/src/warpPath.h b/src/warpPath.h index 1caa7ce..ebebba3 100644 --- a/src/warpPath.h +++ b/src/warpPath.h @@ -24,17 +24,15 @@ class warpPath */ void add(std::size_t x, std::size_t y); - std::vector< std::pair > indices; + std::vector> indices; }; /** return struct holding a warp path and the cost of that path */ template struct warpInfo { -public: warpPath path; T cost; - }; #endif