From 6ea3c7f99f3cb024f57763b13fb972744e2c2d5e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Sun, 27 Aug 2023 17:12:15 -0700 Subject: [PATCH 01/50] Added code to generate tiled meshes, plus a data generation program. --- src/executables/CMakeLists.txt | 1 + src/executables/generate_data/CMakeLists.txt | 36 ++ .../generate_data/conduit_generate_data.cpp | 278 ++++++++++ src/libs/blueprint/CMakeLists.txt | 2 + .../conduit_blueprint_mesh_examples.hpp | 1 + .../conduit_blueprint_mesh_examples_tiled.cpp | 479 ++++++++++++++++++ .../conduit_blueprint_mesh_examples_tiled.hpp | 78 +++ 7 files changed, 875 insertions(+) create mode 100644 src/executables/generate_data/CMakeLists.txt create mode 100644 src/executables/generate_data/conduit_generate_data.cpp create mode 100644 src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp create mode 100644 src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp diff --git a/src/executables/CMakeLists.txt b/src/executables/CMakeLists.txt index bd9ac22ed..ba8b2d069 100644 --- a/src/executables/CMakeLists.txt +++ b/src/executables/CMakeLists.txt @@ -3,3 +3,4 @@ # other details. No copyright assignment is required to contribute to Conduit. add_subdirectory(adjset_validate) +add_subdirectory(generate_data) diff --git a/src/executables/generate_data/CMakeLists.txt b/src/executables/generate_data/CMakeLists.txt new file mode 100644 index 000000000..cfa521edb --- /dev/null +++ b/src/executables/generate_data/CMakeLists.txt @@ -0,0 +1,36 @@ +# Copyright (c) Lawrence Livermore National Security, LLC and other Conduit +# Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +# other details. No copyright assignment is required to contribute to Conduit. + +if(ENABLE_UTILS) + blt_add_executable( + NAME conduit_generate_data + SOURCES conduit_generate_data.cpp + OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS_ON conduit conduit_blueprint conduit_relay + FOLDER utils + ) + + # add install target + install(TARGETS conduit_generate_data + RUNTIME DESTINATION bin) + + ################################################################ + # If we have mpi, add a parallel version. + ################################################################ + + if(MPI_FOUND) + blt_add_executable( + NAME conduit_generate_data_mpi + SOURCES conduit_generate_data.cpp + OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR} + DEFINES CONDUIT_PARALLEL + DEPENDS_ON conduit conduit_blueprint conduit_relay conduit_relay_mpi_io ${conduit_blt_mpi_deps} + FOLDER utils + ) + + # add install target + install(TARGETS conduit_generate_data_mpi + RUNTIME DESTINATION bin) + endif() +endif() diff --git a/src/executables/generate_data/conduit_generate_data.cpp b/src/executables/generate_data/conduit_generate_data.cpp new file mode 100644 index 000000000..7f0845ce5 --- /dev/null +++ b/src/executables/generate_data/conduit_generate_data.cpp @@ -0,0 +1,278 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other Conduit +// Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +// other details. No copyright assignment is required to contribute to Conduit. + +//----------------------------------------------------------------------------- +/// +/// file: conduit_generate_data.hpp +/// +//----------------------------------------------------------------------------- +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef CONDUIT_PARALLEL +#include +#include +#else +#include +#endif + +//#define MAKE_UNIQUE(T) std::make_unique() +#define MAKE_UNIQUE(T) std::unique_ptr(new T()) + +//----------------------------------------------------------------------------- +/// Generate data for a domain +class DomainGenerator +{ +public: + virtual void generate(int domain[3], conduit::Node &n, conduit::Node &opts) = 0; + + void setDims(const int d[3]) + { + for(int i = 0; i < 3; i++) + dims[i] = d[i]; + } + void setDomains(const int d[3]) + { + for(int i = 0; i < 3; i++) + domains[i] = d[i]; + } + void setMeshType(const std::string &m) + { + meshType = m; + } +protected: + int dims[3]{0,0,0}; + int domains[3]{1,1,1}; + std::string meshType{}; +}; + +//----------------------------------------------------------------------------- +/// Generate data using tiled data generator. +class TiledDomainGenerator : public DomainGenerator +{ +public: + virtual void generate(int domain[3], conduit::Node &n, conduit::Node &opts) override + { + constexpr double side = 20.; + + opts["origin/x"] = domain[0] * dims[0] * side; + opts["origin/y"] = domain[1] * dims[1] * side; + opts["origin/z"] = domain[2] * dims[2] * side; + + // Selectively create boundaries based on where the domain is the + // whole set of domains. + if(domains[0] * domains[1] * domains[2] == 1) + { + opts["boundaries/left"] = 1; + opts["boundaries/right"] = 1; + opts["boundaries/bottom"] = 1; + opts["boundaries/top"] = 1; + if(dims[0] > 0) + { + opts["boundaries/back"] = 1; + opts["boundaries/front"] = 1; + } + } + else + { + opts["boundaries/left"] = ((domain[0] == 0) ? 1 : 0); + opts["boundaries/right"] = ((domain[0] == domains[0]-1) ? 1 : 0); + opts["boundaries/bottom"] = ((domain[1] == 0) ? 1 : 0); + opts["boundaries/top"] = ((domain[1] == domains[1]-1) ? 1 : 0); + if(dims[0] > 0) + { + opts["boundaries/back"] = ((domain[2] == 0) ? 1 : 0); + opts["boundaries/front"] = ((domain[2] == domains[2]-1) ? 1 : 0); + } + } + + conduit::blueprint::mesh::examples::tiled(dims[0], dims[1], dims[2], n, opts); + } +}; + +//----------------------------------------------------------------------------- +/// Generate data using braid data generator. +class BraidDomainGenerator : public DomainGenerator +{ +public: + virtual void generate(int /*domain*/[3], conduit::Node &n, conduit::Node &) override + { + conduit::blueprint::mesh::examples::braid(meshType, dims[0], dims[1], dims[2], n); + + // TODO: Use domain,domains to adjust coordinates to get them to line up nicely. + } +}; + +//----------------------------------------------------------------------------- +void +printUsage(const char *exeName) +{ + std::cout << "Usage: " << exeName << "[-dims x,y,z] [-domains x,y,z] [-tile]\n" + << " [-braid] [-output fileroot] [-protocol name] [-meshtype type]\n" + << " [-tiledef filename] [-help]\n"; + std::cout << "\n"; + std::cout << "Argument Description\n"; + std::cout << "=================== ==========================================================\n"; + std::cout << "-dims x,y,z The number of mesh zones in each dimension. For 2D meshes,\n"; + std::cout << " supply 0 for z.\n"; + std::cout << "\n"; + std::cout << "-domains x,y,z The number of domains in each dimension.\n"; + std::cout << "\n"; + std::cout << "-tile Generate a mesh using the tiled data generator.\n"; + std::cout << "\n"; + std::cout << "-braid Generate a mesh using the braid data generator.\n"; + std::cout << "\n"; + std::cout << "-output fileroot Specify the root used in filenames that are created.\n"; + std::cout << "\n"; + std::cout << "-protocol name Specify the protocol used in writing the data. The default\n"; + std::cout << " is \"hdf5\".\n"; + std::cout << "\n"; + std::cout << "-meshtype type The mesh type used when generating data using braid.\n"; + std::cout << "\n"; + std::cout << "-tiledef filename A file containing a tile definition.\n"; + std::cout << "\n"; + std::cout << "-help Print the usage and exit.\n"; +} + +//----------------------------------------------------------------------------- +int +main(int argc, char *argv[]) +{ + int rank = 0; +#ifdef CONDUIT_PARALLEL + int size = 1; + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); +#endif + + // Some basic arg parsing. + int dims[3] = {10, 10, 10}; + int domains[3] = {1, 1, 1}; + conduit::Node n, opts; + std::unique_ptr g = MAKE_UNIQUE(TiledDomainGenerator); + std::string meshType("quad"), output("output"), protocol("hdf5"); + for(int i = 1; i < argc; i++) + { + if(strcmp(argv[i], "-dims") == 0 && (i+1) < argc) + { + int d[3] = {1,1,1}; + if(sscanf(argv[i+1], "%d,%d,%d", &d[0], &d[1], &d[2]) == 3) + { + dims[0] = std::max(d[0], 1); + dims[1] = std::max(d[1], 1); + dims[2] = std::max(d[2], 1); + } + i++; + } + else if(strcmp(argv[i], "-domains") == 0 && (i+1) < argc) + { + int d[3] = {1,1,1}; + if(sscanf(argv[i+1], "%d,%d,%d", &d[0], &d[1], &d[2]) == 3) + { + domains[0] = std::max(d[0], 1); + domains[1] = std::max(d[1], 1); + domains[2] = std::max(d[2], 1); + } + i++; + } + else if(strcmp(argv[i], "-tile") == 0) + { + g = MAKE_UNIQUE(TiledDomainGenerator); + } + else if(strcmp(argv[i], "-braid") == 0) + { + g = MAKE_UNIQUE(BraidDomainGenerator); + } + else if(strcmp(argv[i], "-output") == 0 && (i+1) < argc) + { + output = argv[i+1]; + i++; + } + else if(strcmp(argv[i], "-protocol") == 0 && (i+1) < argc) + { + protocol = argv[i+1]; + i++; + } + else if(strcmp(argv[i], "-meshtype") == 0 && (i+1) < argc) + { + meshType = argv[i+1]; + i++; + } + else if(strcmp(argv[i], "-tiledef") == 0 && (i+1) < argc) + { + // Load a tile definition file into the options tile node. + conduit::relay::io::load(argv[i+1], opts["tile"]); + i++; + } + else if(strcmp(argv[i], "-help") == 0 || + strcmp(argv[i], "--help") == 0) + { + if(rank == 0) + printUsage(argv[0]); +#ifdef CONDUIT_PARALLEL + MPI_Finalize(); +#endif + return 0; + } + } + + g->setDims(dims); + g->setDomains(domains); + g->setMeshType(meshType); + + int ndoms = domains[0] * domains[1] * domains[2]; + if(ndoms == 1 && rank == 0) + { + // Single domain. + int domain[] = {0, 0, 0}; + g->generate(domain, n, opts); + } + else + { + int domainid = 0; + for(int k = 0; k < domains[2]; k++) + { + for(int j = 0; j < domains[1]; j++) + { + for(int i = 0; i < domains[0]; i++, domainid++) + { + int domain[] = {i, j, k}; + +#ifdef CONDUIT_PARALLEL + if(domainid % size == rank) + { +#endif + // Make the new domain. + char domainName[32]; + sprintf(domainName, "domain_%07d", domainid); + conduit::Node &d = n[domainName]; + g->generate(domain, d, opts); +#ifdef CONDUIT_PARALLEL + } +#endif + } + } + } + } + + // Save the output domains. +#ifdef CONDUIT_PARALLEL + conduit::relay::mpi::io::blueprint::save_mesh(n, output, protocol, MPI_COMM_WORLD); + + MPI_Finalize(); +#else + conduit::relay::io::save(n, output + "-inspect.yaml", "yaml"); + conduit::relay::io::blueprint::save_mesh(n, output, protocol); +#endif + + return 0; +} diff --git a/src/libs/blueprint/CMakeLists.txt b/src/libs/blueprint/CMakeLists.txt index f7efc3a71..d457699b6 100644 --- a/src/libs/blueprint/CMakeLists.txt +++ b/src/libs/blueprint/CMakeLists.txt @@ -39,6 +39,7 @@ set(blueprint_headers conduit_blueprint_mesh_examples_related_boundary.hpp conduit_blueprint_mesh_examples_polystar.hpp conduit_blueprint_mesh_examples_rz_cylinder.hpp + conduit_blueprint_mesh_examples_tiled.hpp conduit_blueprint_mcarray.hpp conduit_blueprint_mcarray_examples.hpp conduit_blueprint_o2mrelation.hpp @@ -81,6 +82,7 @@ set(blueprint_sources conduit_blueprint_mesh_examples_related_boundary.cpp conduit_blueprint_mesh_examples_polystar.cpp conduit_blueprint_mesh_examples_rz_cylinder.cpp + conduit_blueprint_mesh_examples_tiled.cpp conduit_blueprint_mesh_flatten.cpp conduit_blueprint_mcarray.cpp conduit_blueprint_mcarray_examples.cpp diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples.hpp b/src/libs/blueprint/conduit_blueprint_mesh_examples.hpp index 07a04dd89..6a24d08c1 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples.hpp @@ -22,6 +22,7 @@ #include "conduit_blueprint_mesh_examples_related_boundary.hpp" #include "conduit_blueprint_mesh_examples_polystar.hpp" #include "conduit_blueprint_mesh_examples_rz_cylinder.hpp" +#include "conduit_blueprint_mesh_examples_tiled.hpp" //----------------------------------------------------------------------------- // -- begin conduit::-- diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp new file mode 100644 index 000000000..bb13fb0fb --- /dev/null +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -0,0 +1,479 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other Conduit +// Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +// other details. No copyright assignment is required to contribute to Conduit. + +//----------------------------------------------------------------------------- +/// +/// file: conduit_blueprint_mesh_examples_tiled.hpp +/// +//----------------------------------------------------------------------------- + +#ifndef CONDUIT_BLUEPRINT_MESH_EXAMPLES_TILED_HPP +#define CONDUIT_BLUEPRINT_MESH_EXAMPLES_TILED_HPP + +//----------------------------------------------------------------------------- +// conduit lib includes +//----------------------------------------------------------------------------- +#include "conduit.hpp" +#include "conduit_blueprint.hpp" +#include "conduit_blueprint_exports.h" + +//----------------------------------------------------------------------------- +// -- begin conduit::-- +//----------------------------------------------------------------------------- +namespace conduit +{ + +//----------------------------------------------------------------------------- +// -- begin conduit::blueprint -- +//----------------------------------------------------------------------------- +namespace blueprint +{ + +//----------------------------------------------------------------------------- +// -- begin conduit::blueprint::mesh -- +//----------------------------------------------------------------------------- +namespace mesh +{ + +//----------------------------------------------------------------------------- +/// Methods that generate example meshes. +//----------------------------------------------------------------------------- +namespace examples +{ + +//----------------------------------------------------------------------------- +/// Detail +//----------------------------------------------------------------------------- +namespace detail +{ + +/** + \brief Keep track of some tile information. + */ +class Tile +{ +public: + Tile() : ptids() + { + } + + /// Reset the tile. + void reset(size_t npts) + { + ptids = std::vector(npts, -1); + } + + /// Return the point ids. + std::vector &getPointIds() { return ptids; } + const std::vector &getPointIds() const { return ptids; } + + /// Get the specified point ids for this tile using the supplied indices. + std::vector getPointIds(const std::vector &indices) const + { + std::vector ids; + ids.reserve(indices.size()); + for(const auto &idx : indices) + ids.push_back(ptids[idx]); + return ids; + } + + // Set the point ids + void setPointIds(const std::vector &indices, const std::vector &ids) + { + for(size_t i = 0; i < indices.size(); i++) + { + ptids[indices[i]] = ids[i]; + } + } + +private: + std::vector ptids; //!< This tile's point ids. +}; + +/** + \brief Build a mesh from tiles. There is a default tile pattern, although it can + be replaced using an options Node containing new tile information. + */ +class Tiler +{ +public: + Tiler(); + + /// Generate the tiled mesh. + void generate(int nx, int ny, int nz, + conduit::Node &res, + const conduit::Node &options); +protected: + /// Fill a default tile pattern into the filter. + void initialize(); + + /// Fill in the tile pattern from a Node. + void initialize(const conduit::Node &t); + + /// Return point indices of points along left edge. + const std::vector &left() const { return m_left; } + + /// Return point indices of points along right edge. + const std::vector &right() const { return m_right; } + + /// Return point indices of points along bottom edge. + const std::vector &bottom() const { return m_bottom; } + + /// Return point indices of points along top edge. + const std::vector &top() const { return m_right; } + + /// Return tile width + double width() const { return m_width; } + + /// Return tile height + double height() const { return m_height; } + + /// Creates the points for the tile (if they need to be created). + void addPoints(double origin[2], + std::vector &ptids, + std::vector &x, + std::vector &y) + { + // Iterate through points in the template and add them if they have + // not been created yet. + for(size_t i = 0; i < m_xpts.size(); i++) + { + if(ptids[i] == -1) + { + ptids[i] = static_cast(x.size()); + x.push_back(origin[0] + m_xpts[i]); + y.push_back(origin[1] + m_ypts[i]); + } + } + } + + /// Emit the quad cells using this tile's point ids. + void addQuads(const std::vector &ptids, std::vector &conn, std::vector &sizes) const + { + const size_t nquads = m_quads.size() / 4; + for(size_t i = 0; i < nquads; i++) + { + conn.push_back(ptids[m_quads[4*i + 0]]); + conn.push_back(ptids[m_quads[4*i + 1]]); + conn.push_back(ptids[m_quads[4*i + 2]]); + conn.push_back(ptids[m_quads[4*i + 3]]); + sizes.push_back(4); + } + } + + /// Emit the hex cells using this tile's point ids. + void addHexs(const std::vector &ptids, int plane1Offset, int plane2Offset, std::vector &conn, std::vector &sizes) const + { + const size_t nquads = m_quads.size() / 4; + for(size_t i = 0; i < nquads; i++) + { + conn.push_back(plane1Offset + ptids[m_quads[4*i + 0]]); + conn.push_back(plane1Offset + ptids[m_quads[4*i + 1]]); + conn.push_back(plane1Offset + ptids[m_quads[4*i + 2]]); + conn.push_back(plane1Offset + ptids[m_quads[4*i + 3]]); + + conn.push_back(plane2Offset + ptids[m_quads[4*i + 0]]); + conn.push_back(plane2Offset + ptids[m_quads[4*i + 1]]); + conn.push_back(plane2Offset + ptids[m_quads[4*i + 2]]); + conn.push_back(plane2Offset + ptids[m_quads[4*i + 3]]); + + sizes.push_back(8); + } + } + + /// Compute the extents of the supplied values. + double computeExtents(const std::vector &values) const + { + double ext[2] = {values[0], values[0]}; + for(auto val : values) + { + ext[0] = std::min(ext[0], val); + ext[1] = std::max(ext[1], val); + } + return ext[1] - ext[0]; + } + + std::vector toDoubleVector(const conduit::Node &n) const + { + auto acc = n.as_double_accessor(); + std::vector vec; + vec.reserve(acc.number_of_elements()); + for(conduit::index_t i = 0; i < acc.number_of_elements(); i++) + vec.push_back(acc[i]); + return vec; + } + + std::vector toIntVector(const conduit::Node &n) const + { + auto acc = n.as_int_accessor(); + std::vector vec; + vec.reserve(acc.number_of_elements()); + for(conduit::index_t i = 0; i < acc.number_of_elements(); i++) + vec.push_back(acc[i]); + return vec; + } + +private: + std::vector m_xpts, m_ypts; + double m_width, m_height; + std::vector m_left, m_right, m_bottom, m_top, m_quads; +}; + +Tiler::Tiler() : m_xpts(), m_ypts(), m_width(0.), m_height(0.), + m_left(), m_right(), m_bottom(), m_top(), m_quads() +{ + initialize(); +} + +void +Tiler::initialize() +{ + // Default pattern + m_xpts = std::vector{ + 0., 3., 10., 17., 20., + 0., 3., 17., 20., + 5., 15., + 7., 10., 13., + 0., 7., 10., 13., 20., + 7., 10., 13., + 5., 15., + 0., 3., 17., 20., + 0., 3., 10., 17., 20., + }; + + m_ypts = std::vector{ + 0., 0., 0., 0., 0., + 3., 3., 3., 3., + 5., 5., + 7., 7., 7., + 10., 10., 10., 10., 10., + 13., 13., 13., + 15., 15., + 17., 17., 17., 17., + 20., 20., 20., 20., 20., + }; + + m_quads = std::vector{ + // lower-left quadrant + 0,1,6,5, + 1,2,9,6, + 2,12,11,9, + 5,6,9,14, + 9,11,15,14, + 11,12,16,15, + // lower-right quadrant + 2,3,7,10, + 3,4,8,7, + 7,8,18,10, + 2,10,13,12, + 12,13,17,16, + 10,18,17,13, + // upper-left quadrant + 14,22,25,24, + 14,15,19,22, + 15,16,20,19, + 24,25,29,28, + 22,30,29,25, + 19,20,30,22, + // upper-right quadrant + 16,17,21,20, + 17,18,23,21, + 18,27,26,23, + 20,21,23,30, + 23,26,31,30, + 26,27,32,31 + }; + + m_left = std::vector{0,5,14,24,28}; + m_right = std::vector{4,8,18,27,32}; + m_bottom = std::vector{0,1,2,3,4}; + m_top = std::vector{28,29,30,31,32}; + + m_width = computeExtents(m_xpts); + m_height = computeExtents(m_ypts); +} + +void +Tiler::initialize(const conduit::Node &t) +{ + m_xpts = toDoubleVector(t.fetch_existing("x")); + m_ypts = toDoubleVector(t.fetch_existing("y")); + m_quads = toIntVector(t.fetch_existing("quads")); + m_left = toIntVector(t.fetch_existing("left")); + m_right = toIntVector(t.fetch_existing("right")); + m_bottom = toIntVector(t.fetch_existing("bottom")); + m_top = toIntVector(t.fetch_existing("top")); + + m_width = computeExtents(m_xpts); + m_height = computeExtents(m_ypts); +} + +/** + \brief Generate coordinate and connectivity arrays using a tiled mesh pattern, + given by the Tile class. + + \param nx The number of tiles in the X dimension. + \param ny The number of tiles in the Y dimension. + \param nz The number of tiles in the Z dimension. + \param[out] res The output node. + \param options A node that may contain additional control options. + */ +void +Tiler::generate(int nx, int ny, int nz, + conduit::Node &res, + const conduit::Node &options) +{ + double origin[] = {0., 0., 0.}; + std::vector x, y, z; + std::vector conn, sizes; + + // Process any options. + if(options.has_path("origin/x")) + origin[0] = options.fetch_existing("origin/x").to_double(); + if(options.has_path("origin/y")) + origin[1] = options.fetch_existing("origin/y").to_double(); + if(options.has_path("origin/z")) + origin[2] = options.fetch_existing("origin/z").to_double(); + if(options.has_path("tile")) + initialize(options.fetch_existing("tile")); + + // Make a pass where we make nx*ny tiles so we can generate their points. + std::vector tiles(nx * ny); + double newOrigin[] = {origin[0], origin[1], origin[2]}; + for(int j = 0; j < ny; j++) + { + newOrigin[0] = origin[0]; + for(int i = 0; i < nx; i++) + { + Tile ¤t = tiles[(j*nx + i)]; + + // The first time we've used the tile, set its size. + current.reset(m_xpts.size()); + + // Copy some previous points over so they can be shared. + if(i > 0) + { + Tile &prevX = tiles[(j*nx + i - 1)]; + current.setPointIds(left(), prevX.getPointIds(right())); + } + if(j > 0) + { + Tile &prevY = tiles[((j-1)*nx + i)]; + current.setPointIds(bottom(), prevY.getPointIds(top())); + } + + addPoints(newOrigin, current.getPointIds(), x, y); + newOrigin[0] += width(); + } + newOrigin[1] += height(); + } + + if(nz < 1) + { + // Iterate over the tiles and add their quads. + // TODO: reserve size for conn, sizes + for(int j = 0; j < ny; j++) + { + for(int i = 0; i < nx; i++) + { + Tile ¤t = tiles[(j*nx + i)]; + addQuads(current.getPointIds(), conn, sizes); + } + } + // NOTE: z coords in output will be empty. + } + else + { + // We have x,y points now. We need to replicate them to make multiple planes. + // We make z coordinates too. + size_t ptsPerPlane = x.size(); + int nplanes = nz + 1; + x.reserve(ptsPerPlane * nplanes); + y.reserve(ptsPerPlane * nplanes); + z.reserve(ptsPerPlane * nplanes); + for(size_t i = 0; i < ptsPerPlane; i++) + z.push_back(origin[2]); + for(int p = 1; p < nplanes; p++) + { + double zvalue = origin[2] + static_cast(p) * std::max(width(), height()); + for(size_t i = 0; i < ptsPerPlane; i++) + { + x.push_back(x[i]); + y.push_back(y[i]); + z.push_back(zvalue); + } + } + + // Iterate over the tiles and add their hexs. + // TODO: reserve size for conn, sizes + for(int k = 0; k < nz; k++) + { + int offset1 = k * ptsPerPlane; + int offset2 = offset1 + ptsPerPlane; + + for(int j = 0; j < ny; j++) + { + for(int i = 0; i < nx; i++) + { + Tile ¤t = tiles[(j*nx + i)]; + addHexs(current.getPointIds(), offset1, offset2, conn, sizes); + } + } + } + } + + // TODO: We should output edges or faces for the tiles that are external so + // we can make a boundary topology. + + res["coordsets/coords/type"] = "explicit"; + res["coordsets/coords/values"] = "explicit"; + res["coordsets/coords/values/x"].set(x); + res["coordsets/coords/values/y"].set(y); + if(!z.empty()) + res["coordsets/coords/values/z"].set(z); + + res["topologies/mesh/type"] = "unstructured"; + res["topologies/mesh/coordset"] = "coords"; + res["topologies/mesh/elements/shape"] = z.empty() ? "quad" : "hex"; + res["topologies/mesh/elements/connectivity"].set(conn); + res["topologies/mesh/elements/sizes"].set(sizes); +} + +} +//----------------------------------------------------------------------------- +// -- end conduit::blueprint::mesh::examples::detail -- +//----------------------------------------------------------------------------- + +void +tiled(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, + conduit::Node &res, const conduit::Node &options) +{ + detail::Tiler T; + T.generate(nx, ny, nz, res, options); +} + +} +//----------------------------------------------------------------------------- +// -- end conduit::blueprint::mesh::examples -- +//----------------------------------------------------------------------------- + +} +//----------------------------------------------------------------------------- +// -- end conduit::blueprint::mesh -- +//----------------------------------------------------------------------------- + +} +//----------------------------------------------------------------------------- +// -- end conduit::blueprint -- +//----------------------------------------------------------------------------- + +} +//----------------------------------------------------------------------------- +// -- end conduit -- +//----------------------------------------------------------------------------- + +#endif + + + diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp new file mode 100644 index 000000000..6d5615121 --- /dev/null +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp @@ -0,0 +1,78 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other Conduit +// Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +// other details. No copyright assignment is required to contribute to Conduit. + +//----------------------------------------------------------------------------- +/// +/// file: conduit_blueprint_mesh_examples_tiled.hpp +/// +//----------------------------------------------------------------------------- + +#ifndef CONDUIT_BLUEPRINT_MESH_EXAMPLES_TILED_HPP +#define CONDUIT_BLUEPRINT_MESH_EXAMPLES_TILED_HPP + +//----------------------------------------------------------------------------- +// conduit lib includes +//----------------------------------------------------------------------------- +#include "conduit.hpp" +#include "conduit_blueprint.hpp" +#include "conduit_blueprint_exports.h" + +//----------------------------------------------------------------------------- +// -- begin conduit::-- +//----------------------------------------------------------------------------- +namespace conduit +{ + +//----------------------------------------------------------------------------- +// -- begin conduit::blueprint -- +//----------------------------------------------------------------------------- +namespace blueprint +{ + +//----------------------------------------------------------------------------- +// -- begin conduit::blueprint::mesh -- +//----------------------------------------------------------------------------- +namespace mesh +{ + +//----------------------------------------------------------------------------- +/// Methods that generate example meshes. +//----------------------------------------------------------------------------- +namespace examples +{ + /// Generates a tiled unstructured mesh of quads or hexs. + void CONDUIT_BLUEPRINT_API tiled(index_t nx, + index_t ny, + index_t nz, + conduit::Node &res, + const conduit::Node &options); + +} +//----------------------------------------------------------------------------- +// -- end conduit::blueprint::mesh::examples -- +//----------------------------------------------------------------------------- + + +//----------------------------------------------------------------------------- +} +//----------------------------------------------------------------------------- +// -- end conduit::blueprint::mesh -- +//----------------------------------------------------------------------------- + + +} +//----------------------------------------------------------------------------- +// -- end conduit::blueprint -- +//----------------------------------------------------------------------------- + +} +//----------------------------------------------------------------------------- +// -- end conduit -- +//----------------------------------------------------------------------------- + + +#endif + + + From e2b0e3b409d69037a4312b267bc8c067263da63b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Sun, 27 Aug 2023 18:17:50 -0700 Subject: [PATCH 02/50] Fixed a bug --- src/executables/generate_data/conduit_generate_data.cpp | 2 +- src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/executables/generate_data/conduit_generate_data.cpp b/src/executables/generate_data/conduit_generate_data.cpp index 7f0845ce5..56ece930f 100644 --- a/src/executables/generate_data/conduit_generate_data.cpp +++ b/src/executables/generate_data/conduit_generate_data.cpp @@ -169,7 +169,7 @@ main(int argc, char *argv[]) { dims[0] = std::max(d[0], 1); dims[1] = std::max(d[1], 1); - dims[2] = std::max(d[2], 1); + dims[2] = std::max(d[2], 0); // Allow 0 for 2D } i++; } diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index bb13fb0fb..cad0aaab4 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -121,7 +121,7 @@ class Tiler const std::vector &bottom() const { return m_bottom; } /// Return point indices of points along top edge. - const std::vector &top() const { return m_right; } + const std::vector &top() const { return m_top; } /// Return tile width double width() const { return m_width; } From 98f936e9db311827ea9d0f983811560ab9185d66 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Sun, 27 Aug 2023 18:50:20 -0700 Subject: [PATCH 03/50] 2D boundaries --- .../conduit_blueprint_mesh_examples_tiled.cpp | 105 +++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index cad0aaab4..787c8c173 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -214,6 +214,10 @@ class Tiler return vec; } + /// Make 2D boundaries. + void makeBoundaries2D(const std::vector &tiles, int nx, int ny, + std::vector &bconn, std::vector &bsizes, + std::vector &btype, const conduit::Node &options) const; private: std::vector m_xpts, m_ypts; double m_width, m_height; @@ -326,7 +330,7 @@ Tiler::generate(int nx, int ny, int nz, { double origin[] = {0., 0., 0.}; std::vector x, y, z; - std::vector conn, sizes; + std::vector conn, sizes, bconn, bsizes, btype; // Process any options. if(options.has_path("origin/x")) @@ -382,6 +386,21 @@ Tiler::generate(int nx, int ny, int nz, } } // NOTE: z coords in output will be empty. + + // Boundaries + makeBoundaries2D(tiles, nx, ny, bconn, bsizes, btype, options); + if(!bconn.empty()) + { + res["topologies/boundary/type"] = "unstructured"; + res["topologies/boundary/coordset"] = "coords"; + res["topologies/boundary/elements/shape"] = "line"; + res["topologies/boundary/elements/connectivity"].set(bconn); + res["topologies/boundary/elements/sizes"].set(bsizes); + + res["fields/boundary_type/topology"] = "boundary"; + res["fields/boundary_type/association"] = "element"; + res["fields/boundary_type/values"].set(btype); + } } else { @@ -421,6 +440,23 @@ Tiler::generate(int nx, int ny, int nz, } } } + + // Boundaries +#if 0 + makeBoundaries3D(tiles, nx, ny, nz, ptsPerPlane, bconn, bsizes, btype, options); + if(!bconn.empty()) + { + res["topologies/boundary/type"] = "unstructured"; + res["topologies/boundary/coordset"] = "coords"; + res["topologies/boundary/elements/shape"] = "quad"; + res["topologies/boundary/elements/connectivity"].set(bconn); + res["topologies/boundary/elements/sizes"].set(bsizes); + + res["fields/boundary_type/topology"] = "boundary"; + res["fields/boundary_type/association"] = "element"; + res["fields/boundary_type/values"].set(btype); + } +#endif } // TODO: We should output edges or faces for the tiles that are external so @@ -440,6 +476,73 @@ Tiler::generate(int nx, int ny, int nz, res["topologies/mesh/elements/sizes"].set(sizes); } +void +Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, + std::vector &bconn, std::vector &bsizes, std::vector &btype, + const conduit::Node &options) const +{ + if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) + { + for(int i = 0, j = ny-1; j >= 0; j--) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(left()); + for(size_t bi = ids.size() - 1; bi > 1; bi--) + { + bconn.push_back(ids[bi]); + bconn.push_back(ids[bi - 1]); + bsizes.push_back(2); + btype.push_back(0); + } + } + } + if(options.has_path("boundaries/bottom") && options.fetch_existing("boundaries/bottom").to_int() > 0) + { + for(int i = 0, j = 0; i < nx; i++) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(bottom()); + for(size_t bi = 0; bi < ids.size() - 1; bi++) + { + bconn.push_back(ids[bi]); + bconn.push_back(ids[bi + 1]); + bsizes.push_back(2); + btype.push_back(2); + } + } + } + if(options.has_path("boundaries/right") && options.fetch_existing("boundaries/right").to_int() > 0) + { + for(int i = 0, j = 0; j < ny; j++) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(right()); + for(size_t bi = 0; bi < ids.size() - 1; bi++) + { + bconn.push_back(ids[bi]); + bconn.push_back(ids[bi + 1]); + bsizes.push_back(2); + btype.push_back(1); + } + } + } + if(options.has_path("boundaries/top") && options.fetch_existing("boundaries/top").to_int() > 0) + { + for(int i = nx - 1, j = 0; i >= 0; i--) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(top()); + for(size_t bi = ids.size() - 1; bi > 1; bi--) + { + bconn.push_back(ids[bi]); + bconn.push_back(ids[bi - 1]); + bsizes.push_back(2); + btype.push_back(3); + } + } + } +} + } //----------------------------------------------------------------------------- // -- end conduit::blueprint::mesh::examples::detail -- From 300794e2b821ac61feef9db5be90b6c37c50ffee Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Sun, 27 Aug 2023 18:57:27 -0700 Subject: [PATCH 04/50] Boundary fix --- .../blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 787c8c173..e8b65bce3 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -487,7 +487,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(left()); - for(size_t bi = ids.size() - 1; bi > 1; bi--) + for(size_t bi = ids.size() - 1; bi > 0; bi--) { bconn.push_back(ids[bi]); bconn.push_back(ids[bi - 1]); @@ -513,7 +513,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, } if(options.has_path("boundaries/right") && options.fetch_existing("boundaries/right").to_int() > 0) { - for(int i = 0, j = 0; j < ny; j++) + for(int i = nx - 1, j = 0; j < ny; j++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(right()); @@ -528,11 +528,11 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, } if(options.has_path("boundaries/top") && options.fetch_existing("boundaries/top").to_int() > 0) { - for(int i = nx - 1, j = 0; i >= 0; i--) + for(int i = nx - 1, j = ny - 1; i >= 0; i--) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(top()); - for(size_t bi = ids.size() - 1; bi > 1; bi--) + for(size_t bi = ids.size() - 1; bi > 0; bi--) { bconn.push_back(ids[bi]); bconn.push_back(ids[bi - 1]); From 20032bb1ce9eb32e40a04998c55d9225801e04b8 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Sun, 27 Aug 2023 20:04:19 -0700 Subject: [PATCH 05/50] Roughed in 3D boundaries --- .../conduit_blueprint_mesh_examples_tiled.cpp | 145 +++++++++++++++++- 1 file changed, 137 insertions(+), 8 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index e8b65bce3..90aa1b0c2 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -149,15 +149,18 @@ class Tiler } /// Emit the quad cells using this tile's point ids. - void addQuads(const std::vector &ptids, std::vector &conn, std::vector &sizes) const + void addFaces(const std::vector &ptids, + std::vector &conn, std::vector &sizes, + int offset = 0, bool reverse = false) const { const size_t nquads = m_quads.size() / 4; + int order[] = {reverse ? 3 : 0, reverse ? 2 : 1, reverse ? 1 : 2, reverse ? 0 : 3}; for(size_t i = 0; i < nquads; i++) { - conn.push_back(ptids[m_quads[4*i + 0]]); - conn.push_back(ptids[m_quads[4*i + 1]]); - conn.push_back(ptids[m_quads[4*i + 2]]); - conn.push_back(ptids[m_quads[4*i + 3]]); + conn.push_back(offset + ptids[m_quads[4*i + order[0]]]); + conn.push_back(offset + ptids[m_quads[4*i + order[1]]]); + conn.push_back(offset + ptids[m_quads[4*i + order[2]]]); + conn.push_back(offset + ptids[m_quads[4*i + order[3]]]); sizes.push_back(4); } } @@ -194,6 +197,7 @@ class Tiler return ext[1] - ext[0]; } + /// Turn a node into a double vector. std::vector toDoubleVector(const conduit::Node &n) const { auto acc = n.as_double_accessor(); @@ -204,6 +208,7 @@ class Tiler return vec; } + /// Turn a node into an int vector. std::vector toIntVector(const conduit::Node &n) const { auto acc = n.as_int_accessor(); @@ -218,6 +223,12 @@ class Tiler void makeBoundaries2D(const std::vector &tiles, int nx, int ny, std::vector &bconn, std::vector &bsizes, std::vector &btype, const conduit::Node &options) const; + + /// Make 3D boundaries. + void makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, + size_t nPtsPerPlane, + std::vector &bconn, std::vector &bsizes, + std::vector &btype, const conduit::Node &options) const; private: std::vector m_xpts, m_ypts; double m_width, m_height; @@ -382,7 +393,7 @@ Tiler::generate(int nx, int ny, int nz, for(int i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; - addQuads(current.getPointIds(), conn, sizes); + addFaces(current.getPointIds(), conn, sizes); } } // NOTE: z coords in output will be empty. @@ -442,7 +453,6 @@ Tiler::generate(int nx, int ny, int nz, } // Boundaries -#if 0 makeBoundaries3D(tiles, nx, ny, nz, ptsPerPlane, bconn, bsizes, btype, options); if(!bconn.empty()) { @@ -456,7 +466,6 @@ Tiler::generate(int nx, int ny, int nz, res["fields/boundary_type/association"] = "element"; res["fields/boundary_type/values"].set(btype); } -#endif } // TODO: We should output edges or faces for the tiles that are external so @@ -543,6 +552,126 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, } } +void +Tiler::makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, + size_t nPtsPerPlane, + std::vector &bconn, std::vector &bsizes, std::vector &btype, + const conduit::Node &options) const +{ + if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) + { + for(int k = 0; k < nz; k++) + { + int offset1 = k * nPtsPerPlane; + int offset2 = (k + 1) * nPtsPerPlane; + for(int i = 0, j = ny-1; j >= 0; j--) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(left()); + for(size_t bi = ids.size() - 1; bi > 0; bi--) + { + bconn.push_back(offset1 + ids[bi]); + bconn.push_back(offset1 + ids[bi - 1]); + bconn.push_back(offset2 + ids[bi - 1]); + bconn.push_back(offset2 + ids[bi]); + bsizes.push_back(4); + btype.push_back(0); + } + } + } + } + if(options.has_path("boundaries/right") && options.fetch_existing("boundaries/right").to_int() > 0) + { + for(int k = 0; k < nz; k++) + { + int offset1 = k * nPtsPerPlane; + int offset2 = (k + 1) * nPtsPerPlane; + for(int i = nx - 1, j = 0; j < ny; j++) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(right()); + for(size_t bi = 0; bi < ids.size() - 1; bi++) + { + bconn.push_back(offset1 + ids[bi]); + bconn.push_back(offset1 + ids[bi + 1]); + bconn.push_back(offset2 + ids[bi + 1]); + bconn.push_back(offset2 + ids[bi]); + bsizes.push_back(4); + btype.push_back(1); + } + } + } + } + if(options.has_path("boundaries/bottom") && options.fetch_existing("boundaries/bottom").to_int() > 0) + { + for(int k = 0; k < nz; k++) + { + int offset1 = k * nPtsPerPlane; + int offset2 = (k + 1) * nPtsPerPlane; + for(int i = 0, j = 0; i < nx; i++) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(bottom()); + for(size_t bi = 0; bi < ids.size() - 1; bi++) + { + bconn.push_back(offset1 + ids[bi]); + bconn.push_back(offset1 + ids[bi + 1]); + bconn.push_back(offset2 + ids[bi + 1]); + bconn.push_back(offset2 + ids[bi]); + bsizes.push_back(4); + btype.push_back(2); + } + } + } + } + if(options.has_path("boundaries/top") && options.fetch_existing("boundaries/top").to_int() > 0) + { + for(int k = 0; k < nz; k++) + { + int offset1 = k * nPtsPerPlane; + int offset2 = (k + 1) * nPtsPerPlane; + for(int i = nx - 1, j = ny - 1; i >= 0; i--) + { + const Tile ¤t = tiles[(j*nx + i)]; + const auto ids = current.getPointIds(top()); + for(size_t bi = ids.size() - 1; bi > 0; bi--) + { + bconn.push_back(offset1 + ids[bi]); + bconn.push_back(offset1 + ids[bi - 1]); + bconn.push_back(offset2 + ids[bi - 1]); + bconn.push_back(offset2 + ids[bi]); + bsizes.push_back(4); + btype.push_back(3); + } + } + } + } + if(options.has_path("boundaries/back") && options.fetch_existing("boundaries/back").to_int() > 0) + { + for(int j = 0; j < ny; j++) + for(int i = nx - 1; i >= 0; i--) + { + const Tile ¤t = tiles[(j*nx + i)]; + size_t s0 = bsizes.size(); + addFaces(current.getPointIds(), bconn, bsizes, 0, true); + for( ; s0 < bsizes.size(); s0++) + btype.push_back(4); + } + } + if(options.has_path("boundaries/front") && options.fetch_existing("boundaries/front").to_int() > 0) + { + for(int j = 0; j < ny; j++) + for(int i = 0; i < nx; i++) + { + const Tile ¤t = tiles[(j*nx + i)]; + size_t s0 = bsizes.size(); + addFaces(current.getPointIds(), bconn, bsizes, nz * nPtsPerPlane); + for( ; s0 < bsizes.size(); s0++) + btype.push_back(5); + } + } +} + } //----------------------------------------------------------------------------- // -- end conduit::blueprint::mesh::examples::detail -- From 984a541cb56a1f321daeace4a45c71f76ed925cd Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Sun, 27 Aug 2023 20:13:33 -0700 Subject: [PATCH 06/50] Remove obsolete comment --- src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 90aa1b0c2..5e9402f38 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -468,9 +468,6 @@ Tiler::generate(int nx, int ny, int nz, } } - // TODO: We should output edges or faces for the tiles that are external so - // we can make a boundary topology. - res["coordsets/coords/type"] = "explicit"; res["coordsets/coords/values"] = "explicit"; res["coordsets/coords/values/x"].set(x); From d3a6c8c80a73ee7be3c67fc4ed85cede0c3d69ad Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 1 Sep 2023 19:26:04 -0700 Subject: [PATCH 07/50] Added method to access sorted index. --- src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp b/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp index 615b0275a..6d6b363b7 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp @@ -106,6 +106,18 @@ class kdtree */ inline int dims() const { return NDIMS; } + /** + @brief Return the sorted index order. + @return A vector sorted spatially. The elements contain the original indices. + */ + std::vector &getIndices() { return index; } + + /** + @brief Return the sorted index order. + @return A vector sorted spatially. The elements contain the original indices. + */ + const std::vector &getIndices() const { return index; } + /** @brief Print the tree to a stream. @param os A stream to use for printing. From b71f2c40bf9c3eb8c4fb9b40c82c418874f07632 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 1 Sep 2023 19:26:33 -0700 Subject: [PATCH 08/50] Prototyping zone reordering function - needs fixes + cleanup still. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 365 +++++++++++++++++- 1 file changed, 364 insertions(+), 1 deletion(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 5e9402f38..96e7a52db 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -17,6 +17,8 @@ #include "conduit.hpp" #include "conduit_blueprint.hpp" #include "conduit_blueprint_exports.h" +#include "conduit_blueprint_mesh_kdtree.hpp" +#include "conduit_blueprint_mesh_utils.hpp" //----------------------------------------------------------------------------- // -- begin conduit::-- @@ -48,6 +50,283 @@ namespace examples namespace detail { +std::vector spatial_reordering(const conduit::Node &topo) +{ + // Make a new centroid topo and coordset. The coordset will contain the + // element centers. + Node topo_dest, coords_dest, s2dmap, d2smap; + mesh::topology::unstructured::generate_centroids(topo, + topo_dest, + coords_dest, + s2dmap, + d2smap); + // Bundle the coordset components into a vector. + std::vector coords; + const conduit::Node &values = coords_dest.fetch_existing("values"); + for(conduit::index_t i = 0; i < values.number_of_children(); i++) + { + coords.push_back(values[i].as_double_accessor()); + } + + // Sort the coordinates spatially + std::vector reorder; + if(coords.size() == 2) + { + conduit::blueprint::mesh::utils::kdtree spatial_sort; + spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); + reorder = std::move(spatial_sort.getIndices()); + } + else if(coords.size() == 3) + { + conduit::blueprint::mesh::utils::kdtree spatial_sort; + spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); + reorder = std::move(spatial_sort.getIndices()); + } + return reorder; +} +//--------------------------------------------------------------------------- +// @brief Slice the n_src array using the indices stored in ids. We use the +// array classes for their [] operators that deal with interleaved +// and non-interleaved arrays. +template +inline void +typed_slice_array(const T &src, const std::vector &ids, T &dest) +{ + size_t n = ids.size(); + for(size_t i = 0; i < n; i++) + dest[i] = src[ids[i]]; +} + +//--------------------------------------------------------------------------- +// @note Should this be part of conduit::Node or DataArray somehow. The number +// of times I've had to slice an array... +template +void +slice_array(const conduit::Node &n_src_values, + const std::vector &ids, + Node &n_dest_values) +{ + // Copy the DataType of the input conduit::Node but override the number of elements + // before copying it in so assigning to n_dest_values triggers a memory + // allocation. + auto dt = n_src_values.dtype(); + n_dest_values = DataType(n_src_values.dtype().id(), ids.size()); + + // Do the slice. + if(dt.is_int8()) + { + auto dest(n_dest_values.as_int8_array()); + typed_slice_array(n_src_values.as_int8_array(), ids, dest); + } + else if(dt.is_int16()) + { + auto dest(n_dest_values.as_int16_array()); + typed_slice_array(n_src_values.as_int16_array(), ids, dest); + } + else if(dt.is_int32()) + { + auto dest(n_dest_values.as_int32_array()); + typed_slice_array(n_src_values.as_int32_array(), ids, dest); + } + else if(dt.is_int64()) + { + auto dest(n_dest_values.as_int64_array()); + typed_slice_array(n_src_values.as_int64_array(), ids, dest); + } + else if(dt.is_uint8()) + { + auto dest(n_dest_values.as_uint8_array()); + typed_slice_array(n_src_values.as_uint8_array(), ids, dest); + } + else if(dt.is_uint16()) + { + auto dest(n_dest_values.as_uint16_array()); + typed_slice_array(n_src_values.as_uint16_array(), ids, dest); + } + else if(dt.is_uint32()) + { + auto dest(n_dest_values.as_uint32_array()); + typed_slice_array(n_src_values.as_uint32_array(), ids, dest); + } + else if(dt.is_uint64()) + { + auto dest(n_dest_values.as_uint64_array()); + typed_slice_array(n_src_values.as_uint64_array(), ids, dest); + } + else if(dt.is_char()) + { + auto dest(n_dest_values.as_char_array()); + typed_slice_array(n_src_values.as_char_array(), ids, dest); + } + else if(dt.is_short()) + { + auto dest(n_dest_values.as_short_array()); + typed_slice_array(n_src_values.as_short_array(), ids, dest); + } + else if(dt.is_int()) + { + auto dest(n_dest_values.as_int_array()); + typed_slice_array(n_src_values.as_int_array(), ids, dest); + } + else if(dt.is_long()) + { + auto dest(n_dest_values.as_long_array()); + typed_slice_array(n_src_values.as_long_array(), ids, dest); + } + else if(dt.is_unsigned_char()) + { + auto dest(n_dest_values.as_unsigned_char_array()); + typed_slice_array(n_src_values.as_unsigned_char_array(), ids, dest); + } + else if(dt.is_unsigned_short()) + { + auto dest(n_dest_values.as_unsigned_short_array()); + typed_slice_array(n_src_values.as_unsigned_short_array(), ids, dest); + } + else if(dt.is_unsigned_int()) + { + auto dest(n_dest_values.as_unsigned_int_array()); + typed_slice_array(n_src_values.as_unsigned_int_array(), ids, dest); + } + else if(dt.is_unsigned_long()) + { + auto dest(n_dest_values.as_unsigned_long_array()); + typed_slice_array(n_src_values.as_unsigned_long_array(), ids, dest); + } + else if(dt.is_float()) + { + auto dest(n_dest_values.as_float_array()); + typed_slice_array(n_src_values.as_float_array(), ids, dest); + } + else if(dt.is_double()) + { + auto dest(n_dest_values.as_double_array()); + typed_slice_array(n_src_values.as_double_array(), ids, dest); + } +} + +void +slice_field(const conduit::Node &src, + conduit::Node &dest, + const std::vector &indices) +{ + if(src.number_of_children() > 0) + { + // Reorder an mcarray + for(conduit::index_t ci = 0; ci < src.number_of_children(); ci++) + { + const conduit::Node &comp = src[ci]; + slice_array(comp, indices, dest[comp.name()]); + } + } + else + { + slice_array(src, indices, dest); + } +} + +void +reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const conduit::Node &fields, + conduit::Node &dest_topo, conduit::Node &dest_coordset, conduit::Node &dest_fields, + const std::vector &reorder) +{ + conduit::blueprint::mesh::utils::ShapeType shape(topo); + + // Handle unstructured meshes (but not polyhedral meshes yet) + if(topo.fetch_existing("type").as_string() == "unstructured" && !shape.is_polyhedral()) + { + // Input connectivity information. + const auto &n_conn = topo.fetch_existing("elements/connectivity"); + const auto &n_sizes = topo.fetch_existing("elements/sizes"); + const auto &n_offsets = topo.fetch_existing("elements/offsets"); + const auto conn = n_conn.as_index_t_accessor(); + const auto sizes = n_sizes.as_index_t_accessor(); + const auto offsets = n_offsets.as_index_t_accessor(); + + // Temp vectors to store reordered connectivity. + std::vector newconn, newoffsets, newsizes; + newconn.reserve(conn.number_of_elements()); + newsizes.reserve(sizes.number_of_elements()); + newoffsets.reserve(offsets.number_of_elements()); + + // Mapping information for the points. + auto npts = conduit::blueprint::mesh::coordset::length(coordset); + std::vector old2NewPoints(npts, -1); + int newPointIndex = 0; + + // We iterate over elements in the specified order. We iterate over the + // points in each element and renumber the points. + conduit::index_t newoffset = 0; + for(const int cellIndex : reorder) + { + for(conduit::index_t i = 0; i < sizes[cellIndex]; i++) + { + auto id = conn[offsets[cellIndex] + i]; +#ifdef REORDER_POINTS + if(old2NewPoints[id] == -1) + { + old2NewPoints[id] = newPointIndex++; + } + newconn.push_back(old2NewPoints[id]); +#else + newconn.push_back(id); +#endif + } + newsizes.push_back(sizes[cellIndex]); + newoffsets.push_back(newoffset); + newoffset += sizes[cellIndex]; + } + + // Store the new connectivity. + dest_topo["type"] = topo["type"]; + dest_topo["coordset"] = topo["coordset"]; + dest_topo["elements/shape"] = topo["elements/shape"]; + conduit::Node tmp; + tmp.set_external(newconn.data(), newconn.size()); + tmp.to_data_type(n_conn.dtype().id(), dest_topo["elements/connectivity"]); + tmp.set_external(newsizes.data(), newsizes.size()); + tmp.to_data_type(n_sizes.dtype().id(), dest_topo["elements/sizes"]); + tmp.set_external(newoffsets.data(), newoffsets.size()); + tmp.to_data_type(n_offsets.dtype().id(), dest_topo["elements/offsets"]); + +#ifdef REORDER_POINTS + // Reorder the coordset now, making it explicit if needed. + dest_coordset["type"] = coordset["type"]; + conduit::Node coordset_explicit; + if(coordset["type"].as_string() == "rectilinear") + conduit::blueprint::mesh::coordset::rectilinear::to_explicit(coordset, coordset_explicit); + else if(coordset["type"].as_string() == "uniform") + conduit::blueprint::mesh::coordset::uniform::to_explicit(coordset, coordset_explicit); + else + coordset_explicit.set_external(coordset); + slice_field(coordset_explicit["values"], dest_coordset["values"], old2NewPoints); +#else + dest_coordset["type"] = coordset["type"]; + dest_coordset["values"].set(coordset["values"]); +#endif + // Reorder fields that match this topo + for(conduit::index_t fi = 0; fi < fields.number_of_children(); fi++) + { + const conduit::Node &src = fields[fi]; + if(src["topology"].as_string() == topo.name()) + { + auto &newfields = dest_topo["fields"]; + conduit::Node &dest = newfields[src.name()]; + dest["association"] = src["association"]; + dest["topology"] = dest_topo.name(); //src["topology"]; + if(dest["association"].as_string() == "element") + { + slice_field(src["values"], dest["values"], reorder); + } + else + { + slice_field(src["values"], dest["values"], old2NewPoints); + } + } + } + } +} + /** \brief Keep track of some tile information. */ @@ -352,7 +631,63 @@ Tiler::generate(int nx, int ny, int nz, origin[2] = options.fetch_existing("origin/z").to_double(); if(options.has_path("tile")) initialize(options.fetch_existing("tile")); +#if 1 + // Make cell centers for each tile and record that no tiles have been visited. + int ncells = nx * ny; + std::vector visited(ncells, 0); + std::vector cx(ncells), cy(ncells); + for(int j = 0, idx = 0; j < ny; j++) + { + for(int i = 0; i < nx; i++, idx++) + { + cx[idx] = origin[0] + (i + 0.5f) * width(); + cy[idx] = origin[1] + (j + 0.5f) * height(); + } + } + double *tileCenters[2] = {&cx[0], &cy[0]}; + conduit::blueprint::mesh::utils::kdtree spatial_sort; + spatial_sort.initialize(tileCenters, ncells); + + // Traverse the cells in the desired spatial order. + std::vector tiles(nx * ny); + for(const int idx : spatial_sort.getIndices()) + { + // The first time we've used the tile, set its size. + Tile ¤t = tiles[idx]; + current.reset(m_xpts.size()); + + // Copy neighbor points to the current tile if we can. + int i = idx % nx; + int j = idx / nx; + int left_idx = (i > 0) ? (idx - 1) : -1; + int right_idx = (i < nx-1) ? (idx + 1) : -1; + int bottom_idx = (j > 0) ? (idx - nx) : -1; + int top_idx = (j < ny-1) ? (idx + nx) : -1; + if(left_idx != -1 && visited[left_idx]) + { + current.setPointIds(left(), tiles[left_idx].getPointIds(right())); + } + if(right_idx != -1 && visited[right_idx]) + { + current.setPointIds(right(), tiles[right_idx].getPointIds(left())); + } + if(bottom_idx != -1 && visited[bottom_idx]) + { + current.setPointIds(bottom(), tiles[bottom_idx].getPointIds(top())); + } + if(top_idx != -1 && visited[top_idx]) + { + current.setPointIds(top(), tiles[top_idx].getPointIds(bottom())); + } + + // Make this tile's points + double newOrigin[] = {origin[0] + i * width(), origin[1] + j * height(), origin[2]}; + addPoints(newOrigin, current.getPointIds(), x, y); + visited[idx] = 1; + } + +#else // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); double newOrigin[] = {origin[0], origin[1], origin[2]}; @@ -383,11 +718,18 @@ Tiler::generate(int nx, int ny, int nz, } newOrigin[1] += height(); } - +#endif if(nz < 1) { // Iterate over the tiles and add their quads. // TODO: reserve size for conn, sizes +#if 1 + // Add the cells in spatial sort order. + for(const int idx : spatial_sort.getIndices()) + { + addFaces(tiles[idx].getPointIds(), conn, sizes); + } +#else for(int j = 0; j < ny; j++) { for(int i = 0; i < nx; i++) @@ -396,6 +738,7 @@ Tiler::generate(int nx, int ny, int nz, addFaces(current.getPointIds(), conn, sizes); } } +#endif // NOTE: z coords in output will be empty. // Boundaries @@ -480,6 +823,26 @@ Tiler::generate(int nx, int ny, int nz, res["topologies/mesh/elements/shape"] = z.empty() ? "quad" : "hex"; res["topologies/mesh/elements/connectivity"].set(conn); res["topologies/mesh/elements/sizes"].set(sizes); + +#if 1 + if(nz > 0) + { + // We need offsets. + conduit::blueprint::mesh::utils::topology::unstructured::generate_offsets(res["topologies/mesh"], res["topologies/mesh/elements/offsets"]); + + // Reorder the mesh in 3D. NOTE: boundaries would have to be fixed because + // of the changes to node ordering, which we'd have to pass out the node ordering. + const auto reorder = spatial_reordering(res["topologies/mesh"]); + reorder_topo(res["topologies/mesh"], res["coordsets/coords"], res["fields"], + res["topologies/rmesh"], res["coordsets/rcoords"], res["rfields"], + reorder); + +conduit::Node opts; +opts["num_children_threshold"] = 100000; +opts["num_elements_threshold"] = 500; +std::cout << res.to_summary_string(opts) << std::endl; + } +#endif } void From 7fce7d7980a999ec4f8b870fa4439c599266e994 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 Sep 2023 16:39:35 -0700 Subject: [PATCH 09/50] Fixed indexing bug --- .../conduit_blueprint_mesh_examples_tiled.cpp | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 96e7a52db..c1821a16a 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -207,8 +207,8 @@ slice_array(const conduit::Node &n_src_values, void slice_field(const conduit::Node &src, - conduit::Node &dest, - const std::vector &indices) + const std::vector &ids, + conduit::Node &dest) { if(src.number_of_children() > 0) { @@ -216,12 +216,12 @@ slice_field(const conduit::Node &src, for(conduit::index_t ci = 0; ci < src.number_of_children(); ci++) { const conduit::Node &comp = src[ci]; - slice_array(comp, indices, dest[comp.name()]); + slice_array(comp, ids, dest[comp.name()]); } } else { - slice_array(src, indices, dest); + slice_array(src, ids, dest); } } @@ -251,7 +251,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con // Mapping information for the points. auto npts = conduit::blueprint::mesh::coordset::length(coordset); - std::vector old2NewPoints(npts, -1); + std::vector old2NewPoints(npts, -1), ptReorder(npts, -1); int newPointIndex = 0; // We iterate over elements in the specified order. We iterate over the @@ -262,9 +262,11 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con for(conduit::index_t i = 0; i < sizes[cellIndex]; i++) { auto id = conn[offsets[cellIndex] + i]; +#define REORDER_POINTS #ifdef REORDER_POINTS if(old2NewPoints[id] == -1) { + ptReorder[newPointIndex] = id; old2NewPoints[id] = newPointIndex++; } newconn.push_back(old2NewPoints[id]); @@ -279,7 +281,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con // Store the new connectivity. dest_topo["type"] = topo["type"]; - dest_topo["coordset"] = topo["coordset"]; + dest_topo["coordset"] = dest_coordset.name(); //topo["coordset"]; dest_topo["elements/shape"] = topo["elements/shape"]; conduit::Node tmp; tmp.set_external(newconn.data(), newconn.size()); @@ -291,7 +293,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con #ifdef REORDER_POINTS // Reorder the coordset now, making it explicit if needed. - dest_coordset["type"] = coordset["type"]; + dest_coordset["type"] = "explicit"; conduit::Node coordset_explicit; if(coordset["type"].as_string() == "rectilinear") conduit::blueprint::mesh::coordset::rectilinear::to_explicit(coordset, coordset_explicit); @@ -299,7 +301,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con conduit::blueprint::mesh::coordset::uniform::to_explicit(coordset, coordset_explicit); else coordset_explicit.set_external(coordset); - slice_field(coordset_explicit["values"], dest_coordset["values"], old2NewPoints); + slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); #else dest_coordset["type"] = coordset["type"]; dest_coordset["values"].set(coordset["values"]); @@ -316,11 +318,11 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con dest["topology"] = dest_topo.name(); //src["topology"]; if(dest["association"].as_string() == "element") { - slice_field(src["values"], dest["values"], reorder); + slice_field(src["values"], reorder, dest["values"]); } else { - slice_field(src["values"], dest["values"], old2NewPoints); + slice_field(src["values"], ptReorder, dest["values"]); } } } From 7a7a5bdda862a708c7f25d7a57c2cdcbaae1144e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 Sep 2023 17:11:20 -0700 Subject: [PATCH 10/50] change a template type from T to CoordinateType --- .../conduit_blueprint_mesh_kdtree.hpp | 180 +++++++++--------- 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp b/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp index 6d6b363b7..679d6ca4e 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp @@ -61,19 +61,19 @@ namespace utils @tparam Indexable The container that contains elements of type T. This can be raw pointers, a vector, a data_array, data_accessor, etc. - @tparam T The storage type of the coordinates. + @tparam CoordinateType The storage type of the coordinates. @tparam NDIMS The number of dimensions in a coordinate. */ -template +template class kdtree { public: - static const int NoChild; - static const int NotFound; + static const conduit::index_t NoChild; + static const conduit::index_t NotFound; using IndexableType = Indexable; - using BoxType = T[NDIMS][2]; - using PointType = T[NDIMS]; + using BoxType = CoordinateType[NDIMS][2]; + using PointType = CoordinateType[NDIMS]; /// Constructor kdtree(); @@ -82,7 +82,7 @@ class kdtree @brief Set the point tolerance, a distance under which points are the same. @param tolerance The point tolerance distance. */ - void setPointTolerance(T tolerance); + void setPointTolerance(CoordinateType tolerance); /** @brief Initialize the tree so it can look up points. @@ -110,13 +110,13 @@ class kdtree @brief Return the sorted index order. @return A vector sorted spatially. The elements contain the original indices. */ - std::vector &getIndices() { return index; } + std::vector &getIndices() { return index; } /** @brief Return the sorted index order. @return A vector sorted spatially. The elements contain the original indices. */ - const std::vector &getIndices() const { return index; } + const std::vector &getIndices() const { return index; } /** @brief Print the tree to a stream. @@ -127,8 +127,8 @@ class kdtree /// Represents ranges of the index vector. struct RangeType { - int offset; - int size; + conduit::index_t offset; + conduit::index_t size; }; /// Represents a box in the tree hierarchy. @@ -145,68 +145,68 @@ class kdtree // Internal helpers. void cutBox(const BoxType &input, int dimension, BoxType &A, BoxType &B) const; - void cutRange(const RangeType &input, int dimension, T maxValue, RangeType &A, RangeType &B) const; + void cutRange(const RangeType &input, int dimension, CoordinateType maxValue, RangeType &A, RangeType &B) const; void calculateExtents(); void construct(); - void constructBox(int bp, const RangeType &range, const BoxType &b, int level, int maxlevel); + void constructBox(conduit::index_t bp, const RangeType &range, const BoxType &b, int level, int maxlevel); int longest(const BoxType &b) const; void sortIndexRange(const RangeType &range, int dimension); bool pointInBox(const PointType &pt, const BoxType &b) const; - bool pointEqual(const PointType &pt, int index) const; + bool pointEqual(const PointType &pt, conduit::index_t index) const; void printBox(std::ostream &os, const BoxType &b, const std::string &indent) const; private: - std::vector boxes; //!< Describes boxes - std::vector index; //!< Contains sorted coordinate indices - BoxType box; //!< Overall bounding box - Indexable coords[NDIMS]; //!< Coordinate arrays - size_t coordlen; //!< Length of a coordinate array. - T pointTolerance; //!< Distance to a point before it is same. - T pointTolerance2; //!< pointTolerance^2. + std::vector boxes; //!< Describes boxes + std::vector index; //!< Contains sorted coordinate indices + BoxType box; //!< Overall bounding box + Indexable coords[NDIMS]; //!< Coordinate arrays + size_t coordlen; //!< Length of a coordinate array. + CoordinateType pointTolerance; //!< Distance to a point before it is same. + CoordinateType pointTolerance2; //!< pointTolerance^2. }; //--------------------------------------------------------------------------- -template +template std::ostream & -operator << (std::ostream &os, const kdtree &obj) +operator << (std::ostream &os, const kdtree &obj) { obj.print(os); return os; } //--------------------------------------------------------------------------- -template -const int kdtree::NoChild = -1; +template +const conduit::index_t kdtree::NoChild = -1; -template -const int kdtree::NotFound = -1; +template +const conduit::index_t kdtree::NotFound = -1; //--------------------------------------------------------------------------- -template -kdtree::kdtree() : boxes(), index() +template +kdtree::kdtree() : boxes(), index() { for(int i = 0; i < dims(); i++) { - box[i][0] = T{}; - box[i][1] = T{}; + box[i][0] = CoordinateType{}; + box[i][1] = CoordinateType{}; coords[i] = Indexable{}; } coordlen = 0; - constexpr T DEFAULT_POINT_TOLERANCE = (T)(1.e-9); + constexpr auto DEFAULT_POINT_TOLERANCE = static_cast(1.e-9); setPointTolerance(DEFAULT_POINT_TOLERANCE); } //--------------------------------------------------------------------------- -template -void kdtree::setPointTolerance(T tolerance) +template +void kdtree::setPointTolerance(CoordinateType tolerance) { pointTolerance = tolerance; pointTolerance2 = pointTolerance * pointTolerance; } //--------------------------------------------------------------------------- -template -void kdtree::initialize(Indexable c[NDIMS], size_t len) +template +void kdtree::initialize(Indexable c[NDIMS], size_t len) { boxes.clear(); index.clear(); @@ -221,9 +221,9 @@ void kdtree::initialize(Indexable c[NDIMS], size_t len) } //--------------------------------------------------------------------------- -template -void kdtree::printBox(std::ostream &os, - const kdtree::BoxType &b, const std::string &indent) const +template +void kdtree::printBox(std::ostream &os, + const kdtree::BoxType &b, const std::string &indent) const { os << indent << "box: ["; for(int i = 0; i < dims(); i++) @@ -235,8 +235,8 @@ void kdtree::printBox(std::ostream &os, } //--------------------------------------------------------------------------- -template -void kdtree::print(std::ostream &os) const +template +void kdtree::print(std::ostream &os) const { os << "boxesSize: " << boxes.size() << std::endl; os << "boxes:" << std::endl; @@ -282,33 +282,33 @@ void kdtree::print(std::ostream &os) const } //--------------------------------------------------------------------------- -template -void kdtree::cutBox(const BoxType &input, int dimension, +template +void kdtree::cutBox(const BoxType &input, int dimension, BoxType &A, BoxType &B) const { // NOTE: This just does uniform splitting for now. It could sample the // coordinates to get an idea of their center and store a t value // to lerp the dimension min/max. This would help with long dimensions // where points are clustered unevenly. - constexpr T two = 2.; + constexpr CoordinateType two = 2.; memcpy(A, input, sizeof(BoxType)); memcpy(B, input, sizeof(BoxType)); - T mid = (input[dimension][0] + input[dimension][1]) / two; + CoordinateType mid = (input[dimension][0] + input[dimension][1]) / two; A[dimension][1] = mid; B[dimension][0] = mid; } //--------------------------------------------------------------------------- -template -void kdtree::cutRange(const RangeType &input, int dimension, - T maxValue, RangeType &A, RangeType &B) const +template +void kdtree::cutRange(const RangeType &input, int dimension, + CoordinateType maxValue, RangeType &A, RangeType &B) const { // We can't just partition the range in half because it might mean leaving // coordinate data with the same value on either side of the split. // That doesn't work spatially. Instead, partition the range such that A // contains all indices less than maxValue and B contains the rest. - int sizeA = 0; - for(int idx = 0; idx < input.size; idx++) + conduit::index_t sizeA = 0; + for(conduit::index_t idx = 0; idx < input.size; idx++) { if(coords[dimension][index[input.offset + idx]] < maxValue) { @@ -319,7 +319,7 @@ void kdtree::cutRange(const RangeType &input, int dimension break; } } - int sizeB = input.size - sizeA; + conduit::index_t sizeB = input.size - sizeA; A.offset = input.offset; A.size = sizeA; @@ -334,14 +334,14 @@ void kdtree::cutRange(const RangeType &input, int dimension << ", maxValue=" << maxValue << std::endl; std::cout << "\tA={"; - for(int idx = 0; idx < A.size; idx++) + for(conduit::index_t idx = 0; idx < A.size; idx++) { if(idx > 0) std::cout << ", "; std::cout << coords[dimension][index[A.offset + idx]]; } std::cout << "}" << std::endl; std::cout << "\tB={"; - for(int idx = 0; idx < B.size; idx++) + for(conduit::index_t idx = 0; idx < B.size; idx++) { if(idx > 0) std::cout << ", "; std::cout << coords[dimension][index[B.offset + idx]]; @@ -351,9 +351,9 @@ void kdtree::cutRange(const RangeType &input, int dimension } //--------------------------------------------------------------------------- -template +template int -kdtree::findPoint(const kdtree::PointType &pt) const +kdtree::findPoint(const kdtree::PointType &pt) const { int foundIndex = NotFound; #ifdef CONDUIT_DEBUG_KDTREE @@ -370,7 +370,7 @@ kdtree::findPoint(const kdtree::PointT // Iterate until we find a box that contains the point. BoxType currentBox; memcpy(currentBox, box, sizeof(BoxType)); - int bp = 0, prevbp = 0; + conduit::index_t bp = 0, prevbp = 0; while(boxes[bp].childOffset != NoChild) { // Cut the box to make 2 child boxes. @@ -402,12 +402,12 @@ kdtree::findPoint(const kdtree::PointT std::cout << " Search box: bp=" << prevbp << std::endl; #endif // Check prevbp box for the points of interest. - int offset = boxes[prevbp].range.offset; - int size = boxes[prevbp].range.size; - for(int i = 0; i < size && (foundIndex == NotFound); i++) + conduit::index_t offset = boxes[prevbp].range.offset; + conduit::index_t size = boxes[prevbp].range.size; + for(conduit::index_t i = 0; i < size && (foundIndex == NotFound); i++) { // This the index of the point we want to look at. - int ptIdx = index[offset + i]; + conduit::index_t ptIdx = index[offset + i]; // See if the point is "equal". if(pointEqual(pt, ptIdx)) @@ -430,27 +430,27 @@ kdtree::findPoint(const kdtree::PointT } //--------------------------------------------------------------------------- -template +template bool -kdtree::pointEqual(const kdtree::PointType &pt, int ptIdx) const +kdtree::pointEqual(const kdtree::PointType &pt, conduit::index_t ptIdx) const { - T dist2 = 0.; + CoordinateType dist2 = 0.; for(int i = 0; i < dims(); i++) { - T delta = pt[i] - coords[i][ptIdx]; + CoordinateType delta = pt[i] - coords[i][ptIdx]; dist2 += delta * delta; } return dist2 < pointTolerance2; } //--------------------------------------------------------------------------- -template -void kdtree::calculateExtents() +template +void kdtree::calculateExtents() { for(int i = 0; i < dims(); i++) { - box[i][0] = std::numeric_limits::max(); - box[i][1] = std::numeric_limits::min(); + box[i][0] = std::numeric_limits::max(); + box[i][1] = std::numeric_limits::min(); for(size_t j = 0; j < coordlen; j++) { box[i][0] = std::min(box[i][0], coords[i][j]); @@ -461,15 +461,15 @@ void kdtree::calculateExtents() // Expand the box a little for(int i = 0; i < dims(); i++) { - T d = (box[i][1] - box[i][0]) / static_cast(200.); + CoordinateType d = (box[i][1] - box[i][0]) / static_cast(200.); box[i][0] -= d; box[i][1] += d; } } //--------------------------------------------------------------------------- -template -void kdtree::construct() +template +void kdtree::construct() { // Fill the index with 0,1,2,3,... index.resize(coordlen); @@ -493,15 +493,15 @@ void kdtree::construct() // Build the box for level 0. RangeType range; range.offset = 0; - range.size = static_cast(coordlen); + range.size = static_cast(coordlen); constructBox(0, range, box, 0, maxLevels); } //--------------------------------------------------------------------------- -template -void kdtree::constructBox(int bp, - const kdtree::RangeType &range, - const kdtree::BoxType &b, +template +void kdtree::constructBox(conduit::index_t bp, + const kdtree::RangeType &range, + const kdtree::BoxType &b, int level, int maxLevels) { @@ -516,14 +516,14 @@ void kdtree::constructBox(int bp, BoxType A, B; cutBox(b, boxes[bp].splitDimension, A, B); - T maxValue = A[boxes[bp].splitDimension][1]; + CoordinateType maxValue = A[boxes[bp].splitDimension][1]; RangeType rangeA, rangeB; cutRange(range, boxes[bp].splitDimension, maxValue, rangeA, rangeB); if(level < maxLevels) { - int child0 = boxes[bp].childOffset; - int child1 = boxes[bp].childOffset + 1; + conduit::index_t child0 = boxes[bp].childOffset; + conduit::index_t child1 = boxes[bp].childOffset + 1; constructBox(child0, rangeA, A, level + 1, maxLevels); constructBox(child1, rangeB, B, level + 1, maxLevels); } @@ -539,14 +539,14 @@ void kdtree::constructBox(int bp, } //--------------------------------------------------------------------------- -template -int kdtree::longest(const kdtree::BoxType &b) const +template +int kdtree::longest(const kdtree::BoxType &b) const { int rv = 0; - T len = b[0][1] - b[0][0]; + CoordinateType len = b[0][1] - b[0][0]; for(int i = 1; i < dims(); i++) { - T newlen = b[i][1] - b[i][0]; + CoordinateType newlen = b[i][1] - b[i][0]; if(newlen > len) rv = i; } @@ -554,9 +554,9 @@ int kdtree::longest(const kdtree::BoxT } //--------------------------------------------------------------------------- -template -void kdtree::sortIndexRange( - const kdtree::RangeType &range, int dimension) +template +void kdtree::sortIndexRange( + const kdtree::RangeType &range, int dimension) { if(range.size > 1) { @@ -564,7 +564,7 @@ void kdtree::sortIndexRange( const Indexable &data = coords[dimension]; std::sort(index.begin() + range.offset, index.begin() + range.offset + range.size, - [&data](int idx1, int idx2) + [&data](conduit::index_t idx1, conduit::index_t idx2) { return data[idx1] < data[idx2]; }); @@ -572,9 +572,9 @@ void kdtree::sortIndexRange( } //--------------------------------------------------------------------------- -template -bool kdtree::pointInBox( - const kdtree::PointType &pt, const BoxType &b) const +template +bool kdtree::pointInBox( + const kdtree::PointType &pt, const BoxType &b) const { bool retval = true; for(int i = 0; i < dims(); i++) From 50e6cbacaf198f77a5c5d2be05110e1fc7da9efe Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 Sep 2023 17:11:49 -0700 Subject: [PATCH 11/50] Clean up --- .../conduit_blueprint_mesh_examples_tiled.cpp | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index c1821a16a..c22ecb9d3 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -50,7 +50,7 @@ namespace examples namespace detail { -std::vector spatial_reordering(const conduit::Node &topo) +std::vector spatial_reordering(const conduit::Node &topo) { // Make a new centroid topo and coordset. The coordset will contain the // element centers. @@ -69,7 +69,7 @@ std::vector spatial_reordering(const conduit::Node &topo) } // Sort the coordinates spatially - std::vector reorder; + std::vector reorder; if(coords.size() == 2) { conduit::blueprint::mesh::utils::kdtree spatial_sort; @@ -207,7 +207,7 @@ slice_array(const conduit::Node &n_src_values, void slice_field(const conduit::Node &src, - const std::vector &ids, + const std::vector &ids, conduit::Node &dest) { if(src.number_of_children() > 0) @@ -228,7 +228,7 @@ slice_field(const conduit::Node &src, void reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const conduit::Node &fields, conduit::Node &dest_topo, conduit::Node &dest_coordset, conduit::Node &dest_fields, - const std::vector &reorder) + const std::vector &reorder) { conduit::blueprint::mesh::utils::ShapeType shape(topo); @@ -250,29 +250,25 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con newoffsets.reserve(offsets.number_of_elements()); // Mapping information for the points. + constexpr conduit::index_t invalidNode = -1; auto npts = conduit::blueprint::mesh::coordset::length(coordset); - std::vector old2NewPoints(npts, -1), ptReorder(npts, -1); - int newPointIndex = 0; + std::vector old2NewPoints(npts, invalidNode), ptReorder(npts, invalidNode); + conduit::index_t newPointIndex = 0; // We iterate over elements in the specified order. We iterate over the // points in each element and renumber the points. conduit::index_t newoffset = 0; - for(const int cellIndex : reorder) + for(const auto cellIndex : reorder) { for(conduit::index_t i = 0; i < sizes[cellIndex]; i++) { auto id = conn[offsets[cellIndex] + i]; -#define REORDER_POINTS -#ifdef REORDER_POINTS - if(old2NewPoints[id] == -1) + if(old2NewPoints[id] == invalidNode) { ptReorder[newPointIndex] = id; old2NewPoints[id] = newPointIndex++; } newconn.push_back(old2NewPoints[id]); -#else - newconn.push_back(id); -#endif } newsizes.push_back(sizes[cellIndex]); newoffsets.push_back(newoffset); @@ -291,7 +287,6 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con tmp.set_external(newoffsets.data(), newoffsets.size()); tmp.to_data_type(n_offsets.dtype().id(), dest_topo["elements/offsets"]); -#ifdef REORDER_POINTS // Reorder the coordset now, making it explicit if needed. dest_coordset["type"] = "explicit"; conduit::Node coordset_explicit; @@ -302,10 +297,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con else coordset_explicit.set_external(coordset); slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); -#else - dest_coordset["type"] = coordset["type"]; - dest_coordset["values"].set(coordset["values"]); -#endif + // Reorder fields that match this topo for(conduit::index_t fi = 0; fi < fields.number_of_children(); fi++) { From 4ef1a3b19bda7a6909ff6d92ba263ed1314ea5a9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 Sep 2023 18:29:29 -0700 Subject: [PATCH 12/50] Moved a function. Converted code to use index_t. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 263 +++++----- .../conduit_blueprint_mesh_examples_tiled.hpp | 6 +- .../conduit_blueprint_mesh_utils.cpp | 480 ++++++++++-------- .../conduit_blueprint_mesh_utils.hpp | 12 + 4 files changed, 403 insertions(+), 358 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index c22ecb9d3..1c6e2ef99 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -14,6 +14,7 @@ //----------------------------------------------------------------------------- // conduit lib includes //----------------------------------------------------------------------------- +#include "conduit_blueprint_mesh_examples_tiled.hpp" #include "conduit.hpp" #include "conduit_blueprint.hpp" #include "conduit_blueprint_exports.h" @@ -50,40 +51,6 @@ namespace examples namespace detail { -std::vector spatial_reordering(const conduit::Node &topo) -{ - // Make a new centroid topo and coordset. The coordset will contain the - // element centers. - Node topo_dest, coords_dest, s2dmap, d2smap; - mesh::topology::unstructured::generate_centroids(topo, - topo_dest, - coords_dest, - s2dmap, - d2smap); - // Bundle the coordset components into a vector. - std::vector coords; - const conduit::Node &values = coords_dest.fetch_existing("values"); - for(conduit::index_t i = 0; i < values.number_of_children(); i++) - { - coords.push_back(values[i].as_double_accessor()); - } - - // Sort the coordinates spatially - std::vector reorder; - if(coords.size() == 2) - { - conduit::blueprint::mesh::utils::kdtree spatial_sort; - spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); - reorder = std::move(spatial_sort.getIndices()); - } - else if(coords.size() == 3) - { - conduit::blueprint::mesh::utils::kdtree spatial_sort; - spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); - reorder = std::move(spatial_sort.getIndices()); - } - return reorder; -} //--------------------------------------------------------------------------- // @brief Slice the n_src array using the indices stored in ids. We use the // array classes for their [] operators that deal with interleaved @@ -327,6 +294,8 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con class Tile { public: + static const conduit::index_t INVALID_POINT; + Tile() : ptids() { } @@ -334,17 +303,17 @@ class Tile /// Reset the tile. void reset(size_t npts) { - ptids = std::vector(npts, -1); + ptids = std::vector(npts, -1); } /// Return the point ids. - std::vector &getPointIds() { return ptids; } - const std::vector &getPointIds() const { return ptids; } + std::vector &getPointIds() { return ptids; } + const std::vector &getPointIds() const { return ptids; } /// Get the specified point ids for this tile using the supplied indices. - std::vector getPointIds(const std::vector &indices) const + std::vector getPointIds(const std::vector &indices) const { - std::vector ids; + std::vector ids; ids.reserve(indices.size()); for(const auto &idx : indices) ids.push_back(ptids[idx]); @@ -352,7 +321,7 @@ class Tile } // Set the point ids - void setPointIds(const std::vector &indices, const std::vector &ids) + void setPointIds(const std::vector &indices, const std::vector &ids) { for(size_t i = 0; i < indices.size(); i++) { @@ -361,9 +330,11 @@ class Tile } private: - std::vector ptids; //!< This tile's point ids. + std::vector ptids; //!< This tile's point ids. }; +const conduit::index_t Tile::INVALID_POINT = -1; + /** \brief Build a mesh from tiles. There is a default tile pattern, although it can be replaced using an options Node containing new tile information. @@ -374,7 +345,7 @@ class Tiler Tiler(); /// Generate the tiled mesh. - void generate(int nx, int ny, int nz, + void generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, conduit::Node &res, const conduit::Node &options); protected: @@ -385,16 +356,16 @@ class Tiler void initialize(const conduit::Node &t); /// Return point indices of points along left edge. - const std::vector &left() const { return m_left; } + const std::vector &left() const { return m_left; } /// Return point indices of points along right edge. - const std::vector &right() const { return m_right; } + const std::vector &right() const { return m_right; } /// Return point indices of points along bottom edge. - const std::vector &bottom() const { return m_bottom; } + const std::vector &bottom() const { return m_bottom; } /// Return point indices of points along top edge. - const std::vector &top() const { return m_top; } + const std::vector &top() const { return m_top; } /// Return tile width double width() const { return m_width; } @@ -404,7 +375,7 @@ class Tiler /// Creates the points for the tile (if they need to be created). void addPoints(double origin[2], - std::vector &ptids, + std::vector &ptids, std::vector &x, std::vector &y) { @@ -412,7 +383,7 @@ class Tiler // not been created yet. for(size_t i = 0; i < m_xpts.size(); i++) { - if(ptids[i] == -1) + if(ptids[i] == Tile::INVALID_POINT) { ptids[i] = static_cast(x.size()); x.push_back(origin[0] + m_xpts[i]); @@ -422,9 +393,11 @@ class Tiler } /// Emit the quad cells using this tile's point ids. - void addFaces(const std::vector &ptids, - std::vector &conn, std::vector &sizes, - int offset = 0, bool reverse = false) const + void addFaces(const std::vector &ptids, + std::vector &conn, + std::vector &sizes, + conduit::index_t offset = 0, + bool reverse = false) const { const size_t nquads = m_quads.size() / 4; int order[] = {reverse ? 3 : 0, reverse ? 2 : 1, reverse ? 1 : 2, reverse ? 0 : 3}; @@ -439,7 +412,11 @@ class Tiler } /// Emit the hex cells using this tile's point ids. - void addHexs(const std::vector &ptids, int plane1Offset, int plane2Offset, std::vector &conn, std::vector &sizes) const + void addHexs(const std::vector &ptids, + conduit::index_t plane1Offset, + conduit::index_t plane2Offset, + std::vector &conn, + std::vector &sizes) const { const size_t nquads = m_quads.size() / 4; for(size_t i = 0; i < nquads; i++) @@ -482,10 +459,10 @@ class Tiler } /// Turn a node into an int vector. - std::vector toIntVector(const conduit::Node &n) const + std::vector toIndexVector(const conduit::Node &n) const { - auto acc = n.as_int_accessor(); - std::vector vec; + auto acc = n.as_index_t_accessor(); + std::vector vec; vec.reserve(acc.number_of_elements()); for(conduit::index_t i = 0; i < acc.number_of_elements(); i++) vec.push_back(acc[i]); @@ -493,19 +470,28 @@ class Tiler } /// Make 2D boundaries. - void makeBoundaries2D(const std::vector &tiles, int nx, int ny, - std::vector &bconn, std::vector &bsizes, - std::vector &btype, const conduit::Node &options) const; + void makeBoundaries2D(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + std::vector &bconn, + std::vector &bsizes, + std::vector &btype, + const conduit::Node &options) const; /// Make 3D boundaries. - void makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, - size_t nPtsPerPlane, - std::vector &bconn, std::vector &bsizes, - std::vector &btype, const conduit::Node &options) const; + void makeBoundaries3D(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + conduit::index_t nz, + conduit::index_t nPtsPerPlane, + std::vector &bconn, + std::vector &bsizes, + std::vector &btype, + const conduit::Node &options) const; private: std::vector m_xpts, m_ypts; double m_width, m_height; - std::vector m_left, m_right, m_bottom, m_top, m_quads; + std::vector m_left, m_right, m_bottom, m_top, m_quads; }; Tiler::Tiler() : m_xpts(), m_ypts(), m_width(0.), m_height(0.), @@ -542,7 +528,7 @@ Tiler::initialize() 20., 20., 20., 20., 20., }; - m_quads = std::vector{ + m_quads = std::vector{ // lower-left quadrant 0,1,6,5, 1,2,9,6, @@ -573,10 +559,10 @@ Tiler::initialize() 26,27,32,31 }; - m_left = std::vector{0,5,14,24,28}; - m_right = std::vector{4,8,18,27,32}; - m_bottom = std::vector{0,1,2,3,4}; - m_top = std::vector{28,29,30,31,32}; + m_left = std::vector{0,5,14,24,28}; + m_right = std::vector{4,8,18,27,32}; + m_bottom = std::vector{0,1,2,3,4}; + m_top = std::vector{28,29,30,31,32}; m_width = computeExtents(m_xpts); m_height = computeExtents(m_ypts); @@ -587,11 +573,11 @@ Tiler::initialize(const conduit::Node &t) { m_xpts = toDoubleVector(t.fetch_existing("x")); m_ypts = toDoubleVector(t.fetch_existing("y")); - m_quads = toIntVector(t.fetch_existing("quads")); - m_left = toIntVector(t.fetch_existing("left")); - m_right = toIntVector(t.fetch_existing("right")); - m_bottom = toIntVector(t.fetch_existing("bottom")); - m_top = toIntVector(t.fetch_existing("top")); + m_quads = toIndexVector(t.fetch_existing("quads")); + m_left = toIndexVector(t.fetch_existing("left")); + m_right = toIndexVector(t.fetch_existing("right")); + m_bottom = toIndexVector(t.fetch_existing("bottom")); + m_top = toIndexVector(t.fetch_existing("top")); m_width = computeExtents(m_xpts); m_height = computeExtents(m_ypts); @@ -608,13 +594,14 @@ Tiler::initialize(const conduit::Node &t) \param options A node that may contain additional control options. */ void -Tiler::generate(int nx, int ny, int nz, +Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, conduit::Node &res, const conduit::Node &options) { double origin[] = {0., 0., 0.}; std::vector x, y, z; - std::vector conn, sizes, bconn, bsizes, btype; + std::vector conn, sizes, bconn, bsizes; + std::vector btype; // Process any options. if(options.has_path("origin/x")) @@ -627,7 +614,7 @@ Tiler::generate(int nx, int ny, int nz, initialize(options.fetch_existing("tile")); #if 1 // Make cell centers for each tile and record that no tiles have been visited. - int ncells = nx * ny; + conduit::index_t ncells = nx * ny; std::vector visited(ncells, 0); std::vector cx(ncells), cy(ncells); for(int j = 0, idx = 0; j < ny; j++) @@ -644,32 +631,33 @@ Tiler::generate(int nx, int ny, int nz, // Traverse the cells in the desired spatial order. std::vector tiles(nx * ny); - for(const int idx : spatial_sort.getIndices()) + constexpr conduit::index_t invalidIndex = -1; + for(const auto idx : spatial_sort.getIndices()) { // The first time we've used the tile, set its size. Tile ¤t = tiles[idx]; current.reset(m_xpts.size()); // Copy neighbor points to the current tile if we can. - int i = idx % nx; - int j = idx / nx; - int left_idx = (i > 0) ? (idx - 1) : -1; - int right_idx = (i < nx-1) ? (idx + 1) : -1; - int bottom_idx = (j > 0) ? (idx - nx) : -1; - int top_idx = (j < ny-1) ? (idx + nx) : -1; - if(left_idx != -1 && visited[left_idx]) + conduit::index_t i = idx % nx; + conduit::index_t j = idx / nx; + conduit::index_t left_idx = (i > 0) ? (idx - 1) : invalidIndex; + conduit::index_t right_idx = (i < nx - 1) ? (idx + 1) : invalidIndex; + conduit::index_t bottom_idx = (j > 0) ? (idx - nx) : invalidIndex; + conduit::index_t top_idx = (j < ny - 1) ? (idx + nx) : invalidIndex; + if(left_idx != invalidIndex && visited[left_idx]) { current.setPointIds(left(), tiles[left_idx].getPointIds(right())); } - if(right_idx != -1 && visited[right_idx]) + if(right_idx != invalidIndex && visited[right_idx]) { current.setPointIds(right(), tiles[right_idx].getPointIds(left())); } - if(bottom_idx != -1 && visited[bottom_idx]) + if(bottom_idx != invalidIndex && visited[bottom_idx]) { current.setPointIds(bottom(), tiles[bottom_idx].getPointIds(top())); } - if(top_idx != -1 && visited[top_idx]) + if(top_idx != invalidIndex && visited[top_idx]) { current.setPointIds(top(), tiles[top_idx].getPointIds(bottom())); } @@ -685,10 +673,10 @@ Tiler::generate(int nx, int ny, int nz, // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); double newOrigin[] = {origin[0], origin[1], origin[2]}; - for(int j = 0; j < ny; j++) + for(conduit::index_t j = 0; j < ny; j++) { newOrigin[0] = origin[0]; - for(int i = 0; i < nx; i++) + for(conduit::index_t i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; @@ -719,14 +707,14 @@ Tiler::generate(int nx, int ny, int nz, // TODO: reserve size for conn, sizes #if 1 // Add the cells in spatial sort order. - for(const int idx : spatial_sort.getIndices()) + for(const auto idx : spatial_sort.getIndices()) { addFaces(tiles[idx].getPointIds(), conn, sizes); } #else - for(int j = 0; j < ny; j++) + for(conduit::index_t j = 0; j < ny; j++) { - for(int i = 0; i < nx; i++) + for(conduit::index_t i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; addFaces(current.getPointIds(), conn, sizes); @@ -754,17 +742,17 @@ Tiler::generate(int nx, int ny, int nz, { // We have x,y points now. We need to replicate them to make multiple planes. // We make z coordinates too. - size_t ptsPerPlane = x.size(); - int nplanes = nz + 1; + conduit::index_t ptsPerPlane = static_cast(x.size()); + conduit::index_t nplanes = nz + 1; x.reserve(ptsPerPlane * nplanes); y.reserve(ptsPerPlane * nplanes); z.reserve(ptsPerPlane * nplanes); - for(size_t i = 0; i < ptsPerPlane; i++) + for(conduit::index_t i = 0; i < ptsPerPlane; i++) z.push_back(origin[2]); - for(int p = 1; p < nplanes; p++) + for(conduit::index_t p = 1; p < nplanes; p++) { double zvalue = origin[2] + static_cast(p) * std::max(width(), height()); - for(size_t i = 0; i < ptsPerPlane; i++) + for(conduit::index_t i = 0; i < ptsPerPlane; i++) { x.push_back(x[i]); y.push_back(y[i]); @@ -774,14 +762,14 @@ Tiler::generate(int nx, int ny, int nz, // Iterate over the tiles and add their hexs. // TODO: reserve size for conn, sizes - for(int k = 0; k < nz; k++) + for(conduit::index_t k = 0; k < nz; k++) { - int offset1 = k * ptsPerPlane; - int offset2 = offset1 + ptsPerPlane; + conduit::index_t offset1 = k * ptsPerPlane; + conduit::index_t offset2 = offset1 + ptsPerPlane; - for(int j = 0; j < ny; j++) + for(conduit::index_t j = 0; j < ny; j++) { - for(int i = 0; i < nx; i++) + for(conduit::index_t i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; addHexs(current.getPointIds(), offset1, offset2, conn, sizes); @@ -826,9 +814,9 @@ Tiler::generate(int nx, int ny, int nz, // Reorder the mesh in 3D. NOTE: boundaries would have to be fixed because // of the changes to node ordering, which we'd have to pass out the node ordering. - const auto reorder = spatial_reordering(res["topologies/mesh"]); + const auto reorder = conduit::blueprint::mesh::utils::topology::spatial_ordering(res["topologies/mesh"]); reorder_topo(res["topologies/mesh"], res["coordsets/coords"], res["fields"], - res["topologies/rmesh"], res["coordsets/rcoords"], res["rfields"], + res["topologies/rmesh"], res["coordsets/rcoords"], res["fields"], reorder); conduit::Node opts; @@ -840,13 +828,17 @@ std::cout << res.to_summary_string(opts) << std::endl; } void -Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, - std::vector &bconn, std::vector &bsizes, std::vector &btype, +Tiler::makeBoundaries2D(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + std::vector &bconn, + std::vector &bsizes, + std::vector &btype, const conduit::Node &options) const { if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) { - for(int i = 0, j = ny-1; j >= 0; j--) + for(conduit::index_t i = 0, j = ny-1; j >= 0; j--) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(left()); @@ -861,7 +853,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, } if(options.has_path("boundaries/bottom") && options.fetch_existing("boundaries/bottom").to_int() > 0) { - for(int i = 0, j = 0; i < nx; i++) + for(conduit::index_t i = 0, j = 0; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(bottom()); @@ -876,7 +868,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, } if(options.has_path("boundaries/right") && options.fetch_existing("boundaries/right").to_int() > 0) { - for(int i = nx - 1, j = 0; j < ny; j++) + for(conduit::index_t i = nx - 1, j = 0; j < ny; j++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(right()); @@ -891,7 +883,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, } if(options.has_path("boundaries/top") && options.fetch_existing("boundaries/top").to_int() > 0) { - for(int i = nx - 1, j = ny - 1; i >= 0; i--) + for(conduit::index_t i = nx - 1, j = ny - 1; i >= 0; i--) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(top()); @@ -907,18 +899,23 @@ Tiler::makeBoundaries2D(const std::vector &tiles, int nx, int ny, } void -Tiler::makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, - size_t nPtsPerPlane, - std::vector &bconn, std::vector &bsizes, std::vector &btype, +Tiler::makeBoundaries3D(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + conduit::index_t nz, + conduit::index_t nPtsPerPlane, + std::vector &bconn, + std::vector &bsizes, + std::vector &btype, const conduit::Node &options) const { if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) { - for(int k = 0; k < nz; k++) + for(conduit::index_t k = 0; k < nz; k++) { - int offset1 = k * nPtsPerPlane; - int offset2 = (k + 1) * nPtsPerPlane; - for(int i = 0, j = ny-1; j >= 0; j--) + conduit::index_t offset1 = k * nPtsPerPlane; + conduit::index_t offset2 = (k + 1) * nPtsPerPlane; + for(conduit::index_t i = 0, j = ny-1; j >= 0; j--) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(left()); @@ -936,11 +933,11 @@ Tiler::makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, } if(options.has_path("boundaries/right") && options.fetch_existing("boundaries/right").to_int() > 0) { - for(int k = 0; k < nz; k++) + for(conduit::index_t k = 0; k < nz; k++) { - int offset1 = k * nPtsPerPlane; - int offset2 = (k + 1) * nPtsPerPlane; - for(int i = nx - 1, j = 0; j < ny; j++) + conduit::index_t offset1 = k * nPtsPerPlane; + conduit::index_t offset2 = (k + 1) * nPtsPerPlane; + for(conduit::index_t i = nx - 1, j = 0; j < ny; j++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(right()); @@ -958,11 +955,11 @@ Tiler::makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, } if(options.has_path("boundaries/bottom") && options.fetch_existing("boundaries/bottom").to_int() > 0) { - for(int k = 0; k < nz; k++) + for(conduit::index_t k = 0; k < nz; k++) { - int offset1 = k * nPtsPerPlane; - int offset2 = (k + 1) * nPtsPerPlane; - for(int i = 0, j = 0; i < nx; i++) + conduit::index_t offset1 = k * nPtsPerPlane; + conduit::index_t offset2 = (k + 1) * nPtsPerPlane; + for(conduit::index_t i = 0, j = 0; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(bottom()); @@ -980,11 +977,11 @@ Tiler::makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, } if(options.has_path("boundaries/top") && options.fetch_existing("boundaries/top").to_int() > 0) { - for(int k = 0; k < nz; k++) + for(conduit::index_t k = 0; k < nz; k++) { - int offset1 = k * nPtsPerPlane; - int offset2 = (k + 1) * nPtsPerPlane; - for(int i = nx - 1, j = ny - 1; i >= 0; i--) + conduit::index_t offset1 = k * nPtsPerPlane; + conduit::index_t offset2 = (k + 1) * nPtsPerPlane; + for(conduit::index_t i = nx - 1, j = ny - 1; i >= 0; i--) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(top()); @@ -1002,8 +999,8 @@ Tiler::makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, } if(options.has_path("boundaries/back") && options.fetch_existing("boundaries/back").to_int() > 0) { - for(int j = 0; j < ny; j++) - for(int i = nx - 1; i >= 0; i--) + for(conduit::index_t j = 0; j < ny; j++) + for(conduit::index_t i = nx - 1; i >= 0; i--) { const Tile ¤t = tiles[(j*nx + i)]; size_t s0 = bsizes.size(); @@ -1014,8 +1011,8 @@ Tiler::makeBoundaries3D(const std::vector &tiles, int nx, int ny, int nz, } if(options.has_path("boundaries/front") && options.fetch_existing("boundaries/front").to_int() > 0) { - for(int j = 0; j < ny; j++) - for(int i = 0; i < nx; i++) + for(conduit::index_t j = 0; j < ny; j++) + for(conduit::index_t i = 0; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; size_t s0 = bsizes.size(); diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp index 6d5615121..1d16d5847 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.hpp @@ -42,9 +42,9 @@ namespace mesh namespace examples { /// Generates a tiled unstructured mesh of quads or hexs. - void CONDUIT_BLUEPRINT_API tiled(index_t nx, - index_t ny, - index_t nz, + void CONDUIT_BLUEPRINT_API tiled(conduit::index_t nx, + conduit::index_t ny, + conduit::index_t nz, conduit::Node &res, const conduit::Node &options); diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index 0c9a17044..dc2e293b8 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -1387,6 +1387,264 @@ topology::reindex_coords(const Node& topo, out_topo["coordset"] = new_coordset.name(); } +//----------------------------------------------------------------------------- +std::vector +topology::spatial_ordering(const conduit::Node &topo) +{ + // Make a new centroid topo and coordset. The coordset will contain the + // element centers. + Node topo_dest, coords_dest, s2dmap, d2smap; + mesh::topology::unstructured::generate_centroids(topo, + topo_dest, + coords_dest, + s2dmap, + d2smap); + // Bundle the coordset components into a vector. + std::vector coords; + const conduit::Node &values = coords_dest.fetch_existing("values"); + for(conduit::index_t i = 0; i < values.number_of_children(); i++) + { + coords.push_back(values[i].as_double_accessor()); + } + + // Sort the coordinates spatially + std::vector reorder; + if(coords.size() == 2) + { + conduit::blueprint::mesh::utils::kdtree spatial_sort; + spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); + reorder = std::move(spatial_sort.getIndices()); + } + else if(coords.size() == 3) + { + conduit::blueprint::mesh::utils::kdtree spatial_sort; + spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); + reorder = std::move(spatial_sort.getIndices()); + } + return reorder; +} + +//--------------------------------------------------------------------------- +topology::TopologyBuilder::TopologyBuilder(const conduit::Node &_topo) : topo(_topo), + old_to_new(), topo_conn(), topo_sizes() +{ + +} + +//--------------------------------------------------------------------------- +topology::TopologyBuilder::TopologyBuilder(const conduit::Node *_topo) : topo(*_topo), + old_to_new(), topo_conn(), topo_sizes() +{ + +} + +//--------------------------------------------------------------------------- +index_t +topology::TopologyBuilder::newPointId(index_t oldPointId) +{ + auto it = old_to_new.find(oldPointId); + index_t newpt; + if(it == old_to_new.end()) + { + newpt = old_to_new.size(); + old_to_new[oldPointId] = newpt; + } + else + { + newpt = it->second; + } + return newpt; +} + +//--------------------------------------------------------------------------- +size_t +topology::TopologyBuilder::add(const index_t *ids, index_t nids) +{ + // Iterate over the ids and renumber them and add the renumbered points + // to the new connectivity. + size_t retval = topo_sizes.size(); + for(index_t i = 0; i < nids; i++) + { + index_t newpid = newPointId(ids[i]); + topo_conn.push_back(newpid); + } + topo_sizes.push_back(nids); + return retval; +} + +//--------------------------------------------------------------------------- +size_t +topology::TopologyBuilder::add(const std::vector &ids) +{ + return add(&ids[0], ids.size()); +} + +//--------------------------------------------------------------------------- +void +topology::TopologyBuilder::execute(conduit::Node &n_out, const std::string &shape) +{ + n_out.reset(); + + // Get the topo and coordset names for the input topo. + const conduit::Node &origcset = coordset(topo); + std::string topoName(topo.name()); + std::string coordsetName(origcset.name()); + + // Build the new topology. + conduit::Node &newcset = n_out["coordsets/"+coordsetName]; + conduit::Node &newtopo = n_out["topologies/"+topoName]; + + // Iterate over the selected original points and make a new coordset + newcset["type"] = "explicit"; + auto axes = coordset::axes(origcset); + auto npts = static_cast(old_to_new.size()); + for(const auto &axis : axes) + { + std::string key("values/" + axis); + auto acc = origcset[key].as_double_accessor(); + + conduit::Node &coords = newcset[key]; + coords.set(DataType::float64(npts)); + auto coords_ptr = static_cast(coords.element_ptr(0)); + for(auto it = old_to_new.begin(); it != old_to_new.end(); it++) + { + coords_ptr[it->second] = acc[it->first]; + } + } + + // Fill in the topo information. + newtopo["type"] = "unstructured"; + newtopo["coordset"] = coordsetName; + conduit::Node &n_ele = newtopo["elements"]; + n_ele["shape"] = shape; + n_ele["connectivity"].set(topo_conn); + n_ele["sizes"].set(topo_sizes); + unstructured::generate_offsets_inline(newtopo); + + clear(); +} + +//--------------------------------------------------------------------------- +void +topology::TopologyBuilder::clear() +{ + old_to_new.clear(); + topo_conn.clear(); + topo_sizes.clear(); +} + + +//--------------------------------------------------------------------------- +std::vector +topology::search(const conduit::Node &topo1, const conduit::Node &topo2) +{ + // The domain_id is not too important in this case. + int domain_id = 0; + + // Get the mesh for topo1 (2 levels up) + const conduit::Node *mesh1 = nullptr; + if(topo1.parent() != nullptr && + topo1.parent()->parent() != nullptr) + { + // Go from the topology up a couple levels to the mesh. + mesh1 = topo1.parent()->parent(); + } + else + { + CONDUIT_ERROR("No parent for topo1."); + } + + // Iterate over mesh2's points to see if its points exist in mesh1. + conduit::blueprint::mesh::utils::query::PointQuery P(*mesh1); + const conduit::Node &cset2 = topology::coordset(topo2); + index_t npts = conduit::blueprint::mesh::utils::coordset::length(cset2); + for(index_t i = 0; i < npts; i++) + { + const std::vector pc = coordset::_explicit::coords(cset2, i); + auto pclen = pc.size(); + double pt3[3]; + pt3[0] = pc[0]; + pt3[1] = (pclen > 1) ? pc[1] : 0.; + pt3[2] = (pclen > 2) ? pc[2] : 0.; + P.add(domain_id, pt3); + } + + // Do the query. + P.execute(cset2.name()); + + const conduit::Node &n_topo1_conn = topo1["elements/connectivity"]; + const conduit::Node &n_topo1_size = topo1["elements/sizes"]; + auto topo1_conn = n_topo1_conn.as_index_t_accessor(); + auto topo1_size = n_topo1_size.as_index_t_accessor(); + + // Iterate over the entities in mesh1 and make hash ids for the entities + // by hashing their sorted points. + index_t nelem = topo1_size.dtype().number_of_elements(); + std::map topo1_entity_ids; + index_t idx = 0; + for(index_t i = 0; i < nelem; i++) + { + std::vector ids; + index_t s = topo1_size[i]; + for(index_t pi = 0; pi < s; pi++) + ids.push_back(topo1_conn[idx++]); + std::sort(ids.begin(), ids.end()); + uint64 h = conduit::utils::hash(&ids[0], static_cast(ids.size())); + topo1_entity_ids[h] = i; + } + + // Get the query results for each mesh2 point. This is a vector of point + // ids from mesh 1 or NotFound. + const auto &r = P.results(domain_id); + + // Iterate over the entities in mesh2 and map their points to mesh 1 points + // if possible before computing hashids for them. If a mesh2 entity's points + // can all be defined in mesh1 then the entity exists in both meshes. + const conduit::Node &n_topo2_conn = topo2["elements/connectivity"]; + const conduit::Node &n_topo2_size = topo2["elements/sizes"]; + auto topo2_conn = n_topo2_conn.as_index_t_accessor(); + auto topo2_size = n_topo2_size.as_index_t_accessor(); + + index_t nelem2 = topo2_size.dtype().number_of_elements(); + std::map topo2_entity_ids; + idx = 0; + std::vector exists; + exists.reserve(nelem2); + std::vector ids; + ids.reserve(10); + for(index_t i = 0; i < nelem2; i++) + { + index_t s = topo2_size[i]; + + // Try and map all of the topo2 entity's points to topo1's coordset. + // If we can do that then the entity may exist. + bool badEntity = false; + ids.clear(); + for(index_t pi = 0; pi < s; pi++) + { + index_t pt = topo2_conn[idx++]; + // See if the point exists in mesh1. + badEntity |= (r[pt] == P.NotFound); + ids.push_back(r[pt]); + } + + if(badEntity) + exists.push_back(0); + else + { + // The entity can be defined in terms of topo1's coordset. Make a + // hash id for it and see if it matches any entities in topo1. + std::sort(ids.begin(), ids.end()); + uint64 h = conduit::utils::hash(&ids[0], static_cast(ids.size())); + + bool found = topo1_entity_ids.find(h) != topo1_entity_ids.end(); + exists.push_back(found ? 1 : 0); + } + } + + return exists; +} + //----------------------------------------------------------------------------- void topology::unstructured::generate_offsets_inline(Node &topo) @@ -1720,228 +1978,6 @@ topology::unstructured::points(const Node &n, return std::vector(pidxs.begin(), pidxs.end()); } - -//--------------------------------------------------------------------------- -topology::TopologyBuilder::TopologyBuilder(const conduit::Node &_topo) : topo(_topo), - old_to_new(), topo_conn(), topo_sizes() -{ - -} - -//--------------------------------------------------------------------------- -topology::TopologyBuilder::TopologyBuilder(const conduit::Node *_topo) : topo(*_topo), - old_to_new(), topo_conn(), topo_sizes() -{ - -} - -//--------------------------------------------------------------------------- -index_t -topology::TopologyBuilder::newPointId(index_t oldPointId) -{ - auto it = old_to_new.find(oldPointId); - index_t newpt; - if(it == old_to_new.end()) - { - newpt = old_to_new.size(); - old_to_new[oldPointId] = newpt; - } - else - { - newpt = it->second; - } - return newpt; -} - -//--------------------------------------------------------------------------- -size_t -topology::TopologyBuilder::add(const index_t *ids, index_t nids) -{ - // Iterate over the ids and renumber them and add the renumbered points - // to the new connectivity. - size_t retval = topo_sizes.size(); - for(index_t i = 0; i < nids; i++) - { - index_t newpid = newPointId(ids[i]); - topo_conn.push_back(newpid); - } - topo_sizes.push_back(nids); - return retval; -} - -//--------------------------------------------------------------------------- -size_t -topology::TopologyBuilder::add(const std::vector &ids) -{ - return add(&ids[0], ids.size()); -} - -//--------------------------------------------------------------------------- -void -topology::TopologyBuilder::execute(conduit::Node &n_out, const std::string &shape) -{ - n_out.reset(); - - // Get the topo and coordset names for the input topo. - const conduit::Node &origcset = coordset(topo); - std::string topoName(topo.name()); - std::string coordsetName(origcset.name()); - - // Build the new topology. - conduit::Node &newcset = n_out["coordsets/"+coordsetName]; - conduit::Node &newtopo = n_out["topologies/"+topoName]; - - // Iterate over the selected original points and make a new coordset - newcset["type"] = "explicit"; - auto axes = coordset::axes(origcset); - auto npts = static_cast(old_to_new.size()); - for(const auto &axis : axes) - { - std::string key("values/" + axis); - auto acc = origcset[key].as_double_accessor(); - - conduit::Node &coords = newcset[key]; - coords.set(DataType::float64(npts)); - auto coords_ptr = static_cast(coords.element_ptr(0)); - for(auto it = old_to_new.begin(); it != old_to_new.end(); it++) - { - coords_ptr[it->second] = acc[it->first]; - } - } - - // Fill in the topo information. - newtopo["type"] = "unstructured"; - newtopo["coordset"] = coordsetName; - conduit::Node &n_ele = newtopo["elements"]; - n_ele["shape"] = shape; - n_ele["connectivity"].set(topo_conn); - n_ele["sizes"].set(topo_sizes); - unstructured::generate_offsets_inline(newtopo); - - clear(); -} - -//--------------------------------------------------------------------------- -void -topology::TopologyBuilder::clear() -{ - old_to_new.clear(); - topo_conn.clear(); - topo_sizes.clear(); -} - - -//--------------------------------------------------------------------------- -std::vector -topology::search(const conduit::Node &topo1, const conduit::Node &topo2) -{ - // The domain_id is not too important in this case. - int domain_id = 0; - - // Get the mesh for topo1 (2 levels up) - const conduit::Node *mesh1 = nullptr; - if(topo1.parent() != nullptr && - topo1.parent()->parent() != nullptr) - { - // Go from the topology up a couple levels to the mesh. - mesh1 = topo1.parent()->parent(); - } - else - { - CONDUIT_ERROR("No parent for topo1."); - } - - // Iterate over mesh2's points to see if its points exist in mesh1. - conduit::blueprint::mesh::utils::query::PointQuery P(*mesh1); - const conduit::Node &cset2 = topology::coordset(topo2); - index_t npts = conduit::blueprint::mesh::utils::coordset::length(cset2); - for(index_t i = 0; i < npts; i++) - { - const std::vector pc = coordset::_explicit::coords(cset2, i); - auto pclen = pc.size(); - double pt3[3]; - pt3[0] = pc[0]; - pt3[1] = (pclen > 1) ? pc[1] : 0.; - pt3[2] = (pclen > 2) ? pc[2] : 0.; - P.add(domain_id, pt3); - } - - // Do the query. - P.execute(cset2.name()); - - const conduit::Node &n_topo1_conn = topo1["elements/connectivity"]; - const conduit::Node &n_topo1_size = topo1["elements/sizes"]; - auto topo1_conn = n_topo1_conn.as_index_t_accessor(); - auto topo1_size = n_topo1_size.as_index_t_accessor(); - - // Iterate over the entities in mesh1 and make hash ids for the entities - // by hashing their sorted points. - index_t nelem = topo1_size.dtype().number_of_elements(); - std::map topo1_entity_ids; - index_t idx = 0; - for(index_t i = 0; i < nelem; i++) - { - std::vector ids; - index_t s = topo1_size[i]; - for(index_t pi = 0; pi < s; pi++) - ids.push_back(topo1_conn[idx++]); - std::sort(ids.begin(), ids.end()); - uint64 h = conduit::utils::hash(&ids[0], static_cast(ids.size())); - topo1_entity_ids[h] = i; - } - - // Get the query results for each mesh2 point. This is a vector of point - // ids from mesh 1 or NotFound. - const auto &r = P.results(domain_id); - - // Iterate over the entities in mesh2 and map their points to mesh 1 points - // if possible before computing hashids for them. If a mesh2 entity's points - // can all be defined in mesh1 then the entity exists in both meshes. - const conduit::Node &n_topo2_conn = topo2["elements/connectivity"]; - const conduit::Node &n_topo2_size = topo2["elements/sizes"]; - auto topo2_conn = n_topo2_conn.as_index_t_accessor(); - auto topo2_size = n_topo2_size.as_index_t_accessor(); - - index_t nelem2 = topo2_size.dtype().number_of_elements(); - std::map topo2_entity_ids; - idx = 0; - std::vector exists; - exists.reserve(nelem2); - std::vector ids; - ids.reserve(10); - for(index_t i = 0; i < nelem2; i++) - { - index_t s = topo2_size[i]; - - // Try and map all of the topo2 entity's points to topo1's coordset. - // If we can do that then the entity may exist. - bool badEntity = false; - ids.clear(); - for(index_t pi = 0; pi < s; pi++) - { - index_t pt = topo2_conn[idx++]; - // See if the point exists in mesh1. - badEntity |= (r[pt] == P.NotFound); - ids.push_back(r[pt]); - } - - if(badEntity) - exists.push_back(0); - else - { - // The entity can be defined in terms of topo1's coordset. Make a - // hash id for it and see if it matches any entities in topo1. - std::sort(ids.begin(), ids.end()); - uint64 h = conduit::utils::hash(&ids[0], static_cast(ids.size())); - - bool found = topo1_entity_ids.find(h) != topo1_entity_ids.end(); - exists.push_back(found ? 1 : 0); - } - } - - return exists; -} - //----------------------------------------------------------------------------- // -- end conduit::blueprint::mesh::utils::topology -- //----------------------------------------------------------------------------- diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp index 8eac7b37a..ee8e10d22 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp @@ -369,6 +369,18 @@ namespace topology const conduit::Node& old_gvids, const conduit::Node& new_gvids, conduit::Node& out_topo); + //------------------------------------------------------------------------- + /** + * @brief Applies a spatial sorting algorithm (based on a kdtree) to the + * topology's centroids and returns a vector containing the sorted + * order. + * + * @param topo The topology whose elements are being sorted. + * + * @return A vector containing the new element order. + */ + std::vector spatial_ordering(const conduit::Node &topo); + //------------------------------------------------------------------------- /** From 40a54f2bcc01dffe3d41569f6efb3ae72935777e Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 Sep 2023 19:02:22 -0700 Subject: [PATCH 13/50] Made kdtree findPoint return conduit::index_t --- src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp b/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp index 679d6ca4e..a88136416 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_kdtree.hpp @@ -98,7 +98,7 @@ class kdtree @return The point id of the point or NotFound if the point was not found. */ - int findPoint(const PointType &pt) const; + conduit::index_t findPoint(const PointType &pt) const; /** @brief Return the number of dimensions. @@ -352,10 +352,10 @@ void kdtree::cutRange(const RangeType &input, //--------------------------------------------------------------------------- template -int +conduit::index_t kdtree::findPoint(const kdtree::PointType &pt) const { - int foundIndex = NotFound; + conduit::index_t foundIndex = NotFound; #ifdef CONDUIT_DEBUG_KDTREE std::cout << "findPoint("; for(int i = 0; i < dims(); i++) From cdc7623dbd60889c8f6b5f41c615ce35de3d0973 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 5 Sep 2023 19:04:28 -0700 Subject: [PATCH 14/50] Moved slice_array from partitioner to mesh utils. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 147 +------------- .../conduit_blueprint_mesh_partition.cpp | 135 +------------ .../conduit_blueprint_mesh_partition.hpp | 4 - .../conduit_blueprint_mesh_utils.cpp | 189 +++++++++++++++++- .../conduit_blueprint_mesh_utils.hpp | 35 ++++ 5 files changed, 231 insertions(+), 279 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 1c6e2ef99..957b9e91b 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -51,147 +51,6 @@ namespace examples namespace detail { -//--------------------------------------------------------------------------- -// @brief Slice the n_src array using the indices stored in ids. We use the -// array classes for their [] operators that deal with interleaved -// and non-interleaved arrays. -template -inline void -typed_slice_array(const T &src, const std::vector &ids, T &dest) -{ - size_t n = ids.size(); - for(size_t i = 0; i < n; i++) - dest[i] = src[ids[i]]; -} - -//--------------------------------------------------------------------------- -// @note Should this be part of conduit::Node or DataArray somehow. The number -// of times I've had to slice an array... -template -void -slice_array(const conduit::Node &n_src_values, - const std::vector &ids, - Node &n_dest_values) -{ - // Copy the DataType of the input conduit::Node but override the number of elements - // before copying it in so assigning to n_dest_values triggers a memory - // allocation. - auto dt = n_src_values.dtype(); - n_dest_values = DataType(n_src_values.dtype().id(), ids.size()); - - // Do the slice. - if(dt.is_int8()) - { - auto dest(n_dest_values.as_int8_array()); - typed_slice_array(n_src_values.as_int8_array(), ids, dest); - } - else if(dt.is_int16()) - { - auto dest(n_dest_values.as_int16_array()); - typed_slice_array(n_src_values.as_int16_array(), ids, dest); - } - else if(dt.is_int32()) - { - auto dest(n_dest_values.as_int32_array()); - typed_slice_array(n_src_values.as_int32_array(), ids, dest); - } - else if(dt.is_int64()) - { - auto dest(n_dest_values.as_int64_array()); - typed_slice_array(n_src_values.as_int64_array(), ids, dest); - } - else if(dt.is_uint8()) - { - auto dest(n_dest_values.as_uint8_array()); - typed_slice_array(n_src_values.as_uint8_array(), ids, dest); - } - else if(dt.is_uint16()) - { - auto dest(n_dest_values.as_uint16_array()); - typed_slice_array(n_src_values.as_uint16_array(), ids, dest); - } - else if(dt.is_uint32()) - { - auto dest(n_dest_values.as_uint32_array()); - typed_slice_array(n_src_values.as_uint32_array(), ids, dest); - } - else if(dt.is_uint64()) - { - auto dest(n_dest_values.as_uint64_array()); - typed_slice_array(n_src_values.as_uint64_array(), ids, dest); - } - else if(dt.is_char()) - { - auto dest(n_dest_values.as_char_array()); - typed_slice_array(n_src_values.as_char_array(), ids, dest); - } - else if(dt.is_short()) - { - auto dest(n_dest_values.as_short_array()); - typed_slice_array(n_src_values.as_short_array(), ids, dest); - } - else if(dt.is_int()) - { - auto dest(n_dest_values.as_int_array()); - typed_slice_array(n_src_values.as_int_array(), ids, dest); - } - else if(dt.is_long()) - { - auto dest(n_dest_values.as_long_array()); - typed_slice_array(n_src_values.as_long_array(), ids, dest); - } - else if(dt.is_unsigned_char()) - { - auto dest(n_dest_values.as_unsigned_char_array()); - typed_slice_array(n_src_values.as_unsigned_char_array(), ids, dest); - } - else if(dt.is_unsigned_short()) - { - auto dest(n_dest_values.as_unsigned_short_array()); - typed_slice_array(n_src_values.as_unsigned_short_array(), ids, dest); - } - else if(dt.is_unsigned_int()) - { - auto dest(n_dest_values.as_unsigned_int_array()); - typed_slice_array(n_src_values.as_unsigned_int_array(), ids, dest); - } - else if(dt.is_unsigned_long()) - { - auto dest(n_dest_values.as_unsigned_long_array()); - typed_slice_array(n_src_values.as_unsigned_long_array(), ids, dest); - } - else if(dt.is_float()) - { - auto dest(n_dest_values.as_float_array()); - typed_slice_array(n_src_values.as_float_array(), ids, dest); - } - else if(dt.is_double()) - { - auto dest(n_dest_values.as_double_array()); - typed_slice_array(n_src_values.as_double_array(), ids, dest); - } -} - -void -slice_field(const conduit::Node &src, - const std::vector &ids, - conduit::Node &dest) -{ - if(src.number_of_children() > 0) - { - // Reorder an mcarray - for(conduit::index_t ci = 0; ci < src.number_of_children(); ci++) - { - const conduit::Node &comp = src[ci]; - slice_array(comp, ids, dest[comp.name()]); - } - } - else - { - slice_array(src, ids, dest); - } -} - void reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const conduit::Node &fields, conduit::Node &dest_topo, conduit::Node &dest_coordset, conduit::Node &dest_fields, @@ -263,7 +122,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con conduit::blueprint::mesh::coordset::uniform::to_explicit(coordset, coordset_explicit); else coordset_explicit.set_external(coordset); - slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); + conduit::blueprint::mesh::utils::slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); // Reorder fields that match this topo for(conduit::index_t fi = 0; fi < fields.number_of_children(); fi++) @@ -277,11 +136,11 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con dest["topology"] = dest_topo.name(); //src["topology"]; if(dest["association"].as_string() == "element") { - slice_field(src["values"], reorder, dest["values"]); + conduit::blueprint::mesh::utils::slice_field(src["values"], reorder, dest["values"]); } else { - slice_field(src["values"], ptReorder, dest["values"]); + conduit::blueprint::mesh::utils::slice_field(src["values"], ptReorder, dest["values"]); } } } diff --git a/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp b/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp index ec515cfb9..ca09a46c2 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp @@ -2768,11 +2768,11 @@ Partitioner::copy_field(const conduit::Node &n_field, for(index_t i = 0; i < n_values.number_of_children(); i++) { const conduit::Node &n_vals = n_values[i]; - slice_array(n_vals, ids, new_values[n_vals.name()]); + conduit::blueprint::mesh::utils::slice_array(n_vals, ids, new_values[n_vals.name()]); } } else - slice_array(n_values, ids, new_values); + conduit::blueprint::mesh::utils::slice_array(n_values, ids, new_values); } else { @@ -2785,130 +2785,11 @@ Partitioner::copy_field(const conduit::Node &n_field, for(index_t i = 0; i < n.number_of_children(); i++) { const conduit::Node &n_vals = n[i]; - slice_array(n_vals, ids, new_values[n_vals.name()]); + conduit::blueprint::mesh::utils::slice_array(n_vals, ids, new_values[n_vals.name()]); } } else - slice_array(n, ids, new_values); - } -} - -//--------------------------------------------------------------------------- -// @brief Slice the n_src array using the indices stored in ids. We use the -// array classes for their [] operators that deal with interleaved -// and non-interleaved arrays. -template -inline void -typed_slice_array(const T &src, const std::vector &ids, T &dest) -{ - size_t n = ids.size(); - for(size_t i = 0; i < n; i++) - dest[i] = src[ids[i]]; -} - -//--------------------------------------------------------------------------- -// @note Should this be part of conduit::Node or DataArray somehow. The number -// of times I've had to slice an array... -void -Partitioner::slice_array(const conduit::Node &n_src_values, - const std::vector &ids, Node &n_dest_values) const -{ - // Copy the DataType of the input conduit::Node but override the number of elements - // before copying it in so assigning to n_dest_values triggers a memory - // allocation. - auto dt = n_src_values.dtype(); - n_dest_values = DataType(n_src_values.dtype().id(), ids.size()); - - // Do the slice. - if(dt.is_int8()) - { - auto dest(n_dest_values.as_int8_array()); - typed_slice_array(n_src_values.as_int8_array(), ids, dest); - } - else if(dt.is_int16()) - { - auto dest(n_dest_values.as_int16_array()); - typed_slice_array(n_src_values.as_int16_array(), ids, dest); - } - else if(dt.is_int32()) - { - auto dest(n_dest_values.as_int32_array()); - typed_slice_array(n_src_values.as_int32_array(), ids, dest); - } - else if(dt.is_int64()) - { - auto dest(n_dest_values.as_int64_array()); - typed_slice_array(n_src_values.as_int64_array(), ids, dest); - } - else if(dt.is_uint8()) - { - auto dest(n_dest_values.as_uint8_array()); - typed_slice_array(n_src_values.as_uint8_array(), ids, dest); - } - else if(dt.is_uint16()) - { - auto dest(n_dest_values.as_uint16_array()); - typed_slice_array(n_src_values.as_uint16_array(), ids, dest); - } - else if(dt.is_uint32()) - { - auto dest(n_dest_values.as_uint32_array()); - typed_slice_array(n_src_values.as_uint32_array(), ids, dest); - } - else if(dt.is_uint64()) - { - auto dest(n_dest_values.as_uint64_array()); - typed_slice_array(n_src_values.as_uint64_array(), ids, dest); - } - else if(dt.is_char()) - { - auto dest(n_dest_values.as_char_array()); - typed_slice_array(n_src_values.as_char_array(), ids, dest); - } - else if(dt.is_short()) - { - auto dest(n_dest_values.as_short_array()); - typed_slice_array(n_src_values.as_short_array(), ids, dest); - } - else if(dt.is_int()) - { - auto dest(n_dest_values.as_int_array()); - typed_slice_array(n_src_values.as_int_array(), ids, dest); - } - else if(dt.is_long()) - { - auto dest(n_dest_values.as_long_array()); - typed_slice_array(n_src_values.as_long_array(), ids, dest); - } - else if(dt.is_unsigned_char()) - { - auto dest(n_dest_values.as_unsigned_char_array()); - typed_slice_array(n_src_values.as_unsigned_char_array(), ids, dest); - } - else if(dt.is_unsigned_short()) - { - auto dest(n_dest_values.as_unsigned_short_array()); - typed_slice_array(n_src_values.as_unsigned_short_array(), ids, dest); - } - else if(dt.is_unsigned_int()) - { - auto dest(n_dest_values.as_unsigned_int_array()); - typed_slice_array(n_src_values.as_unsigned_int_array(), ids, dest); - } - else if(dt.is_unsigned_long()) - { - auto dest(n_dest_values.as_unsigned_long_array()); - typed_slice_array(n_src_values.as_unsigned_long_array(), ids, dest); - } - else if(dt.is_float()) - { - auto dest(n_dest_values.as_float_array()); - typed_slice_array(n_src_values.as_float_array(), ids, dest); - } - else if(dt.is_double()) - { - auto dest(n_dest_values.as_double_array()); - typed_slice_array(n_src_values.as_double_array(), ids, dest); + conduit::blueprint::mesh::utils::slice_array(n, ids, new_values); } } @@ -3270,7 +3151,7 @@ Partitioner::create_new_rectilinear_coordset(const conduit::Node &n_coordset, indices.push_back(i); const conduit::Node &src = n_values[d]; - slice_array(src, indices, n_new_values[src.name()]); + conduit::blueprint::mesh::utils::slice_array(src, indices, n_new_values[src.name()]); } } @@ -3292,7 +3173,7 @@ Partitioner::create_new_explicit_coordset(const conduit::Node &n_coordset, { const conduit::Node &n_axis_values = n_values[axes[i]]; conduit::Node &n_new_axis_values = n_new_values[axes[i]]; - slice_array(n_axis_values, vertex_ids, n_new_axis_values); + conduit::blueprint::mesh::utils::slice_array(n_axis_values, vertex_ids, n_new_axis_values); } } else if(n_coordset["type"].as_string() == "rectilinear") @@ -3306,7 +3187,7 @@ Partitioner::create_new_explicit_coordset(const conduit::Node &n_coordset, { const conduit::Node &n_axis_values = n_values[axes[i]]; conduit::Node &n_new_axis_values = n_new_values[axes[i]]; - slice_array(n_axis_values, vertex_ids, n_new_axis_values); + conduit::blueprint::mesh::utils::slice_array(n_axis_values, vertex_ids, n_new_axis_values); } } else if(n_coordset["type"].as_string() == "explicit") @@ -3318,7 +3199,7 @@ Partitioner::create_new_explicit_coordset(const conduit::Node &n_coordset, { const conduit::Node &n_axis_values = n_values[axes[i]]; conduit::Node &n_new_axis_values = n_new_values[axes[i]]; - slice_array(n_axis_values, vertex_ids, n_new_axis_values); + conduit::blueprint::mesh::utils::slice_array(n_axis_values, vertex_ids, n_new_axis_values); } } } diff --git a/src/libs/blueprint/conduit_blueprint_mesh_partition.hpp b/src/libs/blueprint/conduit_blueprint_mesh_partition.hpp index fe9284ce8..059470a11 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_partition.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_partition.hpp @@ -431,10 +431,6 @@ class CONDUIT_BLUEPRINT_API Partitioner const std::vector &ids, Node &n_output_fields) const; - void slice_array(const conduit::Node &n_src_values, - const std::vector &ids, - Node &n_dest_values) const; - void get_vertex_ids_for_element_ids(const conduit::Node &n_topo, const std::vector &element_ids, std::set &vertex_ids) const; diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index dc2e293b8..ae33c3b00 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -345,6 +345,187 @@ find_domain_id(const Node &node) return domain_id; } +//--------------------------------------------------------------------------- +// @brief Slice the n_src array using the indices stored in ids. We use the +// array classes for their [] operators that deal with interleaved +// and non-interleaved arrays. +template +inline void +typed_slice_array(const T &src, const std::vector &ids, T &dest) +{ + size_t n = ids.size(); + for(size_t i = 0; i < n; i++) + dest[i] = src[ids[i]]; +} + +//--------------------------------------------------------------------------- +/** + @brief Slice a node as an array using its native data type. + */ +template +void +slice_array_internal(const conduit::Node &n_src_values, + const std::vector &ids, + Node &n_dest_values) +{ + // Copy the DataType of the input conduit::Node but override the number of elements + // before copying it in so assigning to n_dest_values triggers a memory + // allocation. + auto dt = n_src_values.dtype(); + n_dest_values = DataType(n_src_values.dtype().id(), ids.size()); + + // Do the slice. + if(dt.is_int8()) + { + auto dest(n_dest_values.as_int8_array()); + typed_slice_array(n_src_values.as_int8_array(), ids, dest); + } + else if(dt.is_int16()) + { + auto dest(n_dest_values.as_int16_array()); + typed_slice_array(n_src_values.as_int16_array(), ids, dest); + } + else if(dt.is_int32()) + { + auto dest(n_dest_values.as_int32_array()); + typed_slice_array(n_src_values.as_int32_array(), ids, dest); + } + else if(dt.is_int64()) + { + auto dest(n_dest_values.as_int64_array()); + typed_slice_array(n_src_values.as_int64_array(), ids, dest); + } + else if(dt.is_uint8()) + { + auto dest(n_dest_values.as_uint8_array()); + typed_slice_array(n_src_values.as_uint8_array(), ids, dest); + } + else if(dt.is_uint16()) + { + auto dest(n_dest_values.as_uint16_array()); + typed_slice_array(n_src_values.as_uint16_array(), ids, dest); + } + else if(dt.is_uint32()) + { + auto dest(n_dest_values.as_uint32_array()); + typed_slice_array(n_src_values.as_uint32_array(), ids, dest); + } + else if(dt.is_uint64()) + { + auto dest(n_dest_values.as_uint64_array()); + typed_slice_array(n_src_values.as_uint64_array(), ids, dest); + } + else if(dt.is_char()) + { + auto dest(n_dest_values.as_char_array()); + typed_slice_array(n_src_values.as_char_array(), ids, dest); + } + else if(dt.is_short()) + { + auto dest(n_dest_values.as_short_array()); + typed_slice_array(n_src_values.as_short_array(), ids, dest); + } + else if(dt.is_int()) + { + auto dest(n_dest_values.as_int_array()); + typed_slice_array(n_src_values.as_int_array(), ids, dest); + } + else if(dt.is_long()) + { + auto dest(n_dest_values.as_long_array()); + typed_slice_array(n_src_values.as_long_array(), ids, dest); + } + else if(dt.is_unsigned_char()) + { + auto dest(n_dest_values.as_unsigned_char_array()); + typed_slice_array(n_src_values.as_unsigned_char_array(), ids, dest); + } + else if(dt.is_unsigned_short()) + { + auto dest(n_dest_values.as_unsigned_short_array()); + typed_slice_array(n_src_values.as_unsigned_short_array(), ids, dest); + } + else if(dt.is_unsigned_int()) + { + auto dest(n_dest_values.as_unsigned_int_array()); + typed_slice_array(n_src_values.as_unsigned_int_array(), ids, dest); + } + else if(dt.is_unsigned_long()) + { + auto dest(n_dest_values.as_unsigned_long_array()); + typed_slice_array(n_src_values.as_unsigned_long_array(), ids, dest); + } + else if(dt.is_float()) + { + auto dest(n_dest_values.as_float_array()); + typed_slice_array(n_src_values.as_float_array(), ids, dest); + } + else if(dt.is_double()) + { + auto dest(n_dest_values.as_double_array()); + typed_slice_array(n_src_values.as_double_array(), ids, dest); + } +} + +//--------------------------------------------------------------------------- +void +slice_array(const conduit::Node &n_src_values, + const std::vector &ids, + Node &n_dest_values) +{ + slice_array_internal(n_src_values, ids, n_dest_values); +} + +//--------------------------------------------------------------------------- +void +slice_array(const conduit::Node &n_src_values, + const std::vector &ids, + Node &n_dest_values) +{ + slice_array_internal(n_src_values, ids, n_dest_values); +} + +//--------------------------------------------------------------------------- +template +void +slice_field_internal(const conduit::Node &n_src_values, + const std::vector &ids, + conduit::Node &n_dest_values) +{ + if(n_src_values.number_of_children() > 0) + { + // Reorder an mcarray + for(conduit::index_t ci = 0; ci < n_src_values.number_of_children(); ci++) + { + const conduit::Node &comp = n_src_values[ci]; + slice_array(comp, ids, n_dest_values[comp.name()]); + } + } + else + { + slice_array(n_src_values, ids, n_dest_values); + } +} + +//--------------------------------------------------------------------------- +void +slice_field(const conduit::Node &n_src_values, + const std::vector &ids, + conduit::Node &n_dest_values) +{ + slice_field_internal(n_src_values, ids, n_dest_values); +} + +//--------------------------------------------------------------------------- +void +slice_field(const conduit::Node &n_src_values, + const std::vector &ids, + conduit::Node &n_dest_values) +{ + slice_field_internal(n_src_values, ids, n_dest_values); +} + + //----------------------------------------------------------------------------- // -- begin conduit::blueprint::mesh::utils::connectivity -- //----------------------------------------------------------------------------- @@ -2527,7 +2708,7 @@ PointQuery::acceleratedSearch(int ndims, float64 searchPt[3] = {static_cast(input_ptr[i * 3 + 0]), static_cast(input_ptr[i * 3 + 1]), static_cast(input_ptr[i * 3 + 2])}; - int found = search.findPoint(searchPt); + auto found = static_cast(search.findPoint(searchPt)); result_ptr[i] = (found != search.NotFound) ? found : NotFound; }); handled = true; @@ -2550,7 +2731,7 @@ PointQuery::acceleratedSearch(int ndims, float32 searchPt[3] = {static_cast(input_ptr[i * 3 + 0]), static_cast(input_ptr[i * 3 + 1]), static_cast(input_ptr[i * 3 + 2])}; - int found = search.findPoint(searchPt); + auto found = static_cast(search.findPoint(searchPt)); result_ptr[i] = (found != search.NotFound) ? found : NotFound; }); handled = true; @@ -2572,7 +2753,7 @@ PointQuery::acceleratedSearch(int ndims, { float64 searchPt[2] = {static_cast(input_ptr[i * 3 + 0]), static_cast(input_ptr[i * 3 + 1])}; - int found = search.findPoint(searchPt); + auto found = static_cast(search.findPoint(searchPt)); result_ptr[i] = (found != search.NotFound) ? found : NotFound; }); handled = true; @@ -2593,7 +2774,7 @@ PointQuery::acceleratedSearch(int ndims, { float32 searchPt[2] = {static_cast(input_ptr[i * 3 + 0]), static_cast(input_ptr[i * 3 + 1])}; - int found = search.findPoint(searchPt); + auto found = static_cast(search.findPoint(searchPt)); result_ptr[i] = (found != search.NotFound) ? found : NotFound; }); handled = true; diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp index ee8e10d22..63e7db590 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp @@ -216,6 +216,41 @@ CONDUIT_BLUEPRINT_API const Node * find_reference_node(const Node &node, const s //----------------------------------------------------------------------------- index_t CONDUIT_BLUEPRINT_API find_domain_id(const Node &node); +//----------------------------------------------------------------------------- +/** + @brief Slice a node containing array data and copy data for the supplied ids + to a new node. + + @param n_src_values The node containing the source values. + @param ids The ids that will be extracted from the source values. + @param dest The new node that will contain the sliced data. + */ +void CONDUIT_BLUEPRINT_API slice_array(const conduit::Node &n_src_values, + const std::vector &ids, + Node &n_dest_values); + +/// Same as above. +void CONDUIT_BLUEPRINT_API slice_array(const conduit::Node &n_src_values, + const std::vector &ids, + Node &n_dest_values); + +//----------------------------------------------------------------------------- +/** + @brief Slice a values node for a field where values may be an mcarray. The new + node will contain the data for the supplied indices. + + @param n_src_values The node containing the source values. + @param ids The ids that will be extracted from the source values. + @param dest The new node that will contain the sliced data. + */ +void CONDUIT_BLUEPRINT_API slice_field(const conduit::Node &n_src_values, + const std::vector &ids, + conduit::Node &dest); + +/// Same as above. +void CONDUIT_BLUEPRINT_API slice_field(const conduit::Node &n_src_values, + const std::vector &ids, + conduit::Node &n_dest_values); //----------------------------------------------------------------------------- // -- begin conduit::blueprint::mesh::utils::connectivity -- From 888bb3461c0560d507eac34948e048ef7817b08d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 Sep 2023 11:42:52 -0700 Subject: [PATCH 15/50] Allow reorder into the same topo nodes. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 78 ++----------------- .../conduit_blueprint_mesh_utils.cpp | 38 ++++++++- 2 files changed, 41 insertions(+), 75 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 957b9e91b..4511bee55 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -69,7 +69,8 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con const auto sizes = n_sizes.as_index_t_accessor(); const auto offsets = n_offsets.as_index_t_accessor(); - // Temp vectors to store reordered connectivity. + // Temp vectors to store reordered connectivity. We use temp vectors so + // we can convert to a matching datatype after we've constructed the data. std::vector newconn, newoffsets, newsizes; newconn.reserve(conn.number_of_elements()); newsizes.reserve(sizes.number_of_elements()); @@ -103,7 +104,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con // Store the new connectivity. dest_topo["type"] = topo["type"]; - dest_topo["coordset"] = dest_coordset.name(); //topo["coordset"]; + dest_topo["coordset"] = dest_coordset.name(); dest_topo["elements/shape"] = topo["elements/shape"]; conduit::Node tmp; tmp.set_external(newconn.data(), newconn.size()); @@ -133,7 +134,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con auto &newfields = dest_topo["fields"]; conduit::Node &dest = newfields[src.name()]; dest["association"] = src["association"]; - dest["topology"] = dest_topo.name(); //src["topology"]; + dest["topology"] = dest_topo.name(); if(dest["association"].as_string() == "element") { conduit::blueprint::mesh::utils::slice_field(src["values"], reorder, dest["values"]); @@ -471,64 +472,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, origin[2] = options.fetch_existing("origin/z").to_double(); if(options.has_path("tile")) initialize(options.fetch_existing("tile")); -#if 1 - // Make cell centers for each tile and record that no tiles have been visited. - conduit::index_t ncells = nx * ny; - std::vector visited(ncells, 0); - std::vector cx(ncells), cy(ncells); - for(int j = 0, idx = 0; j < ny; j++) - { - for(int i = 0; i < nx; i++, idx++) - { - cx[idx] = origin[0] + (i + 0.5f) * width(); - cy[idx] = origin[1] + (j + 0.5f) * height(); - } - } - double *tileCenters[2] = {&cx[0], &cy[0]}; - conduit::blueprint::mesh::utils::kdtree spatial_sort; - spatial_sort.initialize(tileCenters, ncells); - - // Traverse the cells in the desired spatial order. - std::vector tiles(nx * ny); - constexpr conduit::index_t invalidIndex = -1; - for(const auto idx : spatial_sort.getIndices()) - { - // The first time we've used the tile, set its size. - Tile ¤t = tiles[idx]; - current.reset(m_xpts.size()); - - // Copy neighbor points to the current tile if we can. - conduit::index_t i = idx % nx; - conduit::index_t j = idx / nx; - conduit::index_t left_idx = (i > 0) ? (idx - 1) : invalidIndex; - conduit::index_t right_idx = (i < nx - 1) ? (idx + 1) : invalidIndex; - conduit::index_t bottom_idx = (j > 0) ? (idx - nx) : invalidIndex; - conduit::index_t top_idx = (j < ny - 1) ? (idx + nx) : invalidIndex; - if(left_idx != invalidIndex && visited[left_idx]) - { - current.setPointIds(left(), tiles[left_idx].getPointIds(right())); - } - if(right_idx != invalidIndex && visited[right_idx]) - { - current.setPointIds(right(), tiles[right_idx].getPointIds(left())); - } - if(bottom_idx != invalidIndex && visited[bottom_idx]) - { - current.setPointIds(bottom(), tiles[bottom_idx].getPointIds(top())); - } - if(top_idx != invalidIndex && visited[top_idx]) - { - current.setPointIds(top(), tiles[top_idx].getPointIds(bottom())); - } - - // Make this tile's points - double newOrigin[] = {origin[0] + i * width(), origin[1] + j * height(), origin[2]}; - addPoints(newOrigin, current.getPointIds(), x, y); - visited[idx] = 1; - } - -#else // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); double newOrigin[] = {origin[0], origin[1], origin[2]}; @@ -559,18 +503,11 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, } newOrigin[1] += height(); } -#endif + if(nz < 1) { // Iterate over the tiles and add their quads. // TODO: reserve size for conn, sizes -#if 1 - // Add the cells in spatial sort order. - for(const auto idx : spatial_sort.getIndices()) - { - addFaces(tiles[idx].getPointIds(), conn, sizes); - } -#else for(conduit::index_t j = 0; j < ny; j++) { for(conduit::index_t i = 0; i < nx; i++) @@ -579,7 +516,6 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, addFaces(current.getPointIds(), conn, sizes); } } -#endif // NOTE: z coords in output will be empty. // Boundaries @@ -666,7 +602,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, res["topologies/mesh/elements/sizes"].set(sizes); #if 1 - if(nz > 0) + //if(nz > 0) { // We need offsets. conduit::blueprint::mesh::utils::topology::unstructured::generate_offsets(res["topologies/mesh"], res["topologies/mesh/elements/offsets"]); @@ -675,7 +611,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, // of the changes to node ordering, which we'd have to pass out the node ordering. const auto reorder = conduit::blueprint::mesh::utils::topology::spatial_ordering(res["topologies/mesh"]); reorder_topo(res["topologies/mesh"], res["coordsets/coords"], res["fields"], - res["topologies/rmesh"], res["coordsets/rcoords"], res["fields"], + res["topologies/mesh"], res["coordsets/coords"], res["fields"], reorder); conduit::Node opts; diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index ae33c3b00..651c3e959 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -467,22 +467,52 @@ slice_array_internal(const conduit::Node &n_src_values, } } +//--------------------------------------------------------------------------- +bool same_nodes(const conduit::Node &n1, const conduit::Node &n2) +{ + return (&n1 == &n2) || + (n1.contiguous_data_ptr() != nullptr && + n1.contiguous_data_ptr() == n2.contiguous_data_ptr()); +} + //--------------------------------------------------------------------------- void slice_array(const conduit::Node &n_src_values, const std::vector &ids, - Node &n_dest_values) + conduit::Node &n_dest_values) { - slice_array_internal(n_src_values, ids, n_dest_values); + // Check whether the src and dest nodes are the same. If so, we slice into + // a tmp node and move its contents. + if(same_nodes(n_src_values, n_dest_values)) + { + conduit::Node tmp; + slice_array_internal(n_src_values, ids, tmp); + n_dest_values.move(tmp); + } + else + { + slice_array_internal(n_src_values, ids, n_dest_values); + } } //--------------------------------------------------------------------------- void slice_array(const conduit::Node &n_src_values, const std::vector &ids, - Node &n_dest_values) + conduit::Node &n_dest_values) { - slice_array_internal(n_src_values, ids, n_dest_values); + // Check whether the src and dest nodes are the same. If so, we slice into + // a tmp node and move its contents. + if(same_nodes(n_src_values, n_dest_values)) + { + conduit::Node tmp; + slice_array_internal(n_src_values, ids, tmp); + n_dest_values.move(tmp); + } + else + { + slice_array_internal(n_src_values, ids, n_dest_values); + } } //--------------------------------------------------------------------------- From 781d67ffe73be3748e7d54420e914560b89f4e18 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 Sep 2023 11:43:20 -0700 Subject: [PATCH 16/50] Remove some std::move in return statements as it is not needed. --- src/libs/blueprint/conduit_blueprint_mesh.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh.cpp b/src/libs/blueprint/conduit_blueprint_mesh.cpp index 276a0a7e7..fd457216e 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh.cpp @@ -150,7 +150,7 @@ std::vector intersect_sets(const Container1 &v1, } } - return std::vector(std::move(res)); + return res; } //----------------------------------------------------------------------------- @@ -182,7 +182,7 @@ std::vector subtract_sets(const std::vector &v1, res.push_back(v1[i1]); } } - return std::vector(std::move(res)); + return res; } //----------------------------------------------------------------------------- @@ -2008,7 +2008,7 @@ mesh::domains(conduit::Node &n) } } - return std::vector(std::move(doms)); + return doms; } @@ -2035,7 +2035,7 @@ mesh::domains(const conduit::Node &mesh) } } - return std::vector(std::move(doms)); + return doms; } //------------------------------------------------------------------------- @@ -2744,7 +2744,7 @@ group_domains_and_maps(conduit::Node &mesh, conduit::Node &s2dmap, conduit::Node } } - return std::vector(std::move(doms_and_maps)); + return doms_and_maps; } //----------------------------------------------------------------------------- @@ -3596,7 +3596,7 @@ mesh::generate_sides(conduit::Node& mesh, side_dims.push_back(2); } - return std::vector(std::move(side_dims)); + return side_dims; }; verify_generate_mesh(mesh, src_adjset_name); @@ -3644,7 +3644,7 @@ mesh::generate_corners(conduit::Node& mesh, corner_dims.push_back(di); } - return std::vector(std::move(corner_dims)); + return corner_dims; }; verify_generate_mesh(mesh, src_adjset_name); From bea2218d874755acea472d3259e64ffbff9254f2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 Sep 2023 15:09:31 -0700 Subject: [PATCH 17/50] Renumber boundary nodes if we are reordering the elements. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 290 ++++++++++++------ 1 file changed, 196 insertions(+), 94 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 4511bee55..f87e1a2e0 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -20,6 +20,10 @@ #include "conduit_blueprint_exports.h" #include "conduit_blueprint_mesh_kdtree.hpp" #include "conduit_blueprint_mesh_utils.hpp" +#include + +// Uncomment this to add some fields on the filed mesh prior to reordering. +//#define CONDUIT_TILER_DEBUG_FIELDS //----------------------------------------------------------------------------- // -- begin conduit::-- @@ -53,8 +57,9 @@ namespace detail void reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const conduit::Node &fields, + const std::vector &reorder, conduit::Node &dest_topo, conduit::Node &dest_coordset, conduit::Node &dest_fields, - const std::vector &reorder) + std::vector &old2NewPoints) { conduit::blueprint::mesh::utils::ShapeType shape(topo); @@ -79,7 +84,10 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con // Mapping information for the points. constexpr conduit::index_t invalidNode = -1; auto npts = conduit::blueprint::mesh::coordset::length(coordset); - std::vector old2NewPoints(npts, invalidNode), ptReorder(npts, invalidNode); + std::vector ptReorder(npts, invalidNode); + old2NewPoints.resize(npts); + for(size_t i = 0; i < npts; i++) + old2NewPoints[i] = invalidNode; conduit::index_t newPointIndex = 0; // We iterate over elements in the specified order. We iterate over the @@ -90,6 +98,7 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con for(conduit::index_t i = 0; i < sizes[cellIndex]; i++) { auto id = conn[offsets[cellIndex] + i]; + // if the old point has not been seen, renumber it. if(old2NewPoints[id] == invalidNode) { ptReorder[newPointIndex] = id; @@ -125,24 +134,29 @@ reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const con coordset_explicit.set_external(coordset); conduit::blueprint::mesh::utils::slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); - // Reorder fields that match this topo + // Reorder fields that match this topo. + std::vector fieldNames; for(conduit::index_t fi = 0; fi < fields.number_of_children(); fi++) { const conduit::Node &src = fields[fi]; if(src["topology"].as_string() == topo.name()) { - auto &newfields = dest_topo["fields"]; - conduit::Node &dest = newfields[src.name()]; - dest["association"] = src["association"]; - dest["topology"] = dest_topo.name(); - if(dest["association"].as_string() == "element") - { - conduit::blueprint::mesh::utils::slice_field(src["values"], reorder, dest["values"]); - } - else - { - conduit::blueprint::mesh::utils::slice_field(src["values"], ptReorder, dest["values"]); - } + fieldNames.push_back(src.name()); + } + } + for(const auto &fieldName : fieldNames) + { + const conduit::Node &src = fields.fetch_existing(fieldName); + conduit::Node &dest = dest_fields[fieldName]; + dest["association"] = src["association"]; + dest["topology"] = dest_topo.name(); + if(dest["association"].as_string() == "element") + { + conduit::blueprint::mesh::utils::slice_field(src["values"], reorder, dest["values"]); + } + else + { + conduit::blueprint::mesh::utils::slice_field(src["values"], ptReorder, dest["values"]); } } } @@ -253,20 +267,22 @@ class Tiler } /// Emit the quad cells using this tile's point ids. + template void addFaces(const std::vector &ptids, std::vector &conn, std::vector &sizes, - conduit::index_t offset = 0, - bool reverse = false) const + conduit::index_t offset, + bool reverse, + Transform &&transform) const { const size_t nquads = m_quads.size() / 4; int order[] = {reverse ? 3 : 0, reverse ? 2 : 1, reverse ? 1 : 2, reverse ? 0 : 3}; for(size_t i = 0; i < nquads; i++) { - conn.push_back(offset + ptids[m_quads[4*i + order[0]]]); - conn.push_back(offset + ptids[m_quads[4*i + order[1]]]); - conn.push_back(offset + ptids[m_quads[4*i + order[2]]]); - conn.push_back(offset + ptids[m_quads[4*i + order[3]]]); + conn.push_back(transform(offset + ptids[m_quads[4*i + order[0]]])); + conn.push_back(transform(offset + ptids[m_quads[4*i + order[1]]])); + conn.push_back(transform(offset + ptids[m_quads[4*i + order[2]]])); + conn.push_back(transform(offset + ptids[m_quads[4*i + order[3]]])); sizes.push_back(4); } } @@ -330,15 +346,18 @@ class Tiler } /// Make 2D boundaries. + template void makeBoundaries2D(const std::vector &tiles, conduit::index_t nx, conduit::index_t ny, std::vector &bconn, std::vector &bsizes, std::vector &btype, - const conduit::Node &options) const; + const conduit::Node &options, + Transform &&transform) const; /// Make 3D boundaries. + template void makeBoundaries3D(const std::vector &tiles, conduit::index_t nx, conduit::index_t ny, @@ -347,19 +366,22 @@ class Tiler std::vector &bconn, std::vector &bsizes, std::vector &btype, - const conduit::Node &options) const; + const conduit::Node &options, + Transform &&transform) const; private: std::vector m_xpts, m_ypts; double m_width, m_height; std::vector m_left, m_right, m_bottom, m_top, m_quads; }; +//--------------------------------------------------------------------------- Tiler::Tiler() : m_xpts(), m_ypts(), m_width(0.), m_height(0.), m_left(), m_right(), m_bottom(), m_top(), m_quads() { initialize(); } +//--------------------------------------------------------------------------- void Tiler::initialize() { @@ -428,6 +450,7 @@ Tiler::initialize() m_height = computeExtents(m_ypts); } +//--------------------------------------------------------------------------- void Tiler::initialize(const conduit::Node &t) { @@ -443,6 +466,7 @@ Tiler::initialize(const conduit::Node &t) m_height = computeExtents(m_ypts); } +//--------------------------------------------------------------------------- /** \brief Generate coordinate and connectivity arrays using a tiled mesh pattern, given by the Tile class. @@ -472,6 +496,9 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, origin[2] = options.fetch_existing("origin/z").to_double(); if(options.has_path("tile")) initialize(options.fetch_existing("tile")); + bool reorder = true; + if(options.has_path("reorder")) + reorder = options.fetch_existing("reorder").to_int() > 0; // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); @@ -504,6 +531,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, newOrigin[1] += height(); } + conduit::index_t ptsPerPlane = 0; if(nz < 1) { // Iterate over the tiles and add their quads. @@ -513,31 +541,20 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; - addFaces(current.getPointIds(), conn, sizes); + addFaces(current.getPointIds(), conn, sizes, 0, false, + [](conduit::index_t id) { + return id; + }); } } // NOTE: z coords in output will be empty. - - // Boundaries - makeBoundaries2D(tiles, nx, ny, bconn, bsizes, btype, options); - if(!bconn.empty()) - { - res["topologies/boundary/type"] = "unstructured"; - res["topologies/boundary/coordset"] = "coords"; - res["topologies/boundary/elements/shape"] = "line"; - res["topologies/boundary/elements/connectivity"].set(bconn); - res["topologies/boundary/elements/sizes"].set(bsizes); - - res["fields/boundary_type/topology"] = "boundary"; - res["fields/boundary_type/association"] = "element"; - res["fields/boundary_type/values"].set(btype); - } } else { + ptsPerPlane = static_cast(x.size()); + // We have x,y points now. We need to replicate them to make multiple planes. // We make z coordinates too. - conduit::index_t ptsPerPlane = static_cast(x.size()); conduit::index_t nplanes = nz + 1; x.reserve(ptsPerPlane * nplanes); y.reserve(ptsPerPlane * nplanes); @@ -571,25 +588,10 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, } } } - - // Boundaries - makeBoundaries3D(tiles, nx, ny, nz, ptsPerPlane, bconn, bsizes, btype, options); - if(!bconn.empty()) - { - res["topologies/boundary/type"] = "unstructured"; - res["topologies/boundary/coordset"] = "coords"; - res["topologies/boundary/elements/shape"] = "quad"; - res["topologies/boundary/elements/connectivity"].set(bconn); - res["topologies/boundary/elements/sizes"].set(bsizes); - - res["fields/boundary_type/topology"] = "boundary"; - res["fields/boundary_type/association"] = "element"; - res["fields/boundary_type/values"].set(btype); - } } + // Make the Blueprint mesh. res["coordsets/coords/type"] = "explicit"; - res["coordsets/coords/values"] = "explicit"; res["coordsets/coords/values/x"].set(x); res["coordsets/coords/values/y"].set(y); if(!z.empty()) @@ -601,27 +603,123 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, res["topologies/mesh/elements/connectivity"].set(conn); res["topologies/mesh/elements/sizes"].set(sizes); -#if 1 - //if(nz > 0) +#ifdef CONDUIT_TILER_DEBUG_FIELDS + // Add fields to test the reordering. + std::vector nodeids, elemids; + auto npts = static_cast(x.size()); + nodeids.reserve(npts); + for(conduit::index_t i = 0; i < npts; i++) + nodeids.push_back(i); + res["fields/nodeids/topology"] = "mesh"; + res["fields/nodeids/association"] = "vertex"; + res["fields/nodeids/values"].set(nodeids); + + auto nelem = static_cast(sizes.size()); + elemids.reserve(nelem); + for(conduit::index_t i = 0; i < nelem; i++) + elemids.push_back(i); + res["fields/elemids/topology"] = "mesh"; + res["fields/elemids/association"] = "element"; + res["fields/elemids/values"].set(elemids); + + std::vector dist; + dist.reserve(npts); + if(nz < 1) + { + for(conduit::index_t i = 0; i < npts; i++) + dist.push_back(sqrt(x[i]*x[i] + y[i]*y[i])); + } + else + { + for(conduit::index_t i = 0; i < npts; i++) + dist.push_back(sqrt(x[i]*x[i] + y[i]*y[i] + z[i]*z[i])); + } + res["fields/dist/topology"] = "mesh"; + res["fields/dist/association"] = "vertex"; + res["fields/dist/values"].set(dist); +#endif + + // Reorder the elements unless it was turned off. + std::vector old2NewPoint; + if(reorder) { // We need offsets. conduit::blueprint::mesh::utils::topology::unstructured::generate_offsets(res["topologies/mesh"], res["topologies/mesh/elements/offsets"]); - // Reorder the mesh in 3D. NOTE: boundaries would have to be fixed because - // of the changes to node ordering, which we'd have to pass out the node ordering. - const auto reorder = conduit::blueprint::mesh::utils::topology::spatial_ordering(res["topologies/mesh"]); + // Reorder the mesh elements. + const auto elemOrder = conduit::blueprint::mesh::utils::topology::spatial_ordering(res["topologies/mesh"]); reorder_topo(res["topologies/mesh"], res["coordsets/coords"], res["fields"], + elemOrder, res["topologies/mesh"], res["coordsets/coords"], res["fields"], - reorder); + old2NewPoint); + } -conduit::Node opts; -opts["num_children_threshold"] = 100000; -opts["num_elements_threshold"] = 500; -std::cout << res.to_summary_string(opts) << std::endl; + // Boundaries + std::string bshape; + if(nz < 1) + { + // 2D + bshape = "line"; + if(reorder) + { + makeBoundaries2D(tiles, nx, ny, bconn, bsizes, btype, options, + [&](conduit::index_t id) { + // Renumber the boundary connectivity + return old2NewPoint[id]; + }); + } + else + { + makeBoundaries2D(tiles, nx, ny, bconn, bsizes, btype, options, + [](conduit::index_t id) { + return id; + }); + } } + else + { + // 3D + bshape = "quad"; + if(reorder) + { + makeBoundaries3D(tiles, nx, ny, nz, ptsPerPlane, bconn, bsizes, btype, options, + [&](conduit::index_t id) { + // Renumber the boundary connectivity + return old2NewPoint[id]; + }); + } + else + { + makeBoundaries3D(tiles, nx, ny, nz, ptsPerPlane, bconn, bsizes, btype, options, + [&](conduit::index_t id) { + return id; + }); + } + } + if(!bconn.empty()) + { + res["topologies/boundary/type"] = "unstructured"; + res["topologies/boundary/coordset"] = "coords"; + res["topologies/boundary/elements/shape"] = bshape; + res["topologies/boundary/elements/connectivity"].set(bconn); + res["topologies/boundary/elements/sizes"].set(bsizes); + + res["fields/boundary_type/topology"] = "boundary"; + res["fields/boundary_type/association"] = "element"; + res["fields/boundary_type/values"].set(btype); + } + +#if 0 + // Print for debugging. + conduit::Node opts; + opts["num_children_threshold"] = 100000; + opts["num_elements_threshold"] = 500; + std::cout << res.to_summary_string(opts) << std::endl; #endif } +//--------------------------------------------------------------------------- +template void Tiler::makeBoundaries2D(const std::vector &tiles, conduit::index_t nx, @@ -629,7 +727,8 @@ Tiler::makeBoundaries2D(const std::vector &tiles, std::vector &bconn, std::vector &bsizes, std::vector &btype, - const conduit::Node &options) const + const conduit::Node &options, + Transform &&transform) const { if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) { @@ -639,8 +738,8 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(left()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(ids[bi]); - bconn.push_back(ids[bi - 1]); + bconn.push_back(transform(ids[bi])); + bconn.push_back(transform(ids[bi - 1])); bsizes.push_back(2); btype.push_back(0); } @@ -654,8 +753,8 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(bottom()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(ids[bi]); - bconn.push_back(ids[bi + 1]); + bconn.push_back(transform(ids[bi])); + bconn.push_back(transform(ids[bi + 1])); bsizes.push_back(2); btype.push_back(2); } @@ -669,8 +768,8 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(right()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(ids[bi]); - bconn.push_back(ids[bi + 1]); + bconn.push_back(transform(ids[bi])); + bconn.push_back(transform(ids[bi + 1])); bsizes.push_back(2); btype.push_back(1); } @@ -684,8 +783,8 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(top()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(ids[bi]); - bconn.push_back(ids[bi - 1]); + bconn.push_back(transform(ids[bi])); + bconn.push_back(transform(ids[bi - 1])); bsizes.push_back(2); btype.push_back(3); } @@ -693,6 +792,8 @@ Tiler::makeBoundaries2D(const std::vector &tiles, } } +//--------------------------------------------------------------------------- +template void Tiler::makeBoundaries3D(const std::vector &tiles, conduit::index_t nx, @@ -702,7 +803,8 @@ Tiler::makeBoundaries3D(const std::vector &tiles, std::vector &bconn, std::vector &bsizes, std::vector &btype, - const conduit::Node &options) const + const conduit::Node &options, + Transform &&transform) const { if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) { @@ -716,10 +818,10 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(left()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(offset1 + ids[bi]); - bconn.push_back(offset1 + ids[bi - 1]); - bconn.push_back(offset2 + ids[bi - 1]); - bconn.push_back(offset2 + ids[bi]); + bconn.push_back(transform(offset1 + ids[bi])); + bconn.push_back(transform(offset1 + ids[bi - 1])); + bconn.push_back(transform(offset2 + ids[bi - 1])); + bconn.push_back(transform(offset2 + ids[bi])); bsizes.push_back(4); btype.push_back(0); } @@ -738,10 +840,10 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(right()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(offset1 + ids[bi]); - bconn.push_back(offset1 + ids[bi + 1]); - bconn.push_back(offset2 + ids[bi + 1]); - bconn.push_back(offset2 + ids[bi]); + bconn.push_back(transform(offset1 + ids[bi])); + bconn.push_back(transform(offset1 + ids[bi + 1])); + bconn.push_back(transform(offset2 + ids[bi + 1])); + bconn.push_back(transform(offset2 + ids[bi])); bsizes.push_back(4); btype.push_back(1); } @@ -760,10 +862,10 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(bottom()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(offset1 + ids[bi]); - bconn.push_back(offset1 + ids[bi + 1]); - bconn.push_back(offset2 + ids[bi + 1]); - bconn.push_back(offset2 + ids[bi]); + bconn.push_back(transform(offset1 + ids[bi])); + bconn.push_back(transform(offset1 + ids[bi + 1])); + bconn.push_back(transform(offset2 + ids[bi + 1])); + bconn.push_back(transform(offset2 + ids[bi])); bsizes.push_back(4); btype.push_back(2); } @@ -782,10 +884,10 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(top()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(offset1 + ids[bi]); - bconn.push_back(offset1 + ids[bi - 1]); - bconn.push_back(offset2 + ids[bi - 1]); - bconn.push_back(offset2 + ids[bi]); + bconn.push_back(transform(offset1 + ids[bi])); + bconn.push_back(transform(offset1 + ids[bi - 1])); + bconn.push_back(transform(offset2 + ids[bi - 1])); + bconn.push_back(transform(offset2 + ids[bi])); bsizes.push_back(4); btype.push_back(3); } @@ -799,7 +901,7 @@ Tiler::makeBoundaries3D(const std::vector &tiles, { const Tile ¤t = tiles[(j*nx + i)]; size_t s0 = bsizes.size(); - addFaces(current.getPointIds(), bconn, bsizes, 0, true); + addFaces(current.getPointIds(), bconn, bsizes, 0, true, transform); for( ; s0 < bsizes.size(); s0++) btype.push_back(4); } @@ -811,7 +913,7 @@ Tiler::makeBoundaries3D(const std::vector &tiles, { const Tile ¤t = tiles[(j*nx + i)]; size_t s0 = bsizes.size(); - addFaces(current.getPointIds(), bconn, bsizes, nz * nPtsPerPlane); + addFaces(current.getPointIds(), bconn, bsizes, nz * nPtsPerPlane, false, transform); for( ; s0 < bsizes.size(); s0++) btype.push_back(5); } From 7d6a20cffa8623712b2e56c55deb9757b7f4a9ab Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 Sep 2023 17:42:31 -0700 Subject: [PATCH 18/50] Explored using partition for reordering. Moved reorder function. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 155 +++++------------- .../conduit_blueprint_mesh_partition.cpp | 11 ++ .../conduit_blueprint_mesh_utils.cpp | 119 ++++++++++++++ .../conduit_blueprint_mesh_utils.hpp | 37 ++++- 4 files changed, 206 insertions(+), 116 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index f87e1a2e0..29d30592a 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -23,7 +23,11 @@ #include // Uncomment this to add some fields on the filed mesh prior to reordering. -//#define CONDUIT_TILER_DEBUG_FIELDS +// #define CONDUIT_TILER_DEBUG_FIELDS + +// Uncomment this to try an experimental mode that uses the partitioner to +// do reordering. +// #define CONDUIT_USE_PARTITIONER_FOR_REORDER //----------------------------------------------------------------------------- // -- begin conduit::-- @@ -55,113 +59,6 @@ namespace examples namespace detail { -void -reorder_topo(const conduit::Node &topo, const conduit::Node &coordset, const conduit::Node &fields, - const std::vector &reorder, - conduit::Node &dest_topo, conduit::Node &dest_coordset, conduit::Node &dest_fields, - std::vector &old2NewPoints) -{ - conduit::blueprint::mesh::utils::ShapeType shape(topo); - - // Handle unstructured meshes (but not polyhedral meshes yet) - if(topo.fetch_existing("type").as_string() == "unstructured" && !shape.is_polyhedral()) - { - // Input connectivity information. - const auto &n_conn = topo.fetch_existing("elements/connectivity"); - const auto &n_sizes = topo.fetch_existing("elements/sizes"); - const auto &n_offsets = topo.fetch_existing("elements/offsets"); - const auto conn = n_conn.as_index_t_accessor(); - const auto sizes = n_sizes.as_index_t_accessor(); - const auto offsets = n_offsets.as_index_t_accessor(); - - // Temp vectors to store reordered connectivity. We use temp vectors so - // we can convert to a matching datatype after we've constructed the data. - std::vector newconn, newoffsets, newsizes; - newconn.reserve(conn.number_of_elements()); - newsizes.reserve(sizes.number_of_elements()); - newoffsets.reserve(offsets.number_of_elements()); - - // Mapping information for the points. - constexpr conduit::index_t invalidNode = -1; - auto npts = conduit::blueprint::mesh::coordset::length(coordset); - std::vector ptReorder(npts, invalidNode); - old2NewPoints.resize(npts); - for(size_t i = 0; i < npts; i++) - old2NewPoints[i] = invalidNode; - conduit::index_t newPointIndex = 0; - - // We iterate over elements in the specified order. We iterate over the - // points in each element and renumber the points. - conduit::index_t newoffset = 0; - for(const auto cellIndex : reorder) - { - for(conduit::index_t i = 0; i < sizes[cellIndex]; i++) - { - auto id = conn[offsets[cellIndex] + i]; - // if the old point has not been seen, renumber it. - if(old2NewPoints[id] == invalidNode) - { - ptReorder[newPointIndex] = id; - old2NewPoints[id] = newPointIndex++; - } - newconn.push_back(old2NewPoints[id]); - } - newsizes.push_back(sizes[cellIndex]); - newoffsets.push_back(newoffset); - newoffset += sizes[cellIndex]; - } - - // Store the new connectivity. - dest_topo["type"] = topo["type"]; - dest_topo["coordset"] = dest_coordset.name(); - dest_topo["elements/shape"] = topo["elements/shape"]; - conduit::Node tmp; - tmp.set_external(newconn.data(), newconn.size()); - tmp.to_data_type(n_conn.dtype().id(), dest_topo["elements/connectivity"]); - tmp.set_external(newsizes.data(), newsizes.size()); - tmp.to_data_type(n_sizes.dtype().id(), dest_topo["elements/sizes"]); - tmp.set_external(newoffsets.data(), newoffsets.size()); - tmp.to_data_type(n_offsets.dtype().id(), dest_topo["elements/offsets"]); - - // Reorder the coordset now, making it explicit if needed. - dest_coordset["type"] = "explicit"; - conduit::Node coordset_explicit; - if(coordset["type"].as_string() == "rectilinear") - conduit::blueprint::mesh::coordset::rectilinear::to_explicit(coordset, coordset_explicit); - else if(coordset["type"].as_string() == "uniform") - conduit::blueprint::mesh::coordset::uniform::to_explicit(coordset, coordset_explicit); - else - coordset_explicit.set_external(coordset); - conduit::blueprint::mesh::utils::slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); - - // Reorder fields that match this topo. - std::vector fieldNames; - for(conduit::index_t fi = 0; fi < fields.number_of_children(); fi++) - { - const conduit::Node &src = fields[fi]; - if(src["topology"].as_string() == topo.name()) - { - fieldNames.push_back(src.name()); - } - } - for(const auto &fieldName : fieldNames) - { - const conduit::Node &src = fields.fetch_existing(fieldName); - conduit::Node &dest = dest_fields[fieldName]; - dest["association"] = src["association"]; - dest["topology"] = dest_topo.name(); - if(dest["association"].as_string() == "element") - { - conduit::blueprint::mesh::utils::slice_field(src["values"], reorder, dest["values"]); - } - else - { - conduit::blueprint::mesh::utils::slice_field(src["values"], ptReorder, dest["values"]); - } - } - } -} - /** \brief Keep track of some tile information. */ @@ -603,7 +500,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, res["topologies/mesh/elements/connectivity"].set(conn); res["topologies/mesh/elements/sizes"].set(sizes); -#ifdef CONDUIT_TILER_DEBUG_FIELDS +#if 1//def CONDUIT_TILER_DEBUG_FIELDS // Add fields to test the reordering. std::vector nodeids, elemids; auto npts = static_cast(x.size()); @@ -646,12 +543,42 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, // We need offsets. conduit::blueprint::mesh::utils::topology::unstructured::generate_offsets(res["topologies/mesh"], res["topologies/mesh/elements/offsets"]); - // Reorder the mesh elements. + // Create a new order for the mesh elements. const auto elemOrder = conduit::blueprint::mesh::utils::topology::spatial_ordering(res["topologies/mesh"]); - reorder_topo(res["topologies/mesh"], res["coordsets/coords"], res["fields"], - elemOrder, - res["topologies/mesh"], res["coordsets/coords"], res["fields"], - old2NewPoint); + +#ifdef CONDUIT_USE_PARTITIONER_FOR_REORDER + // NOTE: This was an idea I had after I made reorder. Reordering is like + // making an explicit selection for the partitioner. Can we just use + // the partitioner? Kind of, it turns out. + // + // 1. Elements are reordered like we want + // 2. Nodes are not reordered in their order of use by elements. + // 3. Passing the same node as input/output does bad things. + + // Make an explicit selection for partition to do the reordering. + conduit::Node options; + conduit::Node &sel = options["selections"].append(); + sel["type"] = "explicit"; + sel["topology"] = "mesh"; + sel["elements"].set_external(const_cast(elemOrder.data()), elemOrder.size()); + conduit::Node output; + conduit::blueprint::mesh::partition(res, options, output); + + // Extract the vertex mapping. + auto ids = output.fetch_existing("fields/original_vertex_ids/values/ids").as_index_t_accessor(); + for(conduit::index_t i = 0; i < ids.number_of_elements(); i++) + { + old2NewPoint.push_back(ids[i]); + } + res.reset(); + res.move(output); +#else + conduit::blueprint::mesh::utils::topology::unstructured::reorder( + res["topologies/mesh"], res["coordsets/coords"], res["fields"], + elemOrder, + res["topologies/mesh"], res["coordsets/coords"], res["fields"], + old2NewPoint); +#endif } // Boundaries diff --git a/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp b/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp index ca09a46c2..350b0fac3 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_partition.cpp @@ -812,6 +812,17 @@ SelectionExplicit::determine_is_whole(const conduit::Node &n_mesh) const for(index_t i = 0; i < n; i++) unique.insert(indices[i]); is_whole = static_cast(unique.size()) == num_elem_in_mesh; + + // If the mesh is whole, check that the indices are all in ascending + // order. If not, we're reordering and we should not consider the + // chunk available for passing through whole. + if(n > 1) + { + for(index_t i = 1; i < n && is_whole; i++) + { + is_whole &= (indices[i - 1] < indices[i]); + } + } } } catch(conduit::Error &) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index 651c3e959..d17983459 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -2189,6 +2189,125 @@ topology::unstructured::points(const Node &n, return std::vector(pidxs.begin(), pidxs.end()); } +//----------------------------------------------------------------------------- +void +topology::unstructured::reorder(const conduit::Node &topo, + const conduit::Node &coordset, + const conduit::Node &fields, + const std::vector &reorder, + conduit::Node &dest_topo, + conduit::Node &dest_coordset, + conduit::Node &dest_fields, + std::vector &old2NewPoints) +{ + conduit::blueprint::mesh::utils::ShapeType shape(topo); + + // Handle unstructured meshes (but not polyhedral meshes yet) + if(topo.fetch_existing("type").as_string() == "unstructured" && !shape.is_polyhedral()) + { + // Input connectivity information. + const auto &n_conn = topo.fetch_existing("elements/connectivity"); + const auto &n_sizes = topo.fetch_existing("elements/sizes"); + const auto &n_offsets = topo.fetch_existing("elements/offsets"); + const auto conn = n_conn.as_index_t_accessor(); + const auto sizes = n_sizes.as_index_t_accessor(); + const auto offsets = n_offsets.as_index_t_accessor(); + + // Temp vectors to store reordered connectivity. We use temp vectors so + // we can convert to a matching datatype after we've constructed the data. + std::vector newconn, newoffsets, newsizes; + newconn.reserve(conn.number_of_elements()); + newsizes.reserve(sizes.number_of_elements()); + newoffsets.reserve(offsets.number_of_elements()); + + // Mapping information for the points. + constexpr conduit::index_t invalidNode = -1; + auto npts = conduit::blueprint::mesh::coordset::length(coordset); + // Fill in the old2New point mapping. It gets passed out of the function. + old2NewPoints.resize(npts); + for(size_t i = 0; i < npts; i++) + old2NewPoints[i] = invalidNode; + // ptReorder is used to reorder/slice vertex-associated data. We'll allow + // up to npts values but it might not be that large if we are selecting a + // subset of elements. + std::vector ptReorder; + ptReorder.reserve(npts); + conduit::index_t newPointIndex = 0; + + // We iterate over elements in the specified order. We iterate over the + // points in each element and renumber the points. + conduit::index_t newoffset = 0; + for(const auto cellIndex : reorder) + { + for(conduit::index_t i = 0; i < sizes[cellIndex]; i++) + { + auto id = conn[offsets[cellIndex] + i]; + // if the old point has not been seen, renumber it. + if(old2NewPoints[id] == invalidNode) + { + ptReorder.push_back(id); + old2NewPoints[id] = newPointIndex++; + } + newconn.push_back(old2NewPoints[id]); + } + newsizes.push_back(sizes[cellIndex]); + newoffsets.push_back(newoffset); + newoffset += sizes[cellIndex]; + } + + // Store the new connectivity. + dest_topo["type"] = topo["type"]; + dest_topo["coordset"] = dest_coordset.name(); + dest_topo["elements/shape"] = topo["elements/shape"]; + conduit::Node tmp; + tmp.set_external(newconn.data(), newconn.size()); + tmp.to_data_type(n_conn.dtype().id(), dest_topo["elements/connectivity"]); + tmp.set_external(newsizes.data(), newsizes.size()); + tmp.to_data_type(n_sizes.dtype().id(), dest_topo["elements/sizes"]); + tmp.set_external(newoffsets.data(), newoffsets.size()); + tmp.to_data_type(n_offsets.dtype().id(), dest_topo["elements/offsets"]); + + // Reorder the coordset now, making it explicit if needed. + dest_coordset["type"] = "explicit"; + conduit::Node coordset_explicit; + if(coordset["type"].as_string() == "rectilinear") + conduit::blueprint::mesh::coordset::rectilinear::to_explicit(coordset, coordset_explicit); + else if(coordset["type"].as_string() == "uniform") + conduit::blueprint::mesh::coordset::uniform::to_explicit(coordset, coordset_explicit); + else + coordset_explicit.set_external(coordset); + conduit::blueprint::mesh::utils::slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); + + // Reorder fields that match this topo. + std::vector fieldNames; + for(conduit::index_t fi = 0; fi < fields.number_of_children(); fi++) + { + const conduit::Node &src = fields[fi]; + if(src["topology"].as_string() == topo.name()) + { + fieldNames.push_back(src.name()); + } + } + for(const auto &fieldName : fieldNames) + { + const conduit::Node &src = fields.fetch_existing(fieldName); + conduit::Node &dest = dest_fields[fieldName]; + dest["association"] = src["association"]; + dest["topology"] = dest_topo.name(); + if(dest["association"].as_string() == "element") + { + conduit::blueprint::mesh::utils::slice_field(src["values"], reorder, dest["values"]); + } + else + { + conduit::blueprint::mesh::utils::slice_field(src["values"], ptReorder, dest["values"]); + } + } + + // TODO: renumber adjsets. + } +} + //----------------------------------------------------------------------------- // -- end conduit::blueprint::mesh::utils::topology -- //----------------------------------------------------------------------------- diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp index 63e7db590..0f3d517d9 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp @@ -414,8 +414,7 @@ namespace topology * * @return A vector containing the new element order. */ - std::vector spatial_ordering(const conduit::Node &topo); - + std::vector CONDUIT_BLUEPRINT_API spatial_ordering(const conduit::Node &topo); //------------------------------------------------------------------------- /** @@ -508,6 +507,40 @@ namespace topology //------------------------------------------------------------------------- std::vector CONDUIT_BLUEPRINT_API points(const Node &topo, const index_t i); + + //------------------------------------------------------------------------- + /** + * @brief Reorder the topology's elements and nodes, according to the new + * element \order vector. + * + * @param topo A node containing the topo to be reordered. + * @param coordset A node containing the coordset to be reordered. + * @param fields A node containing the fields to be reordered. Only fields + * that match the topology node name will be modified. + * @param order A vector containing element indices in their new order. + * @param dest_topo A node that will contain reordered topo. It can + * be the same node as topo. + * @param dest_coordset A node that will contain reordered coordset. It can + * be the same node as coordset. + * @param dest_fields A node that will contain the reordered fields. It can + * be the same node as fields. + * @param[out] old2NewPoints A vector that contains new point indices for + * old point indices, which can be used for mapping + * data from the original order to the new order. + * + * @note This reorder function is similar to calling partition with an explicit + * index selection if the indices are provided in a new order. This + * function will also reorder the nodes in their order of use by elements + * in the new order and partition does not currently do that. + */ + void CONDUIT_BLUEPRINT_API reorder(const conduit::Node &topo, + const conduit::Node &coordset, + const conduit::Node &fields, + const std::vector &order, + conduit::Node &dest_topo, + conduit::Node &dest_coordset, + conduit::Node &dest_fields, + std::vector &old2NewPoints); } //------------------------------------------------------------------------- // -- end conduit::blueprint::mesh::utils::topology::unstructured -- From 16eefc4088a079d9704301f820197379e8f9c74d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 6 Sep 2023 18:11:25 -0700 Subject: [PATCH 19/50] Make the index type selectable through options in tiled function. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 29d30592a..a862832b6 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -393,10 +393,21 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, origin[2] = options.fetch_existing("origin/z").to_double(); if(options.has_path("tile")) initialize(options.fetch_existing("tile")); + bool reorder = true; if(options.has_path("reorder")) reorder = options.fetch_existing("reorder").to_int() > 0; + conduit::DataType indexDT(conduit::DataType::index_t()); + if(options.has_child("datatype")) + { + auto s = options.fetch_existing("datatype").as_string(); + if((s == "int") || (s == "int32") || (s == "integer")) + { + indexDT = conduit::DataType::int32(); + } + } + // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); double newOrigin[] = {origin[0], origin[1], origin[2]}; @@ -497,10 +508,13 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, res["topologies/mesh/type"] = "unstructured"; res["topologies/mesh/coordset"] = "coords"; res["topologies/mesh/elements/shape"] = z.empty() ? "quad" : "hex"; - res["topologies/mesh/elements/connectivity"].set(conn); - res["topologies/mesh/elements/sizes"].set(sizes); + conduit::Node tmp; + tmp.set_external(conn.data(), conn.size()); + tmp.to_data_type(indexDT.id(), res["topologies/mesh/elements/connectivity"]); + tmp.set_external(sizes.data(), sizes.size()); + tmp.to_data_type(indexDT.id(), res["topologies/mesh/elements/sizes"]); -#if 1//def CONDUIT_TILER_DEBUG_FIELDS +#ifdef CONDUIT_TILER_DEBUG_FIELDS // Add fields to test the reordering. std::vector nodeids, elemids; auto npts = static_cast(x.size()); @@ -628,8 +642,12 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, res["topologies/boundary/type"] = "unstructured"; res["topologies/boundary/coordset"] = "coords"; res["topologies/boundary/elements/shape"] = bshape; - res["topologies/boundary/elements/connectivity"].set(bconn); - res["topologies/boundary/elements/sizes"].set(bsizes); + + tmp.set_external(bconn.data(), bconn.size()); + tmp.to_data_type(indexDT.id(), res["topologies/boundary/elements/connectivity"]); + + tmp.set_external(bsizes.data(), bsizes.size()); + tmp.to_data_type(indexDT.id(), res["topologies/boundary/elements/sizes"]); res["fields/boundary_type/topology"] = "boundary"; res["fields/boundary_type/association"] = "element"; From 50358cb2aa790aef8a8234a0db55ecd1cceadeb7 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 Sep 2023 11:58:42 -0700 Subject: [PATCH 20/50] Added coordset to_expicit function. --- src/libs/blueprint/conduit_blueprint_mesh.cpp | 20 ++++++++++++++++--- src/libs/blueprint/conduit_blueprint_mesh.hpp | 10 ++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh.cpp b/src/libs/blueprint/conduit_blueprint_mesh.cpp index fd457216e..c52a4518e 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh.cpp @@ -3966,8 +3966,22 @@ mesh::coordset::generate_strip(const Node& coordset, } } +//----------------------------------------------------------------------------- +void +mesh::coordset::to_explicit(const conduit::Node& coordset, + conduit::Node& coordset_dest) +{ + std::string type = coordset.fetch_existing("type").as_string(); -//------------------------------------------------------------------------- + if(type == "uniform") + mesh::coordset::uniform::to_explicit(coordset, coordset_dest); + else if(type == "rectilinear") + mesh::coordset::rectilinear::to_explicit(coordset, coordset_dest); + else if(type == "explicit") + coordset_dest.set_external(coordset); +} + +//----------------------------------------------------------------------------- void mesh::coordset::uniform::to_rectilinear(const conduit::Node &coordset, conduit::Node &dest) @@ -3976,7 +3990,7 @@ mesh::coordset::uniform::to_rectilinear(const conduit::Node &coordset, } -//------------------------------------------------------------------------- +//----------------------------------------------------------------------------- void mesh::coordset::uniform::to_explicit(const conduit::Node &coordset, conduit::Node &dest) @@ -3985,7 +3999,7 @@ mesh::coordset::uniform::to_explicit(const conduit::Node &coordset, } -//------------------------------------------------------------------------- +//----------------------------------------------------------------------------- void mesh::coordset::rectilinear::to_explicit(const conduit::Node &coordset, conduit::Node &dest) diff --git a/src/libs/blueprint/conduit_blueprint_mesh.hpp b/src/libs/blueprint/conduit_blueprint_mesh.hpp index c5c2ca5cf..c338d22ed 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh.hpp @@ -355,6 +355,16 @@ namespace coordset void CONDUIT_BLUEPRINT_API generate_strip(const conduit::Node& coordset, conduit::Node& coordset_dest); + //------------------------------------------------------------------------- + /** + @brief Convert the coordset, no matter its type, to explicit. + + @param coordset The input coordset. + @param[out] coordset_dest The output coordset. + */ + void CONDUIT_BLUEPRINT_API to_explicit(const conduit::Node& coordset, + conduit::Node& coordset_dest); + //------------------------------------------------------------------------- // blueprint::mesh::coordset::uniform protocol interface //------------------------------------------------------------------------- From a146c4ecedff365987f6e2986c3337467191b401 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 Sep 2023 11:59:53 -0700 Subject: [PATCH 21/50] Added a fast path in spatial_ordering. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 1 - .../conduit_blueprint_mesh_utils.cpp | 116 ++++++++++++++---- .../conduit_blueprint_mesh_utils.hpp | 13 ++ 3 files changed, 106 insertions(+), 24 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index a862832b6..b3b7a617c 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -18,7 +18,6 @@ #include "conduit.hpp" #include "conduit_blueprint.hpp" #include "conduit_blueprint_exports.h" -#include "conduit_blueprint_mesh_kdtree.hpp" #include "conduit_blueprint_mesh_utils.hpp" #include diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index d17983459..63a8a9640 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -1369,6 +1369,32 @@ coordset::extents(const Node &n) return cset_extents; } +//----------------------------------------------------------------------------- +std::tuple +coordset::supports_pointer_access(const conduit::Node &coordset) +{ + bool suitable = false; + conduit::DataType dt; + if(coordset.has_child("values")) + { + suitable = true; + const conduit::Node &values = coordset.fetch_existing("values"); + for(conduit::index_t i = 0; i < values.number_of_children(); i++) + { + if(i == 0) + { + suitable &= values[i].dtype().is_compact(); + dt = values[i].dtype(); + } + else + { + suitable &= (values[i].dtype().is_compact() && dt.id() == values[i].dtype().id()); + } + } + } + return std::make_tuple(suitable, dt); +} + //----------------------------------------------------------------------------- // -- begin conduit::blueprint::mesh::utils::coordset::uniform -- //----------------------------------------------------------------------------- @@ -1598,40 +1624,89 @@ topology::reindex_coords(const Node& topo, out_topo["coordset"] = new_coordset.name(); } +//----------------------------------------------------------------------------- +template +static std::vector +spatial_ordering_impl(std::vector &coords, ElementType /*notused*/, conduit::index_t npts) +{ + // Sort the coordinates spatially + std::vector reorder; + if(coords.size() == 2) + { + conduit::blueprint::mesh::utils::kdtree spatial_sort; + spatial_sort.initialize(&coords[0], npts); + reorder = std::move(spatial_sort.getIndices()); + } + else if(coords.size() == 3) + { + conduit::blueprint::mesh::utils::kdtree spatial_sort; + spatial_sort.initialize(&coords[0], npts); + reorder = std::move(spatial_sort.getIndices()); + } + return reorder; +} + //----------------------------------------------------------------------------- std::vector topology::spatial_ordering(const conduit::Node &topo) { // Make a new centroid topo and coordset. The coordset will contain the - // element centers. + // element centers. This ought to be an explicit coordset. Node topo_dest, coords_dest, s2dmap, d2smap; mesh::topology::unstructured::generate_centroids(topo, topo_dest, coords_dest, s2dmap, d2smap); - // Bundle the coordset components into a vector. - std::vector coords; - const conduit::Node &values = coords_dest.fetch_existing("values"); - for(conduit::index_t i = 0; i < values.number_of_children(); i++) - { - coords.push_back(values[i].as_double_accessor()); - } - // Sort the coordinates spatially std::vector reorder; - if(coords.size() == 2) + conduit::Node &values = coords_dest.fetch_existing("values"); + const auto pa = coordset::supports_pointer_access(coords_dest); + const auto suitable = std::get<0>(pa); + conduit::index_t npts{}; + if(suitable) { - conduit::blueprint::mesh::utils::kdtree spatial_sort; - spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); - reorder = std::move(spatial_sort.getIndices()); + // The coordinates will be accessed using pointers. + const auto &dt = std::get<1>(pa); + +std::cout << "spatial_ordering: dt=" << dt.name() << std::endl; + if(dt.is_double()) + { + std::vector coords; + double elem{}; + for(conduit::index_t i = 0; i < values.number_of_children(); i++) + { + npts = (i == 0) ? (values[i].as_double_array().number_of_elements()) : npts; + coords.push_back(values[i].as_double_ptr()); + } + reorder = spatial_ordering_impl(coords, elem, npts); + } + else if(dt.is_float()) + { + std::vector coords; + float elem{}; + for(conduit::index_t i = 0; i < values.number_of_children(); i++) + { + npts = (i == 0) ? (values[i].as_float_array().number_of_elements()) : npts; + coords.push_back(values[i].as_float_ptr()); + } + reorder = spatial_ordering_impl(coords, elem, npts); + } } - else if(coords.size() == 3) + if(reorder.empty()) { - conduit::blueprint::mesh::utils::kdtree spatial_sort; - spatial_sort.initialize(&coords[0], coords[0].number_of_elements()); - reorder = std::move(spatial_sort.getIndices()); +std::cout << "spatial_ordering: using double accessor" << std::endl; + // Use a double accessor to access coordinates. + std::vector coords; + double elem{}; + for(conduit::index_t i = 0; i < values.number_of_children(); i++) + { + npts = (i == 0) ? (values[i].as_double_accessor().number_of_elements()) : npts; + coords.push_back(values[i].as_double_accessor()); + } + reorder = spatial_ordering_impl(coords, elem, npts); } + return reorder; } @@ -2270,12 +2345,7 @@ topology::unstructured::reorder(const conduit::Node &topo, // Reorder the coordset now, making it explicit if needed. dest_coordset["type"] = "explicit"; conduit::Node coordset_explicit; - if(coordset["type"].as_string() == "rectilinear") - conduit::blueprint::mesh::coordset::rectilinear::to_explicit(coordset, coordset_explicit); - else if(coordset["type"].as_string() == "uniform") - conduit::blueprint::mesh::coordset::uniform::to_explicit(coordset, coordset_explicit); - else - coordset_explicit.set_external(coordset); + conduit::blueprint::mesh::coordset::to_explicit(coordset, coordset_explicit); conduit::blueprint::mesh::utils::slice_field(coordset_explicit["values"], ptReorder, dest_coordset["values"]); // Reorder fields that match this topo. diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp index 0f3d517d9..081ee845e 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.hpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -347,6 +348,18 @@ namespace coordset */ std::vector CONDUIT_BLUEPRINT_API extents(const Node &n); + //----------------------------------------------------------------------------- + /** + @brief Check whether all component types match and are compact, making it + suitable for pointer access. + + @param cset The coordset we're checking. + + @return A tuple containing 1) whether the components are suitable for pointer + access and 2) the data type. + */ + std::tuple CONDUIT_BLUEPRINT_API supports_pointer_access(const conduit::Node &coordset); + namespace uniform { /** From fbaf607e6c18fe331bca5003a540fde27ee3a506 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 Sep 2023 12:01:44 -0700 Subject: [PATCH 22/50] Removed stray prints. --- src/libs/blueprint/conduit_blueprint_mesh_utils.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index 63a8a9640..5b16224d2 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -1668,8 +1668,6 @@ topology::spatial_ordering(const conduit::Node &topo) { // The coordinates will be accessed using pointers. const auto &dt = std::get<1>(pa); - -std::cout << "spatial_ordering: dt=" << dt.name() << std::endl; if(dt.is_double()) { std::vector coords; @@ -1695,7 +1693,6 @@ std::cout << "spatial_ordering: dt=" << dt.name() << std::endl; } if(reorder.empty()) { -std::cout << "spatial_ordering: using double accessor" << std::endl; // Use a double accessor to access coordinates. std::vector coords; double elem{}; From 8cab63494bc6233083f3b8d040bd19911a0a6a9b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 Sep 2023 15:24:32 -0700 Subject: [PATCH 23/50] Allow extents to be provided and used to generate tiled mesh. --- .../generate_data/conduit_generate_data.cpp | 72 ++++----- .../conduit_blueprint_mesh_examples_tiled.cpp | 137 ++++++++++++++---- 2 files changed, 151 insertions(+), 58 deletions(-) diff --git a/src/executables/generate_data/conduit_generate_data.cpp b/src/executables/generate_data/conduit_generate_data.cpp index 56ece930f..61614bd45 100644 --- a/src/executables/generate_data/conduit_generate_data.cpp +++ b/src/executables/generate_data/conduit_generate_data.cpp @@ -8,6 +8,7 @@ /// //----------------------------------------------------------------------------- #include +#include #include #include #include @@ -44,6 +45,11 @@ class DomainGenerator for(int i = 0; i < 3; i++) domains[i] = d[i]; } + void setExtents(const double d[3]) + { + for(int i = 0; i < 6; i++) + extents[i] = d[i]; + } void setMeshType(const std::string &m) { meshType = m; @@ -51,6 +57,7 @@ class DomainGenerator protected: int dims[3]{0,0,0}; int domains[3]{1,1,1}; + double extents[6]{0., 1., 0., 1., 0., 1.}; std::string meshType{}; }; @@ -61,38 +68,19 @@ class TiledDomainGenerator : public DomainGenerator public: virtual void generate(int domain[3], conduit::Node &n, conduit::Node &opts) override { - constexpr double side = 20.; - - opts["origin/x"] = domain[0] * dims[0] * side; - opts["origin/y"] = domain[1] * dims[1] * side; - opts["origin/z"] = domain[2] * dims[2] * side; - - // Selectively create boundaries based on where the domain is the - // whole set of domains. - if(domains[0] * domains[1] * domains[2] == 1) - { - opts["boundaries/left"] = 1; - opts["boundaries/right"] = 1; - opts["boundaries/bottom"] = 1; - opts["boundaries/top"] = 1; - if(dims[0] > 0) - { - opts["boundaries/back"] = 1; - opts["boundaries/front"] = 1; - } - } - else - { - opts["boundaries/left"] = ((domain[0] == 0) ? 1 : 0); - opts["boundaries/right"] = ((domain[0] == domains[0]-1) ? 1 : 0); - opts["boundaries/bottom"] = ((domain[1] == 0) ? 1 : 0); - opts["boundaries/top"] = ((domain[1] == domains[1]-1) ? 1 : 0); - if(dims[0] > 0) - { - opts["boundaries/back"] = ((domain[2] == 0) ? 1 : 0); - opts["boundaries/front"] = ((domain[2] == domains[2]-1) ? 1 : 0); - } - } + // Determine the size and location of this domain in the whole. + double sideX = (extents[1] - extents[0]) / static_cast(domains[0]); + double sideY = (extents[3] - extents[2]) / static_cast(domains[1]); + double sideZ = (extents[5] - extents[4]) / static_cast(domains[2]); + double domainExt[] = {extents[0] + domain[0] * sideX, + extents[0] + (domain[0]+1) * sideX, + extents[2] + domain[1] * sideY, + extents[2] + (domain[1]+1) * sideY, + extents[4] + domain[2] * sideZ, + extents[4] + (domain[2]+1) * sideZ}; + opts["extents"].set(domainExt, 6); + opts["domain"].set(domain, 3); + opts["domains"].set(domains, 3); conduit::blueprint::mesh::examples::tiled(dims[0], dims[1], dims[2], n, opts); } @@ -117,7 +105,7 @@ printUsage(const char *exeName) { std::cout << "Usage: " << exeName << "[-dims x,y,z] [-domains x,y,z] [-tile]\n" << " [-braid] [-output fileroot] [-protocol name] [-meshtype type]\n" - << " [-tiledef filename] [-help]\n"; + << " [-tiledef filename] [-extents x0,x1,y0,y1[,z0,z1]] [-help]\n"; std::cout << "\n"; std::cout << "Argument Description\n"; std::cout << "=================== ==========================================================\n"; @@ -139,6 +127,9 @@ printUsage(const char *exeName) std::cout << "\n"; std::cout << "-tiledef filename A file containing a tile definition.\n"; std::cout << "\n"; + std::cout << "-extents ext A list of 4 or 6 comma-separated values indicating extents\n"; + std::cout << " as pairs of min,max values for each dimension.\n"; + std::cout << "\n"; std::cout << "-help Print the usage and exit.\n"; } @@ -157,6 +148,7 @@ main(int argc, char *argv[]) // Some basic arg parsing. int dims[3] = {10, 10, 10}; int domains[3] = {1, 1, 1}; + double extents[6] = {0., 1., 0., 1., 0., 1.}; conduit::Node n, opts; std::unique_ptr g = MAKE_UNIQUE(TiledDomainGenerator); std::string meshType("quad"), output("output"), protocol("hdf5"); @@ -213,6 +205,19 @@ main(int argc, char *argv[]) conduit::relay::io::load(argv[i+1], opts["tile"]); i++; } + else if(strcmp(argv[i], "-extents") == 0 && (i+1) < argc) + { + double e[6] = {0., 1., 0., 1., 0., 1.}; + if(sscanf(argv[i + 1], "%lg,%lg,%lg,%lg,%lg,%lg", &e[0], &e[1], &e[2], &e[3], &e[4], &e[5]) == 6) + { + memcpy(extents, e, 6 * sizeof(double)); + } + else if(sscanf(argv[i + 1], "%lg,%lg,%lg,%lg", &e[0], &e[1], &e[2], &e[3]) == 4) + { + memcpy(extents, e, 4 * sizeof(double)); + } + i++; + } else if(strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) { @@ -228,6 +233,7 @@ main(int argc, char *argv[]) g->setDims(dims); g->setDomains(domains); g->setMeshType(meshType); + g->setExtents(extents); int ndoms = domains[0] * domains[1] * domains[2]; if(ndoms == 1 && rank == 0) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index b3b7a617c..9d70de21f 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -144,7 +144,7 @@ class Tiler double height() const { return m_height; } /// Creates the points for the tile (if they need to be created). - void addPoints(double origin[2], + void addPoints(const double M[3][3], std::vector &ptids, std::vector &x, std::vector &y) @@ -156,8 +156,15 @@ class Tiler if(ptids[i] == Tile::INVALID_POINT) { ptids[i] = static_cast(x.size()); - x.push_back(origin[0] + m_xpts[i]); - y.push_back(origin[1] + m_ypts[i]); + + // (x,y,1) * M + double xc = m_xpts[i] * M[0][0] + m_ypts[i] * M[1][0] + M[2][0]; + double yc = m_xpts[i] * M[0][1] + m_ypts[i] * M[1][1] + M[2][1]; + double h = m_xpts[i] * M[0][2] + m_ypts[i] * M[1][2] + M[2][2]; + xc /= h; + yc /= h; + x.push_back(xc); + y.push_back(yc); } } } @@ -241,6 +248,12 @@ class Tiler return vec; } + /// Determine which boundaries are needed. + void boundaryFlags(const conduit::Node &options, + bool &left, bool &right, + bool &bottom, bool &top, + bool &back, bool &front) const; + /// Make 2D boundaries. template void makeBoundaries2D(const std::vector &tiles, @@ -378,18 +391,11 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, conduit::Node &res, const conduit::Node &options) { - double origin[] = {0., 0., 0.}; std::vector x, y, z; std::vector conn, sizes, bconn, bsizes; std::vector btype; // Process any options. - if(options.has_path("origin/x")) - origin[0] = options.fetch_existing("origin/x").to_double(); - if(options.has_path("origin/y")) - origin[1] = options.fetch_existing("origin/y").to_double(); - if(options.has_path("origin/z")) - origin[2] = options.fetch_existing("origin/z").to_double(); if(options.has_path("tile")) initialize(options.fetch_existing("tile")); @@ -407,12 +413,51 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, } } + // Make a transformation matrix for the tile points. + double origin[] = {0., 0., 0.}; + double tx = width(), ty = height(); + double z1 = std::max(width(), height()) * nz; + double M[3][3] = {{1., 0., 0.}, {0., 1., 0.}, {0., 0., 1.}}; + if(options.has_path("extents")) + { + auto extents = options.fetch_existing("extents").as_double_accessor(); + tx = (extents[1] - extents[0]) / nx; + ty = (extents[3] - extents[2]) / ny; + origin[0] = extents[0]; + origin[1] = extents[2]; + origin[2] = extents[4]; + z1 = extents[5]; + } + else + { + // There are no extents so figure out some based on the domains, if present. + if(options.has_path("domain") && options.has_path("domains")) + { + auto domain = options.fetch_existing("domain").as_int_accessor(); + auto domains = options.fetch_existing("domains").as_int_accessor(); + if(domain.number_of_elements() == 3 && + domain.number_of_elements() == domains.number_of_elements()) + { + origin[0] = domain[0] * nx * width(); + origin[1] = domain[1] * ny * height(); + origin[2] = domain[2] * z1; + z1 = origin[2] + z1; + } + } + } + // Scaling + M[0][0] = tx / width(); + M[1][1] = ty / height(); + // Translation + M[2][0] = origin[0]; + M[2][1] = origin[1]; + // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); double newOrigin[] = {origin[0], origin[1], origin[2]}; for(conduit::index_t j = 0; j < ny; j++) { - newOrigin[0] = origin[0]; + M[2][0] = origin[0]; for(conduit::index_t i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; @@ -432,10 +477,10 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, current.setPointIds(bottom(), prevY.getPointIds(top())); } - addPoints(newOrigin, current.getPointIds(), x, y); - newOrigin[0] += width(); + addPoints(M, current.getPointIds(), x, y); + M[2][0] += tx; } - newOrigin[1] += height(); + M[2][1] += ty; } conduit::index_t ptsPerPlane = 0; @@ -470,7 +515,8 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, z.push_back(origin[2]); for(conduit::index_t p = 1; p < nplanes; p++) { - double zvalue = origin[2] + static_cast(p) * std::max(width(), height()); + double t = static_cast(p) / static_cast(nplanes - 1); + double zvalue = (1. - t) * origin[2] + t * z1; for(conduit::index_t i = 0; i < ptsPerPlane; i++) { x.push_back(x[i]); @@ -662,6 +708,41 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, #endif } +//--------------------------------------------------------------------------- +void +Tiler::boundaryFlags(const conduit::Node &options, bool &_left, bool &_right, + bool &_bottom, bool &_top, bool &_back, bool &_front) const +{ + bool handled = false; + if(options.has_path("domain") && options.has_path("domains")) + { + auto domain = options.fetch_existing("domain").as_int_accessor(); + auto domains = options.fetch_existing("domains").as_int_accessor(); + if(domain.number_of_elements() == 3 && + domain.number_of_elements() == domains.number_of_elements()) + { + int ndoms = domains[0] * domains[1] * domains[2]; + if(ndoms > 1) + { + _left = (domain[0] == 0); + _right = (domain[0] == domains[0]-1); + _bottom = (domain[1] == 0); + _top = (domain[1] == domains[1]-1); + _back = (domain[2] == 0); + _front = (domain[2] == domains[2]-1); + + handled = true; + } + } + } + if(!handled) + { + _left = _right = true; + _bottom = _top = true; + _back = _front = true; + } +} + //--------------------------------------------------------------------------- template void @@ -674,7 +755,10 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const conduit::Node &options, Transform &&transform) const { - if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) + bool _left, _right, _bottom, _top, _back, _front; + boundaryFlags(options, _left, _right, _bottom, _top, _back, _front); + + if(_left) { for(conduit::index_t i = 0, j = ny-1; j >= 0; j--) { @@ -689,7 +773,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, } } } - if(options.has_path("boundaries/bottom") && options.fetch_existing("boundaries/bottom").to_int() > 0) + if(_bottom) { for(conduit::index_t i = 0, j = 0; i < nx; i++) { @@ -704,7 +788,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, } } } - if(options.has_path("boundaries/right") && options.fetch_existing("boundaries/right").to_int() > 0) + if(_right) { for(conduit::index_t i = nx - 1, j = 0; j < ny; j++) { @@ -719,7 +803,7 @@ Tiler::makeBoundaries2D(const std::vector &tiles, } } } - if(options.has_path("boundaries/top") && options.fetch_existing("boundaries/top").to_int() > 0) + if(_top) { for(conduit::index_t i = nx - 1, j = ny - 1; i >= 0; i--) { @@ -750,7 +834,10 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const conduit::Node &options, Transform &&transform) const { - if(options.has_path("boundaries/left") && options.fetch_existing("boundaries/left").to_int() > 0) + bool _left, _right, _bottom, _top, _back, _front; + boundaryFlags(options, _left, _right, _bottom, _top, _back, _front); + + if(_left) { for(conduit::index_t k = 0; k < nz; k++) { @@ -772,7 +859,7 @@ Tiler::makeBoundaries3D(const std::vector &tiles, } } } - if(options.has_path("boundaries/right") && options.fetch_existing("boundaries/right").to_int() > 0) + if(_right) { for(conduit::index_t k = 0; k < nz; k++) { @@ -794,7 +881,7 @@ Tiler::makeBoundaries3D(const std::vector &tiles, } } } - if(options.has_path("boundaries/bottom") && options.fetch_existing("boundaries/bottom").to_int() > 0) + if(_bottom) { for(conduit::index_t k = 0; k < nz; k++) { @@ -816,7 +903,7 @@ Tiler::makeBoundaries3D(const std::vector &tiles, } } } - if(options.has_path("boundaries/top") && options.fetch_existing("boundaries/top").to_int() > 0) + if(_top) { for(conduit::index_t k = 0; k < nz; k++) { @@ -838,7 +925,7 @@ Tiler::makeBoundaries3D(const std::vector &tiles, } } } - if(options.has_path("boundaries/back") && options.fetch_existing("boundaries/back").to_int() > 0) + if(_back) { for(conduit::index_t j = 0; j < ny; j++) for(conduit::index_t i = nx - 1; i >= 0; i--) @@ -850,7 +937,7 @@ Tiler::makeBoundaries3D(const std::vector &tiles, btype.push_back(4); } } - if(options.has_path("boundaries/front") && options.fetch_existing("boundaries/front").to_int() > 0) + if(_front) { for(conduit::index_t j = 0; j < ny; j++) for(conduit::index_t i = 0; i < nx; i++) From bb4416050d5392210bb8e0cdd7d1df85ade73f36 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 Sep 2023 17:30:25 -0700 Subject: [PATCH 24/50] Changed boundary routines so they are based on iteration that calls a lambda. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 365 +++++++++++------- 1 file changed, 230 insertions(+), 135 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 9d70de21f..fb77abde0 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -112,6 +112,13 @@ const conduit::index_t Tile::INVALID_POINT = -1; class Tiler { public: + static constexpr int BoundaryLeft = 0; + static constexpr int BoundaryRight = 1; + static constexpr int BoundaryBottom = 2; + static constexpr int BoundaryTop = 3; + static constexpr int BoundaryBack = 4; + static constexpr int BoundaryFront = 5; + Tiler(); /// Generate the tiled mesh. @@ -169,24 +176,21 @@ class Tiler } } - /// Emit the quad cells using this tile's point ids. - template - void addFaces(const std::vector &ptids, - std::vector &conn, - std::vector &sizes, - conduit::index_t offset, - bool reverse, - Transform &&transform) const + /// Iterate over the tile's quad cells and apply a lambda. + template + void iterateFaces(const std::vector &ptids, conduit::index_t offset, + bool reverse, int stype, Body &&body) const { const size_t nquads = m_quads.size() / 4; int order[] = {reverse ? 3 : 0, reverse ? 2 : 1, reverse ? 1 : 2, reverse ? 0 : 3}; for(size_t i = 0; i < nquads; i++) { - conn.push_back(transform(offset + ptids[m_quads[4*i + order[0]]])); - conn.push_back(transform(offset + ptids[m_quads[4*i + order[1]]])); - conn.push_back(transform(offset + ptids[m_quads[4*i + order[2]]])); - conn.push_back(transform(offset + ptids[m_quads[4*i + order[3]]])); - sizes.push_back(4); + conduit::index_t idlist[4]; + idlist[0] = offset + ptids[m_quads[4*i + order[0]]]; + idlist[1] = offset + ptids[m_quads[4*i + order[1]]]; + idlist[2] = offset + ptids[m_quads[4*i + order[2]]]; + idlist[3] = offset + ptids[m_quads[4*i + order[3]]]; + body(idlist, 4, stype); } } @@ -249,11 +253,8 @@ class Tiler } /// Determine which boundaries are needed. - void boundaryFlags(const conduit::Node &options, - bool &left, bool &right, - bool &bottom, bool &top, - bool &back, bool &front) const; - + void boundaryFlags(const conduit::Node &options, bool flags[6]) const; +#if 0 /// Make 2D boundaries. template void makeBoundaries2D(const std::vector &tiles, @@ -264,19 +265,24 @@ class Tiler std::vector &btype, const conduit::Node &options, Transform &&transform) const; - - /// Make 3D boundaries. - template - void makeBoundaries3D(const std::vector &tiles, - conduit::index_t nx, - conduit::index_t ny, - conduit::index_t nz, - conduit::index_t nPtsPerPlane, - std::vector &bconn, - std::vector &bsizes, - std::vector &btype, - const conduit::Node &options, - Transform &&transform) const; +#endif + // Iterate over 2D boundaries + template + void iterateBoundary2D(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + const bool flags[6], + Body &&body) const; + + /// Iterate over 3D boundaries. + template + void iterateBoundary3D(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + conduit::index_t nz, + conduit::index_t nPtsPerPlane, + const bool flags[6], + Body &&body) const; private: std::vector m_xpts, m_ypts; double m_width, m_height; @@ -493,9 +499,12 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; - addFaces(current.getPointIds(), conn, sizes, 0, false, - [](conduit::index_t id) { - return id; + iterateFaces(current.getPointIds(), 0, false, BoundaryBack, + [&](const conduit::index_t *ids, conduit::index_t npts, int) + { + for(conduit::index_t pi = 0; pi < npts; pi++) + conn.push_back(ids[pi]); + sizes.push_back(npts); }); } } @@ -642,23 +651,32 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, // Boundaries std::string bshape; + bool flags[6]; + boundaryFlags(options, flags); if(nz < 1) { // 2D bshape = "line"; if(reorder) { - makeBoundaries2D(tiles, nx, ny, bconn, bsizes, btype, options, - [&](conduit::index_t id) { - // Renumber the boundary connectivity - return old2NewPoint[id]; + iterateBoundary2D(tiles, nx, ny, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + bconn.push_back(old2NewPoint[ids[i]]); // Renumber + bsizes.push_back(npts); + btype.push_back(bnd); }); } else { - makeBoundaries2D(tiles, nx, ny, bconn, bsizes, btype, options, - [](conduit::index_t id) { - return id; + iterateBoundary2D(tiles, nx, ny, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + bconn.push_back(ids[i]); + bsizes.push_back(npts); + btype.push_back(bnd); }); } } @@ -668,17 +686,24 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, bshape = "quad"; if(reorder) { - makeBoundaries3D(tiles, nx, ny, nz, ptsPerPlane, bconn, bsizes, btype, options, - [&](conduit::index_t id) { - // Renumber the boundary connectivity - return old2NewPoint[id]; + iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + bconn.push_back(old2NewPoint[ids[i]]); // Renumber + bsizes.push_back(npts); + btype.push_back(bnd); }); } else { - makeBoundaries3D(tiles, nx, ny, nz, ptsPerPlane, bconn, bsizes, btype, options, - [&](conduit::index_t id) { - return id; + iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + bconn.push_back(ids[i]); + bsizes.push_back(npts); + btype.push_back(bnd); }); } } @@ -710,8 +735,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, //--------------------------------------------------------------------------- void -Tiler::boundaryFlags(const conduit::Node &options, bool &_left, bool &_right, - bool &_bottom, bool &_top, bool &_back, bool &_front) const +Tiler::boundaryFlags(const conduit::Node &options, bool flags[6]) const { bool handled = false; if(options.has_path("domain") && options.has_path("domains")) @@ -724,12 +748,12 @@ Tiler::boundaryFlags(const conduit::Node &options, bool &_left, bool &_right, int ndoms = domains[0] * domains[1] * domains[2]; if(ndoms > 1) { - _left = (domain[0] == 0); - _right = (domain[0] == domains[0]-1); - _bottom = (domain[1] == 0); - _top = (domain[1] == domains[1]-1); - _back = (domain[2] == 0); - _front = (domain[2] == domains[2]-1); + flags[BoundaryLeft] = (domain[0] == 0); + flags[BoundaryRight] = (domain[0] == domains[0]-1); + flags[BoundaryBottom] = (domain[1] == 0); + flags[BoundaryTop] = (domain[1] == domains[1]-1); + flags[BoundaryBack] = (domain[2] == 0); + flags[BoundaryFront] = (domain[2] == domains[2]-1); handled = true; } @@ -737,28 +761,23 @@ Tiler::boundaryFlags(const conduit::Node &options, bool &_left, bool &_right, } if(!handled) { - _left = _right = true; - _bottom = _top = true; - _back = _front = true; + for(int i = 0; i < 6; i++) + flags[i] = true; } } //--------------------------------------------------------------------------- -template +template void -Tiler::makeBoundaries2D(const std::vector &tiles, +Tiler::iterateBoundary2D(const std::vector &tiles, conduit::index_t nx, conduit::index_t ny, - std::vector &bconn, - std::vector &bsizes, - std::vector &btype, - const conduit::Node &options, - Transform &&transform) const + const bool flags[6], + Body &&body) const { - bool _left, _right, _bottom, _top, _back, _front; - boundaryFlags(options, _left, _right, _bottom, _top, _back, _front); + conduit::index_t idlist[2]; - if(_left) + if(flags[BoundaryLeft]) { for(conduit::index_t i = 0, j = ny-1; j >= 0; j--) { @@ -766,14 +785,13 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(left()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(transform(ids[bi])); - bconn.push_back(transform(ids[bi - 1])); - bsizes.push_back(2); - btype.push_back(0); + idlist[0] = ids[bi]; + idlist[1] = ids[bi - 1]; + body(idlist, 2, BoundaryLeft); } } } - if(_bottom) + if(flags[BoundaryBottom]) { for(conduit::index_t i = 0, j = 0; i < nx; i++) { @@ -781,14 +799,13 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(bottom()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(transform(ids[bi])); - bconn.push_back(transform(ids[bi + 1])); - bsizes.push_back(2); - btype.push_back(2); + idlist[0] = ids[bi]; + idlist[1] = ids[bi + 1]; + body(idlist, 2, BoundaryBottom); } } } - if(_right) + if(flags[BoundaryRight]) { for(conduit::index_t i = nx - 1, j = 0; j < ny; j++) { @@ -796,14 +813,13 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(right()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(transform(ids[bi])); - bconn.push_back(transform(ids[bi + 1])); - bsizes.push_back(2); - btype.push_back(1); + idlist[0] = ids[bi]; + idlist[1] = ids[bi + 1]; + body(idlist, 2, BoundaryRight); } } } - if(_top) + if(flags[BoundaryTop]) { for(conduit::index_t i = nx - 1, j = ny - 1; i >= 0; i--) { @@ -811,33 +827,28 @@ Tiler::makeBoundaries2D(const std::vector &tiles, const auto ids = current.getPointIds(top()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(transform(ids[bi])); - bconn.push_back(transform(ids[bi - 1])); - bsizes.push_back(2); - btype.push_back(3); + idlist[0] = ids[bi]; + idlist[1] = ids[bi - 1]; + body(idlist, 2, BoundaryTop); } } } } //--------------------------------------------------------------------------- -template +template void -Tiler::makeBoundaries3D(const std::vector &tiles, +Tiler::iterateBoundary3D(const std::vector &tiles, conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, conduit::index_t nPtsPerPlane, - std::vector &bconn, - std::vector &bsizes, - std::vector &btype, - const conduit::Node &options, - Transform &&transform) const + const bool flags[6], + Body &&body) const { - bool _left, _right, _bottom, _top, _back, _front; - boundaryFlags(options, _left, _right, _bottom, _top, _back, _front); + conduit::index_t idlist[4]; - if(_left) + if(flags[BoundaryLeft]) { for(conduit::index_t k = 0; k < nz; k++) { @@ -849,17 +860,16 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(left()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(transform(offset1 + ids[bi])); - bconn.push_back(transform(offset1 + ids[bi - 1])); - bconn.push_back(transform(offset2 + ids[bi - 1])); - bconn.push_back(transform(offset2 + ids[bi])); - bsizes.push_back(4); - btype.push_back(0); + idlist[0] = offset1 + ids[bi]; + idlist[1] = offset1 + ids[bi - 1]; + idlist[2] = offset2 + ids[bi - 1]; + idlist[3] = offset2 + ids[bi]; + body(idlist, 4, BoundaryLeft); } } } } - if(_right) + if(flags[BoundaryRight]) { for(conduit::index_t k = 0; k < nz; k++) { @@ -871,17 +881,16 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(right()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(transform(offset1 + ids[bi])); - bconn.push_back(transform(offset1 + ids[bi + 1])); - bconn.push_back(transform(offset2 + ids[bi + 1])); - bconn.push_back(transform(offset2 + ids[bi])); - bsizes.push_back(4); - btype.push_back(1); + idlist[0] = offset1 + ids[bi]; + idlist[1] = offset1 + ids[bi + 1]; + idlist[2] = offset2 + ids[bi + 1]; + idlist[3] = offset2 + ids[bi]; + body(idlist, 4, BoundaryRight); } } } } - if(_bottom) + if(flags[BoundaryBottom]) { for(conduit::index_t k = 0; k < nz; k++) { @@ -893,17 +902,16 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(bottom()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { - bconn.push_back(transform(offset1 + ids[bi])); - bconn.push_back(transform(offset1 + ids[bi + 1])); - bconn.push_back(transform(offset2 + ids[bi + 1])); - bconn.push_back(transform(offset2 + ids[bi])); - bsizes.push_back(4); - btype.push_back(2); + idlist[0] = offset1 + ids[bi]; + idlist[1] = offset1 + ids[bi + 1]; + idlist[2] = offset2 + ids[bi + 1]; + idlist[3] = offset2 + ids[bi]; + body(idlist, 4, BoundaryBottom); } } } } - if(_top) + if(flags[BoundaryTop]) { for(conduit::index_t k = 0; k < nz; k++) { @@ -915,42 +923,129 @@ Tiler::makeBoundaries3D(const std::vector &tiles, const auto ids = current.getPointIds(top()); for(size_t bi = ids.size() - 1; bi > 0; bi--) { - bconn.push_back(transform(offset1 + ids[bi])); - bconn.push_back(transform(offset1 + ids[bi - 1])); - bconn.push_back(transform(offset2 + ids[bi - 1])); - bconn.push_back(transform(offset2 + ids[bi])); - bsizes.push_back(4); - btype.push_back(3); + idlist[0] = offset1 + ids[bi]; + idlist[1] = offset1 + ids[bi - 1]; + idlist[2] = offset2 + ids[bi - 1]; + idlist[3] = offset2 + ids[bi]; + body(idlist, 4, BoundaryTop); } } } } - if(_back) + if(flags[BoundaryBack]) { for(conduit::index_t j = 0; j < ny; j++) for(conduit::index_t i = nx - 1; i >= 0; i--) { const Tile ¤t = tiles[(j*nx + i)]; - size_t s0 = bsizes.size(); - addFaces(current.getPointIds(), bconn, bsizes, 0, true, transform); - for( ; s0 < bsizes.size(); s0++) - btype.push_back(4); + iterateFaces(current.getPointIds(), 0, true, BoundaryBack, body); } } - if(_front) + if(flags[BoundaryFront]) { for(conduit::index_t j = 0; j < ny; j++) for(conduit::index_t i = 0; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; - size_t s0 = bsizes.size(); - addFaces(current.getPointIds(), bconn, bsizes, nz * nPtsPerPlane, false, transform); - for( ; s0 < bsizes.size(); s0++) - btype.push_back(5); + iterateFaces(current.getPointIds(), nz * nPtsPerPlane, false, BoundaryFront, body); } } } +#if 0 +void +Tiler::boundary_to_adjset(const std::vector &bconn, + const std::vector &bsize, + const std::vector &btype, + int selected_type, + conduit::Node &out) const +{ + // Skim through the boundary data and get the unique point ids for + // selected boundary type. + std::set unique; + conduit::index_t offset = 0; + for(size_t i = 0; i < btype.size(); i++) + { + if(btype[i] == selected_type) + { + for(conduit::index_t b = 0; b < bsize[i]; b++) + unique.insert(bconn[offset + b]); + } + offset += bsize[i]; + } + + // Store the results into a node. + out.set(conduit::DataType::index_t(unique.size())); + conduit::index_t *out_ptr = out.as_index_t_ptr(); + for(const auto &ptid : unique) + *out_ptr++ = ptid; +} + +void +Tiler::add_adjset(const std::vector &bconn, + const std::vector &bsize, + const std::vector &btype, + const conduit::Node &options, + conduit::Node &out) const +{ + // Make the adjset name for 2 domains. + auto adjset_name = [](conduit::index_t d0, conduit::index_t d1) { + if(d0 > d1) + std::swap(d0, d1); + std::stringstream ss; + ss << "domain_" << d0 << "_" << d1; + return ss.str(); + }; + + if(options.has_child("domain") && options.has_child("domains")) + { + auto domain = options.fetch_existing("domain").as_index_t_accessor(); + auto domains = options.fetch_existing("domains").as_index_t_accessor(); + if(domain.number_of_elements() == 3 && + domain.number_of_elements() == domains.number_of_elements()) + { + auto dnxny = domains[0] * domains[1]; + auto dnx = domains[0]; + auto thisDom = domain[2] * dnxny + domains[1] * dnx + domain[0]; + + conduit::Node &adjset = out["adjsets/adjset"]; + adjset["association"] = "vertex"; + adjset["topology"] = "mesh"; + conduit::Node &groups = adjset["groups"]; + +// I can't just scan through the boundary connectivity because we probably did +// not make boundary info for all edges/faces of the domain. +adjset: + association: vertex + topology: topology + groups: + domain_0_1: + neighbors: [1] + values: [v00, v01] + + // Left side. + if(domain[0] - 1 >= 0) + { + auto neighborDom = domain[2] * dnxny + domains[1] * dnx + (domain[0] - 1); + auto name = adjset_name(thisDom, neighborDom); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborDom; + boundary_to_adjset(bconn, bsize, btype, BoundaryLeft, group["values"]); + } + // Right side. + if(domain[0] + 1 < domains[0]) + { + auto neighborDom = domain[2] * dnxny + domains[1] * dnx + (domain[0] + 1); + auto name = adjset_name(thisDom, neighborDom); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborDom; + boundary_to_adjset(bconn, bsize, btype, BoundaryRight, group["values"]); + } + } + } +} +#endif + } //----------------------------------------------------------------------------- // -- end conduit::blueprint::mesh::examples::detail -- From cedd0021008ba5a19907d7caab2b59878f8285fe Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 7 Sep 2023 18:25:31 -0700 Subject: [PATCH 25/50] Added adjset generation. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 201 +++++++++++------- 1 file changed, 119 insertions(+), 82 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index fb77abde0..61a0f8e9a 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -254,18 +254,7 @@ class Tiler /// Determine which boundaries are needed. void boundaryFlags(const conduit::Node &options, bool flags[6]) const; -#if 0 - /// Make 2D boundaries. - template - void makeBoundaries2D(const std::vector &tiles, - conduit::index_t nx, - conduit::index_t ny, - std::vector &bconn, - std::vector &bsizes, - std::vector &btype, - const conduit::Node &options, - Transform &&transform) const; -#endif + // Iterate over 2D boundaries template void iterateBoundary2D(const std::vector &tiles, @@ -283,6 +272,17 @@ class Tiler conduit::index_t nPtsPerPlane, const bool flags[6], Body &&body) const; + + /// Add adjacency set + void addAdjset(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + conduit::index_t nz, + conduit::index_t ptsPerPlane, + bool reorder, + const std::vector &old2NewPoint, + const conduit::Node &options, + conduit::Node &out) const; private: std::vector m_xpts, m_ypts; double m_width, m_height; @@ -724,6 +724,9 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, res["fields/boundary_type/values"].set(btype); } + // Build an adjacency set. + addAdjset(tiles, nx, ny, nz, ptsPerPlane, reorder, old2NewPoint, options, res); + #if 0 // Print for debugging. conduit::Node opts; @@ -952,41 +955,17 @@ Tiler::iterateBoundary3D(const std::vector &tiles, } } -#if 0 -void -Tiler::boundary_to_adjset(const std::vector &bconn, - const std::vector &bsize, - const std::vector &btype, - int selected_type, - conduit::Node &out) const -{ - // Skim through the boundary data and get the unique point ids for - // selected boundary type. - std::set unique; - conduit::index_t offset = 0; - for(size_t i = 0; i < btype.size(); i++) - { - if(btype[i] == selected_type) - { - for(conduit::index_t b = 0; b < bsize[i]; b++) - unique.insert(bconn[offset + b]); - } - offset += bsize[i]; - } - - // Store the results into a node. - out.set(conduit::DataType::index_t(unique.size())); - conduit::index_t *out_ptr = out.as_index_t_ptr(); - for(const auto &ptid : unique) - *out_ptr++ = ptid; -} - +//--------------------------------------------------------------------------- void -Tiler::add_adjset(const std::vector &bconn, - const std::vector &bsize, - const std::vector &btype, - const conduit::Node &options, - conduit::Node &out) const +Tiler::addAdjset(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + conduit::index_t nz, + conduit::index_t ptsPerPlane, + bool reorder, + const std::vector &old2NewPoint, + const conduit::Node &options, + conduit::Node &out) const { // Make the adjset name for 2 domains. auto adjset_name = [](conduit::index_t d0, conduit::index_t d1) { @@ -997,6 +976,7 @@ Tiler::add_adjset(const std::vector &bconn, return ss.str(); }; + // We need to know where this domain is in the domains to make the adjset. if(options.has_child("domain") && options.has_child("domains")) { auto domain = options.fetch_existing("domain").as_index_t_accessor(); @@ -1004,47 +984,104 @@ Tiler::add_adjset(const std::vector &bconn, if(domain.number_of_elements() == 3 && domain.number_of_elements() == domains.number_of_elements()) { - auto dnxny = domains[0] * domains[1]; - auto dnx = domains[0]; - auto thisDom = domain[2] * dnxny + domains[1] * dnx + domain[0]; - - conduit::Node &adjset = out["adjsets/adjset"]; - adjset["association"] = "vertex"; - adjset["topology"] = "mesh"; - conduit::Node &groups = adjset["groups"]; - -// I can't just scan through the boundary connectivity because we probably did -// not make boundary info for all edges/faces of the domain. -adjset: - association: vertex - topology: topology - groups: - domain_0_1: - neighbors: [1] - values: [v00, v01] - - // Left side. - if(domain[0] - 1 >= 0) - { - auto neighborDom = domain[2] * dnxny + domains[1] * dnx + (domain[0] - 1); - auto name = adjset_name(thisDom, neighborDom); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborDom; - boundary_to_adjset(bconn, bsize, btype, BoundaryLeft, group["values"]); - } - // Right side. - if(domain[0] + 1 < domains[0]) + if(domains[0] * domains[1] * domains[2] > 1) { - auto neighborDom = domain[2] * dnxny + domains[1] * dnx + (domain[0] + 1); - auto name = adjset_name(thisDom, neighborDom); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborDom; - boundary_to_adjset(bconn, bsize, btype, BoundaryRight, group["values"]); + auto dnxny = domains[0] * domains[1]; + auto dnx = domains[0]; +#define DOMAIN_INDEX(I,J,K) ((domain[2] + (K)) * dnxny + (domain[1] + (J)) * dnx + (domain[0] + (I))) + auto thisDom = DOMAIN_INDEX(0, 0, 0); + + conduit::Node &adjset = out["adjsets/adjset"]; + adjset["association"] = "vertex"; + adjset["topology"] = "mesh"; + conduit::Node &groups = adjset["groups"]; + + // Neighbor domain indices. + conduit::index_t neighbor[6]; + neighbor[BoundaryLeft] = (domain[0] - 1 >= 0) ? DOMAIN_INDEX(-1, 0, 0) : -1; + neighbor[BoundaryRight] = (domain[0] + 1 < domains[0]) ? DOMAIN_INDEX(1, 0, 0) : -1; + neighbor[BoundaryBottom] = (domain[1] - 1 >= 0) ? DOMAIN_INDEX(0, -1, 0) : -1; + neighbor[BoundaryTop] = (domain[1] + 1 < domains[1]) ? DOMAIN_INDEX(0, 1, 0) : -1; + neighbor[BoundaryBack] = (domain[2] - 1 >= 0) ? DOMAIN_INDEX(0, 0, -1) : -1; + neighbor[BoundaryFront] = (domain[2] + 1 < domains[2]) ? DOMAIN_INDEX(0, 0, 1) : -1; +#undef DOMAIN_INDEX + + // Make a state node. + out["state/domain_id"] = thisDom; + + int maxfaces = (nz < 1) ? 4 : 6; + for(int di = 0; di < maxfaces; di++) + { + // If this domain has no neighbor in the current direction, skip. + if(neighbor[di] == -1) + continue; + + // Iterate over faces and come up with unique points. + bool flags[6] = {false, false, false, false, false, false}; + flags[di] = true; + std::set unique; + if(nz < 1) + { + // 2D + if(reorder) + { + iterateBoundary2D(tiles, nx, ny, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + unique.insert(old2NewPoint[ids[i]]); // Renumber + }); + } + else + { + iterateBoundary2D(tiles, nx, ny, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + unique.insert(ids[i]); + }); + } + } + else + { + if(reorder) + { + iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + unique.insert(old2NewPoint[ids[i]]); // Renumber + }); + } + else + { + iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + { + for(conduit::index_t i = 0; i < npts; i++) + unique.insert(ids[i]); + }); + } + } + + if(!unique.empty()) + { + auto name = adjset_name(thisDom, neighbor[di]); + conduit::Node &group = groups[name]; + group["neighbors"] = neighbor[di]; + + // Store the results into a node. + conduit::Node &values = group["values"]; + values.set(conduit::DataType::index_t(unique.size())); + conduit::index_t *out_ptr = values.as_index_t_ptr(); + for(const auto &ptid : unique) + *out_ptr++ = ptid; + } + } // end for } } } } -#endif } //----------------------------------------------------------------------------- From 0ecbb61bcfa09cf2263a3c8a6e2c944b76a344be Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 8 Sep 2023 11:28:23 -0700 Subject: [PATCH 26/50] Small braid fix --- .../generate_data/conduit_generate_data.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/executables/generate_data/conduit_generate_data.cpp b/src/executables/generate_data/conduit_generate_data.cpp index 61614bd45..284b77b54 100644 --- a/src/executables/generate_data/conduit_generate_data.cpp +++ b/src/executables/generate_data/conduit_generate_data.cpp @@ -93,7 +93,7 @@ class BraidDomainGenerator : public DomainGenerator public: virtual void generate(int /*domain*/[3], conduit::Node &n, conduit::Node &) override { - conduit::blueprint::mesh::examples::braid(meshType, dims[0], dims[1], dims[2], n); + conduit::blueprint::mesh::examples::braid(meshType, dims[0] + 1, dims[1] + 1, dims[2] + 1, n); // TODO: Use domain,domains to adjust coordinates to get them to line up nicely. } @@ -151,7 +151,7 @@ main(int argc, char *argv[]) double extents[6] = {0., 1., 0., 1., 0., 1.}; conduit::Node n, opts; std::unique_ptr g = MAKE_UNIQUE(TiledDomainGenerator); - std::string meshType("quad"), output("output"), protocol("hdf5"); + std::string meshType("hexs"),meshTypeDefault("hexs"), output("output"), protocol("hdf5"); for(int i = 1; i < argc; i++) { if(strcmp(argv[i], "-dims") == 0 && (i+1) < argc) @@ -162,6 +162,12 @@ main(int argc, char *argv[]) dims[0] = std::max(d[0], 1); dims[1] = std::max(d[1], 1); dims[2] = std::max(d[2], 0); // Allow 0 for 2D + + // If we have not set the mesh type, set it according to dimension. + if(meshType == meshTypeDefault) + { + meshType = (dims[2] > 0) ? "hexs" : "quads"; + } } i++; } From 9a60344f5eb85e504db2576349e1f3709bb3bc9d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 8 Sep 2023 11:29:04 -0700 Subject: [PATCH 27/50] Allow setting mesh and boundary names via options node for tiler. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 61a0f8e9a..77247b494 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -287,11 +287,13 @@ class Tiler std::vector m_xpts, m_ypts; double m_width, m_height; std::vector m_left, m_right, m_bottom, m_top, m_quads; + std::string meshName, boundaryMeshName; }; //--------------------------------------------------------------------------- Tiler::Tiler() : m_xpts(), m_ypts(), m_width(0.), m_height(0.), - m_left(), m_right(), m_bottom(), m_top(), m_quads() + m_left(), m_right(), m_bottom(), m_top(), m_quads(), + meshName("mesh"), boundaryMeshName("boundary") { initialize(); } @@ -409,6 +411,11 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, if(options.has_path("reorder")) reorder = options.fetch_existing("reorder").to_int() > 0; + if(options.has_path("meshname")) + meshName = options.fetch_existing("meshname").as_string(); + if(options.has_path("boundarymeshname")) + boundaryMeshName = options.fetch_existing("boundarymeshname").as_string(); + conduit::DataType indexDT(conduit::DataType::index_t()); if(options.has_child("datatype")) { @@ -559,14 +566,15 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, if(!z.empty()) res["coordsets/coords/values/z"].set(z); - res["topologies/mesh/type"] = "unstructured"; - res["topologies/mesh/coordset"] = "coords"; - res["topologies/mesh/elements/shape"] = z.empty() ? "quad" : "hex"; + conduit::Node &topo = res["topologies/" + meshName]; + topo["type"] = "unstructured"; + topo["coordset"] = "coords"; + topo["elements/shape"] = z.empty() ? "quad" : "hex"; conduit::Node tmp; tmp.set_external(conn.data(), conn.size()); - tmp.to_data_type(indexDT.id(), res["topologies/mesh/elements/connectivity"]); + tmp.to_data_type(indexDT.id(), topo["elements/connectivity"]); tmp.set_external(sizes.data(), sizes.size()); - tmp.to_data_type(indexDT.id(), res["topologies/mesh/elements/sizes"]); + tmp.to_data_type(indexDT.id(), topo["elements/sizes"]); #ifdef CONDUIT_TILER_DEBUG_FIELDS // Add fields to test the reordering. @@ -575,7 +583,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, nodeids.reserve(npts); for(conduit::index_t i = 0; i < npts; i++) nodeids.push_back(i); - res["fields/nodeids/topology"] = "mesh"; + res["fields/nodeids/topology"] = meshName; res["fields/nodeids/association"] = "vertex"; res["fields/nodeids/values"].set(nodeids); @@ -583,7 +591,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, elemids.reserve(nelem); for(conduit::index_t i = 0; i < nelem; i++) elemids.push_back(i); - res["fields/elemids/topology"] = "mesh"; + res["fields/elemids/topology"] = meshName; res["fields/elemids/association"] = "element"; res["fields/elemids/values"].set(elemids); @@ -599,7 +607,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < npts; i++) dist.push_back(sqrt(x[i]*x[i] + y[i]*y[i] + z[i]*z[i])); } - res["fields/dist/topology"] = "mesh"; + res["fields/dist/topology"] = meshName; res["fields/dist/association"] = "vertex"; res["fields/dist/values"].set(dist); #endif @@ -609,10 +617,10 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, if(reorder) { // We need offsets. - conduit::blueprint::mesh::utils::topology::unstructured::generate_offsets(res["topologies/mesh"], res["topologies/mesh/elements/offsets"]); + conduit::blueprint::mesh::utils::topology::unstructured::generate_offsets(topo, topo["elements/offsets"]); // Create a new order for the mesh elements. - const auto elemOrder = conduit::blueprint::mesh::utils::topology::spatial_ordering(res["topologies/mesh"]); + const auto elemOrder = conduit::blueprint::mesh::utils::topology::spatial_ordering(topo); #ifdef CONDUIT_USE_PARTITIONER_FOR_REORDER // NOTE: This was an idea I had after I made reorder. Reordering is like @@ -627,7 +635,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, conduit::Node options; conduit::Node &sel = options["selections"].append(); sel["type"] = "explicit"; - sel["topology"] = "mesh"; + sel["topology"] = meshName; sel["elements"].set_external(const_cast(elemOrder.data()), elemOrder.size()); conduit::Node output; conduit::blueprint::mesh::partition(res, options, output); @@ -642,9 +650,9 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, res.move(output); #else conduit::blueprint::mesh::utils::topology::unstructured::reorder( - res["topologies/mesh"], res["coordsets/coords"], res["fields"], + topo, res["coordsets/coords"], res["fields"], elemOrder, - res["topologies/mesh"], res["coordsets/coords"], res["fields"], + topo, res["coordsets/coords"], res["fields"], old2NewPoint); #endif } @@ -709,17 +717,18 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, } if(!bconn.empty()) { - res["topologies/boundary/type"] = "unstructured"; - res["topologies/boundary/coordset"] = "coords"; - res["topologies/boundary/elements/shape"] = bshape; + conduit::Node &btopo = res["topologies/" + boundaryMeshName]; + btopo["type"] = "unstructured"; + btopo["coordset"] = "coords"; + btopo["elements/shape"] = bshape; tmp.set_external(bconn.data(), bconn.size()); - tmp.to_data_type(indexDT.id(), res["topologies/boundary/elements/connectivity"]); + tmp.to_data_type(indexDT.id(), btopo["elements/connectivity"]); tmp.set_external(bsizes.data(), bsizes.size()); - tmp.to_data_type(indexDT.id(), res["topologies/boundary/elements/sizes"]); + tmp.to_data_type(indexDT.id(), btopo["elements/sizes"]); - res["fields/boundary_type/topology"] = "boundary"; + res["fields/boundary_type/topology"] = boundaryMeshName; res["fields/boundary_type/association"] = "element"; res["fields/boundary_type/values"].set(btype); } @@ -991,9 +1000,9 @@ Tiler::addAdjset(const std::vector &tiles, #define DOMAIN_INDEX(I,J,K) ((domain[2] + (K)) * dnxny + (domain[1] + (J)) * dnx + (domain[0] + (I))) auto thisDom = DOMAIN_INDEX(0, 0, 0); - conduit::Node &adjset = out["adjsets/adjset"]; + conduit::Node &adjset = out["adjsets/" + meshName + "_adjset"]; adjset["association"] = "vertex"; - adjset["topology"] = "mesh"; + adjset["topology"] = meshName; conduit::Node &groups = adjset["groups"]; // Neighbor domain indices. From 25ca4262a2ba6c48c25886150f7b949bbfa86fe2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 8 Sep 2023 18:30:36 -0700 Subject: [PATCH 28/50] Rename a field. --- .../blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 77247b494..0ad4f8605 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -728,9 +728,9 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, tmp.set_external(bsizes.data(), bsizes.size()); tmp.to_data_type(indexDT.id(), btopo["elements/sizes"]); - res["fields/boundary_type/topology"] = boundaryMeshName; - res["fields/boundary_type/association"] = "element"; - res["fields/boundary_type/values"].set(btype); + res["fields/boundary_attribute/topology"] = boundaryMeshName; + res["fields/boundary_attribute/association"] = "element"; + res["fields/boundary_attribute/values"].set(btype); } // Build an adjacency set. From 8ffa16b16051ce4168d7a4dc8d7ebcac2fb1ce2d Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 11 Sep 2023 11:30:57 -0700 Subject: [PATCH 29/50] Make boundary_attribute values 1-origin. --- .../blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 0ad4f8605..48883978d 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -673,7 +673,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < npts; i++) bconn.push_back(old2NewPoint[ids[i]]); // Renumber bsizes.push_back(npts); - btype.push_back(bnd); + btype.push_back(bnd + 1); // Make 1-origin }); } else @@ -684,7 +684,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < npts; i++) bconn.push_back(ids[i]); bsizes.push_back(npts); - btype.push_back(bnd); + btype.push_back(bnd + 1); // Make 1-origin }); } } @@ -700,7 +700,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < npts; i++) bconn.push_back(old2NewPoint[ids[i]]); // Renumber bsizes.push_back(npts); - btype.push_back(bnd); + btype.push_back(bnd + 1); // Make 1-origin }); } else @@ -711,7 +711,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < npts; i++) bconn.push_back(ids[i]); bsizes.push_back(npts); - btype.push_back(bnd); + btype.push_back(bnd + 1); // Make 1-origin }); } } From 891f19cb75f5f6d5c1fdb667b3861404051c0f16 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 13 Sep 2023 16:14:06 -0700 Subject: [PATCH 30/50] Changes to face traversal order to make better adjset --- .../conduit_blueprint_mesh_examples_tiled.cpp | 64 +++++++++---------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 48883978d..e5f69e6f0 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -791,56 +791,56 @@ Tiler::iterateBoundary2D(const std::vector &tiles, if(flags[BoundaryLeft]) { - for(conduit::index_t i = 0, j = ny-1; j >= 0; j--) + for(conduit::index_t i = 0, j = 0; j < ny; j++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(left()); - for(size_t bi = ids.size() - 1; bi > 0; bi--) + for(size_t bi = 0; bi < ids.size() - 1; bi++) { idlist[0] = ids[bi]; - idlist[1] = ids[bi - 1]; + idlist[1] = ids[bi + 1]; body(idlist, 2, BoundaryLeft); } } } - if(flags[BoundaryBottom]) + if(flags[BoundaryRight]) { - for(conduit::index_t i = 0, j = 0; i < nx; i++) + for(conduit::index_t i = nx - 1, j = 0; j < ny; j++) { const Tile ¤t = tiles[(j*nx + i)]; - const auto ids = current.getPointIds(bottom()); + const auto ids = current.getPointIds(right()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { idlist[0] = ids[bi]; idlist[1] = ids[bi + 1]; - body(idlist, 2, BoundaryBottom); + body(idlist, 2, BoundaryRight); } } } - if(flags[BoundaryRight]) + if(flags[BoundaryBottom]) { - for(conduit::index_t i = nx - 1, j = 0; j < ny; j++) + for(conduit::index_t i = 0, j = 0; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; - const auto ids = current.getPointIds(right()); + const auto ids = current.getPointIds(bottom()); for(size_t bi = 0; bi < ids.size() - 1; bi++) { idlist[0] = ids[bi]; idlist[1] = ids[bi + 1]; - body(idlist, 2, BoundaryRight); + body(idlist, 2, BoundaryBottom); } } } if(flags[BoundaryTop]) { - for(conduit::index_t i = nx - 1, j = ny - 1; i >= 0; i--) + for(conduit::index_t i = 0, j = ny - 1; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(top()); - for(size_t bi = ids.size() - 1; bi > 0; bi--) + for(size_t bi = 0; bi < ids.size() - 1; bi++) { idlist[0] = ids[bi]; - idlist[1] = ids[bi - 1]; + idlist[1] = ids[bi + 1]; body(idlist, 2, BoundaryTop); } } @@ -866,16 +866,16 @@ Tiler::iterateBoundary3D(const std::vector &tiles, { conduit::index_t offset1 = k * nPtsPerPlane; conduit::index_t offset2 = (k + 1) * nPtsPerPlane; - for(conduit::index_t i = 0, j = ny-1; j >= 0; j--) + for(conduit::index_t i = 0, j = 0; j < ny; j++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(left()); - for(size_t bi = ids.size() - 1; bi > 0; bi--) + for(size_t bi = 0; bi < ids.size() - 1; bi++) { idlist[0] = offset1 + ids[bi]; - idlist[1] = offset1 + ids[bi - 1]; - idlist[2] = offset2 + ids[bi - 1]; - idlist[3] = offset2 + ids[bi]; + idlist[1] = offset2 + ids[bi]; + idlist[2] = offset2 + ids[bi + 1]; + idlist[3] = offset1 + ids[bi + 1]; body(idlist, 4, BoundaryLeft); } } @@ -929,16 +929,16 @@ Tiler::iterateBoundary3D(const std::vector &tiles, { conduit::index_t offset1 = k * nPtsPerPlane; conduit::index_t offset2 = (k + 1) * nPtsPerPlane; - for(conduit::index_t i = nx - 1, j = ny - 1; i >= 0; i--) + for(conduit::index_t i = 0, j = ny - 1; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; const auto ids = current.getPointIds(top()); - for(size_t bi = ids.size() - 1; bi > 0; bi--) + for(size_t bi = 0; bi < ids.size() - 1; bi++) { idlist[0] = offset1 + ids[bi]; - idlist[1] = offset1 + ids[bi - 1]; - idlist[2] = offset2 + ids[bi - 1]; - idlist[3] = offset2 + ids[bi]; + idlist[1] = offset2 + ids[bi]; + idlist[2] = offset2 + ids[bi + 1]; + idlist[3] = offset1 + ids[bi + 1]; body(idlist, 4, BoundaryTop); } } @@ -947,7 +947,7 @@ Tiler::iterateBoundary3D(const std::vector &tiles, if(flags[BoundaryBack]) { for(conduit::index_t j = 0; j < ny; j++) - for(conduit::index_t i = nx - 1; i >= 0; i--) + for(conduit::index_t i = 0; i < nx; i++) { const Tile ¤t = tiles[(j*nx + i)]; iterateFaces(current.getPointIds(), 0, true, BoundaryBack, body); @@ -978,8 +978,6 @@ Tiler::addAdjset(const std::vector &tiles, { // Make the adjset name for 2 domains. auto adjset_name = [](conduit::index_t d0, conduit::index_t d1) { - if(d0 > d1) - std::swap(d0, d1); std::stringstream ss; ss << "domain_" << d0 << "_" << d1; return ss.str(); @@ -1018,16 +1016,16 @@ Tiler::addAdjset(const std::vector &tiles, // Make a state node. out["state/domain_id"] = thisDom; - int maxfaces = (nz < 1) ? 4 : 6; - for(int di = 0; di < maxfaces; di++) + int maxNeighbors = (nz < 1) ? 4 : 6; + for(int ni = 0; ni < maxNeighbors; ni++) { // If this domain has no neighbor in the current direction, skip. - if(neighbor[di] == -1) + if(neighbor[ni] == -1) continue; // Iterate over faces and come up with unique points. bool flags[6] = {false, false, false, false, false, false}; - flags[di] = true; + flags[ni] = true; std::set unique; if(nz < 1) { @@ -1075,9 +1073,9 @@ Tiler::addAdjset(const std::vector &tiles, if(!unique.empty()) { - auto name = adjset_name(thisDom, neighbor[di]); + auto name = adjset_name(thisDom, neighbor[ni]); conduit::Node &group = groups[name]; - group["neighbors"] = neighbor[di]; + group["neighbors"] = neighbor[ni]; // Store the results into a node. conduit::Node &values = group["values"]; From 3b29b7a25c414502c4d5f6f1a9e7aee8572b3b63 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 Sep 2023 13:21:10 -0700 Subject: [PATCH 31/50] Added adjset comparison function that lets us make sure each point is the same on both sides of an interface. --- .../conduit_blueprint_mpi_mesh_utils.cpp | 108 ++++++++++++++++++ .../conduit_blueprint_mpi_mesh_utils.hpp | 16 +++ 2 files changed, 124 insertions(+) diff --git a/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.cpp index 9b853deb3..fc95f0be5 100644 --- a/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.cpp @@ -635,6 +635,114 @@ adjset::validate(const Node &doms, return retval; } +//----------------------------------------------------------------------------- +bool +adjset::compare_pointwise(conduit::Node &mesh, const std::string &adjsetName, MPI_Comm comm) +{ + namespace bputils = conduit::blueprint::mesh::utils; + std::vector domains = conduit::blueprint::mesh::domains(mesh); + + // Determine total number of domains. + conduit::Node nd_local, nd_total; + nd_local = static_cast(domains.size()); + relay::mpi::sum_all_reduce(nd_local, nd_total, comm); + int maxDomains = nd_total.to_int(); + const int par_rank = relay::mpi::rank(comm); + + // Figure out which MPI ranks own each domain. + conduit::Node n_domain2rank; + { + conduit::Node n_local(DataType::int32(maxDomains)); + int *iptr = n_local.as_int_ptr(); + memset(iptr, 0, sizeof(int) * maxDomains); + for(size_t i = 0; i < domains.size(); i++) + { + auto domainId = bputils::find_domain_id(*domains[i]); + iptr[domainId] = par_rank; + } + relay::mpi::sum_all_reduce(n_local, n_domain2rank, comm); + } + const int *domain2rank = n_domain2rank.as_int_ptr(); + + // Iterate over each of the possible adjset relationships. Not all of these + // will have adjset groups. + const int tag = 12211221; + for(int d0 = 0; d0 < maxDomains; d0++) + { + for(int d1 = d0 + 1; d1 < maxDomains; d1++) + { + // make the adjset group name. + std::stringstream ss; + ss << "group_" << d0 << "_" << d1; + std::string groupName(ss.str()); + + // There are up to 2 local meshes and their corresponding remote mesh. + relay::mpi::communicate_using_schema C(comm); + conduit::Node localMesh[2], remoteMesh[2]; + int mi = 0; + for(auto dom_ptr : domains) + { + Node &domain = *dom_ptr; + auto domainId = bputils::find_domain_id(domain); + + // If the domain has the adjset, make a point mesh of its points + // that we can send to the neighbor. + std::string key("adjsets/" + adjsetName + "/groups/" + groupName + "/values"); + if(domain.has_path(key)) + { + // Get the topology that the adjset wants. + std::string tkey("adjsets/" + adjsetName + "/topology"); + std::string topoName = domain.fetch_existing(tkey).as_string(); + const Node &topo = domain.fetch_existing("topologies/" + topoName); + + // Get the group values and add them as points to the topo builder + // so we pull out a point mesh. + std::string key("adjsets/" + adjsetName + "/groups/" + groupName + "/values"); + const Node &n_values = domain.fetch_existing(key); + const auto values = n_values.as_index_t_accessor(); + bputils::topology::TopologyBuilder B(topo); + for(index_t i = 0; i < values.number_of_elements(); i++) + { + index_t ptid = values[i]; + B.add(&ptid, 1); + } + + // Make the local point mesh. + B.execute(localMesh[mi], "vertex"); + + // Get the neighbor for this group + std::string nkey("adjsets/" + adjsetName + "/groups/" + groupName + "/neighbors"); + int neighbor = domain.fetch_existing(nkey).to_int(); + + // Send this local mesh to the neighbor. + C.add_isend(localMesh[mi], domain2rank[neighbor], tag + mi); + // That neighbor will have to send us a mesh too. + C.add_irecv(remoteMesh[mi], domain2rank[neighbor], tag + mi); + + mi++; + } + } + + // Perform the exchange. + C.execute(); + + // Make sure the nodes are not different. + Node different, reducedDiff; + different = 0; + for(int i = 0; i < mi; i++) + { + Node info; + bool d = localMesh[i].diff(remoteMesh[i], info, 1.e-8); + different = different.to_int() + (d ? 1 : 0); + } + relay::mpi::sum_all_reduce(different, reducedDiff, comm); + if(reducedDiff.to_int() > 0) + return false; + } + } + return true; +} + //----------------------------------------------------------------------------- // -- end conduit::blueprint::mpi::mesh::utils::adjset -- //----------------------------------------------------------------------------- diff --git a/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.hpp b/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.hpp index 0735cb3dc..d0ca824c0 100644 --- a/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.hpp +++ b/src/libs/blueprint/conduit_blueprint_mpi_mesh_utils.hpp @@ -166,6 +166,22 @@ namespace adjset Node &info, MPI_Comm comm); + /** + @brief Traverse the adjset groups and make sure that the points are the same + on both sides of the interface. This is more restrictive than just + checking whether they exist on the other domain. Now, they have to be + the same point. + + @param mesh A node that contains one or more mesh domains. + @param adjsetName The name of the adjset to check. This must be a pairwise adjset. + @param comm The MPI communicator to use. + + @return True if the adjset are the same pointwise across each interface; + False otherwise. + */ + bool CONDUIT_BLUEPRINT_API compare_pointwise(conduit::Node &mesh, + const std::string &adjsetName, + MPI_Comm comm); } //----------------------------------------------------------------------------- // -- end conduit::blueprint::mpi::mesh::utils::adjset -- From b6b47f58575ed180e4676e5102e11b412a72258c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 Sep 2023 13:22:53 -0700 Subject: [PATCH 32/50] More adjset testing --- .../t_blueprint_mpi_mesh_transform.cpp | 88 ++++++++++++++----- 1 file changed, 66 insertions(+), 22 deletions(-) diff --git a/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp b/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp index ccf6b676a..f7f5d084a 100644 --- a/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp +++ b/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp @@ -836,32 +836,13 @@ iterate_adjset(conduit::Node &mesh, const std::string &adjsetName, Func &&func) } //----------------------------------------------------------------------------- -TEST(conduit_blueprint_mesh_examples, generate_corners_wonky) +void +test_adjset_points(conduit::Node &mesh, const std::string &adjsetName) { - // There is a 3x3x3 zone mesh that was giving generate_corners a problem - // due to adjacency sets. Adjacency sets are produced from the original one - // and when making corners. It was giving rise to adjacency sets that - // contained points that do not exist in neighbor domains. We can use the - // PointQuery to test this. - - conduit::Node mesh, s2dmap, d2smap; - generate_wonky_mesh(mesh); - - // Make the corner mesh. - conduit::blueprint::mpi::mesh::generate_corners(mesh, - "main_adjset", - "corner_adjset", - "corner_mesh", - "corner_coords", - s2dmap, - d2smap, - MPI_COMM_WORLD); - - conduit::blueprint::mpi::mesh::utils::query::PointQuery Q(mesh, MPI_COMM_WORLD); // Iterate over the points in the adjset and add them to the - iterate_adjset(mesh, "corner_adjset", + iterate_adjset(mesh, adjsetName, [&](int /*dom*/, int nbr, int val, const conduit::Node *cset, const conduit::Node */*topo*/) { // Get the point (it might not be 3D) @@ -884,10 +865,73 @@ TEST(conduit_blueprint_mesh_examples, generate_corners_wonky) const auto &r = Q.results(domainId); auto it = std::find(r.begin(), r.end(), Q.NotFound); bool found = it != r.end(); + + // If NotFound was in the results, print the occurrances for debugging. + if(found) + { + const int par_rank = relay::mpi::rank(MPI_COMM_WORLD); + const auto &p = Q.inputs(domainId); + std::stringstream ss; + ss << "rank" << par_rank << ":\n"; + ss << " domain: " << domainId << "\n"; + ss << " search:\n"; + for(size_t i = 0; i < r.size(); i++) + { + if(r[i] == Q.NotFound) + { + ss << " -\n"; + ss << " point: [" << p[3*i+0] << ", " << p[3*i+1] << ", " << p[3*i+2] << "]\n"; + ss << " found: " << r[i] << "\n"; + } + } + std::cout << ss.str() << std::endl; + } + EXPECT_FALSE(found); } } +//----------------------------------------------------------------------------- +TEST(conduit_blueprint_mesh_examples, generate_corners_wonky) +{ + // There is a 3x3x3 zone mesh that was giving generate_corners a problem + // due to adjacency sets. Adjacency sets are produced from the original one + // and when making corners. It was giving rise to adjacency sets that + // contained points that do not exist in neighbor domains. We can use the + // PointQuery to test this. + + conduit::Node mesh, s2dmap, d2smap; + generate_wonky_mesh(mesh); + + // Make the corner mesh. + conduit::blueprint::mpi::mesh::generate_corners(mesh, + "main_adjset", + "corner_adjset", + "corner_mesh", + "corner_coords", + s2dmap, + d2smap, + MPI_COMM_WORLD); + + std::vector domains = conduit::blueprint::mesh::domains(mesh); + + // Test the adjset points. + test_adjset_points(mesh, "corner_adjset"); + + // Convert the adjset in all domains to pairwise and test the points again. + for(auto dom_ptr : domains) + { + Node &domain = *dom_ptr; + conduit::blueprint::mesh::adjset::to_pairwise(domain["adjsets/corner_adjset"], + domain["adjsets/corner_pairwise_adjset"]); + } + test_adjset_points(mesh, "corner_pairwise_adjset"); + + bool same_pointwise = conduit::blueprint::mpi::mesh::utils::adjset::compare_pointwise( + mesh, "corner_pairwise_adjset", MPI_COMM_WORLD); + EXPECT_TRUE(same_pointwise); +} + //----------------------------------------------------------------------------- TEST(conduit_blueprint_mesh_examples, generate_faces_wonky) { From e45fe6e0dbe62d51b2b4c6924e075110b380fb35 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 Sep 2023 13:25:16 -0700 Subject: [PATCH 33/50] Change adjset to_pairwise function so it appends points starting with 1 neighbor, then 2 neighbors, etc. This is aimed at improving the likelihood that the order of points on both sides of an interface will be the same. --- src/libs/blueprint/conduit_blueprint_mesh.cpp | 53 ++++++++++--------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh.cpp b/src/libs/blueprint/conduit_blueprint_mesh.cpp index c52a4518e..a4fb8b0b1 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh.cpp @@ -6894,41 +6894,44 @@ mesh::adjset::to_pairwise(const Node &adjset, std::vector adjset_group_names = adjset["groups"].child_names(); std::sort(adjset_group_names.begin(), adjset_group_names.end()); - // Compile ordered lists for each neighbor containing their unique lists - // of 'adjset' entity indices, as compiled from all groups in the source 'adjset'. - std::map> pair_values_map; + // Determine the max number of neighbors. + int maxNeighbors = 1; for(const std::string &group_name : adjset_group_names) { const Node &group_node = adjset["groups"][group_name]; + const Node &neighbors_node = group_node["neighbors"]; + maxNeighbors = std::max(maxNeighbors, static_cast(neighbors_node.dtype().number_of_elements())); + } - std::vector group_neighbors; + // Compile ordered lists for each neighbor containing their unique lists + // of 'adjset' entity indices, as compiled from all groups in the source 'adjset'. + // We append the values in order according to the length of the neighbor lists, + // with shorter lists going first. + std::map> pair_values_map; + for(int nlen = 1; nlen <= maxNeighbors; nlen++) + { + for(const std::string &group_name : adjset_group_names) { - const Node &group_nvals = group_node["neighbors"]; - for(index_t ni = 0; ni < group_nvals.dtype().number_of_elements(); ++ni) - { - Node temp(DataType(group_nvals.dtype().id(), 1), - (void*)group_nvals.element_ptr(ni), true); - group_neighbors.push_back(temp.to_index_t()); - } - } + const Node &group_node = adjset["groups"][group_name]; + const Node &neighbors_node = group_node["neighbors"]; + const auto thisNeighborLen = neighbors_node.dtype().number_of_elements(); + if(neighbors_node.dtype().number_of_elements() != nlen) + continue; - std::vector group_values; - { const Node &group_vals = group_node["values"]; - for(index_t vi = 0; vi < group_vals.dtype().number_of_elements(); ++vi) + const auto group_neighbors = neighbors_node.as_index_t_accessor(); + const auto group_values = group_vals.as_index_t_accessor(); + + for(index_t ni = 0; ni < thisNeighborLen; ni++) { - Node temp(DataType(group_vals.dtype().id(), 1), - (void*)group_vals.element_ptr(vi), true); - group_values.push_back(temp.to_index_t()); + const index_t neighbor_id = group_neighbors[ni]; + std::vector &neighbor_values = pair_values_map[neighbor_id]; + auto numValues = group_values.number_of_elements(); + neighbor_values.reserve(neighbor_values.size() + numValues); + for(index_t vi = 0; vi < numValues; vi++) + neighbor_values.push_back(group_values[vi]); } } - - for(const index_t &neighbor_id : group_neighbors) - { - std::vector &neighbor_values = pair_values_map[neighbor_id]; - neighbor_values.insert(neighbor_values.end(), - group_values.begin(), group_values.end()); - } } // Given ordered lists of adjset values per neighbor, generate the destination From 2bb934f006ee530db77fc14b6e23d0977e0d4fdf Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 Sep 2023 15:36:30 -0700 Subject: [PATCH 34/50] Added a new test that makes tiled meshes in parallel, makes their corner mesh, and tests their adjsets. --- src/tests/blueprint/CMakeLists.txt | 3 +- .../blueprint/t_blueprint_mpi_mesh_tiled.cpp | 248 ++++++++++++++++++ 2 files changed, 250 insertions(+), 1 deletion(-) create mode 100644 src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp diff --git a/src/tests/blueprint/CMakeLists.txt b/src/tests/blueprint/CMakeLists.txt index dc4288f1e..51a5553c5 100644 --- a/src/tests/blueprint/CMakeLists.txt +++ b/src/tests/blueprint/CMakeLists.txt @@ -85,7 +85,8 @@ set(BLUEPRINT_RELAY_PARMETIS_MPI_TESTS set(BLUEPRINT_RELAY_MPI_TESTS_RANKS_4 t_blueprint_mpi_mesh_flatten t_blueprint_mpi_mesh_partition - t_blueprint_mpi_mesh_distribute_4_ranks) + t_blueprint_mpi_mesh_distribute_4_ranks + t_blueprint_mpi_mesh_tiled) if(MPI_FOUND) message(STATUS "MPI enabled: Adding conduit_blueprint_mpi and conduit_relay_mpi unit tests") diff --git a/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp new file mode 100644 index 000000000..983abecea --- /dev/null +++ b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp @@ -0,0 +1,248 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and other Conduit +// Project developers. See top-level LICENSE AND COPYRIGHT files for dates and +// other details. No copyright assignment is required to contribute to Conduit. + +//----------------------------------------------------------------------------- +/// +/// file: t_blueprint_mpi_mesh_tiled.cpp +/// +//----------------------------------------------------------------------------- + +#include "conduit.hpp" +#include "conduit_blueprint.hpp" +#include "conduit_blueprint_mesh_examples.hpp" +#include "conduit_blueprint_mpi_mesh.hpp" +#include "conduit_blueprint_mpi_mesh_utils.hpp" +#include "conduit_relay.hpp" +#include "conduit_relay_mpi.hpp" +#include "conduit_relay_mpi_io_blueprint.hpp" +#include "conduit_log.hpp" + +#include "blueprint_test_helpers.hpp" + +#include +#include +#include +#include +#include "gtest/gtest.h" + +using namespace conduit; +using namespace conduit::utils; +using namespace generate; + +// Uncomment if we want to write the data files. +// #define CONDUIT_WRITE_TEST_DATA + +//--------------------------------------------------------------------------- +#ifdef CONDUIT_WRITE_TEST_DATA +/** + @brief Save the node to an HDF5 compatible with VisIt or the + conduit_adjset_validate tool. + */ +void save_mesh(const conduit::Node &root, const std::string &filebase) +{ + // NOTE: Enable this to write files for debugging. + const std::string protocol("hdf5"); + conduit::relay::mpi::io::blueprint::save_mesh(root, filebase, protocol, MPI_COMM_WORLD); +} +#endif + +//----------------------------------------------------------------------------- +/** + @brief Make domains for a tiled mesh. + + @param mesh The node that will contain the domains. + @param dims The number of zones in each domain. dims[2] == 0 for 2D meshes. + @param domains The number of domains to make. All values > 0. + @param domainNumbering An optional vector that can reorder the domains. + */ +void +make_tiled(conduit::Node &mesh, const int dims[3], const int domains[3], bool reorder, const std::vector &domainNumbering) +{ + const int ndoms = domains[0] * domains[1] * domains[2]; + const int par_rank = relay::mpi::rank(MPI_COMM_WORLD); + const int par_size = relay::mpi::size(MPI_COMM_WORLD); + std::vector domains_per_rank(par_size, 0); + for(int di = 0; di < ndoms; di++) + domains_per_rank[di % par_size]++; + int offset = 0; + for(int i = 0; i < par_rank; i++) + offset += domains_per_rank[i]; + + // Make domains. + const double extents[] = {0., 1., 0., 1., 0., 1.}; + int domainIndex = 0; + for(int k = 0; k < domains[2]; k++) + for(int j = 0; j < domains[1]; j++) + for(int i = 0; i < domains[0]; i++, domainIndex++) + { + int domain[] = {i,j,k}; + + int domainId = domainIndex; + if(!domainNumbering.empty()) + domainId = domainNumbering[domainIndex]; + + // See if we need to make the domain on this rank. + if(domainId >= offset && domainId < offset + domains_per_rank[par_rank]) + { + // Determine the size and location of this domain in the whole. + double sideX = (extents[1] - extents[0]) / static_cast(domains[0]); + double sideY = (extents[3] - extents[2]) / static_cast(domains[1]); + double sideZ = (extents[5] - extents[4]) / static_cast(domains[2]); + double domainExt[] = {extents[0] + domain[0] * sideX, + extents[0] + (domain[0]+1) * sideX, + extents[2] + domain[1] * sideY, + extents[2] + (domain[1]+1) * sideY, + extents[4] + domain[2] * sideZ, + extents[4] + (domain[2]+1) * sideZ}; + conduit::Node opts; + opts["extents"].set(domainExt, 6); + opts["domain"].set(domain, 3); + opts["domains"].set(domains, 3); + opts["reorder"] = reorder ? 1 : 0; + + if(ndoms > 1) + { + char domainName[64]; + if(!domainNumbering.empty()) + sprintf(domainName, "domain_%07d", domainNumbering[domainIndex]); + else + sprintf(domainName, "domain_%07d", domainIndex); + conduit::Node &dom = mesh[domainName]; + conduit::blueprint::mesh::examples::tiled(dims[0], dims[1], dims[2], dom, opts); + } + else + { + conduit::blueprint::mesh::examples::tiled(dims[0], dims[1], dims[2], mesh, opts); + } + } + } +} + +//----------------------------------------------------------------------------- +template +void +foreach_permutation_impl(int level, std::vector &values, Func &&func) +{ + int n = static_cast(values.size()); + if(level < n) + { + int n = static_cast(values.size()); + for(int i = 0; i < n; i++) + { + const int *start = &values[0]; + const int *end = start + level; + if(std::find(start, end, i) == end) + { + values[level] = i; + foreach_permutation_impl(level + 1, values, func); + } + } + } + else + { + func(values); + } +} + +template +void +foreach_permutation(int maxval, Func &&func) +{ + std::vector values(maxval, -1); + foreach_permutation_impl(0, values, func); +} + +//----------------------------------------------------------------------------- +void +test_tiled_adjsets(const int dims[3], const std::string &testName) +{ + // Make 4 domains but alter their locations. + int index = 0; + foreach_permutation(4, [&](const std::vector &domainNumbering) + { + const int domains[] = {2,2,1}; + const int par_rank = relay::mpi::rank(MPI_COMM_WORLD); + + for(int r = 0; r < 2; r++) + { + bool reorder = r == 1; + + // Make the mesh. + conduit::Node mesh; + make_tiled(mesh, dims, domains, reorder, domainNumbering); + + // Now, make a corner mesh + conduit::Node s2dmap, d2smap; + conduit::blueprint::mpi::mesh::generate_corners(mesh, + "mesh_adjset", + "corner_adjset", + "corner_mesh", + "corner_coords", + s2dmap, + d2smap, + MPI_COMM_WORLD); + // Convert to pairwise adjset. + std::vector doms = conduit::blueprint::mesh::domains(mesh); + for(auto dom_ptr : doms) + { + conduit::Node &domain = *dom_ptr; + conduit::blueprint::mesh::adjset::to_pairwise(domain["adjsets/corner_adjset"], + domain["adjsets/corner_pairwise_adjset"]); + } + +#ifdef CONDUIT_WRITE_TEST_DATA + // Save the mesh. + std::stringstream ss; + ss << "_r" << r; + for(const auto &value : domainNumbering) + ss << "_" << value; + std::string filebase(testName + ss.str()); + save_mesh(mesh, filebase); +#endif + + // Check that its adjset points are the same along the edges. + bool same = conduit::blueprint::mpi::mesh::utils::adjset::compare_pointwise(mesh, "mesh_adjset", MPI_COMM_WORLD); + if(!same) + { + mesh.print(); + } + EXPECT_TRUE(same); + + // Check that its adjset points are the same along the edges. + same = conduit::blueprint::mpi::mesh::utils::adjset::compare_pointwise(mesh, "corner_pairwise_adjset", MPI_COMM_WORLD); + if(!same) + { + mesh.print(); + } + EXPECT_TRUE(same); + } + }); +} + +//----------------------------------------------------------------------------- +TEST(conduit_blueprint_mpi_mesh_tiled, two_dimensional) +{ + const int dims[]= {3,3,0}; + test_tiled_adjsets(dims, "two_dimensional"); +} + +//----------------------------------------------------------------------------- +TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional) +{ + const int dims[]= {2,2,2}; + test_tiled_adjsets(dims, "three_dimensional"); +} + +//----------------------------------------------------------------------------- +int main(int argc, char* argv[]) +{ + int result = 0; + + ::testing::InitGoogleTest(&argc, argv); + MPI_Init(&argc, &argv); + result = RUN_ALL_TESTS(); + MPI_Finalize(); + + return result; +} From ddda2ef82f7a1858a9616ee01867bc7a0bcd8aa3 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 Sep 2023 21:41:56 -0700 Subject: [PATCH 35/50] Compiler warnings. --- .../blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 9 ++++----- src/libs/blueprint/conduit_blueprint_mesh_utils.cpp | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index e5f69e6f0..e22aebce2 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -467,7 +467,6 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); - double newOrigin[] = {origin[0], origin[1], origin[2]}; for(conduit::index_t j = 0; j < ny; j++) { M[2][0] = origin[0]; @@ -1033,7 +1032,7 @@ Tiler::addAdjset(const std::vector &tiles, if(reorder) { iterateBoundary2D(tiles, nx, ny, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) { for(conduit::index_t i = 0; i < npts; i++) unique.insert(old2NewPoint[ids[i]]); // Renumber @@ -1042,7 +1041,7 @@ Tiler::addAdjset(const std::vector &tiles, else { iterateBoundary2D(tiles, nx, ny, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) { for(conduit::index_t i = 0; i < npts; i++) unique.insert(ids[i]); @@ -1054,7 +1053,7 @@ Tiler::addAdjset(const std::vector &tiles, if(reorder) { iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) { for(conduit::index_t i = 0; i < npts; i++) unique.insert(old2NewPoint[ids[i]]); // Renumber @@ -1063,7 +1062,7 @@ Tiler::addAdjset(const std::vector &tiles, else { iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int bnd) + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) { for(conduit::index_t i = 0; i < npts; i++) unique.insert(ids[i]); diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index 5b16224d2..9762ce319 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -2297,7 +2297,7 @@ topology::unstructured::reorder(const conduit::Node &topo, auto npts = conduit::blueprint::mesh::coordset::length(coordset); // Fill in the old2New point mapping. It gets passed out of the function. old2NewPoints.resize(npts); - for(size_t i = 0; i < npts; i++) + for(conduit::index_t i = 0; i < npts; i++) old2NewPoints[i] = invalidNode; // ptReorder is used to reorder/slice vertex-associated data. We'll allow // up to npts values but it might not be that large if we are selecting a From 8998d79b5c376ce5fafa7584077c0914dda1c8c9 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Thu, 14 Sep 2023 22:28:23 -0700 Subject: [PATCH 36/50] Added a 12 domain test case that finds a bug. --- .../blueprint/t_blueprint_mpi_mesh_tiled.cpp | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp index 983abecea..a8308cee4 100644 --- a/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp +++ b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp @@ -220,6 +220,7 @@ test_tiled_adjsets(const int dims[3], const std::string &testName) }); } +#if 1 //----------------------------------------------------------------------------- TEST(conduit_blueprint_mpi_mesh_tiled, two_dimensional) { @@ -233,7 +234,57 @@ TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional) const int dims[]= {2,2,2}; test_tiled_adjsets(dims, "three_dimensional"); } +#endif +#if 0 +//----------------------------------------------------------------------------- +TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional_12) +{ + // This 12 domain case was found to cause adjset problems. + const int dims[] = {2,2,2}, domains[] = {3,2,2}; + const int par_rank = relay::mpi::rank(MPI_COMM_WORLD); + for(int r = 0; r < 2; r++) + { + // Make the mesh. + bool reorder = r == 1; + conduit::Node mesh; + make_tiled(mesh, dims, domains, reorder, std::vector{}); + + // Now, make a corner mesh + conduit::Node s2dmap, d2smap; + conduit::blueprint::mpi::mesh::generate_corners(mesh, + "mesh_adjset", + "corner_adjset", + "corner_mesh", + "corner_coords", + s2dmap, + d2smap, + MPI_COMM_WORLD); + // Convert to pairwise adjset. + std::vector doms = conduit::blueprint::mesh::domains(mesh); + for(auto dom_ptr : doms) + { + conduit::Node &domain = *dom_ptr; + conduit::blueprint::mesh::adjset::to_pairwise(domain["adjsets/corner_adjset"], + domain["adjsets/corner_pairwise_adjset"]); + } + +#ifdef CONDUIT_WRITE_TEST_DATA + // Save the mesh. + std::stringstream ss; + ss << "_r" << r; + for(const auto &value : domainNumbering) + ss << "_" << value; + std::string filebase(testName + ss.str()); + save_mesh(mesh, filebase); +#endif + + // Check that its adjset points are the same along the edges. + bool same = conduit::blueprint::mpi::mesh::utils::adjset::compare_pointwise(mesh, "corner_pairwise_adjset", MPI_COMM_WORLD); + EXPECT_TRUE(same); + } +} +#endif //----------------------------------------------------------------------------- int main(int argc, char* argv[]) { From d5a79b7af4ab7adc733c4a375831c7832795aa3b Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 15 Sep 2023 01:29:32 -0700 Subject: [PATCH 37/50] Support reading alternate tiles in blueprint form. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 330 ++++++++++++++---- 1 file changed, 260 insertions(+), 70 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index e22aebce2..44fdedf25 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -132,6 +132,21 @@ class Tiler /// Fill in the tile pattern from a Node. void initialize(const conduit::Node &t); + /// Return the topology (the first one) + const conduit::Node &getTopology() const + { + const conduit::Node &t = m_options.fetch_existing("topologies"); + return t[0]; + } + + /// Return the coordset + const conduit::Node &getCoordset() const + { + const conduit::Node &t = getTopology(); + std::string coordsetName(t.fetch_existing("coordset").as_string()); + return m_options.fetch_existing("coordsets/" + coordsetName); + } + /// Return point indices of points along left edge. const std::vector &left() const { return m_left; } @@ -158,16 +173,18 @@ class Tiler { // Iterate through points in the template and add them if they have // not been created yet. - for(size_t i = 0; i < m_xpts.size(); i++) + const auto &xpts = getCoordset().fetch_existing("values/x").as_double_array(); + const auto &ypts = getCoordset().fetch_existing("values/y").as_double_array(); + for(conduit::index_t i = 0; i < xpts.number_of_elements(); i++) { if(ptids[i] == Tile::INVALID_POINT) { ptids[i] = static_cast(x.size()); // (x,y,1) * M - double xc = m_xpts[i] * M[0][0] + m_ypts[i] * M[1][0] + M[2][0]; - double yc = m_xpts[i] * M[0][1] + m_ypts[i] * M[1][1] + M[2][1]; - double h = m_xpts[i] * M[0][2] + m_ypts[i] * M[1][2] + M[2][2]; + double xc = xpts[i] * M[0][0] + ypts[i] * M[1][0] + M[2][0]; + double yc = xpts[i] * M[0][1] + ypts[i] * M[1][1] + M[2][1]; + double h = xpts[i] * M[0][2] + ypts[i] * M[1][2] + M[2][2]; xc /= h; yc /= h; x.push_back(xc); @@ -176,69 +193,160 @@ class Tiler } } - /// Iterate over the tile's quad cells and apply a lambda. + /// Iterate over faces + template + void iterateFaces(const Connectivity &conn, + conduit::index_t nelem, + conduit::index_t sides, + conduit::index_t *idlist, + const std::vector &ptids, + conduit::index_t offset, + bool reverse, + int stype, + Body &&body) const + { + if(reverse) + { + for(conduit::index_t i = 0; i < nelem; i++) + { + auto start = i * sides; + for(conduit::index_t s = 0; s < sides; s++) + idlist[s] = offset + ptids[conn[start + sides - s]]; + body(idlist, sides, stype); + } + } + else + { + for(conduit::index_t i = 0; i < nelem; i++) + { + auto start = i * sides; + for(conduit::index_t s = 0; s < sides; s++) + idlist[s] = offset + ptids[conn[start + s]]; + body(idlist, sides, stype); + } + } + } + + /// Iterate over the tile's elements and apply a lambda. template - void iterateFaces(const std::vector &ptids, conduit::index_t offset, - bool reverse, int stype, Body &&body) const + void iterateFaces(const std::vector &ptids, + conduit::index_t offset, + bool reverse, + int stype, + Body &&body) const { - const size_t nquads = m_quads.size() / 4; - int order[] = {reverse ? 3 : 0, reverse ? 2 : 1, reverse ? 1 : 2, reverse ? 0 : 3}; - for(size_t i = 0; i < nquads; i++) + const conduit::Node &topo = getTopology(); + std::string shape = topo.fetch_existing("elements/shape").as_string(); + conduit::index_t sides = 0; + if(shape == "tri") + sides = 3; + else if(shape == "quad") + sides = 4; + + // Handle triangles and quads. + const conduit::Node &n_conn = topo.fetch_existing("elements/connectivity"); + if(sides == 3 || sides == 4) { conduit::index_t idlist[4]; - idlist[0] = offset + ptids[m_quads[4*i + order[0]]]; - idlist[1] = offset + ptids[m_quads[4*i + order[1]]]; - idlist[2] = offset + ptids[m_quads[4*i + order[2]]]; - idlist[3] = offset + ptids[m_quads[4*i + order[3]]]; - body(idlist, 4, stype); + bool handled = false; + if(n_conn.dtype().spanned_bytes() == n_conn.dtype().strided_bytes()) + { + if(n_conn.dtype().is_index_t()) + { + iterateFaces(n_conn.as_index_t_ptr(), + n_conn.dtype().number_of_elements() / sides, + sides, idlist, ptids, + offset, reverse, stype, body); + handled = true; + } + else if(n_conn.dtype().is_int32()) + { + iterateFaces(n_conn.as_int32_ptr(), + n_conn.dtype().number_of_elements() / sides, + sides, idlist, ptids, + offset, reverse, stype, body); + handled = true; + } + } + if(!handled) + { + iterateFaces(n_conn.as_index_t_accessor(), + n_conn.dtype().number_of_elements() / sides, + sides, idlist, ptids, + offset, reverse, stype, body); + } + } + else if(shape == "polygonal") + { + // handle polygons + const auto conn = n_conn.as_index_t_accessor(); + const auto sizes = topo.fetch_existing("elements/sizes").as_index_t_accessor(); + const conduit::index_t nelem = sizes.number_of_elements(); + conduit::index_t start = 0; + std::vector idlist(10); + if(reverse) + { + for(conduit::index_t i = 0; i < nelem; i++) + { + auto esides = sizes[i]; + idlist.reserve(esides); + for(conduit::index_t s = 0; s < esides; s++) + idlist[s] = offset + ptids[conn[start + esides - s]]; + body(&idlist[0], esides, stype); + start += esides; + } + } + else + { + for(conduit::index_t i = 0; i < nelem; i++) + { + auto esides = sizes[i]; + idlist.reserve(esides); + for(conduit::index_t s = 0; s < esides; s++) + idlist[s] = offset + ptids[conn[start + s]]; + body(&idlist[0], esides, stype); + start += esides; + } + } } } /// Emit the hex cells using this tile's point ids. - void addHexs(const std::vector &ptids, + void addVolumeElements(const std::vector &ptids, conduit::index_t plane1Offset, conduit::index_t plane2Offset, std::vector &conn, std::vector &sizes) const { - const size_t nquads = m_quads.size() / 4; - for(size_t i = 0; i < nquads; i++) + const conduit::Node &topo = getTopology(); + std::string shape = topo.fetch_existing("elements/shape").as_string(); + conduit::index_t sides = 0; + if(shape == "tri") + sides = 3; + else if(shape == "quad") + sides = 4; + + if(sides == 3 || sides == 4) { - conn.push_back(plane1Offset + ptids[m_quads[4*i + 0]]); - conn.push_back(plane1Offset + ptids[m_quads[4*i + 1]]); - conn.push_back(plane1Offset + ptids[m_quads[4*i + 2]]); - conn.push_back(plane1Offset + ptids[m_quads[4*i + 3]]); + const conduit::Node &n_conn = topo.fetch_existing("elements/connectivity"); + const auto tileconn = n_conn.as_index_t_accessor(); + const conduit::index_t nelem = tileconn.number_of_elements() / sides; + for(conduit::index_t i = 0; i < nelem; i++) + { + conduit::index_t start = i * sides; + for(conduit::index_t s = 0; s < sides; s++) + conn.push_back(plane1Offset + ptids[tileconn[start + s]]); - conn.push_back(plane2Offset + ptids[m_quads[4*i + 0]]); - conn.push_back(plane2Offset + ptids[m_quads[4*i + 1]]); - conn.push_back(plane2Offset + ptids[m_quads[4*i + 2]]); - conn.push_back(plane2Offset + ptids[m_quads[4*i + 3]]); + for(conduit::index_t s = 0; s < sides; s++) + conn.push_back(plane2Offset + ptids[tileconn[start + s]]); - sizes.push_back(8); + sizes.push_back(2 * sides); + } } - } - - /// Compute the extents of the supplied values. - double computeExtents(const std::vector &values) const - { - double ext[2] = {values[0], values[0]}; - for(auto val : values) + else { - ext[0] = std::min(ext[0], val); - ext[1] = std::max(ext[1], val); + CONDUIT_ERROR("Tiling polygonal shapes into 3D polyhedra is not yet supported."); } - return ext[1] - ext[0]; - } - - /// Turn a node into a double vector. - std::vector toDoubleVector(const conduit::Node &n) const - { - auto acc = n.as_double_accessor(); - std::vector vec; - vec.reserve(acc.number_of_elements()); - for(conduit::index_t i = 0; i < acc.number_of_elements(); i++) - vec.push_back(acc[i]); - return vec; } /// Turn a node into an int vector. @@ -284,15 +392,15 @@ class Tiler const conduit::Node &options, conduit::Node &out) const; private: - std::vector m_xpts, m_ypts; + conduit::Node m_options; double m_width, m_height; - std::vector m_left, m_right, m_bottom, m_top, m_quads; + std::vector m_left, m_right, m_bottom, m_top; std::string meshName, boundaryMeshName; }; //--------------------------------------------------------------------------- -Tiler::Tiler() : m_xpts(), m_ypts(), m_width(0.), m_height(0.), - m_left(), m_right(), m_bottom(), m_top(), m_quads(), +Tiler::Tiler() : m_options(), m_width(0.), m_height(0.), + m_left(), m_right(), m_bottom(), m_top(), meshName("mesh"), boundaryMeshName("boundary") { initialize(); @@ -303,7 +411,7 @@ void Tiler::initialize() { // Default pattern - m_xpts = std::vector{ + const double x[] = { 0., 3., 10., 17., 20., 0., 3., 17., 20., 5., 15., @@ -315,7 +423,7 @@ Tiler::initialize() 0., 3., 10., 17., 20., }; - m_ypts = std::vector{ + const double y[] = { 0., 0., 0., 0., 0., 3., 3., 3., 3., 5., 5., @@ -327,7 +435,7 @@ Tiler::initialize() 20., 20., 20., 20., 20., }; - m_quads = std::vector{ + const conduit::index_t conn[] = { // lower-left quadrant 0,1,6,5, 1,2,9,6, @@ -358,29 +466,97 @@ Tiler::initialize() 26,27,32,31 }; - m_left = std::vector{0,5,14,24,28}; - m_right = std::vector{4,8,18,27,32}; - m_bottom = std::vector{0,1,2,3,4}; - m_top = std::vector{28,29,30,31,32}; - - m_width = computeExtents(m_xpts); - m_height = computeExtents(m_ypts); + // Define the tile as a topology. + conduit::Node opts; + opts["coordsets/coords/type"] = "explicit"; + opts["coordsets/coords/values/x"].set(x, sizeof(x) / sizeof(double)); + opts["coordsets/coords/values/y"].set(y, sizeof(y) / sizeof(double)); + opts["topologies/tile/type"] = "unstructured"; + opts["topologies/tile/coordset"] = "coords"; + opts["topologies/tile/elements/shape"] = "quad"; + constexpr auto nelem = (sizeof(conn) / sizeof(conduit::index_t)) / 4; + opts["topologies/tile/elements/connectivity"].set(conn, nelem * 4); + std::vector size(nelem, 4); + opts["topologies/tile/elements/sizes"].set(size.data(), size.size()); + + // Define tile boundary indices. + const conduit::index_t left[] = {0,5,14,24,28}; + const conduit::index_t right[] = {4,8,18,27,32}; + const conduit::index_t bottom[] = {0,1,2,3,4}; + const conduit::index_t top[] = {28,29,30,31,32}; + opts["left"].set(left, sizeof(left) / sizeof(conduit::index_t)); + opts["right"].set(right, sizeof(right) / sizeof(conduit::index_t)); + opts["bottom"].set(bottom, sizeof(bottom) / sizeof(conduit::index_t)); + opts["top"].set(top, sizeof(top) / sizeof(conduit::index_t)); + + initialize(opts); } //--------------------------------------------------------------------------- void Tiler::initialize(const conduit::Node &t) { - m_xpts = toDoubleVector(t.fetch_existing("x")); - m_ypts = toDoubleVector(t.fetch_existing("y")); - m_quads = toIndexVector(t.fetch_existing("quads")); + std::vector required{"coordsets", "topologies", "left", "right", "bottom", "top"}; + for(const auto &name : required) + { + if(!t.has_child(name)) + { + CONDUIT_ERROR("Node does not contain key: " << name); + } + } + + // Get the tile boundaries and convert them. m_left = toIndexVector(t.fetch_existing("left")); m_right = toIndexVector(t.fetch_existing("right")); m_bottom = toIndexVector(t.fetch_existing("bottom")); m_top = toIndexVector(t.fetch_existing("top")); + if(m_left.size() != m_right.size()) + { + CONDUIT_ERROR("left/right vectors have different lengths."); + } + if(m_bottom.size() != m_top.size()) + { + CONDUIT_ERROR("bottom/top vectors have different lengths."); + } - m_width = computeExtents(m_xpts); - m_height = computeExtents(m_ypts); + // Save the tile definition options. + m_options.set(t); + + // Make sure the coordset is 2D, explicit. + if(conduit::blueprint::mesh::coordset::dims(getCoordset()) != 2) + { + CONDUIT_ERROR("The tile coordset must be 2D."); + } + if(getCoordset()["type"].as_string() != "explicit") + { + CONDUIT_ERROR("The tile coordset must be explicit."); + } + // Make sure the topology is 2D, unstructured + if(conduit::blueprint::mesh::topology::dims(getTopology()) != 2) + { + CONDUIT_ERROR("The tile topology must be 2D."); + } + if(getTopology()["type"].as_string() != "unstructured") + { + CONDUIT_ERROR("The tile topology must be 2D."); + } + + // Compute the tile extents. + if(t.has_path("translate/x")) + m_width = t["translate/x"].to_double(); + else + { + const auto &xc = getCoordset().fetch_existing("values/x").as_double_array(); + m_width = xc.max() - xc.min(); + } + + if(t.has_path("translate/y")) + m_height = t["translate/y"].to_double(); + else + { + const auto &yc = getCoordset().fetch_existing("values/y").as_double_array(); + m_height = yc.max() - yc.min(); + } } //--------------------------------------------------------------------------- @@ -433,6 +609,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, double M[3][3] = {{1., 0., 0.}, {0., 1., 0.}, {0., 0., 1.}}; if(options.has_path("extents")) { + // Extents for the domain were given. Fit the domain into it. auto extents = options.fetch_existing("extents").as_double_accessor(); tx = (extents[1] - extents[0]) / nx; ty = (extents[3] - extents[2]) / ny; @@ -465,6 +642,9 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, M[2][0] = origin[0]; M[2][1] = origin[1]; + // Number of tile points. + const auto nTilePts = getCoordset().fetch_existing("values/x").dtype().number_of_elements(); + // Make a pass where we make nx*ny tiles so we can generate their points. std::vector tiles(nx * ny); for(conduit::index_t j = 0; j < ny; j++) @@ -475,7 +655,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, Tile ¤t = tiles[(j*nx + i)]; // The first time we've used the tile, set its size. - current.reset(m_xpts.size()); + current.reset(nTilePts); // Copy some previous points over so they can be shared. if(i > 0) @@ -496,6 +676,8 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, } conduit::index_t ptsPerPlane = 0; + std::string shape2, shape3; + shape2 = getTopology().fetch_existing("elements/shape").as_string(); if(nz < 1) { // Iterate over the tiles and add their quads. @@ -520,6 +702,8 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, { ptsPerPlane = static_cast(x.size()); + shape3 = (shape2 == "tri") ? "wedge" : "hex"; + // We have x,y points now. We need to replicate them to make multiple planes. // We make z coordinates too. conduit::index_t nplanes = nz + 1; @@ -552,7 +736,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, for(conduit::index_t i = 0; i < nx; i++) { Tile ¤t = tiles[(j*nx + i)]; - addHexs(current.getPointIds(), offset1, offset2, conn, sizes); + addVolumeElements(current.getPointIds(), offset1, offset2, conn, sizes); } } } @@ -568,7 +752,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, conduit::Node &topo = res["topologies/" + meshName]; topo["type"] = "unstructured"; topo["coordset"] = "coords"; - topo["elements/shape"] = z.empty() ? "quad" : "hex"; + topo["elements/shape"] = z.empty() ? shape2 : shape3; conduit::Node tmp; tmp.set_external(conn.data(), conn.size()); tmp.to_data_type(indexDT.id(), topo["elements/connectivity"]); @@ -660,6 +844,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, std::string bshape; bool flags[6]; boundaryFlags(options, flags); + if(nz < 1) { // 2D @@ -691,6 +876,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, { // 3D bshape = "quad"; + bool anyNonQuads = false; if(reorder) { iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, @@ -700,6 +886,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, bconn.push_back(old2NewPoint[ids[i]]); // Renumber bsizes.push_back(npts); btype.push_back(bnd + 1); // Make 1-origin + anyNonQuads |= (npts != 4); }); } else @@ -711,8 +898,11 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, bconn.push_back(ids[i]); bsizes.push_back(npts); btype.push_back(bnd + 1); // Make 1-origin + anyNonQuads |= (npts != 4); }); } + if(anyNonQuads) + bshape = "polygonal"; } if(!bconn.empty()) { From f5b62236135ddf39b5650052265229b0919a526c Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Fri, 15 Sep 2023 11:11:50 -0700 Subject: [PATCH 38/50] Fixes for tiling --- .../blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 44fdedf25..ef14c2cd8 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -207,11 +207,12 @@ class Tiler { if(reverse) { + auto s1 = sides - 1; for(conduit::index_t i = 0; i < nelem; i++) { auto start = i * sides; for(conduit::index_t s = 0; s < sides; s++) - idlist[s] = offset + ptids[conn[start + sides - s]]; + idlist[s1 - s] = offset + ptids[conn[start + s]]; body(idlist, sides, stype); } } @@ -917,6 +918,9 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, tmp.set_external(bsizes.data(), bsizes.size()); tmp.to_data_type(indexDT.id(), btopo["elements/sizes"]); + if(bshape == "polygonal") + conduit::blueprint::mesh::utils::topology::unstructured::generate_offsets(btopo, btopo["elements/offsets"]); + res["fields/boundary_attribute/topology"] = boundaryMeshName; res["fields/boundary_attribute/association"] = "element"; res["fields/boundary_attribute/values"].set(btype); From 2e350aaa0c4c3a1e3ebcaf58658e65a0cb850e03 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Mon, 18 Sep 2023 18:38:16 -0700 Subject: [PATCH 39/50] Working on adjset --- .../conduit_blueprint_mesh_examples_tiled.cpp | 334 ++++++++++++++++-- 1 file changed, 300 insertions(+), 34 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index ef14c2cd8..985bdfb3c 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -1157,6 +1157,26 @@ Tiler::iterateBoundary3D(const std::vector &tiles, } } +#if 0 +//--------------------------------------------------------------------------- +template +void +Tiler::iterateDomainEdges(const std::vector &tiles, + conduit::index_t nx, + conduit::index_t ny, + conduit::index_t nz, + conduit::index_t nPtsPerPlane, + conduit::index_t dom[3], + conduit::index_t domains[3], + Body &&body) const +{ + conduit::index_t idlist[4]; + + if(domainExists(domain[0], + +} +#endif + //--------------------------------------------------------------------------- void Tiler::addAdjset(const std::vector &tiles, @@ -1171,6 +1191,8 @@ Tiler::addAdjset(const std::vector &tiles, { // Make the adjset name for 2 domains. auto adjset_name = [](conduit::index_t d0, conduit::index_t d1) { + if(d0 > d1) + std::swap(d0, d1); std::stringstream ss; ss << "domain_" << d0 << "_" << d1; return ss.str(); @@ -1206,9 +1228,271 @@ Tiler::addAdjset(const std::vector &tiles, neighbor[BoundaryFront] = (domain[2] + 1 < domains[2]) ? DOMAIN_INDEX(0, 0, 1) : -1; #undef DOMAIN_INDEX +#if 0 + if(nz < 1) + { + } + else + { + const conduit::index_t cornerNeighbors[][3] = {{-1, -1, -1}, + {1, -1, -1}, + {-1, 1, -1}, + {1, 1, -1}, + {-1, -1, 1}, + {1, -1, 1}, + {-1, 1, 1}, + {1, 1, 1}}; + for(int ni = 0; ni < 8; ni++) + { + conduit::index_t ndom[3] = {domain[0] + cornerNeighbors[ni][0], + domain[1] + cornerNeighbors[ni][1], + domain[2] + cornerNeighbors[ni][2]}; + // If the neighbor domain exists, + if((ndom[0] >= 0 && ndom[0] < domains[0]) && + (ndom[1] >= 0 && ndom[1] < domains[1]) && + (ndom[2] >= 0 && ndom[2] < domains[2])) + { + conduit::index_t ti = (cornerNeighbors[ni][0] == -1) ? 0 : (nx - 1); + conduit::index_t tj = (cornerNeighbors[ni][1] == -1) ? 0 : (ny - 1); + conduit::index_t planeOffset = (cornerNeighbors[ni][2] == -1) ? 0 : (nz - 1) * ptsPerPlane; + const Tile &tile = tiles[tj * nx + ti]; + + } + } + } + + if(domainExists(-1, -1, -1, domain, domains)) + { + + const Tile &tile = tiles[tj * nx + ti]; + } + + // Trace around the tile to get the points for the edges of the tile + // in original node ordering. + std::vector left, right, bottom, top; + const bool left_flags[] = {true, false, false, false}; + iterateBoundary2D(tiles, nx, ny, left_flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + { + for(conduit::index_t i = 0; i < npts; i++) +// TODO: add unique tests because we get passed the start/end of each line segment. +// Maybe pass a "lastSegment" flag to the lambda so we can keep just the first point most of the time. +// Also, why pass npts when it is always 2? + left.push_back(ids[i]); + }); + const bool right_flags[] = {false, true, false, false}; + iterateBoundary2D(tiles, nx, ny, right_flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + { + right.reserve(npts); + for(conduit::index_t i = 0; i < npts; i++) + right.push_back(ids[i]); + }); + const bool bottom_flags[] = {false, false, true, false}; + iterateBoundary2D(tiles, nx, ny, right_flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + { + bottom.reserve(npts); + for(conduit::index_t i = 0; i < npts; i++) + bottom.push_back(ids[i]); + }); + const bool top_flags[] = {false, false, false, true}; + iterateBoundary2D(tiles, nx, ny, right_flags, + [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + { + top.reserve(npts); + for(conduit::index_t i = 0; i < npts; i++) + top.push_back(ids[i]); + }); + + conduit::index_t frontPlane = ptsPerPlane * (nz - 1); + conduit::index_t corners[8]; + corner[0] = left[0]; // back, lower left + corner[1] = right[0]; // back, lower right + corner[2] = left[left.size() - 1]; // back, upper left + corner[3] = right[right.size() - 1]; // back, upper right + corner[4] = left[0] + frontPlane; // front, lower left + corner[5] = right[0] + frontPlane; // front, lower right + corner[6] = left[left.size() - 1] + frontPlane; // front, upper left + corner[7] = right[right.size() - 1] + frontPlane; // front, upper right + +conduit::index_t +Tiler::domainExists(conduit::index_t d0, conduit::index_t d1, conduit::index_t d2, + const conduit::index_t domain[3], + const conduit::index_t domains[3]) const +{ + const conduit::index_t dom[3] = {domain[0] + d0, + domain[1] + d1, + domain[2] + d2}; + // If the domain exists, make its domain id. + conduit::index_t domainId = InvalidDomain; + if((dom[0] >= 0 && dom[0] < domains[0]) && + (dom[1] >= 0 && dom[1] < domains[1]) && + (dom[2] >= 0 && dom[2] < domains[2])) + { + domainId = dom[2] * (domains[0] * domains[1]) + dom[1] * domains[0] + dom[0]; + } + return domainId; +} + +conduit::index_t +Tiler::domainExists(const conduit::index_t delta[3], + const conduit::index_t domain[3], + const conduit::index_t domains[3]) const +{ + return domainExists(delta[0], delta[1], delta[2], domain, domains); +} + +void +Tiler::addValues(const std::vector &ptids, conduit::index_t planeOffset, bool reorder, const std::vector &old2NewPoint, conduit::Node &values) const +{ + if(reorder) + { + if(planeOffset > 0) + { + values.set(conduit::DataType::index_t(ptids.size()); + conduit::index_t *dest = values.as_index_t_ptr(); + for(size_t i = 0; i < ptids.size(); i++) + *dest++ = old2NewPoint[ptids[i] + planeOffset]; + } + else + { + values.set(conduit::DataType::index_t(ptids.size()); + conduit::index_t *dest = values.as_index_t_ptr(); + for(size_t i = 0; i < ptids.size(); i++) + *dest++ = old2NewPoint[ptids[i]]; + } + } + else if(planeOffset > 0) + { + values.set(conduit::DataType::index_t(ptids.size()); + conduit::index_t *dest = values.as_index_t_ptr(); + for(size_t i = 0; i < ptids.size(); i++) + *dest++ = ptids[i] + planeOffset; + } + else + { + values.set(ptids); + } +} + + // Make corner neighbors. + const conduit::index_t cornerNeighbors[][3] = {{-1, -1, -1}, + {1, -1, -1}, + {-1, 1, -1}, + {1, 1, -1}, + {-1, -1, 1}, + {1, -1, 1}, + {-1, 1, 1}, + {1, 1, 1}}; + for(int ni = 0; ni < 8; ni++) + { + conduit::index_t neighborId; + if((neighborId = domainExists(cornerNeighbors[i], domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + group["values"] = reorder ? old2NewPoint[corner[0]] : corner[0]; + } + } + + // Back, left edge. + if(neighborId = domainExists(-1, 0, -1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(left, 0, reorder, old2NewPoint, group["values"]); + } + // Back, right edge. + if(neighborId = domainExists(1, 0, -1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(right, 0, reorder, old2NewPoint, group["values"]); + } + // Back, bottom edge. + if(neighborId = domainExists(0, -1, -1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(bottom, 0, reorder, old2NewPoint, group["values"]); + } + // Back, top edge. + if(neighborId = domainExists(0, -1, -1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(bottom, 0, reorder, old2NewPoint, group["values"]); + } + // Front, left edge. + if(neighborId = domainExists(-1, 0, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(left, frontPlane, reorder, old2NewPoint, group["values"]); + } + // Front, right edge. + if(neighborId = domainExists(1, 0, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(right, frontPlane, reorder, old2NewPoint, group["values"]); + } + // Front, bottom edge. + if(neighborId = domainExists(0, -1, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(bottom, frontPlane, reorder, old2NewPoint, group["values"]); + } + // Front, top edge. + if(neighborId = domainExists(0, -1, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addValues(bottom, frontPlane, reorder, old2NewPoint, group["values"]); + } + + +#endif // Make a state node. out["state/domain_id"] = thisDom; + std::set unique; + std::vector ptvec; + auto addPoints = [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + { + for(conduit::index_t i = 0; i < npts; i++) + { + const auto id = ids[i]; + if(unique.find(id) == unique.end()) + { + unique.insert(id); + ptvec.push_back(id); + } + } + }; + auto addPointsReorder = [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + { + for(conduit::index_t i = 0; i < npts; i++) + { + const auto id = old2NewPoint[ids[i]]; // Reorder + if(unique.find(id) == unique.end()) + { + unique.insert(id); + ptvec.push_back(id); + } + } + }; int maxNeighbors = (nz < 1) ? 4 : 6; for(int ni = 0; ni < maxNeighbors; ni++) { @@ -1219,51 +1503,32 @@ Tiler::addAdjset(const std::vector &tiles, // Iterate over faces and come up with unique points. bool flags[6] = {false, false, false, false, false, false}; flags[ni] = true; - std::set unique; + unique.clear(); + ptvec.clear(); if(nz < 1) { // 2D if(reorder) - { - iterateBoundary2D(tiles, nx, ny, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - for(conduit::index_t i = 0; i < npts; i++) - unique.insert(old2NewPoint[ids[i]]); // Renumber - }); - } + iterateBoundary2D(tiles, nx, ny, flags, addPointsReorder); else - { - iterateBoundary2D(tiles, nx, ny, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - for(conduit::index_t i = 0; i < npts; i++) - unique.insert(ids[i]); - }); - } + iterateBoundary2D(tiles, nx, ny, flags, addPoints); } else { if(reorder) - { - iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - for(conduit::index_t i = 0; i < npts; i++) - unique.insert(old2NewPoint[ids[i]]); // Renumber - }); - } + iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, addPointsReorder); else - { - iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - for(conduit::index_t i = 0; i < npts; i++) - unique.insert(ids[i]); - }); - } + iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, addPoints); } - +#if 1 + if(!ptvec.empty()) + { + auto name = adjset_name(thisDom, neighbor[ni]); + conduit::Node &group = groups[name]; + group["neighbors"] = neighbor[ni]; + group["values"].set(ptvec); + } +#else if(!unique.empty()) { auto name = adjset_name(thisDom, neighbor[ni]); @@ -1277,6 +1542,7 @@ Tiler::addAdjset(const std::vector &tiles, for(const auto &ptid : unique) *out_ptr++ = ptid; } +#endif } // end for } } From 5bc4bf18f4d3ba435d0c360b4d1d86a79001ea54 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 14:07:36 -0700 Subject: [PATCH 40/50] Undo to_pairwise changes. --- src/libs/blueprint/conduit_blueprint_mesh.cpp | 53 +++++++++---------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh.cpp b/src/libs/blueprint/conduit_blueprint_mesh.cpp index a4fb8b0b1..c52a4518e 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh.cpp @@ -6894,44 +6894,41 @@ mesh::adjset::to_pairwise(const Node &adjset, std::vector adjset_group_names = adjset["groups"].child_names(); std::sort(adjset_group_names.begin(), adjset_group_names.end()); - // Determine the max number of neighbors. - int maxNeighbors = 1; - for(const std::string &group_name : adjset_group_names) - { - const Node &group_node = adjset["groups"][group_name]; - const Node &neighbors_node = group_node["neighbors"]; - maxNeighbors = std::max(maxNeighbors, static_cast(neighbors_node.dtype().number_of_elements())); - } - // Compile ordered lists for each neighbor containing their unique lists // of 'adjset' entity indices, as compiled from all groups in the source 'adjset'. - // We append the values in order according to the length of the neighbor lists, - // with shorter lists going first. std::map> pair_values_map; - for(int nlen = 1; nlen <= maxNeighbors; nlen++) + for(const std::string &group_name : adjset_group_names) { - for(const std::string &group_name : adjset_group_names) + const Node &group_node = adjset["groups"][group_name]; + + std::vector group_neighbors; { - const Node &group_node = adjset["groups"][group_name]; - const Node &neighbors_node = group_node["neighbors"]; - const auto thisNeighborLen = neighbors_node.dtype().number_of_elements(); - if(neighbors_node.dtype().number_of_elements() != nlen) - continue; + const Node &group_nvals = group_node["neighbors"]; + for(index_t ni = 0; ni < group_nvals.dtype().number_of_elements(); ++ni) + { + Node temp(DataType(group_nvals.dtype().id(), 1), + (void*)group_nvals.element_ptr(ni), true); + group_neighbors.push_back(temp.to_index_t()); + } + } + std::vector group_values; + { const Node &group_vals = group_node["values"]; - const auto group_neighbors = neighbors_node.as_index_t_accessor(); - const auto group_values = group_vals.as_index_t_accessor(); - - for(index_t ni = 0; ni < thisNeighborLen; ni++) + for(index_t vi = 0; vi < group_vals.dtype().number_of_elements(); ++vi) { - const index_t neighbor_id = group_neighbors[ni]; - std::vector &neighbor_values = pair_values_map[neighbor_id]; - auto numValues = group_values.number_of_elements(); - neighbor_values.reserve(neighbor_values.size() + numValues); - for(index_t vi = 0; vi < numValues; vi++) - neighbor_values.push_back(group_values[vi]); + Node temp(DataType(group_vals.dtype().id(), 1), + (void*)group_vals.element_ptr(vi), true); + group_values.push_back(temp.to_index_t()); } } + + for(const index_t &neighbor_id : group_neighbors) + { + std::vector &neighbor_values = pair_values_map[neighbor_id]; + neighbor_values.insert(neighbor_values.end(), + group_values.begin(), group_values.end()); + } } // Given ordered lists of adjset values per neighbor, generate the destination From 9bd4a0dc843a7a4a89682165181d33ea950de434 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 14:11:57 -0700 Subject: [PATCH 41/50] Changed how 3D adjsets work in tiled mesh generator. --- .../conduit_blueprint_mesh_examples_tiled.cpp | 598 +++++++++--------- 1 file changed, 288 insertions(+), 310 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 985bdfb3c..0ccb6310e 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -28,6 +28,9 @@ // do reordering. // #define CONDUIT_USE_PARTITIONER_FOR_REORDER +// Uncomment this to use a simpler tiled pattern for debugging. +// #define CONDUIT_SIMPLE_TILED_PATTERN + //----------------------------------------------------------------------------- // -- begin conduit::-- //----------------------------------------------------------------------------- @@ -112,12 +115,14 @@ const conduit::index_t Tile::INVALID_POINT = -1; class Tiler { public: - static constexpr int BoundaryLeft = 0; - static constexpr int BoundaryRight = 1; - static constexpr int BoundaryBottom = 2; - static constexpr int BoundaryTop = 3; - static constexpr int BoundaryBack = 4; - static constexpr int BoundaryFront = 5; + static const int BoundaryLeft = 0; + static const int BoundaryRight = 1; + static const int BoundaryBottom = 2; + static const int BoundaryTop = 3; + static const int BoundaryBack = 4; + static const int BoundaryFront = 5; + + static const conduit::index_t InvalidDomain = -1; Tiler(); @@ -382,6 +387,23 @@ class Tiler const bool flags[6], Body &&body) const; + /// Compute domain id, if it exists, or return InvalidDomain. + conduit::index_t domainIndex(conduit::index_t d0, conduit::index_t d1, conduit::index_t d2, + const std::vector &domain, + const std::vector &domains) const; + + /// Compute domain id, if it exists, or return InvalidDomain. + conduit::index_t domainIndex(const conduit::index_t delta[3], + const std::vector &domain, + const std::vector &domains) const; + + /// Add ptids to a values node for an adjset, possibly transforming the point ids. + void addAdjsetValues(const std::vector &ptids, + conduit::index_t planeOffset, + bool reorder, + const std::vector &old2NewPoint, + conduit::Node &values) const; + /// Add adjacency set void addAdjset(const std::vector &tiles, conduit::index_t nx, @@ -411,6 +433,17 @@ Tiler::Tiler() : m_options(), m_width(0.), m_height(0.), void Tiler::initialize() { +#ifdef CONDUIT_SIMPLE_TILED_PATTERN + // Simpler case for debugging. + const double x[] = {0., 1., 0., 1.}; + const double y[] = {0., 0., 1., 1.}; + const conduit::index_t conn[] = {0,1,3,2}; + + const conduit::index_t left[] = {0,2}; + const conduit::index_t right[] = {1,3}; + const conduit::index_t bottom[] = {0,1}; + const conduit::index_t top[] = {2,3}; +#else // Default pattern const double x[] = { 0., 3., 10., 17., 20., @@ -467,6 +500,12 @@ Tiler::initialize() 26,27,32,31 }; + const conduit::index_t left[] = {0,5,14,24,28}; + const conduit::index_t right[] = {4,8,18,27,32}; + const conduit::index_t bottom[] = {0,1,2,3,4}; + const conduit::index_t top[] = {28,29,30,31,32}; +#endif + // Define the tile as a topology. conduit::Node opts; opts["coordsets/coords/type"] = "explicit"; @@ -481,10 +520,6 @@ Tiler::initialize() opts["topologies/tile/elements/sizes"].set(size.data(), size.size()); // Define tile boundary indices. - const conduit::index_t left[] = {0,5,14,24,28}; - const conduit::index_t right[] = {4,8,18,27,32}; - const conduit::index_t bottom[] = {0,1,2,3,4}; - const conduit::index_t top[] = {28,29,30,31,32}; opts["left"].set(left, sizeof(left) / sizeof(conduit::index_t)); opts["right"].set(right, sizeof(right) / sizeof(conduit::index_t)); opts["bottom"].set(bottom, sizeof(bottom) / sizeof(conduit::index_t)); @@ -1157,25 +1192,68 @@ Tiler::iterateBoundary3D(const std::vector &tiles, } } -#if 0 //--------------------------------------------------------------------------- -template -void -Tiler::iterateDomainEdges(const std::vector &tiles, - conduit::index_t nx, - conduit::index_t ny, - conduit::index_t nz, - conduit::index_t nPtsPerPlane, - conduit::index_t dom[3], - conduit::index_t domains[3], - Body &&body) const +conduit::index_t +Tiler::domainIndex(conduit::index_t d0, conduit::index_t d1, conduit::index_t d2, + const std::vector &domain, + const std::vector &domains) const { - conduit::index_t idlist[4]; + const conduit::index_t dom[3] = {domain[0] + d0, domain[1] + d1, domain[2] + d2}; + // If the domain exists, make its domain id. + conduit::index_t domainId = InvalidDomain; + if((dom[0] >= 0 && dom[0] < domains[0]) && + (dom[1] >= 0 && dom[1] < domains[1]) && + (dom[2] >= 0 && dom[2] < domains[2])) + { + domainId = dom[2] * (domains[0] * domains[1]) + dom[1] * domains[0] + dom[0]; + } + return domainId; +} - if(domainExists(domain[0], +//--------------------------------------------------------------------------- +conduit::index_t +Tiler::domainIndex(const conduit::index_t delta[3], + const std::vector &domain, + const std::vector &domains) const +{ + return domainIndex(delta[0], delta[1], delta[2], domain, domains); +} +//--------------------------------------------------------------------------- +void +Tiler::addAdjsetValues(const std::vector &ptids, + conduit::index_t planeOffset, + bool reorder, + const std::vector &old2NewPoint, + conduit::Node &values) const +{ + if(reorder) + { + values.set(conduit::DataType::index_t(ptids.size())); + conduit::index_t *dest = values.as_index_t_ptr(); + if(planeOffset > 0) + { + for(size_t i = 0; i < ptids.size(); i++) + *dest++ = old2NewPoint[ptids[i] + planeOffset]; + } + else + { + for(size_t i = 0; i < ptids.size(); i++) + *dest++ = old2NewPoint[ptids[i]]; + } + } + else if(planeOffset > 0) + { + values.set(conduit::DataType::index_t(ptids.size())); + conduit::index_t *dest = values.as_index_t_ptr(); + for(size_t i = 0; i < ptids.size(); i++) + *dest++ = ptids[i] + planeOffset; + } + else + { + values.set(ptids); + } } -#endif //--------------------------------------------------------------------------- void @@ -1198,115 +1276,67 @@ Tiler::addAdjset(const std::vector &tiles, return ss.str(); }; + // Build up unique points, given a list of points. Use unique and ptvec by capture. + std::set unique; + std::vector ptvec; + auto addPoints = [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + { + for(conduit::index_t i = 0; i < npts; i++) + { + const auto id = ids[i]; + if(unique.find(id) == unique.end()) + { + unique.insert(id); + ptvec.push_back(id); + } + } + }; + // We need to know where this domain is in the domains to make the adjset. if(options.has_child("domain") && options.has_child("domains")) { - auto domain = options.fetch_existing("domain").as_index_t_accessor(); - auto domains = options.fetch_existing("domains").as_index_t_accessor(); - if(domain.number_of_elements() == 3 && - domain.number_of_elements() == domains.number_of_elements()) + auto domain = toIndexVector(options.fetch_existing("domain")); + auto domains = toIndexVector(options.fetch_existing("domains")); + if(domain.size() == 3 && domain.size() == domains.size()) { if(domains[0] * domains[1] * domains[2] > 1) { - auto dnxny = domains[0] * domains[1]; - auto dnx = domains[0]; -#define DOMAIN_INDEX(I,J,K) ((domain[2] + (K)) * dnxny + (domain[1] + (J)) * dnx + (domain[0] + (I))) - auto thisDom = DOMAIN_INDEX(0, 0, 0); + auto thisDom = domainIndex(0, 0, 0, domain, domains); + // Make a state node. + out["state/domain_id"] = thisDom; + + // Make the top level adjset nodes. conduit::Node &adjset = out["adjsets/" + meshName + "_adjset"]; adjset["association"] = "vertex"; adjset["topology"] = meshName; conduit::Node &groups = adjset["groups"]; - // Neighbor domain indices. - conduit::index_t neighbor[6]; - neighbor[BoundaryLeft] = (domain[0] - 1 >= 0) ? DOMAIN_INDEX(-1, 0, 0) : -1; - neighbor[BoundaryRight] = (domain[0] + 1 < domains[0]) ? DOMAIN_INDEX(1, 0, 0) : -1; - neighbor[BoundaryBottom] = (domain[1] - 1 >= 0) ? DOMAIN_INDEX(0, -1, 0) : -1; - neighbor[BoundaryTop] = (domain[1] + 1 < domains[1]) ? DOMAIN_INDEX(0, 1, 0) : -1; - neighbor[BoundaryBack] = (domain[2] - 1 >= 0) ? DOMAIN_INDEX(0, 0, -1) : -1; - neighbor[BoundaryFront] = (domain[2] + 1 < domains[2]) ? DOMAIN_INDEX(0, 0, 1) : -1; -#undef DOMAIN_INDEX - -#if 0 - if(nz < 1) - { - } - else - { - const conduit::index_t cornerNeighbors[][3] = {{-1, -1, -1}, - {1, -1, -1}, - {-1, 1, -1}, - {1, 1, -1}, - {-1, -1, 1}, - {1, -1, 1}, - {-1, 1, 1}, - {1, 1, 1}}; - for(int ni = 0; ni < 8; ni++) - { - conduit::index_t ndom[3] = {domain[0] + cornerNeighbors[ni][0], - domain[1] + cornerNeighbors[ni][1], - domain[2] + cornerNeighbors[ni][2]}; - // If the neighbor domain exists, - if((ndom[0] >= 0 && ndom[0] < domains[0]) && - (ndom[1] >= 0 && ndom[1] < domains[1]) && - (ndom[2] >= 0 && ndom[2] < domains[2])) - { - conduit::index_t ti = (cornerNeighbors[ni][0] == -1) ? 0 : (nx - 1); - conduit::index_t tj = (cornerNeighbors[ni][1] == -1) ? 0 : (ny - 1); - conduit::index_t planeOffset = (cornerNeighbors[ni][2] == -1) ? 0 : (nz - 1) * ptsPerPlane; - const Tile &tile = tiles[tj * nx + ti]; - - } - } - } - - if(domainExists(-1, -1, -1, domain, domains)) - { - - const Tile &tile = tiles[tj * nx + ti]; - } - // Trace around the tile to get the points for the edges of the tile - // in original node ordering. + // in original node order. + //----------------------------------------------------------- std::vector left, right, bottom, top; const bool left_flags[] = {true, false, false, false}; - iterateBoundary2D(tiles, nx, ny, left_flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - for(conduit::index_t i = 0; i < npts; i++) -// TODO: add unique tests because we get passed the start/end of each line segment. -// Maybe pass a "lastSegment" flag to the lambda so we can keep just the first point most of the time. -// Also, why pass npts when it is always 2? - left.push_back(ids[i]); - }); + iterateBoundary2D(tiles, nx, ny, left_flags, addPoints); + left.swap(ptvec); // Steal the points. + unique.clear(); const bool right_flags[] = {false, true, false, false}; - iterateBoundary2D(tiles, nx, ny, right_flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - right.reserve(npts); - for(conduit::index_t i = 0; i < npts; i++) - right.push_back(ids[i]); - }); + iterateBoundary2D(tiles, nx, ny, right_flags, addPoints); + right.swap(ptvec); // Steal the points. + unique.clear(); const bool bottom_flags[] = {false, false, true, false}; - iterateBoundary2D(tiles, nx, ny, right_flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - bottom.reserve(npts); - for(conduit::index_t i = 0; i < npts; i++) - bottom.push_back(ids[i]); - }); + iterateBoundary2D(tiles, nx, ny, bottom_flags, addPoints); + bottom.swap(ptvec); // Steal the points. + unique.clear(); const bool top_flags[] = {false, false, false, true}; - iterateBoundary2D(tiles, nx, ny, right_flags, - [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - top.reserve(npts); - for(conduit::index_t i = 0; i < npts; i++) - top.push_back(ids[i]); - }); + iterateBoundary2D(tiles, nx, ny, top_flags, addPoints); + top.swap(ptvec); // Steal the points. + unique.clear(); - conduit::index_t frontPlane = ptsPerPlane * (nz - 1); - conduit::index_t corners[8]; + // Make corner neighbors. + //----------------------------------------------------------- + conduit::index_t frontPlane = ptsPerPlane * nz; + conduit::index_t corner[8]; corner[0] = left[0]; // back, lower left corner[1] = right[0]; // back, lower right corner[2] = left[left.size() - 1]; // back, upper left @@ -1315,189 +1345,163 @@ Tiler::addAdjset(const std::vector &tiles, corner[5] = right[0] + frontPlane; // front, lower right corner[6] = left[left.size() - 1] + frontPlane; // front, upper left corner[7] = right[right.size() - 1] + frontPlane; // front, upper right - -conduit::index_t -Tiler::domainExists(conduit::index_t d0, conduit::index_t d1, conduit::index_t d2, - const conduit::index_t domain[3], - const conduit::index_t domains[3]) const -{ - const conduit::index_t dom[3] = {domain[0] + d0, - domain[1] + d1, - domain[2] + d2}; - // If the domain exists, make its domain id. - conduit::index_t domainId = InvalidDomain; - if((dom[0] >= 0 && dom[0] < domains[0]) && - (dom[1] >= 0 && dom[1] < domains[1]) && - (dom[2] >= 0 && dom[2] < domains[2])) - { - domainId = dom[2] * (domains[0] * domains[1]) + dom[1] * domains[0] + dom[0]; - } - return domainId; -} - -conduit::index_t -Tiler::domainExists(const conduit::index_t delta[3], - const conduit::index_t domain[3], - const conduit::index_t domains[3]) const -{ - return domainExists(delta[0], delta[1], delta[2], domain, domains); -} - -void -Tiler::addValues(const std::vector &ptids, conduit::index_t planeOffset, bool reorder, const std::vector &old2NewPoint, conduit::Node &values) const -{ - if(reorder) - { - if(planeOffset > 0) - { - values.set(conduit::DataType::index_t(ptids.size()); - conduit::index_t *dest = values.as_index_t_ptr(); - for(size_t i = 0; i < ptids.size(); i++) - *dest++ = old2NewPoint[ptids[i] + planeOffset]; - } - else - { - values.set(conduit::DataType::index_t(ptids.size()); - conduit::index_t *dest = values.as_index_t_ptr(); - for(size_t i = 0; i < ptids.size(); i++) - *dest++ = old2NewPoint[ptids[i]]; - } - } - else if(planeOffset > 0) - { - values.set(conduit::DataType::index_t(ptids.size()); - conduit::index_t *dest = values.as_index_t_ptr(); - for(size_t i = 0; i < ptids.size(); i++) - *dest++ = ptids[i] + planeOffset; - } - else - { - values.set(ptids); - } -} - - // Make corner neighbors. - const conduit::index_t cornerNeighbors[][3] = {{-1, -1, -1}, - {1, -1, -1}, - {-1, 1, -1}, - {1, 1, -1}, - {-1, -1, 1}, - {1, -1, 1}, - {-1, 1, 1}, - {1, 1, 1}}; - for(int ni = 0; ni < 8; ni++) + int maxCornerNeighbors = (nz < 1) ? 4 : 8; + conduit::index_t z0 = (nz < 1) ? 0 : -1; + conduit::index_t z1 = (nz < 1) ? 0 : 1; + conduit::index_t neighborId = InvalidDomain; + const conduit::index_t cornerNeighbors[][3] = { + {-1, -1, z0}, + {1, -1, z0}, + {-1, 1, z0}, + {1, 1, z0}, + {-1, -1, z1}, + {1, -1, z1}, + {-1, 1, z1}, + {1, 1, z1} + }; + for(int ni = 0; ni < maxCornerNeighbors; ni++) { - conduit::index_t neighborId; - if((neighborId = domainExists(cornerNeighbors[i], domain, domains)) != InvalidDomain) + if((neighborId = domainIndex(cornerNeighbors[ni], domain, domains)) != InvalidDomain) { auto name = adjset_name(thisDom, neighborId); conduit::Node &group = groups[name]; group["neighbors"] = neighborId; - group["values"] = reorder ? old2NewPoint[corner[0]] : corner[0]; + group["values"] = reorder ? old2NewPoint[corner[ni]] : corner[ni]; } } - // Back, left edge. - if(neighborId = domainExists(-1, 0, -1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(left, 0, reorder, old2NewPoint, group["values"]); - } - // Back, right edge. - if(neighborId = domainExists(1, 0, -1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(right, 0, reorder, old2NewPoint, group["values"]); - } - // Back, bottom edge. - if(neighborId = domainExists(0, -1, -1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(bottom, 0, reorder, old2NewPoint, group["values"]); - } - // Back, top edge. - if(neighborId = domainExists(0, -1, -1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(bottom, 0, reorder, old2NewPoint, group["values"]); - } - // Front, left edge. - if(neighborId = domainExists(-1, 0, 1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(left, frontPlane, reorder, old2NewPoint, group["values"]); - } - // Front, right edge. - if(neighborId = domainExists(1, 0, 1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(right, frontPlane, reorder, old2NewPoint, group["values"]); - } - // Front, bottom edge. - if(neighborId = domainExists(0, -1, 1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(bottom, frontPlane, reorder, old2NewPoint, group["values"]); - } - // Front, top edge. - if(neighborId = domainExists(0, -1, 1, domain, domains)) != InvalidDomain) - { - auto name = adjset_name(thisDom, neighborId); - conduit::Node &group = groups[name]; - group["neighbors"] = neighborId; - addValues(bottom, frontPlane, reorder, old2NewPoint, group["values"]); - } - - -#endif - // Make a state node. - out["state/domain_id"] = thisDom; - - std::set unique; - std::vector ptvec; - auto addPoints = [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) + // Make edge neighbors for 3D + //----------------------------------------------------------- + if(nz > 0) { - for(conduit::index_t i = 0; i < npts; i++) + // Back, left edge. + if((neighborId = domainIndex(-1, 0, -1, domain, domains)) != InvalidDomain) { - const auto id = ids[i]; - if(unique.find(id) == unique.end()) - { - unique.insert(id); - ptvec.push_back(id); - } + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(left, 0, reorder, old2NewPoint, group["values"]); } - }; - auto addPointsReorder = [&](const conduit::index_t *ids, conduit::index_t npts, int /*bnd*/) - { - for(conduit::index_t i = 0; i < npts; i++) + // Back, right edge. + if((neighborId = domainIndex(1, 0, -1, domain, domains)) != InvalidDomain) { - const auto id = old2NewPoint[ids[i]]; // Reorder - if(unique.find(id) == unique.end()) - { - unique.insert(id); - ptvec.push_back(id); - } + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(right, 0, reorder, old2NewPoint, group["values"]); } - }; + // Back, bottom edge. + if((neighborId = domainIndex(0, -1, -1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(bottom, 0, reorder, old2NewPoint, group["values"]); + } + // Back, top edge. + if((neighborId = domainIndex(0, 1, -1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(top, 0, reorder, old2NewPoint, group["values"]); + } + // Lower left edge + if((neighborId = domainIndex(-1, -1, 0, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + std::vector values; + values.reserve(nz); + for(conduit::index_t zi = 0; zi <= nz; zi++) + values.push_back(corner[0] + zi * ptsPerPlane); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(values, 0, reorder, old2NewPoint, group["values"]); + } + // Lower right edge + if((neighborId = domainIndex(1, -1, 0, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + std::vector values; + values.reserve(nz); + for(conduit::index_t zi = 0; zi <= nz; zi++) + values.push_back(corner[1] + zi * ptsPerPlane); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(values, 0, reorder, old2NewPoint, group["values"]); + } + // Upper left edge + if((neighborId = domainIndex(-1, 1, 0, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + std::vector values; + values.reserve(nz); + for(conduit::index_t zi = 0; zi <= nz; zi++) + values.push_back(corner[2] + zi * ptsPerPlane); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(values, 0, reorder, old2NewPoint, group["values"]); + } + // Upper right edge + if((neighborId = domainIndex(1, 1, 0, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + std::vector values; + values.reserve(nz); + for(conduit::index_t zi = 0; zi <= nz; zi++) + values.push_back(corner[3] + zi * ptsPerPlane); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(values, 0, reorder, old2NewPoint, group["values"]); + } + // Front, left edge. + if((neighborId = domainIndex(-1, 0, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(left, frontPlane, reorder, old2NewPoint, group["values"]); + } + // Front, right edge. + if((neighborId = domainIndex(1, 0, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(right, frontPlane, reorder, old2NewPoint, group["values"]); + } + // Front, bottom edge. + if((neighborId = domainIndex(0, -1, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(bottom, frontPlane, reorder, old2NewPoint, group["values"]); + } + // Front, top edge. + if((neighborId = domainIndex(0, 1, 1, domain, domains)) != InvalidDomain) + { + auto name = adjset_name(thisDom, neighborId); + conduit::Node &group = groups[name]; + group["neighbors"] = neighborId; + addAdjsetValues(top, frontPlane, reorder, old2NewPoint, group["values"]); + } + } + + // Make "face" neighbors. + //----------------------------------------------------------- + conduit::index_t neighbor[6]; + neighbor[BoundaryLeft] = domainIndex(-1, 0, 0, domain, domains); + neighbor[BoundaryRight] = domainIndex(1, 0, 0, domain, domains); + neighbor[BoundaryBottom] = domainIndex(0, -1, 0, domain, domains); + neighbor[BoundaryTop] = domainIndex(0, 1, 0, domain, domains); + neighbor[BoundaryBack] = domainIndex(0, 0, -1, domain, domains); + neighbor[BoundaryFront] = domainIndex(0, 0, 1, domain, domains); int maxNeighbors = (nz < 1) ? 4 : 6; for(int ni = 0; ni < maxNeighbors; ni++) { // If this domain has no neighbor in the current direction, skip. - if(neighbor[ni] == -1) + if(neighbor[ni] == InvalidDomain) continue; // Iterate over faces and come up with unique points. @@ -1506,43 +1510,17 @@ Tiler::addValues(const std::vector &ptids, conduit::index_t pl unique.clear(); ptvec.clear(); if(nz < 1) - { - // 2D - if(reorder) - iterateBoundary2D(tiles, nx, ny, flags, addPointsReorder); - else - iterateBoundary2D(tiles, nx, ny, flags, addPoints); - } + iterateBoundary2D(tiles, nx, ny, flags, addPoints); else - { - if(reorder) - iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, addPointsReorder); - else - iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, addPoints); - } -#if 1 + iterateBoundary3D(tiles, nx, ny, nz, ptsPerPlane, flags, addPoints); + if(!ptvec.empty()) { auto name = adjset_name(thisDom, neighbor[ni]); conduit::Node &group = groups[name]; group["neighbors"] = neighbor[ni]; - group["values"].set(ptvec); + addAdjsetValues(ptvec, 0, reorder, old2NewPoint, group["values"]); } -#else - if(!unique.empty()) - { - auto name = adjset_name(thisDom, neighbor[ni]); - conduit::Node &group = groups[name]; - group["neighbors"] = neighbor[ni]; - - // Store the results into a node. - conduit::Node &values = group["values"]; - values.set(conduit::DataType::index_t(unique.size())); - conduit::index_t *out_ptr = values.as_index_t_ptr(); - for(const auto &ptid : unique) - *out_ptr++ = ptid; - } -#endif } // end for } } From 7718f6694a5bc2ea5ed515d16ff4e1081f5c4930 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 14:12:26 -0700 Subject: [PATCH 42/50] Added a test for 3D meshes that had failed before. --- src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp index a8308cee4..d1cc06416 100644 --- a/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp +++ b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp @@ -31,7 +31,7 @@ using namespace conduit::utils; using namespace generate; // Uncomment if we want to write the data files. -// #define CONDUIT_WRITE_TEST_DATA +//#define CONDUIT_WRITE_TEST_DATA //--------------------------------------------------------------------------- #ifdef CONDUIT_WRITE_TEST_DATA @@ -220,7 +220,6 @@ test_tiled_adjsets(const int dims[3], const std::string &testName) }); } -#if 1 //----------------------------------------------------------------------------- TEST(conduit_blueprint_mpi_mesh_tiled, two_dimensional) { @@ -234,8 +233,7 @@ TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional) const int dims[]= {2,2,2}; test_tiled_adjsets(dims, "three_dimensional"); } -#endif -#if 0 + //----------------------------------------------------------------------------- TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional_12) { @@ -271,10 +269,9 @@ TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional_12) #ifdef CONDUIT_WRITE_TEST_DATA // Save the mesh. + const std::string testName("three_dimensional_12"); std::stringstream ss; ss << "_r" << r; - for(const auto &value : domainNumbering) - ss << "_" << value; std::string filebase(testName + ss.str()); save_mesh(mesh, filebase); #endif @@ -284,7 +281,7 @@ TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional_12) EXPECT_TRUE(same); } } -#endif + //----------------------------------------------------------------------------- int main(int argc, char* argv[]) { From 512ff374fb5a1daf5fa38cd866a0ca85e406ba22 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 17:02:38 -0700 Subject: [PATCH 43/50] Added some doc for the tiled function. --- CHANGELOG.md | 7 +++ src/docs/sphinx/blueprint_mesh.rst | 83 +++++++++++++++++++++++++++++ src/docs/sphinx/index.rst | 2 +- src/docs/sphinx/tiled.png | Bin 0 -> 345322 bytes src/docs/sphinx/tiled_single.png | Bin 0 -> 146297 bytes 5 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 src/docs/sphinx/tiled.png create mode 100644 src/docs/sphinx/tiled_single.png diff --git a/CHANGELOG.md b/CHANGELOG.md index 2374ba4f2..f7181ddbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s #### Relay - Added ability to read N-dimensional hyperslabs from HDF5 leaf arrays into linear memory arrays. +#### Blueprint +- Added a `conduit::blueprint::mesh::examples::tiled()` function that can generate meshes by repeating a tiled pattern. +- Added a `conduit::blueprint::mpi::mesh::utils::adjset::compare_pointwise()` function that can compare adjsets for multi-domain meshes in parallel. The function is used to diagnose adjsets with points that are out of order on either side of the boundary. The comparison is done point by point within each group and it checks to ensure that the points reference the same spatial location. + +#### Executables +- Added a `conduit_generate_data` executable that can generate datasets using the `tiled()` and `braid()` functions and save the datasets to files. + ### Fixed #### Blueprint diff --git a/src/docs/sphinx/blueprint_mesh.rst b/src/docs/sphinx/blueprint_mesh.rst index 9324fe0b5..e3aef9f46 100644 --- a/src/docs/sphinx/blueprint_mesh.rst +++ b/src/docs/sphinx/blueprint_mesh.rst @@ -1687,6 +1687,89 @@ equal to half the number of prisms. The resulting data is placed the Node ``res``, which is passed in via reference. + +tiled ++++++++++ + +The ``tiled()`` function repeats a tile (given as a 2D Blueprint topology) into a larger mesh composed of +a regular square tiling of the supplied tile. If no topology is given, a default pattern consisting of quads +is used. The output mesh can be either 2D or 3D. For 3D, supply a ``nz`` parameter greater than zero. Note +that the input tile must consist of a homogeneous set of triangles or quads to extrude the tile into 3D since +polyhedral output is not yet supported. The ``tiled()`` function produces a single domain comprised of a +main mesh, a boundary mesh, and adjacency sets of the output mesh is to be part of a multi-domain dataset. + +.. code:: cpp + + conduit::blueprint::mesh::examples::tiled(index_t nx, // number of tiles along x + index_t ny, // number of tiles along y + index_t nz, // number of elements along z (0 for 2D) + conduit::Node &res, // result container + const conduit::Node &options);// options node + +The ``tiled()`` function accepts a Conduit node containing options that influence how the mesh is generated. +If the node contains a ``tile`` that contains a 2D blueprint topology, then the first supplied topology will +be used to override the default tile pattern. A ``reorder`` flag indicates whether the mesh's +points and elements will be reordered to be more cache-friendly. The default is to reorder points and elements. +The name of the mesh can be given by passing ``meshname`` option string. Likewise, the name of the boundary +mesh can be supplied using the ``boundarymeshname`` option. The optional ``translate/x`` and ``translate/y`` +options determing the tile spacing. If the translation values are not given, they will be determined from +the coordset extents. The output mesh topology will store its integer connectivity information as index_t +by default. The precision of the integer output can turned to int32 by passing a ``datatype`` option containing +the "int", "int32", "integer" strings. + +An important set of options define the left, right, bottom, top sets of points within the supplied tile +pattern. The values in the ``left`` option identify the list of points that define the left edge of the tile. +These are indices into the coordset and the values should be in consecutive order along the edge. Opposite point +sets must match. In other words, the left and right point sets must contain the same number of points and +they need to proceed along their edges in the same order. The same is true of the bottom and top point sets. + +The ``tiled()`` function options also support options that simplify the task of using ``tiled()`` to generate +mesh domains for a multi-domain dataset. The coordinate extents of the current mesh domain are given using +the ``extents`` option, which contains 6 double values: {xmin, xmax, ymin, ymax, zmin, zmax}. The ``domains`` +option contains a triple of {domainsI, domainsJ, domainsK} values that indicate how many divisions there are +of the extents in the I,J,K dimensions. The ``domain`` option specifies a triple indicating the I,J,K domain +id within the overall set of domains. This is used to help construct adjacency sets. + +.. code:: yaml + + # Define the tile topology + tile: + coordsets: + coords: + type: explicit + values: + x: [0., 1., 2., 0., 1., 2., 0., 1., 2.] + y: [0., 0.5, 0., 1., 1.5, 1., 2., 2.5, 2.] + topologies: + tile: + type: unstructured + coordset: coords + elements: + shape: tri + connectivity: [0,1,4, 0,4,3, 1,2,5, 1,5,4, 3,4,7, 3,7,6, 4,5,8, 4,8,7] + # Set some options that aid tiling. + reorder: 1 + left: [0,3,6] + right: [2,5,8] + bottom: [0,1,2] + top: [6,7,8] + translate: + x: 2. + y: 2. + +.. figure:: tiled_single.png + :width: 400px + :align: center + + Pseudocolor plot of zoneid for default tile mesh that has been reordered. + +.. figure:: tiled.png + :width: 600px + :align: center + + Subset plots of multi-domain datasets created using the ``tiled()`` function. + + miscellaneous ++++++++++++++ diff --git a/src/docs/sphinx/index.rst b/src/docs/sphinx/index.rst index 49e104037..a931196fb 100644 --- a/src/docs/sphinx/index.rst +++ b/src/docs/sphinx/index.rst @@ -115,7 +115,7 @@ Contributors - Mark Miller (LLNL) - Todd Gamblin (LLNL) - Kevin Huynh (LLNL) -- Brad Whitlock (Intelligent Light) +- Brad Whitlock (LLNL) - Chris Laganella (Intelligent Light) - George Aspesi (Harvey Mudd) - Justin Bai (Harvey Mudd) diff --git a/src/docs/sphinx/tiled.png b/src/docs/sphinx/tiled.png new file mode 100644 index 0000000000000000000000000000000000000000..9229398b4dc1db1f65cd50f215e2e28f5d5c221b GIT binary patch literal 345322 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
  • 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

    x>+ZTt;LI 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

    8S&nMP1 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

    H++ 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%0Aa5-6%A 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 Mw2D;ie`a6)A4_xoAOHXW literal 0 HcmV?d00001 diff --git a/src/docs/sphinx/tiled_single.png b/src/docs/sphinx/tiled_single.png new file mode 100644 index 0000000000000000000000000000000000000000..e008b05def8f2bb0e1b3c01e886c8bc985d58ea5 GIT binary patch literal 146297 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 z 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 literal 0 HcmV?d00001 From e581e5f6b5875e9d6459b4ecdb00eccf03828d33 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 17:33:24 -0700 Subject: [PATCH 44/50] Changed the reorder option for tiled to a string. --- src/docs/sphinx/blueprint_mesh.rst | 5 +++-- .../conduit_blueprint_mesh_examples_tiled.cpp | 2 +- .../blueprint/t_blueprint_mpi_mesh_tiled.cpp | 22 +++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/docs/sphinx/blueprint_mesh.rst b/src/docs/sphinx/blueprint_mesh.rst index e3aef9f46..7733708f0 100644 --- a/src/docs/sphinx/blueprint_mesh.rst +++ b/src/docs/sphinx/blueprint_mesh.rst @@ -1708,8 +1708,9 @@ main mesh, a boundary mesh, and adjacency sets of the output mesh is to be part The ``tiled()`` function accepts a Conduit node containing options that influence how the mesh is generated. If the node contains a ``tile`` that contains a 2D blueprint topology, then the first supplied topology will -be used to override the default tile pattern. A ``reorder`` flag indicates whether the mesh's -points and elements will be reordered to be more cache-friendly. The default is to reorder points and elements. +be used to override the default tile pattern. The ``reorder`` option indicates the type of point and element +reordering that will be done. Reordering can improve cache-friendliness. The default is to reorder points +and elements, using "kdtree" method. Passing "none" or an empty string will prevent reordering. The name of the mesh can be given by passing ``meshname`` option string. Likewise, the name of the boundary mesh can be supplied using the ``boundarymeshname`` option. The optional ``translate/x`` and ``translate/y`` options determing the tile spacing. If the translation values are not given, they will be determined from diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 0ccb6310e..5c2fd343d 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -621,7 +621,7 @@ Tiler::generate(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, bool reorder = true; if(options.has_path("reorder")) - reorder = options.fetch_existing("reorder").to_int() > 0; + reorder = options.fetch_existing("reorder").as_string() == "kdtree"; if(options.has_path("meshname")) meshName = options.fetch_existing("meshname").as_string(); diff --git a/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp index d1cc06416..b1ef81d96 100644 --- a/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp +++ b/src/tests/blueprint/t_blueprint_mpi_mesh_tiled.cpp @@ -54,10 +54,12 @@ void save_mesh(const conduit::Node &root, const std::string &filebase) @param mesh The node that will contain the domains. @param dims The number of zones in each domain. dims[2] == 0 for 2D meshes. @param domains The number of domains to make. All values > 0. + @param reorder The reordering method, if any. @param domainNumbering An optional vector that can reorder the domains. */ void -make_tiled(conduit::Node &mesh, const int dims[3], const int domains[3], bool reorder, const std::vector &domainNumbering) +make_tiled(conduit::Node &mesh, const int dims[3], const int domains[3], + const std::string &reorder, const std::vector &domainNumbering) { const int ndoms = domains[0] * domains[1] * domains[2]; const int par_rank = relay::mpi::rank(MPI_COMM_WORLD); @@ -99,7 +101,7 @@ make_tiled(conduit::Node &mesh, const int dims[3], const int domains[3], bool re opts["extents"].set(domainExt, 6); opts["domain"].set(domain, 3); opts["domains"].set(domains, 3); - opts["reorder"] = reorder ? 1 : 0; + opts["reorder"] = reorder; if(ndoms > 1) { @@ -163,14 +165,12 @@ test_tiled_adjsets(const int dims[3], const std::string &testName) { const int domains[] = {2,2,1}; const int par_rank = relay::mpi::rank(MPI_COMM_WORLD); - - for(int r = 0; r < 2; r++) + const std::vector reorder{"normal", "kdtree"}; + for(const auto &r : reorder) { - bool reorder = r == 1; - // Make the mesh. conduit::Node mesh; - make_tiled(mesh, dims, domains, reorder, domainNumbering); + make_tiled(mesh, dims, domains, r, domainNumbering); // Now, make a corner mesh conduit::Node s2dmap, d2smap; @@ -240,13 +240,13 @@ TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional_12) // This 12 domain case was found to cause adjset problems. const int dims[] = {2,2,2}, domains[] = {3,2,2}; const int par_rank = relay::mpi::rank(MPI_COMM_WORLD); + const std::vector reorder{"normal", "kdtree"}; - for(int r = 0; r < 2; r++) + for(const auto &r : reorder) { // Make the mesh. - bool reorder = r == 1; conduit::Node mesh; - make_tiled(mesh, dims, domains, reorder, std::vector{}); + make_tiled(mesh, dims, domains, r, std::vector{}); // Now, make a corner mesh conduit::Node s2dmap, d2smap; @@ -271,7 +271,7 @@ TEST(conduit_blueprint_mpi_mesh_tiled, three_dimensional_12) // Save the mesh. const std::string testName("three_dimensional_12"); std::stringstream ss; - ss << "_r" << r; + ss << "_r_" << r; std::string filebase(testName + ss.str()); save_mesh(mesh, filebase); #endif From 818b5750d5ff800edc8190890ccbb331b15484bb Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 17:33:44 -0700 Subject: [PATCH 45/50] Upped Conduit version. --- src/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index a24e64517..0f9ea7f70 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,7 @@ endif() # Conduit ################################ -project(conduit VERSION "0.8.8") +project(conduit VERSION "0.8.9") ################################ # Build Options From 444531f8a52953f415c367f10059b579028dd9f0 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 18:07:43 -0700 Subject: [PATCH 46/50] Added some missing items in CHANGELOG.md. --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7181ddbf..00412a48b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,10 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s #### Blueprint - Added a `conduit::blueprint::mesh::examples::tiled()` function that can generate meshes by repeating a tiled pattern. - Added a `conduit::blueprint::mpi::mesh::utils::adjset::compare_pointwise()` function that can compare adjsets for multi-domain meshes in parallel. The function is used to diagnose adjsets with points that are out of order on either side of the boundary. The comparison is done point by point within each group and it checks to ensure that the points reference the same spatial location. +- Added a `conduit::blueprint::mesh::utils::reorder()` function that can accept a vector of element ids and create a reordered topology. The points and coordinates are re-ordered according to their first use in the new element ordering. +- Added a `conduit::blueprint::mesh::utils::topology::spatial_ordering()` function that takes a topology and computes centroids for each element, passes them through a kdtree, and returns the new element ordering. The new ordering can be used with the `reorder()` function. +- Added a `conduit::blueprint::mesh::utils::slice_array()` function that can slice Conduit nodes that contain arrays. A new node with the same type is created but it contains only the selected indices. +- Added a `conduit::blueprint::mesh::utils::slice_field()` was added. It is like slice_array() but it can handle the mcarray protocol. This functionality was generalized from the partitioner. #### Executables - Added a `conduit_generate_data` executable that can generate datasets using the `tiled()` and `braid()` functions and save the datasets to files. From a97e13ab23304fa8f720a1f1a60079747fda3624 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 22:36:50 -0700 Subject: [PATCH 47/50] Removed errant ifdef guard in a cpp file. --- src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 5c2fd343d..18ee10c50 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -8,9 +8,6 @@ /// //----------------------------------------------------------------------------- -#ifndef CONDUIT_BLUEPRINT_MESH_EXAMPLES_TILED_HPP -#define CONDUIT_BLUEPRINT_MESH_EXAMPLES_TILED_HPP - //----------------------------------------------------------------------------- // conduit lib includes //----------------------------------------------------------------------------- @@ -1560,7 +1557,5 @@ tiled(conduit::index_t nx, conduit::index_t ny, conduit::index_t nz, // -- end conduit -- //----------------------------------------------------------------------------- -#endif - From 330719eb6b81e105008f4151aa56d53a74ef9776 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Tue, 19 Sep 2023 23:29:58 -0700 Subject: [PATCH 48/50] Fixed a comment. Fixed a typo in CHANGELOG.md. --- CHANGELOG.md | 2 +- src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 609987567..39f6a88a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,7 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s - Added a `conduit::blueprint::mesh::utils::reorder()` function that can accept a vector of element ids and create a reordered topology. The points and coordinates are re-ordered according to their first use in the new element ordering. - Added a `conduit::blueprint::mesh::utils::topology::spatial_ordering()` function that takes a topology and computes centroids for each element, passes them through a kdtree, and returns the new element ordering. The new ordering can be used with the `reorder()` function. - Added a `conduit::blueprint::mesh::utils::slice_array()` function that can slice Conduit nodes that contain arrays. A new node with the same type is created but it contains only the selected indices. -- Added a `conduit::blueprint::mesh::utils::slice_field()` was added. It is like slice_array() but it can handle the mcarray protocol. This functionality was generalized from the partitioner. +- Added a `conduit::blueprint::mesh::utils::slice_field()` function. It is like `slice_array()` but it can handle the mcarray protocol. This functionality was generalized from the partitioner. ### Changed diff --git a/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp b/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp index f7f5d084a..98241b806 100644 --- a/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp +++ b/src/tests/blueprint/t_blueprint_mpi_mesh_transform.cpp @@ -843,7 +843,7 @@ test_adjset_points(conduit::Node &mesh, const std::string &adjsetName) // Iterate over the points in the adjset and add them to the iterate_adjset(mesh, adjsetName, - [&](int /*dom*/, int nbr, int val, const conduit::Node *cset, const conduit::Node */*topo*/) + [&](int /*dom*/, int nbr, int val, const conduit::Node *cset, const conduit::Node * /*topo*/) { // Get the point (it might not be 3D) auto pt = conduit::blueprint::mesh::utils::coordset::_explicit::coords(*cset, val); From fba4998bc22c164fef8a883bfe3f8eb75fb998a2 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 20 Sep 2023 00:41:00 -0700 Subject: [PATCH 49/50] Added missing include. --- src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp index 18ee10c50..b54100ede 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_examples_tiled.cpp @@ -16,7 +16,9 @@ #include "conduit_blueprint.hpp" #include "conduit_blueprint_exports.h" #include "conduit_blueprint_mesh_utils.hpp" + #include +#include // Uncomment this to add some fields on the filed mesh prior to reordering. // #define CONDUIT_TILER_DEBUG_FIELDS From 7f8be282a497cfb6ae0b41fe205ee4c4a4387043 Mon Sep 17 00:00:00 2001 From: Brad Whitlock Date: Wed, 20 Sep 2023 09:55:21 -0700 Subject: [PATCH 50/50] Add float32/float64 cases in slice_array. Fix typos in tiled sphinx doc. --- src/docs/sphinx/blueprint_mesh.rst | 17 +++++++++-------- .../blueprint/conduit_blueprint_mesh_utils.cpp | 10 ++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/docs/sphinx/blueprint_mesh.rst b/src/docs/sphinx/blueprint_mesh.rst index 7733708f0..0f14bd6b8 100644 --- a/src/docs/sphinx/blueprint_mesh.rst +++ b/src/docs/sphinx/blueprint_mesh.rst @@ -1696,7 +1696,7 @@ a regular square tiling of the supplied tile. If no topology is given, a default is used. The output mesh can be either 2D or 3D. For 3D, supply a ``nz`` parameter greater than zero. Note that the input tile must consist of a homogeneous set of triangles or quads to extrude the tile into 3D since polyhedral output is not yet supported. The ``tiled()`` function produces a single domain comprised of a -main mesh, a boundary mesh, and adjacency sets of the output mesh is to be part of a multi-domain dataset. +main mesh, a boundary mesh, and adjacency sets if the output mesh is to be part of a multi-domain dataset. .. code:: cpp @@ -1707,29 +1707,30 @@ main mesh, a boundary mesh, and adjacency sets of the output mesh is to be part const conduit::Node &options);// options node The ``tiled()`` function accepts a Conduit node containing options that influence how the mesh is generated. -If the node contains a ``tile`` that contains a 2D blueprint topology, then the first supplied topology will +If the options contain a ``tile`` node that contains a 2D blueprint topology, the first supplied topology will be used to override the default tile pattern. The ``reorder`` option indicates the type of point and element reordering that will be done. Reordering can improve cache-friendliness. The default is to reorder points and elements, using "kdtree" method. Passing "none" or an empty string will prevent reordering. -The name of the mesh can be given by passing ``meshname`` option string. Likewise, the name of the boundary +The name of the mesh can be given by passing a ``meshname`` option string. Likewise, the name of the boundary mesh can be supplied using the ``boundarymeshname`` option. The optional ``translate/x`` and ``translate/y`` -options determing the tile spacing. If the translation values are not given, they will be determined from +options determine the tile spacing. If the translation values are not given, they will be determined from the coordset extents. The output mesh topology will store its integer connectivity information as index_t by default. The precision of the integer output can turned to int32 by passing a ``datatype`` option containing the "int", "int32", "integer" strings. -An important set of options define the left, right, bottom, top sets of points within the supplied tile +An important set of options define the left, right, bottom, and top sets of points within the supplied tile pattern. The values in the ``left`` option identify the list of points that define the left edge of the tile. These are indices into the coordset and the values should be in consecutive order along the edge. Opposite point sets must match. In other words, the left and right point sets must contain the same number of points and they need to proceed along their edges in the same order. The same is true of the bottom and top point sets. -The ``tiled()`` function options also support options that simplify the task of using ``tiled()`` to generate +The ``tiled()`` function also accepts options that simplify the task of generating mesh domains for a multi-domain dataset. The coordinate extents of the current mesh domain are given using the ``extents`` option, which contains 6 double values: {xmin, xmax, ymin, ymax, zmin, zmax}. The ``domains`` option contains a triple of {domainsI, domainsJ, domainsK} values that indicate how many divisions there are -of the extents in the I,J,K dimensions. The ``domain`` option specifies a triple indicating the I,J,K domain -id within the overall set of domains. This is used to help construct adjacency sets. +of the extents in the I,J,K dimensions. The product of these numbers determines the total number of domains. +The ``domain`` option specifies a triple indicating the I,J,K domain id within the overall set of domains. +This is used to help construct adjacency sets. .. code:: yaml diff --git a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp index 9762ce319..9ca1ca867 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh_utils.cpp @@ -415,6 +415,16 @@ slice_array_internal(const conduit::Node &n_src_values, auto dest(n_dest_values.as_uint64_array()); typed_slice_array(n_src_values.as_uint64_array(), ids, dest); } + else if(dt.is_float32()) + { + auto dest(n_dest_values.as_float32_array()); + typed_slice_array(n_src_values.as_float32_array(), ids, dest); + } + else if(dt.is_float64()) + { + auto dest(n_dest_values.as_float64_array()); + typed_slice_array(n_src_values.as_float64_array(), ids, dest); + } else if(dt.is_char()) { auto dest(n_dest_values.as_char_array());