diff --git a/quickTest/FVMAdaptTests/.gitignore b/quickTest/FVMAdaptTests/.gitignore new file mode 100644 index 00000000..9544c285 --- /dev/null +++ b/quickTest/FVMAdaptTests/.gitignore @@ -0,0 +1 @@ +TestResults diff --git a/quickTest/FVMAdaptTests/Core/.gitignore b/quickTest/FVMAdaptTests/Core/.gitignore new file mode 100644 index 00000000..1eba5489 --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/.gitignore @@ -0,0 +1,7 @@ +*.d +*.exe +*.log +*.log1 +*.o +TestResults +output/ diff --git a/quickTest/FVMAdaptTests/Core/Makefile b/quickTest/FVMAdaptTests/Core/Makefile new file mode 100644 index 00000000..45132abe --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/Makefile @@ -0,0 +1,66 @@ +############################################################################### +# +# Copyright 2008-2026, Mississippi State University +# +# This file is part of the Loci Framework. +# +# The Loci Framework is free software: you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Loci Framework 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 +# Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# along with the Loci Framework. If not, see +# +############################################################################### + +include $(LOCI_BASE)/Loci.conf +include $(LOCI_BASE)/version.conf + +INCLUDES = -I../../contrib/doctest -I$(LOCI_BASE)/include +LIB_PATH = $(LOCI_BASE)/lib:$(LD_LIBRARY_PATH) +DY_PATH = $(LOCI_BASE)/lib:$(DYLD_LIBRARY_PATH) +SOURCES = test_fvmadapt_core.cc fixtures.cc core_utils.cc plan_checks.cc \ + replay_trace.cc example_artifacts.cc +OBJS = $(SOURCES:.cc=.o) + +.PHONY: default examples list clean distclean FRC + +default: TestResults + +TestResults: test_fvmadapt_core.exe FRC + @(LD_LIBRARY_PATH=$(LIB_PATH) DYLD_LIBRARY_PATH=$(DY_PATH) ./test_fvmadapt_core.exe --check-only > test_fvmadapt_core.log || true) + @tail -n 1 test_fvmadapt_core.log > test_fvmadapt_core.log1 + @echo -n FVMAdaptTests/Core/test_fvmadapt_core":" > $@ + @if grep -q "SUCCESS!" test_fvmadapt_core.log1 ; then echo " PASSED!"; else echo " FAILED!"; fi >>$@ + @rm test_fvmadapt_core.log1 + @cat $@ + +examples: test_fvmadapt_core.exe + @rm -fr output + @mkdir -p output + LD_LIBRARY_PATH=$(LIB_PATH) DYLD_LIBRARY_PATH=$(DY_PATH) ./test_fvmadapt_core.exe --write-dir output + +list: test_fvmadapt_core.exe + LD_LIBRARY_PATH=$(LIB_PATH) DYLD_LIBRARY_PATH=$(DY_PATH) ./test_fvmadapt_core.exe --list + +test_fvmadapt_core.exe: $(OBJS) $(LOCI_BASE)/lib/libfvmadaptfunc.$(LIB_SUFFIX) + $(LD) -o $@ $(OBJS) -L$(LOCI_BASE)/lib -lfvmadaptfunc $(LIBS) $(LDFLAGS) + +clean: + rm -fr *.o *.exe *.d *.log *.log1 TestResults output + +distclean: clean + +FRC: + +DEPEND_FILES=$(SOURCES:.cc=.d) + +ifeq ($(filter $(MAKECMDGOALS),clean distclean ),) +-include $(DEPEND_FILES) +endif diff --git a/quickTest/FVMAdaptTests/Core/core_utils.cc b/quickTest/FVMAdaptTests/Core/core_utils.cc new file mode 100644 index 00000000..66b1e65e --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/core_utils.cc @@ -0,0 +1,63 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#include "fvmadapt_core.h" + +#include +#include +#include + +/// Format split-plan char codes as a readable integer list. +std::string plan_string(const std::vector& plan) { + std::ostringstream out ; + out << "[" ; + for(size_t i = 0; i < plan.size(); ++i) { + if(i != 0) { + out << "," ; + } + out << int(plan[i]) ; + } + out << "]" ; + return out.str() ; +} + +/// Create an output directory if it does not already exist. +void make_directory(const std::string& path) { + if(path.empty()) { + return ; + } + if(mkdir(path.c_str(), 0775) != 0 && errno != EEXIST) { + std::ostringstream msg ; + msg << "could not create directory '" << path << "': " << std::strerror(errno) ; + throw std::runtime_error(msg.str()) ; + } +} + +/// Join a directory and file name with one path separator. +std::string join_path(const std::string& dir, const std::string& file) { + if(dir.empty()) { + return file ; + } + if(dir[dir.size() - 1] == '/') { + return dir + file ; + } + return dir + "/" + file ; +} diff --git a/quickTest/FVMAdaptTests/Core/example_artifacts.cc b/quickTest/FVMAdaptTests/Core/example_artifacts.cc new file mode 100644 index 00000000..4b3ae3dc --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/example_artifacts.cc @@ -0,0 +1,465 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#include "fvmadapt_core.h" + +#include +#include +#include +#include + +namespace { + +/// Format cell-neighbor index lists as readable integers. +std::string int_vector_string(const std::vector& values) { + std::ostringstream out ; + out << "[" ; + for(size_t i = 0; i < values.size(); ++i) { + if(i != 0) { + out << "," ; + } + out << values[i] ; + } + out << "]" ; + return out.str() ; +} + +/// Return x, y, or z from a node coordinate. +double coord(const Node* node, int axis) { + if(axis == 0) { + return node->p.x ; + } + if(axis == 1) { + return node->p.y ; + } + return node->p.z ; +} + +/// Collect the eight distinct corner nodes owned by one HexCell leaf. +std::vector unique_leaf_nodes(HexCell* leaf) { + std::map seen ; + std::vector nodes ; + std::vector edges = leaf->get_edges() ; + + for(size_t i = 0; i < edges.size(); ++i) { + if(edges[i] == 0 || edges[i]->head == 0 || edges[i]->tail == 0) { + throw std::runtime_error("leaf has an incomplete edge") ; + } + if(seen.find(edges[i]->head) == seen.end()) { + seen[edges[i]->head] = true ; + nodes.push_back(edges[i]->head) ; + } + if(seen.find(edges[i]->tail) == seen.end()) { + seen[edges[i]->tail] = true ; + nodes.push_back(edges[i]->tail) ; + } + } + + if(nodes.size() != 8) { + std::ostringstream msg ; + msg << "expected 8 nodes for a hex leaf, found " << nodes.size() ; + throw std::runtime_error(msg.str()) ; + } + + return nodes ; +} + +/// Compare coordinates with a small tolerance for corner matching. +bool close_to(double lhs, double rhs) { + return std::fabs(lhs - rhs) <= 1.0e-10 ; +} + +/// Find the node at one min/max corner of a hex leaf. +Node* find_corner(const std::vector& nodes, + const double min_coord[3], + const double max_coord[3], + const int corner[3]) { + for(size_t i = 0; i < nodes.size(); ++i) { + bool match = true ; + for(int axis = 0; axis < 3; ++axis) { + const double target = corner[axis] ? max_coord[axis] : min_coord[axis] ; + match = match && close_to(coord(nodes[i], axis), target) ; + } + if(match) { + return nodes[i] ; + } + } + + throw std::runtime_error("could not identify a VTK hex corner") ; +} + +/// Order a HexCell leaf's corner nodes in VTK hexahedron order. +std::vector vtk_hex_nodes(HexCell* leaf) { + std::vector nodes = unique_leaf_nodes(leaf) ; + double min_coord[3] = {coord(nodes[0], 0), coord(nodes[0], 1), coord(nodes[0], 2)} ; + double max_coord[3] = {min_coord[0], min_coord[1], min_coord[2]} ; + + for(size_t i = 1; i < nodes.size(); ++i) { + for(int axis = 0; axis < 3; ++axis) { + min_coord[axis] = std::min(min_coord[axis], coord(nodes[i], axis)) ; + max_coord[axis] = std::max(max_coord[axis], coord(nodes[i], axis)) ; + } + } + + const int corner_bits[8][3] = { + {0, 0, 0}, + {1, 0, 0}, + {1, 1, 0}, + {0, 1, 0}, + {0, 0, 1}, + {1, 0, 1}, + {1, 1, 1}, + {0, 1, 1} + } ; + + std::vector ordered(8) ; + for(int i = 0; i < 8; ++i) { + ordered[i] = find_corner(nodes, min_coord, max_coord, corner_bits[i]) ; + } + return ordered ; +} + +} // namespace + +/// Write HexCell leaves as a VTK unstructured-grid file. +int write_hex_vtk(const std::string& path, + const std::string& title, + int root_split_code, + const std::vector& leaves) { + std::vector > cell_nodes(leaves.size()) ; + std::vector points ; + std::map point_id ; + + for(size_t cell = 0; cell < leaves.size(); ++cell) { + cell_nodes[cell] = vtk_hex_nodes(leaves[cell]) ; + for(size_t n = 0; n < cell_nodes[cell].size(); ++n) { + Node* node = cell_nodes[cell][n] ; + if(point_id.find(node) == point_id.end()) { + const int id = int(points.size()) ; + point_id[node] = id ; + points.push_back(node) ; + } + } + } + + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open VTK output file") ; + } + + out << "# vtk DataFile Version 3.0\n" ; + out << title << "\n" ; + out << "ASCII\n" ; + out << "DATASET UNSTRUCTURED_GRID\n" ; + out << "POINTS " << points.size() << " float\n" ; + out << std::setprecision(17) ; + for(size_t i = 0; i < points.size(); ++i) { + out << points[i]->p.x << " " << points[i]->p.y << " " << points[i]->p.z << "\n" ; + } + + out << "CELLS " << cell_nodes.size() << " " << cell_nodes.size() * 9 << "\n" ; + for(size_t cell = 0; cell < cell_nodes.size(); ++cell) { + out << "8" ; + for(size_t n = 0; n < cell_nodes[cell].size(); ++n) { + out << " " << point_id[cell_nodes[cell][n]] ; + } + out << "\n" ; + } + + out << "CELL_TYPES " << cell_nodes.size() << "\n" ; + for(size_t cell = 0; cell < cell_nodes.size(); ++cell) { + out << "12\n" ; + } + + out << "CELL_DATA " << cell_nodes.size() << "\n" ; + out << "SCALARS root_split_code int 1\n" ; + out << "LOOKUP_TABLE default\n" ; + for(size_t cell = 0; cell < cell_nodes.size(); ++cell) { + out << root_split_code << "\n" ; + } + out << "SCALARS fvmadapt_cell_index int 1\n" ; + out << "LOOKUP_TABLE default\n" ; + for(size_t cell = 0; cell < leaves.size(); ++cell) { + out << leaves[cell]->getCellIndex() << "\n" ; + } + + return int(points.size()) ; +} + +/// Collect unique leaf edges from a fixture's root edge list. +std::vector edge_leaves(std::list& root_edges) { + std::set seen ; + std::vector leaves ; + + for(std::list::iterator edge = root_edges.begin() ; + edge != root_edges.end(); ++edge) { + std::list local_leaves ; + (*edge)->sort_leaves(local_leaves) ; + for(std::list::iterator leaf = local_leaves.begin() ; + leaf != local_leaves.end(); ++leaf) { + if(seen.insert(*leaf).second) { + leaves.push_back(*leaf) ; + } + } + } + + return leaves ; +} + +/// Return an existing point id or assign a new one. +int point_id(Node* node, std::map& ids, std::vector& points) { + std::map::const_iterator existing = ids.find(node) ; + if(existing != ids.end()) { + return existing->second ; + } + + const int id = int(points.size()) ; + ids[node] = id ; + points.push_back(node) ; + return id ; +} + +/// Write fixture edge leaves as a VTK wireframe file. +int write_wireframe_vtk(const std::string& path, + const std::string& title, + std::list& root_nodes, + std::list& root_edges) { + std::vector points ; + std::map ids ; + for(std::list::iterator node = root_nodes.begin() ; + node != root_nodes.end(); ++node) { + point_id(*node, ids, points) ; + } + + const std::vector leaves = edge_leaves(root_edges) ; + for(size_t i = 0; i < leaves.size(); ++i) { + point_id(leaves[i]->head, ids, points) ; + point_id(leaves[i]->tail, ids, points) ; + } + + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open wireframe VTK output file") ; + } + + out << "# vtk DataFile Version 3.0\n" ; + out << title << "\n" ; + out << "ASCII\n" ; + out << "DATASET POLYDATA\n" ; + out << "POINTS " << points.size() << " float\n" ; + out << std::setprecision(17) ; + for(size_t i = 0; i < points.size(); ++i) { + out << points[i]->p.x << " " << points[i]->p.y << " " << points[i]->p.z << "\n" ; + } + + out << "LINES " << leaves.size() << " " << leaves.size() * 3 << "\n" ; + for(size_t i = 0; i < leaves.size(); ++i) { + out << "2 " << ids[leaves[i]->head] << " " << ids[leaves[i]->tail] << "\n" ; + } + + return int(points.size()) ; +} + +namespace { + +/// Count leaves in a QuadFace plan without writing geometry. +int count_quad_face_leaves(const std::vector& face_plan) { + QuadFace face(4) ; + std::vector leaves ; + face.empty_resplit(face_plan, 0, leaves) ; + return int(leaves.size()) ; +} + +/// Count leaves in an isotropic Face plan without writing geometry. +int count_general_face_leaves(const std::vector& face_plan, int num_edges) { + if(face_plan.empty()) { + return 1 ; + } + + std::vector work ; + work.push_back(char(0)) ; + size_t index = 0 ; + int leaves = 0 ; + for(size_t cursor = 0; cursor < work.size(); ++cursor) { + const char code = index < face_plan.size() ? face_plan[index++] : char(0) ; + if(code == 0) { + ++leaves ; + } else if(code == 1) { + for(int child = 0; child < num_edges; ++child) { + work.push_back(char(0)) ; + } + } else if(code == 8) { + work.push_back(char(0)) ; + } else { + std::ostringstream msg ; + msg << "unsupported general face plan code " << int(code) + << " in " << plan_string(face_plan) ; + throw std::runtime_error(msg.str()) ; + } + } + return leaves ; +} + +/// Count leaves in an Edge plan without keeping the generated nodes. +int count_edge_leaves(const std::vector& edge_plan) { + Node head(vect3d(0.0, 0.0, 0.0)) ; + Node tail(vect3d(1.0, 0.0, 0.0)) ; + Edge edge(&head, &tail) ; + std::list nodes ; + edge.resplit(edge_plan, nodes) ; + std::list leaves ; + edge.sort_leaves(leaves) ; + const int count = int(leaves.size()) ; + for(std::list::iterator node = nodes.begin(); node != nodes.end(); ++node) { + delete *node ; + } + nodes.clear() ; + return count ; +} + +/// Write the HexCell section of core_plan_audit.dat for one plan case. +/// This is a human-inspection artifact: it shows the split plan induced on +/// each parent face, the fine-cell-side c1 index for each face leaf, and the +/// edge plans induced by each face plan. +void write_hex_plan_audit(std::ofstream& out, const CaseResult& result) { + out << "case " << result.shape << " " << result.case_name << "\n" ; + out << " input_cell_plan " << plan_string(result.input_plan) << "\n" ; + out << " canonical_cell_plan " << plan_string(result.canonical_plan) << "\n" ; + out << " counted_leaves " << result.counted_leaves << "\n" ; + if(!result.vtk_file.empty()) { + out << " vtk_file " << result.vtk_file << "\n" ; + } + for(int face = 0; face < 6; ++face) { + const std::vector face_plan = + extract_hex_face(result.input_plan, DIRECTION(face)) ; + const std::vector c1 = + get_c1_hex(result.input_plan, face_plan, 0, char(face)) ; + out << " face " << face + << " plan " << plan_string(face_plan) + << " leaves " << count_quad_face_leaves(face_plan) + << " c1 " << int_vector_string(c1) << "\n" ; + for(int edge = 0; edge < 4; ++edge) { + std::vector edge_plan ; + extract_quad_edge(face_plan, edge_plan, unsigned(edge)) ; + out << " edge " << edge + << " plan " << plan_string(edge_plan) + << " leaves " << count_edge_leaves(edge_plan) << "\n" ; + } + } +} + +/// Write face and c1 projections for one Prism plan case. +void write_prism_plan_audit(std::ofstream& out, const CaseResult& result) { + out << "case " << result.shape << " " << result.case_name << "\n" ; + out << " input_cell_plan " << plan_string(result.input_plan) << "\n" ; + out << " canonical_cell_plan " << plan_string(result.canonical_plan) << "\n" ; + out << " counted_leaves " << result.counted_leaves << "\n" ; + if(!result.vtk_file.empty()) { + out << " vtk_file " << result.vtk_file << "\n" ; + } + for(int face = 0; face < 5; ++face) { + const std::vector face_plan = extract_prism_face(result.input_plan, face) ; + const std::vector c1 = + get_c1_prism(result.input_plan, face_plan, 0, face) ; + out << " face " << face + << " plan " << plan_string(face_plan) + << " leaves " + << (face < 2 ? count_general_face_leaves(face_plan, 3) + : count_quad_face_leaves(face_plan)) + << " c1 " << int_vector_string(c1) << "\n" ; + } +} + +/// Write the plan-level audit rows for one general Cell case. +void write_general_plan_audit(std::ofstream& out, const CaseResult& result) { + out << "case " << result.shape << " " << result.case_name << "\n" ; + out << " input_cell_plan " << plan_string(result.input_plan) << "\n" ; + out << " canonical_cell_plan " << plan_string(result.canonical_plan) << "\n" ; + out << " counted_leaves " << result.counted_leaves << "\n" ; + if(!result.vtk_file.empty()) { + out << " vtk_file " << result.vtk_file << "\n" ; + } +} + +/// Write one summary row per plan case. +void write_summary(const std::string& output_dir, + const std::vector& results) { + const std::string path = join_path(output_dir, "core_split_summary.dat") ; + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open summary output file") ; + } + + out << "# shape case input_plan canonical_plan counted_leaves " + << "resplit_leaves sorted_leaves vtk_points vtk_file\n" ; + for(size_t i = 0; i < results.size(); ++i) { + out << results[i].shape << " " + << results[i].case_name << " " + << plan_string(results[i].input_plan) << " " + << plan_string(results[i].canonical_plan) << " " + << results[i].counted_leaves << " " + << results[i].resplit_leaves << " " + << results[i].sorted_leaves << " " + << results[i].point_count << " " + << results[i].vtk_file << "\n" ; + } +} + +/// Write the detailed plan projection audit. +void write_plan_audit(const std::string& output_dir, + const std::vector& results) { + const std::string path = join_path(output_dir, "core_plan_audit.dat") ; + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open plan audit output file") ; + } + + out << "# FVMAdapt core plan audit\n" ; + out << "# Plans are char vectors printed as integer codes in breadth-first order.\n" ; + for(size_t i = 0; i < results.size(); ++i) { + if(results[i].shape == "hex") { + write_hex_plan_audit(out, results[i]) ; + } else if(results[i].shape == "prism") { + write_prism_plan_audit(out, results[i]) ; + } else { + write_general_plan_audit(out, results[i]) ; + } + out << "\n" ; + } +} + +} // namespace + +/// Write all optional human-inspection artifacts for the core tests. +void write_example_artifacts(const std::string& output_dir, + const std::vector& results) { + write_summary(output_dir, results) ; + write_plan_audit(output_dir, results) ; + write_plan_replay(output_dir, results) ; + for(size_t i = 0; i < results.size(); ++i) { + if(!results[i].vtk_file.empty()) { + std::cout << "wrote " << results[i].vtk_file << "\n" ; + } + } + std::cout << "wrote " << join_path(output_dir, "core_split_summary.dat") << "\n" ; + std::cout << "wrote " << join_path(output_dir, "core_plan_audit.dat") << "\n" ; + std::cout << "wrote " << join_path(output_dir, "core_plan_replay.dat") << "\n" ; +} diff --git a/quickTest/FVMAdaptTests/Core/fixtures.cc b/quickTest/FVMAdaptTests/Core/fixtures.cc new file mode 100644 index 00000000..822768b5 --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/fixtures.cc @@ -0,0 +1,260 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#include "fvmadapt_core.h" + +// These are small test-owned cell topologies. +// They keep the nodes, edges, faces, and root cell together for cleanup. + +namespace { + +/// Allocate a test node and store it for cleanup. +template +Node* add_node(Fixture& fixture, double x, double y, double z) { + vect3d p(x, y, z) ; + Node* node = new Node(p, int32(fixture.nodes.size() + 1)) ; + fixture.nodes.push_back(node) ; + return node ; +} + +/// Allocate a test edge and store it for cleanup. +template +Edge* add_edge(Fixture& fixture, Node* head, Node* tail) { + Edge* edge = new Edge(head, tail) ; + fixture.edges.push_back(edge) ; + return edge ; +} +} // namespace + +HexFixture::HexFixture(): root(0) {} + +HexFixture::~HexFixture() { + delete root ; + cleanup_list(nodes, edges, faces) ; +} + +PrismFixture::PrismFixture(): root(0) {} + +PrismFixture::~PrismFixture() { + delete root ; + cleanup_list(nodes, edges) ; + cleanup_list(qfaces) ; + cleanup_list(gfaces) ; +} + +GeneralFixture::GeneralFixture(): root(0) {} + +GeneralFixture::~GeneralFixture() { + delete root ; + cleanup_list(nodes, edges, faces) ; +} + +/// Build a unit hexahedron with the face ordering expected by HexCell. +void build_unit_hex(HexFixture& fixture) { + Node* node[8] ; + node[0] = add_node(fixture, 0.0, 0.0, 0.0) ; + node[1] = add_node(fixture, 0.0, 0.0, 1.0) ; + node[2] = add_node(fixture, 0.0, 1.0, 0.0) ; + node[3] = add_node(fixture, 0.0, 1.0, 1.0) ; + node[4] = add_node(fixture, 1.0, 0.0, 0.0) ; + node[5] = add_node(fixture, 1.0, 0.0, 1.0) ; + node[6] = add_node(fixture, 1.0, 1.0, 0.0) ; + node[7] = add_node(fixture, 1.0, 1.0, 1.0) ; + + const int edge_head[12] = {0, 1, 2, 3, 0, 1, 4, 5, 0, 2, 4, 6} ; + const int edge_tail[12] = {4, 5, 6, 7, 2, 3, 6, 7, 1, 3, 5, 7} ; + + Edge* edge[12] ; + for(int i = 0; i < 12; ++i) { + edge[i] = add_edge(fixture, node[edge_head[i]], node[edge_tail[i]]) ; + } + + // Wire the 12 unit-hex edges into the six QuadFace objects. + const int face_to_edge[6][4] = { + {6, 11, 7, 10}, + {4, 9, 5, 8}, + {2, 11, 3, 9}, + {0, 10, 1, 8}, + {1, 7, 3, 5}, + {0, 6, 2, 4} + } ; + + QuadFace** face = new QuadFace*[6] ; + for(int i = 0; i < 6; ++i) { + face[i] = new QuadFace(4) ; + fixture.faces.push_back(face[i]) ; + for(int j = 0; j < 4; ++j) { + face[i]->edge[j] = edge[face_to_edge[i][j]] ; + } + } + + fixture.root = new HexCell(face) ; +} + +/// Build a unit triangular prism with two general faces and three quad faces. +void build_unit_prism(PrismFixture& fixture) { + Node* node[6] ; + node[0] = add_node(fixture, 0.0, 0.0, 0.0) ; + node[1] = add_node(fixture, 1.0, 0.0, 0.0) ; + node[2] = add_node(fixture, 0.0, 1.0, 0.0) ; + node[3] = add_node(fixture, 0.0, 0.0, 1.0) ; + node[4] = add_node(fixture, 1.0, 0.0, 1.0) ; + node[5] = add_node(fixture, 0.0, 1.0, 1.0) ; + + const int edge_head[9] = {0, 1, 2, 3, 4, 5, 0, 1, 2} ; + const int edge_tail[9] = {1, 2, 0, 4, 5, 3, 3, 4, 5} ; + + Edge* edge[9] ; + for(int i = 0; i < 9; ++i) { + edge[i] = add_edge(fixture, node[edge_head[i]], node[edge_tail[i]]) ; + } + + const int gface_to_edge[2][3] = {{0, 1, 2}, {3, 4, 5}} ; + const int qface_to_edge[3][4] = {{0, 7, 3, 6}, {1, 8, 4, 7}, {2, 6, 5, 8}} ; + + Prism* prism = new Prism(3) ; + for(int i = 0; i < 2; ++i) { + Face* face = new Face(3) ; + fixture.gfaces.push_back(face) ; + for(int j = 0; j < 3; ++j) { + face->edge[j] = edge[gface_to_edge[i][j]] ; + face->needReverse[j] = false ; + } + prism->setFace(i, face) ; + } + + for(int i = 0; i < 3; ++i) { + QuadFace* face = new QuadFace(4) ; + fixture.qfaces.push_back(face) ; + for(int j = 0; j < 4; ++j) { + face->edge[j] = edge[qface_to_edge[i][j]] ; + } + prism->setFace(i, face) ; + } + + fixture.root = prism ; +} + +/// Add an owned general Face to a general-cell fixture. +Face* add_general_face(GeneralFixture& fixture, + const std::vector& edges, + const std::vector& need_reverse) { + Face* face = new Face(int(edges.size())) ; + fixture.faces.push_back(face) ; + for(size_t i = 0; i < edges.size(); ++i) { + face->edge[i] = edges[i] ; + face->needReverse[i] = need_reverse[i] ; + } + return face ; +} + +/// Build a tetrahedron represented by the general Cell path. +void build_tetra_cell(GeneralFixture& fixture) { + Node* node[4] ; + node[0] = add_node(fixture, 0.0, 0.0, 0.0) ; + node[1] = add_node(fixture, 1.0, 0.0, 0.0) ; + node[2] = add_node(fixture, 0.0, 1.0, 0.0) ; + node[3] = add_node(fixture, 0.0, 0.0, 1.0) ; + + Edge* edge[6] ; + edge[0] = add_edge(fixture, node[0], node[1]) ; + edge[1] = add_edge(fixture, node[1], node[2]) ; + edge[2] = add_edge(fixture, node[2], node[0]) ; + edge[3] = add_edge(fixture, node[0], node[3]) ; + edge[4] = add_edge(fixture, node[1], node[3]) ; + edge[5] = add_edge(fixture, node[2], node[3]) ; + + Face** face = new Face*[4] ; + face[0] = add_general_face(fixture, {edge[0], edge[1], edge[2]}, + {false, false, false}) ; + face[1] = add_general_face(fixture, {edge[0], edge[4], edge[3]}, + {false, false, true}) ; + face[2] = add_general_face(fixture, {edge[1], edge[5], edge[4]}, + {false, false, true}) ; + face[3] = add_general_face(fixture, {edge[2], edge[3], edge[5]}, + {false, false, true}) ; + + Node** cell_nodes = new Node*[4] ; + for(int i = 0; i < 4; ++i) { + cell_nodes[i] = node[i] ; + } + + Edge** cell_edges = new Edge*[6] ; + for(int i = 0; i < 6; ++i) { + cell_edges[i] = edge[i] ; + } + + char* face_orient = new char[4] ; + for(int i = 0; i < 4; ++i) { + face_orient[i] = 0 ; + } + + fixture.root = new Cell(4, 6, 4, cell_nodes, cell_edges, face, face_orient) ; +} + +/// Build a square pyramid represented by the general Cell path. +void build_pyramid_cell(GeneralFixture& fixture) { + Node* node[5] ; + node[0] = add_node(fixture, 0.0, 0.0, 0.0) ; + node[1] = add_node(fixture, 1.0, 0.0, 0.0) ; + node[2] = add_node(fixture, 1.0, 1.0, 0.0) ; + node[3] = add_node(fixture, 0.0, 1.0, 0.0) ; + node[4] = add_node(fixture, 0.5, 0.5, 1.0) ; + + Edge* edge[8] ; + edge[0] = add_edge(fixture, node[0], node[1]) ; + edge[1] = add_edge(fixture, node[1], node[2]) ; + edge[2] = add_edge(fixture, node[2], node[3]) ; + edge[3] = add_edge(fixture, node[3], node[0]) ; + edge[4] = add_edge(fixture, node[0], node[4]) ; + edge[5] = add_edge(fixture, node[1], node[4]) ; + edge[6] = add_edge(fixture, node[2], node[4]) ; + edge[7] = add_edge(fixture, node[3], node[4]) ; + + Face** face = new Face*[5] ; + face[0] = add_general_face(fixture, {edge[0], edge[1], edge[2], edge[3]}, + {false, false, false, false}) ; + face[1] = add_general_face(fixture, {edge[0], edge[5], edge[4]}, + {false, false, true}) ; + face[2] = add_general_face(fixture, {edge[1], edge[6], edge[5]}, + {false, false, true}) ; + face[3] = add_general_face(fixture, {edge[2], edge[7], edge[6]}, + {false, false, true}) ; + face[4] = add_general_face(fixture, {edge[3], edge[4], edge[7]}, + {false, false, true}) ; + + Node** cell_nodes = new Node*[5] ; + for(int i = 0; i < 5; ++i) { + cell_nodes[i] = node[i] ; + } + + Edge** cell_edges = new Edge*[8] ; + for(int i = 0; i < 8; ++i) { + cell_edges[i] = edge[i] ; + } + + char* face_orient = new char[5] ; + for(int i = 0; i < 5; ++i) { + face_orient[i] = 0 ; + } + + fixture.root = new Cell(5, 8, 5, cell_nodes, cell_edges, face, face_orient) ; +} diff --git a/quickTest/FVMAdaptTests/Core/fvmadapt_core.h b/quickTest/FVMAdaptTests/Core/fvmadapt_core.h new file mode 100644 index 00000000..20162ea8 --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/fvmadapt_core.h @@ -0,0 +1,162 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#ifndef FVMADAPT_CORE_H +#define FVMADAPT_CORE_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +/// FVMAdapt library functions used directly by these core tests. +void extract_quad_edge(const std::vector& facePlan, + std::vector& edgePlan, + unsigned int dd) ; +std::vector extract_prism_face(const std::vector& cellPlan, int dd) ; +std::vector transfer_plan_g2q(std::vector& facePlan) ; +std::vector transfer_plan_q2g(const std::vector& facePlan) ; + +/// Owns the unit hexahedron used by the HexCell tests. +struct HexFixture { + HexFixture() ; + ~HexFixture() ; + + std::list nodes ; + std::list edges ; + std::list faces ; + HexCell* root ; +} ; + +/// Owns the unit triangular prism used by the Prism tests. +struct PrismFixture { + PrismFixture() ; + ~PrismFixture() ; + + std::list nodes ; + std::list edges ; + std::list qfaces ; + std::list gfaces ; + Prism* root ; +} ; + +/// Owns one general Cell shape used by the Cell/DiamondCell tests. +struct GeneralFixture { + GeneralFixture() ; + ~GeneralFixture() ; + + std::list nodes ; + std::list edges ; + std::list faces ; + Cell* root ; +} ; + +typedef void (*GeneralBuilder)(GeneralFixture&) ; + +/// Records the observable result of replaying one cell plan. +struct CaseResult { + std::string shape ; + std::string case_name ; + std::vector input_plan ; + std::vector canonical_plan ; + int counted_leaves ; + int resplit_leaves ; + int sorted_leaves ; + int point_count ; + std::string vtk_file ; +} ; + +/// Build a unit hexahedron test topology. +void build_unit_hex(HexFixture& fixture) ; + +/// Build a unit triangular-prism test topology. +void build_unit_prism(PrismFixture& fixture) ; + +/// Build a tetrahedron as a general Cell test topology. +void build_tetra_cell(GeneralFixture& fixture) ; + +/// Build a pyramid as a general Cell test topology. +void build_pyramid_cell(GeneralFixture& fixture) ; + +/// Format a split plan as a compact integer list. +std::string plan_string(const std::vector& plan) ; + +/// Create one output directory if it does not already exist. +void make_directory(const std::string& path) ; + +/// Join a directory and file name without normalizing the path. +std::string join_path(const std::string& dir, const std::string& file) ; + +/// Check fixed general-face and quad-face transfer round trips. +void check_plan_transfer_round_trips() ; + +/// Replay the HexCell plan cases and return their observed counts. +std::vector run_hex_cell_plan_tests(bool write_example_files, + const std::string& output_dir) ; + +/// Replay the Prism plan cases and return their observed counts. +std::vector run_prism_cell_plan_tests(bool write_example_files, + const std::string& output_dir) ; + +/// Replay the general tetrahedron plan cases and return their observed counts. +std::vector run_tetra_cell_plan_tests(bool write_example_files, + const std::string& output_dir) ; + +/// Replay the general pyramid plan cases and return their observed counts. +std::vector run_pyramid_cell_plan_tests(bool write_example_files, + const std::string& output_dir) ; + +/// Replay all cell plan cases and return their observed counts. +std::vector run_cell_plan_tests(bool write_example_files, + const std::string& output_dir) ; + +/// Check replay-trace leaf counts against the plan check results. +void check_plan_replay_counts(const std::vector& results) ; + +/// Write a simple VTK file for HexCell leaves. +int write_hex_vtk(const std::string& path, + const std::string& title, + int root_split_code, + const std::vector& leaves) ; + +/// Write a VTK wireframe for the owned fixture topology. +int write_wireframe_vtk(const std::string& path, + const std::string& title, + std::list& root_nodes, + std::list& root_edges) ; + +/// Write the breadth-first replay trace artifact. +void write_plan_replay(const std::string& output_dir, + const std::vector& results) ; + +/// Write all optional human-inspection artifacts for the plan cases. +void write_example_artifacts(const std::string& output_dir, + const std::vector& results) ; + +#endif diff --git a/quickTest/FVMAdaptTests/Core/plan_checks.cc b/quickTest/FVMAdaptTests/Core/plan_checks.cc new file mode 100644 index 00000000..fb8684b2 --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/plan_checks.cc @@ -0,0 +1,480 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#include "fvmadapt_core.h" + +namespace { + +/// One split-plan case and its expected canonical form. +struct PlanCase { + PlanCase(const std::string& n, const std::vector& p, + const std::vector& c, bool example): + name(n), plan(p), canonical(c), write_example(example) {} + + std::string name ; + std::vector plan ; + std::vector canonical ; + bool write_example ; +} ; + +/// Build a one-entry plan for a root split code. +std::vector root_plan(int split_code) { + return std::vector(1, char(split_code)) ; +} + +/// Return the canonical plan for one root split code. +std::vector canonical_root_plan(int split_code) { + if(split_code == 0) { + return std::vector() ; + } + return root_plan(split_code) ; +} + +/// Build a filesystem-safe token for generated case artifacts. +std::string plan_file_token(const std::string& shape, const std::string& name) { + return shape + "_" + name ; +} + +/// Return the first split code, or zero for an empty plan. +int root_split_code(const std::vector& plan) { + return plan.empty() ? 0 : int(plan[0]) ; +} + +/// Replay one HexCell plan and check count and canonical-plan invariants. +CaseResult run_hex_case(const PlanCase& plan_case, + bool write_example_files, + const std::string& output_dir) { + HexFixture fixture ; + build_unit_hex(fixture) ; + + std::vector resplit_leaves ; + fixture.root->resplit(plan_case.plan, fixture.nodes, fixture.edges, + fixture.faces, resplit_leaves) ; + + std::list sorted_leaves ; + fixture.root->sort_leaves(sorted_leaves) ; + + HexCell counter ; + const int counted = counter.num_fine_cells(plan_case.plan) ; + + if(int(resplit_leaves.size()) != counted) { + std::ostringstream msg ; + msg << "hex plan " << plan_case.name << " produced " + << resplit_leaves.size() << " resplit leaves, counted " << counted ; + throw std::runtime_error(msg.str()) ; + } + + if(int(sorted_leaves.size()) != counted) { + std::ostringstream msg ; + msg << "hex plan " << plan_case.name << " produced " + << sorted_leaves.size() << " sorted leaves, counted " << counted ; + throw std::runtime_error(msg.str()) ; + } + + const std::vector round_trip = fixture.root->make_cellplan() ; + if(round_trip != plan_case.canonical) { + std::ostringstream msg ; + msg << "hex plan " << plan_case.name << " round-tripped as " + << plan_string(round_trip) << ", expected " + << plan_string(plan_case.canonical) ; + throw std::runtime_error(msg.str()) ; + } + + CaseResult result ; + result.shape = "hex" ; + result.case_name = plan_case.name ; + result.input_plan = plan_case.plan ; + result.canonical_plan = round_trip ; + result.counted_leaves = counted ; + result.resplit_leaves = int(resplit_leaves.size()) ; + result.sorted_leaves = int(sorted_leaves.size()) ; + result.point_count = 0 ; + + if(write_example_files && plan_case.write_example) { + std::ostringstream name ; + name << plan_file_token(result.shape, plan_case.name) << ".vtk" ; + result.vtk_file = join_path(output_dir, name.str()) ; + std::ostringstream title ; + title << "FVMAdapt HexCell " << plan_case.name + << " plan " << plan_string(plan_case.plan) ; + result.point_count = write_hex_vtk(result.vtk_file, title.str(), + root_split_code(plan_case.plan), + resplit_leaves) ; + } + + return result ; +} + +/// Run one Prism split plan through the real Prism library paths. This checks +/// that resplit(), sort_leaves(), empty_resplit(), and make_cellplan() agree on +/// the leaf count and canonical plan; example mode also writes a VTK wireframe. +CaseResult run_prism_case(const PlanCase& plan_case, bool write_example_files, + const std::string& output_dir) { + PrismFixture fixture ; + build_unit_prism(fixture) ; + + std::vector resplit_leaves ; + fixture.root->resplit(plan_case.plan, fixture.nodes, fixture.edges, + fixture.qfaces, fixture.gfaces, resplit_leaves) ; + + std::list sorted_leaves ; + fixture.root->sort_leaves(sorted_leaves) ; + + PrismFixture counter ; + build_unit_prism(counter) ; + const int counted = counter.root->empty_resplit(plan_case.plan) ; + + if(int(resplit_leaves.size()) != counted) { + std::ostringstream msg ; + msg << "prism plan " << plan_case.name << " produced " + << resplit_leaves.size() << " resplit leaves, counted " << counted ; + throw std::runtime_error(msg.str()) ; + } + + if(int(sorted_leaves.size()) != counted) { + std::ostringstream msg ; + msg << "prism plan " << plan_case.name << " produced " + << sorted_leaves.size() << " sorted leaves, counted " << counted ; + throw std::runtime_error(msg.str()) ; + } + + const std::vector round_trip = fixture.root->make_cellplan() ; + if(round_trip != plan_case.canonical) { + std::ostringstream msg ; + msg << "prism plan " << plan_case.name << " round-tripped as " + << plan_string(round_trip) << ", expected " + << plan_string(plan_case.canonical) ; + throw std::runtime_error(msg.str()) ; + } + + CaseResult result ; + result.shape = "prism" ; + result.case_name = plan_case.name ; + result.input_plan = plan_case.plan ; + result.canonical_plan = round_trip ; + result.counted_leaves = counted ; + result.resplit_leaves = int(resplit_leaves.size()) ; + result.sorted_leaves = int(sorted_leaves.size()) ; + result.point_count = 0 ; + + if(write_example_files && plan_case.write_example) { + std::ostringstream name ; + name << plan_file_token(result.shape, plan_case.name) << "_wire.vtk" ; + result.vtk_file = join_path(output_dir, name.str()) ; + std::ostringstream title ; + title << "FVMAdapt Prism " << plan_case.name + << " plan " << plan_string(plan_case.plan) << " wireframe" ; + result.point_count = write_wireframe_vtk(result.vtk_file, title.str(), + fixture.nodes, fixture.edges) ; + } + + return result ; +} + +/// Replay one general Cell plan and check count and canonical-plan invariants. +CaseResult run_general_case(const std::string& shape, + const std::string& title_shape, + GeneralBuilder build_cell, + const PlanCase& plan_case, + bool write_example_files, + const std::string& output_dir) { + GeneralFixture fixture ; + build_cell(fixture) ; + + std::vector resplit_leaves ; + if(!plan_case.plan.empty()) { + fixture.root->resplit(plan_case.plan, + fixture.nodes, + fixture.edges, + fixture.faces, + resplit_leaves) ; + } + + std::list sorted_leaves ; + if(!plan_case.plan.empty()) { + fixture.root->sort_leaves(sorted_leaves) ; + } + + GeneralFixture counter ; + build_cell(counter) ; + const int counted = counter.root->empty_resplit(plan_case.plan) ; + const int resplit_count = plan_case.plan.empty() ? 1 : int(resplit_leaves.size()) ; + const int sorted_count = plan_case.plan.empty() ? 1 : int(sorted_leaves.size()) ; + + if(resplit_count != counted) { + std::ostringstream msg ; + msg << title_shape << " plan " << plan_case.name << " produced " + << resplit_count << " resplit leaves, counted " << counted ; + throw std::runtime_error(msg.str()) ; + } + + if(sorted_count != counted) { + std::ostringstream msg ; + msg << title_shape << " plan " << plan_case.name << " produced " + << sorted_count << " sorted leaves, counted " << counted ; + throw std::runtime_error(msg.str()) ; + } + + const std::vector round_trip = fixture.root->make_cellplan() ; + if(round_trip != plan_case.canonical) { + std::ostringstream msg ; + msg << title_shape << " plan " << plan_case.name << " round-tripped as " + << plan_string(round_trip) << ", expected " + << plan_string(plan_case.canonical) ; + throw std::runtime_error(msg.str()) ; + } + + CaseResult result ; + result.shape = shape ; + result.case_name = plan_case.name ; + result.input_plan = plan_case.plan ; + result.canonical_plan = round_trip ; + result.counted_leaves = counted ; + result.resplit_leaves = resplit_count ; + result.sorted_leaves = sorted_count ; + result.point_count = 0 ; + + if(write_example_files && plan_case.write_example) { + std::ostringstream name ; + name << plan_file_token(shape, plan_case.name) << "_wire.vtk" ; + result.vtk_file = join_path(output_dir, name.str()) ; + std::ostringstream title ; + title << "FVMAdapt " << title_shape << " " << plan_case.name + << " plan " << plan_string(plan_case.plan) + << " wireframe" ; + result.point_count = write_wireframe_vtk(result.vtk_file, title.str(), + fixture.nodes, fixture.edges) ; + } + + return result ; +} + +/// Return representative HexCell split-plan cases. +std::vector hex_plan_cases() { + std::vector cases ; + for(int split_code = 0; split_code <= 7; ++split_code) { + std::ostringstream name ; + name << "split_" << split_code ; + cases.push_back(PlanCase(name.str(), + root_plan(split_code), + canonical_root_plan(split_code), + true)) ; + } + + cases.push_back(PlanCase("split_7_trailing_zeros", + {char(7), char(0), char(0), char(0), char(0), + char(0), char(0), char(0), char(0)}, + {char(7)}, + false)) ; + cases.push_back(PlanCase("split_7_child0_z", + {char(7), char(1)}, + {char(7), char(1)}, + true)) ; + cases.push_back(PlanCase("split_7_child7_z", + {char(7), char(0), char(0), char(0), char(0), + char(0), char(0), char(0), char(1)}, + {char(7), char(0), char(0), char(0), char(0), + char(0), char(0), char(0), char(1)}, + true)) ; + cases.push_back(PlanCase("split_4_child0_yz", + {char(4), char(3)}, + {char(4), char(3)}, + true)) ; + return cases ; +} + +/// Return representative Prism split-plan cases. +std::vector prism_plan_cases() { + std::vector cases ; + for(int split_code = 0; split_code <= 3; ++split_code) { + std::ostringstream name ; + name << "split_" << split_code ; + cases.push_back(PlanCase(name.str(), + root_plan(split_code), + canonical_root_plan(split_code), + true)) ; + } + + cases.push_back(PlanCase("split_3_trailing_zeros", + {char(3), char(0), char(0), char(0), char(0), + char(0), char(0)}, + {char(3)}, + false)) ; + cases.push_back(PlanCase("split_3_child0_z", + {char(3), char(1)}, + {char(3), char(1)}, + true)) ; + cases.push_back(PlanCase("split_2_child0_z", + {char(2), char(1)}, + {char(2), char(1)}, + true)) ; + return cases ; +} + +/// Return representative general Cell split-plan cases. +std::vector general_plan_cases() { + std::vector cases ; + cases.push_back(PlanCase("split_0", std::vector(), std::vector(), true)) ; + cases.push_back(PlanCase("split_1", {char(1)}, {char(1)}, true)) ; + cases.push_back(PlanCase("split_1_trailing_zeros", + {char(1), char(0), char(0), char(0), char(0)}, + {char(1)}, + false)) ; + cases.push_back(PlanCase("split_1_child0", + {char(1), char(1)}, + {char(1), char(1)}, + true)) ; + return cases ; +} + +} // namespace + +/// Check that general-face and quad-face plan conversions preserve splits. +void check_plan_transfer_round_trips() { + const std::vector > general_face_plans = { + std::vector(), + {char(1)}, + {char(1), char(1)}, + {char(1), char(0), char(1)} + } ; + + for(size_t i = 0; i < general_face_plans.size(); ++i) { + std::vector general_plan = general_face_plans[i] ; + Face canonical_face(4) ; + canonical_face.empty_resplit(general_plan) ; + const std::vector canonical = canonical_face.make_faceplan() ; + const std::vector quad_plan = transfer_plan_g2q(general_plan) ; + const std::vector round_trip = transfer_plan_q2g(quad_plan) ; + if(round_trip != canonical) { + std::ostringstream msg ; + msg << "general face plan " << plan_string(general_face_plans[i]) + << " transferred through quad as " << plan_string(round_trip) + << ", expected " << plan_string(canonical) ; + throw std::runtime_error(msg.str()) ; + } + } + + const std::vector > quad_face_plans = { + std::vector(), + {char(3)}, + {char(3), char(3)}, + {char(3), char(0), char(0), char(0), char(3)} + } ; + + for(size_t i = 0; i < quad_face_plans.size(); ++i) { + std::vector general_plan = transfer_plan_q2g(quad_face_plans[i]) ; + const std::vector round_trip = transfer_plan_g2q(general_plan) ; + if(round_trip != quad_face_plans[i]) { + std::ostringstream msg ; + msg << "quad face plan " << plan_string(quad_face_plans[i]) + << " transferred through general as " << plan_string(round_trip) + << ", expected " << plan_string(quad_face_plans[i]) ; + throw std::runtime_error(msg.str()) ; + } + } +} + +/// Run all HexCell plan checks. +std::vector run_hex_cell_plan_tests(bool write_example_files, + const std::string& output_dir) { + std::vector results ; + + const std::vector hex_cases = hex_plan_cases() ; + for(size_t i = 0; i < hex_cases.size(); ++i) { + results.push_back(run_hex_case(hex_cases[i], + write_example_files, + output_dir)) ; + } + + return results ; +} + +/// Run all Prism plan checks. +std::vector run_prism_cell_plan_tests(bool write_example_files, + const std::string& output_dir) { + std::vector results ; + + const std::vector prism_cases = prism_plan_cases() ; + for(size_t i = 0; i < prism_cases.size(); ++i) { + results.push_back(run_prism_case(prism_cases[i], + write_example_files, + output_dir)) ; + } + + return results ; +} + +/// Run all general tetrahedron plan checks. +std::vector run_tetra_cell_plan_tests(bool write_example_files, + const std::string& output_dir) { + std::vector results ; + + const std::vector general_cases = general_plan_cases() ; + for(size_t i = 0; i < general_cases.size(); ++i) { + results.push_back(run_general_case("general_tet", + "general tetra", + build_tetra_cell, + general_cases[i], + write_example_files, + output_dir)) ; + } + + return results ; +} + +/// Run all general pyramid plan checks. +std::vector run_pyramid_cell_plan_tests(bool write_example_files, + const std::string& output_dir) { + std::vector results ; + + const std::vector general_cases = general_plan_cases() ; + for(size_t i = 0; i < general_cases.size(); ++i) { + results.push_back(run_general_case("general_pyramid", + "general pyramid", + build_pyramid_cell, + general_cases[i], + write_example_files, + output_dir)) ; + } + + return results ; +} + +/// Run every cell-family plan check. +std::vector run_cell_plan_tests(bool write_example_files, + const std::string& output_dir) { + std::vector results ; + + std::vector group = + run_hex_cell_plan_tests(write_example_files, output_dir) ; + results.insert(results.end(), group.begin(), group.end()) ; + + group = run_prism_cell_plan_tests(write_example_files, output_dir) ; + results.insert(results.end(), group.begin(), group.end()) ; + + group = run_tetra_cell_plan_tests(write_example_files, output_dir) ; + results.insert(results.end(), group.begin(), group.end()) ; + + group = run_pyramid_cell_plan_tests(write_example_files, output_dir) ; + results.insert(results.end(), group.begin(), group.end()) ; + + return results ; +} diff --git a/quickTest/FVMAdaptTests/Core/replay_trace.cc b/quickTest/FVMAdaptTests/Core/replay_trace.cc new file mode 100644 index 00000000..7c09af4a --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/replay_trace.cc @@ -0,0 +1,415 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#include "fvmadapt_core.h" + +namespace { + +/// Cell-family selector for plan replay. +enum TraceKind { + TRACE_HEX, + TRACE_PRISM, + TRACE_GENERAL +} ; + +/// Queue state for one node in the replayed cell tree. +struct TraceState { + int id ; + int parent_id ; + int child_slot ; + int depth ; + int local_fold ; + std::string path ; +} ; + +/// Output row for one breadth-first replay step. +struct TraceRow { + int id ; + int parent_id ; + int child_slot ; + int depth ; + int local_fold ; + std::string path ; + int plan_index ; + bool explicit_code ; + int code ; + int child_count ; + int first_child_id ; + int leaf_id ; +} ; + +/// Complete replay result for one cell plan. +struct PlanReplayTrace { + std::vector rows ; + int consumed_plan_entries ; + int leaf_count ; +} ; + +/// Format the unused suffix of a plan vector. +std::string plan_tail_string(const std::vector& plan, int start) { + if(start >= int(plan.size())) { + return "[]" ; + } + std::vector tail ; + for(size_t i = size_t(start); i < plan.size(); ++i) { + tail.push_back(plan[i]) ; + } + return plan_string(tail) ; +} + +/// Return the number of children created by one HexCell split code. +int hex_trace_child_count(int code) { + switch(code) { + case 0: + return 0 ; + case 1: + case 2: + case 4: + return 2 ; + case 3: + case 5: + case 6: + return 4 ; + case 7: + return 8 ; + default: + break ; + } + + std::ostringstream msg ; + msg << "unsupported hex plan code " << code ; + throw std::runtime_error(msg.str()) ; +} + +/// Return the number of children created by one Prism split code. +int prism_trace_child_count(int code, int nfold) { + switch(code) { + case 0: + return 0 ; + case 1: + return 2 ; + case 2: + return nfold ; + case 3: + return 2*nfold ; + default: + break ; + } + + std::ostringstream msg ; + msg << "unsupported prism plan code " << code ; + throw std::runtime_error(msg.str()) ; +} + +/// Return the number of children created by one general Cell or DiamondCell code. +int general_trace_child_count(int code, + int local_fold, + int depth, + const std::vector& root_child_folds) { + switch(code) { + case 0: + return 0 ; + case 1: + return depth == 0 ? int(root_child_folds.size()) : 2*local_fold + 2 ; + default: + break ; + } + + std::ostringstream msg ; + msg << "unsupported general-cell plan code " << code ; + throw std::runtime_error(msg.str()) ; +} + +/// Dispatch child-count rules by cell family. +int trace_child_count(TraceKind kind, + int code, + int local_fold, + int depth, + const std::vector& root_child_folds) { + switch(kind) { + case TRACE_HEX: + return hex_trace_child_count(code) ; + case TRACE_PRISM: + return prism_trace_child_count(code, local_fold) ; + case TRACE_GENERAL: + return general_trace_child_count(code, local_fold, depth, root_child_folds) ; + } + throw std::runtime_error("unsupported trace kind") ; +} + +/// Return the local fold value for each replayed child. +std::vector trace_child_folds(TraceKind kind, + int code, + int local_fold, + int depth, + const std::vector& root_child_folds) { + const int count = + trace_child_count(kind, code, local_fold, depth, root_child_folds) ; + std::vector folds(count, -1) ; + + if(kind == TRACE_HEX || code == 0) { + return folds ; + } + + if(kind == TRACE_PRISM) { + if(code == 1) { + for(int i = 0; i < count; ++i) { + folds[i] = local_fold ; + } + } else { + for(int i = 0; i < count; ++i) { + folds[i] = 4 ; + } + } + return folds ; + } + + if(depth == 0) { + return root_child_folds ; + } + + if(count >= 1) { + folds[0] = local_fold ; + } + if(count >= 2) { + folds[1] = local_fold ; + } + for(int i = 2; i < count; ++i) { + folds[i] = 3 ; + } + return folds ; +} + +/// Name a split code in the replay trace output. +std::string code_meaning(TraceKind kind, int code, int depth) { + switch(kind) { + case TRACE_HEX: + switch(code) { + case 0: return "leaf" ; + case 1: return "split_z" ; + case 2: return "split_y" ; + case 3: return "split_yz" ; + case 4: return "split_x" ; + case 5: return "split_xz" ; + case 6: return "split_xy" ; + case 7: return "split_xyz" ; + default: return "unknown" ; + } + case TRACE_PRISM: + switch(code) { + case 0: return "leaf" ; + case 1: return "split_z" ; + case 2: return "split_xy_ring" ; + case 3: return "split_xy_and_z" ; + default: return "unknown" ; + } + case TRACE_GENERAL: + switch(code) { + case 0: return "leaf" ; + case 1: return depth == 0 ? "split_cell_to_diamonds" + : "split_diamond_isotropic" ; + default: return "unknown" ; + } + } + return "unknown" ; +} + +/// Replay a breadth-first split plan without calling the FVMAdapt cell classes. +PlanReplayTrace make_plan_replay_trace(TraceKind kind, + const std::vector& plan, + int root_fold, + const std::vector& root_child_folds) { + std::vector queue ; + queue.push_back({0, -1, -1, 0, root_fold, "R"}) ; + + PlanReplayTrace trace ; + trace.consumed_plan_entries = 0 ; + trace.leaf_count = 0 ; + + for(size_t cursor = 0; cursor < queue.size(); ++cursor) { + const TraceState state = queue[cursor] ; + const bool explicit_code = trace.consumed_plan_entries < int(plan.size()) ; + const int plan_index = explicit_code ? trace.consumed_plan_entries : -1 ; + const int code = explicit_code ? int(plan[trace.consumed_plan_entries]) : 0 ; + if(explicit_code) { + ++trace.consumed_plan_entries ; + } + + const int child_count = trace_child_count(kind, + code, + state.local_fold, + state.depth, + root_child_folds) ; + TraceRow row ; + row.id = state.id ; + row.parent_id = state.parent_id ; + row.child_slot = state.child_slot ; + row.depth = state.depth ; + row.local_fold = state.local_fold ; + row.path = state.path ; + row.plan_index = plan_index ; + row.explicit_code = explicit_code ; + row.code = code ; + row.child_count = child_count ; + row.first_child_id = child_count == 0 ? -1 : int(queue.size()) ; + row.leaf_id = child_count == 0 ? ++trace.leaf_count : 0 ; + trace.rows.push_back(row) ; + + const std::vector child_folds = + trace_child_folds(kind, code, state.local_fold, state.depth, root_child_folds) ; + for(int child = 0; child < child_count; ++child) { + std::ostringstream path ; + path << state.path << "." << child ; + TraceState child_state ; + child_state.id = int(queue.size()) ; + child_state.parent_id = state.id ; + child_state.child_slot = child ; + child_state.depth = state.depth + 1 ; + child_state.local_fold = child_folds[child] ; + child_state.path = path.str() ; + queue.push_back(child_state) ; + } + } + + return trace ; +} + +/// Map a CaseResult shape name to the replay model. +TraceKind trace_kind_for_shape(const std::string& shape) { + if(shape == "hex") { + return TRACE_HEX ; + } + if(shape == "prism") { + return TRACE_PRISM ; + } + return TRACE_GENERAL ; +} + +/// Return the root fold used by prism and general-cell replay. +int root_fold_for_shape(const std::string& shape) { + if(shape == "prism") { + return 3 ; + } + if(shape == "general_tet") { + return 4 ; + } + if(shape == "general_pyramid") { + return 5 ; + } + return -1 ; +} + +/// Return the DiamondCell folds created by a general-cell root split. +std::vector root_child_folds_for_shape(const std::string& shape) { + if(shape == "general_tet") { + return {3, 3, 3, 3} ; + } + if(shape == "general_pyramid") { + return {3, 3, 3, 3, 4} ; + } + return std::vector() ; +} + +/// Build a replay trace and check its leaf count against the plan result. +PlanReplayTrace checked_plan_replay_trace(const CaseResult& result) { + const TraceKind kind = trace_kind_for_shape(result.shape) ; + const PlanReplayTrace trace = + make_plan_replay_trace(kind, + result.input_plan, + root_fold_for_shape(result.shape), + root_child_folds_for_shape(result.shape)) ; + + if(trace.leaf_count != result.counted_leaves) { + std::ostringstream msg ; + msg << "plan replay trace for " << result.shape << " " << result.case_name + << " has " << trace.leaf_count << " leaves, counted " + << result.counted_leaves ; + throw std::runtime_error(msg.str()) ; + } + + return trace ; +} + +/// Write the replay trace rows for one plan case. +void write_one_plan_replay(std::ofstream& out, const CaseResult& result) { + const TraceKind kind = trace_kind_for_shape(result.shape) ; + const PlanReplayTrace trace = checked_plan_replay_trace(result) ; + + out << "case " << result.shape << " " << result.case_name << "\n" ; + out << " input_cell_plan " << plan_string(result.input_plan) << "\n" ; + out << " canonical_cell_plan " << plan_string(result.canonical_plan) << "\n" ; + out << " consumed_plan_entries " << trace.consumed_plan_entries + << " of " << result.input_plan.size() << "\n" ; + if(trace.consumed_plan_entries < int(result.input_plan.size())) { + out << " unused_plan_tail " + << plan_tail_string(result.input_plan, trace.consumed_plan_entries) + << "\n" ; + } + out << " replay_rows\n" ; + out << " # id parent child depth path local_fold plan_index source code " + << "child_count first_child leaf meaning\n" ; + for(size_t i = 0; i < trace.rows.size(); ++i) { + const TraceRow& row = trace.rows[i] ; + out << " node " + << row.id << " " + << row.parent_id << " " + << row.child_slot << " " + << row.depth << " " + << row.path << " " + << row.local_fold << " " + << row.plan_index << " " + << (row.explicit_code ? "explicit" : "default") << " " + << row.code << " " + << row.child_count << " " + << row.first_child_id << " " + << row.leaf_id << " " + << code_meaning(kind, row.code, row.depth) << "\n" ; + } +} + +} // namespace + +/// Write breadth-first replay traces for all plan cases. +void write_plan_replay(const std::string& output_dir, + const std::vector& results) { + const std::string path = join_path(output_dir, "core_plan_replay.dat") ; + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open plan replay output file") ; + } + + out << "# FVMAdapt core plan replay trace\n" ; + out << "# Each row is the breadth-first visit of one cell-tree node.\n" ; + out << "# source=default means the plan vector was exhausted and replay used code 0.\n" ; + out << "# local_fold is prism nfold, general Cell node count at depth 0, or\n" ; + out << "# general DiamondCell nfold below depth 0. Hex rows use -1.\n" ; + for(size_t i = 0; i < results.size(); ++i) { + write_one_plan_replay(out, results[i]) ; + out << "\n" ; + } +} + +/// Check replay-trace leaf counts for all plan cases. +void check_plan_replay_counts(const std::vector& results) { + for(size_t i = 0; i < results.size(); ++i) { + checked_plan_replay_trace(results[i]) ; + } +} diff --git a/quickTest/FVMAdaptTests/Core/test_fvmadapt_core.cc b/quickTest/FVMAdaptTests/Core/test_fvmadapt_core.cc new file mode 100644 index 00000000..8d573625 --- /dev/null +++ b/quickTest/FVMAdaptTests/Core/test_fvmadapt_core.cc @@ -0,0 +1,474 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#include "fvmadapt_core.h" + +#define DOCTEST_CONFIG_IMPLEMENT +#include + +#include +#include +#include + +namespace { + +// Keeps test cases readable while plan storage remains char-based. For tests +// writing the split codes as integers is more readable, so this function +// just bridges that gap between readability and the actual library codes. +std::vector plan(std::initializer_list codes) { + std::vector result ; + for(std::initializer_list::const_iterator i = codes.begin() ; + i != codes.end(); ++i) { + result.push_back(char(*i)) ; + } + return result ; +} + +/// Print the custom inventory used by the Makefile list target. +void print_test_inventory(std::ostream& out) { + out << "FVMAdapt core checks\n" ; + out << " root split counts: HexCell, Prism, and general Cell\n" ; + out << " plan replay: omitted children, trailing zeros, and canonical plans\n" ; + out << " plan-transfer: transfer_plan_g2q/transfer_plan_q2g round trips\n" ; + out << " plan projection: cell-to-face and face-to-edge extraction\n" ; + out << " hex-cell plans: resplit, sort_leaves, num_fine_cells, make_cellplan\n" ; + out << " prism-cell plans: resplit, sort_leaves, empty_resplit, make_cellplan\n" ; + out << " general-cell plans: Cell/DiamondCell resplit, sort_leaves, " + << "empty_resplit, make_cellplan\n" ; + out << " tree state: child tags, derefine checks, and derefine cleanup\n" ; + out << " replay traces: independent breadth-first leaf counts for all cases\n" ; + out << "\n" ; + out << "Example artifacts written by --write-dir\n" ; + out << " VTK/wireframe files for selected plans\n" ; + out << " core_split_summary.dat\n" ; + out << " core_plan_audit.dat\n" ; + out << " core_plan_replay.dat\n" ; +} + +/// Run the checks and then write the optional example artifacts. +void write_examples(const std::string& output_dir) { + make_directory(output_dir) ; + + const std::vector results = + run_cell_plan_tests(true, output_dir) ; + + check_plan_replay_counts(results) ; + write_example_artifacts(output_dir, results) ; +} + +/// Check the independent replay trace against the cell-plan results. +void check_replay_traces() { + const std::vector results = run_cell_plan_tests(false, "") ; + check_plan_replay_counts(results) ; +} + +} // namespace + + +/// Checks that a four-sided original mesh face keeps the same refined pattern +/// when its plan is translated between general Face and QuadFace formats. +/// Mixed hex/general interfaces use this so both cells split their copy of the +/// same original face consistently. +TEST_CASE("FVMAdapt face plans round-trip between general and quad encodings") { + CHECK_NOTHROW(check_plan_transfer_round_trips()) ; +} + + +/// A one-entry cell plan says how the parent cell itself is split. With no +/// child entries following it, every child is a leaf, so the library should +/// report exactly the child count implied by that root split code. +TEST_CASE("root split codes produce the expected fine-cell counts") { + // Hex root split codes produce the fixed HexCell leaf-count table. + const int hex_counts[8] = {1, 2, 2, 4, 2, 4, 4, 8} ; + for(int code = 0; code < 8; ++code) { + CAPTURE(code) ; + HexCell counter ; + CHECK(counter.num_fine_cells(plan({code})) == hex_counts[code]) ; + } + + // Prism root split codes produce the fixed Prism leaf-count table. + const int prism_counts[4] = {1, 2, 3, 6} ; + for(int code = 0; code < 4; ++code) { + CAPTURE(code) ; + Prism counter ; + CHECK(counter.empty_resplit(plan({code})) == prism_counts[code]) ; + } + + // A tetrahedron root split creates one DiamondCell child per original node. + GeneralFixture tetra ; + build_tetra_cell(tetra) ; + CHECK(tetra.root->empty_resplit(plan({1})) == tetra.root->numNode) ; + + // A pyramid root split follows the same general-cell child-count rule. + GeneralFixture pyramid ; + build_pyramid_cell(pyramid) ; + CHECK(pyramid.root->empty_resplit(plan({1})) == pyramid.root->numNode) ; +} + + +/// Checks the compact cell-plan convention for omitted child entries. When a +/// plan ends before every child has an explicit code, the missing children are +/// unsplit leaves, and make_cellplan() should preserve that compact form. +TEST_CASE("short cell plans treat omitted child entries as leaves") { + // Only one hex child is split explicitly; omitted siblings remain leaves. + HexFixture hex ; + build_unit_hex(hex) ; + std::vector hex_leaves ; + hex.root->resplit(plan({7, 1}), hex.nodes, hex.edges, hex.faces, hex_leaves) ; + CHECK(hex_leaves.size() == 9) ; + CHECK(hex.root->make_cellplan() == plan({7, 1})) ; + + // Prism plans use the same breadth-first convention for omitted children. + PrismFixture prism ; + build_unit_prism(prism) ; + std::vector prism_leaves ; + prism.root->resplit(plan({3, 1}), prism.nodes, prism.edges, prism.qfaces, + prism.gfaces, prism_leaves) ; + CHECK(prism_leaves.size() == 7) ; + CHECK(prism.root->make_cellplan() == plan({3, 1})) ; + + // General-cell plans also allow the trailing child entries to be implicit. + GeneralFixture tetra ; + build_tetra_cell(tetra) ; + std::vector tetra_leaves ; + tetra.root->resplit(plan({1, 1}), tetra.nodes, tetra.edges, tetra.faces, + tetra_leaves) ; + CHECK(tetra_leaves.size() > size_t(tetra.root->numNode)) ; + CHECK(tetra.root->make_cellplan() == plan({1, 1})) ; +} + + +/// Checks that explicit trailing leaf markers do not become part of the saved +/// cell plan. A producer may spell out every child of a split parent as `0`, +/// but make_cellplan() should serialize the same compact tree as if those leaf +/// children had been omitted. This keeps equivalent plans stable across +/// HexCell, Prism, and general Cell replay paths. +TEST_CASE("trailing zero child plans canonicalize to the split tree") { + // A fully split hex with all eight children marked as leaves saves as `{7}`. + HexFixture hex ; + build_unit_hex(hex) ; + std::vector hex_leaves ; + hex.root->resplit(plan({7, 0, 0, 0, 0, 0, 0, 0, 0}), + hex.nodes, + hex.edges, + hex.faces, + hex_leaves) ; + CHECK(hex.root->make_cellplan() == plan({7})) ; + + // Prism full-split plans trim the same explicit child-leaf markers. + PrismFixture prism ; + build_unit_prism(prism) ; + std::vector prism_leaves ; + prism.root->resplit(plan({3, 0, 0, 0, 0, 0, 0}), + prism.nodes, + prism.edges, + prism.qfaces, + prism.gfaces, + prism_leaves) ; + CHECK(prism.root->make_cellplan() == plan({3})) ; + + // General-cell plans also save only the parent split once children are leaves. + GeneralFixture pyramid ; + build_pyramid_cell(pyramid) ; + std::vector pyramid_leaves ; + pyramid.root->resplit(plan({1, 0, 0, 0, 0, 0}), + pyramid.nodes, + pyramid.edges, + pyramid.faces, + pyramid_leaves) ; + CHECK(pyramid.root->make_cellplan() == plan({1})) ; +} + + +TEST_CASE("general cells keep the empty plan as the unsplit-cell convention") { + // resplit(empty) leaves the output list empty for an unsplit general cell. + GeneralFixture tetra_for_resplit ; + build_tetra_cell(tetra_for_resplit) ; + std::vector leaves ; + tetra_for_resplit.root->resplit(std::vector(), + tetra_for_resplit.nodes, + tetra_for_resplit.edges, + tetra_for_resplit.faces, + leaves) ; + CHECK(leaves.empty()) ; + + // empty_resplit(empty) still counts the parent as one unsplit cell. + GeneralFixture tetra_for_count ; + build_tetra_cell(tetra_for_count) ; + CHECK(tetra_for_count.root->empty_resplit(std::vector()) == 1) ; + // make_cellplan() preserves the empty-plan representation. + CHECK(tetra_for_count.root->make_cellplan().empty()) ; +} + + +/// Checks the helpers that derive lower-dimensional plans from a cell or face +/// plan. Face-balancing code uses these projections to decide how much of a +/// cell split is visible on one shared parent face, and edge-balancing code +/// does the same for one edge of a quad face. These checks anchor a few small +/// mappings for hex faces, prism triangular/quad faces, and quad-face edges. +TEST_CASE("cell and face plans project to representative boundary plans") { + // An unsplit hex cell induces no split on any selected face. + CHECK(extract_hex_face(std::vector(), RIGHT).empty()) ; + + // A full hex split induces a full quad-face split on the right face. + CHECK(extract_hex_face(plan({7}), RIGHT) == plan({3})) ; + + // A one-direction hex split is visible on a side face it cuts. + CHECK(extract_hex_face(plan({1}), RIGHT) == plan({1})) ; + + // The same one-direction split does not cut the top face. + CHECK(extract_hex_face(plan({1}), UP).empty()) ; + + // An unsplit prism cell induces no split on the selected triangular face. + CHECK(extract_prism_face(std::vector(), 0).empty()) ; + + // A full prism split induces a one-level split on a triangular end face. + CHECK(extract_prism_face(plan({3}), 0) == plan({1})) ; + + // The same full prism split induces a full split on a quad side face. + CHECK(extract_prism_face(plan({3}), 2) == plan({3})) ; + + // Prism split code 1 leaves the triangular end face unsplit. + CHECK(extract_prism_face(plan({1}), 0).empty()) ; + + // Prism split code 1 is visible on the selected quad side face. + CHECK(extract_prism_face(plan({1}), 2) == plan({1})) ; + + // A full quad-face split cuts the selected edge. + std::vector edge_plan ; + extract_quad_edge(plan({3}), edge_plan, 0) ; + CHECK(edge_plan == plan({1})) ; + + // Quad-face split code 1 does not cut edge 0. + edge_plan.clear() ; + extract_quad_edge(plan({1}), edge_plan, 0) ; + CHECK(edge_plan.empty()) ; + + // The same quad-face split code does cut edge 1. + edge_plan.clear() ; + extract_quad_edge(plan({1}), edge_plan, 1) ; + CHECK(edge_plan == plan({1})) ; +} + + +/// Checks the smallest fixed mapping between the general Face plan encoding +/// and the QuadFace plan encoding. Mixed general/hex and general/prism paths +/// convert a four-sided general face into QuadFace form before merging face +/// plans. In that conversion, a one-level general Face split uses code `1`, +/// while the equivalent QuadFace split uses code `3`. +TEST_CASE("small face-plan transfers have fixed general and quad encodings") { + std::vector general_split = plan({1}) ; + + // General Face code 1 becomes QuadFace code 3 for the same four-way split. + CHECK(transfer_plan_g2q(general_split) == plan({3})) ; + + // QuadFace code 3 converts back to general Face code 1. + CHECK(transfer_plan_q2g(plan({3})) == plan({1})) ; +} + + +/// Checks the direct-tag derefinement predicate for split cells. A parent is +/// ready for `needDerefine_ctag()` only when its immediate children are tagged +/// with `2` and those children are leaves. If that test passes, `derefine()` +/// should collapse the parent back to an unsplit cell plan. +TEST_CASE("derefine checks require tagged immediate leaf children") { + // A split hex with every immediate child tagged 2 is ready to derefine. + HexFixture hex ; + build_unit_hex(hex) ; + std::vector hex_leaves ; + hex.root->resplit(plan({7}), hex.nodes, hex.edges, hex.faces, hex_leaves) ; + for(int child = 0; child < hex.root->numChildren(); ++child) { + hex.root->getChildCell(child)->setTag(char(2)) ; + } + CHECK(hex.root->needDerefine_ctag()) ; + + // derefine() removes the root split and returns the plan to unsplit. + hex.root->derefine() ; + CHECK(hex.root->getMySplitCode() == 0) ; + CHECK(hex.root->make_cellplan().empty()) ; + + // Tagged immediate children are not enough when one child is still split. + HexFixture nested_hex ; + build_unit_hex(nested_hex) ; + std::vector nested_hex_leaves ; + nested_hex.root->resplit(plan({7, 1}), + nested_hex.nodes, + nested_hex.edges, + nested_hex.faces, + nested_hex_leaves) ; + for(int child = 0; child < nested_hex.root->numChildren(); ++child) { + nested_hex.root->getChildCell(child)->setTag(char(2)) ; + } + CHECK_FALSE(nested_hex.root->needDerefine_ctag()) ; + + // Prism derefine readiness uses the same direct-tag and leaf-child rule. + PrismFixture prism ; + build_unit_prism(prism) ; + std::vector prism_leaves ; + prism.root->resplit(plan({3}), + prism.nodes, + prism.edges, + prism.qfaces, + prism.gfaces, + prism_leaves) ; + for(int child = 0; child < prism.root->numChildren(); ++child) { + prism.root->getChildCell(child)->setTag(char(2)) ; + } + CHECK(prism.root->needDerefine_ctag()) ; +} + + +/// Smoke-tests that representative split operations create usable child cells, +/// not just the right plan structure. The checks verify that the split returns +/// the expected number of non-null leaf pointers and that the resulting parent +/// still reports a finite, positive minimum edge length. +TEST_CASE("representative splits leave finite positive geometry") { + // A full hex split produces eight non-null leaves. + HexFixture hex ; + build_unit_hex(hex) ; + std::vector hex_leaves ; + hex.root->resplit(plan({7}), hex.nodes, hex.edges, hex.faces, hex_leaves) ; + REQUIRE(hex_leaves.size() == 8) ; + for(size_t i = 0; i < hex_leaves.size(); ++i) { + CAPTURE(i) ; + CHECK(hex_leaves[i] != 0) ; + } + // Split hex geometry still has a finite, positive minimum edge length. + const double hex_min_edge = hex.root->get_min_edge_length() ; + CHECK(std::isfinite(hex_min_edge)) ; + CHECK(hex_min_edge > 0.0) ; + + // A general-cell split produces one non-null child per original node. + GeneralFixture tetra ; + build_tetra_cell(tetra) ; + std::vector tetra_leaves ; + tetra.root->resplit(plan({1}), + tetra.nodes, + tetra.edges, + tetra.faces, + tetra_leaves) ; + REQUIRE(tetra_leaves.size() == size_t(tetra.root->numNode)) ; + for(size_t i = 0; i < tetra_leaves.size(); ++i) { + CAPTURE(i) ; + CHECK(tetra_leaves[i] != 0) ; + } + // Split general-cell geometry also keeps a finite, positive minimum edge. + const double tetra_min_edge = tetra.root->get_min_edge_length() ; + CHECK(std::isfinite(tetra_min_edge)) ; + CHECK(tetra_min_edge > 0.0) ; +} + + +/// Runs the representative HexCell plan table through the real HexCell replay +/// paths. Each case must give the same leaf count from resplit(), sort_leaves(), +/// and num_fine_cells(), and make_cellplan() must return the expected compact +/// canonical plan. +TEST_CASE("HexCell plans preserve resplit leaves, sorted leaves, and canonical plans") { + CHECK_NOTHROW(run_hex_cell_plan_tests(false, "")) ; +} + + +/// Runs the representative Prism plan table through the real Prism replay +/// paths. Each case must give the same leaf count from resplit(), sort_leaves(), +/// and empty_resplit(), and make_cellplan() must return the expected compact +/// canonical plan. +TEST_CASE("Prism plans preserve resplit leaves, sorted leaves, and canonical plans") { + CHECK_NOTHROW(run_prism_cell_plan_tests(false, "")) ; +} + + +/// Runs the representative general-cell plan table on a tetrahedron. This +/// checks that Cell/DiamondCell resplit(), sort_leaves(), empty_resplit(), and +/// make_cellplan() agree on the leaf count and canonical plan. +TEST_CASE("general tetrahedron plans preserve resplit leaves, sorted leaves, and canonical plans") { + CHECK_NOTHROW(run_tetra_cell_plan_tests(false, "")) ; +} + + +/// Runs the same general-cell plan table on a pyramid. This gives the generic +/// Cell/DiamondCell replay checks a second parent-cell topology. +TEST_CASE("general pyramid plans preserve resplit leaves, sorted leaves, and canonical plans") { + CHECK_NOTHROW(run_pyramid_cell_plan_tests(false, "")) ; +} + + +/// Checks the independent breadth-first replay tracer against the real cell +/// plan results. The tracer does not call HexCell, Prism, or Cell replay; it +/// reads the plan vector with the same child-count rules and verifies that its +/// leaf count matches the library result for every representative plan case. +TEST_CASE("plan replay traces reproduce the counted leaves for every cell case") { + CHECK_NOTHROW(check_replay_traces()) ; +} + + +int main(int argc, char** argv) { + bool write_example_files = false ; + bool list_tests = false ; + std::string output_dir = "output" ; + std::vector doctest_args ; + doctest_args.push_back(argv[0]) ; + + for(int i = 1; i < argc; ++i) { + const std::string arg(argv[i]) ; + if(arg == "--check-only") { + continue ; + } else if(arg == "--list") { + list_tests = true ; + } else if(arg == "--write-dir") { + if(i + 1 >= argc) { + std::cerr << "--write-dir requires a path\n" ; + std::cout << "FAILURE!\n" ; + return 1 ; + } + write_example_files = true ; + output_dir = argv[++i] ; + } else { + doctest_args.push_back(argv[i]) ; + } + } + + if(list_tests) { + print_test_inventory(std::cout) ; + return 0 ; + } + + doctest::Context context ; + context.applyCommandLine(int(doctest_args.size()), doctest_args.data()) ; + const int result = context.run() ; + + if(context.shouldExit() || result != 0) { + return result ; + } + + if(!write_example_files) { + return result ; + } + + try { + write_examples(output_dir) ; + std::cout << "SUCCESS!\n" ; + return result ; + } catch(const std::exception& error) { + std::cerr << error.what() << "\n" ; + std::cout << "FAILURE!\n" ; + return 1 ; + } +} diff --git a/quickTest/FVMAdaptTests/Illustrations/.gitignore b/quickTest/FVMAdaptTests/Illustrations/.gitignore new file mode 100644 index 00000000..627e799b --- /dev/null +++ b/quickTest/FVMAdaptTests/Illustrations/.gitignore @@ -0,0 +1,6 @@ +*.d +*.exe +*.log +*.o +output/ +TestResults diff --git a/quickTest/FVMAdaptTests/Illustrations/Makefile b/quickTest/FVMAdaptTests/Illustrations/Makefile new file mode 100644 index 00000000..1d57333a --- /dev/null +++ b/quickTest/FVMAdaptTests/Illustrations/Makefile @@ -0,0 +1,54 @@ +############################################################################### +# +# Copyright 2008-2026, Mississippi State University +# +# This file is part of the Loci Framework. +# +# The Loci Framework is free software: you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Loci Framework 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 +# Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# along with the Loci Framework. If not, see +# +############################################################################### + +include $(LOCI_BASE)/Loci.conf +include $(LOCI_BASE)/version.conf + +INCLUDES = -I../Core -I$(LOCI_BASE)/include +LIB_PATH = $(LOCI_BASE)/lib:$(LD_LIBRARY_PATH) +DY_PATH = $(LOCI_BASE)/lib:$(DYLD_LIBRARY_PATH) +SOURCES = fvmadapt_illustrations.cc ../Core/fixtures.cc +OBJS = fvmadapt_illustrations.o fixtures.o + +.PHONY: default examples clean distclean FRC + +default: examples + +examples: fvmadapt_illustrations.exe + @rm -fr output + @mkdir -p output + LD_LIBRARY_PATH=$(LIB_PATH) DYLD_LIBRARY_PATH=$(DY_PATH) ./fvmadapt_illustrations.exe --write-dir output + +fvmadapt_illustrations.exe: $(OBJS) $(LOCI_BASE)/lib/libfvmadaptfunc.$(LIB_SUFFIX) + $(LD) -o $@ $(OBJS) -L$(LOCI_BASE)/lib -lfvmadaptfunc $(LIBS) $(LDFLAGS) + +fvmadapt_illustrations.o: fvmadapt_illustrations.cc + $(CXX) $(COPT) $(DEFINES) $(LOCI_INCLUDES) $(INCLUDES) -o $@ -c $< + +fixtures.o: ../Core/fixtures.cc ../Core/fvmadapt_core.h + $(CXX) $(COPT) $(DEFINES) $(LOCI_INCLUDES) $(INCLUDES) -o $@ -c ../Core/fixtures.cc + +clean: + rm -fr *.o *.exe *.d *.log TestResults output + +distclean: clean + +FRC: diff --git a/quickTest/FVMAdaptTests/Illustrations/fvmadapt_illustrations.cc b/quickTest/FVMAdaptTests/Illustrations/fvmadapt_illustrations.cc new file mode 100644 index 00000000..8eb85fad --- /dev/null +++ b/quickTest/FVMAdaptTests/Illustrations/fvmadapt_illustrations.cc @@ -0,0 +1,1565 @@ +//############################################################################# +//# +//# Copyright 2008-2026, Mississippi State University +//# +//# This file is part of the Loci Framework. +//# +//# The Loci Framework is free software: you can redistribute it and/or modify +//# it under the terms of the Lesser GNU General Public License as published by +//# the Free Software Foundation, either version 3 of the License, or +//# (at your option) any later version. +//# +//# The Loci Framework 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 +//# Lesser GNU General Public License for more details. +//# +//# You should have received a copy of the Lesser GNU General Public License +//# along with the Loci Framework. If not, see +//# +//############################################################################# + +#include "../Core/fvmadapt_core.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +struct Point { + Point(): x(0.0), y(0.0), z(0.0) {} + Point(double x_in, double y_in, double z_in): x(x_in), y(y_in), z(z_in) {} + + double x ; + double y ; + double z ; +} ; + +struct HexBlock { + HexBlock(): root(0) {} + ~HexBlock() { + delete root ; + cleanup_list(nodes, edges, faces) ; + } + + std::list nodes ; + std::list edges ; + std::list faces ; + HexCell* root ; +} ; + +struct PrismBlock { + PrismBlock(): root(0) {} + ~PrismBlock() { + delete root ; + cleanup_list(nodes, edges) ; + cleanup_list(qfaces) ; + cleanup_list(gfaces) ; + } + + std::list nodes ; + std::list edges ; + std::list qfaces ; + std::list gfaces ; + Prism* root ; +} ; + +struct HexView { + HexView(HexCell* c, int source, int code): cell(c), source_cell(source), + root_split_code(code) {} + + HexCell* cell ; + int source_cell ; + int root_split_code ; +} ; + +struct QuadPatch { + QuadPatch(): u0(0.0), u1(1.0), v0(0.0), v1(1.0) {} + QuadPatch(double u0_in, double u1_in, double v0_in, double v1_in): + u0(u0_in), u1(u1_in), v0(v0_in), v1(v1_in) {} + + double u0 ; + double u1 ; + double v0 ; + double v1 ; +} ; + +std::vector plan(std::initializer_list codes) { + std::vector result ; + for(std::initializer_list::const_iterator code = codes.begin() ; + code != codes.end(); ++code) { + result.push_back(char(*code)) ; + } + return result ; +} + +std::string plan_string(const std::vector& codes) { + std::ostringstream out ; + out << "[" ; + for(size_t i = 0; i < codes.size(); ++i) { + if(i != 0) { + out << "," ; + } + out << int(codes[i]) ; + } + out << "]" ; + return out.str() ; +} + +void make_directory(const std::string& path) { + if(path.empty()) { + return ; + } + if(mkdir(path.c_str(), 0775) != 0 && errno != EEXIST) { + std::ostringstream msg ; + msg << "could not create directory '" << path << "': " + << std::strerror(errno) ; + throw std::runtime_error(msg.str()) ; + } +} + +void make_directories(const std::string& path) { + if(path.empty()) { + return ; + } + + std::string current ; + size_t start = 0 ; + if(path[0] == '/') { + current = "/" ; + start = 1 ; + } + + while(start <= path.size()) { + const size_t slash = path.find('/', start) ; + const std::string part = + path.substr(start, + slash == std::string::npos ? + std::string::npos : slash - start) ; + if(!part.empty() && part != ".") { + if(!current.empty() && current[current.size() - 1] != '/') { + current += "/" ; + } + current += part ; + make_directory(current) ; + } + if(slash == std::string::npos) { + break ; + } + start = slash + 1 ; + } +} + +std::string join_path(const std::string& dir, const std::string& file) { + if(dir.empty()) { + return file ; + } + if(dir[dir.size() - 1] == '/') { + return dir + file ; + } + return dir + "/" + file ; +} + +std::string output_group_dir(const std::string& output_dir, + const std::string& group) { + const std::string group_dir = join_path(output_dir, group) ; + make_directories(group_dir) ; + return group_dir ; +} + +Point mix(const Point& p00, const Point& p10, + const Point& p11, const Point& p01, + double u, double v) { + const double w00 = (1.0 - u) * (1.0 - v) ; + const double w10 = u * (1.0 - v) ; + const double w11 = u * v ; + const double w01 = (1.0 - u) * v ; + return Point(w00 * p00.x + w10 * p10.x + w11 * p11.x + w01 * p01.x, + w00 * p00.y + w10 * p10.y + w11 * p11.y + w01 * p01.y, + w00 * p00.z + w10 * p10.z + w11 * p11.z + w01 * p01.z) ; +} + +Node* add_node(std::list& nodes, double x, double y, double z) { + vect3d p(x, y, z) ; + Node* node = new Node(p, int32(nodes.size() + 1)) ; + nodes.push_back(node) ; + return node ; +} + +Edge* add_edge(std::list& edges, Node* head, Node* tail) { + Edge* edge = new Edge(head, tail) ; + edges.push_back(edge) ; + return edge ; +} + +void build_hex_box(HexBlock& block, + double x0, double x1, + double y0, double y1, + double z0, double z1) { + Node* node[8] ; + node[0] = add_node(block.nodes, x0, y0, z0) ; + node[1] = add_node(block.nodes, x0, y0, z1) ; + node[2] = add_node(block.nodes, x0, y1, z0) ; + node[3] = add_node(block.nodes, x0, y1, z1) ; + node[4] = add_node(block.nodes, x1, y0, z0) ; + node[5] = add_node(block.nodes, x1, y0, z1) ; + node[6] = add_node(block.nodes, x1, y1, z0) ; + node[7] = add_node(block.nodes, x1, y1, z1) ; + + const int edge_head[12] = {0, 1, 2, 3, 0, 1, 4, 5, 0, 2, 4, 6} ; + const int edge_tail[12] = {4, 5, 6, 7, 2, 3, 6, 7, 1, 3, 5, 7} ; + + Edge* edge[12] ; + for(int i = 0; i < 12; ++i) { + edge[i] = add_edge(block.edges, node[edge_head[i]], node[edge_tail[i]]) ; + } + + const int face_to_edge[6][4] = { + {6, 11, 7, 10}, + {4, 9, 5, 8}, + {2, 11, 3, 9}, + {0, 10, 1, 8}, + {1, 7, 3, 5}, + {0, 6, 2, 4} + } ; + + QuadFace** face = new QuadFace*[6] ; + for(int i = 0; i < 6; ++i) { + face[i] = new QuadFace(4) ; + block.faces.push_back(face[i]) ; + for(int j = 0; j < 4; ++j) { + face[i]->edge[j] = edge[face_to_edge[i][j]] ; + } + } + + block.root = new HexCell(face) ; +} + +void build_prism(PrismBlock& block, const Point base[3], double height) { + Node* node[6] ; + for(int i = 0; i < 3; ++i) { + node[i] = add_node(block.nodes, base[i].x, base[i].y, base[i].z) ; + node[i + 3] = add_node(block.nodes, base[i].x, base[i].y, + base[i].z + height) ; + } + + const int edge_head[9] = {0, 1, 2, 3, 4, 5, 0, 1, 2} ; + const int edge_tail[9] = {1, 2, 0, 4, 5, 3, 3, 4, 5} ; + + Edge* edge[9] ; + for(int i = 0; i < 9; ++i) { + edge[i] = add_edge(block.edges, node[edge_head[i]], node[edge_tail[i]]) ; + } + + const int gface_to_edge[2][3] = {{0, 1, 2}, {3, 4, 5}} ; + const int qface_to_edge[3][4] = {{0, 7, 3, 6}, {1, 8, 4, 7}, {2, 6, 5, 8}} ; + + Prism* prism = new Prism(3) ; + for(int i = 0; i < 2; ++i) { + Face* face = new Face(3) ; + block.gfaces.push_back(face) ; + for(int j = 0; j < 3; ++j) { + face->edge[j] = edge[gface_to_edge[i][j]] ; + face->needReverse[j] = false ; + } + prism->setFace(i, face) ; + } + + for(int i = 0; i < 3; ++i) { + QuadFace* face = new QuadFace(4) ; + block.qfaces.push_back(face) ; + for(int j = 0; j < 4; ++j) { + face->edge[j] = edge[qface_to_edge[i][j]] ; + } + prism->setFace(i, face) ; + } + + block.root = prism ; +} + +bool close_to(double lhs, double rhs) { + return std::fabs(lhs - rhs) <= 1.0e-10 ; +} + +double coord(const Node* node, int axis) { + if(axis == 0) { + return node->p.x ; + } + if(axis == 1) { + return node->p.y ; + } + return node->p.z ; +} + +std::vector unique_hex_nodes(HexCell* leaf) { + std::map seen ; + std::vector nodes ; + std::vector edges = leaf->get_edges() ; + + for(size_t i = 0; i < edges.size(); ++i) { + if(edges[i]->head == 0 || edges[i]->tail == 0) { + throw std::runtime_error("hex leaf has an incomplete edge") ; + } + if(!seen[edges[i]->head]) { + seen[edges[i]->head] = true ; + nodes.push_back(edges[i]->head) ; + } + if(!seen[edges[i]->tail]) { + seen[edges[i]->tail] = true ; + nodes.push_back(edges[i]->tail) ; + } + } + + if(nodes.size() != 8) { + std::ostringstream msg ; + msg << "expected 8 nodes for a hex leaf, found " << nodes.size() ; + throw std::runtime_error(msg.str()) ; + } + return nodes ; +} + +Node* find_corner(const std::vector& nodes, + const double min_coord[3], + const double max_coord[3], + const bool corner[3]) { + for(size_t i = 0; i < nodes.size(); ++i) { + bool match = true ; + for(int axis = 0; axis < 3; ++axis) { + const double target = corner[axis] ? max_coord[axis] : min_coord[axis] ; + match = match && close_to(coord(nodes[i], axis), target) ; + } + if(match) { + return nodes[i] ; + } + } + throw std::runtime_error("could not identify a VTK hex corner") ; +} + +std::vector vtk_hex_nodes(HexCell* leaf) { + std::vector nodes = unique_hex_nodes(leaf) ; + double min_coord[3] = {coord(nodes[0], 0), coord(nodes[0], 1), + coord(nodes[0], 2)} ; + double max_coord[3] = {min_coord[0], min_coord[1], min_coord[2]} ; + + for(size_t i = 1; i < nodes.size(); ++i) { + for(int axis = 0; axis < 3; ++axis) { + min_coord[axis] = std::min(min_coord[axis], coord(nodes[i], axis)) ; + max_coord[axis] = std::max(max_coord[axis], coord(nodes[i], axis)) ; + } + } + + const bool corner_bits[8][3] = { + {false, false, false}, + {true, false, false}, + {true, true, false}, + {false, true, false}, + {false, false, true}, + {true, false, true}, + {true, true, true}, + {false, true, true} + } ; + + std::vector ordered(8) ; + for(int i = 0; i < 8; ++i) { + ordered[i] = find_corner(nodes, min_coord, max_coord, corner_bits[i]) ; + } + return ordered ; +} + +int point_id(Node* node, std::map& ids, std::vector& points) { + std::map::const_iterator existing = ids.find(node) ; + if(existing != ids.end()) { + return existing->second ; + } + const int id = int(points.size()) ; + ids[node] = id ; + points.push_back(node) ; + return id ; +} + +void write_hex_vtk(const std::string& path, + const std::string& title, + const std::vector& cells) { + std::vector > cell_nodes(cells.size()) ; + std::vector points ; + std::map ids ; + + for(size_t cell = 0; cell < cells.size(); ++cell) { + cell_nodes[cell] = vtk_hex_nodes(cells[cell].cell) ; + for(size_t n = 0; n < cell_nodes[cell].size(); ++n) { + point_id(cell_nodes[cell][n], ids, points) ; + } + } + + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open hex VTK output file") ; + } + + out << "# vtk DataFile Version 3.0\n" ; + out << title << "\n" ; + out << "ASCII\n" ; + out << "DATASET UNSTRUCTURED_GRID\n" ; + out << "POINTS " << points.size() << " float\n" ; + out << std::setprecision(17) ; + for(size_t i = 0; i < points.size(); ++i) { + out << points[i]->p.x << " " << points[i]->p.y << " " + << points[i]->p.z << "\n" ; + } + + out << "CELLS " << cell_nodes.size() << " " << cell_nodes.size() * 9 << "\n" ; + for(size_t cell = 0; cell < cell_nodes.size(); ++cell) { + out << "8" ; + for(size_t n = 0; n < cell_nodes[cell].size(); ++n) { + out << " " << ids[cell_nodes[cell][n]] ; + } + out << "\n" ; + } + + out << "CELL_TYPES " << cell_nodes.size() << "\n" ; + for(size_t cell = 0; cell < cell_nodes.size(); ++cell) { + out << "12\n" ; + } + + out << "CELL_DATA " << cell_nodes.size() << "\n" ; + out << "SCALARS source_cell int 1\n" ; + out << "LOOKUP_TABLE default\n" ; + for(size_t cell = 0; cell < cells.size(); ++cell) { + out << cells[cell].source_cell << "\n" ; + } + out << "SCALARS root_split_code int 1\n" ; + out << "LOOKUP_TABLE default\n" ; + for(size_t cell = 0; cell < cells.size(); ++cell) { + out << cells[cell].root_split_code << "\n" ; + } + out << "SCALARS fvmadapt_cell_index int 1\n" ; + out << "LOOKUP_TABLE default\n" ; + for(size_t cell = 0; cell < cells.size(); ++cell) { + out << cells[cell].cell->getCellIndex() << "\n" ; + } +} + +void collect_edge_leaves(const std::list& root_edges, + std::vector& leaves) { + std::set seen ; + for(std::list::const_iterator edge = root_edges.begin(); + edge != root_edges.end(); ++edge) { + std::list local_leaves ; + (*edge)->sort_leaves(local_leaves) ; + for(std::list::iterator leaf = local_leaves.begin(); + leaf != local_leaves.end(); ++leaf) { + if(seen.insert(*leaf).second) { + leaves.push_back(*leaf) ; + } + } + } +} + +void add_wire_inputs(HexBlock& block, + std::vector*>& node_lists, + std::vector*>& edge_lists) { + node_lists.push_back(&block.nodes) ; + edge_lists.push_back(&block.edges) ; +} + +void collect_nodes(const std::list& roots, std::vector& points, + std::map& ids) { + for(std::list::const_iterator node = roots.begin(); + node != roots.end(); ++node) { + point_id(*node, ids, points) ; + } +} + +void write_wireframe_vtk(const std::string& path, + const std::string& title, + const std::vector*>& node_lists, + const std::vector*>& edge_lists) { + std::vector points ; + std::map ids ; + std::vector leaves ; + + for(size_t i = 0; i < node_lists.size(); ++i) { + collect_nodes(*node_lists[i], points, ids) ; + } + for(size_t i = 0; i < edge_lists.size(); ++i) { + collect_edge_leaves(*edge_lists[i], leaves) ; + } + for(size_t i = 0; i < leaves.size(); ++i) { + point_id(leaves[i]->head, ids, points) ; + point_id(leaves[i]->tail, ids, points) ; + } + + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open wireframe VTK output file") ; + } + + out << "# vtk DataFile Version 3.0\n" ; + out << title << "\n" ; + out << "ASCII\n" ; + out << "DATASET POLYDATA\n" ; + out << "POINTS " << points.size() << " float\n" ; + out << std::setprecision(17) ; + for(size_t i = 0; i < points.size(); ++i) { + out << points[i]->p.x << " " << points[i]->p.y << " " + << points[i]->p.z << "\n" ; + } + + out << "LINES " << leaves.size() << " " << leaves.size() * 3 << "\n" ; + for(size_t i = 0; i < leaves.size(); ++i) { + out << "2 " << ids[leaves[i]->head] << " " + << ids[leaves[i]->tail] << "\n" ; + } +} + +void replay_quad_face_plan(const std::vector& face_plan, + std::vector& leaves) { + std::vector queue ; + queue.push_back(QuadPatch()) ; + + size_t plan_index = 0 ; + for(size_t cursor = 0; cursor < queue.size(); ++cursor) { + const char code = plan_index < face_plan.size() ? + face_plan[plan_index++] : char(0) ; + const QuadPatch patch = queue[cursor] ; + const double um = 0.5 * (patch.u0 + patch.u1) ; + const double vm = 0.5 * (patch.v0 + patch.v1) ; + + if(code == 0) { + leaves.push_back(patch) ; + } else if(code == 1) { + queue.push_back(QuadPatch(patch.u0, um, patch.v0, patch.v1)) ; + queue.push_back(QuadPatch(um, patch.u1, patch.v0, patch.v1)) ; + } else if(code == 2) { + queue.push_back(QuadPatch(patch.u0, patch.u1, patch.v0, vm)) ; + queue.push_back(QuadPatch(patch.u0, patch.u1, vm, patch.v1)) ; + } else if(code == 3) { + queue.push_back(QuadPatch(patch.u0, um, patch.v0, vm)) ; + queue.push_back(QuadPatch(um, patch.u1, patch.v0, vm)) ; + queue.push_back(QuadPatch(patch.u0, um, vm, patch.v1)) ; + queue.push_back(QuadPatch(um, patch.u1, vm, patch.v1)) ; + } else { + std::ostringstream msg ; + msg << "unsupported quad face split code " << int(code) + << " in " << plan_string(face_plan) ; + throw std::runtime_error(msg.str()) ; + } + } +} + +void write_quad_face_vtk(const std::string& path, + const std::string& title, + const Point& p00, + const Point& p10, + const Point& p11, + const Point& p01, + const std::vector& face_plan) { + std::vector leaves ; + replay_quad_face_plan(face_plan, leaves) ; + std::vector points ; + std::map, int> ids ; + std::vector > quads ; + + for(size_t i = 0; i < leaves.size(); ++i) { + const double us[2] = {leaves[i].u0, leaves[i].u1} ; + const double vs[2] = {leaves[i].v0, leaves[i].v1} ; + std::vector quad ; + const int corner[4][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}} ; + for(int c = 0; c < 4; ++c) { + const int ukey = int(std::floor(us[corner[c][0]] * 1048576.0 + 0.5)) ; + const int vkey = int(std::floor(vs[corner[c][1]] * 1048576.0 + 0.5)) ; + const std::pair key = std::make_pair(ukey, vkey) ; + std::map, int>::const_iterator existing = + ids.find(key) ; + if(existing == ids.end()) { + const int id = int(points.size()) ; + ids[key] = id ; + points.push_back(mix(p00, p10, p11, p01, + us[corner[c][0]], vs[corner[c][1]])) ; + quad.push_back(id) ; + } else { + quad.push_back(existing->second) ; + } + } + quads.push_back(quad) ; + } + + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open shared-face VTK output file") ; + } + + out << "# vtk DataFile Version 3.0\n" ; + out << title << "\n" ; + out << "ASCII\n" ; + out << "DATASET UNSTRUCTURED_GRID\n" ; + out << "POINTS " << points.size() << " float\n" ; + out << std::setprecision(17) ; + for(size_t i = 0; i < points.size(); ++i) { + out << points[i].x << " " << points[i].y << " " << points[i].z << "\n" ; + } + + out << "CELLS " << quads.size() << " " << quads.size() * 5 << "\n" ; + for(size_t i = 0; i < quads.size(); ++i) { + out << "4 " << quads[i][0] << " " << quads[i][1] << " " + << quads[i][2] << " " << quads[i][3] << "\n" ; + } + + out << "CELL_TYPES " << quads.size() << "\n" ; + for(size_t i = 0; i < quads.size(); ++i) { + out << "9\n" ; + } +} + +void write_plan_note(const std::string& path, + const std::string& case_name, + const std::vector& left_cell_plan, + const std::vector& shared_face_plan, + const std::string& note) { + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open plan note output file") ; + } + out << "case " << case_name << "\n" ; + out << "left_cell_plan " << plan_string(left_cell_plan) << "\n" ; + out << "shared_face_plan " << plan_string(shared_face_plan) << "\n" ; + out << "note " << note << "\n" ; +} + +void add_manifest_case(std::ofstream& manifest, + const std::string& group, + const std::string& name, + const std::string& description, + const std::vector& files) { + manifest << "case " << group << "/" << name << "\n" ; + manifest << " " << description << "\n" ; + for(size_t i = 0; i < files.size(); ++i) { + manifest << " file " << files[i] << "\n" ; + } + manifest << "\n" ; +} + +void write_hex_pair_case(const std::string& output_dir, + std::ofstream& manifest, + const std::string& group, + const std::string& name, + const std::vector& left_plan, + int split_code, + double z_extent) { + const std::string group_dir = output_group_dir(output_dir, group) ; + + const std::string before = join_path(group_dir, name + "_before.vtk") ; + const std::string after = join_path(group_dir, name + "_after.vtk") ; + const std::string before_wire = join_path(group_dir, name + "_before_wire.vtk") ; + const std::string after_wire = join_path(group_dir, name + "_after_wire.vtk") ; + const std::string face = join_path(group_dir, name + "_shared_face.vtk") ; + const std::string note = join_path(group_dir, name + "_plans.dat") ; + + { + HexBlock left ; + HexBlock right ; + build_hex_box(left, 0.0, 1.0, 0.0, 1.0, 0.0, z_extent) ; + build_hex_box(right, 1.0, 2.0, 0.0, 1.0, 0.0, z_extent) ; + left.root->empty_resplit(std::vector()) ; + right.root->empty_resplit(std::vector()) ; + std::vector cells ; + cells.push_back(HexView(left.root, 0, 0)) ; + cells.push_back(HexView(right.root, 1, 0)) ; + write_hex_vtk(before, "before " + name, cells) ; + + std::vector*> nodes ; + std::vector*> edges ; + add_wire_inputs(left, nodes, edges) ; + add_wire_inputs(right, nodes, edges) ; + write_wireframe_vtk(before_wire, "before wireframe " + name, nodes, edges) ; + } + + { + HexBlock left ; + HexBlock right ; + build_hex_box(left, 0.0, 1.0, 0.0, 1.0, 0.0, z_extent) ; + build_hex_box(right, 1.0, 2.0, 0.0, 1.0, 0.0, z_extent) ; + std::vector left_leaves ; + left.root->resplit(left_plan, left.nodes, left.edges, left.faces, + left_leaves) ; + right.root->empty_resplit(std::vector()) ; + + std::vector cells ; + for(size_t i = 0; i < left_leaves.size(); ++i) { + cells.push_back(HexView(left_leaves[i], 0, split_code)) ; + } + cells.push_back(HexView(right.root, 1, 0)) ; + write_hex_vtk(after, "after " + name, cells) ; + + std::vector*> nodes ; + std::vector*> edges ; + add_wire_inputs(left, nodes, edges) ; + add_wire_inputs(right, nodes, edges) ; + write_wireframe_vtk(after_wire, "after wireframe " + name, nodes, edges) ; + } + + std::vector shared_face_plan = extract_hex_face(left_plan, RIGHT) ; + write_quad_face_vtk(face, "shared face " + name, + Point(1.0, 0.0, 0.0), + Point(1.0, 1.0, 0.0), + Point(1.0, 1.0, z_extent), + Point(1.0, 0.0, z_extent), + shared_face_plan) ; + write_plan_note(note, name, left_plan, shared_face_plan, + "The right cell remains coarse; this file shows the split " + "pattern induced on the original shared face.") ; + + std::vector files ; + files.push_back(join_path(group, name + "_before.vtk")) ; + files.push_back(join_path(group, name + "_after.vtk")) ; + files.push_back(join_path(group, name + "_before_wire.vtk")) ; + files.push_back(join_path(group, name + "_after_wire.vtk")) ; + files.push_back(join_path(group, name + "_shared_face.vtk")) ; + files.push_back(join_path(group, name + "_plans.dat")) ; + add_manifest_case(manifest, group, name, + "Two neighboring hex cells with the left cell refined.", + files) ; +} + +void build_hex_grid(std::vector& blocks, + int nx, + int ny, + double z_extent) { + for(int j = 0; j < ny; ++j) { + for(int i = 0; i < nx; ++i) { + HexBlock* block = new HexBlock ; + build_hex_box(*block, + double(i), + double(i + 1), + double(j), + double(j + 1), + 0.0, + z_extent) ; + blocks.push_back(block) ; + } + } +} + +void delete_hex_grid(std::vector& blocks) { + for(size_t i = 0; i < blocks.size(); ++i) { + delete blocks[i] ; + } + blocks.clear() ; +} + +void add_hex_grid_wire_inputs(std::vector& blocks, + std::vector*>& nodes, + std::vector*>& edges) { + for(size_t i = 0; i < blocks.size(); ++i) { + add_wire_inputs(*blocks[i], nodes, edges) ; + } +} + +void write_hex_grid_plan_note(const std::string& path, + const std::string& case_name, + const std::vector& center_plan, + const std::vector& right_plan, + const std::vector& left_plan, + const std::vector& front_plan, + const std::vector& back_plan, + const std::string& note) { + std::ofstream out(path.c_str()) ; + if(!out) { + throw std::runtime_error("could not open 5x5 plan note output file") ; + } + out << "case " << case_name << "\n" ; + out << "center_cell ij 2 2\n" ; + out << "center_cell_plan " << plan_string(center_plan) << "\n" ; + out << "right_face_plan " << plan_string(right_plan) << "\n" ; + out << "left_face_plan " << plan_string(left_plan) << "\n" ; + out << "front_face_plan " << plan_string(front_plan) << "\n" ; + out << "back_face_plan " << plan_string(back_plan) << "\n" ; + out << "note " << note << "\n" ; +} + +void write_hex_grid_focus_case(const std::string& output_dir, + std::ofstream& manifest) { + const std::string group = "2d/hex" ; + const std::string name = "grid_5x5_refine_center_twice" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + + const int nx = 5 ; + const int ny = 5 ; + const int center = 2 + nx * 2 ; + const double z_extent = 0.08 ; + const std::vector center_plan = plan({6, 6, 6, 6, 6}) ; + + const std::string before = join_path(group_dir, name + "_before.vtk") ; + const std::string after = join_path(group_dir, name + "_after.vtk") ; + const std::string before_wire = join_path(group_dir, name + "_before_wire.vtk") ; + const std::string after_wire = join_path(group_dir, name + "_after_wire.vtk") ; + const std::string right_face = join_path(group_dir, name + "_center_right_face.vtk") ; + const std::string left_face = join_path(group_dir, name + "_center_left_face.vtk") ; + const std::string front_face = join_path(group_dir, name + "_center_front_face.vtk") ; + const std::string back_face = join_path(group_dir, name + "_center_back_face.vtk") ; + const std::string note = join_path(group_dir, name + "_plans.dat") ; + + { + std::vector blocks ; + build_hex_grid(blocks, nx, ny, z_extent) ; + + std::vector cells ; + for(size_t i = 0; i < blocks.size(); ++i) { + blocks[i]->root->empty_resplit(std::vector()) ; + cells.push_back(HexView(blocks[i]->root, int(i), 0)) ; + } + write_hex_vtk(before, "before " + name, cells) ; + + std::vector*> nodes ; + std::vector*> edges ; + add_hex_grid_wire_inputs(blocks, nodes, edges) ; + write_wireframe_vtk(before_wire, "before wireframe " + name, nodes, edges) ; + delete_hex_grid(blocks) ; + } + + { + std::vector blocks ; + build_hex_grid(blocks, nx, ny, z_extent) ; + + std::vector cells ; + for(size_t i = 0; i < blocks.size(); ++i) { + if(int(i) == center) { + std::vector leaves ; + blocks[i]->root->resplit(center_plan, + blocks[i]->nodes, + blocks[i]->edges, + blocks[i]->faces, + leaves) ; + for(size_t leaf = 0; leaf < leaves.size(); ++leaf) { + cells.push_back(HexView(leaves[leaf], int(i), 6)) ; + } + } else { + blocks[i]->root->empty_resplit(std::vector()) ; + cells.push_back(HexView(blocks[i]->root, int(i), 0)) ; + } + } + write_hex_vtk(after, "after " + name, cells) ; + + std::vector*> nodes ; + std::vector*> edges ; + add_hex_grid_wire_inputs(blocks, nodes, edges) ; + write_wireframe_vtk(after_wire, "after wireframe " + name, nodes, edges) ; + delete_hex_grid(blocks) ; + } + + const std::vector right_plan = extract_hex_face(center_plan, RIGHT) ; + const std::vector left_plan = extract_hex_face(center_plan, LEFT) ; + const std::vector front_plan = extract_hex_face(center_plan, FRONT) ; + const std::vector back_plan = extract_hex_face(center_plan, BACK) ; + write_quad_face_vtk(right_face, "center right face " + name, + Point(3.0, 2.0, 0.0), + Point(3.0, 3.0, 0.0), + Point(3.0, 3.0, z_extent), + Point(3.0, 2.0, z_extent), + right_plan) ; + write_quad_face_vtk(left_face, "center left face " + name, + Point(2.0, 2.0, 0.0), + Point(2.0, 3.0, 0.0), + Point(2.0, 3.0, z_extent), + Point(2.0, 2.0, z_extent), + left_plan) ; + write_quad_face_vtk(front_face, "center front face " + name, + Point(2.0, 3.0, 0.0), + Point(3.0, 3.0, 0.0), + Point(3.0, 3.0, z_extent), + Point(2.0, 3.0, z_extent), + front_plan) ; + write_quad_face_vtk(back_face, "center back face " + name, + Point(2.0, 2.0, 0.0), + Point(3.0, 2.0, 0.0), + Point(3.0, 2.0, z_extent), + Point(2.0, 2.0, z_extent), + back_plan) ; + write_hex_grid_plan_note(note, + name, + center_plan, + right_plan, + left_plan, + front_plan, + back_plan, + "This is a direct library illustration. It shows " + "the refined center-cell tree and the face plans " + "induced on its four neighbors; it does not run " + "the full marker/refmesh scheduling path.") ; + + std::vector files ; + files.push_back(join_path(group, name + "_before.vtk")) ; + files.push_back(join_path(group, name + "_after.vtk")) ; + files.push_back(join_path(group, name + "_before_wire.vtk")) ; + files.push_back(join_path(group, name + "_after_wire.vtk")) ; + files.push_back(join_path(group, name + "_center_right_face.vtk")) ; + files.push_back(join_path(group, name + "_center_left_face.vtk")) ; + files.push_back(join_path(group, name + "_center_front_face.vtk")) ; + files.push_back(join_path(group, name + "_center_back_face.vtk")) ; + files.push_back(join_path(group, name + "_plans.dat")) ; + add_manifest_case(manifest, group, name, + "A 5x5 thin hex grid with the center cell refined twice.", + files) ; +} + +void write_hex_anisotropic_grid_case(const std::string& output_dir, + std::ofstream& manifest, + const std::string& name, + const std::vector& center_plan, + int split_code, + const std::string& description) { + const std::string group = "anisotropic/hex" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + + const int nx = 5 ; + const int ny = 5 ; + const int center = 2 + nx * 2 ; + const double z_extent = 0.08 ; + + const std::string before = join_path(group_dir, name + "_before.vtk") ; + const std::string after = join_path(group_dir, name + "_after.vtk") ; + const std::string before_wire = join_path(group_dir, name + "_before_wire.vtk") ; + const std::string after_wire = join_path(group_dir, name + "_after_wire.vtk") ; + const std::string right_face = join_path(group_dir, name + "_center_right_face.vtk") ; + const std::string left_face = join_path(group_dir, name + "_center_left_face.vtk") ; + const std::string front_face = join_path(group_dir, name + "_center_front_face.vtk") ; + const std::string back_face = join_path(group_dir, name + "_center_back_face.vtk") ; + const std::string note = join_path(group_dir, name + "_plans.dat") ; + + { + std::vector blocks ; + build_hex_grid(blocks, nx, ny, z_extent) ; + + std::vector cells ; + for(size_t i = 0; i < blocks.size(); ++i) { + blocks[i]->root->empty_resplit(std::vector()) ; + cells.push_back(HexView(blocks[i]->root, int(i), 0)) ; + } + write_hex_vtk(before, "before " + name, cells) ; + + std::vector*> nodes ; + std::vector*> edges ; + add_hex_grid_wire_inputs(blocks, nodes, edges) ; + write_wireframe_vtk(before_wire, "before wireframe " + name, nodes, edges) ; + delete_hex_grid(blocks) ; + } + + { + std::vector blocks ; + build_hex_grid(blocks, nx, ny, z_extent) ; + + std::vector cells ; + for(size_t i = 0; i < blocks.size(); ++i) { + if(int(i) == center) { + std::vector leaves ; + blocks[i]->root->resplit(center_plan, + blocks[i]->nodes, + blocks[i]->edges, + blocks[i]->faces, + leaves) ; + for(size_t leaf = 0; leaf < leaves.size(); ++leaf) { + cells.push_back(HexView(leaves[leaf], int(i), split_code)) ; + } + } else { + blocks[i]->root->empty_resplit(std::vector()) ; + cells.push_back(HexView(blocks[i]->root, int(i), 0)) ; + } + } + write_hex_vtk(after, "after " + name, cells) ; + + std::vector*> nodes ; + std::vector*> edges ; + add_hex_grid_wire_inputs(blocks, nodes, edges) ; + write_wireframe_vtk(after_wire, "after wireframe " + name, nodes, edges) ; + delete_hex_grid(blocks) ; + } + + const std::vector right_plan = extract_hex_face(center_plan, RIGHT) ; + const std::vector left_plan = extract_hex_face(center_plan, LEFT) ; + const std::vector front_plan = extract_hex_face(center_plan, FRONT) ; + const std::vector back_plan = extract_hex_face(center_plan, BACK) ; + write_quad_face_vtk(right_face, "center right face " + name, + Point(3.0, 2.0, 0.0), + Point(3.0, 3.0, 0.0), + Point(3.0, 3.0, z_extent), + Point(3.0, 2.0, z_extent), + right_plan) ; + write_quad_face_vtk(left_face, "center left face " + name, + Point(2.0, 2.0, 0.0), + Point(2.0, 3.0, 0.0), + Point(2.0, 3.0, z_extent), + Point(2.0, 2.0, z_extent), + left_plan) ; + write_quad_face_vtk(front_face, "center front face " + name, + Point(2.0, 3.0, 0.0), + Point(3.0, 3.0, 0.0), + Point(3.0, 3.0, z_extent), + Point(2.0, 3.0, z_extent), + front_plan) ; + write_quad_face_vtk(back_face, "center back face " + name, + Point(2.0, 2.0, 0.0), + Point(3.0, 2.0, 0.0), + Point(3.0, 2.0, z_extent), + Point(2.0, 2.0, z_extent), + back_plan) ; + write_hex_grid_plan_note(note, + name, + center_plan, + right_plan, + left_plan, + front_plan, + back_plan, + description + " This is a direct library " + "illustration of one anisotropically refined " + "center cell embedded in coarse neighbors.") ; + + std::vector files ; + files.push_back(join_path(group, name + "_before.vtk")) ; + files.push_back(join_path(group, name + "_after.vtk")) ; + files.push_back(join_path(group, name + "_before_wire.vtk")) ; + files.push_back(join_path(group, name + "_after_wire.vtk")) ; + files.push_back(join_path(group, name + "_center_right_face.vtk")) ; + files.push_back(join_path(group, name + "_center_left_face.vtk")) ; + files.push_back(join_path(group, name + "_center_front_face.vtk")) ; + files.push_back(join_path(group, name + "_center_back_face.vtk")) ; + files.push_back(join_path(group, name + "_plans.dat")) ; + add_manifest_case(manifest, group, name, description, files) ; +} + +void write_prism_pair_case(const std::string& output_dir, + std::ofstream& manifest) { + const std::string group = "2d/prism" ; + const std::string name = "square_refine_lower" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + + const std::string before = join_path(group_dir, name + "_before_wire.vtk") ; + const std::string after = join_path(group_dir, name + "_after_wire.vtk") ; + const std::string face = join_path(group_dir, name + "_shared_face.vtk") ; + const std::string note = join_path(group_dir, name + "_plans.dat") ; + + const Point lower[3] = { + Point(0.0, 0.0, 0.0), + Point(1.0, 0.0, 0.0), + Point(0.0, 1.0, 0.0) + } ; + const Point upper[3] = { + Point(1.0, 1.0, 0.0), + Point(0.0, 1.0, 0.0), + Point(1.0, 0.0, 0.0) + } ; + const double height = 0.08 ; + const std::vector lower_plan = plan({3}) ; + + { + PrismBlock lower_prism ; + PrismBlock upper_prism ; + build_prism(lower_prism, lower, height) ; + build_prism(upper_prism, upper, height) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&lower_prism.nodes) ; + nodes.push_back(&upper_prism.nodes) ; + edges.push_back(&lower_prism.edges) ; + edges.push_back(&upper_prism.edges) ; + write_wireframe_vtk(before, "before " + name, nodes, edges) ; + } + + { + PrismBlock lower_prism ; + PrismBlock upper_prism ; + build_prism(lower_prism, lower, height) ; + build_prism(upper_prism, upper, height) ; + std::vector leaves ; + lower_prism.root->resplit(lower_plan, + lower_prism.nodes, + lower_prism.edges, + lower_prism.qfaces, + lower_prism.gfaces, + leaves) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&lower_prism.nodes) ; + nodes.push_back(&upper_prism.nodes) ; + edges.push_back(&lower_prism.edges) ; + edges.push_back(&upper_prism.edges) ; + write_wireframe_vtk(after, "after " + name, nodes, edges) ; + } + + const std::vector shared_face_plan = extract_prism_face(lower_plan, 3) ; + write_quad_face_vtk(face, "shared prism face " + name, + Point(1.0, 0.0, 0.0), + Point(0.0, 1.0, 0.0), + Point(0.0, 1.0, height), + Point(1.0, 0.0, height), + shared_face_plan) ; + write_plan_note(note, name, lower_plan, shared_face_plan, + "The triangular prism split induces this plan on the " + "diagonal quad face shared with the neighboring prism.") ; + + std::vector files ; + files.push_back(join_path(group, name + "_before_wire.vtk")) ; + files.push_back(join_path(group, name + "_after_wire.vtk")) ; + files.push_back(join_path(group, name + "_shared_face.vtk")) ; + files.push_back(join_path(group, name + "_plans.dat")) ; + add_manifest_case(manifest, group, name, + "Two extruded triangular prisms forming one square.", + files) ; +} + +void write_prism_anisotropic_pair_case(const std::string& output_dir, + std::ofstream& manifest, + const std::string& name, + const std::vector& lower_plan, + const std::string& description) { + const std::string group = "anisotropic/prism" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + + const std::string before = join_path(group_dir, name + "_before_wire.vtk") ; + const std::string after = join_path(group_dir, name + "_after_wire.vtk") ; + const std::string face = join_path(group_dir, name + "_shared_face.vtk") ; + const std::string note = join_path(group_dir, name + "_plans.dat") ; + + const Point lower[3] = { + Point(0.0, 0.0, 0.0), + Point(1.0, 0.0, 0.0), + Point(0.0, 1.0, 0.0) + } ; + const Point upper[3] = { + Point(1.0, 1.0, 0.0), + Point(0.0, 1.0, 0.0), + Point(1.0, 0.0, 0.0) + } ; + const double height = 0.08 ; + + { + PrismBlock lower_prism ; + PrismBlock upper_prism ; + build_prism(lower_prism, lower, height) ; + build_prism(upper_prism, upper, height) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&lower_prism.nodes) ; + nodes.push_back(&upper_prism.nodes) ; + edges.push_back(&lower_prism.edges) ; + edges.push_back(&upper_prism.edges) ; + write_wireframe_vtk(before, "before " + name, nodes, edges) ; + } + + { + PrismBlock lower_prism ; + PrismBlock upper_prism ; + build_prism(lower_prism, lower, height) ; + build_prism(upper_prism, upper, height) ; + std::vector leaves ; + lower_prism.root->resplit(lower_plan, + lower_prism.nodes, + lower_prism.edges, + lower_prism.qfaces, + lower_prism.gfaces, + leaves) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&lower_prism.nodes) ; + nodes.push_back(&upper_prism.nodes) ; + edges.push_back(&lower_prism.edges) ; + edges.push_back(&upper_prism.edges) ; + write_wireframe_vtk(after, "after " + name, nodes, edges) ; + } + + const std::vector shared_face_plan = extract_prism_face(lower_plan, 3) ; + write_quad_face_vtk(face, "shared prism face " + name, + Point(1.0, 0.0, 0.0), + Point(0.0, 1.0, 0.0), + Point(0.0, 1.0, height), + Point(1.0, 0.0, height), + shared_face_plan) ; + write_plan_note(note, name, lower_plan, shared_face_plan, + description + " The upper prism remains coarse so the " + "shared-face artifact shows the induced interface plan.") ; + + std::vector files ; + files.push_back(join_path(group, name + "_before_wire.vtk")) ; + files.push_back(join_path(group, name + "_after_wire.vtk")) ; + files.push_back(join_path(group, name + "_shared_face.vtk")) ; + files.push_back(join_path(group, name + "_plans.dat")) ; + add_manifest_case(manifest, group, name, description, files) ; +} + +void write_single_prism_case(const std::string& output_dir, + std::ofstream& manifest) { + const std::string group = "3d/prism" ; + const std::string name = "full_split" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + + const std::string before = join_path(group_dir, name + "_before_wire.vtk") ; + const std::string after = join_path(group_dir, name + "_after_wire.vtk") ; + const std::vector cell_plan = plan({3}) ; + + { + PrismFixture fixture ; + build_unit_prism(fixture) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&fixture.nodes) ; + edges.push_back(&fixture.edges) ; + write_wireframe_vtk(before, "before " + name, nodes, edges) ; + } + + { + PrismFixture fixture ; + build_unit_prism(fixture) ; + std::vector leaves ; + fixture.root->resplit(cell_plan, + fixture.nodes, + fixture.edges, + fixture.qfaces, + fixture.gfaces, + leaves) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&fixture.nodes) ; + edges.push_back(&fixture.edges) ; + write_wireframe_vtk(after, "after " + name, nodes, edges) ; + } + + std::vector files ; + files.push_back(join_path(group, name + "_before_wire.vtk")) ; + files.push_back(join_path(group, name + "_after_wire.vtk")) ; + add_manifest_case(manifest, group, name, + "A single 3D prism before and after a full prism split.", + files) ; +} + +void write_general_cell_case(const std::string& output_dir, + std::ofstream& manifest, + const std::string& group, + const std::string& name, + GeneralBuilder builder, + const std::string& description) { + const std::string group_dir = output_group_dir(output_dir, group) ; + const std::string before = join_path(group_dir, name + "_before_wire.vtk") ; + const std::string after = join_path(group_dir, name + "_after_wire.vtk") ; + const std::vector cell_plan = plan({1}) ; + + { + GeneralFixture fixture ; + builder(fixture) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&fixture.nodes) ; + edges.push_back(&fixture.edges) ; + write_wireframe_vtk(before, "before " + name, nodes, edges) ; + } + + { + GeneralFixture fixture ; + builder(fixture) ; + std::vector leaves ; + fixture.root->resplit(cell_plan, + fixture.nodes, + fixture.edges, + fixture.faces, + leaves) ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&fixture.nodes) ; + edges.push_back(&fixture.edges) ; + write_wireframe_vtk(after, "after " + name, nodes, edges) ; + } + + std::vector files ; + files.push_back(join_path(group, name + "_before_wire.vtk")) ; + files.push_back(join_path(group, name + "_after_wire.vtk")) ; + add_manifest_case(manifest, group, name, description, files) ; +} + +void write_hex_split_catalog(const std::string& output_dir, + std::ofstream& manifest) { + const std::string group = "split_catalog/hex" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + std::vector files ; + + for(int code = 0; code <= 7; ++code) { + HexFixture fixture ; + build_unit_hex(fixture) ; + std::vector leaves ; + fixture.root->resplit(plan({code}), + fixture.nodes, + fixture.edges, + fixture.faces, + leaves) ; + std::vector cells ; + for(size_t i = 0; i < leaves.size(); ++i) { + cells.push_back(HexView(leaves[i], 0, code)) ; + } + + std::ostringstream cell_name ; + cell_name << "split_" << code << "_cells.vtk" ; + write_hex_vtk(join_path(group_dir, cell_name.str()), + "HexCell root split " + plan_string(plan({code})), + cells) ; + files.push_back(join_path(group, cell_name.str())) ; + + std::ostringstream wire_name ; + wire_name << "split_" << code << "_wire.vtk" ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&fixture.nodes) ; + edges.push_back(&fixture.edges) ; + write_wireframe_vtk(join_path(group_dir, wire_name.str()), + "HexCell root split wireframe " + plan_string(plan({code})), + nodes, + edges) ; + files.push_back(join_path(group, wire_name.str())) ; + } + + add_manifest_case(manifest, + group, + "root_split_strategies", + "Root split visuals for HexCell split codes 0 through 7.", + files) ; +} + +void write_prism_split_catalog(const std::string& output_dir, + std::ofstream& manifest) { + const std::string group = "split_catalog/prism" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + std::vector files ; + + for(int code = 0; code <= 3; ++code) { + PrismFixture fixture ; + build_unit_prism(fixture) ; + std::vector leaves ; + fixture.root->resplit(plan({code}), + fixture.nodes, + fixture.edges, + fixture.qfaces, + fixture.gfaces, + leaves) ; + + std::ostringstream name ; + name << "split_" << code << "_wire.vtk" ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&fixture.nodes) ; + edges.push_back(&fixture.edges) ; + write_wireframe_vtk(join_path(group_dir, name.str()), + "Prism root split " + plan_string(plan({code})), + nodes, + edges) ; + files.push_back(join_path(group, name.str())) ; + } + + add_manifest_case(manifest, + group, + "root_split_strategies", + "Root split visuals for Prism split codes 0 through 3.", + files) ; +} + +void write_general_split_catalog_case(const std::string& output_dir, + const std::string& shape, + GeneralBuilder builder, + const std::vector& cell_plan, + std::vector& files) { + const std::string group = "split_catalog/general" ; + const std::string group_dir = output_group_dir(output_dir, group) ; + + GeneralFixture fixture ; + builder(fixture) ; + if(!cell_plan.empty()) { + std::vector leaves ; + fixture.root->resplit(cell_plan, + fixture.nodes, + fixture.edges, + fixture.faces, + leaves) ; + } + + std::ostringstream name ; + name << shape << "_split_" << (cell_plan.empty() ? 0 : int(cell_plan[0])) + << "_wire.vtk" ; + std::vector*> nodes ; + std::vector*> edges ; + nodes.push_back(&fixture.nodes) ; + edges.push_back(&fixture.edges) ; + write_wireframe_vtk(join_path(group_dir, name.str()), + shape + " split " + plan_string(cell_plan), + nodes, + edges) ; + files.push_back(join_path(group, name.str())) ; +} + +void write_split_catalog(const std::string& output_dir, + std::ofstream& manifest) { + write_hex_split_catalog(output_dir, manifest) ; + write_prism_split_catalog(output_dir, manifest) ; + + std::vector files ; + write_general_split_catalog_case(output_dir, + "tetra", + build_tetra_cell, + std::vector(), + files) ; + write_general_split_catalog_case(output_dir, + "tetra", + build_tetra_cell, + plan({1}), + files) ; + write_general_split_catalog_case(output_dir, + "pyramid", + build_pyramid_cell, + std::vector(), + files) ; + write_general_split_catalog_case(output_dir, + "pyramid", + build_pyramid_cell, + plan({1}), + files) ; + + add_manifest_case(manifest, + "split_catalog/general", + "root_split_strategies", + "Root split visuals for tetrahedral and pyramidal " + "general Cell shapes.", + files) ; +} + +void write_anisotropic_cases(const std::string& output_dir, + std::ofstream& manifest) { + write_hex_anisotropic_grid_case(output_dir, + manifest, + "center_x_split", + plan({4}), + 4, + "A 5x5 thin hex grid with the center cell " + "split in the x direction.") ; + write_hex_anisotropic_grid_case(output_dir, + manifest, + "center_y_split", + plan({2}), + 2, + "A 5x5 thin hex grid with the center cell " + "split in the y direction.") ; + write_hex_anisotropic_grid_case(output_dir, + manifest, + "center_z_split", + plan({1}), + 1, + "A 5x5 thin hex grid with the center cell " + "split through the thin z direction.") ; + write_hex_anisotropic_grid_case(output_dir, + manifest, + "center_xy_split", + plan({6}), + 6, + "A 5x5 thin hex grid with the center cell " + "split in both x and y.") ; + + write_prism_anisotropic_pair_case(output_dir, + manifest, + "pair_code1_two_child_split", + plan({1}), + "Two neighboring triangular prisms with " + "the lower prism using split code 1, " + "which creates two children.") ; + write_prism_anisotropic_pair_case(output_dir, + manifest, + "pair_code2_three_child_split", + plan({2}), + "Two neighboring triangular prisms with " + "the lower prism using split code 2, " + "which creates three children.") ; + write_prism_anisotropic_pair_case(output_dir, + manifest, + "pair_code3_six_child_split", + plan({3}), + "Two neighboring triangular prisms with " + "the lower prism using split code 3, " + "which creates six children.") ; +} + +void write_all(const std::string& output_dir) { + make_directories(output_dir) ; + + const std::string manifest_path = join_path(output_dir, "manifest.dat") ; + std::ofstream manifest(manifest_path.c_str()) ; + if(!manifest) { + throw std::runtime_error("could not open illustration manifest") ; + } + manifest << "# FVMAdapt illustration artifacts\n" ; + manifest << "# Run from quickTest/FVMAdaptTests/Illustrations with make.\n\n" ; + + write_hex_pair_case(output_dir, manifest, + "2d/hex", + "strip_refine_left_xy", + plan({6}), + 6, + 0.08) ; + write_hex_grid_focus_case(output_dir, manifest) ; + write_prism_pair_case(output_dir, manifest) ; + write_hex_pair_case(output_dir, manifest, + "3d/hex", + "pair_refine_left_xyz", + plan({7}), + 7, + 1.0) ; + write_single_prism_case(output_dir, manifest) ; + write_general_cell_case(output_dir, manifest, + "3d/general/tetra", + "split", + build_tetra_cell, + "A tetrahedral general Cell before and after " + "isotropic split.") ; + write_general_cell_case(output_dir, manifest, + "3d/general/pyramid", + "split", + build_pyramid_cell, + "A pyramidal general Cell before and after " + "isotropic split.") ; + write_anisotropic_cases(output_dir, manifest) ; + write_split_catalog(output_dir, manifest) ; + + std::cout << "wrote " << manifest_path << "\n" ; + std::cout << "wrote illustration VTK files under " + << join_path(output_dir, "2d") << ", " + << join_path(output_dir, "3d") << ", " + << join_path(output_dir, "anisotropic") << ", and " + << join_path(output_dir, "split_catalog") << "\n" ; +} + +} // namespace + +int main(int argc, char** argv) { + std::string output_dir = "output" ; + + for(int i = 1; i < argc; ++i) { + const std::string arg(argv[i]) ; + if(arg == "--write-dir") { + if(i + 1 >= argc) { + std::cerr << "--write-dir requires a path\n" ; + return 1 ; + } + output_dir = argv[++i] ; + } else { + std::cerr << "unknown argument: " << arg << "\n" ; + return 1 ; + } + } + + try { + write_all(output_dir) ; + return 0 ; + } catch(const std::exception& error) { + std::cerr << error.what() << "\n" ; + return 1 ; + } +} diff --git a/quickTest/FVMAdaptTests/Makefile b/quickTest/FVMAdaptTests/Makefile new file mode 100644 index 00000000..872df529 --- /dev/null +++ b/quickTest/FVMAdaptTests/Makefile @@ -0,0 +1,33 @@ +TESTS = Core Module +TEST_RESULTS = $(addsuffix /TestResults,$(TESTS)) + +.PHONY: default $(TESTS) Illustrations illustrations clean distclean FRC + +default: TestResults + +TestResults: $(TESTS) + cat $(TEST_RESULTS) > $@ + rm $(TEST_RESULTS) + @cat $@ + +$(TESTS): FRC + $(MAKE) -C $@ LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) + +Illustrations: FRC + $(MAKE) -C Illustrations LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) + +illustrations: Illustrations + +FRC: + +clean: + rm -f TestResults + $(MAKE) -C Core LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean + $(MAKE) -C Module LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean + $(MAKE) -C Illustrations LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean + +distclean: + rm -f TestResults + $(MAKE) -C Core LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean + $(MAKE) -C Module LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean + $(MAKE) -C Illustrations LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean diff --git a/quickTest/FVMAdaptTests/Module/Makefile b/quickTest/FVMAdaptTests/Module/Makefile new file mode 100644 index 00000000..190e5ee6 --- /dev/null +++ b/quickTest/FVMAdaptTests/Module/Makefile @@ -0,0 +1,24 @@ +TESTS = RefMesh +TEST_RESULTS = $(addsuffix /TestResults,$(TESTS)) + +.PHONY: default $(TESTS) clean distclean FRC + +default: TestResults + +TestResults: $(TESTS) + cat $(TEST_RESULTS) > $@ + rm $(TEST_RESULTS) + @cat $@ + +$(TESTS): FRC + $(MAKE) -C $@ LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) + +clean: + rm -f TestResults + $(MAKE) -C RefMesh LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean + +distclean: + rm -f TestResults + $(MAKE) -C RefMesh LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean + +FRC: diff --git a/quickTest/FVMAdaptTest/Makefile b/quickTest/FVMAdaptTests/Module/RefMesh/Makefile similarity index 94% rename from quickTest/FVMAdaptTest/Makefile rename to quickTest/FVMAdaptTests/Module/RefMesh/Makefile index e634deb4..e19004ec 100644 --- a/quickTest/FVMAdaptTest/Makefile +++ b/quickTest/FVMAdaptTests/Module/RefMesh/Makefile @@ -4,7 +4,9 @@ include $(TEST_BASE)/test.conf LIB_PATH=$(LOCI_BASE)/lib:$(LD_LIBRARY_PATH) DY_PATH=$(LOCI_BASE)/lib:$(DYLD_LIBRARY_PATH) -default: testGridRef.dat testGridRef2.dat testGridRef3.dat +default: TestResults + +TestResults: testGridRef.dat testGridRef2.dat testGridRef3.dat FRC cat dats/testGridRef.dat |sort -g > testGridRef.ref diff testGridRef.dat testGridRef.ref rm testGridRef.ref @@ -17,6 +19,7 @@ default: testGridRef.dat testGridRef2.dat testGridRef3.dat rm -fr output debug rm *.plan *.tag testGridRef*.vog *.quality rm testGridRef.dat testGridRef2.dat testGridRef3.dat + @echo "FVMAdaptTests/Module/RefMesh: PASSED!" > $@ testGrid.tag: testGrid.vog LD_LIBRARY_PATH=$(LIB_PATH) DYLD_LIBRARY_PATH=$(DY_PATH) \ @@ -77,8 +80,8 @@ testGridRef3.dat: output/testGridRef3.topo $(SERIALRUN) $(LOCI_BASE)/bin/extract -ascii testGridRef3 0 x y z | sort -g > testGridRef3.dat clean: - rm -fr output *.plan *.tag testGridRef*.vog *.quality testGridRef.dat testGridRef2.dat testGridRef3.dat *~ debug + rm -fr output *.plan *.tag testGridRef*.vog *.quality testGridRef.dat testGridRef2.dat testGridRef3.dat TestResults *~ debug distclean: clean - +FRC: diff --git a/quickTest/FVMAdaptTest/dats/testGridRef.dat b/quickTest/FVMAdaptTests/Module/RefMesh/dats/testGridRef.dat similarity index 100% rename from quickTest/FVMAdaptTest/dats/testGridRef.dat rename to quickTest/FVMAdaptTests/Module/RefMesh/dats/testGridRef.dat diff --git a/quickTest/FVMAdaptTest/dats/testGridRef2.dat b/quickTest/FVMAdaptTests/Module/RefMesh/dats/testGridRef2.dat similarity index 100% rename from quickTest/FVMAdaptTest/dats/testGridRef2.dat rename to quickTest/FVMAdaptTests/Module/RefMesh/dats/testGridRef2.dat diff --git a/quickTest/FVMAdaptTest/dats/testGridRef3.dat b/quickTest/FVMAdaptTests/Module/RefMesh/dats/testGridRef3.dat similarity index 100% rename from quickTest/FVMAdaptTest/dats/testGridRef3.dat rename to quickTest/FVMAdaptTests/Module/RefMesh/dats/testGridRef3.dat diff --git a/quickTest/FVMAdaptTest/testGrid.vog b/quickTest/FVMAdaptTests/Module/RefMesh/testGrid.vog similarity index 100% rename from quickTest/FVMAdaptTest/testGrid.vog rename to quickTest/FVMAdaptTests/Module/RefMesh/testGrid.vog diff --git a/quickTest/Makefile b/quickTest/Makefile index febd83e9..daffb217 100644 --- a/quickTest/Makefile +++ b/quickTest/Makefile @@ -1,10 +1,10 @@ TEST_BASE = $(shell pwd) -.PHONY: FRC FVMModUnitTests FVMAdaptTest Containers Tools SystemTests test default +.PHONY: FRC FVMModUnitTests FVMAdaptTests FVMAdaptTest FVMAdaptIllustrations Containers Tools SystemTests test default -default: FVM FVMModUnitTests FVMAdaptTest Containers Tools SystemTests - cat Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults SystemTests/TestResults > TestResults - rm Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults SystemTests/TestResults +default: FVM FVMModUnitTests FVMAdaptTests Containers Tools SystemTests + cat Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults FVMAdaptTests/TestResults SystemTests/TestResults > TestResults + rm Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults FVMAdaptTests/TestResults SystemTests/TestResults @grep PASS TestResults @grep FAIL TestResults || true @! grep -q FAIL TestResults @@ -17,8 +17,13 @@ FVMModUnitTests: FRC FVM: FRC $(MAKE) -C FVM LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) -FVMAdaptTest: FRC - $(MAKE) -C FVMAdaptTest LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) +FVMAdaptTests: FRC + $(MAKE) -C FVMAdaptTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) + +FVMAdaptTest: FVMAdaptTests + +FVMAdaptIllustrations: FRC + $(MAKE) -C FVMAdaptTests Illustrations LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) Containers: FRC $(MAKE) -C Containers LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) @@ -35,7 +40,7 @@ clean: FRC $(MAKE) -C FVMModUnitTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean $(MAKE) -C Containers LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean - $(MAKE) -C FVMAdaptTest LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean + $(MAKE) -C FVMAdaptTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean distclean: @@ -44,5 +49,5 @@ distclean: $(MAKE) -C FVMModUnitTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean $(MAKE) -C Containers LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean - $(MAKE) -C FVMAdaptTest LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean + $(MAKE) -C FVMAdaptTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean