diff --git a/CHANGELOG.md b/CHANGELOG.md index bad60313bd..879ed8c6be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project aspires to adhere to [Semantic Versioning](https://semver.org/s - Added a bulk `DataAccessor::set()` method for setting many elements at once. #### Blueprint +- Added `conduit::blueprint::mesh::rename()` helper that renames sub components of a valid mesh and their dependent references. Example: rename a topology and ensure field topology references are updated. - Added `conduit::blueprint::mesh::remove()` helper that removes sub components of a valid mesh and their dependencies. Examples: remove a field by name, or remove a topology by name and any dependent entries. - Added `conduit::blueprint::mesh::matset::get_material_names()`, which returns material names for a material set. - Added the ability for the Partitioner to handle field `matset_values` for both domain slicing and combination. diff --git a/src/libs/blueprint/conduit_blueprint_mesh.cpp b/src/libs/blueprint/conduit_blueprint_mesh.cpp index c2dc015c69..5b8a8c23f3 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh.cpp +++ b/src/libs/blueprint/conduit_blueprint_mesh.cpp @@ -9440,6 +9440,190 @@ void mesh::remove(const conduit::Node &n_options, } +void mesh::rename(const conduit::Node &n_options, + conduit::Node &n_mesh) +{ + // check options, each top level entry should be + // a component type name (coordsets) node that is an object with + // children that encode old vs new name + // current_name: new_name (coords: my_coords_new) + + NodeConstIterator itr = n_options.children(); + while(itr.has_next()) + { + const Node &chld = itr.next(); + const std::string chld_name = itr.name(); + + if(!chld.dtype().is_object()) + { + CONDUIT_ERROR(conduit_fmt::format( + "mesh::rename option must contain an object with children that are strings\n Value provided: {}\n", + n_options.to_yaml())); + } + else // we have a an object + { + // check that all object entries are strings + NodeConstIterator chld_itr = chld.children(); + while(chld_itr.has_next()) + { + if(!chld_itr.next().dtype().is_string()) + { + CONDUIT_ERROR(conduit_fmt::format( + "mesh::rename option must contain an object with children that are strings\n Value provided: {}\n", + n_options.to_yaml())); + } + } + } + } + + // helper to rename a component + auto rename_component = [&](conduit::Node *dom, + const std::string &comp_type, + const std::string &comp_name_old, + const std::string &comp_name_new) + { + if(dom->has_child(comp_type)) + { + Node &comp_node = dom->fetch_existing(comp_type); + if(comp_node.has_child(comp_name_old)) + { + comp_node.rename_child(comp_name_old,comp_name_new); + } + } + }; + + // helper to update references to a component that was renamed + auto rename_dependent_component_refs = [&](conduit::Node *dom, + const std::string &comp_type, + const std::string &ref_type, + const std::string &ref_name_old, + const std::string &ref_name_new) + { + if(dom->has_child(comp_type)) + { + NodeIterator itr = dom->fetch_existing(comp_type).children(); + while(itr.has_next()) + { + conduit::Node &curr = itr.next(); + if(curr.has_child(ref_type)) + { + const std::string ref_name = curr[ref_type].as_string(); + if(ref_name == ref_name_old) + { + curr[ref_type] = ref_name_new; + } + } + } + } + }; + + // note: this isn't const b/c we will alter the mesh + auto domains = conduit::blueprint::mesh::domains(n_mesh); + + // loop over all domains + for(size_t i = 0; i < domains.size(); i++) + { + conduit::Node *dom = domains[i]; + + if(n_options.has_child("coordsets")) + { + NodeConstIterator itr = n_options["coordsets"].children(); + while(itr.has_next()) + { + const Node &chld_node = itr.next(); + std::string chld_name_old = itr.name(); + std::string chld_name_new = chld_node.as_string(); + rename_component(dom, "coordsets", chld_name_old, chld_name_new); + rename_dependent_component_refs(dom, "topologies", "coordset", chld_name_old, chld_name_new); + } + } + + if(n_options.has_child("topologies")) + { + NodeConstIterator itr = n_options["topologies"].children(); + while(itr.has_next()) + { + const Node &chld_node = itr.next(); + std::string chld_name_old = chld_node.name(); + std::string chld_name_new = chld_node.as_string(); + rename_component(dom, "topologies", chld_name_old, chld_name_new); + rename_dependent_component_refs(dom, "fields", "topology", chld_name_old, chld_name_new); + rename_dependent_component_refs(dom, "matsets", "topology", chld_name_old, chld_name_new); + rename_dependent_component_refs(dom, "adjsets", "topology", chld_name_old, chld_name_new); + rename_dependent_component_refs(dom, "nestsets", "topology", chld_name_old, chld_name_new); + } + } + + if(n_options.has_child("fields")) + { + NodeConstIterator itr = n_options["fields"].children(); + while(itr.has_next()) + { + const Node &chld_node = itr.next(); + std::string chld_name_old = chld_node.name(); + std::string chld_name_new = chld_node.as_string(); + conduit::Node *dom = domains[i]; + rename_component(dom, "fields", chld_name_old, chld_name_new); + } + } + + if(n_options.has_child("matsets")) + { + NodeConstIterator itr = n_options["matsets"].children(); + while(itr.has_next()) + { + const Node &chld_node = itr.next(); + std::string chld_name_old = chld_node.name(); + std::string chld_name_new = chld_node.as_string(); + conduit::Node *dom = domains[i]; + rename_component(dom, "matsets", chld_name_old, chld_name_new); + rename_dependent_component_refs(dom, "specsets", "matset", chld_name_old, chld_name_new); + rename_dependent_component_refs(dom, "fields", "matset", chld_name_old, chld_name_new); + } + } + + if(n_options.has_child("specsets")) + { + NodeConstIterator itr = n_options["specsets"].children(); + while(itr.has_next()) + { + const Node &chld_node = itr.next(); + std::string chld_name_old = chld_node.name(); + std::string chld_name_new = chld_node.as_string(); + conduit::Node *dom = domains[i]; + rename_component(dom, "specsets", chld_name_old, chld_name_new); + } + } + + if(n_options.has_child("adjsets")) + { + NodeConstIterator itr = n_options["adjsets"].children(); + while(itr.has_next()) + { + const Node &chld_node = itr.next(); + std::string chld_name_old = chld_node.name(); + std::string chld_name_new = chld_node.as_string(); + conduit::Node *dom = domains[i]; + rename_component(dom, "adjsets", chld_name_old, chld_name_new); + } + } + + if(n_options.has_child("nestsets")) + { + NodeConstIterator itr = n_options["nestsets"].children(); + while(itr.has_next()) + { + const Node &chld_node = itr.next(); + std::string chld_name_old = chld_node.name(); + std::string chld_name_new = chld_node.as_string(); + conduit::Node *dom = domains[i]; + rename_component(dom, "nestsets", chld_name_old, chld_name_new); + } + } + } +} + + } //----------------------------------------------------------------------------- // -- end conduit::blueprint -- diff --git a/src/libs/blueprint/conduit_blueprint_mesh.hpp b/src/libs/blueprint/conduit_blueprint_mesh.hpp index 6a50429fe8..1a17276230 100644 --- a/src/libs/blueprint/conduit_blueprint_mesh.hpp +++ b/src/libs/blueprint/conduit_blueprint_mesh.hpp @@ -401,10 +401,13 @@ void CONDUIT_BLUEPRINT_API convert(const conduit::Node &n_mesh, //------------------------------------------------------------------------- /*! * @brief Remove specific components of mesh and their dependent entires. - * For example, remove a topology and all assocated fields -* + * For example, remove a topology and all associated fields + * + * If patterns do not match, remove is a no-op and no errors + * are thrown. + * * @param n_mesh The node containing the input mesh (coordsets, topos, - * fields, matsets, adjsets) + * fields, matsets, specsets, adjsets, and nestsets) * * @param n_options A node containing options. Categories support * both a single string or a list of strings @@ -415,6 +418,7 @@ void CONDUIT_BLUEPRINT_API convert(const conduit::Node &n_mesh, * topologies: mytopo | [ mytopo1, mytopo2, ... ] * fields: myfield | [ myfield1, myfield2, ... ] * matsets: mymatset | [ mymatset1, mymatset2, ... ] + * specsets: myspecset | [ myspecset1, myspecset3, ... ] * adjsets: myadj | [ myadj1,myadj2, ... ] * nestsets: mynestset | [ mynestset1, mynestset2, ... ] * @@ -428,6 +432,50 @@ void CONDUIT_BLUEPRINT_API remove(const conduit::Node &n_options, conduit::Node &n_mesh); + +//------------------------------------------------------------------------- +/*! + * @brief Rename specific components of mesh and update their dependent + * entires. + * + * For example, rename topology to `my_topo` and update + * all associated fields. + * + * If components are not found rename is a no-op and + * no errors are thrown. + * + * @param n_mesh The node containing the input mesh (coordsets, topos, + * fields, matsets, specsets, adjsets, and nestsets) + * + * @param n_options A node containing options. Entries + * should be an object with children that encode + * desired old_name: new_name relationship. + * + * @verbatim + * options: + * coordsets: + * coords_old : coords_new + * topologies: + * topo_old : topo_new + * fields: + * field1_old : field1_new + * field2_old : field2_new + * matsets: + * matset_old : matset_new + * specsets: + * specset_old : specset_new + * adjsets: + * adjset_old : adjset_new + * nestsets: + * nestset_old : nestset_new + * + * @endverbatim + */ +void CONDUIT_BLUEPRINT_API rename(const conduit::Node &n_options, + conduit::Node &n_mesh); + + + //----------------------------------------------------------------------------- // blueprint::mesh::logical_dims protocol interface //----------------------------------------------------------------------------- diff --git a/src/libs/blueprint/python/conduit_blueprint_mesh_python.cpp b/src/libs/blueprint/python/conduit_blueprint_mesh_python.cpp index ab40db362a..7d59366d87 100644 --- a/src/libs/blueprint/python/conduit_blueprint_mesh_python.cpp +++ b/src/libs/blueprint/python/conduit_blueprint_mesh_python.cpp @@ -393,6 +393,86 @@ PyBlueprint_mesh_remove(PyObject *, //self } +//---------------------------------------------------------------------------// +// conduit::blueprint::mesh::rename +//---------------------------------------------------------------------------// + +// doc str +const char *PyBlueprint_mesh_rename_doc_str = +"rename(options, mesh)\n" +"\n" +"Assumes mesh::verify() is True\n" +"\n" +"Renames subcomponents of a blueprint mesh tree\n" +"\n" +"Arguments:\n" +" options: options node (conduit.Node instance)\n" +" mesh: input node (conduit.Node instance)\n" +"\n" +"Example Options:\n" +"topologies:\n" +" topo: mytopo\n" +"\n"; + +// py func +static PyObject * +PyBlueprint_mesh_rename(PyObject *, //self + PyObject *args, + PyObject *kwargs) +{ + + PyObject *py_options = NULL; + PyObject *py_mesh = NULL; + + static const char *kwlist[] = {"options", + "mesh", + NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, + kwargs, + "OO", + const_cast(kwlist), + &py_options, + &py_mesh)) + { + return NULL; + } + + if(!PyConduit_Node_Check(py_options)) + { + PyErr_SetString(PyExc_TypeError, + "'options' argument must be a " + "conduit.Node instance"); + return NULL; + } + + if(!PyConduit_Node_Check(py_mesh)) + { + PyErr_SetString(PyExc_TypeError, + "'mesh' argument must be a " + "conduit.Node instance"); + return NULL; + } + + try + { + Node &options = *PyConduit_Node_Get_Node_Ptr(py_options); + Node &mesh = *PyConduit_Node_Get_Node_Ptr(py_mesh); + + blueprint::mesh::rename(options,mesh); + } + catch(conduit::Error &e) + { + PyErr_SetString(PyExc_IOError, + e.message().c_str()); + return NULL; + } + + + Py_RETURN_NONE; +} + + //---------------------------------------------------------------------------// // conduit::blueprint::mesh::partition @@ -663,6 +743,10 @@ static PyMethodDef blueprint_mesh_python_funcs[] = _PyCFunction_CAST(PyBlueprint_mesh_remove), METH_VARARGS | METH_KEYWORDS, PyBlueprint_mesh_remove_doc_str}, + {"rename", + _PyCFunction_CAST(PyBlueprint_mesh_rename), + METH_VARARGS | METH_KEYWORDS, + PyBlueprint_mesh_rename_doc_str}, {"partition", _PyCFunction_CAST(PyBlueprint_mesh_partition), METH_VARARGS | METH_KEYWORDS, diff --git a/src/tests/blueprint/CMakeLists.txt b/src/tests/blueprint/CMakeLists.txt index f38adb556f..d95ac24dd4 100644 --- a/src/tests/blueprint/CMakeLists.txt +++ b/src/tests/blueprint/CMakeLists.txt @@ -30,6 +30,7 @@ set(BLUEPRINT_TESTS t_blueprint_smoke t_blueprint_mesh_matset_accessor t_blueprint_mesh_topology_metadata t_blueprint_mesh_remove + t_blueprint_mesh_rename t_blueprint_mesh_utils t_blueprint_table_verify t_blueprint_table_examples diff --git a/src/tests/blueprint/python/t_python_blueprint_mesh.py b/src/tests/blueprint/python/t_python_blueprint_mesh.py index 57e23699c5..93f1fdbeb9 100644 --- a/src/tests/blueprint/python/t_python_blueprint_mesh.py +++ b/src/tests/blueprint/python/t_python_blueprint_mesh.py @@ -134,7 +134,7 @@ def test_venn(self): self.assertTrue(self.has_empty_warning(info)) self.assertTrue(blueprint.mesh.verify(n,info)) self.assertTrue(self.has_empty_warning(info)) - for matset_type in ['full', + for matset_type in ['full', 'sparse_by_material', 'sparse_by_element' ]: blueprint.mesh.examples.venn(matset_type, @@ -220,7 +220,7 @@ def test_examples_generate_default_opts(self): "rz_cylinder", "tiled", "venn"] - + for ename in enames: opts = conduit.Node() blueprint.mesh.examples.generate_default_options(opts,ename) @@ -264,6 +264,22 @@ def test_remove(self): self.assertFalse(n.has_path("fields/vel")) self.assertTrue(n.has_path("fields/radial")) + def test_rename(self): + n = conduit.Node() + opts = conduit.Node() + info = conduit.Node() + blueprint.mesh.examples.generate("braid",n) + opts["topologies/mesh"] = "topo" + opts["fields/braid"] = "awesome" + blueprint.mesh.rename(opts,n) + print(n) + self.assertFalse(n.has_path("fields/mesh")) + self.assertFalse(n.has_path("fields/braid")) + self.assertTrue(n.has_path("topologies/topo")) + self.assertTrue(n.has_path("fields/awesome")) + self.assertTrue(n["fields/awesome/topology"],"topo") + + def test_partition(self): def same(a, b): for i in range(len(a)): @@ -288,7 +304,7 @@ def same(a, b): self.assertTrue(same(expected_y1, output[1]["coordsets/coords/values/y"])) def test_convert(self): - tgts = { + tgts = { "uniform": [ "uniform", "rectilinear", "structured", "unstructured", "polytopal"], "rectilinear": [ "rectilinear", "structured", "unstructured", "polytopal"], "structured": [ "structured", "unstructured", "polytopal"], @@ -301,13 +317,13 @@ def test_convert(self): "generate_corners"] } - braid_types = { "uniform" : [ { "mesh_type": "uniform", "dims": [5, 5, 0]} , + braid_types = { "uniform" : [ { "mesh_type": "uniform", "dims": [5, 5, 0]} , { "mesh_type": "uniform", "dims": [5, 5, 5]} ], - "rectilinear" : [ { "mesh_type": "rectilinear", "dims": [5, 5, 0]} , + "rectilinear" : [ { "mesh_type": "rectilinear", "dims": [5, 5, 0]} , { "mesh_type": "rectilinear", "dims": [5, 5, 5]} ], - "structured" : [ { "mesh_type": "structured", "dims": [5, 5, 0]} , + "structured" : [ { "mesh_type": "structured", "dims": [5, 5, 0]} , { "mesh_type": "structured", "dims": [5, 5, 5]} ], - "unstructured" : [ { "mesh_type": "tris", "dims": [5, 5, 0]} , + "unstructured" : [ { "mesh_type": "tris", "dims": [5, 5, 0]} , { "mesh_type": "hexs", "dims": [5, 5, 5]}, { "mesh_type": "quads_poly", "dims": [5, 5, 0]}, { "mesh_type": "hexs_poly", "dims": [5, 5, 5]}, @@ -326,7 +342,7 @@ def test_convert(self): n) for target in tgts[mesh_cat]: print("Testing",test_mesh, "to", target) - options["target"] = target + options["target"] = target conduit.blueprint.mesh.convert(n, options, output) conduit.blueprint.mesh.convert(n, options, output, maps) diff --git a/src/tests/blueprint/t_blueprint_mesh_rename.cpp b/src/tests/blueprint/t_blueprint_mesh_rename.cpp new file mode 100644 index 0000000000..500146b963 --- /dev/null +++ b/src/tests/blueprint/t_blueprint_mesh_rename.cpp @@ -0,0 +1,395 @@ +// 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_mesh_rename.cpp +/// +//----------------------------------------------------------------------------- + + +#include "conduit.hpp" +#include "conduit_blueprint.hpp" +#include "conduit_blueprint_mesh_utils.hpp" +#include "conduit_relay.hpp" +#include "conduit_log.hpp" + +#include +#include "gtest/gtest.h" + +void echo_title(const std::string &title) +{ + std::cout << "---------------------------------------------------" << std::endl; + std::cout << title << std::endl; + std::cout << "---------------------------------------------------" << std::endl; +} + +void echo_node(const std::string tag, const conduit::Node &node, bool full = false) +{ + std::cout << tag << std::endl; + if(full) + { + std::cout << node.to_yaml() << std::endl; + } + else + { + std::cout << node.to_summary_string() << std::endl; + } +} + + +//----------------------------------------------------------------------------- +TEST(conduit_blueprint_mesh_rename, bad_options) +{ + echo_title("remove bad options"); + conduit::Node n_mesh; + conduit::Node n_opts; + n_opts["topologies"] = 1; + EXPECT_THROW(conduit::blueprint::mesh::rename(n_opts,n_mesh),conduit::Error); + // entries are current name to desired name + n_opts["topologies/bonkers"] = 1; + EXPECT_THROW(conduit::blueprint::mesh::rename(n_opts,n_mesh),conduit::Error); + n_opts.reset(); +} + +//----------------------------------------------------------------------------- +TEST(conduit_blueprint_mesh_rename, rename_coords) +{ + // coordset change impacts refs in: + // topos + + conduit::Node n_mesh, n_mesh_opts, n_opts, n_info; + n_mesh_opts["mesh_type"] = "uniform"; + n_mesh_opts["nx"] = 3; + n_mesh_opts["ny"] = 3; + + echo_title("rename coords"); + + conduit::blueprint::mesh::examples::generate("braid",n_mesh_opts,n_mesh); + EXPECT_TRUE(n_mesh.has_path("coordsets/coords")); + EXPECT_TRUE(n_mesh.has_path("topologies/mesh")); + EXPECT_EQ(n_mesh["topologies/mesh/coordset"].as_string(),"coords"); + + n_opts["coordsets/coords"] = "mycoords"; + + echo_node("[before]",n_mesh); + echo_node("[options]",n_opts); + conduit::blueprint::mesh::rename(n_opts,n_mesh); + EXPECT_TRUE(conduit::blueprint::mesh::verify(n_mesh, n_info)); + echo_node("[after]",n_mesh); + echo_node("info",n_info); + + EXPECT_FALSE(n_mesh.has_path("coordsets/coords")); + EXPECT_TRUE(n_mesh.has_path("coordsets/mycoords")); + EXPECT_EQ(n_mesh["topologies/mesh/coordset"].as_string(),"mycoords"); +} + +//----------------------------------------------------------------------------- +TEST(conduit_blueprint_mesh_rename, rename_topo) +{ + // topo change impacts refs in: + // fields, matsets, nestsets, adjsets + + // check fields updates + { + echo_title("rename topo check fields"); + conduit::Node n_mesh, n_mesh_opts, n_opts, n_info; + n_mesh_opts["mesh_type"] = "uniform"; + n_mesh_opts["nx"] = 3; + n_mesh_opts["ny"] = 3; + + conduit::blueprint::mesh::examples::generate("braid",n_mesh_opts,n_mesh); + EXPECT_TRUE(n_mesh.has_path("topologies/mesh")); + EXPECT_TRUE(n_mesh.has_path("fields/braid")); + EXPECT_EQ(n_mesh["fields/braid/topology"].as_string(),"mesh"); + + n_opts["topologies/mesh"] = "topo"; + + echo_node("[before]",n_mesh); + echo_node("[options]",n_opts); + conduit::blueprint::mesh::rename(n_opts,n_mesh); + EXPECT_TRUE(conduit::blueprint::mesh::verify(n_mesh, n_info)); + echo_node("[after]",n_mesh); + echo_node("info",n_info); + + EXPECT_FALSE(n_mesh.has_path("topologies/mesh")); + EXPECT_TRUE(n_mesh.has_path("topologies/topo")); + EXPECT_EQ(n_mesh["fields/braid/topology"].as_string(),"topo"); + EXPECT_EQ(n_mesh["fields/radial/topology"].as_string(),"topo"); + EXPECT_EQ(n_mesh["fields/vel/topology"].as_string(),"topo"); + } + + // check matsets updates + { + echo_title("rename topo check matsets"); + conduit::Node n_mesh, n_mesh_opts, n_opts, n_info; + n_mesh_opts["matset_type"] = "full"; + n_mesh_opts["nx"] = 4; + n_mesh_opts["ny"] = 4; + n_mesh_opts["radius"] = 1; + + conduit::blueprint::mesh::examples::generate("venn",n_mesh_opts,n_mesh); + EXPECT_TRUE(n_mesh.has_path("topologies/topo")); + EXPECT_EQ(n_mesh["fields/importance/topology"].as_string(),"topo"); + EXPECT_EQ(n_mesh["matsets/matset/topology"].as_string(),"topo"); + + n_opts["topologies/topo"] = "mytopo"; + + echo_node("[before]",n_mesh,false); + echo_node("[options]",n_opts); + conduit::blueprint::mesh::rename(n_opts,n_mesh); + EXPECT_TRUE(conduit::blueprint::mesh::verify(n_mesh, n_info)); + echo_node("[after]",n_mesh,false); + echo_node("info",n_info); + + EXPECT_TRUE(n_mesh.has_path("topologies/mytopo")); + EXPECT_EQ(n_mesh["fields/importance/topology"].as_string(),"mytopo"); + EXPECT_EQ(n_mesh["matsets/matset/topology"].as_string(),"mytopo"); + } + + // check adjsets updates + { + echo_title("rename topo check adjsets"); + // adjsets do not have dependent refs + conduit::Node n_mesh, n_mesh_opts, n_opts, n_info; + conduit::blueprint::mesh::examples::generate("adjset_uniform",n_mesh); + size_t number_of_doms = 8; + EXPECT_EQ(n_mesh.number_of_children(),number_of_doms); + for(size_t i=0; i