diff --git a/src/docs/sphinx/Actions/Examples.rst b/src/docs/sphinx/Actions/Examples.rst index 185509c8f..594d62c3d 100644 --- a/src/docs/sphinx/Actions/Examples.rst +++ b/src/docs/sphinx/Actions/Examples.rst @@ -820,6 +820,20 @@ YAML actions: .. literalinclude:: examples/tout_sample_plane_000100.yaml +An example of using the sample filter to sample on a plane topology from the input dataset. +------------------------------------------------------------------------------------------- + +YAML actions: + +.. literalinclude:: examples/tout_sample_topology_plane_000100.yaml + +An example of using the sample filter to sample on a sphere surface topology from the input dataset. +---------------------------------------------------------------------------------------------------- + +YAML actions: + +.. literalinclude:: examples/tout_sample_topology_sphere_000100.yaml + An example of using the sample filter to sample a list of 3D points. --------------------------------------------------------------------- diff --git a/src/docs/sphinx/Actions/examples/tout_sample_topology_plane_000100.yaml b/src/docs/sphinx/Actions/examples/tout_sample_topology_plane_000100.yaml new file mode 100644 index 000000000..99d437198 --- /dev/null +++ b/src/docs/sphinx/Actions/examples/tout_sample_topology_plane_000100.yaml @@ -0,0 +1,22 @@ +#An example of using the sample filter to sample on a topology from the input dataset. + +- + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "sample" + params: + fields: + - "braid" + topology: "sample_plane" + invalid_value: -10.0 +- + action: "add_extracts" + extracts: + e1: + pipeline: "pl1" + type: "relay" + params: + protocol: "hdf5" + path: "/usr/WS1/nicolem/sadbox/ascent/scripts/build_ascent/build/ascent-checkout/tests/_output/tout_sample_topology_plane" diff --git a/src/docs/sphinx/Actions/examples/tout_sample_topology_sphere_000100.yaml b/src/docs/sphinx/Actions/examples/tout_sample_topology_sphere_000100.yaml new file mode 100644 index 000000000..faab0bb95 --- /dev/null +++ b/src/docs/sphinx/Actions/examples/tout_sample_topology_sphere_000100.yaml @@ -0,0 +1,22 @@ +#An example of using the sample filter to sample on a sphere surface topology from the input dataset. + +- + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "sample" + params: + fields: + - "braid" + topology: "sample_sphere" + invalid_value: -10.0 +- + action: "add_extracts" + extracts: + e1: + pipeline: "pl1" + type: "relay" + params: + protocol: "hdf5" + path: "/usr/WS1/nicolem/sadbox/ascent/scripts/build_ascent/build/ascent-checkout/tests/_output/tout_sample_topology_sphere" diff --git a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp index a58a71134..873aa44c1 100644 --- a/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp +++ b/src/libs/ascent/runtimes/flow_filters/ascent_runtime_vtkh_filters.cpp @@ -3325,6 +3325,7 @@ VTKHSample::declare_interface(Node &i) string_schema(param_schema["properties/field"]); array_schema(param_schema["properties/fields"]); + string_schema(param_schema["properties/topology"]); number_schema(param_schema["properties/invalid_value"]); // --- Line --- @@ -3447,8 +3448,75 @@ VTKHSample::execute() vtkh::DataSet &data = collection->dataset_by_topology(topo_name); vtkh::Sample sampler; + std::string output_topo_name = topo_name; - if(params().has_path("line")) + const bool has_topology = params().has_path("topology"); + if(has_topology) + { + std::string sample_topo_name = params()["topology"].as_string(); + if(!collection->has_topology(sample_topo_name)) + { + ASCENT_ERROR("vtkh_sample topology '" << sample_topo_name + << "' does not exist. Known topologies: " + << detail::possible_topologies(collection)); + } + + std::shared_ptr sample_collection; +#ifdef ASCENT_MPI_ENABLED + MPI_Comm mpi_comm = MPI_Comm_f2c(Workspace::default_mpi_comm()); + int rank = 0; + MPI_Comm_rank(mpi_comm, &rank); + + std::shared_ptr blueprint_data = data_object->as_low_order_bp(); + conduit::Node sample_mesh; + if(rank == 0) + { + const std::vector domains = blueprint::mesh::domains(*blueprint_data); + + //make a conduit::Node sample mesh with input topo + for(size_t i = 0; i < domains.size(); ++i) + { + const conduit::Node &src_dom = *domains[i]; + const std::string topo_path = "topologies/" + sample_topo_name; + if(src_dom.has_path(topo_path)) + { + conduit::Node &dst_dom = sample_mesh.append(); + if(src_dom.has_path("state")) + { + dst_dom["state"].set(src_dom["state"]); + } + + dst_dom[topo_path].set(src_dom[topo_path]); + const std::string coordset_name = src_dom[topo_path + "/coordset"].as_string(); + dst_dom["coordsets/" + coordset_name].set(src_dom["coordsets/" + coordset_name]); + } + } + + if(sample_mesh.number_of_children() == 0) + { + ASCENT_ERROR("vtkh_sample topology '" << sample_topo_name + << "' must be present on rank 0 for MPI topology sampling"); + } + } + //broadcast created sample node and convert back to vtkh + conduit::relay::mpi::broadcast_using_schema(sample_mesh, 0, mpi_comm); + sample_collection.reset(VTKHDataAdapter::BlueprintToVTKHCollection(sample_mesh, + false)); +#else + sample_collection = collection; +#endif + + vtkh::DataSet &sample_data = sample_collection->dataset_by_topology(sample_topo_name); + std::vector sample_domains; + std::vector sample_domain_ids = sample_data.GetDomainIds(); + for(size_t i = 0; i < sample_domain_ids.size(); ++i) + { + sample_domains.push_back(sample_data.GetDomainById(sample_domain_ids[i])); + } + sampler.Topology(sample_domains, sample_domain_ids); + output_topo_name = sample_topo_name; + } + else if(params().has_path("line")) { const Node &line_p = params()["line"]; int num_samples = line_p["num_samples"].to_int(); @@ -3782,7 +3850,7 @@ VTKHSample::execute() vtkh::DataSet *grid_output = sampler.GetOutput(); VTKHCollection *new_coll = new VTKHCollection(); - new_coll->add(*grid_output, topo_name); + new_coll->add(*grid_output, output_topo_name); // re wrap in data object DataObject *res = new DataObject(new_coll); delete grid_output; diff --git a/src/libs/vtkh/filters/Sample.cpp b/src/libs/vtkh/filters/Sample.cpp index 735c51152..dbb79d775 100644 --- a/src/libs/vtkh/filters/Sample.cpp +++ b/src/libs/vtkh/filters/Sample.cpp @@ -856,7 +856,8 @@ class LocalReduceField //---------------------------------------------------------------------------// Sample::Sample() : m_invalid_value(std::numeric_limits::min()), - m_is_points(false) + m_is_points(false), + m_is_topology(false) { } @@ -911,98 +912,159 @@ Sample::DoExecute() #endif #endif - viskores::cont::DataSet local_res; - for(int i = 0; i < num_domains; ++i) + auto sample_domain = [&](viskores::cont::DataSet *sample_geometry) { - viskores::cont::DataSet dom; - - if(this->m_input->HasDomainId(domain_ids[i])) + viskores::cont::DataSet local_res; + for(int i = 0; i < num_domains; ++i) { - dom = this->m_input->GetDomainById(domain_ids[i]); - for(const auto &field_name : m_fields) + viskores::cont::DataSet dom; + + if(this->m_input->HasDomainId(domain_ids[i])) { - vtkh::viskoresProbe probe; - if(m_is_points) + dom = this->m_input->GetDomainById(domain_ids[i]); + for(const auto &field_name : m_fields) { - probe.setPoints(m_points_xs,m_points_ys,m_points_zs); - } - else - { - probe.setBoxDims(m_dims); - probe.setBoxOrigin(m_origin); - probe.setBoxSpacing(m_spacing); - } - probe.setInvalidValue(m_invalid_value); - auto dataset = probe.Run(dom); - viskores::cont::Field tmp_field = dataset.GetField(field_name); + vtkh::viskoresProbe probe; + if(sample_geometry != nullptr) + { + probe.setGeometry(*sample_geometry); + } + else if(m_is_points) + { + probe.setPoints(m_points_xs,m_points_ys,m_points_zs); + } + else + { + probe.setBoxDims(m_dims); + probe.setBoxOrigin(m_origin); + probe.setBoxSpacing(m_spacing); + } + probe.setInvalidValue(m_invalid_value); + auto dataset = probe.Run(dom); + viskores::cont::Field tmp_field = dataset.GetField(field_name); #if _DEBUG - std::cerr <<"UNIFORM GRID OUTPUT START: " << std::endl; - dataset.PrintSummary(std::cerr); - std::cerr <<"UNIFORM GRID OUTPUT END" << std::endl; + std::cerr <<"UNIFORM GRID OUTPUT START: " << std::endl; + dataset.PrintSummary(std::cerr); + std::cerr <<"UNIFORM GRID OUTPUT END" << std::endl; #endif - viskores::cont::Field valid_field; - if(tmp_field.IsPointField()) - { - viskores::cont::Field point_field = dataset.GetPointField("HIDDEN"); - valid_field = point_field; - } - else - { - viskores::cont::Field cell_field = dataset.GetCellField("HIDDEN"); - valid_field = cell_field; - } + viskores::cont::Field valid_field; + if(tmp_field.IsPointField()) + { + viskores::cont::Field point_field = dataset.GetPointField("HIDDEN"); + valid_field = point_field; + } + else + { + viskores::cont::Field cell_field = dataset.GetCellField("HIDDEN"); + valid_field = cell_field; + } - std::string cs_name = dataset.GetCoordinateSystemName(); - if(!local_res.HasCoordinateSystem(cs_name)) - { - local_res.CopyStructure(dataset); - local_res.AddField("valid_mask", valid_field.GetAssociation(), valid_field.GetData()); - } + std::string cs_name = dataset.GetCoordinateSystemName(); + if(!local_res.HasCoordinateSystem(cs_name)) + { + local_res.CopyStructure(dataset); + local_res.AddField("valid_mask", valid_field.GetAssociation(), valid_field.GetData()); + } - if(!local_res.HasField(field_name)) - { - local_res.AddField(tmp_field); - } - else - { - vtkh::detail::LocalReduceField localreducefield(local_res, - tmp_field, - valid_field, - field_name, - m_invalid_value); - localreducefield.LocalReduce(); + if(!local_res.HasField(field_name)) + { + local_res.AddField(tmp_field); + } + else + { + vtkh::detail::LocalReduceField localreducefield(local_res, + tmp_field, + valid_field, + field_name, + m_invalid_value); + localreducefield.LocalReduce(); + } } } } + return local_res; + }; + + std::vector local_results; + std::vector local_result_domain_ids; + if(m_is_topology) + { + const int num_topology_domains = static_cast(m_topology_domains.size()); + for(int i = 0; i < num_topology_domains; ++i) + { + local_results.push_back(sample_domain(&m_topology_domains[i])); + local_result_domain_ids.push_back(m_topology_domain_ids[i]); + } + } + else + { + local_results.push_back(sample_domain(nullptr)); + local_result_domain_ids.push_back(0); } #if _DEBUG - std::cerr <<" LOCAL RES START" << std::endl; - local_res.PrintSummary(std::cerr); - std::cerr <<" LOCAL RES END" << std::endl; + for(size_t i = 0; i < local_results.size(); ++i) + { + std::cerr <<" LOCAL RES START" << std::endl; + local_results[i].PrintSummary(std::cerr); + std::cerr <<" LOCAL RES END" << std::endl; + } #endif #ifdef VTKH_PARALLEL - //take uniform sampled grid and reduce to root process - viskores::cont::DataSet reduced_output; - reduced_output.CopyStructure(local_res); - - for(const auto &field_name : m_fields) + if(m_is_topology) { - viskores::cont::DataSet reduced; - bool valid_field; - viskores::Id field_id = this->m_input->GetFieldType(field_name, valid_field); - vtkh::detail::GlobalReduceField g_reducefield(local_res, field_name, m_invalid_value, m_num_samples, field_id, reduced); - g_reducefield.Reduce(); - viskores::cont::Field reduced_field = reduced.GetField(field_name); - reduced_output.AddField(reduced_field); + for(size_t i = 0; i < local_results.size(); ++i) + { + viskores::cont::DataSet reduced_output; + reduced_output.CopyStructure(local_results[i]); + + for(const auto &field_name : m_fields) + { + viskores::cont::DataSet reduced; + bool valid_field; + viskores::Id field_id = this->m_input->GetFieldType(field_name, valid_field); + const int num_values = static_cast(local_results[i].GetField(field_name).GetData().GetNumberOfValues()); + vtkh::detail::GlobalReduceField g_reducefield(local_results[i], + field_name, + m_invalid_value, + num_values, + field_id, + reduced); + g_reducefield.Reduce(); + viskores::cont::Field reduced_field = reduced.GetField(field_name); + reduced_output.AddField(reduced_field); + } + + if(par_rank == 0) + { + this->m_output->AddDomain(reduced_output, local_result_domain_ids[i]); + } + } } - - if(par_rank == 0) + else { - this->m_output->AddDomain(reduced_output, 0); + //take uniform sampled grid and reduce to root process + viskores::cont::DataSet reduced_output; + reduced_output.CopyStructure(local_results[0]); + + for(const auto &field_name : m_fields) + { + viskores::cont::DataSet reduced; + bool valid_field; + viskores::Id field_id = this->m_input->GetFieldType(field_name, valid_field); + vtkh::detail::GlobalReduceField g_reducefield(local_results[0], field_name, m_invalid_value, m_num_samples, field_id, reduced); + g_reducefield.Reduce(); + viskores::cont::Field reduced_field = reduced.GetField(field_name); + reduced_output.AddField(reduced_field); + } + + if(par_rank == 0) + { + this->m_output->AddDomain(reduced_output, 0); + } } #if _DEBUG @@ -1018,7 +1080,10 @@ Sample::DoExecute() std::cerr <<" PAR RANK " << par_rank << " at the very end" << std::endl; #endif //end _DEBUG #else //serial - this->m_output->AddDomain(local_res,0); + for(size_t i = 0; i < local_results.size(); ++i) + { + this->m_output->AddDomain(local_results[i], local_result_domain_ids[i]); + } #endif } @@ -1047,6 +1112,7 @@ Sample::Line(int num_samples, { m_is_points = true; + m_is_topology = false; m_num_samples = num_samples; int line_spatial_num_points = 3; @@ -1100,7 +1166,8 @@ Sample::Box(int *num_points, { m_is_points = true; - int m_num_samples = num_points[0]*num_points[1]*num_points[2]; + m_is_topology = false; + m_num_samples = num_points[0]*num_points[1]*num_points[2]; // alloc array handles to hold num_samples // alloc xs, ys, zs @@ -1161,6 +1228,7 @@ Sample::Plane(const Vec3_f64 point, const int axes[2]) { m_is_points = true; + m_is_topology = false; const int dim_1 = dims[0] > 0 ? static_cast(dims[0]) : 1; const int dim_2 = dims[1] > 0 ? static_cast(dims[1]) : 1; @@ -1242,6 +1310,7 @@ Sample::Points(viskores::cont::ArrayHandle xs, viskores::cont::ArrayHandle zs) { m_is_points = true; + m_is_topology = false; m_num_samples = xs.GetNumberOfValues(); m_points_xs = xs; m_points_ys = ys; @@ -1255,6 +1324,7 @@ Sample::UniformGrid(const Vec3_f64 dims, const Vec3_f64 spacing) { m_is_points = false; + m_is_topology = false; m_dims = dims; m_origin = origin; m_spacing = spacing; @@ -1264,6 +1334,22 @@ Sample::UniformGrid(const Vec3_f64 dims, } +//---------------------------------------------------------------------------// +void +Sample::Topology(const std::vector &domains, + const std::vector &domain_ids) +{ + if(domains.size() != domain_ids.size()) + { + throw Error("Sample::Topology requires matching domain and domain id counts"); + } + + m_is_topology = true; + m_is_points = false; + m_topology_domains = domains; + m_topology_domain_ids = domain_ids; +} + //---------------------------------------------------------------------------// void Sample::Fields(const std::vector fields) diff --git a/src/libs/vtkh/filters/Sample.hpp b/src/libs/vtkh/filters/Sample.hpp index 3da8f8be2..95f929120 100644 --- a/src/libs/vtkh/filters/Sample.hpp +++ b/src/libs/vtkh/filters/Sample.hpp @@ -64,6 +64,9 @@ class VTKH_API Sample : public Filter const Vec3_f64 origin, const Vec3_f64 spacing); + void Topology(const std::vector &domains, + const std::vector &domain_ids); + void Fields(const std::vector fields); void InvalidValue(const viskores::Float64 invalid_value); @@ -83,6 +86,9 @@ class VTKH_API Sample : public Filter Vec3_f64 m_spacing; int m_num_samples; bool m_is_points; + bool m_is_topology; + std::vector m_topology_domains; + std::vector m_topology_domain_ids; }; } //namespace vtkh diff --git a/src/libs/vtkh/viskores_filters/viskoresProbe.cpp b/src/libs/vtkh/viskores_filters/viskoresProbe.cpp index 71496e030..9969a2870 100644 --- a/src/libs/vtkh/viskores_filters/viskoresProbe.cpp +++ b/src/libs/vtkh/viskores_filters/viskoresProbe.cpp @@ -58,6 +58,14 @@ viskoresProbe::setBoxSpacing(const Vec3_f64 spacing) m_box_spacing = spacing; } +//---------------------------------------------------------------------------// +void +viskoresProbe::setGeometry(const viskores::cont::DataSet &geometry) +{ + m_mode = SampleMode::GEOMETRY; + m_geometry = geometry; +} + //---------------------------------------------------------------------------// void viskoresProbe::setInvalidValue(const viskores::Float64 invalid_value) @@ -130,6 +138,27 @@ viskoresProbe::Run(viskores::cont::DataSet &input) cellset.Fill(num_points, shape_id, indices_per, connectivity); ds_probe.SetCellSet(cellset); } + else if(m_mode == GEOMETRY) + { + const viskores::cont::CoordinateSystem coords = m_geometry.GetCoordinateSystem(); + const viskores::Id num_points = coords.GetData().GetNumberOfValues(); + + ds_probe.AddCoordinateSystem(coords); + + viskores::UInt8 shape_id = 1; + viskores::IdComponent indices_per = 1; + viskores::cont::CellSetSingleType<> cellset; + + viskores::cont::ArrayHandle connectivity; + connectivity.Allocate(num_points); + auto conn_portal = connectivity.WritePortal(); + for(viskores::Id i = 0; i < num_points; ++i) + { + conn_portal.Set(i, i); + } + cellset.Fill(num_points, shape_id, indices_per, connectivity); + ds_probe.SetCellSet(cellset); + } else // error { std::ostringstream oss; @@ -140,6 +169,20 @@ viskoresProbe::Run(viskores::cont::DataSet &input) probe.SetGeometry(ds_probe); probe.SetInvalidValue(m_invalid_value); auto output = probe.Execute(input); + if(m_mode == GEOMETRY) + { + viskores::cont::DataSet topology_output; + topology_output.CopyStructure(m_geometry); + const viskores::Id num_fields = output.GetNumberOfFields(); + for(viskores::Id i = 0; i < num_fields; ++i) + { + viskores::cont::Field field = output.GetField(i); + topology_output.AddField(viskores::cont::Field(field.GetName(), + viskores::cont::Field::Association::Points, + field.GetData())); + } + output = topology_output; + } return output; } diff --git a/src/libs/vtkh/viskores_filters/viskoresProbe.hpp b/src/libs/vtkh/viskores_filters/viskoresProbe.hpp index 9cb375461..2afc1ef9c 100644 --- a/src/libs/vtkh/viskores_filters/viskoresProbe.hpp +++ b/src/libs/vtkh/viskores_filters/viskoresProbe.hpp @@ -19,6 +19,7 @@ class viskoresProbe void setBoxDims(const viskores::Vec dims); void setBoxOrigin(const viskores::Vec origin); void setBoxSpacing(const viskores::Vec spacing); + void setGeometry(const viskores::cont::DataSet &geometry); void setInvalidValue(const viskores::Float64 invalid_value); @@ -28,7 +29,8 @@ class viskoresProbe enum SampleMode { NONE, POINTS, - BOX + BOX, + GEOMETRY }; int m_mode; @@ -45,6 +47,7 @@ class viskoresProbe viskores::Vec m_box_origin; viskores::Vec m_box_spacing; + viskores::cont::DataSet m_geometry; }; } diff --git a/src/tests/_baseline_images/tout_sample_topology_plane_000100.png b/src/tests/_baseline_images/tout_sample_topology_plane_000100.png new file mode 100644 index 000000000..2f505f487 Binary files /dev/null and b/src/tests/_baseline_images/tout_sample_topology_plane_000100.png differ diff --git a/src/tests/_baseline_images/tout_sample_topology_sphere_000100.png b/src/tests/_baseline_images/tout_sample_topology_sphere_000100.png new file mode 100644 index 000000000..3bdaac0d0 Binary files /dev/null and b/src/tests/_baseline_images/tout_sample_topology_sphere_000100.png differ diff --git a/src/tests/ascent/t_ascent_mpi_multi_topo.cpp b/src/tests/ascent/t_ascent_mpi_multi_topo.cpp index be733643b..44d06006a 100644 --- a/src/tests/ascent/t_ascent_mpi_multi_topo.cpp +++ b/src/tests/ascent/t_ascent_mpi_multi_topo.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "t_config.hpp" #include "t_utils.hpp" @@ -130,6 +131,100 @@ TEST(ascent_mpi_mult_topo, test_multi_semi_madness) // EXPECT_TRUE(check_test_image(output_file)); } +//----------------------------------------------------------------------------- +TEST(ascent_mpi_mult_topo, sample_rank_zero_topology) +{ + Node n; + ascent::about(n); + if(n["runtimes/ascent/viskores/status"].as_string() == "disabled") + { + ASCENT_INFO("Ascent support disabled, skipping MPI topology sample test"); + return; + } + + int par_rank; + MPI_Comm comm = MPI_COMM_WORLD; + MPI_Comm_rank(comm, &par_rank); + + Node data, verify_info; + conduit::blueprint::mesh::examples::braid("hexs", 10, 10, 10, data); + data["state/domain_id"] = par_rank; + data["state/cycle"] = 100; + + if(par_rank == 0) + { + Node plane; + conduit::blueprint::mesh::examples::braid("quads", 5, 5, 0, plane); + data["topologies/sample_plane"] = plane["topologies/mesh"]; + data["topologies/sample_plane/coordset"] = "sample_plane_coords"; + data["coordsets/sample_plane_coords"] = plane["coordsets/coords"]; + } + + EXPECT_TRUE(conduit::blueprint::mesh::verify(data, verify_info)); + + string output_path = ""; + if(par_rank == 0) + { + output_path = prepare_output_dir(); + } + else + { + output_path = output_dir(); + } + + string output_file = conduit::utils::join_file_path(output_path, + "tout_mpi_sample_rank_zero_topology"); + string output_root = output_file + ".cycle_000100.root"; + if(par_rank == 0 && conduit::utils::is_file(output_root)) + { + conduit::utils::remove_file(output_root); + } + + conduit::Node actions; + conduit::Node &add_pipelines = actions.append(); + add_pipelines["action"] = "add_pipelines"; + conduit::Node &pipelines = add_pipelines["pipelines"]; + pipelines["pl1/f1/type"] = "sample"; + pipelines["pl1/f1/params/fields"].append() = "braid"; + pipelines["pl1/f1/params/topology"] = "sample_plane"; + pipelines["pl1/f1/params/invalid_value"] = -10.0; + + conduit::Node &add_extracts = actions.append(); + add_extracts["action"] = "add_extracts"; + conduit::Node &extracts = add_extracts["extracts"]; + extracts["e1/pipeline"] = "pl1"; + extracts["e1/type"] = "relay"; + extracts["e1/params/path"] = output_file; + extracts["e1/params/protocol"] = "blueprint/mesh/yaml"; + + Ascent ascent; + Node ascent_opts; + ascent_opts["mpi_comm"] = MPI_Comm_c2f(comm); + ascent_opts["runtime"] = "ascent"; + ascent.open(ascent_opts); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + MPI_Barrier(comm); + + if(par_rank == 0) + { + EXPECT_TRUE(conduit::utils::is_file(output_root)); + + Node read_mesh, read_verify_info; + conduit::relay::io::blueprint::load_mesh(output_root, read_mesh); + EXPECT_TRUE(conduit::blueprint::mesh::verify(read_mesh, read_verify_info)); + EXPECT_EQ(conduit::blueprint::mesh::number_of_domains(read_mesh), 1); + + const Node &dom = read_mesh.child(0); + EXPECT_TRUE(dom.has_path("topologies/sample_plane")); + EXPECT_TRUE(dom.has_path("fields/braid")); + EXPECT_EQ(dom["fields/braid/topology"].as_string(), "sample_plane"); + EXPECT_GT(dom["fields/braid/values"].dtype().number_of_elements(), 0); + } +} + //----------------------------------------------------------------------------- int main(int argc, char* argv[]) @@ -144,4 +239,3 @@ int main(int argc, char* argv[]) return result; } - diff --git a/src/tests/ascent/t_ascent_sample.cpp b/src/tests/ascent/t_ascent_sample.cpp index 5c5989bd8..835082162 100644 --- a/src/tests/ascent/t_ascent_sample.cpp +++ b/src/tests/ascent/t_ascent_sample.cpp @@ -17,8 +17,10 @@ #include #include +#include #include +#include #include "t_config.hpp" #include "t_utils.hpp" @@ -33,6 +35,123 @@ using namespace ascent; index_t EXAMPLE_MESH_SIDE_DIM = 20; +namespace +{ + +//----------------------------------------------------------------------------- +index_t +add_sample_sphere_topology(Node &data, + const std::string &topo_name, + const std::string &coordset_name, + const double radius, + const int num_lat, + const int num_lon) +{ + std::vector x_vals; + std::vector y_vals; + std::vector z_vals; + std::vector conn; + + const double pi = acos(-1.0); + + x_vals.push_back(0.0); + y_vals.push_back(0.0); + z_vals.push_back(radius); + + for(int lat = 1; lat < num_lat; ++lat) + { + const double theta = pi * static_cast(lat) / + static_cast(num_lat); + const double sin_theta = sin(theta); + const double cos_theta = cos(theta); + + for(int lon = 0; lon < num_lon; ++lon) + { + const double phi = 2.0 * pi * static_cast(lon) / + static_cast(num_lon); + x_vals.push_back(radius * sin_theta * cos(phi)); + y_vals.push_back(radius * sin_theta * sin(phi)); + z_vals.push_back(radius * cos_theta); + } + } + + const int32 south_pole = static_cast(x_vals.size()); + x_vals.push_back(0.0); + y_vals.push_back(0.0); + z_vals.push_back(-radius); + + auto ring_index = [num_lon](const int lat, const int lon) + { + return static_cast(1 + (lat - 1) * num_lon + + (lon + num_lon) % num_lon); + }; + + for(int lon = 0; lon < num_lon; ++lon) + { + conn.push_back(0); + conn.push_back(ring_index(1, lon)); + conn.push_back(ring_index(1, lon + 1)); + } + + for(int lat = 1; lat < num_lat - 1; ++lat) + { + for(int lon = 0; lon < num_lon; ++lon) + { + const int32 lower_left = ring_index(lat, lon); + const int32 lower_right = ring_index(lat, lon + 1); + const int32 upper_left = ring_index(lat + 1, lon); + const int32 upper_right = ring_index(lat + 1, lon + 1); + + conn.push_back(lower_left); + conn.push_back(upper_left); + conn.push_back(lower_right); + + conn.push_back(lower_right); + conn.push_back(upper_left); + conn.push_back(upper_right); + } + } + + for(int lon = 0; lon < num_lon; ++lon) + { + conn.push_back(ring_index(num_lat - 1, lon)); + conn.push_back(south_pole); + conn.push_back(ring_index(num_lat - 1, lon + 1)); + } + + Node &coords = data["coordsets/" + coordset_name]; + coords["type"] = "explicit"; + coords["values/x"].set(DataType::float64(x_vals.size())); + coords["values/y"].set(DataType::float64(y_vals.size())); + coords["values/z"].set(DataType::float64(z_vals.size())); + + float64_array xs = coords["values/x"].value(); + float64_array ys = coords["values/y"].value(); + float64_array zs = coords["values/z"].value(); + for(index_t i = 0; i < static_cast(x_vals.size()); ++i) + { + xs[i] = x_vals[i]; + ys[i] = y_vals[i]; + zs[i] = z_vals[i]; + } + + Node &topo = data["topologies/" + topo_name]; + topo["type"] = "unstructured"; + topo["coordset"] = coordset_name; + topo["elements/shape"] = "tri"; + topo["elements/connectivity"].set(DataType::int32(conn.size())); + + int32_array topo_conn = topo["elements/connectivity"].value(); + for(index_t i = 0; i < static_cast(conn.size()); ++i) + { + topo_conn[i] = conn[i]; + } + + return static_cast(x_vals.size()); +} + +} // namespace + //----------------------------------------------------------------------------- TEST(ascent_sample, line_2d) { @@ -535,6 +654,202 @@ TEST(ascent_sample, plane) ASCENT_ACTIONS_DUMP(actions,output_file,msg); } +//----------------------------------------------------------------------------- +TEST(ascent_sample, topology_plane) +{ + Node n; + ascent::about(n); + // only run this test if ascent was built with viskores support + if(n["runtimes/ascent/viskores/status"].as_string() == "disabled") + { + ASCENT_INFO("Ascent viskores support disabled, skipping test"); + return; + } + + Node data, plane, verify_info; + conduit::blueprint::mesh::examples::braid("hexs", + EXAMPLE_MESH_SIDE_DIM, + EXAMPLE_MESH_SIDE_DIM, + EXAMPLE_MESH_SIDE_DIM, + data); + conduit::blueprint::mesh::examples::braid("quads", 5, 5, 0, plane); + + data["topologies/sample_plane"] = plane["topologies/mesh"]; + data["topologies/sample_plane/coordset"] = "sample_plane_coords"; + data["coordsets/sample_plane_coords"] = plane["coordsets/coords"]; + EXPECT_TRUE(conduit::blueprint::mesh::verify(data, verify_info)); + + ASCENT_INFO("Testing sampling with a topology from the input dataset"); + + string output_path = prepare_output_dir(); + string output_file = conduit::utils::join_file_path(output_path, + "tout_sample_topology_plane"); + string output_root = output_file + ".cycle_000100.root"; + remove_test_image(output_file); + if(conduit::utils::is_file(output_root)) + { + conduit::utils::remove_file(output_root); + } + + data["state/cycle"] = 100; + data["state/domain_id"] = 0; + + std::string acts_str = R"xyzxyz( +- + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "sample" + params: + fields: ["braid"] + topology: "sample_plane" + invalid_value: -10.0 +- + action: "add_extracts" + extracts: + e1: + pipeline: pl1 + type: "relay" + params: + protocol: "hdf5" +- + action: "add_scenes" + scenes: + s1: + plots: + p1: + type: "pseudocolor" + field: "braid" + pipeline: pl1 +)xyzxyz"; + conduit::Node actions; + actions.parse(acts_str,"yaml"); + actions[1]["extracts/e1/params/path"] = output_file; + actions[2]["scenes/s1/image_prefix"] = output_file; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + EXPECT_TRUE(conduit::utils::is_file(output_root)); + + Node read_mesh, read_verify_info; + conduit::relay::io::blueprint::load_mesh(output_root, read_mesh); + EXPECT_TRUE(conduit::blueprint::mesh::verify(read_mesh, read_verify_info)); + EXPECT_EQ(conduit::blueprint::mesh::number_of_domains(read_mesh), 1); + + const Node &dom = read_mesh.child(0); + EXPECT_TRUE(dom.has_path("topologies/sample_plane")); + EXPECT_TRUE(dom.has_path("fields/braid")); + EXPECT_EQ(dom["fields/braid/topology"].as_string(), "sample_plane"); + EXPECT_GT(dom["fields/braid/values"].dtype().number_of_elements(), 0); + EXPECT_TRUE(check_test_image(output_file)); + + std::string msg = "An example of using the sample filter to sample on a topology from the input dataset."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + +//----------------------------------------------------------------------------- +TEST(ascent_sample, topology_sphere) +{ + Node n; + ascent::about(n); + // only run this test if ascent was built with viskores support + if(n["runtimes/ascent/viskores/status"].as_string() == "disabled") + { + ASCENT_INFO("Ascent viskores support disabled, skipping test"); + return; + } + + Node data, verify_info; + conduit::blueprint::mesh::examples::braid("hexs", + EXAMPLE_MESH_SIDE_DIM, + EXAMPLE_MESH_SIDE_DIM, + EXAMPLE_MESH_SIDE_DIM, + data); + const index_t num_sphere_points = + add_sample_sphere_topology(data, "sample_sphere", "sample_sphere_coords", + 4.0, 6, 12); + EXPECT_TRUE(conduit::blueprint::mesh::verify(data, verify_info)); + + ASCENT_INFO("Testing sampling with a spherical surface topology from the input dataset"); + + string output_path = prepare_output_dir(); + string output_file = conduit::utils::join_file_path(output_path, + "tout_sample_topology_sphere"); + string output_root = output_file + ".cycle_000100.root"; + remove_test_image(output_file); + if(conduit::utils::is_file(output_root)) + { + conduit::utils::remove_file(output_root); + } + + data["state/cycle"] = 100; + data["state/domain_id"] = 0; + + std::string acts_str = R"xyzxyz( +- + action: "add_pipelines" + pipelines: + pl1: + f1: + type: "sample" + params: + fields: ["braid"] + topology: "sample_sphere" + invalid_value: -10.0 +- + action: "add_extracts" + extracts: + e1: + pipeline: pl1 + type: "relay" + params: + protocol: "hdf5" +- + action: "add_scenes" + scenes: + s1: + plots: + p1: + type: "pseudocolor" + field: "braid" + pipeline: pl1 +)xyzxyz"; + conduit::Node actions; + actions.parse(acts_str,"yaml"); + actions[1]["extracts/e1/params/path"] = output_file; + actions[2]["scenes/s1/image_prefix"] = output_file; + + Ascent ascent; + ascent.open(); + ascent.publish(data); + ascent.execute(actions); + ascent.close(); + + EXPECT_TRUE(conduit::utils::is_file(output_root)); + + Node read_mesh, read_verify_info; + conduit::relay::io::blueprint::load_mesh(output_root, read_mesh); + EXPECT_TRUE(conduit::blueprint::mesh::verify(read_mesh, read_verify_info)); + EXPECT_EQ(conduit::blueprint::mesh::number_of_domains(read_mesh), 1); + + const Node &dom = read_mesh.child(0); + EXPECT_TRUE(dom.has_path("topologies/sample_sphere")); + EXPECT_TRUE(dom.has_path("coordsets/sample_sphere_coords")); + EXPECT_TRUE(dom.has_path("fields/braid")); + EXPECT_EQ(dom["fields/braid/topology"].as_string(), "sample_sphere"); + EXPECT_EQ(dom["fields/braid/values"].dtype().number_of_elements(), + num_sphere_points); + EXPECT_TRUE(check_test_image(output_file)); + + std::string msg = "An example of using the sample filter to sample on a sphere surface topology from the input dataset."; + ASCENT_ACTIONS_DUMP(actions,output_file,msg); +} + //----------------------------------------------------------------------------- TEST(ascent_sample, test_uniform_grid_slice_along_y)