Merge branch 'implement-field-class' into 'main'

feat: add Field data structure as substitution of field declaration

See merge request sec34/port!18
This commit is contained in:
Max Lübke 2023-03-03 15:43:00 +01:00
commit 29344a3ff0
12 changed files with 506 additions and 1 deletions

View File

@ -17,6 +17,7 @@ options:
- doc
- chore
- ci
- test
commit_groups:
title_maps:
feat: Features
@ -29,6 +30,7 @@ options:
doc: Documentation
chore: Householding and Cleanup
ci: CI
test: Software Testing
header:
pattern: "^(\\w*)\\:\\s(.*)$"
pattern_maps:

View File

@ -21,6 +21,7 @@ image: git.gfz-potsdam.de:5000/sec34/port:builder
stages: # List of stages for jobs, and their order of execution
- build
- release
- test
variables:
GIT_SUBMODULE_STRATEGY: recursive
@ -34,6 +35,13 @@ build-poet: # This job runs in the build stage, which runs first.
- cmake ..
- make -j$(nproc)
test-poet:
stage: test
script:
- mkdir build_test && cd build_test
- cmake -DPOET_ENABLE_TESTING=ON ..
- make -j$(nproc) check
archive-sources: # This job runs in the build stage, which runs first.
image: python:3
stage: release

3
.gitmodules vendored
View File

@ -5,3 +5,6 @@
[submodule "ext/phreeqcrm"]
path = ext/phreeqcrm
url = ../../mluebke/phreeqcrm-gfz.git
[submodule "ext/doctest"]
path = ext/doctest
url = https://github.com/doctest/doctest.git

View File

@ -24,9 +24,19 @@ add_subdirectory(data)
add_subdirectory(app)
add_subdirectory(bench/dolo_diffu_inner)
# as tug will also pull in doctest as a dependency
set(TUG_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
add_subdirectory(ext/tug EXCLUDE_FROM_ALL)
add_subdirectory(ext/phreeqcrm EXCLUDE_FROM_ALL)
option(POET_ENABLE_TESTING "Build test suite for POET" OFF)
if (POET_ENABLE_TESTING)
add_subdirectory(ext/doctest EXCLUDE_FROM_ALL)
add_subdirectory(test)
endif()
option(BUILD_DOC "Build documentation with doxygen" OFF)
if(BUILD_DOC)

1
ext/doctest Submodule

@ -0,0 +1 @@
Subproject commit 8fdfd113dcb4ad1a31705ff8ddb13a21a505bad8

151
include/poet/Field.hpp Normal file
View File

@ -0,0 +1,151 @@
#ifndef FIELD_H_
#define FIELD_H_
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <string>
#include <unordered_map>
#include <vector>
namespace poet {
using FieldColumn = std::vector<double>;
/**
* Stores data for input/output of a module. The class keeps track of all
* defined properties with name and import/export to 1D and 2D STL vectors.
* Also, it eases the update process of a field with an input from another
* field.
*
* It can be seen as an R-like data frame, but with less access to the members.
* Species values are stored as "columns", where column is a STL vector.
*/
class Field : private std::unordered_map<std::string, FieldColumn> {
public:
/**
* Creates a new instance of a field with fixed expected vector size.
*
* \param vec_s expected vector size of each component/column. \
*/
Field(uint32_t vec_s) : req_vec_size(vec_s){};
/**
* Initializes instance with a 2D vector and according names for each columnn.
* There is no check if names were given multiple times. The order of name
* vector also defines the ordering of the output.
*
* \param input 2D vector using STL semantic describing the current state of
* the field.
* \param prop_vec Name of each vector in the input. Shall match
* the count of vectors.
*
* \exception std::runtime_error If prop_vec size doesn't match input vector
* size or column vectors size doesn't match expected vector size.
*/
void InitFromVec(const std::vector<std::vector<double>> &input,
const std::vector<std::string> &prop_vec);
/**
* Initializes instance with a 1D continious memory vector and according names
* for each columnn. There is no check if names were given multiple times. The
* order of name vector also defines the ordering of the output.
*
* \param input 1D vector using STL semantic describing the current state of
* the field storing each column starting at index *i times requested vector
* size*.
* \param prop_vec Name of each vector in the input. Shall match the
* count of vectors.
*
* \exception std::runtime_error If prop_vec size doesn't match input vector
* size or column vectors size doesn't match expected vector size.
*/
void InitFromVec(const std::vector<double> &input,
const std::vector<std::string> &prop_vec);
/**
* Returns a reference to the column vector with given name. Creates a new
* vector if prop was not found. The prop name will be added to the end of the
* list.
*
* \param key Name of the prop.
*
* \return Reference to the column vector.
*/
FieldColumn &operator[](const std::string &key);
/**
* Returns the names of all species defined in the instance.
*
* \return Vector containing all species names in output order.
*/
auto GetProps() const { return this->props; };
/**
* Return the requested vector size.
*
* \return Requested vector size set in the instanciation of the object.
*/
auto GetRequestedVecSize() const { return this->req_vec_size; };
/**
* Updates all species with values from another field. If one element of the
* input field doesn't match the names of the calling instance it will get
* skipped.
*
* \param input Field to update the current instance's columns.
*/
void UpdateFromField(const Field &input);
/**
* Builds a new 1D vector with the current state of the instance. The output
* order is given by the given species name vector set earlier and/or added
* values using the [] operator.
*
* \return 1D STL vector stored each column one after another.
*/
auto AsVector() const -> FieldColumn;
/**
* Builds a new 2D vector with the current state of the instance. The output
* order is given by the given species name vector set earlier and/or added
* values using the [] operator.
*
* \return 2D STL vector stored each column one after anothe in a new vector
* element.
*/
auto As2DVector() const -> std::vector<FieldColumn>;
/**
* Read in a (previously exported) 1D vector. It has to have the same
* dimensions as the current column count times the requested vector size of
* this instance. Import order is given by the species name vector.
*
* \param cont_field 1D field as vector.
*
* \exception std::runtime_error Input vector does not match the expected
* size.
*/
void SetFromVector(const FieldColumn &cont_field);
/**
* Read in a (previously exported) 2D vector. It has to have the same
* dimensions as the current column count of this instance and each vector
* must have the size of the requested vector size. Import order is given by
* the species name vector.
*
* \param cont_field 2D field as vector.
*
* \exception std::runtime_error Input vector has more or less elements than
* the instance or a column vector does not match expected vector size.
*/
void SetFromVector(const std::vector<FieldColumn> &cont_field);
private:
const uint32_t req_vec_size;
std::vector<std::string> props;
};
} // namespace poet
#endif // FIELD_H_

View File

@ -1,3 +1,5 @@
add_subdirectory(DataStructures)
file(GLOB poet_lib_SRC
CONFIGURE_DEPENDS
"*.cpp" "*.c")
@ -10,7 +12,7 @@ option(POET_DHT_DEBUG "Build with DHT debug info" OFF)
add_library(poet_lib ${poet_lib_SRC})
target_include_directories(poet_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)
target_link_libraries(poet_lib PUBLIC
MPI::MPI_C ${MATH_LIBRARY} ${CRYPTO_LIBRARY} RRuntime tug PhreeqcRM)
MPI::MPI_C ${MATH_LIBRARY} ${CRYPTO_LIBRARY} RRuntime tug PhreeqcRM DataStructures)
target_compile_definitions(poet_lib PUBLIC STRICT_R_HEADERS OMPI_SKIP_MPICXX)
if(POET_DHT_DEBUG)

View File

@ -0,0 +1,6 @@
file(GLOB DataStructures_SRC
CONFIGURE_DEPENDS
"*.cpp" "*.c")
add_library(DataStructures ${DataStructures_SRC})
target_include_directories(DataStructures PUBLIC ${PROJECT_SOURCE_DIR}/include)

View File

