Browse Source
* Add SparseVector temp * Add gtest * Some reworking of the sparse concept * Change type of M from int to size_t * Add const modifier * Add needed declaration for accessing elements of _indices * Add norm_squared, norm, longerThan * Add test for all sparse vector functions * Add missing const to slice's norm_squared, norm and longerThan * Construction from Vector<M> and carray[N] * try to fix ci Co-authored-by: Julian Kent <julian@auterion.com>master
9 changed files with 299 additions and 6 deletions
@ -0,0 +1,160 @@ |
|||||||
|
/**
|
||||||
|
* @file SparseVector.hpp |
||||||
|
* |
||||||
|
* SparseVector class. |
||||||
|
* |
||||||
|
* @author Kamil Ritz <kritz@ethz.ch> |
||||||
|
* @author Julian Kent <julian@auterion.com> |
||||||
|
* |
||||||
|
*/ |
||||||
|
|
||||||
|
#pragma once |
||||||
|
|
||||||
|
#include "math.hpp" |
||||||
|
|
||||||
|
namespace matrix { |
||||||
|
template<int N> struct force_constexpr_eval { |
||||||
|
static const int value = N; |
||||||
|
}; |
||||||
|
|
||||||
|
// Vector that only store nonzero elements,
|
||||||
|
// which indices are specified as parameter pack
|
||||||
|
template<typename Type, size_t M, int... Idxs> |
||||||
|
class SparseVector { |
||||||
|
private: |
||||||
|
static constexpr size_t N = sizeof...(Idxs); |
||||||
|
static constexpr int _indices[N] {Idxs...}; |
||||||
|
|
||||||
|
static constexpr bool duplicateIndices() { |
||||||
|
for (int i = 0; i < N; i++) |
||||||
|
for (int j = 0; j < i; j++) |
||||||
|
if (_indices[i] == _indices[j]) |
||||||
|
return true; |
||||||
|
return false; |
||||||
|
} |
||||||
|
static constexpr int findMaxIndex() { |
||||||
|
int maxIndex = -1; |
||||||
|
for (int i = 0; i < N; i++) { |
||||||
|
if (maxIndex < _indices[i]) { |
||||||
|
maxIndex = _indices[i]; |
||||||
|
} |
||||||
|
} |
||||||
|
return maxIndex; |
||||||
|
} |
||||||
|
static_assert(duplicateIndices() == false, "Duplicate indices"); |
||||||
|
static_assert(N < M, "More entries than elements, use a dense vector"); |
||||||
|
static_assert(findMaxIndex() < M, "Largest entry doesn't fit in sparse vector"); |
||||||
|
|
||||||
|
Type _data[N] {}; |
||||||
|
|
||||||
|
static constexpr int findCompressedIndex(int index) { |
||||||
|
int compressedIndex = -1; |
||||||
|
for (int i = 0; i < N; i++) { |
||||||
|
if (index == _indices[i]) { |
||||||
|
compressedIndex = i; |
||||||
|
} |
||||||
|
} |
||||||
|
return compressedIndex; |
||||||
|
} |
||||||
|
|
||||||
|
public: |
||||||
|
static constexpr int non_zeros() { |
||||||
|
return N; |
||||||
|
} |
||||||
|
|
||||||
|
constexpr int index(int i) const { |
||||||
|
return SparseVector::_indices[i]; |
||||||
|
} |
||||||
|
|
||||||
|
SparseVector() = default; |
||||||
|
|
||||||
|
SparseVector(const matrix::Vector<Type, M>& data) { |
||||||
|
for (int i = 0; i < N; i++) { |
||||||
|
_data[i] = data(_indices[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
explicit SparseVector(const Type data[N]) { |
||||||
|
memcpy(_data, data, sizeof(_data)); |
||||||
|
} |
||||||
|
|
||||||
|
template <int i> |
||||||
|
inline Type at() const { |
||||||
|
static constexpr int compressed_index = force_constexpr_eval<findCompressedIndex(i)>::value; |
||||||
|
static_assert(compressed_index >= 0, "cannot access unpopulated indices"); |
||||||
|
return _data[compressed_index]; |
||||||
|
} |
||||||
|
|
||||||
|
template <int i> |
||||||
|
inline Type& at() { |
||||||
|
static constexpr int compressed_index = force_constexpr_eval<findCompressedIndex(i)>::value; |
||||||
|
static_assert(compressed_index >= 0, "cannot access unpopulated indices"); |
||||||
|
return _data[compressed_index]; |
||||||
|
} |
||||||
|
|
||||||
|
void setZero() { |
||||||
|
for (size_t i = 0; i < N; i++) { |
||||||
|
_data[i] = Type(0); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
Type dot(const matrix::Vector<Type, M>& other) const { |
||||||
|
Type accum (0); |
||||||
|
for (size_t i = 0; i < N; i++) { |
||||||
|
accum += _data[i] * other(_indices[i]); |
||||||
|
} |
||||||
|
return accum; |
||||||
|
} |
||||||
|
|
||||||
|
matrix::Vector<Type, M> operator+(const matrix::Vector<Type, M>& other) const { |
||||||
|
matrix::Vector<Type, M> vec = other; |
||||||
|
for (size_t i = 0; i < N; i++) { |
||||||
|
vec(_indices[i]) += _data[i]; |
||||||
|
} |
||||||
|
return vec; |
||||||
|
} |
||||||
|
|
||||||
|
SparseVector& operator+=(Type t) { |
||||||
|
for (size_t i = 0; i < N; i++) { |
||||||
|
_data[i] += t; |
||||||
|
} |
||||||
|
return *this; |
||||||
|
} |
||||||
|
|
||||||
|
Type norm_squared() const |
||||||
|
{ |
||||||
|
Type accum(0); |
||||||
|
for (size_t i = 0; i < N; i++) { |
||||||
|
accum += _data[i] * _data[i]; |
||||||
|
} |
||||||
|
return accum; |
||||||
|
} |
||||||
|
|
||||||
|
Type norm() const |
||||||
|
{ |
||||||
|
return matrix::sqrt(norm_squared()); |
||||||
|
} |
||||||
|
|
||||||
|
bool longerThan(Type testVal) const |
||||||
|
{ |
||||||
|
return norm_squared() > testVal*testVal; |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
template<typename Type, size_t Q, size_t M, int ... Idxs> |
||||||
|
matrix::Vector<Type, Q> operator*(const matrix::Matrix<Type, Q, M>& mat, const matrix::SparseVector<Type, M, Idxs...>& vec) { |
||||||
|
matrix::Vector<Type, Q> res; |
||||||
|
for (size_t i = 0; i < Q; i++) { |
||||||
|
const Vector<Type, M> row = mat.row(i); |
||||||
|
res(i) = vec.dot(row); |
||||||
|
} |
||||||
|
return res; |
||||||
|
} |
||||||
|
|
||||||
|
template<typename Type,size_t M, int... Idxs> |
||||||
|
constexpr int SparseVector<Type, M, Idxs...>::_indices[SparseVector<Type, M, Idxs...>::N]; |
||||||
|
|
||||||
|
template<size_t M, int ... Idxs> |
||||||
|
using SparseVectorf = SparseVector<float, M, Idxs...>; |
||||||
|
|
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
cmake_minimum_required(VERSION 2.8.4) |
||||||
|
|
||||||
|
project(googletest-download NONE) |
||||||
|
|
||||||
|
include(ExternalProject) |
||||||
|
ExternalProject_Add(googletest |
||||||
|
URL https://github.com/google/googletest/archive/8b6d3f9c4a774bef3081195d422993323b6bb2e0.zip |
||||||
|
SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" |
||||||
|
BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" |
||||||
|
CONFIGURE_COMMAND "" |
||||||
|
BUILD_COMMAND "" |
||||||
|
INSTALL_COMMAND "" |
||||||
|
TEST_COMMAND "" |
||||||
|
# Wrap download, configure and build steps in a script to log output |
||||||
|
LOG_DOWNLOAD ON |
||||||
|
LOG_CONFIGURE ON |
||||||
|
LOG_BUILD ON |
||||||
|
) |
@ -0,0 +1,12 @@ |
|||||||
|
set_directory_properties(PROPERTIES COMPILE_OPTIONS "") |
||||||
|
|
||||||
|
# Download and unpack googletest at configure time |
||||||
|
configure_file(${CMAKE_CURRENT_LIST_DIR}/CMakeLists.txt.in googletest-download/CMakeLists.txt) |
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . RESULT_VARIABLE result1 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download) |
||||||
|
execute_process(COMMAND ${CMAKE_COMMAND} --build . RESULT_VARIABLE result2 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download) |
||||||
|
if(result1 OR result2) |
||||||
|
message(FATAL_ERROR "Preparing googletest failed: ${result1} ${result2}") |
||||||
|
endif() |
||||||
|
|
||||||
|
# Add googletest, defines gtest and gtest_main targets |
||||||
|
add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) |
@ -0,0 +1,98 @@ |
|||||||
|
#include <matrix/math.hpp> |
||||||
|
#include <gtest/gtest.h> |
||||||
|
|
||||||
|
using namespace matrix; |
||||||
|
|
||||||
|
TEST(sparseVectorTest, defaultConstruction) { |
||||||
|
SparseVectorf<24, 4, 6> a; |
||||||
|
EXPECT_EQ(a.non_zeros(), 2); |
||||||
|
EXPECT_EQ(a.index(0), 4); |
||||||
|
EXPECT_EQ(a.index(1), 6); |
||||||
|
a.at<4>() = 1.f; |
||||||
|
a.at<6>() = 2.f; |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, initializationWithData) { |
||||||
|
const float data[3] = {1.f, 2.f, 3.f}; |
||||||
|
SparseVectorf<24, 4, 6, 22> a(data); |
||||||
|
EXPECT_EQ(a.non_zeros(), 3); |
||||||
|
EXPECT_EQ(a.index(0), 4); |
||||||
|
EXPECT_EQ(a.index(1), 6); |
||||||
|
EXPECT_EQ(a.index(2), 22); |
||||||
|
EXPECT_FLOAT_EQ(a.at<4>(), data[0]); |
||||||
|
EXPECT_FLOAT_EQ(a.at<6>(), data[1]); |
||||||
|
EXPECT_FLOAT_EQ(a.at<22>(), data[2]); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, initialisationFromVector) { |
||||||
|
const Vector3f vec(1.f, 2.f, 3.f); |
||||||
|
const SparseVectorf<3, 0, 2> a(vec); |
||||||
|
EXPECT_FLOAT_EQ(a.at<0>(), vec(0)); |
||||||
|
EXPECT_FLOAT_EQ(a.at<2>(), vec(2)); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, setZero) { |
||||||
|
const float data[3] = {1.f, 2.f, 3.f}; |
||||||
|
SparseVectorf<24, 4, 6, 22> a(data); |
||||||
|
a.setZero(); |
||||||
|
EXPECT_FLOAT_EQ(a.at<4>(), 0.f); |
||||||
|
EXPECT_FLOAT_EQ(a.at<6>(), 0.f); |
||||||
|
EXPECT_FLOAT_EQ(a.at<22>(), 0.f); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, additionWithDenseVector) { |
||||||
|
Vector<float, 4> dense_vec; |
||||||
|
dense_vec.setAll(1.f); |
||||||
|
const float data[3] = {1.f, 2.f, 3.f}; |
||||||
|
const SparseVectorf<4, 1, 2, 3> sparse_vec(data); |
||||||
|
const Vector<float, 4> res = sparse_vec + dense_vec; |
||||||
|
EXPECT_FLOAT_EQ(res(0), 1.f); |
||||||
|
EXPECT_FLOAT_EQ(res(1), 2.f); |
||||||
|
EXPECT_FLOAT_EQ(res(2), 3.f); |
||||||
|
EXPECT_FLOAT_EQ(res(3), 4.f); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, addScalar) { |
||||||
|
const float data[3] = {1.f, 2.f, 3.f}; |
||||||
|
SparseVectorf<4, 1, 2, 3> sparse_vec(data); |
||||||
|
sparse_vec += 2.f; |
||||||
|
EXPECT_FLOAT_EQ(sparse_vec.at<1>(), 3.f); |
||||||
|
EXPECT_FLOAT_EQ(sparse_vec.at<2>(), 4.f); |
||||||
|
EXPECT_FLOAT_EQ(sparse_vec.at<3>(), 5.f); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, dotProductWithDenseVector) { |
||||||
|
Vector<float, 4> dense_vec; |
||||||
|
dense_vec.setAll(3.f); |
||||||
|
const float data[3] = {1.f, 2.f, 3.f}; |
||||||
|
const SparseVectorf<4, 1, 2, 3> sparse_vec(data); |
||||||
|
float res = sparse_vec.dot(dense_vec); |
||||||
|
EXPECT_FLOAT_EQ(res, 18.f); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, multiplicationWithDenseMatrix) { |
||||||
|
Matrix<float, 2, 3> dense_matrix; |
||||||
|
dense_matrix.setAll(2.f); |
||||||
|
dense_matrix(1, 1) = 3.f; |
||||||
|
const Vector3f dense_vec(0.f, 1.f, 5.f); |
||||||
|
const SparseVectorf<3, 1, 2> sparse_vec(dense_vec); |
||||||
|
const Vector<float, 2> res_sparse = dense_matrix * sparse_vec; |
||||||
|
const Vector<float, 2> res_dense = dense_matrix * dense_vec; |
||||||
|
EXPECT_TRUE(isEqual(res_dense, res_sparse)); |
||||||
|
} |
||||||
|
|
||||||
|
TEST(sparseVectorTest, norms) { |
||||||
|
const float data[2] = {3.f, 4.f}; |
||||||
|
const SparseVectorf<4, 1, 3> sparse_vec(data); |
||||||
|
EXPECT_FLOAT_EQ(sparse_vec.norm_squared(), 25.f); |
||||||
|
EXPECT_FLOAT_EQ(sparse_vec.norm(), 5.f); |
||||||
|
EXPECT_TRUE(sparse_vec.longerThan(4.5f)); |
||||||
|
EXPECT_FALSE(sparse_vec.longerThan(5.5f)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv) { |
||||||
|
testing::InitGoogleTest(&argc, argv); |
||||||
|
std::cout << "Run SparseVector tests" << std::endl; |
||||||
|
return RUN_ALL_TESTS(); |
||||||
|
} |
Loading…
Reference in new issue