Added all exercises worked on.
This commit is contained in:
BIN
a1_ln2_openmp/.DS_Store
vendored
Normal file
BIN
a1_ln2_openmp/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
a2_ln2_mpi/.DS_Store
vendored
Normal file
BIN
a2_ln2_mpi/.DS_Store
vendored
Normal file
Binary file not shown.
62
a2_ln2_mpi/a2_ln2_mpi.cpp
Normal file
62
a2_ln2_mpi/a2_ln2_mpi.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <iostream>
|
||||
#include <mpi.h>
|
||||
#include "test.h"
|
||||
|
||||
void runningWithMPITest();
|
||||
void computeLn2Test();
|
||||
|
||||
// ****************************************************
|
||||
// TODO: parallelize this function with MPI
|
||||
// ****************************************************
|
||||
|
||||
double ln2(int numTerms) {
|
||||
int rank, numProcs;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &numProcs);
|
||||
|
||||
int start = 1 + (numTerms / numProcs) * rank;
|
||||
int end = (numTerms / numProcs) * (rank + 1);
|
||||
|
||||
double localSum = 0;
|
||||
|
||||
for (int i = start; i < end + 1; ++i) {
|
||||
int sign = 1;
|
||||
if (i % 2 == 0) {
|
||||
sign = -1;
|
||||
}
|
||||
localSum += sign / static_cast<double>(i);
|
||||
}
|
||||
|
||||
double globalSum;
|
||||
MPI_Allreduce(&localSum, &globalSum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
|
||||
|
||||
return globalSum;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
MPI_Init(&argc, &argv);
|
||||
runningWithMPITest();
|
||||
computeLn2Test();
|
||||
MPI_Finalize();
|
||||
}
|
||||
|
||||
// Do not change anything below this line!
|
||||
// You may use the tests below to verify that your
|
||||
// program works correctly.
|
||||
// ****************************************************
|
||||
|
||||
// Verify that the program actually runs with MPI
|
||||
void runningWithMPITest() {
|
||||
int numProc;
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &numProc);
|
||||
check(numProc, 4);
|
||||
}
|
||||
|
||||
// Verify correct functionality of ln2()
|
||||
void computeLn2Test() {
|
||||
check(ln2(4), 0.5833333333333333);
|
||||
check(ln2(8), 0.6345238095238095);
|
||||
check(ln2(12), 0.6532106782106781);
|
||||
check(ln2(100), 0.688172179310195);
|
||||
check(ln2(1e6), 0.6931466805602525);
|
||||
}
|
||||
BIN
a2_ln2_mpi/ln2
Normal file
BIN
a2_ln2_mpi/ln2
Normal file
Binary file not shown.
5
a2_ln2_mpi/makefile
Normal file
5
a2_ln2_mpi/makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
all: a2_ln2_mpi.cpp
|
||||
mpicxx a2_ln2_mpi.cpp -std=c++17 -o ln2
|
||||
|
||||
run: all
|
||||
mpirun -np 4 ./ln2
|
||||
290
a2_ln2_mpi/test.h
Normal file
290
a2_ln2_mpi/test.h
Normal file
@@ -0,0 +1,290 @@
|
||||
/** test.h, an extremly simple test framework.
|
||||
* Version 1.4
|
||||
* Copyright (C) 2022-2023 Tobias Kreilos, Offenburg University of Applied
|
||||
* Sciences
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*
|
||||
* The framework defines a function check(a,b) that can be called with
|
||||
* parameters of different types. The function asserts
|
||||
* that the two paramters are equal (within a certain, predefined range for
|
||||
* floating point numbers) and prints the result of the comparison on the
|
||||
* command line. Additionally a summary of all tests is printed at the end of
|
||||
* the program.
|
||||
* Additionally there is TEST macro, which you can place outside main to group
|
||||
* tests together. Code in the macro is automatically executed at the beginning
|
||||
* of the program.
|
||||
* The file also defines a class InstanceCount, that can be used to
|
||||
* count how many instances of an object are still alive at the end of a
|
||||
* program. To use it, derive your class from InstanceCount<ClassName> and the
|
||||
* message is automatically printed at the end of the program.
|
||||
*/
|
||||
|
||||
#ifndef VERY_SIMPLE_TEST_H
|
||||
#define VERY_SIMPLE_TEST_H
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
|
||||
/** Simple macro to execute the code that follows the macro (without call from
|
||||
* main)
|
||||
*
|
||||
* Define a class, that is directly instantiated
|
||||
* and contains the test code in the constructor.
|
||||
*
|
||||
* Usage:
|
||||
* TEST(MyTest)
|
||||
* {
|
||||
* // test code
|
||||
* }
|
||||
*/
|
||||
#define TEST(name) \
|
||||
struct _TestClass##name { \
|
||||
_TestClass##name(); \
|
||||
} _TestClass##name##Instance; \
|
||||
_TestClass##name::_TestClass##name()
|
||||
|
||||
// Use a namespace to hide implementation details
|
||||
namespace Test::Detail {
|
||||
/**
|
||||
* Make it possible to print the underlying value of class enums with ostream
|
||||
*
|
||||
* The expression typename std::enable_if<std::is_enum<T>::value,
|
||||
* std::ostream>::type decays to ostream if the type T is an enum. Otherwise,
|
||||
* the function is not generated.
|
||||
*/
|
||||
template <typename T>
|
||||
std::ostream& operator<<(
|
||||
typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream,
|
||||
const T& e) {
|
||||
return stream << static_cast<typename std::underlying_type<T>::type>(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert anything to a string.
|
||||
*/
|
||||
template <typename T>
|
||||
std::string toString(const T& t) {
|
||||
std::ostringstream ss;
|
||||
ss << t;
|
||||
return "\"" + ss.str() + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bools to string "true" or "false" instead of 0 and 1
|
||||
*/
|
||||
template <>
|
||||
inline std::string toString<bool>(const bool& b) {
|
||||
return b ? "\"true\"" : "\"false\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison function for different types
|
||||
*/
|
||||
template <typename T>
|
||||
bool isEqual(const T& t1, const T& t2) {
|
||||
return t1 == t2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Double values are equal if they differ no more than 1e-12
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<double>(const double& expectedValue,
|
||||
const double& actualValue) {
|
||||
const double epsilon = 1e-12;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Float values are equal if they differ no more than 1e-6
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<float>(const float& expectedValue,
|
||||
const float& actualValue) {
|
||||
const double epsilon = 1e-6;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class realizes some basics of the test framework.
|
||||
* Test summary is printed in the destructor.
|
||||
* Apart from that, the class implements counting of total and failed tests,
|
||||
* comparison of floating point numbers within sensible boundaries and prints
|
||||
* the result of each test on the command line.
|
||||
*/
|
||||
class Test {
|
||||
public:
|
||||
/**
|
||||
* Test class is a Singleton
|
||||
*/
|
||||
static Test& instance() {
|
||||
static Test test;
|
||||
return test;
|
||||
}
|
||||
|
||||
/**
|
||||
* the main entry point for tests. Test two values for equality and output the
|
||||
* result.
|
||||
*/
|
||||
template <typename T>
|
||||
bool check(const T& expectedValue, const T& actualValue) {
|
||||
bool testResult = isEqual(expectedValue, actualValue);
|
||||
if (testResult == true) {
|
||||
registerPassingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Test successful! Expected value == actual value (="
|
||||
<< toString(expectedValue) << ")" << std::endl;
|
||||
} else {
|
||||
registerFailingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Error in test: expected value " << toString(expectedValue)
|
||||
<< ", but actual value was " << toString(actualValue)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
return testResult;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* On destruction, print a summary of all tests.
|
||||
*/
|
||||
~Test() {
|
||||
std::cout << "\n--------------------------------------" << std::endl;
|
||||
std::cout << "Test summary:" << std::endl;
|
||||
std::cout << "Executed tests: " << numTests_ << std::endl;
|
||||
std::cout << "Failed tests: " << numFailedTests_ << std::endl;
|
||||
}
|
||||
|
||||
void registerPassingTest() { numTests_++; }
|
||||
|
||||
void registerFailingTest() {
|
||||
numTests_++;
|
||||
numFailedTests_++;
|
||||
}
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numTests_ = 0;
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numFailedTests_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class InstanceCounterHelper {
|
||||
public:
|
||||
~InstanceCounterHelper() {
|
||||
std::cout << "The remaining number of objects of type " << typeid(T).name()
|
||||
<< " at the end of the program is " << count;
|
||||
if (count > 0)
|
||||
std::cout << " (NOT zero!)";
|
||||
std::cout << "\nThe total number of objects created was " << total
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
count++;
|
||||
total++;
|
||||
}
|
||||
|
||||
void decrement() { count--; }
|
||||
|
||||
private:
|
||||
std::atomic<int> count = 0;
|
||||
std::atomic<int> total = 0;
|
||||
};
|
||||
|
||||
} // namespace Test::Detail
|
||||
|
||||
/**
|
||||
* Count the instances of a class T.
|
||||
* Result gets printed automatically at the end of the program.
|
||||
* To use it, inherit T from InstanceCounter<T>, e.g.
|
||||
* class MyClass : InstanceCounter<MyClass>
|
||||
*/
|
||||
template <typename T>
|
||||
class InstanceCounter {
|
||||
public:
|
||||
InstanceCounter() { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&) { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&&) { counter().increment(); }
|
||||
|
||||
virtual ~InstanceCounter() { counter().decrement(); }
|
||||
|
||||
Test::Detail::InstanceCounterHelper<T>& counter() {
|
||||
static Test::Detail::InstanceCounterHelper<T> c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the expected value is equal to the actual value.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
void check(const T1& actualValue, const T2& expectedValue) {
|
||||
const T1& expectedValueCasted{
|
||||
expectedValue}; // allows conversion in general, but avoids narrowing
|
||||
// conversion
|
||||
Test::Detail::Test::instance().check(expectedValueCasted, actualValue);
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const int& actualValue, const double& expectedValue) {
|
||||
Test::Detail::Test::instance().check(expectedValue,
|
||||
static_cast<double>(actualValue));
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const double& actualValue, const int& expectedValue) {
|
||||
Test::Detail::Test::instance().check(static_cast<double>(expectedValue),
|
||||
actualValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entered value is true.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
inline void check(bool a) {
|
||||
Test::Detail::Test::instance().check(true, a);
|
||||
}
|
||||
|
||||
#endif // VERY_SIMPLE_TEST_H
|
||||
|
||||
/**
|
||||
* V1.0: Creation of franework
|
||||
* V1.1: make check(bool) inline, automatically convert expected value type to
|
||||
* actual value type
|
||||
* V1.2: added possibilty to count constructions and destructions of some type
|
||||
* V1.3: tweaks on check for int and double types
|
||||
* V1.4: Adding thread safety in OpenMP programs (not general thread safety, as
|
||||
* OpenMP and std::thread might not play along)
|
||||
*/
|
||||
69
a3_ln2_hybrid/a3_ln2_hybrid.cpp
Normal file
69
a3_ln2_hybrid/a3_ln2_hybrid.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
#include <iostream>
|
||||
#include <mpi.h>
|
||||
#include <omp.h>
|
||||
#include "test.h"
|
||||
|
||||
void runningHybridTest();
|
||||
void computeLn2Test();
|
||||
|
||||
// ****************************************************
|
||||
// TODO: parallelize this function with MPI and OpenMP
|
||||
// ****************************************************
|
||||
|
||||
double ln2(int numTerms) {
|
||||
int rank, numProcs;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &numProcs);
|
||||
|
||||
int start = 1 + (numTerms / numProcs) * rank;
|
||||
int end = (numTerms / numProcs) * (rank + 1);
|
||||
|
||||
double localSum = 0;
|
||||
|
||||
#pragma omp parallel for reduction(+ : localSum)
|
||||
for (int i = start; i < end + 1; ++i) {
|
||||
int sign = 1;
|
||||
if (i % 2 == 0) {
|
||||
sign = -1;
|
||||
}
|
||||
localSum += sign / static_cast<double>(i);
|
||||
}
|
||||
|
||||
double globalSum;
|
||||
MPI_Allreduce(&localSum, &globalSum, 1, MPI_DOUBLE, MPI_SUM, MPI_COMM_WORLD);
|
||||
|
||||
return globalSum;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
MPI_Init(&argc, &argv);
|
||||
runningHybridTest();
|
||||
computeLn2Test();
|
||||
MPI_Finalize();
|
||||
}
|
||||
|
||||
// Do not change anything below this line!
|
||||
// You may use the tests below to verify that your
|
||||
// program works correctly.
|
||||
// ****************************************************
|
||||
|
||||
// Verify that the program actually runs with MPI and OpenMP
|
||||
void runningHybridTest() {
|
||||
int numProc;
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &numProc);
|
||||
#pragma omp parallel
|
||||
{
|
||||
int numThreads = omp_get_num_threads();
|
||||
check(numProc, 2);
|
||||
check(numThreads, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Verify correct functionality of ln2()
|
||||
void computeLn2Test() {
|
||||
check(ln2(4), 0.5833333333333333);
|
||||
check(ln2(8), 0.6345238095238095);
|
||||
check(ln2(12), 0.6532106782106781);
|
||||
check(ln2(100), 0.688172179310195);
|
||||
check(ln2(1e6), 0.6931466805602525);
|
||||
}
|
||||
BIN
a3_ln2_hybrid/ln2
Normal file
BIN
a3_ln2_hybrid/ln2
Normal file
Binary file not shown.
5
a3_ln2_hybrid/makefile
Normal file
5
a3_ln2_hybrid/makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
all: a3_ln2_hybrid.cpp
|
||||
mpicxx a3_ln2_hybrid.cpp -fopenmp -o ln2
|
||||
|
||||
run: all
|
||||
mpirun -np 2 -x OMP_NUM_THREADS=2 ./ln2
|
||||
290
a3_ln2_hybrid/test.h
Normal file
290
a3_ln2_hybrid/test.h
Normal file
@@ -0,0 +1,290 @@
|
||||
/** test.h, an extremly simple test framework.
|
||||
* Version 1.4
|
||||
* Copyright (C) 2022-2023 Tobias Kreilos, Offenburg University of Applied
|
||||
* Sciences
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*
|
||||
* The framework defines a function check(a,b) that can be called with
|
||||
* parameters of different types. The function asserts
|
||||
* that the two paramters are equal (within a certain, predefined range for
|
||||
* floating point numbers) and prints the result of the comparison on the
|
||||
* command line. Additionally a summary of all tests is printed at the end of
|
||||
* the program.
|
||||
* Additionally there is TEST macro, which you can place outside main to group
|
||||
* tests together. Code in the macro is automatically executed at the beginning
|
||||
* of the program.
|
||||
* The file also defines a class InstanceCount, that can be used to
|
||||
* count how many instances of an object are still alive at the end of a
|
||||
* program. To use it, derive your class from InstanceCount<ClassName> and the
|
||||
* message is automatically printed at the end of the program.
|
||||
*/
|
||||
|
||||
#ifndef VERY_SIMPLE_TEST_H
|
||||
#define VERY_SIMPLE_TEST_H
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
|
||||
/** Simple macro to execute the code that follows the macro (without call from
|
||||
* main)
|
||||
*
|
||||
* Define a class, that is directly instantiated
|
||||
* and contains the test code in the constructor.
|
||||
*
|
||||
* Usage:
|
||||
* TEST(MyTest)
|
||||
* {
|
||||
* // test code
|
||||
* }
|
||||
*/
|
||||
#define TEST(name) \
|
||||
struct _TestClass##name { \
|
||||
_TestClass##name(); \
|
||||
} _TestClass##name##Instance; \
|
||||
_TestClass##name::_TestClass##name()
|
||||
|
||||
// Use a namespace to hide implementation details
|
||||
namespace Test::Detail {
|
||||
/**
|
||||
* Make it possible to print the underlying value of class enums with ostream
|
||||
*
|
||||
* The expression typename std::enable_if<std::is_enum<T>::value,
|
||||
* std::ostream>::type decays to ostream if the type T is an enum. Otherwise,
|
||||
* the function is not generated.
|
||||
*/
|
||||
template <typename T>
|
||||
std::ostream& operator<<(
|
||||
typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream,
|
||||
const T& e) {
|
||||
return stream << static_cast<typename std::underlying_type<T>::type>(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert anything to a string.
|
||||
*/
|
||||
template <typename T>
|
||||
std::string toString(const T& t) {
|
||||
std::ostringstream ss;
|
||||
ss << t;
|
||||
return "\"" + ss.str() + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bools to string "true" or "false" instead of 0 and 1
|
||||
*/
|
||||
template <>
|
||||
inline std::string toString<bool>(const bool& b) {
|
||||
return b ? "\"true\"" : "\"false\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison function for different types
|
||||
*/
|
||||
template <typename T>
|
||||
bool isEqual(const T& t1, const T& t2) {
|
||||
return t1 == t2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Double values are equal if they differ no more than 1e-12
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<double>(const double& expectedValue,
|
||||
const double& actualValue) {
|
||||
const double epsilon = 1e-12;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Float values are equal if they differ no more than 1e-6
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<float>(const float& expectedValue,
|
||||
const float& actualValue) {
|
||||
const double epsilon = 1e-6;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class realizes some basics of the test framework.
|
||||
* Test summary is printed in the destructor.
|
||||
* Apart from that, the class implements counting of total and failed tests,
|
||||
* comparison of floating point numbers within sensible boundaries and prints
|
||||
* the result of each test on the command line.
|
||||
*/
|
||||
class Test {
|
||||
public:
|
||||
/**
|
||||
* Test class is a Singleton
|
||||
*/
|
||||
static Test& instance() {
|
||||
static Test test;
|
||||
return test;
|
||||
}
|
||||
|
||||
/**
|
||||
* the main entry point for tests. Test two values for equality and output the
|
||||
* result.
|
||||
*/
|
||||
template <typename T>
|
||||
bool check(const T& expectedValue, const T& actualValue) {
|
||||
bool testResult = isEqual(expectedValue, actualValue);
|
||||
if (testResult == true) {
|
||||
registerPassingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Test successful! Expected value == actual value (="
|
||||
<< toString(expectedValue) << ")" << std::endl;
|
||||
} else {
|
||||
registerFailingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Error in test: expected value " << toString(expectedValue)
|
||||
<< ", but actual value was " << toString(actualValue)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
return testResult;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* On destruction, print a summary of all tests.
|
||||
*/
|
||||
~Test() {
|
||||
std::cout << "\n--------------------------------------" << std::endl;
|
||||
std::cout << "Test summary:" << std::endl;
|
||||
std::cout << "Executed tests: " << numTests_ << std::endl;
|
||||
std::cout << "Failed tests: " << numFailedTests_ << std::endl;
|
||||
}
|
||||
|
||||
void registerPassingTest() { numTests_++; }
|
||||
|
||||
void registerFailingTest() {
|
||||
numTests_++;
|
||||
numFailedTests_++;
|
||||
}
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numTests_ = 0;
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numFailedTests_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class InstanceCounterHelper {
|
||||
public:
|
||||
~InstanceCounterHelper() {
|
||||
std::cout << "The remaining number of objects of type " << typeid(T).name()
|
||||
<< " at the end of the program is " << count;
|
||||
if (count > 0)
|
||||
std::cout << " (NOT zero!)";
|
||||
std::cout << "\nThe total number of objects created was " << total
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
count++;
|
||||
total++;
|
||||
}
|
||||
|
||||
void decrement() { count--; }
|
||||
|
||||
private:
|
||||
std::atomic<int> count = 0;
|
||||
std::atomic<int> total = 0;
|
||||
};
|
||||
|
||||
} // namespace Test::Detail
|
||||
|
||||
/**
|
||||
* Count the instances of a class T.
|
||||
* Result gets printed automatically at the end of the program.
|
||||
* To use it, inherit T from InstanceCounter<T>, e.g.
|
||||
* class MyClass : InstanceCounter<MyClass>
|
||||
*/
|
||||
template <typename T>
|
||||
class InstanceCounter {
|
||||
public:
|
||||
InstanceCounter() { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&) { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&&) { counter().increment(); }
|
||||
|
||||
virtual ~InstanceCounter() { counter().decrement(); }
|
||||
|
||||
Test::Detail::InstanceCounterHelper<T>& counter() {
|
||||
static Test::Detail::InstanceCounterHelper<T> c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the expected value is equal to the actual value.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
void check(const T1& actualValue, const T2& expectedValue) {
|
||||
const T1& expectedValueCasted{
|
||||
expectedValue}; // allows conversion in general, but avoids narrowing
|
||||
// conversion
|
||||
Test::Detail::Test::instance().check(expectedValueCasted, actualValue);
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const int& actualValue, const double& expectedValue) {
|
||||
Test::Detail::Test::instance().check(expectedValue,
|
||||
static_cast<double>(actualValue));
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const double& actualValue, const int& expectedValue) {
|
||||
Test::Detail::Test::instance().check(static_cast<double>(expectedValue),
|
||||
actualValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entered value is true.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
inline void check(bool a) {
|
||||
Test::Detail::Test::instance().check(true, a);
|
||||
}
|
||||
|
||||
#endif // VERY_SIMPLE_TEST_H
|
||||
|
||||
/**
|
||||
* V1.0: Creation of franework
|
||||
* V1.1: make check(bool) inline, automatically convert expected value type to
|
||||
* actual value type
|
||||
* V1.2: added possibilty to count constructions and destructions of some type
|
||||
* V1.3: tweaks on check for int and double types
|
||||
* V1.4: Adding thread safety in OpenMP programs (not general thread safety, as
|
||||
* OpenMP and std::thread might not play along)
|
||||
*/
|
||||
BIN
a4_minimum_search/.DS_Store
vendored
Normal file
BIN
a4_minimum_search/.DS_Store
vendored
Normal file
Binary file not shown.
73
a4_minimum_search/a4_min.cpp
Normal file
73
a4_minimum_search/a4_min.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <omp.h>
|
||||
#include "test.h"
|
||||
|
||||
void findMinimumTest();
|
||||
|
||||
// ****************************************************
|
||||
// TODO: find and fix the data race in this function
|
||||
// ****************************************************
|
||||
double findMinimum(std::function<double(double)> f,
|
||||
double xMin,
|
||||
double xMax,
|
||||
int n) {
|
||||
|
||||
double globalMin = f(xMax);
|
||||
double localMin = f(xMax);
|
||||
|
||||
#pragma omp parallel firstprivate(localMin) shared(globalMin)
|
||||
{
|
||||
double range = xMax - xMin;
|
||||
double step = range / n;
|
||||
|
||||
#pragma omp for schedule(static)
|
||||
for (int i = 0; i < n; ++i) {
|
||||
double x = xMin + step * i;
|
||||
double fx = f(x);
|
||||
if (fx < localMin) {
|
||||
localMin = fx;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma omp critical
|
||||
if (localMin < globalMin) {
|
||||
globalMin = localMin;
|
||||
}
|
||||
}
|
||||
|
||||
return globalMin;
|
||||
}
|
||||
|
||||
int main() {
|
||||
findMinimumTest();
|
||||
}
|
||||
|
||||
// Do not change anything below this line!
|
||||
// You may use the tests below to verify that your
|
||||
// program works correctly.
|
||||
// ****************************************************
|
||||
|
||||
double f0(double x) {
|
||||
return (x - 2) * (x - 2);
|
||||
}
|
||||
|
||||
double f1(double x) {
|
||||
return sin(x) + 1. / 5. * x * x * x * x - 3. * (x - 3) * (x - 3);
|
||||
};
|
||||
|
||||
double f2(double x) {
|
||||
return sin(x) + x * x;
|
||||
}
|
||||
|
||||
double f3(double x) {
|
||||
return x*x*x*x - x*x;
|
||||
}
|
||||
|
||||
void findMinimumTest() {
|
||||
check(findMinimum(f0, -5, 5, 1e6), 0);
|
||||
check(findMinimum(f1, -5, 5, 1e6), -96.68321722021884);
|
||||
check(findMinimum(f2, -5, 5, 1e6), -0.2324655751423368);
|
||||
check(findMinimum(f3, -2, 2, 1e7), -0.25);
|
||||
}
|
||||
5
a4_minimum_search/makefile
Normal file
5
a4_minimum_search/makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
build: a4_min.cpp
|
||||
g++ -std=c++17 a4_min.cpp -fopenmp -o min
|
||||
|
||||
run: build
|
||||
./min
|
||||
BIN
a4_minimum_search/min
Normal file
BIN
a4_minimum_search/min
Normal file
Binary file not shown.
290
a4_minimum_search/test.h
Normal file
290
a4_minimum_search/test.h
Normal file
@@ -0,0 +1,290 @@
|
||||
/** test.h, an extremly simple test framework.
|
||||
* Version 1.4
|
||||
* Copyright (C) 2022-2023 Tobias Kreilos, Offenburg University of Applied
|
||||
* Sciences
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*
|
||||
* The framework defines a function check(a,b) that can be called with
|
||||
* parameters of different types. The function asserts
|
||||
* that the two paramters are equal (within a certain, predefined range for
|
||||
* floating point numbers) and prints the result of the comparison on the
|
||||
* command line. Additionally a summary of all tests is printed at the end of
|
||||
* the program.
|
||||
* Additionally there is TEST macro, which you can place outside main to group
|
||||
* tests together. Code in the macro is automatically executed at the beginning
|
||||
* of the program.
|
||||
* The file also defines a class InstanceCount, that can be used to
|
||||
* count how many instances of an object are still alive at the end of a
|
||||
* program. To use it, derive your class from InstanceCount<ClassName> and the
|
||||
* message is automatically printed at the end of the program.
|
||||
*/
|
||||
|
||||
#ifndef VERY_SIMPLE_TEST_H
|
||||
#define VERY_SIMPLE_TEST_H
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
|
||||
/** Simple macro to execute the code that follows the macro (without call from
|
||||
* main)
|
||||
*
|
||||
* Define a class, that is directly instantiated
|
||||
* and contains the test code in the constructor.
|
||||
*
|
||||
* Usage:
|
||||
* TEST(MyTest)
|
||||
* {
|
||||
* // test code
|
||||
* }
|
||||
*/
|
||||
#define TEST(name) \
|
||||
struct _TestClass##name { \
|
||||
_TestClass##name(); \
|
||||
} _TestClass##name##Instance; \
|
||||
_TestClass##name::_TestClass##name()
|
||||
|
||||
// Use a namespace to hide implementation details
|
||||
namespace Test::Detail {
|
||||
/**
|
||||
* Make it possible to print the underlying value of class enums with ostream
|
||||
*
|
||||
* The expression typename std::enable_if<std::is_enum<T>::value,
|
||||
* std::ostream>::type decays to ostream if the type T is an enum. Otherwise,
|
||||
* the function is not generated.
|
||||
*/
|
||||
template <typename T>
|
||||
std::ostream& operator<<(
|
||||
typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream,
|
||||
const T& e) {
|
||||
return stream << static_cast<typename std::underlying_type<T>::type>(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert anything to a string.
|
||||
*/
|
||||
template <typename T>
|
||||
std::string toString(const T& t) {
|
||||
std::ostringstream ss;
|
||||
ss << t;
|
||||
return "\"" + ss.str() + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bools to string "true" or "false" instead of 0 and 1
|
||||
*/
|
||||
template <>
|
||||
inline std::string toString<bool>(const bool& b) {
|
||||
return b ? "\"true\"" : "\"false\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison function for different types
|
||||
*/
|
||||
template <typename T>
|
||||
bool isEqual(const T& t1, const T& t2) {
|
||||
return t1 == t2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Double values are equal if they differ no more than 1e-12
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<double>(const double& expectedValue,
|
||||
const double& actualValue) {
|
||||
const double epsilon = 1e-12;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Float values are equal if they differ no more than 1e-6
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<float>(const float& expectedValue,
|
||||
const float& actualValue) {
|
||||
const double epsilon = 1e-6;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class realizes some basics of the test framework.
|
||||
* Test summary is printed in the destructor.
|
||||
* Apart from that, the class implements counting of total and failed tests,
|
||||
* comparison of floating point numbers within sensible boundaries and prints
|
||||
* the result of each test on the command line.
|
||||
*/
|
||||
class Test {
|
||||
public:
|
||||
/**
|
||||
* Test class is a Singleton
|
||||
*/
|
||||
static Test& instance() {
|
||||
static Test test;
|
||||
return test;
|
||||
}
|
||||
|
||||
/**
|
||||
* the main entry point for tests. Test two values for equality and output the
|
||||
* result.
|
||||
*/
|
||||
template <typename T>
|
||||
bool check(const T& expectedValue, const T& actualValue) {
|
||||
bool testResult = isEqual(expectedValue, actualValue);
|
||||
if (testResult == true) {
|
||||
registerPassingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Test successful! Expected value == actual value (="
|
||||
<< toString(expectedValue) << ")" << std::endl;
|
||||
} else {
|
||||
registerFailingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Error in test: expected value " << toString(expectedValue)
|
||||
<< ", but actual value was " << toString(actualValue)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
return testResult;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* On destruction, print a summary of all tests.
|
||||
*/
|
||||
~Test() {
|
||||
std::cout << "\n--------------------------------------" << std::endl;
|
||||
std::cout << "Test summary:" << std::endl;
|
||||
std::cout << "Executed tests: " << numTests_ << std::endl;
|
||||
std::cout << "Failed tests: " << numFailedTests_ << std::endl;
|
||||
}
|
||||
|
||||
void registerPassingTest() { numTests_++; }
|
||||
|
||||
void registerFailingTest() {
|
||||
numTests_++;
|
||||
numFailedTests_++;
|
||||
}
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numTests_ = 0;
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numFailedTests_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class InstanceCounterHelper {
|
||||
public:
|
||||
~InstanceCounterHelper() {
|
||||
std::cout << "The remaining number of objects of type " << typeid(T).name()
|
||||
<< " at the end of the program is " << count;
|
||||
if (count > 0)
|
||||
std::cout << " (NOT zero!)";
|
||||
std::cout << "\nThe total number of objects created was " << total
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
count++;
|
||||
total++;
|
||||
}
|
||||
|
||||
void decrement() { count--; }
|
||||
|
||||
private:
|
||||
std::atomic<int> count = 0;
|
||||
std::atomic<int> total = 0;
|
||||
};
|
||||
|
||||
} // namespace Test::Detail
|
||||
|
||||
/**
|
||||
* Count the instances of a class T.
|
||||
* Result gets printed automatically at the end of the program.
|
||||
* To use it, inherit T from InstanceCounter<T>, e.g.
|
||||
* class MyClass : InstanceCounter<MyClass>
|
||||
*/
|
||||
template <typename T>
|
||||
class InstanceCounter {
|
||||
public:
|
||||
InstanceCounter() { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&) { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&&) { counter().increment(); }
|
||||
|
||||
virtual ~InstanceCounter() { counter().decrement(); }
|
||||
|
||||
Test::Detail::InstanceCounterHelper<T>& counter() {
|
||||
static Test::Detail::InstanceCounterHelper<T> c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the expected value is equal to the actual value.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
void check(const T1& actualValue, const T2& expectedValue) {
|
||||
const T1& expectedValueCasted{
|
||||
expectedValue}; // allows conversion in general, but avoids narrowing
|
||||
// conversion
|
||||
Test::Detail::Test::instance().check(expectedValueCasted, actualValue);
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const int& actualValue, const double& expectedValue) {
|
||||
Test::Detail::Test::instance().check(expectedValue,
|
||||
static_cast<double>(actualValue));
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const double& actualValue, const int& expectedValue) {
|
||||
Test::Detail::Test::instance().check(static_cast<double>(expectedValue),
|
||||
actualValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entered value is true.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
inline void check(bool a) {
|
||||
Test::Detail::Test::instance().check(true, a);
|
||||
}
|
||||
|
||||
#endif // VERY_SIMPLE_TEST_H
|
||||
|
||||
/**
|
||||
* V1.0: Creation of franework
|
||||
* V1.1: make check(bool) inline, automatically convert expected value type to
|
||||
* actual value type
|
||||
* V1.2: added possibilty to count constructions and destructions of some type
|
||||
* V1.3: tweaks on check for int and double types
|
||||
* V1.4: Adding thread safety in OpenMP programs (not general thread safety, as
|
||||
* OpenMP and std::thread might not play along)
|
||||
*/
|
||||
BIN
a5_mpi_deadlock/.DS_Store
vendored
Normal file
BIN
a5_mpi_deadlock/.DS_Store
vendored
Normal file
Binary file not shown.
49
a5_mpi_deadlock/a5_deadlock.cpp
Normal file
49
a5_mpi_deadlock/a5_deadlock.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include <mpi.h>
|
||||
#include <iostream>
|
||||
#include "test.h"
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
MPI_Init(&argc, &argv);
|
||||
|
||||
int rank, numProc;
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
MPI_Comm_size(MPI_COMM_WORLD, &numProc);
|
||||
|
||||
int sendBuf;
|
||||
int recvBuf;
|
||||
int val = 0;
|
||||
MPI_Status status;
|
||||
|
||||
// send and receive message on rank 0
|
||||
if (rank == 0) {
|
||||
sendBuf = 123;
|
||||
MPI_Recv(&recvBuf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD, &status);
|
||||
std::cout << "Rank 0 received " << recvBuf << "\n";
|
||||
|
||||
MPI_Send(&sendBuf, 1, MPI_INT, 1, 0, MPI_COMM_WORLD);
|
||||
std::cout << "Rank 0 sent " << sendBuf << " to rank 1\n";
|
||||
|
||||
check(recvBuf, 456);
|
||||
}
|
||||
|
||||
// send and receive message on rank 1
|
||||
if (rank == 1) {
|
||||
sendBuf = 456;
|
||||
MPI_Send(&sendBuf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD);
|
||||
std::cout << "Rank 1 sent " << sendBuf << " to rank 0\n";
|
||||
|
||||
MPI_Recv(&recvBuf, 1, MPI_INT, 0, 0, MPI_COMM_WORLD, &status);
|
||||
std::cout << "Rank 1 received " << recvBuf << "\n";
|
||||
|
||||
check(recvBuf, 123);
|
||||
}
|
||||
|
||||
// Broadcast value from rank 0 to all others
|
||||
if (rank == 0) val = 789;
|
||||
MPI_Bcast(&val, 1, MPI_DOUBLE, 0, MPI_COMM_WORLD);
|
||||
|
||||
// Verify that broadcast message arrived correctly everywhere
|
||||
check(val, 789);
|
||||
|
||||
MPI_Finalize();
|
||||
}
|
||||
BIN
a5_mpi_deadlock/deadlock
Normal file
BIN
a5_mpi_deadlock/deadlock
Normal file
Binary file not shown.
5
a5_mpi_deadlock/makefile
Normal file
5
a5_mpi_deadlock/makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
all: a5_deadlock.cpp
|
||||
mpicxx a5_deadlock.cpp -std=c++17 -o deadlock
|
||||
|
||||
run: all
|
||||
mpirun -np 3 ./deadlock
|
||||
290
a5_mpi_deadlock/test.h
Normal file
290
a5_mpi_deadlock/test.h
Normal file
@@ -0,0 +1,290 @@
|
||||
/** test.h, an extremly simple test framework.
|
||||
* Version 1.4
|
||||
* Copyright (C) 2022-2023 Tobias Kreilos, Offenburg University of Applied
|
||||
* Sciences
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*
|
||||
* The framework defines a function check(a,b) that can be called with
|
||||
* parameters of different types. The function asserts
|
||||
* that the two paramters are equal (within a certain, predefined range for
|
||||
* floating point numbers) and prints the result of the comparison on the
|
||||
* command line. Additionally a summary of all tests is printed at the end of
|
||||
* the program.
|
||||
* Additionally there is TEST macro, which you can place outside main to group
|
||||
* tests together. Code in the macro is automatically executed at the beginning
|
||||
* of the program.
|
||||
* The file also defines a class InstanceCount, that can be used to
|
||||
* count how many instances of an object are still alive at the end of a
|
||||
* program. To use it, derive your class from InstanceCount<ClassName> and the
|
||||
* message is automatically printed at the end of the program.
|
||||
*/
|
||||
|
||||
#ifndef VERY_SIMPLE_TEST_H
|
||||
#define VERY_SIMPLE_TEST_H
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
|
||||
/** Simple macro to execute the code that follows the macro (without call from
|
||||
* main)
|
||||
*
|
||||
* Define a class, that is directly instantiated
|
||||
* and contains the test code in the constructor.
|
||||
*
|
||||
* Usage:
|
||||
* TEST(MyTest)
|
||||
* {
|
||||
* // test code
|
||||
* }
|
||||
*/
|
||||
#define TEST(name) \
|
||||
struct _TestClass##name { \
|
||||
_TestClass##name(); \
|
||||
} _TestClass##name##Instance; \
|
||||
_TestClass##name::_TestClass##name()
|
||||
|
||||
// Use a namespace to hide implementation details
|
||||
namespace Test::Detail {
|
||||
/**
|
||||
* Make it possible to print the underlying value of class enums with ostream
|
||||
*
|
||||
* The expression typename std::enable_if<std::is_enum<T>::value,
|
||||
* std::ostream>::type decays to ostream if the type T is an enum. Otherwise,
|
||||
* the function is not generated.
|
||||
*/
|
||||
template <typename T>
|
||||
std::ostream& operator<<(
|
||||
typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream,
|
||||
const T& e) {
|
||||
return stream << static_cast<typename std::underlying_type<T>::type>(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert anything to a string.
|
||||
*/
|
||||
template <typename T>
|
||||
std::string toString(const T& t) {
|
||||
std::ostringstream ss;
|
||||
ss << t;
|
||||
return "\"" + ss.str() + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bools to string "true" or "false" instead of 0 and 1
|
||||
*/
|
||||
template <>
|
||||
inline std::string toString<bool>(const bool& b) {
|
||||
return b ? "\"true\"" : "\"false\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison function for different types
|
||||
*/
|
||||
template <typename T>
|
||||
bool isEqual(const T& t1, const T& t2) {
|
||||
return t1 == t2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Double values are equal if they differ no more than 1e-12
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<double>(const double& expectedValue,
|
||||
const double& actualValue) {
|
||||
const double epsilon = 1e-12;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Float values are equal if they differ no more than 1e-6
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<float>(const float& expectedValue,
|
||||
const float& actualValue) {
|
||||
const double epsilon = 1e-6;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class realizes some basics of the test framework.
|
||||
* Test summary is printed in the destructor.
|
||||
* Apart from that, the class implements counting of total and failed tests,
|
||||
* comparison of floating point numbers within sensible boundaries and prints
|
||||
* the result of each test on the command line.
|
||||
*/
|
||||
class Test {
|
||||
public:
|
||||
/**
|
||||
* Test class is a Singleton
|
||||
*/
|
||||
static Test& instance() {
|
||||
static Test test;
|
||||
return test;
|
||||
}
|
||||
|
||||
/**
|
||||
* the main entry point for tests. Test two values for equality and output the
|
||||
* result.
|
||||
*/
|
||||
template <typename T>
|
||||
bool check(const T& expectedValue, const T& actualValue) {
|
||||
bool testResult = isEqual(expectedValue, actualValue);
|
||||
if (testResult == true) {
|
||||
registerPassingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Test successful! Expected value == actual value (="
|
||||
<< toString(expectedValue) << ")" << std::endl;
|
||||
} else {
|
||||
registerFailingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Error in test: expected value " << toString(expectedValue)
|
||||
<< ", but actual value was " << toString(actualValue)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
return testResult;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* On destruction, print a summary of all tests.
|
||||
*/
|
||||
~Test() {
|
||||
std::cout << "\n--------------------------------------" << std::endl;
|
||||
std::cout << "Test summary:" << std::endl;
|
||||
std::cout << "Executed tests: " << numTests_ << std::endl;
|
||||
std::cout << "Failed tests: " << numFailedTests_ << std::endl;
|
||||
}
|
||||
|
||||
void registerPassingTest() { numTests_++; }
|
||||
|
||||
void registerFailingTest() {
|
||||
numTests_++;
|
||||
numFailedTests_++;
|
||||
}
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numTests_ = 0;
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numFailedTests_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class InstanceCounterHelper {
|
||||
public:
|
||||
~InstanceCounterHelper() {
|
||||
std::cout << "The remaining number of objects of type " << typeid(T).name()
|
||||
<< " at the end of the program is " << count;
|
||||
if (count > 0)
|
||||
std::cout << " (NOT zero!)";
|
||||
std::cout << "\nThe total number of objects created was " << total
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
count++;
|
||||
total++;
|
||||
}
|
||||
|
||||
void decrement() { count--; }
|
||||
|
||||
private:
|
||||
std::atomic<int> count = 0;
|
||||
std::atomic<int> total = 0;
|
||||
};
|
||||
|
||||
} // namespace Test::Detail
|
||||
|
||||
/**
|
||||
* Count the instances of a class T.
|
||||
* Result gets printed automatically at the end of the program.
|
||||
* To use it, inherit T from InstanceCounter<T>, e.g.
|
||||
* class MyClass : InstanceCounter<MyClass>
|
||||
*/
|
||||
template <typename T>
|
||||
class InstanceCounter {
|
||||
public:
|
||||
InstanceCounter() { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&) { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&&) { counter().increment(); }
|
||||
|
||||
virtual ~InstanceCounter() { counter().decrement(); }
|
||||
|
||||
Test::Detail::InstanceCounterHelper<T>& counter() {
|
||||
static Test::Detail::InstanceCounterHelper<T> c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the expected value is equal to the actual value.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
void check(const T1& actualValue, const T2& expectedValue) {
|
||||
const T1& expectedValueCasted{
|
||||
expectedValue}; // allows conversion in general, but avoids narrowing
|
||||
// conversion
|
||||
Test::Detail::Test::instance().check(expectedValueCasted, actualValue);
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const int& actualValue, const double& expectedValue) {
|
||||
Test::Detail::Test::instance().check(expectedValue,
|
||||
static_cast<double>(actualValue));
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const double& actualValue, const int& expectedValue) {
|
||||
Test::Detail::Test::instance().check(static_cast<double>(expectedValue),
|
||||
actualValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entered value is true.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
inline void check(bool a) {
|
||||
Test::Detail::Test::instance().check(true, a);
|
||||
}
|
||||
|
||||
#endif // VERY_SIMPLE_TEST_H
|
||||
|
||||
/**
|
||||
* V1.0: Creation of franework
|
||||
* V1.1: make check(bool) inline, automatically convert expected value type to
|
||||
* actual value type
|
||||
* V1.2: added possibilty to count constructions and destructions of some type
|
||||
* V1.3: tweaks on check for int and double types
|
||||
* V1.4: Adding thread safety in OpenMP programs (not general thread safety, as
|
||||
* OpenMP and std::thread might not play along)
|
||||
*/
|
||||
BIN
a6_send_column/.DS_Store
vendored
Normal file
BIN
a6_send_column/.DS_Store
vendored
Normal file
Binary file not shown.
98
a6_send_column/a6_send_column.cpp
Normal file
98
a6_send_column/a6_send_column.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
#include <mpi.h>
|
||||
#include <vector>
|
||||
#include "test.h"
|
||||
#include "matrix.h"
|
||||
|
||||
using namespace HPC;
|
||||
|
||||
// For simplicity, store rank in a global variable
|
||||
int rank;
|
||||
|
||||
// ****************************************************
|
||||
// TODO: modify this function to use a custom data
|
||||
// type instead of sending each entry separately.
|
||||
// ****************************************************
|
||||
|
||||
// Send one column of the matrix from rank 0 to rank 1
|
||||
void sendColumn(Matrix& m, int col) {
|
||||
|
||||
// Store MPI_Reqeuests
|
||||
std::vector<MPI_Request> req(m.dim1());
|
||||
|
||||
// Send/receive each entry of the column separately
|
||||
for (int i=0; i<m.dim1(); ++i) {
|
||||
if (rank == 0) {
|
||||
MPI_Isend(&m(i, col), 1, MPI_DOUBLE, 1, i, MPI_COMM_WORLD, &req[i]);
|
||||
}
|
||||
else if (rank == 1) {
|
||||
MPI_Irecv(&m(i, col), 1, MPI_DOUBLE, 0, i, MPI_COMM_WORLD, &req[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for all data to be received
|
||||
std::vector<MPI_Status> stat(m.dim1());
|
||||
MPI_Waitall(req.size(), req.data(), stat.data());
|
||||
}
|
||||
|
||||
// Do not change anything below this line!
|
||||
// You may use the tests below to verify that your
|
||||
// program works correctly.
|
||||
// ****************************************************
|
||||
|
||||
// Create a matrix with some contents
|
||||
Matrix createMatrix(int n) {
|
||||
Matrix m(n);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
for (int j = 0; j < n; ++j) {
|
||||
m(i, j) = 2 * i + i * j + 1;
|
||||
}
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
void sendColumnTest() {
|
||||
int n = 3;
|
||||
|
||||
Matrix m(n);
|
||||
if (rank == 0) {
|
||||
m = createMatrix(n);
|
||||
}
|
||||
|
||||
sendColumn(m, 1);
|
||||
|
||||
if (rank == 0) {
|
||||
check(m(0, 0), 1);
|
||||
check(m(0, 1), 1);
|
||||
check(m(0, 2), 1);
|
||||
check(m(1, 0), 3);
|
||||
check(m(1, 1), 4);
|
||||
check(m(1, 2), 5);
|
||||
check(m(2, 0), 5);
|
||||
check(m(2, 1), 7);
|
||||
check(m(2, 2), 9);
|
||||
}
|
||||
|
||||
if (rank == 1) {
|
||||
check(m(0, 0), 0);
|
||||
check(m(0, 1), 1);
|
||||
check(m(0, 2), 0);
|
||||
check(m(1, 0), 0);
|
||||
check(m(1, 1), 4);
|
||||
check(m(1, 2), 0);
|
||||
check(m(2, 0), 0);
|
||||
check(m(2, 1), 7);
|
||||
check(m(2, 2), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
MPI_Init(&argc, &argv);
|
||||
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
|
||||
|
||||
sendColumnTest();
|
||||
|
||||
MPI_Finalize();
|
||||
}
|
||||
5
a6_send_column/makefile
Normal file
5
a6_send_column/makefile
Normal file
@@ -0,0 +1,5 @@
|
||||
build: a6_send_column.cpp test.h matrix.h
|
||||
mpicxx -std=c++17 a6_send_column.cpp -o send-column
|
||||
|
||||
run: build
|
||||
mpirun -np 2 -x OMP_NUM_THREADS=2 ./send-column
|
||||
324
a6_send_column/matrix.h
Normal file
324
a6_send_column/matrix.h
Normal file
@@ -0,0 +1,324 @@
|
||||
/**
|
||||
* matrix.h a very simplistic class for m times n matrices.
|
||||
*/
|
||||
|
||||
#ifndef MATRIX_H
|
||||
#define MATRIX_H
|
||||
|
||||
#include <vector>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <cmath>
|
||||
|
||||
namespace HPC {
|
||||
|
||||
// A very simplistic vector class for vectors of size n
|
||||
class Vector {
|
||||
public:
|
||||
// constructors
|
||||
Vector(int n) : n_(n), data_(n_, 0) {}
|
||||
Vector(const Vector& other) = default;
|
||||
Vector(Vector&& other) = default;
|
||||
~Vector() = default;
|
||||
|
||||
// assignment operators
|
||||
Vector& operator=(const Vector& other) = default;
|
||||
Vector& operator=(Vector&& other) = default;
|
||||
|
||||
// element access
|
||||
double& operator()(int i) { return data_[i]; }
|
||||
const double& operator()(int i) const { return data_[i]; }
|
||||
|
||||
// getter functions for the dimensions
|
||||
int dim() const { return n_; }
|
||||
|
||||
// comparison operators
|
||||
bool operator==(const Vector& b) { return (data_ == b.data_); }
|
||||
bool operator!=(const Vector& b) { return (data_ != b.data_); }
|
||||
|
||||
// addition, substraction, multiplication with scalars
|
||||
Vector& operator+=(const Vector& b) {
|
||||
for (int i = 0; i < n_; ++i) {
|
||||
operator()(i) += b(i);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector& operator-=(const Vector& b) {
|
||||
for (int i = 0; i < n_; ++i) {
|
||||
operator()(i) -= b(i);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector& operator*=(double x) {
|
||||
for (int i = 0; i < n_; ++i) {
|
||||
operator()(i) *= x;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Vector& operator/=(double x) {
|
||||
for (int i = 0; i < n_; ++i) {
|
||||
operator()(i) /= x;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
double dot(const Vector& other) const {
|
||||
double sum = 0;
|
||||
for (int i = 0; i < n_; ++i) {
|
||||
sum += operator()(i) * other(i);
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
private:
|
||||
int n_; // vector dimension
|
||||
std::vector<double> data_; // the vectors entries
|
||||
};
|
||||
|
||||
inline double dot(const Vector& v1, const Vector& v2) {
|
||||
return v1.dot(v2);
|
||||
}
|
||||
|
||||
// Print the vector as a table
|
||||
inline std::ostream& operator<<(std::ostream& os, const Vector& a) {
|
||||
const int width = 10;
|
||||
const int precision = 4;
|
||||
|
||||
const auto originalPrecision = os.precision();
|
||||
os << std::setprecision(precision);
|
||||
|
||||
for (int i = 0; i < a.dim(); ++i) {
|
||||
os << std::setw(width) << a(i) << " ";
|
||||
}
|
||||
|
||||
os << "\n";
|
||||
|
||||
os << std::setprecision(originalPrecision);
|
||||
return os;
|
||||
}
|
||||
|
||||
// A very simple class for m times n matrices
|
||||
class Matrix {
|
||||
public:
|
||||
// constructors
|
||||
Matrix() : Matrix (0, 0) {}
|
||||
Matrix(int m, int n) : m_(m), n_(n), data_(m_ * n_, 0) {}
|
||||
Matrix(std::pair<int, int> dim) : Matrix(dim.first, dim.second) {}
|
||||
Matrix(int n) : Matrix(n, n) {}
|
||||
Matrix(const Matrix& other) = default;
|
||||
Matrix(Matrix&& other) = default;
|
||||
~Matrix() = default;
|
||||
|
||||
// assignment operators
|
||||
Matrix& operator=(const Matrix& other) = default;
|
||||
Matrix& operator=(Matrix&& other) = default;
|
||||
|
||||
// element access
|
||||
double& operator()(int i, int j) { return data_[i * n_ + j]; }
|
||||
const double& operator()(int i, int j) const { return data_[i * n_ + j]; }
|
||||
|
||||
// getter functions for the dimensions
|
||||
std::pair<int, int> dim() const { return std::pair<int, int>(m_, n_); }
|
||||
int dim1() const { return m_; }
|
||||
int dim2() const { return n_; }
|
||||
int numEntries() const { return data_.size(); }
|
||||
|
||||
// comparison operators
|
||||
bool operator==(const Matrix& b) { return (data_ == b.data_); }
|
||||
bool operator!=(const Matrix& b) { return (data_ != b.data_); }
|
||||
|
||||
// addition, substraction, multiplication with scalars
|
||||
Matrix& operator+=(const Matrix& b) {
|
||||
for (int i = 0; i < m_; ++i) {
|
||||
for (int j = 0; j < n_; ++j) {
|
||||
operator()(i, j) += b(i, j);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Matrix& operator-=(const Matrix& b) {
|
||||
for (int i = 0; i < m_; ++i) {
|
||||
for (int j = 0; j < n_; ++j) {
|
||||
operator()(i, j) -= b(i, j);
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Matrix& operator*=(double x) {
|
||||
for (int i = 0; i < m_; ++i) {
|
||||
for (int j = 0; j < n_; ++j) {
|
||||
operator()(i, j) *= x;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Matrix& operator/=(double x) {
|
||||
for (int i = 0; i < m_; ++i) {
|
||||
for (int j = 0; j < n_; ++j) {
|
||||
operator()(i, j) /= x;
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
public:
|
||||
int m_; // first dimension
|
||||
int n_; // second dimension
|
||||
std::vector<double> data_; // the matrix' entries
|
||||
};
|
||||
|
||||
// Print the matrix as a table
|
||||
inline std::ostream& operator<<(std::ostream& os, const Matrix& a) {
|
||||
const int width = 10;
|
||||
const int precision = 4;
|
||||
|
||||
const auto originalPrecision = os.precision();
|
||||
os << std::setprecision(precision);
|
||||
|
||||
for (int i = 0; i < a.dim1(); ++i) {
|
||||
for (int j = 0; j < a.dim2(); ++j) {
|
||||
os << std::setw(width) << a(i, j) << " ";
|
||||
}
|
||||
if (i != a.dim1() - 1)
|
||||
os << "\n";
|
||||
}
|
||||
|
||||
os << std::setprecision(originalPrecision);
|
||||
return os;
|
||||
}
|
||||
|
||||
inline Matrix operator*(const Matrix& a, const Matrix& b) {
|
||||
if (a.dim2() == b.dim1()) {
|
||||
int m = a.dim1();
|
||||
int n = a.dim2();
|
||||
int p = b.dim2();
|
||||
Matrix c(m, p);
|
||||
for (int i = 0; i < m; ++i) {
|
||||
for (int j = 0; j < p; ++j) {
|
||||
for (int k = 0; k < n; ++k) {
|
||||
c(i, j) += a(i, k) * b(k, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
return c;
|
||||
} else {
|
||||
return Matrix(0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
inline bool equalWithinRange(const Matrix& a, const Matrix& b, double eps = 1e-12) {
|
||||
if (a.dim2() != b.dim1())
|
||||
return false;
|
||||
|
||||
int m = a.dim1();
|
||||
int n = a.dim2();
|
||||
for (int i = 0; i < m; ++i) {
|
||||
for (int j = 0; j < n; ++j) {
|
||||
if (fabs(a(i, j) - b(i, j)) > eps) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// A very simple class for "3D-Matrices" (tensors) with dimension l x m x n
|
||||
class Matrix3D {
|
||||
public:
|
||||
// constructors
|
||||
Matrix3D(int l, int m, int n) : l_(l), m_(m), n_(n), data_(l) {
|
||||
for (int i = 0; i < l_; ++i) {
|
||||
data_[i] = std::vector<std::vector<double>>(m_);
|
||||
for (int j = 0; j < m_; ++j) {
|
||||
data_[i][j] = std::vector<double>(n_, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Matrix3D(int n) : Matrix3D(n, n, n) {}
|
||||
Matrix3D(const Matrix3D& other) = default;
|
||||
Matrix3D(Matrix3D&& other) = default;
|
||||
~Matrix3D() = default;
|
||||
|
||||
// assignment operators
|
||||
Matrix3D& operator=(const Matrix3D& other) = default;
|
||||
Matrix3D& operator=(Matrix3D&& other) = default;
|
||||
|
||||
// element access
|
||||
double& operator()(int i, int j, int k) { return data_[i][j][k]; }
|
||||
const double& operator()(int i, int j, int k) const { return data_[i][j][k]; }
|
||||
|
||||
// getter functions for the dimensions
|
||||
int dim1() const { return l_; }
|
||||
int dim2() const { return m_; }
|
||||
int dim3() const { return n_; }
|
||||
|
||||
// comparison operators
|
||||
bool operator==(const Matrix3D& b) { return (data_ == b.data_); }
|
||||
bool operator!=(const Matrix3D& b) { return (data_ != b.data_); }
|
||||
|
||||
// addition
|
||||
Matrix3D& operator+=(const Matrix3D& b) {
|
||||
for (int i = 0; i < l_; ++i) {
|
||||
for (int j = 0; j < m_; ++j) {
|
||||
for (int k = 0; k < n_; ++k) {
|
||||
operator()(i, j, k) += b(i, j, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// substraction
|
||||
Matrix3D& operator-=(const Matrix3D& b) {
|
||||
for (int i = 0; i < l_; ++i) {
|
||||
for (int j = 0; j < m_; ++j) {
|
||||
for (int k = 0; k < n_; ++k) {
|
||||
operator()(i, j, k) -= b(i, j, k);
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// scalar multiplication
|
||||
Matrix3D& operator*=(double x) {
|
||||
for (int i = 0; i < l_; ++i) {
|
||||
for (int j = 0; j < m_; ++j) {
|
||||
for (int k = 0; k < n_; ++k) {
|
||||
operator()(i, j, k) *= x;
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// scalar division
|
||||
Matrix3D& operator/=(double x) {
|
||||
for (int i = 0; i < l_; ++i) {
|
||||
for (int j = 0; j < m_; ++j) {
|
||||
for (int k = 0; k < n_; ++k) {
|
||||
operator()(i, j, k) /= x;
|
||||
}
|
||||
}
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
private:
|
||||
int l_; // first dimension
|
||||
int m_; // second dimension
|
||||
int n_; // third dimension
|
||||
std::vector<std::vector<std::vector<double>>> data_; // the tensors' entries
|
||||
};
|
||||
|
||||
} // namespace HPC
|
||||
|
||||
#endif // MATRIX_H
|
||||
BIN
a6_send_column/send-column
Normal file
BIN
a6_send_column/send-column
Normal file
Binary file not shown.
290
a6_send_column/test.h
Normal file
290
a6_send_column/test.h
Normal file
@@ -0,0 +1,290 @@
|
||||
/** test.h, an extremly simple test framework.
|
||||
* Version 1.4
|
||||
* Copyright (C) 2022-2023 Tobias Kreilos, Offenburg University of Applied
|
||||
* Sciences
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*
|
||||
*
|
||||
* The framework defines a function check(a,b) that can be called with
|
||||
* parameters of different types. The function asserts
|
||||
* that the two paramters are equal (within a certain, predefined range for
|
||||
* floating point numbers) and prints the result of the comparison on the
|
||||
* command line. Additionally a summary of all tests is printed at the end of
|
||||
* the program.
|
||||
* Additionally there is TEST macro, which you can place outside main to group
|
||||
* tests together. Code in the macro is automatically executed at the beginning
|
||||
* of the program.
|
||||
* The file also defines a class InstanceCount, that can be used to
|
||||
* count how many instances of an object are still alive at the end of a
|
||||
* program. To use it, derive your class from InstanceCount<ClassName> and the
|
||||
* message is automatically printed at the end of the program.
|
||||
*/
|
||||
|
||||
#ifndef VERY_SIMPLE_TEST_H
|
||||
#define VERY_SIMPLE_TEST_H
|
||||
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
|
||||
/** Simple macro to execute the code that follows the macro (without call from
|
||||
* main)
|
||||
*
|
||||
* Define a class, that is directly instantiated
|
||||
* and contains the test code in the constructor.
|
||||
*
|
||||
* Usage:
|
||||
* TEST(MyTest)
|
||||
* {
|
||||
* // test code
|
||||
* }
|
||||
*/
|
||||
#define TEST(name) \
|
||||
struct _TestClass##name { \
|
||||
_TestClass##name(); \
|
||||
} _TestClass##name##Instance; \
|
||||
_TestClass##name::_TestClass##name()
|
||||
|
||||
// Use a namespace to hide implementation details
|
||||
namespace Test::Detail {
|
||||
/**
|
||||
* Make it possible to print the underlying value of class enums with ostream
|
||||
*
|
||||
* The expression typename std::enable_if<std::is_enum<T>::value,
|
||||
* std::ostream>::type decays to ostream if the type T is an enum. Otherwise,
|
||||
* the function is not generated.
|
||||
*/
|
||||
template <typename T>
|
||||
std::ostream& operator<<(
|
||||
typename std::enable_if<std::is_enum<T>::value, std::ostream>::type& stream,
|
||||
const T& e) {
|
||||
return stream << static_cast<typename std::underlying_type<T>::type>(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert anything to a string.
|
||||
*/
|
||||
template <typename T>
|
||||
std::string toString(const T& t) {
|
||||
std::ostringstream ss;
|
||||
ss << t;
|
||||
return "\"" + ss.str() + "\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert bools to string "true" or "false" instead of 0 and 1
|
||||
*/
|
||||
template <>
|
||||
inline std::string toString<bool>(const bool& b) {
|
||||
return b ? "\"true\"" : "\"false\"";
|
||||
}
|
||||
|
||||
/**
|
||||
* Comparison function for different types
|
||||
*/
|
||||
template <typename T>
|
||||
bool isEqual(const T& t1, const T& t2) {
|
||||
return t1 == t2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Double values are equal if they differ no more than 1e-12
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<double>(const double& expectedValue,
|
||||
const double& actualValue) {
|
||||
const double epsilon = 1e-12;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Float values are equal if they differ no more than 1e-6
|
||||
*/
|
||||
template <>
|
||||
inline bool isEqual<float>(const float& expectedValue,
|
||||
const float& actualValue) {
|
||||
const double epsilon = 1e-6;
|
||||
const double distance = fabs(actualValue - expectedValue);
|
||||
return (distance < epsilon);
|
||||
}
|
||||
|
||||
/**
|
||||
* This class realizes some basics of the test framework.
|
||||
* Test summary is printed in the destructor.
|
||||
* Apart from that, the class implements counting of total and failed tests,
|
||||
* comparison of floating point numbers within sensible boundaries and prints
|
||||
* the result of each test on the command line.
|
||||
*/
|
||||
class Test {
|
||||
public:
|
||||
/**
|
||||
* Test class is a Singleton
|
||||
*/
|
||||
static Test& instance() {
|
||||
static Test test;
|
||||
return test;
|
||||
}
|
||||
|
||||
/**
|
||||
* the main entry point for tests. Test two values for equality and output the
|
||||
* result.
|
||||
*/
|
||||
template <typename T>
|
||||
bool check(const T& expectedValue, const T& actualValue) {
|
||||
bool testResult = isEqual(expectedValue, actualValue);
|
||||
if (testResult == true) {
|
||||
registerPassingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Test successful! Expected value == actual value (="
|
||||
<< toString(expectedValue) << ")" << std::endl;
|
||||
} else {
|
||||
registerFailingTest();
|
||||
#pragma omp critical
|
||||
std::cout << "Error in test: expected value " << toString(expectedValue)
|
||||
<< ", but actual value was " << toString(actualValue)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
return testResult;
|
||||
}
|
||||
|
||||
private:
|
||||
/**
|
||||
* On destruction, print a summary of all tests.
|
||||
*/
|
||||
~Test() {
|
||||
std::cout << "\n--------------------------------------" << std::endl;
|
||||
std::cout << "Test summary:" << std::endl;
|
||||
std::cout << "Executed tests: " << numTests_ << std::endl;
|
||||
std::cout << "Failed tests: " << numFailedTests_ << std::endl;
|
||||
}
|
||||
|
||||
void registerPassingTest() { numTests_++; }
|
||||
|
||||
void registerFailingTest() {
|
||||
numTests_++;
|
||||
numFailedTests_++;
|
||||
}
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numTests_ = 0;
|
||||
|
||||
/**
|
||||
* For statistics
|
||||
*/
|
||||
std::atomic<int> numFailedTests_ = 0;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class InstanceCounterHelper {
|
||||
public:
|
||||
~InstanceCounterHelper() {
|
||||
std::cout << "The remaining number of objects of type " << typeid(T).name()
|
||||
<< " at the end of the program is " << count;
|
||||
if (count > 0)
|
||||
std::cout << " (NOT zero!)";
|
||||
std::cout << "\nThe total number of objects created was " << total
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void increment() {
|
||||
count++;
|
||||
total++;
|
||||
}
|
||||
|
||||
void decrement() { count--; }
|
||||
|
||||
private:
|
||||
std::atomic<int> count = 0;
|
||||
std::atomic<int> total = 0;
|
||||
};
|
||||
|
||||
} // namespace Test::Detail
|
||||
|
||||
/**
|
||||
* Count the instances of a class T.
|
||||
* Result gets printed automatically at the end of the program.
|
||||
* To use it, inherit T from InstanceCounter<T>, e.g.
|
||||
* class MyClass : InstanceCounter<MyClass>
|
||||
*/
|
||||
template <typename T>
|
||||
class InstanceCounter {
|
||||
public:
|
||||
InstanceCounter() { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&) { counter().increment(); }
|
||||
|
||||
InstanceCounter(const InstanceCounter&&) { counter().increment(); }
|
||||
|
||||
virtual ~InstanceCounter() { counter().decrement(); }
|
||||
|
||||
Test::Detail::InstanceCounterHelper<T>& counter() {
|
||||
static Test::Detail::InstanceCounterHelper<T> c;
|
||||
return c;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if the expected value is equal to the actual value.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
template <typename T1, typename T2>
|
||||
void check(const T1& actualValue, const T2& expectedValue) {
|
||||
const T1& expectedValueCasted{
|
||||
expectedValue}; // allows conversion in general, but avoids narrowing
|
||||
// conversion
|
||||
Test::Detail::Test::instance().check(expectedValueCasted, actualValue);
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const int& actualValue, const double& expectedValue) {
|
||||
Test::Detail::Test::instance().check(expectedValue,
|
||||
static_cast<double>(actualValue));
|
||||
}
|
||||
|
||||
// allow conversion from int to double explicitely
|
||||
template <>
|
||||
inline void check(const double& actualValue, const int& expectedValue) {
|
||||
Test::Detail::Test::instance().check(static_cast<double>(expectedValue),
|
||||
actualValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the entered value is true.
|
||||
* Result is printed on the command line and at the end of the program, a
|
||||
* summary of all tests is printed.
|
||||
*/
|
||||
inline void check(bool a) {
|
||||
Test::Detail::Test::instance().check(true, a);
|
||||
}
|
||||
|
||||
#endif // VERY_SIMPLE_TEST_H
|
||||
|
||||
/**
|
||||
* V1.0: Creation of franework
|
||||
* V1.1: make check(bool) inline, automatically convert expected value type to
|
||||
* actual value type
|
||||
* V1.2: added possibilty to count constructions and destructions of some type
|
||||
* V1.3: tweaks on check for int and double types
|
||||
* V1.4: Adding thread safety in OpenMP programs (not general thread safety, as
|
||||
* OpenMP and std::thread might not play along)
|
||||
*/
|
||||
Reference in New Issue
Block a user