@ -0,0 +1,134 @@
#include "poet/Field.hpp"
#include <cstdint>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
void poet::Field::InitFromVec(const std::vector<std::vector<double>> &input,
const std::vector<std::string> &prop_vec) {
if (prop_vec.size() != input.size()) {
throw std::runtime_error("Prop vector shall name all elements.");
}
auto name_it = prop_vec.begin();
for (const auto &in_vec : input) {
if (in_vec.size() != req_vec_size) {
throw std::runtime_error(
"Input vector doesn't match expected vector size.");
}
this->insert({*(name_it++), in_vec});
}
this->props = prop_vec;
}
void poet::Field::InitFromVec(const std::vector<double> &input,
const std::vector<std::string> &prop_vec) {
const uint32_t expected_size = prop_vec.size() * req_vec_size;
if (expected_size != input.size()) {
throw std::runtime_error(
"Input vector have more (or less) elements than expected.");
}
auto name_it = prop_vec.begin();
for (uint32_t i = 0; i < expected_size; i += req_vec_size) {
auto input_pair = std::make_pair(
*(name_it++), std::vector<double>(&input[i], &input[i + req_vec_size]));
this->insert(input_pair);
}
this->props = prop_vec;
}
auto poet::Field::AsVector() const -> poet::FieldColumn {
const uint32_t min_size = req_vec_size * this->size();
poet::FieldColumn output;
output.reserve(min_size);
for (const auto &elem : props) {
const auto map_it = this->find(elem);
const auto start = map_it->second.begin();
const auto end = map_it->second.end();
output.insert(output.end(), start, end);
}
return output;
}
void poet::Field::SetFromVector(const poet::FieldColumn &cont_field) {
if (cont_field.size() != this->size() * req_vec_size) {
throw std::runtime_error(
"Field::SetFromVector: vector does not match expected size");
}
uint32_t vec_p = 0;
for (const auto &elem : props) {
const auto start = cont_field.begin() + vec_p;
const auto end = start + req_vec_size;
const auto map_it = this->find(elem);
map_it->second = FieldColumn(start, end);
vec_p += req_vec_size;
}
}
void poet::Field::SetFromVector(
const std::vector<poet::FieldColumn> &cont_field) {
if (cont_field.size() != this->size()) {
throw std::runtime_error(
"Input field contains more or less elements than this container.");
}
auto in_field_it = cont_field.begin();
for (const auto &elem : props) {
if (in_field_it->size() != req_vec_size) {
throw std::runtime_error(
"One vector contains more or less elements than expected.");
}
const auto map_it = this->find(elem);
map_it->second = *(in_field_it++);
}
}
void poet::Field::UpdateFromField(const poet::Field &input) {
for (const auto &input_elem : input) {
auto it_self = this->find(input_elem.first);
if (it_self == this->end()) {
continue;
}
it_self->second = input_elem.second;
}
}
auto poet::Field::As2DVector() const -> std::vector<poet::FieldColumn> {
std::vector<poet::FieldColumn> output;
output.reserve(this->size());
for (const auto &elem : props) {
const auto map_it = this->find(elem);
output.push_back(map_it->second);
}
return output;
}
poet::FieldColumn &poet::Field::operator[](const std::string &key) {
if (this->find(key) == this->end()) {
props.push_back(key);
}
return std::unordered_map<std::string, FieldColumn>::operator[](key);
}

11
test/CMakeLists.txt Normal file
View File

@ -0,0 +1,11 @@
file(GLOB test_SRC
CONFIGURE_DEPENDS
"*.cpp" "*.c")
add_executable(testPOET ${test_SRC})
target_link_libraries(testPOET doctest poet_lib)
add_custom_target(check
COMMAND $<TARGET_FILE:testPOET>
DEPENDS testPOET
)

2
test/setup.cpp Normal file
View File

@ -0,0 +1,2 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

175
test/testDataStructures.cpp Normal file
View File

