diff --git a/include/gdsb/graph_input.h b/include/gdsb/graph_input.h index fedf412..786346e 100644 --- a/include/gdsb/graph_input.h +++ b/include/gdsb/graph_input.h @@ -325,6 +325,19 @@ std::tuple read_binary_graph_partition(std::ifstream& input, return std::make_tuple(data.vertex_count, edge_count); } +namespace binary +{ + +void read(std::ifstream&, gdsb::Edge32&); + +void read(std::ifstream&, gdsb::WeightedEdge32&); + +void read(std::ifstream&, gdsb::TimestampedEdge32&); + +void read(std::ifstream&, gdsb::WeightedTimestampedEdge32&); + +} // namespace binary + template void read_labels(std::istream& ins, F&& emplace) { std::string line; diff --git a/include/gdsb/mpi_graph_io.h b/include/gdsb/mpi_graph_io.h index 895e80a..4ba6c68 100644 --- a/include/gdsb/mpi_graph_io.h +++ b/include/gdsb/mpi_graph_io.h @@ -132,6 +132,23 @@ std::tuple all_read_binary_graph_partition(MPI_File const in return std::make_tuple(data.vertex_count, edge_count); } +namespace binary +{ + +// Returned boolean (of all read functions) indicate if anything went wrong +// during reading from input. Such read errors may also early return from the +// function. Thus, if the returned value is false, all written data to e (edge) +// may be invalid. +bool read(MPI_File const input, gdsb::Edge32& e); + +bool read(MPI_File const input, gdsb::WeightedEdge32& e); + +bool read(MPI_File const input, gdsb::TimestampedEdge32& e); + +bool read(MPI_File const input, gdsb::WeightedTimestampedEdge32& e); + +} // namespace binary + class MPIDataTypeAdapter { public: diff --git a/src/graph_input.cpp b/src/graph_input.cpp index db25c15..4ef2594 100644 --- a/src/graph_input.cpp +++ b/src/graph_input.cpp @@ -49,4 +49,37 @@ float read_float(char const* source, char** end) return value; } +namespace binary +{ + +void read(std::ifstream& input, Edge32& e) +{ + input.read(reinterpret_cast(&e.source), sizeof(Vertex32)); + input.read(reinterpret_cast(&e.target), sizeof(Vertex32)); +} + +void read(std::ifstream& input, gdsb::WeightedEdge32& e) +{ + input.read(reinterpret_cast(&e.source), sizeof(Vertex32)); + input.read(reinterpret_cast(&e.target.vertex), sizeof(Vertex32)); + input.read(reinterpret_cast(&e.target.weight), sizeof(Weight)); +} + +void read(std::ifstream& input, gdsb::TimestampedEdge32& e) +{ + input.read(reinterpret_cast(&e.edge.source), sizeof(Vertex32)); + input.read(reinterpret_cast(&e.edge.target), sizeof(Vertex32)); + input.read(reinterpret_cast(&e.timestamp), sizeof(Timestamp32)); +} + +void read(std::ifstream& input, gdsb::WeightedTimestampedEdge32& e) +{ + input.read(reinterpret_cast(&e.edge.source), sizeof(Vertex32)); + input.read(reinterpret_cast(&e.edge.target.vertex), sizeof(Vertex32)); + input.read(reinterpret_cast(&e.edge.target.weight), sizeof(Weight)); + input.read(reinterpret_cast(&e.timestamp), sizeof(Timestamp32)); +} + +} // namespace binary + } // namespace gdsb \ No newline at end of file diff --git a/src/mpi_graph_io.cpp b/src/mpi_graph_io.cpp index eef7436..c8f5779 100644 --- a/src/mpi_graph_io.cpp +++ b/src/mpi_graph_io.cpp @@ -159,5 +159,83 @@ MPIWeightedTimestampedEdge32::MPIWeightedTimestampedEdge32() MPI_Datatype MPIWeightedTimestampedEdge32::get() const { return m_type; } +namespace binary +{ + +// For every call to MPI_File_read() we pass MPI_STATUS_IGNORE since we do not +// investigate any issues using the status but the returned error codes. +bool read(MPI_File const input, gdsb::Edge32& e) +{ + int ec = MPI_File_read(input, &e.source, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.target, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + return ec == MPI_SUCCESS; +} + +bool read(MPI_File const input, gdsb::WeightedEdge32& e) +{ + int ec = MPI_File_read(input, &e.source, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.target.vertex, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.target.weight, 1, MPI_FLOAT, MPI_STATUS_IGNORE); + return ec == MPI_SUCCESS; +} + +bool read(MPI_File const input, gdsb::TimestampedEdge32& e) +{ + int ec = MPI_File_read(input, &e.edge.source, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.edge.target, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.timestamp, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + return ec == MPI_SUCCESS; +} + +bool read(MPI_File const input, gdsb::WeightedTimestampedEdge32& e) +{ + int ec = MPI_File_read(input, &e.edge.source, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.edge.target.vertex, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.edge.target.weight, 1, MPI_FLOAT, MPI_STATUS_IGNORE); + if (ec != MPI_SUCCESS) + { + return false; + } + + ec = MPI_File_read(input, &e.timestamp, 1, MPI_INT32_T, MPI_STATUS_IGNORE); + return ec == MPI_SUCCESS; +} +} // namespace binary + } // namespace mpi } // namespace gdsb \ No newline at end of file diff --git a/test/batcher.cpp b/test/batcher.cpp index 66f5435..ed22858 100644 --- a/test/batcher.cpp +++ b/test/batcher.cpp @@ -27,7 +27,7 @@ TEST_CASE("Batcher") TEST_CASE("partition_batch_count, on enzymes graph") { - std::ifstream binary_graph(graph_path + unweighted_directed_graph_enzymes_bin); + std::ifstream binary_graph(graph_path + directed_unweighted_graph_enzymes_bin); BinaryGraphHeader header = read_binary_graph_header(binary_graph); SECTION("partition size 2") diff --git a/test/graph_input_tests.cpp b/test/graph_input_tests.cpp index 8512602..1934547 100644 --- a/test/graph_input_tests.cpp +++ b/test/graph_input_tests.cpp @@ -435,7 +435,7 @@ TEST_CASE("read_graph, floating point weights") } } -TEST_CASE("read_graph, loops") +TEST_CASE("read_graph", "loops, ia southernwoman graph") { Edges32 edges; auto emplace = [&](Vertex32 const u, Vertex32 const v) { edges.push_back({ u, v }); }; @@ -507,6 +507,72 @@ TEST_CASE("read_graph, market_matrix") } } +TEST_CASE("read", "binary") +{ + SECTION("Edge32, enzymes graph") + { + std::filesystem::path file_path(graph_path + directed_unweighted_graph_enzymes_bin); + std::ifstream binary_graph{ file_path }; + + BinaryGraphHeader header = read_binary_graph_header(binary_graph); + + gdsb::Edge32 e; + binary::read(binary_graph, e); + + CHECK(e.source == 2u); + CHECK(e.target == 1u); + } + + SECTION("WeightedEdge32, songbird social graph") + { + std::filesystem::path file_path(graph_path + undirected_weighted_aves_songbird_social_bin); + std::ifstream binary_graph{ file_path }; + + BinaryGraphHeader header = read_binary_graph_header(binary_graph); + + gdsb::WeightedEdge32 e; + binary::read(binary_graph, e); + + // First edge should be {1, 2, 0.0735930735931} + CHECK(e.source == 1u); + CHECK(e.target.vertex == 2u); + CHECK(e.target.weight == float(0.0735930735931)); + } + + SECTION("TimestampedEdge32, reptilia tortoise graph") + { + std::filesystem::path file_path(graph_path + undirected_unweighted_temporal_reptilia_tortoise_bin); + std::ifstream binary_graph{ file_path }; + + BinaryGraphHeader header = read_binary_graph_header(binary_graph); + + gdsb::TimestampedEdge32 e; + binary::read(binary_graph, e); + + // First edge should be {1, 2}, {2005} + CHECK(e.edge.source == 1u); + CHECK(e.edge.target == 2u); + CHECK(e.timestamp == 2005u); + } + + SECTION("WeightedTimestampedEdge32, small weighted temporal graph") + { + std::filesystem::path file_path(graph_path + small_weighted_temporal_graph_bin); + std::ifstream binary_graph{ file_path }; + + BinaryGraphHeader header = read_binary_graph_header(binary_graph); + + gdsb::WeightedTimestampedEdge32 e; + binary::read(binary_graph, e); + + // First edge should be {0, 1, 1}, {1} + CHECK(e.edge.source == 0u); + CHECK(e.edge.target.vertex == 1u); + CHECK(e.edge.target.weight == float(1.)); + CHECK(e.timestamp == 1u); + } +} + TEST_CASE("read_binary_graph, small weighted temporal") { WeightedTimestampedEdges32 timestamped_edges; @@ -607,7 +673,7 @@ TEST_CASE("read_binary_graph, undirected, unweighted, static") return true; }; - std::ifstream binary_graph(graph_path + unweighted_directed_graph_enzymes_bin); + std::ifstream binary_graph(graph_path + directed_unweighted_graph_enzymes_bin); BinaryGraphHeader header = read_binary_graph_header(binary_graph); REQUIRE(header.vertex_id_byte_size == sizeof(Vertex32)); diff --git a/test/graph_output_tests.cpp b/test/graph_output_tests.cpp index e252353..1f14aef 100644 --- a/test/graph_output_tests.cpp +++ b/test/graph_output_tests.cpp @@ -107,6 +107,181 @@ TEST_CASE("write_graph, enzymes, binary") } } +TEST_CASE("write_graph, aves-songbird-social, binary") +{ + // First we read in a test graph + WeightedEdges32 edges; + auto emplace = [&](Vertex32 u, Vertex32 v, Weight w) { edges.push_back(WeightedEdge32{ u, Target32{ v, w } }); }; + std::ifstream graph_input_unweighted_directed(graph_path + undirected_weighted_aves_songbird_social); + + auto const [vertex_count, edge_count] = + read_graph(graph_input_unweighted_directed, + std::move(emplace)); + + CHECK(vertex_count == aves_songbird_social_vertex_count); + CHECK(edge_count == aves_songbird_social_edge_count); + REQUIRE(edge_count == edges.size()); + + std::filesystem::path file_path = [&]() -> std::filesystem::path + { + if constexpr (developing_new_file_format) + { + return { graph_path + undirected_weighted_aves_songbird_social_bin }; + } + else + { + return { graph_path + "test_graph.bin" }; + } + }(); + + std::ofstream out_file = open_binary_file(file_path); + + REQUIRE(out_file); + + // Write Graph + write_graph( + out_file, edges, vertex_count, edge_count, + [](std::ofstream& o, WeightedEdge32 edge) + { + o.write(reinterpret_cast(&edge.source), sizeof(Vertex32)); + o.write(reinterpret_cast(&edge.target.vertex), sizeof(Vertex32)); + o.write(reinterpret_cast(&edge.target.weight), sizeof(Weight)); + }); + + // Now we read in the written graph and check if we read the expected data. + std::ifstream binary_graph(file_path); + + BinaryGraphHeader header = read_binary_graph_header(binary_graph); + REQUIRE(header.vertex_id_byte_size == sizeof(Vertex32)); + REQUIRE(header.weight_byte_size == sizeof(Weight)); + REQUIRE(!header.directed); + REQUIRE(header.weighted); + REQUIRE(!header.dynamic); + + WeightedEdges32 edges_in; + auto read_f = [&](std::ifstream& input) + { + edges_in.push_back(WeightedEdge32{}); + input.read((char*)&edges_in.back().source, sizeof(Vertex32)); + input.read((char*)&edges_in.back().target.vertex, sizeof(Vertex32)); + input.read((char*)&edges_in.back().target.weight, sizeof(Weight)); + return true; + }; + + auto [vertex_count_in, edge_count_in] = read_binary_graph(binary_graph, header, std::move(read_f)); + + // Note: EOF is an int (for most OS?) + int eof_marker; + binary_graph.read(reinterpret_cast(&eof_marker), sizeof(decltype(eof_marker))); + REQUIRE(binary_graph.eof()); + + CHECK(vertex_count_in == aves_songbird_social_vertex_count); + CHECK(edge_count_in == aves_songbird_social_edge_count); + + REQUIRE(edges_in.size() == aves_songbird_social_edge_count); + + // Looking for edge: {42, 81, 0.00970873786408} + bool edge_42_to_81_exists = std::any_of(std::begin(edges_in), std::end(edges_in), + [](WeightedEdge32 const& edge) { + return edge.source == 42u && edge.target.vertex == 81u && + edge.target.weight == float(0.00970873786408); + }); + CHECK(edge_42_to_81_exists); + + // In case of developing a new format: comment this line + if constexpr (not developing_new_file_format) + { + REQUIRE(std::remove(file_path.c_str()) == 0); + } +} + +TEST_CASE("write_graph, reptilia-tortoise-network-pv, binary") +{ + // First we read in a test graph + TimestampedEdges32 edges; + auto emplace = [&](Vertex32 u, Vertex32 v, Timestamp32 t) + { edges.push_back(TimestampedEdge32{ Edge32{ u, v }, t }); }; + std::ifstream graph_input(graph_path + undirected_unweighted_temporal_reptilia_tortoise); + + auto const [vertex_count, edge_count] = + read_graph(graph_input, std::move(emplace)); + + CHECK(vertex_count == reptilia_tortoise_network_vertex_count); + CHECK(edge_count == reptilia_tortoise_network_edge_count); + REQUIRE(edge_count == edges.size()); + + std::filesystem::path file_path = [&]() -> std::filesystem::path + { + if constexpr (developing_new_file_format) + { + return { graph_path + undirected_unweighted_temporal_reptilia_tortoise_bin }; + } + else + { + return { graph_path + "test_graph.bin" }; + } + }(); + + std::ofstream out_file = open_binary_file(file_path); + + REQUIRE(out_file); + + // Write Graph + write_graph( + out_file, edges, vertex_count, edge_count, + [](std::ofstream& o, TimestampedEdge32 edge) + { + o.write(reinterpret_cast(&edge.edge.source), sizeof(Vertex32)); + o.write(reinterpret_cast(&edge.edge.target), sizeof(Vertex32)); + o.write(reinterpret_cast(&edge.timestamp), sizeof(Timestamp32)); + }); + + // Now we read in the written graph and check if we read the expected data. + std::ifstream binary_graph(file_path); + + BinaryGraphHeader header = read_binary_graph_header(binary_graph); + REQUIRE(header.vertex_id_byte_size == sizeof(Vertex32)); + REQUIRE(header.weight_byte_size == sizeof(Weight)); + REQUIRE(header.timestamp_byte_size == sizeof(Timestamp32)); + REQUIRE(!header.directed); + REQUIRE(!header.weighted); + REQUIRE(header.dynamic); + + TimestampedEdges32 edges_in; + auto read_f = [&](std::ifstream& input) + { + edges_in.push_back(TimestampedEdge32{}); + input.read((char*)&edges_in.back().edge.source, sizeof(Vertex32)); + input.read((char*)&edges_in.back().edge.target, sizeof(Vertex32)); + input.read((char*)&edges_in.back().timestamp, sizeof(Timestamp32)); + return true; + }; + + auto [vertex_count_in, edge_count_in] = read_binary_graph(binary_graph, header, std::move(read_f)); + + // Note: EOF is an int (for most OS?) + int eof_marker; + binary_graph.read(reinterpret_cast(&eof_marker), sizeof(decltype(eof_marker))); + REQUIRE(binary_graph.eof()); + + CHECK(vertex_count_in == reptilia_tortoise_network_vertex_count); + CHECK(edge_count_in == reptilia_tortoise_network_edge_count); + + REQUIRE(edges_in.size() == edge_count_in); + + // Looking for edge: {7, 29} {2013} + bool edge_7_to_29_exists = + std::any_of(std::begin(edges_in), std::end(edges_in), [](TimestampedEdge32 const& edge) + { return edge.edge.source == 7u && edge.edge.target == 29u && edge.timestamp == 2013u; }); + CHECK(edge_7_to_29_exists); + + // In case of developing a new format: comment this line + if constexpr (not developing_new_file_format) + { + REQUIRE(std::remove(file_path.c_str()) == 0); + } +} + TEST_CASE("write_graph, small weighted temporal, binary") { // First we read in a test graph diff --git a/test/graphs/aves-songbird-social.bin b/test/graphs/aves-songbird-social.bin new file mode 100644 index 0000000..14787ef Binary files /dev/null and b/test/graphs/aves-songbird-social.bin differ diff --git a/test/graphs/reptilia-tortoise-network-pv.bin b/test/graphs/reptilia-tortoise-network-pv.bin new file mode 100644 index 0000000..5716fd0 Binary files /dev/null and b/test/graphs/reptilia-tortoise-network-pv.bin differ diff --git a/test/mpi_graph_io_tests.cpp b/test/mpi_graph_io_tests.cpp index bce8b23..02fc5df 100644 --- a/test/mpi_graph_io_tests.cpp +++ b/test/mpi_graph_io_tests.cpp @@ -43,7 +43,7 @@ TEST_CASE("MPI, Open File") SECTION("Does not throw using valid path opening binary file.") { - std::filesystem::path file_path(graph_path + unweighted_directed_graph_enzymes_bin); + std::filesystem::path file_path(graph_path + directed_unweighted_graph_enzymes_bin); CHECK_NOTHROW(mpi::FileWrapper(file_path)); } @@ -184,7 +184,7 @@ TEST_CASE("MPI, read_binary_graph, undirected, unweighted, static") return true; }; - std::filesystem::path file_path(graph_path + unweighted_directed_graph_enzymes_bin); + std::filesystem::path file_path(graph_path + directed_unweighted_graph_enzymes_bin); mpi::FileWrapper binary_graph{ file_path }; BinaryGraphHeader header = mpi::read_binary_graph_header(binary_graph.get()); @@ -464,7 +464,7 @@ TEST_CASE("MPI, all_read_binary_graph_partition, small weighted temporal, partit TEST_CASE("MPI, all_read_binary_graph_partition, undirected, unweighted, static, partition id 0, partition size 4") { - std::filesystem::path file_path(graph_path + unweighted_directed_graph_enzymes_bin); + std::filesystem::path file_path(graph_path + directed_unweighted_graph_enzymes_bin); mpi::FileWrapper binary_graph{ file_path }; BinaryGraphHeader header = mpi::read_binary_graph_header(binary_graph.get()); @@ -535,7 +535,7 @@ TEST_CASE("MPI, all_read_binary_graph_partition, undirected, unweighted, static, TEST_CASE("MPI, all_read_binary_graph_partition, undirected, unweighted, static, partition id 0, partition size 1") { - std::filesystem::path file_path(graph_path + unweighted_directed_graph_enzymes_bin); + std::filesystem::path file_path(graph_path + directed_unweighted_graph_enzymes_bin); mpi::FileWrapper binary_graph{ file_path }; BinaryGraphHeader header = mpi::read_binary_graph_header(binary_graph.get()); @@ -556,4 +556,70 @@ TEST_CASE("MPI, all_read_binary_graph_partition, undirected, unweighted, static, REQUIRE(vertex_count == enzymes_g1_vertex_count); REQUIRE(edge_count == enzymes_g1_edge_count); REQUIRE(edges.size() == edge_count); +} + +TEST_CASE("MPI", "read") +{ + SECTION("Edge32, enzymes graph") + { + std::filesystem::path file_path(graph_path + directed_unweighted_graph_enzymes_bin); + mpi::FileWrapper binary_graph{ file_path }; + + BinaryGraphHeader header = mpi::read_binary_graph_header(binary_graph.get()); + + gdsb::Edge32 e; + REQUIRE(mpi::binary::read(binary_graph.get(), e)); + + CHECK(e.source == 2u); + CHECK(e.target == 1u); + } + + SECTION("WeightedEdge32, songbird social graph") + { + std::filesystem::path file_path(graph_path + undirected_weighted_aves_songbird_social_bin); + mpi::FileWrapper binary_graph{ file_path }; + + BinaryGraphHeader header = mpi::read_binary_graph_header(binary_graph.get()); + + gdsb::WeightedEdge32 e; + REQUIRE(mpi::binary::read(binary_graph.get(), e)); + + // First edge should be {1, 2, 0.0735930735931} + CHECK(e.source == 1u); + CHECK(e.target.vertex == 2u); + CHECK(e.target.weight == float(0.0735930735931)); + } + + SECTION("TimestampedEdge32, reptilia tortoise graph") + { + std::filesystem::path file_path(graph_path + undirected_unweighted_temporal_reptilia_tortoise_bin); + mpi::FileWrapper binary_graph{ file_path }; + + BinaryGraphHeader header = mpi::read_binary_graph_header(binary_graph.get()); + + gdsb::TimestampedEdge32 e; + REQUIRE(mpi::binary::read(binary_graph.get(), e)); + + // First edge should be {1, 2}, {2005} + CHECK(e.edge.source == 1u); + CHECK(e.edge.target == 2u); + CHECK(e.timestamp == 2005u); + } + + SECTION("WeightedTimestampedEdge32, small weighted temporal graph") + { + std::filesystem::path file_path(graph_path + small_weighted_temporal_graph_bin); + mpi::FileWrapper binary_graph{ file_path }; + + BinaryGraphHeader header = mpi::read_binary_graph_header(binary_graph.get()); + + gdsb::WeightedTimestampedEdge32 e; + REQUIRE(mpi::binary::read(binary_graph.get(), e)); + + // First edge should be {0, 1, 1}, {1} + CHECK(e.edge.source == 0u); + CHECK(e.edge.target.vertex == 1u); + CHECK(e.edge.target.weight == float(1.)); + CHECK(e.timestamp == 1u); + } } \ No newline at end of file diff --git a/test/test_graph.h b/test/test_graph.h index e8fe8b7..b68d854 100644 --- a/test/test_graph.h +++ b/test/test_graph.h @@ -10,17 +10,27 @@ static std::string const graph_path = #endif static std::string undirected_unweighted_temporal_reptilia_tortoise{ "reptilia-tortoise-network-pv.edges" }; +static std::string undirected_unweighted_temporal_reptilia_tortoise_bin{ "reptilia-tortoise-network-pv.bin" }; static std::string small_weighted_temporal_graph{ "small_weighted_temporal_graph.edges" }; static std::string small_weighted_temporal_graph_bin{ "small_weighted_temporal_graph.bin" }; static std::string unweighted_directed_graph_enzymes{ "ENZYMES_g1.edges" }; -static std::string unweighted_directed_graph_enzymes_bin{ "ENZYMES_g1.bin" }; +static std::string directed_unweighted_graph_enzymes_bin{ "ENZYMES_g1.bin" }; static std::string undirected_unweighted_soc_dolphins{ "soc-dolphins.mtx" }; static std::string undirected_weighted_aves_songbird_social{ "aves-songbird-social.edges" }; +static std::string undirected_weighted_aves_songbird_social_bin{ "aves-songbird-social.bin" }; static std::string undirected_unweighted_loops_ia_southernwomen{ "ia-southernwomen.edges" }; constexpr uint32_t enzymes_g1_vertex_count = 38; constexpr uint32_t enzymes_g1_edge_count = 168; +// Highest vertex ID is 117, lowest is 1 so we add 1 for the missing vertex ID 0 +constexpr uint32_t aves_songbird_social_vertex_count = 117 + 1; +constexpr uint32_t aves_songbird_social_edge_count = 1027 * 2; + +// Highest vertex ID is 35, lowest is 1 so we add 1 for the missing vertex ID 0 +constexpr uint32_t reptilia_tortoise_network_vertex_count = 35 + 1; +constexpr uint32_t reptilia_tortoise_network_edge_count = 104 * 2; + static std::string const test_file_path = #ifdef GDSB_TEST_FILES_DIR std::string(GDSB_TEST_FILES_DIR) + "/";