@@ -0,0 +1,9 @@ | |||
# project | |||
resources/ | |||
bin/ | |||
# eclipse | |||
.project | |||
.cproject | |||
.settings/ | |||
@@ -0,0 +1,167 @@ | |||
# | |||
# PDS excercise 1 Makefile | |||
# | |||
# Copyright (C) 2019-2020 Christos Choutouridis <christos@choutouridis.net> | |||
# | |||
# This program is free software: you can redistribute it and/or modify | |||
# it under the terms of the GNU Lesser General Public License as | |||
# published by the Free Software Foundation, either version 3 | |||
# of the License, or (at your option) any later version. | |||
# | |||
# This program is distributed in the hope that it will be useful, | |||
# but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||
# GNU Lesser General Public License for more details. | |||
# | |||
# You should have received a copy of the GNU Lesser General Public License | |||
# along with this program. If not, see <http://www.gnu.org/licenses/>. | |||
# | |||
# ============== Project settings ============== | |||
# Project's name | |||
PROJECT := exercise_1 | |||
# Excecutable's name | |||
TARGET := triangCounting | |||
# Source directories list(space seperated). Makefile-relative path, UNDER current directory. | |||
SRC_DIR_LIST := src | |||
# Include directories list(space seperated). Makefile-relative path. | |||
INC_DIR_LIST := src inc | |||
# Exclude files list(space seperated). Filenames only. | |||
# EXC_FILE_LIST := bad.cpp old.cpp | |||
# Build directories | |||
BUILD_DIR := bin | |||
OBJ_DIR := $(BUILD_DIR)/obj | |||
DEP_DIR := $(BUILD_DIR)/.dep | |||
# ========== Compiler settings ========== | |||
# Compiler flags for debug and release | |||
DEB_CFLAGS := -DDEBUG -g3 -Wall -Wextra -std=c++14 | |||
REL_CFLAGS := -Wall -Wextra -O2 -std=c++14 | |||
# Pre-defines | |||
# PRE_DEFS := MYCAB=1729 SUPER_MODE | |||
# ============== Linker settings ============== | |||
# Linker flags (example: -pthread -lm) | |||
LDFLAGS := -pthread | |||
# Map output file | |||
MAP_FILE := output.map | |||
MAP_FLAG := -Xlinker -Map=$(BUILD_DIR)/$(MAP_FILE) | |||
# ============== Docker settings ============== | |||
# We need: | |||
# - Bind the entire project directory(the dir that icludes all the code) as volume. | |||
# - In docker instance, change to working directory(where the makefile is). | |||
DOCKER_VOL_DIR := $${PWD} | |||
DOCKER_WRK_DIR := | |||
DOCKER_RUN := docker run --rm | |||
DOCKER_FLAGS := -v $(DOCKER_VOL_DIR):/usr/src/$(PROJECT) -w /usr/src/$(PROJECT)/$(DOCKER_WRK_DIR) | |||
# docker invoke mechanism (edit with care) | |||
# note: | |||
# Here, `DOCKER` variable is empty. Rules can assign `DOCKER := DOCKER_CMD` when docker | |||
# functionality is needed. | |||
DOCKER_CMD = $(DOCKER_RUN) $(DOCKER_FLAGS) $(IMAGE) | |||
DOCKER := | |||
# ============== Tool selection ============== | |||
# compiler and compiler flags. | |||
CSIZE := size | |||
CFLAGS := $(DEB_CFLAGS) | |||
CXX := g++ | |||
# | |||
# =========== Main body and Patterns =========== | |||
# | |||
#ifeq ($(OS), Windows_NT) | |||
# TARGET := $(TARGET).exe | |||
#endif | |||
INC := $(foreach dir,$(INC_DIR_LIST),-I$(dir)) | |||
DEF := $(foreach def,$(PRE_DEFS),-D$(def)) | |||
EXC := $(foreach fil,$(EXC_FILE_LIST), \ | |||
$(foreach dir,$(SRC_DIR_LIST),$(wildcard $(dir)/$(fil))) \ | |||
) | |||
# source files. object and dependencies list | |||
# recursive search into current and source directories | |||
SRC := $(wildcard *.cpp) | |||
SRC += $(foreach dir,$(SRC_DIR_LIST),$(wildcard $(dir)/*.cpp)) | |||
SRC += $(foreach dir,$(SRC_DIR_LIST),$(wildcard $(dir)/**/*.cpp)) | |||
SRC := $(filter-out $(EXC),$(SRC)) | |||
#SRC := $(abspath $(SRC)) | |||
OBJ := $(foreach file,$(SRC:%.cpp=%.o),$(OBJ_DIR)/$(file)) | |||
DEP := $(foreach file,$(SRC:%.cpp=%.d),$(DEP_DIR)/$(file)) | |||
# Make Dependencies pattern. | |||
# This little trick enables recompilation only when dependencies change | |||
# and it does so for changes both in source AND header files ;) | |||
# | |||
# It is based on Tom Tromey's method. | |||
# | |||
# Invoke cpp to create makefile rules with dependencies for each source file | |||
$(DEP_DIR)/%.d: %.cpp | |||
@mkdir -p $(@D) | |||
@$(DOCKER) $(CXX) -E $(CFLAGS) $(INC) $(DEF) -MM -MT $(OBJ_DIR)/$(<:.cpp=.o) -MF $@ $< | |||
# objects depent on .cpp AND dependency files, which have an empty recipe | |||
$(OBJ_DIR)/%.o: %.cpp $(DEP_DIR)/%.d | |||
@mkdir -p $(@D) | |||
$(DOCKER) $(CXX) -c $(CFLAGS) $(INC) $(DEF) -o $@ $< | |||
# empty recipe for dependency files. This prevents make errors | |||
$(DEP): | |||
# now include all dependencies | |||
# After all they are makefile dependency rules ;) | |||
include $(wildcard $(DEP)) | |||
# main target rule | |||
$(BUILD_DIR)/$(TARGET): $(OBJ) | |||
@mkdir -p $(@D) | |||
@echo Linking to target: $(TARGET) | |||
@echo $(DOCKER) $(CXX) $(LDFLAGS) $(MAP_FLAG) -o $(@D)/$(TARGET) '$$(OBJ)' | |||
@$(DOCKER) $(CXX) $(LDFLAGS) $(MAP_FLAG) -o $(@D)/$(TARGET) $(OBJ) | |||
@echo | |||
@echo Print size information | |||
@$(CSIZE) $(@D)/$(TARGET) | |||
@echo Done | |||
.PHONY: clean | |||
clean: | |||
@echo Cleaning build directories | |||
@rm -rf $(OBJ_DIR) | |||
@rm -rf $(DEP_DIR) | |||
@rm -rf $(BUILD_DIR) | |||
# | |||
# ================ Local build rules ================= | |||
# examples: | |||
# make debug | |||
.PHONY: debug | |||
debug: CFLAGS := $(DEB_CFLAGS) | |||
debug: $(BUILD_DIR)/$(TARGET) | |||
.PHONY: release | |||
release: CFLAGS := $(REL_CFLAGS) | |||
release: $(BUILD_DIR)/$(TARGET) | |||
.PHONY: all | |||
all: release | |||
# | |||
# ================ Docker based rules ================ | |||
# examples: | |||
# make IMAGE="gcc:8.3" dock | |||
# | |||
.PHONY: dock | |||
dock: DOCKER := $(DOCKER_CMD) | |||
dock: CFLAGS := $(REL_CFLAGS) | |||
dock: $(BUILD_DIR)/$(TARGET) | |||
@@ -0,0 +1,36 @@ | |||
/*! | |||
* \file config,h | |||
* \brief Build configuration file. | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#ifndef CONFIG_H_ | |||
#define CONFIG_H_ | |||
#include <v12.h> | |||
#include <v3.h> | |||
#include <v4.h> | |||
#define V12 2 | |||
#define V3 3 | |||
#define V4 4 | |||
#if !defined CODE_VERSION | |||
#define CODE_VERSION V4 | |||
#endif | |||
#if CODE_VERSION == V12 | |||
using namespace v12; | |||
using matrix = v12::matrix; | |||
#elif CODE_VERSION == V3 | |||
using namespace v3; | |||
using matrix = v3::matrix; | |||
#elif CODE_VERSION == V4 | |||
using namespace v4; | |||
using matrix = v4::matrix; | |||
#endif | |||
#endif /* CONFIG_H_ */ |
@@ -0,0 +1,519 @@ | |||
/** | |||
* \file impl.hpp | |||
* \brief Implementation common header file | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#ifndef IMPL_HPP_ | |||
#define IMPL_HPP_ | |||
#include <type_traits> | |||
#include <utility> | |||
#include <algorithm> | |||
#include <vector> | |||
#include <tuple> | |||
#include <cstddef> | |||
#include <iostream> | |||
#include <fstream> | |||
using std::size_t; | |||
/* | |||
* Small helper to strip types | |||
*/ | |||
template<typename T> | |||
struct remove_cvref { | |||
typedef std::remove_cv_t<std::remove_reference_t<T>> type; | |||
}; | |||
template<typename T> | |||
using remove_cvref_t = typename remove_cvref<T>::type; | |||
/*! | |||
* Enumerator to denote the storage type of the array to use. | |||
*/ | |||
enum class MatrixType { | |||
FULL, /*!< Matrix is asymmetric */ | |||
SYMMETRIC, /*!< Matrix is symmetric */ | |||
}; | |||
/* | |||
* Forward type declerations | |||
*/ | |||
template<typename DataType, typename IndexType, MatrixType Type = MatrixType::SYMMETRIC> struct Matrix; | |||
template<typename DataType, typename IndexType, MatrixType Type = MatrixType::SYMMETRIC> struct SpMat; | |||
template<typename DataType, typename IndexType> struct SpMatCol; | |||
template<typename DataType, typename IndexType> struct SpMatRow; | |||
template<typename DataType, typename IndexType> struct SpMatVal; | |||
/*! | |||
* 2D-array wrapper for v1 and v2 part of the exercise t use as RAII | |||
* and copy-prevention. | |||
* | |||
* This is a very thin abstraction layer over a native array. | |||
* This is tested using compiler explorer and our template produce | |||
* almost identical assembly. | |||
* | |||
* The penalty hit we have is due to the fact that we use a one dimension array | |||
* and we have to calculate the actual position from an (i,j) pair. | |||
* The use of 1D array was our intention from the beginning, so the penalty | |||
* was pretty much unavoidable. | |||
* | |||
* \tparam DataType The underling data type of the array | |||
* \tparam Type The storage type of the array | |||
* \arg FULL For full matrix | |||
* \arg SYMMETRIC For symmetric matrix (we use only the lower part) | |||
*/ | |||
template<typename DataType, typename IndexType, MatrixType Type> | |||
struct Matrix { | |||
using dataType = DataType; //!< meta:export of underling data type | |||
using indexType = IndexType; //!< meta:export of underling index type | |||
static constexpr MatrixType matrixType = Type; //!< export of array type | |||
/*! | |||
* \name Obj lifetime | |||
*/ | |||
//! @{ | |||
//! Constructor using data type and size | |||
Matrix(DataType* t, IndexType s) noexcept : m_(t), size_(s) { } | |||
//! RAII deleter | |||
~Matrix() { delete m_; } | |||
//! move ctor | |||
Matrix(Matrix&& a) noexcept { a.swap(*this); } | |||
//! move | |||
Matrix& operator=(Matrix&& a) noexcept { a.swap(*this); return *this; } | |||
Matrix(const Matrix& a) = delete; //!< No copy ctor | |||
Matrix& operator=(const Matrix& a) = delete; //!< No copy | |||
//! @} | |||
//! \name Data exposure | |||
//! @{ | |||
//! memory capacity of the matrix with diagonal data | |||
template<MatrixType AT=Type> std::enable_if_t<AT==MatrixType::SYMMETRIC, IndexType> | |||
static constexpr capacity(IndexType N) noexcept { return (N+1)*N/2; } | |||
//! memory capacity for full matrix | |||
template<MatrixType AT=Type> std::enable_if_t<AT==MatrixType::FULL, IndexType> | |||
static constexpr capacity(IndexType N) noexcept { return N*N; } | |||
//! Get the size of each dimension | |||
IndexType size() noexcept { return size_; } | |||
/* | |||
* virtual 2D accessors | |||
*/ | |||
template<MatrixType AT=Type> | |||
std::enable_if_t <AT==MatrixType::SYMMETRIC, DataType> | |||
get (IndexType i, IndexType j) { | |||
if (i<j) return m_[j*(j+1)/2 + i]; // Upper, use opposite index | |||
else return m_[i*(i+1)/2 + j]; // Lower, use our notation | |||
} | |||
template<MatrixType AT=Type> | |||
std::enable_if_t <AT==MatrixType::FULL, DataType> | |||
get(IndexType i, IndexType j) { | |||
return m_[i*size_ + j]; | |||
} | |||
template<MatrixType AT=Type> | |||
std::enable_if_t <AT==MatrixType::SYMMETRIC, DataType> | |||
set(DataType v, IndexType i, IndexType j) { | |||
if (i<j) return m_[j*(j+1)/2 + i] =v; // Upper, use opposite index | |||
else return m_[i*(i+1)/2 + j] =v; // Lower, use our notation | |||
} | |||
template<MatrixType AT=Type> | |||
std::enable_if_t <AT==MatrixType::FULL, DataType> | |||
set(DataType v, IndexType i, IndexType j) { | |||
return m_[i*size_ + j] =v; | |||
} | |||
DataType operator()(IndexType i, IndexType j) { return get(i, j); } | |||
// a basic serial iterator support | |||
DataType* begin() noexcept { return m_; } | |||
DataType* end() noexcept { return m_ + Matrix::capacity(size_); } | |||
//! @} | |||
/*! | |||
* \name Safe iteration API | |||
* | |||
* This api automates the iteration over the array based on | |||
* MatrixType | |||
*/ | |||
//! @{ | |||
template<typename F, typename... Args> | |||
void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) { | |||
for (IndexType it=begin ; it<end ; ++it) { | |||
std::forward<F>(lambda)(std::forward<Args>(args)..., it); | |||
} | |||
} | |||
//! @} | |||
// move helper | |||
void swap(Matrix& src) noexcept { | |||
std::swap(m_, src.m_); | |||
std::swap(size_, src.size_); | |||
} | |||
private: | |||
DataType* m_; //!< Pointer to actual data. | |||
IndexType size_; //!< the virtual size of each dimension. | |||
}; | |||
/*! | |||
* RAII allocation helper, the smart_ptr way. | |||
*/ | |||
template<typename DataType, typename IndexType, MatrixType Type = MatrixType::SYMMETRIC> | |||
Matrix<DataType, IndexType, Type> make_Matrix(IndexType s) { | |||
return Matrix<DataType, IndexType, Type>(new DataType[Matrix<DataType, IndexType, Type>::capacity(s)], s); | |||
} | |||
template<typename DataType, typename IndexType> | |||
using CooVal = std::tuple<DataType, IndexType, IndexType>; | |||
template<typename DataType, typename IndexType, MatrixType Type> | |||
struct SpMat { | |||
using dataType = DataType; //!< meta:export of underling data type | |||
using indexType = IndexType; //!< meta:export of underling index type | |||
static constexpr MatrixType matrixType = Type; //!< export of array type | |||
friend class SpMatCol<DataType, IndexType>; | |||
friend class SpMatRow<DataType, IndexType>; | |||
friend class SpMatVal<DataType, IndexType>; | |||
/*! | |||
* \name Obj lifetime | |||
*/ | |||
//! @{ | |||
//! allocation with init value ctor | |||
SpMat(IndexType n=IndexType{}, IndexType nnz=IndexType{}) : | |||
values(nnz, DataType{}), | |||
rows(nnz, IndexType{}), | |||
col_ptr((n)? n+1:2, IndexType{}), | |||
N(n), | |||
NNZ(nnz) { } | |||
SpMat(IndexType n, IndexType nnz, const IndexType* row, const IndexType* col) : | |||
values(nnz, 1), | |||
rows(row, row+nnz), | |||
col_ptr(col, col+n+1), | |||
N(n), | |||
NNZ(nnz) { } | |||
SpMat(IndexType n, IndexType nnz, const DataType* v, const IndexType* row, const IndexType* col) : | |||
values(v, v+nnz), | |||
rows(row, row+nnz), | |||
col_ptr(col, col+n+1), | |||
N(n), | |||
NNZ(nnz) { } | |||
SpMat(IndexType n, IndexType nnz, const DataType v, const std::vector<IndexType>& row, const std::vector<IndexType>& col) : | |||
values(nnz, v), | |||
rows (row), | |||
col_ptr(col), | |||
N(n), | |||
NNZ(nnz) { } | |||
//! move ctor | |||
SpMat(SpMat&& a) noexcept { moves(std::move(a)); } | |||
//! move | |||
SpMat& operator=(SpMat&& a) noexcept { moves(std::move(a)); return *this; } | |||
SpMat(const SpMat& a) = delete; //!< No copy ctor | |||
SpMat& operator=(const SpMat& a) = delete; //!< No copy assignment | |||
//! @} | |||
//! \name Data exposure | |||
//! @{ | |||
IndexType size() noexcept { return N; } | |||
IndexType size(IndexType n) { | |||
col_ptr.resize(n+1); | |||
return N = n; | |||
} | |||
IndexType capacity() noexcept { return NNZ; } | |||
IndexType capacity(IndexType nnz) { | |||
values.reserve(nnz); | |||
rows.reserve(nnz); | |||
return NNZ; | |||
} | |||
std::vector<IndexType>& getRows() noexcept { return rows; } | |||
std::vector<IndexType>& getCols() noexcept { return col_ptr; } | |||
SpMatVal<DataType, IndexType> operator()(IndexType i, IndexType j) { | |||
return SpMatVal<DataType, IndexType>(this, get(i, j), i, j); | |||
} | |||
DataType get(IndexType i, IndexType j) { | |||
IndexType idx; | |||
bool found; | |||
std::tie(idx, found) =find_idx(rows, col_ptr[j], col_ptr[j+1], i); | |||
return (found) ? values[idx] : 0; | |||
} | |||
DataType set(DataType v, IndexType i, IndexType j) { | |||
IndexType idx; bool found; | |||
std::tie(idx, found) = find_idx(rows, col_ptr[j], col_ptr[j+1], i); | |||
if (found) | |||
return values[idx] = v; // we don't change NNZ even if we write "0" | |||
else { | |||
values.insert(values.begin()+idx, v); | |||
rows.insert(rows.begin()+idx, i); | |||
std::transform(col_ptr.begin()+j+1, col_ptr.end(), col_ptr.begin()+j+1, [](IndexType it) { | |||
return ++it; | |||
}); | |||
++NNZ; // we increase the NNZ even if we write "0" | |||
return v; | |||
} | |||
} | |||
SpMatCol<DataType, IndexType> getCol(IndexType j) { | |||
return SpMatCol<DataType, IndexType>(this, col_ptr[j], col_ptr[j+1]); | |||
} | |||
template<MatrixType AT= Type> | |||
std::enable_if_t<AT==MatrixType::SYMMETRIC, SpMatCol<DataType, IndexType>> | |||
getRow(IndexType i) { | |||
return getCol(i); | |||
} | |||
template<MatrixType AT= Type> | |||
std::enable_if_t<AT==MatrixType::FULL, SpMatCol<DataType, IndexType>> | |||
getRow(IndexType i) { | |||
return SpMatRow<DataType, IndexType>(this, i); | |||
} | |||
// iterator support | |||
DataType* begin() noexcept { return values.begin(); } | |||
DataType* end() noexcept { return values.end(); } | |||
//! @} | |||
/*! | |||
* \name Safe iteration API | |||
* | |||
* This api automates the iteration over the array based on | |||
* MatrixType | |||
*/ | |||
//! @{ | |||
template<typename F, typename... Args> | |||
void for_each_in (IndexType begin, IndexType end, F&& lambda, Args&&... args) { | |||
for (IndexType it=begin ; it<end ; ++it) { | |||
std::forward<F>(lambda)(std::forward<Args>(args)..., it); | |||
} | |||
} | |||
//! @} | |||
// operations | |||
template<typename D, typename I, MatrixType T> friend void print(SpMat<D, I, T>& mat); | |||
template<typename D, typename I, MatrixType T> friend void print_dense(SpMat<D, I, T>& mat); | |||
private: | |||
// index-find helper | |||
std::pair<IndexType, bool> find_idx(const std::vector<IndexType>& v, IndexType begin, IndexType end, IndexType match) { | |||
for ( ; begin < end ; ++begin) { | |||
if (match == v[begin]) return std::make_pair(begin, true); | |||
else if (match < v[begin]) return std::make_pair(begin, false); | |||
} | |||
return std::make_pair(end, false); | |||
} | |||
// move helper | |||
void moves(SpMat&& src) noexcept { | |||
values = std::move(src.values); | |||
rows = std::move(src.rows); | |||
col_ptr = std::move(src.col_ptr); | |||
N = std::move(src.N); // redundant for primitives | |||
NNZ = std::move(src.NNZ); // | |||
} | |||
//! \name Data | |||
//! @{ | |||
std::vector<DataType> values {}; | |||
std::vector<IndexType> rows{}; | |||
std::vector<IndexType> col_ptr{1,0}; | |||
IndexType N{0}; | |||
IndexType NNZ{0}; | |||
//! @} | |||
}; | |||
template<typename DataType, typename IndexType> | |||
struct SpMatCol { | |||
using owner_t = SpMat<DataType, IndexType>; | |||
SpMatCol(owner_t* own, const IndexType begin, const IndexType end) noexcept : | |||
owner_(own), index_(begin), begin_(begin), end_(end) { | |||
vindex_ = vIndexCalc(index_); | |||
} | |||
SpMatCol() = default; | |||
SpMatCol(const SpMatCol&) = delete; | |||
SpMatCol& operator=(const SpMatCol&)= delete; | |||
SpMatCol(SpMatCol&&) = default; | |||
SpMatCol& operator=(SpMatCol&&) = default; | |||
DataType operator* () { | |||
return get(); | |||
} | |||
SpMatCol& operator++ () { advance(); return *this; } | |||
SpMatCol& operator++ (int) { SpMatCol& p = *this; advance(); return p; } | |||
DataType operator()(IndexType x) { | |||
return (x == index())? get() : DataType{}; | |||
} | |||
DataType operator= (DataType v) { return owner_->values[index_] = v; } | |||
IndexType index() noexcept { return vindex_; } | |||
const IndexType index() const noexcept { return vindex_; } | |||
IndexType begin() noexcept { return vIndexCalc(begin_); } | |||
const IndexType begin() const noexcept { return vIndexCalc(begin_); } | |||
IndexType end() noexcept { return owner_->N; } | |||
const IndexType end() const noexcept { return owner_->N; } | |||
template <typename C> | |||
DataType operator* (C&& c) { | |||
static_assert(std::is_same<remove_cvref_t<C>, SpMatCol<DataType, IndexType>>(), ""); | |||
DataType v{}; | |||
while (index() != end() && c.index() != c.end()) { | |||
if (index() < c.index()) advance(); | |||
else if (index() > c.index()) ++c; | |||
else { //index() == c.index() | |||
v += get() * *c; | |||
++c; | |||
advance(); | |||
} | |||
} | |||
return v; | |||
} | |||
private: | |||
void advance() noexcept { | |||
++index_; | |||
vindex_ = vIndexCalc(index_); | |||
} | |||
IndexType vIndexCalc(IndexType idx) { | |||
return (idx < end_) ? owner_->rows[idx] : end(); | |||
} | |||
DataType get() { return owner_->values[index_]; } | |||
owner_t* owner_{nullptr}; | |||
IndexType vindex_ {IndexType{}}; | |||
IndexType index_{IndexType{}}; | |||
IndexType begin_{IndexType{}}; | |||
IndexType end_{IndexType{}}; | |||
}; | |||
template<typename DataType, typename IndexType> | |||
struct SpMatRow { | |||
using owner_t = SpMat<DataType, IndexType>; | |||
SpMatRow(owner_t* own, const IndexType row) noexcept : | |||
owner_(own), vindex_(IndexType{}), row_(row), index_(IndexType{}), | |||
begin_(IndexType{}), end_(owner_->NNZ) { | |||
// place begin | |||
while(begin_ != end_ && owner_->rows[begin_] != row_) | |||
++begin_; | |||
// place index_ and vindex_ | |||
if (owner_->rows[index_] != row_) | |||
advance(); | |||
} | |||
SpMatRow() = default; | |||
SpMatRow(const SpMatRow&) = delete; | |||
SpMatRow& operator=(const SpMatRow&)= delete; | |||
SpMatRow(SpMatRow&&) = default; | |||
SpMatRow& operator=(SpMatRow&&) = default; | |||
DataType operator* () { | |||
return get(); | |||
} | |||
SpMatRow& operator++ () { advance(); return *this; } | |||
SpMatRow& operator++ (int) { SpMatRow& p = *this; advance(); return p; } | |||
DataType operator()(IndexType x) { | |||
return (x == index())? get() : DataType{}; | |||
} | |||
DataType operator= (DataType v) { return owner_->values[index_] = v; } | |||
IndexType index() noexcept { return vindex_; } | |||
const IndexType index() const noexcept { return vindex_; } | |||
IndexType begin() noexcept { return vIndexCalc(begin_); } | |||
const IndexType begin() const noexcept { return vIndexCalc(begin_); } | |||
IndexType end() noexcept { return owner_->N; } | |||
const IndexType end() const noexcept { return owner_->N; } | |||
template <typename C> | |||
DataType operator* (C&& c) { | |||
static_assert(std::is_same<remove_cvref_t<C>, SpMatCol<DataType, IndexType>>(), ""); | |||
DataType v{}; | |||
while (index() != end() && c.index() != c.end()) { | |||
if (index() < c.index()) advance(); | |||
else if (index() > c.index()) ++c; | |||
else { //index() == c.index() | |||
v += get()* *c; | |||
++c; | |||
advance(); | |||
} | |||
} | |||
return v; | |||
} | |||
private: | |||
void advance() noexcept { | |||
do | |||
++index_; | |||
while(index_ != end_ && owner_->rows[index_] != row_); | |||
vindex_ = vIndexCalc(index_); | |||
} | |||
IndexType vIndexCalc(IndexType idx) { | |||
for(IndexType i =0 ; i<(owner_->N+1) ; ++i) | |||
if (idx < owner_->col_ptr[i]) | |||
return i-1; | |||
return end(); | |||
} | |||
DataType get() { return owner_->values[index_]; } | |||
owner_t* owner_ {nullptr}; | |||
IndexType vindex_ {IndexType{}}; | |||
IndexType row_ {IndexType{}}; | |||
IndexType index_ {IndexType{}}; | |||
IndexType begin_ {IndexType{}}; | |||
IndexType end_ {IndexType{}}; | |||
}; | |||
template<typename DataType, typename IndexType> | |||
struct SpMatVal { | |||
using owner_t = SpMat<DataType, IndexType>; | |||
SpMatVal(owner_t* own, DataType v, IndexType i, IndexType j) : | |||
owner_(own), v_(v), i_(i), j_(j) { } | |||
SpMatVal() = default; | |||
SpMatVal(const SpMatVal&) = delete; | |||
SpMatVal& operator=(const SpMatVal&) = delete; | |||
SpMatVal(SpMatVal&&) = default; | |||
SpMatVal& operator=(SpMatVal&&) = default; | |||
operator DataType() { return v_; } | |||
SpMatVal& operator=(DataType v) { | |||
v_ = v; | |||
owner_->set(v_, i_, j_); | |||
return *this; | |||
} | |||
private: | |||
owner_t* owner_{nullptr};; | |||
DataType v_{DataType{}}; | |||
IndexType i_{IndexType{}}; | |||
IndexType j_{IndexType{}}; | |||
}; | |||
enum class InputMatrix{ | |||
GENERATE, | |||
MTX | |||
}; | |||
/*! | |||
* Session option for each invocation of the executable | |||
*/ | |||
struct session_t { | |||
std::size_t size {0}; | |||
double probability {0}; | |||
InputMatrix inputMatrix {InputMatrix::GENERATE}; | |||
std::ifstream mtxFile {}; | |||
std::size_t print_size {80}; | |||
bool timing {false}; | |||
bool print {false}; | |||
bool makeSymmetric {false}; | |||
}; | |||
extern session_t session; | |||
#endif /* IMPL_HPP_ */ |
@@ -0,0 +1,198 @@ | |||
/*! | |||
* \file utils.h | |||
* \brief Utilities to handle matrix files, chrono, etc... | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#ifndef UTILS_H_ | |||
#define UTILS_H_ | |||
#include <string> | |||
#include <sstream> | |||
#include <iostream> | |||
#include <chrono> | |||
#include <random> | |||
#include <impl.hpp> | |||
#include <config.h> | |||
template <typename T> | |||
struct buffer_t { | |||
buffer_t(size_t s) { p = new T[s]; } | |||
~buffer_t() { delete[] p; } | |||
buffer_t() = default; | |||
buffer_t(buffer_t&&) = default; | |||
buffer_t& operator=(buffer_t&&) = default; | |||
buffer_t(const buffer_t&) = delete; | |||
buffer_t& operator=(const buffer_t&) = delete; | |||
T* allocate(size_t s) { return p = new T[s]; } | |||
T* operator() () { return p; } | |||
T& operator[] (size_t i){ return p[i]; } | |||
private: | |||
T* p{nullptr}; | |||
}; | |||
struct Mtx { | |||
template<typename I> | |||
static void coo2csc(I *row, I *col, I const* row_coo, I const* col_coo, I nnz, I n, I isOneBased) { | |||
// ----- cannot assume that input is already 0! | |||
for (I l = 0; l < n+1; l++) col[l] = 0; | |||
// ----- find the correct column sizes | |||
for (I l = 0; l < nnz; l++) | |||
col[col_coo[l] - isOneBased]++; | |||
// ----- cumulative sum | |||
for (I i = 0, cumsum = 0; i < n; i++) { | |||
I temp = col[i]; | |||
col[i] = cumsum; | |||
cumsum += temp; | |||
} | |||
col[n] = nnz; | |||
// ----- copy the row indices to the correct place | |||
for (I l = 0; l < nnz; l++) { | |||
I col_l; | |||
col_l = col_coo[l] - isOneBased; | |||
I dst = col[col_l]; | |||
row[dst] = row_coo[l] - isOneBased; | |||
col[col_l]++; | |||
} | |||
// ----- revert the column pointers | |||
for (I i = 0, last = 0; i < n; i++) { | |||
I temp = col[i]; | |||
col[i] = last; | |||
last = temp; | |||
} | |||
} | |||
template<typename I> | |||
static bool is_triangular (std::ifstream& file) { | |||
std::string line, token; | |||
enum state_en {HEADER, SIZE, DATA} state = HEADER; | |||
enum LU_t {Z, LOWER, UPPER} LU = Z; | |||
while (std::getline (file, line, '\n')) { | |||
std::stringstream ss(line); | |||
switch (state) { | |||
case HEADER: | |||
ss >> token; | |||
if (token != "%%MatrixMarket") return false; | |||
else state = SIZE; | |||
break; | |||
case SIZE: | |||
if (line[0] == '%') continue; | |||
else state = DATA; | |||
break; | |||
case DATA: | |||
if (line[0] == '%') continue; | |||
I i, j; | |||
ss >> i >> j; | |||
switch (LU) { | |||
case Z: LU = (i<j) ? UPPER: LOWER; break; | |||
case LOWER: if (i<=j) return false; break; | |||
case UPPER: if (j<=i) return false; break; | |||
} | |||
break; | |||
} | |||
} | |||
file.clear(); // rewind | |||
file.seekg(0); | |||
return true; | |||
} | |||
template<typename DataT, typename IndexT, MatrixType MatrixT> | |||
static bool load (SpMat<DataT, IndexT, MatrixT>& M, std::ifstream& file) { | |||
std::string line, token; | |||
enum state_en {HEADER, SIZE, DATA} state = HEADER; | |||
enum LU_t {Z, LOWER, UPPER} LU = Z; | |||
IndexT n1, n2, nnz; | |||
buffer_t<IndexT> col{}, row{}, coo_col{}, coo_row{}; | |||
IndexT cnt{}; | |||
while (std::getline (file, line, '\n')) { | |||
std::stringstream ss(line); | |||
switch (state) { | |||
case HEADER: | |||
ss >> token; | |||
if (token != "%%MatrixMarket") return false; | |||
else state = SIZE; | |||
break; | |||
case SIZE: | |||
if (line[0] == '%') continue; | |||
else { | |||
ss >> n1 >> n2 >> nnz; | |||
if (session.makeSymmetric) | |||
nnz *= 2; | |||
col.allocate(nnz); | |||
row.allocate(nnz); | |||
coo_col.allocate(nnz); | |||
coo_row.allocate(nnz); | |||
state = DATA; | |||
} | |||
break; | |||
case DATA: | |||
if (line[0] == '%') continue; | |||
IndexT i, j; | |||
ss >> i >> j; | |||
if (session.makeSymmetric) { | |||
if (LU == Z) { | |||
LU = (i<j) ? UPPER: LOWER; | |||
} | |||
if ((LU==LOWER && j<i) || (LU==UPPER && i<j)) { | |||
coo_row[cnt] = i; | |||
coo_col[cnt++] = j; | |||
coo_row[cnt] = j; | |||
coo_col[cnt++] = i; | |||
} | |||
} | |||
else { | |||
coo_row[cnt] = i; | |||
coo_col[cnt++] = j; | |||
} | |||
break; | |||
} | |||
} | |||
coo2csc(&row[0], &col[0], &coo_row[0], &coo_col[0], cnt, n1, 1); | |||
M = SpMat<DataT, IndexT, MatrixT>(n1, cnt, &row[0], &col[0]); | |||
return true; | |||
} | |||
}; | |||
struct Timing{ | |||
using Tpoint = std::chrono::steady_clock::time_point; | |||
using microseconds = std::chrono::microseconds; | |||
using milliseconds = std::chrono::milliseconds; | |||
using seconds = std::chrono::seconds; | |||
Tpoint start () noexcept { return start_ = std::chrono::steady_clock::now(); } | |||
Tpoint stop () noexcept { return stop_ = std::chrono::steady_clock::now(); } | |||
auto dt () noexcept { | |||
return std::chrono::duration_cast<std::chrono::microseconds>(stop_ - start_).count(); | |||
} | |||
void print_dt () noexcept { | |||
auto t = stop_ - start_; | |||
if (std::chrono::duration_cast<microseconds>(t).count() < 10000) | |||
std::cout << "time: " << std::to_string(std::chrono::duration_cast<microseconds>(t).count()) << " [usec]\n"; | |||
else if (std::chrono::duration_cast<milliseconds>(t).count() < 10000) | |||
std::cout << "time: " << std::to_string(std::chrono::duration_cast<milliseconds>(t).count()) << " [msec]\n"; | |||
else | |||
std::cout << "time: " << std::to_string(std::chrono::duration_cast<seconds>(t).count()) << " [sec]\n"; | |||
} | |||
private: | |||
Tpoint start_; | |||
Tpoint stop_; | |||
}; | |||
void init_ER_graph (matrix& A, double p); | |||
void print_ER_graph (matrix& A); | |||
#endif /* UTILS_H_ */ |
@@ -0,0 +1,21 @@ | |||
/*! | |||
* \file v12.h | |||
* \brief v1 and v2 part of the exercise header file. | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#ifndef V12_H_ | |||
#define V12_H_ | |||
#include <impl.hpp> | |||
namespace v12 { | |||
using matrix = Matrix<int, int>; | |||
int triang_count (matrix& A) ; | |||
}; | |||
#endif /* V12_H_ */ |
@@ -0,0 +1,21 @@ | |||
/*! | |||
* \file v3.h | |||
* \brief v3 part of the exercise header file. | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#ifndef V3_H_ | |||
#define V3_H_ | |||
#include <impl.hpp> | |||
namespace v3 { | |||
using matrix = SpMat<int, int>; | |||
int triang_count (matrix& A); | |||
}; | |||
#endif /* V3_H_ */ |
@@ -0,0 +1,21 @@ | |||
/*! | |||
* \file v4.h | |||
* \brief v4 part of the exercise header file. | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#ifndef V4_H_ | |||
#define V4_H_ | |||
#include <impl.hpp> | |||
namespace v4 { | |||
using matrix = SpMat<int, int>; | |||
int triang_count (matrix& A); | |||
}; | |||
#endif /* V4_H_ */ |
@@ -0,0 +1,112 @@ | |||
/*! | |||
* \file main.cpp | |||
* \brief Main application file | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#include <iostream> | |||
#include <string> | |||
#include <exception> | |||
#include <utils.h> | |||
#include <config.h> | |||
// Global session data | |||
session_t session; | |||
/*! | |||
* A small command line argument parser | |||
* \return The status of the operation | |||
*/ | |||
bool get_options(int argc, char* argv[]){ | |||
bool status =true; | |||
// iterate over the passed arguments | |||
for (int i=1 ; i<argc ; ++i) { | |||
std::string arg(argv[i]); // get current argument | |||
if (arg == "-i" || arg == "--input") { | |||
session.inputMatrix = InputMatrix::MTX; | |||
if (i+1 < argc) | |||
session.mtxFile = std::ifstream(argv[++i]); | |||
else | |||
status = false; | |||
} | |||
else if (arg == "-g" || arg == "--generate") | |||
session.inputMatrix = InputMatrix::GENERATE; | |||
else if (arg == "-s" || arg == "--size") | |||
session.size = (i+1 < argc) ? std::atoi(argv[++i]) : session.size; | |||
else if (arg == "-p" || arg == "--probability") | |||
session.probability = (i+1 < argc) ? std::atof(argv[++i]) : session.probability; | |||
else if (arg == "--print") { | |||
session.print = true; | |||
session.print_size = (i+1 < argc) ? std::atoi(argv[++i]) : session.print_size; | |||
} | |||
else if (arg == "--make_symmetric") | |||
session.makeSymmetric = true; | |||
else if (arg == "-t" || arg == "--timing") | |||
session.timing = true; | |||
else if (arg == "-h" || arg == "--help") { | |||
std::cout << "Help message\n"; | |||
exit(0); | |||
} | |||
else { | |||
std::cout << "Error message\n"; | |||
status = false; | |||
} | |||
} | |||
return status; | |||
} | |||
int main(int argc, char* argv[]) try { | |||
Timing timer; | |||
matrix A; | |||
// try to read command line | |||
if (!get_options(argc, argv)) | |||
exit(1); | |||
// get or generate matrix | |||
if (session.inputMatrix == InputMatrix::GENERATE) { | |||
std::cout << "Initialize matrix with size: " << session.size << " and probability: " << session.probability << '\n'; | |||
timer.start(); | |||
A.size(session.size); | |||
init_ER_graph(A, session.probability); | |||
timer.stop(); | |||
if (session.timing) timer.print_dt(); | |||
} | |||
else { | |||
std::cout << "Read matrix from file\n"; | |||
timer.start(); | |||
if (session.makeSymmetric && !Mtx::is_triangular<matrix::indexType> (session.mtxFile)) | |||
throw std::runtime_error("Error: Matrix is not strictly upper or lower"); | |||
if (!Mtx::load (A, session.mtxFile)) { | |||
throw std::runtime_error("Error: fail to load matrix"); | |||
} | |||
timer.stop(); | |||
std::cout << "Matrix size: " << A.size() << " and capacity: " << A.capacity() <<'\n'; | |||
if (session.timing) timer.print_dt(); | |||
} | |||
if (session.print) { | |||
std::cout << "Array A:\n"; | |||
print_ER_graph (A); | |||
} | |||
std::cout << "count triangles\n"; | |||
timer.start(); | |||
std::cout << "There are " << triang_count(A) << " triangles\n"; | |||
timer.stop(); | |||
if (session.timing) timer.print_dt(); | |||
return 0; | |||
} | |||
catch (std::exception& e) { | |||
//we probably pollute the user's screen. Comment `cerr << ...` if you don't like it. | |||
std::cerr << e.what() << '\n'; | |||
exit(1); | |||
} |
@@ -0,0 +1,53 @@ | |||
/*! | |||
* \file utils.cpp | |||
* \brief Utilities to handle matrix files, chrono, etc... | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#include <utils.h> | |||
#include <algorithm> | |||
/*! | |||
* Initialize the matrix as Erdős-Rényi graph | |||
* \param A The matrix to initialize | |||
* \param p The probability of each edge | |||
*/ | |||
void init_ER_graph (matrix& A, double p) { | |||
std::random_device rd; | |||
std::mt19937 gen(rd()); | |||
std::binomial_distribution<> d(1, p); | |||
#if CODE_VERSION == V12 | |||
std::transform (A.begin(), A.end(), A.begin(), | |||
[&] (int x) { std::ignore =x; return d(gen); } | |||
#else | |||
A.for_each_in(0, A.size(), [&](auto i) { | |||
A.for_each_in(i+1, A.size(), [&](auto j){ | |||
matrix::dataType edge = d(gen); | |||
if (edge) { | |||
A.set(edge, i, j); | |||
A.set(edge, j, i); | |||
} | |||
}); | |||
}); | |||
#endif | |||
} | |||
/*! | |||
* Utility to print the graph to sdtout | |||
*/ | |||
void print_ER_graph (matrix& A) { | |||
matrix::indexType N = (A.size() < (matrix::indexType)session.print_size) ? A.size() : session.print_size; | |||
A.for_each_in(0, N, [&](auto i){ | |||
A.for_each_in(0, N, [&](auto j) { | |||
std::cout << A(i, j) << ' '; | |||
}); | |||
std::cout << '\n'; | |||
}); | |||
} | |||
@@ -0,0 +1,35 @@ | |||
/*! | |||
* \file v12.cpp | |||
* \brief v1 and v2 part of the exercise. | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#include <iostream> | |||
#include <random> | |||
#include <v12.h> | |||
namespace v12 { | |||
/*! | |||
* A naive triangle counting algorithm | |||
* \param A The adjacency matrix | |||
* \return The number of triangles | |||
*/ | |||
int triang_count (matrix& A) { | |||
int count =0; | |||
// We use a symmetric matrix so we iterate using the constrain i<j<k | |||
A.for_each_in(0, A.size(), [&](auto i) { | |||
A.for_each_in(i+1, A.size(), [&](auto j) { | |||
A.for_each_in(j+1, A.size(), [&](auto k){ | |||
count += (A(i,j) && A(i,k) && A(j,k)) ? 1:0; | |||
}); | |||
}); | |||
}); | |||
return count; | |||
} | |||
} |
@@ -0,0 +1,48 @@ | |||
/*! | |||
* \file v3.cpp | |||
* \brief vv3 part of the exercise. | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#include <iostream> | |||
#include <random> | |||
#include <v3.h> | |||
namespace v3 { | |||
using index_t = typename matrix::indexType; | |||
using value_t = typename matrix::dataType; | |||
/*! | |||
* A naive triangle counting algorithm | |||
* \param A The adjacency matrix | |||
* \return The number of triangles | |||
*/ | |||
std::vector<value_t> triang_v(matrix& A) { | |||
std::vector<value_t> c(A.size()); | |||
for (int i=0 ; i<A.size() ; ++i) { | |||
for (auto j = A.getCol(i); j.index() != j.end() ; ++j) // j list all the edges with i | |||
for (auto k = A.getCol(j.index()); k.index() != k.end() ; ++k) // k list all the edges with j | |||
for (auto ii = A.getCol(i) ; ii.index() <= k.index() ; ++ii) // search for i-k edge (this could be binary search) | |||
if (ii.index() == k.index()) ++c[i]; | |||
} | |||
return c; | |||
} | |||
value_t sum (std::vector<value_t>& v) { | |||
value_t s =0; | |||
for (auto& it : v) | |||
s += it; | |||
return s; | |||
} | |||
value_t triang_count (matrix& A) { | |||
auto v = triang_v(A); | |||
return sum(v); | |||
} | |||
} |
@@ -0,0 +1,43 @@ | |||
/*! | |||
* \file v4.cpp | |||
* \brief vv3 part of the exercise. | |||
* | |||
* \author | |||
* Christos Choutouridis AEM:8997 | |||
* <cchoutou@ece.auth.gr> | |||
*/ | |||
#include <iostream> | |||
#include <random> | |||
#include <v4.h> | |||
namespace v4 { | |||
using index_t = typename matrix::indexType; | |||
using value_t = typename matrix::dataType; | |||
std::vector<value_t> mmacc_v(matrix& A, matrix& B) { | |||
std::vector<value_t> c(A.size()); | |||
for (int i=0 ; i<A.size() ; ++i) { | |||
for (auto j = A.getRow(i); j.index() != j.end() ; ++j){ | |||
c[i] += A.getRow(i)*B.getCol(j.index()); | |||
} | |||
} | |||
return c; | |||
} | |||
value_t sum (std::vector<value_t>& v) { | |||
value_t s =0; | |||
for (auto& it : v) | |||
s += it; | |||
return s; | |||
} | |||
value_t triang_count (matrix& A) { | |||
auto v = mmacc_v(A, A); | |||
return (session.makeSymmetric) ? sum(v)/6 : sum(v); | |||
} | |||
} |