@ -0,0 +1,175 @@
#include <algorithm>
#include <array>
#include <cstdint>
#include <doctest/doctest.h>
#include <iostream>
#include <poet/Field.hpp>
#include <string>
#include <vector>
using namespace poet;
#define CHECK_AND_FAIL_LOOP(val1, val2) \
if (val1 != val2) { \
FAIL_CHECK("Value differs: ", val1, " != ", val2); \
}
TEST_CASE("Field") {
constexpr uint32_t VEC_SIZE = 5;
constexpr uint32_t VEC_COUNT = 3;
constexpr double INIT_VAL = 1;
Field dut(VEC_SIZE);
std::vector<std::string> names = {"C", "Ca", "Na"};
std::vector<FieldColumn> init_values(names.size(),
FieldColumn(VEC_SIZE, INIT_VAL));
SUBCASE("Initialize field with 2D vector") {
dut.InitFromVec(init_values, names);
auto props = dut.GetProps();
CHECK_EQ(names.size(), props.size());
const auto res = dut["C"];
CHECK_EQ(res.size(), VEC_SIZE);
for (const auto &elem : res) {
CHECK_AND_FAIL_LOOP(elem, INIT_VAL);
}
}
SUBCASE("Initialize field with 2D vector") {
std::vector<double> init_values(VEC_SIZE * VEC_COUNT, 1);
dut.InitFromVec(init_values, names);
auto props = dut.GetProps();
CHECK_EQ(names.size(), props.size());
const auto res = dut["C"];
CHECK_EQ(res.size(), VEC_SIZE);
for (const auto &elem : res) {
CHECK_AND_FAIL_LOOP(elem, INIT_VAL);
}
}
dut.InitFromVec(init_values, names);
SUBCASE("Return vector") {
std::vector<double> output = dut.AsVector();
CHECK(output.size() == VEC_SIZE * VEC_COUNT);
}
constexpr double NEW_VAL = 2.;
std::vector<double> new_val_vec(VEC_SIZE, NEW_VAL);
dut["C"] = new_val_vec;
SUBCASE("Check manipulation of column") {
auto test_it = new_val_vec.begin();
for (const auto &val : dut["C"]) {
CHECK_EQ(val, *test_it);
test_it++;
}
}
SUBCASE("Check correctly manipulated values of 1D vector") {
auto out_res = dut.AsVector();
auto out_it = out_res.begin();
for (uint32_t i = 0; i < VEC_SIZE; i++, out_it++) {
CHECK_AND_FAIL_LOOP(NEW_VAL, *out_it);
}
for (; out_it != out_res.end(); out_it++) {
CHECK_AND_FAIL_LOOP(INIT_VAL, *out_it);
}
}
std::vector<double> new_field(VEC_SIZE * VEC_COUNT);
for (uint32_t i = 0; i < VEC_COUNT; i++) {
for (uint32_t j = 0; j < VEC_SIZE; j++) {
new_field[j + (i * VEC_SIZE)] = (double)(i + 1) / (double)(j + 1);
}
}
dut.SetFromVector(new_field);
SUBCASE("SetFromVector return correct field vector") {
auto ret_vec = dut.AsVector();
auto ret_it = ret_vec.begin();
auto new_it = new_field.begin();
for (; ret_it != ret_vec.end(); ret_it++, new_it++) {
CHECK_AND_FAIL_LOOP(*ret_it, *new_it);
}
}
SUBCASE("Get single column with new values") {
auto new_it = new_field.begin() + 2 * VEC_SIZE;
auto ret_vec = dut["Na"];
auto ret_it = ret_vec.begin();
CHECK_EQ(ret_vec.size(), VEC_SIZE);
for (; ret_it != ret_vec.end(); ret_it++, new_it++) {
CHECK_AND_FAIL_LOOP(*ret_it, *new_it);
}
}
std::vector<double> mg_vec = {1, 2, 3, 4, 5};
dut["Mg"] = mg_vec;
SUBCASE("Operator creates new element and places it at the end") {
auto new_props = dut.GetProps();
CHECK_EQ(new_props.size(), 4);
CHECK_EQ(new_props[3], "Mg");
auto ret_vec = dut.As2DVector();
auto mg_vec_it = mg_vec.begin();
for (const auto &val : ret_vec[3]) {
CHECK_AND_FAIL_LOOP(val, *(mg_vec_it++));
}
}
// reset field
names = dut.GetProps();
dut.SetFromVector(
std::vector<FieldColumn>(names.size(), FieldColumn(VEC_SIZE, INIT_VAL)));
constexpr double SOME_OTHER_VAL = -0.5;
Field some_other_field(VEC_SIZE);
std::vector<std::string> some_other_props = {"Na", "Cl"};
std::vector<double> some_other_values(VEC_SIZE * some_other_props.size(),
SOME_OTHER_VAL);
some_other_field.InitFromVec(some_other_values, some_other_props);
SUBCASE("Update existing field from another field") {
dut.UpdateFromField(some_other_field);
auto ret_vec = dut.As2DVector();
auto ret_it = ret_vec.begin();
for (const auto &prop : names) {
const auto &curr_vec = *(ret_it++);
for (const auto &val : curr_vec) {
CHECK_AND_FAIL_LOOP((prop == "Na" ? SOME_OTHER_VAL : INIT_VAL), val);
}
}
}
}